From 3f556f2e3725d8f1daa6e70682644cf6c0557ca6 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Thu, 25 Jan 2018 14:29:11 -0500 Subject: [PATCH] adds infoblox dynamic inventory script (#35328) * adds infoblox dynamic inventory script * fix up issues from sanity testing * fix pep8 issues --- .github/BOTMETA.yml | 8 ++ contrib/inventory/infoblox.py | 135 ++++++++++++++++++ contrib/inventory/infoblox.yaml | 24 ++++ .../module_utils/net_tools/nios/api.py | 76 +++++----- 4 files changed, 206 insertions(+), 37 deletions(-) create mode 100755 contrib/inventory/infoblox.py create mode 100644 contrib/inventory/infoblox.yaml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 89be6c7ea47..dde465b72b1 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -898,6 +898,14 @@ files: labels: - cloud - openstack + contrib/inventory/infoblox.py: + keywords: + - infoblox dynamic inventory script + labels: + - ipam + - infoblox + - networking + maintainers: $team_networking contrib/inventory/azure_rm.py: keywords: - azure inventory diff --git a/contrib/inventory/infoblox.py b/contrib/inventory/infoblox.py new file mode 100755 index 00000000000..374a7564d44 --- /dev/null +++ b/contrib/inventory/infoblox.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# +# (c) 2018, Red Hat, Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +import os +import sys +import json +import argparse + +from ansible.parsing.dataloader import DataLoader +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_text +from ansible.module_utils.net_tools.nios.api import get_connector +from ansible.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs + +try: + # disable urllib3 warnings so as to not interfere with printing to stdout + # which is read by ansible + import urllib3 + urllib3.disable_warnings() +except ImportError: + sys.stdout.write('missing required library: urllib3\n') + sys.exit(-1) + + +CONFIG_FILES = [ + '/etc/ansible/infoblox.yaml', + '/etc/ansible/infoblox.yml' +] + + +def parse_args(): + parser = argparse.ArgumentParser() + + parser.add_argument('--list', action='store_true', + help='List host records from NIOS for use in Ansible') + + parser.add_argument('--host', + help='List meta data about single host (not used)') + + return parser.parse_args() + + +def main(): + args = parse_args() + + for config_file in CONFIG_FILES: + if os.path.exists(config_file): + break + else: + sys.stdout.write('unable to locate config file at /etc/ansible/infoblox.yaml\n') + sys.exit(-1) + + try: + loader = DataLoader() + config = loader.load_from_file(config_file) + provider = config.get('provider') or {} + connector = get_connector(**provider) + except Exception as exc: + sys.stdout.write(to_text(exc)) + sys.exit(-1) + + if args.host: + host_filter = {'name': args.host} + else: + host_filter = {} + + config_filters = config.get('filters') + + if config_filters.get('view') is not None: + host_filter['view'] = config_filters['view'] + + if config_filters.get('extattrs'): + extattrs = normalize_extattrs(config_filters['extattrs']) + else: + extattrs = {} + + hostvars = {} + inventory = { + '_meta': { + 'hostvars': hostvars + } + } + + return_fields = ['name', 'view', 'extattrs', 'ipv4addrs'] + + hosts = connector.get_object('record:host', + host_filter, + extattrs=extattrs, + return_fields=return_fields) + + if hosts: + for item in hosts: + view = item['view'] + name = item['name'] + + if view not in inventory: + inventory[view] = {'hosts': []} + + inventory[view]['hosts'].append(name) + + hostvars[name] = { + 'view': view + } + + if item.get('extattrs'): + for key, value in iteritems(flatten_extattrs(item['extattrs'])): + if key.startswith('ansible_'): + hostvars[name][key] = value + else: + if 'extattrs' not in hostvars: + hostvars[name]['extattrs'] = {} + hostvars[name]['extattrs'][key] = value + + sys.stdout.write(json.dumps(inventory, indent=4)) + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/contrib/inventory/infoblox.yaml b/contrib/inventory/infoblox.yaml new file mode 100644 index 00000000000..c1be5324acf --- /dev/null +++ b/contrib/inventory/infoblox.yaml @@ -0,0 +1,24 @@ +--- +# This file provides the configuration information for the Infoblox dynamic +# inventory script that is used to dynamically pull host information from NIOS. +# This file should be copied to /etc/ansible/infoblox.yaml in order for the +# dynamic script to find it. + +# Sets the provider arguments for authenticating to the Infoblox server to +# retrieve inventory hosts. Provider arguments can also be set using +# environment variables. Supported environment variables all start with +# INFOBLOX_{{ name }}. For instance, to set the host provider value, the +# environment variable would be INFOBLOX_HOST. +provider: + host: + username: + password: + +# Filters allow the dynamic inventory script to restrict the set of hosts that +# are returned from the Infoblox server. +filters: + # restrict returned hosts by extensible attributes + extattrs: {} + + # restrict returned hosts to a specified DNS view + view: null diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py index fd1689f011d..8ecc3a450e7 100644 --- a/lib/ansible/module_utils/net_tools/nios/api.py +++ b/lib/ansible/module_utils/net_tools/nios/api.py @@ -75,6 +75,43 @@ def get_connector(*args, **kwargs): return Connector(kwargs) +def normalize_extattrs(value): + ''' Normalize extattrs field to expected format + + The module accepts extattrs as key/value pairs. This method will + transform the key/value pairs into a structure suitable for + sending across WAPI in the format of: + + extattrs: { + key: { + value: + } + } + ''' + return dict([(k, {'value': v}) for k, v in iteritems(value)]) + + +def flatten_extattrs(value): + ''' Flatten the key/value struct for extattrs + + WAPI returns extattrs field as a dict in form of: + + extattrs: { + key: { + value: + } + } + + This method will flatten the structure to: + + extattrs: { + key: value + } + + ''' + return dict([(k, v['value']) for k, v in iteritems(value)]) + + class WapiBase(object): ''' Base class for implementing Infoblox WAPI API ''' @@ -134,7 +171,7 @@ class Wapi(WapiBase): if ib_obj: current_object = ib_obj[0] if 'extattrs' in current_object: - current_object['extattrs'] = self.flatten_extattrs(current_object['extattrs']) + current_object['extattrs'] = flatten_extattrs(current_object['extattrs']) ref = current_object.pop('_ref') else: current_object = obj_filter @@ -151,7 +188,7 @@ class Wapi(WapiBase): modified = not self.compare_objects(current_object, proposed_object) if 'extattrs' in proposed_object: - proposed_object['extattrs'] = self.normalize_extattrs(proposed_object['extattrs']) + proposed_object['extattrs'] = normalize_extattrs(proposed_object['extattrs']) if state == 'present': if ref is None: @@ -206,41 +243,6 @@ class Wapi(WapiBase): 'it using nios_network_view first' % name) return res - def normalize_extattrs(self, value): - ''' Normalize extattrs field to expected format - - The module accepts extattrs as key/value pairs. This method will - transform the key/value pairs into a structure suitable for - sending across WAPI in the format of: - - extattrs: { - key: { - value: - } - } - ''' - return dict([(k, {'value': v}) for k, v in iteritems(value)]) - - def flatten_extattrs(self, value): - ''' Flatten the key/value struct for extattrs - - WAPI returns extattrs field as a dict in form of: - - extattrs: { - key: { - value: - } - } - - This method will flatten the structure to: - - extattrs: { - key: value - } - - ''' - return dict([(k, v['value']) for k, v in iteritems(value)]) - def issubset(self, item, objects): ''' Checks if item is a subset of objects