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

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”: {
“name”: “abc.fastnet.com”,
“cn”: “abc”,
“osname”: “Windows Server 2016 Standard”
},
xyz.fastnet.com”: {
“name”: “xyz.fastnet.com”,
“cn”: “xyz”,
“osname”: “Windows Server 2016 Standard”
}
}
},
“non-production”: {
“hosts”: [
abc.fastnet.com”,
xyz.fastnet.com
],
“vars”: {},
“children”:
}
}

any help would be appreciated.

Thanks.

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

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’),

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()

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”
]
}
}

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

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,

Try to run with the sudo command

I am running it as sudo,

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.