# Error while Trying to run a dynamic inventory script from ldap source,

**URL:** <https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031>\
**Category:** Ansible Project\
**Tags:** windows\
**Created:** [October 14, 2020, 8:27am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031 "2020-10-14T08:27:03Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 14, 2020, 8:27am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/1 "2020-10-14T08:27:03Z")

</div>

when I run the script with python on command line, I get the json output, however when I run it with ansible-inventory or ansible-playbook I hit the following error

ansible-inventory 2.9.6  
config file = /Users/sm/ansible\_liquibase/ansible.cfg  
configured module search path = [‘/Users/sm/.ansible/plugins/modules’, ‘/usr/share/ansible/plugins/modules’]  
ansible python module location = /usr/local/lib/python3.7/site-packages/ansible  
executable location = /usr/local/bin/ansible-inventory  
python version = 3.7.7 (default, Mar 10 2020, 15:43:03) [Clang 11.0.0 (clang-1100.0.33.17)]  
Using /Users/sm/ansible\_liquibase/ansible.cfg as config file  
setting up inventory plugins  
[WARNING]: \* Failed to parse /Users/sm/ansible\_liquibase/get\_branches.py with script plugin: failed to parse executable inventory script results from  
/Users/sm/ansible\_liquibase/get\_branches.py: Syntax Error while loading YAML. mapping values are not allowed here The error appears to be in  
‘’: line 3, column 10, but may be elsewhere in the file depending on the exact syntax problem.  
File “/usr/local/lib/python3.7/site-packages/ansible/inventory/manager.py”, line 280, in parse\_source  
plugin.parse(self.\_inventory, self.\_loader, source, cache=cache)  
**File “/usr/local/lib/python3.7/site-packages/ansible/plugins/inventory/script.py”, line 161, in parse**  
**raise AnsibleParserError(to\_native(e))**  
[WARNING]: Unable to parse /Users/smohamme/ansible\_liquibase/get\_branches.py as an inventory source  
[WARNING]: No inventory was parsed, only implicit localhost is available  
{  
“\_meta”: {  
“hostvars”: {}  
},  
“all”: {  
“children”: [  
“ungrouped”  
]  
}  
}

when I run with python, I get the following output ,

{  
“\_meta”: {  
“hostvars”: {  
“[abc.fastnet.com](http://abc.fastnet.com)”: {  
“name”: “[abc.fastnet.com](http://abc.fastnet.com)”,  
“cn”: “abc”,  
“osname”: “Windows Server 2016 Standard”  
},  
“[xyz.fastnet.com](http://xyz.fastnet.com)”: {  
“name”: “[xyz.fastnet.com](http://xyz.fastnet.com)”,  
“cn”: “xyz”,  
“osname”: “Windows Server 2016 Standard”  
}  
}  
},  
“non-production”: {  
“hosts”: [  
“[abc.fastnet.com](http://abc.fastnet.com)”,  
“[xyz.fastnet.com](http://xyz.fastnet.com)”  
],  
“vars”: {},  
“children”:   
}  
}

any help would be appreciated.

Thanks.

---

<div class="post-metadata">

**Author:** ![Dick\_Visser](https://avatars.discourse-cdn.com/v4/letter/d/d9b06d/32.png) [@Dick\_Visser](https://forum.ansible.com/u/Dick_Visser)\
**Post date:** [October 14, 2020, 8:48am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/2 "2020-10-14T08:48:17Z")

</div>

Maybe the python environments are different between the manually run  
script and the ansible run one?  
What does the get\_branches.py look like?

---

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 14, 2020, 9:04am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/3 "2020-10-14T09:04:20Z")

</div>

It starts with a shebang, and it is parsing a .ini config file, below is the code,

#!/usr/bin/env python3

import os  
import sys  
import argparse  
import json  
import logging  
import configparser  
import re  
try:  
import ldap3  
except ImportError:  
print(‘Could not import 'ldap3' module.’)  
print(‘Please ensure 'ldap3' module is installed.’)  
sys.exit(1)  
import ansible\_vault  
from ansible\_vault import Vault  
from ansible import constants as C  
from ansible.parsing.vault import VaultLib  
from ansible.cli import CLI  
from ansible.parsing.dataloader import DataLoader

# Configure fallback\_args so you don’t have to pass any commandline arguments in, or alternatively

# rely on environmental variables (Takes precedence over explicitly defined options), eg:

# user = os.getenv(‘LDAP\_PASS’,‘mypassword123!’)

# fallback\_args = dict(

# ldapuri = os.getenv(‘LDAP\_URI’,‘[fastenal.com](http://fastenal.com)’),

# user = os.getenv(‘LDAP\_USER’,‘ansibledadquery’),

# password = os.getenv(‘LDAP\_PASS’,‘mysupersecretamazingpasswordhere’),

# )

class AnsibleInventoryLDAP(object):

def **init** (self):

directory = os.path.dirname(os.path.abspath( **file** ))  
print(directory)  
configfile = directory + ‘/ldap-ad.ini’  
config = configparser.ConfigParser()  
config.read(configfile)  
username = config.get(‘ldap-ad’, ‘username’)

# password = config.get(‘ldap-ad’, ‘password’)

basedn = config.get(‘ldap-ad’, ‘basedn’)  
ldapuri = config.get(‘ldap-ad’, ‘ldapuri’)  
port = config.get(‘ldap-ad’, ‘port’)

self.ansible\_inventory = { ‘\_meta’: { ‘hostvars’: { } } }

# Parse arguments passed at cli

self.parse\_arguments()

# Auth against ldap

self.ldap\_auth(username, ldapuri, port)

# Get search results with provided options

if self.args.os != False:  
ldapfilter = “(&(sAMAccountType=805306369)(operatingSystem=%s))” % (self.args.os)  
else:  
ldapfilter = “(&(ObjectCategory=CN=Computer,CN=Schema,CN=Configuration,DC=fastenal,DC=com)(|(name=POSTEST\*)))”

self.ldap\_search(basedn, ldapfilter)

self.build\_hierarchy(basedn)  
print(json.dumps(self.ansible\_inventory, indent=2))

def ldap\_auth(self, username, ldapuri, port):  
“”“Authenticate to LDAP.”“”

loader = DataLoader()  
vault\_secret = CLI.setup\_vault\_secrets(loader=loader, vault\_ids=C.DEFAULT\_VAULT\_IDENTITY\_LIST)  
loader.set\_vault\_secrets(vault\_secret)  
vault = VaultLib(vault\_secret)  
data = vault.decrypt(open(‘./ldappass.yml’).read())  
server = ldap3.Server(ldapuri, use\_ssl=False)  
conn = ldap3.Connection(server, auto\_bind=True, user=username, password=data, authentication=ldap3.SIMPLE)

# print(conn)

self.conn = conn

def ldap\_search(self, basedn, ldapfilter):  
“”“Search LDAP in given OU.”“”  
self.conn.search(search\_base=basedn,search\_filter=ldapfilter, attributes=[‘cn’, ‘dNSHostName’, ‘operatingSystem’])  
self.searchresults = self.conn.response

def add\_inventory\_entry(self, host=None, group\_name=None, child\_group=None, hostvars=None):

# Force the group name to lowercase

group\_name = group\_name.lower()

# Append the --group-prefix value if one is specified

if self.args.group\_prefix != False:  
group\_name = self.args.group\_prefix + group\_name

# If the group doesn’t exist, then create it

if group\_name not in self.ansible\_inventory.keys():  
self.ansible\_inventory[group\_name] = { ‘hosts’: , ‘vars’: { }, ‘children’: }

# Add the ‘children’ value if --no-children wasn’t specified

if child\_group != None and not self.args.no\_children:  
child\_group = child\_group.lower()  
if self.args.group\_prefix != False:  
child\_group = self.args.group\_prefix + child\_group

if child\_group not in self.ansible\_inventory[group\_name][‘children’]:  
self.ansible\_inventory[group\_name][‘children’].append(child\_group)

# Add the host if a host was passed

if host != None:

# The host should never get added twice anyway, but we’ll add this as a safeguard

if host not in self.ansible\_inventory[group\_name][‘hosts’]:  
self.ansible\_inventory[group\_name][‘hosts’].append(host)

# And add the hostvars for the host to the \_meta dict

if hostvars != None:  
self.ansible\_inventory[‘\_meta’][‘hostvars’][host] = hostvars

def build\_hierarchy(self, basedn):  
“”“We want to build the hierarchy of OUs and child OUs by traversing the ldap tree via.  
the host’s distinguished name and linking each sub-OU as a ‘child’.”“”

# basedn = self.args.basedn

hostvars = {}  
basedn\_list = (re.sub(r"..=“, “”, basedn)).split(”,")  
for computer in self.searchresults:  
hostvars[‘name’] = computer[‘attributes’][‘dNSHostName’]  
hostvars[‘cn’] = computer[‘attributes’][‘cn’]  
hostvars[‘osname’] = computer[‘attributes’][‘operatingSystem’]

org\_list = (re.sub(r"..=“, “”, computer[‘dn’])).split(”,")

# Remove hostname

del org\_list[0]

# Removes all excess OUs and DC

for count in range(0, (len(basedn\_list)-1)):  
del org\_list[-1]

# Reverse list so top group is first

org\_list.reverse()  
counter\_range = range(0,(len(org\_list)))  
for group\_counter in counter\_range:  
if computer[‘attributes’][‘dNSHostName’]:  
if group\_counter == counter\_range[-1]:  
self.add\_inventory\_entry(group\_name=org\_list[group\_counter], host=hostvars[‘name’], hostvars=hostvars)  
else:  
self.add\_inventory\_entry(group\_name=org\_list[group\_counter], child\_group=org\_list[group\_counter+1])

def parse\_arguments(self):  
parser.add\_argument(‘–recursive’,‘-r’, help=‘Recursively search into sub-OUs’, default=False, action=‘store\_true’)  
parser.add\_argument(‘–no-children’,‘-c’, help=‘Don\t link child OUs as children in the inventory (Stops inheritance).’, default=False, action=‘store\_true’)  
parser.add\_argument(‘–fqdn’, help=‘Output the hosts FQDN, not just host name’, default=False, action=‘store\_true’)  
parser.add\_argument(‘–os’,‘-os’, help=‘Only return hosts matching the OS specified (Uses ldap formatting, so '_windows_').’, default=False)  
parser.add\_argument(‘–group-prefix’, help=‘Prefix all group names.’, default=False)  
args\_hostlist.add\_argument(‘–list’, help=‘List all nodes from specified OU.’, action=‘store\_true’)  
args\_hostlist.add\_argument(‘–host’, help=‘Not implemented.’)

self.args = parser.parse\_args()

if **name** == ‘ **main** ’:

# Instantiate the inventory object

AnsibleInventoryLDAP()

---

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 14, 2020, 9:11am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/4 "2020-10-14T09:11:03Z")

</div>

[https://github.com/ansible/ansible/blob/stable-2.9/lib/ansible/plugins/inventory/script.py](https://github.com/ansible/ansible/blob/stable-2.9/lib/ansible/plugins/inventory/script.py) #L161  
this line does it expects a precise format, as the script is returning a precise json format, not sure what I am doing wrong, to have that exception raised, I have given a proper file path,

ansible-inventory -i ‘/Users/sm/ansible\_liquibase/get\_branches.py’ --list -vvvv

it still goes to default local host, and throws this exception,

ansible-inventory 2.9.6  
config file = /Users/sm/ansible\_liquibase/ansible.cfg  
configured module search path = [‘/Users/sm/.ansible/plugins/modules’, ‘/usr/share/ansible/plugins/modules’]  
ansible python module location = /usr/local/lib/python3.7/site-packages/ansible  
executable location = /usr/local/bin/ansible-inventory  
python version = 3.7.7 (default, Mar 10 2020, 15:43:03) [Clang 11.0.0 (clang-1100.0.33.17)]  
Using /Users/sm/ansible\_liquibase/ansible.cfg as config file  
Reading vault password file: /Users/smohamme/.vault\_pass  
setting up inventory plugins  
[WARNING]: \* Failed to parse /Users/sm/ansible\_liquibase/get\_branches.py with script plugin: Inventory script  
(/Users/sm/ansible\_liquibase/get\_branches.py) had an execution error:  
File “/usr/local/lib/python3.7/site-packages/ansible/inventory/manager.py”, line 280, in parse\_source  
plugin.parse(self.\_inventory, self.\_loader, source, cache=cache)  
File “/usr/local/lib/python3.7/site-packages/ansible/plugins/inventory/script.py”, line 161, in parse  
raise AnsibleParserError(to\_native(e))  
[WARNING]: Unable to parse /Users/sm/ansible\_liquibase/get\_branches.py as an inventory source  
[WARNING]: No inventory was parsed, only implicit localhost is available  
{  
“\_meta”: {  
“hostvars”: {}  
},  
“all”: {  
“children”: [  
“ungrouped”  
]  
}  
}

---

<div class="post-metadata">

**Author:** ![Dick\_Visser](https://avatars.discourse-cdn.com/v4/letter/d/d9b06d/32.png) [@Dick\_Visser](https://forum.ansible.com/u/Dick_Visser)\
**Post date:** [October 14, 2020, 9:12am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/5 "2020-10-14T09:12:36Z")

</div>

ah i think i see, the inventory script needs to be executable - can  
you check that?

---

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 14, 2020, 9:21am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/6 "2020-10-14T09:21:12Z")

</div>

yep I did chmod a+x get\_branch.py

no luck with that too and I did the same to the config file as well,

---

<div class="post-metadata">

**Author:** ![P\_R\_Music\_video\_s](https://avatars.discourse-cdn.com/v4/letter/p/eb8c5e/32.png) [@P\_R\_Music\_video\_s](https://forum.ansible.com/u/P_R_Music_video_s)\
**Post date:** [October 14, 2020, 9:50am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/7 "2020-10-14T09:50:22Z")

</div>

Try to run with the sudo command

---

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 14, 2020, 9:56am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/8 "2020-10-14T09:56:22Z")

</div>

I am running it as sudo,

---

<div class="post-metadata">

**Author:** ![sameer\_khan](https://avatars.discourse-cdn.com/v4/letter/s/ba8739/32.png) [@sameer\_khan](https://forum.ansible.com/u/sameer_khan)\
**Post date:** [October 16, 2020, 7:32am UTC](https://forum.ansible.com/t/error-while-trying-to-run-a-dynamic-inventory-script-from-ldap-source/34031/9 "2020-10-16T07:32:56Z")

</div>

So what is the proper way of configuring the dynamic inventory plugin in the ansible-config ?

my ansible.cfg looks as follows.

[inventory]

# List of enabled inventory plugins and the order in which they are used.

enable\_plugins = auto, yaml

# Ignore these extensions when parsing a directory as inventory source

ignore\_extensions = .pyc, .pyo, .swp, .bak, ~, .rpm, .md, .txt, ~, .orig, .ini, .cfg, .retry

# Default ansible inventory plugin path

inventory = /Users/sm/ansible\_liquibase/inventory\_plugins

when I ran the ansible-config it is looking for the dynamic inventory plugin in these places hence may be not allowing

ansible-config dump | grep DEFAULT\_INVENTORY\_PLUGIN\_PATH  
DEFAULT\_INVENTORY\_PLUGIN\_PATH(default) = [‘/Users/sm/.ansible/plugins/inventory’, ‘/usr/share/ansible/plugins/inventory’]

I am having difficulty understanding which part needs to be configured properly,

Any help would be great.

Thanks.
