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