From b21fb3b96187bf27b10d9df4fe088ea1d5c573f5 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Sun, 2 Feb 2025 20:01:14 -0500 Subject: [PATCH 01/14] Inventory plugin Report all devices which are part of the fabric. --- plugins/inventory/aci.py | 187 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 plugins/inventory/aci.py diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py new file mode 100644 index 000000000..f349c587d --- /dev/null +++ b/plugins/inventory/aci.py @@ -0,0 +1,187 @@ +# Copyright (c) 2025 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' + name: aci + short_description: Cisco aci inventory plugin + extends_documentation_fragment: + - cisco.aci.aci + - constructed + description: + - Query details from APIC + - Requires a YAML configuration file whose name ends with 'cisco_aci.(yml|yaml)' +''' + +EXAMPLES = ''' +--- +plugin: cisco.aci.aci +host: 192.168.1.90 +username: admin +# You can also use env var ACI_PASSWORD +#password: ******* +validate_certs: false +state: query + +keyed_groups: + - prefix: role + key: role +''' + +import os +import atexit +import time +import tempfile +import shutil +import typing as t +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec +from ansible.module_utils.common.arg_spec import ArgumentSpecValidator +from ansible.module_utils.common.text.converters import to_native +from ansible.errors import AnsibleError +from ansible.utils.display import Display + +display = Display() + + +class MockAnsibleModule(object): + def __init__(self, argument_spec, parameters): + """ Mock AnsibleModule + + This is needed in order to use the aci methods which assume to be working + with a module only. + """ + + self._socket_path = None + self._debug = False + self._diff = False + self._tmpdir = None + self.check_mode = False + self.params = dict() + + validator = ArgumentSpecValidator(argument_spec) + result = validator.validate(parameters) + + if result.error_messages: + display.vvv("Validation failed: {0}".format(", ".join(result.error_messages))) + + self.params = result.validated_parameters + + @property + def tmpdir(self): + # if _ansible_tmpdir was not set and we have a remote_tmp, + # the module needs to create it and clean it up once finished. + # otherwise we create our own module tmp dir from the system defaults + if self._tmpdir is None: + basedir = None + + if basedir is not None and not os.path.exists(basedir): + try: + os.makedirs(basedir, mode=0o700) + except (OSError, IOError) as e: + self.warn("Unable to use %s as temporary directory, " + "failing back to system: %s" % (basedir, to_native(e))) + basedir = None + else: + self.warn("Module remote_tmp %s did not exist and was " + "created with a mode of 0700, this may cause" + " issues when running as another user. To " + "avoid this, create the remote_tmp dir with " + "the correct permissions manually" % basedir) + + basefile = "ansible-moduletmp-%s-" % time.time() + try: + tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir) + except (OSError, IOError) as e: + self.fail_json( + msg="Failed to create remote module tmp path at dir %s " + "with prefix %s: %s" % (basedir, basefile, to_native(e)) + ) + atexit.register(shutil.rmtree, tmpdir) + self._tmpdir = tmpdir + + return self._tmpdir + + def warn(self, warning): + display.vvv(warning) + + def fail_json(self, msg, **kwargs) -> t.NoReturn: + raise AnsibleError(msg) + + +class InventoryModule(BaseInventoryPlugin, Constructable): + + NAME = 'cisco.aci.aci' + + def verify_file(self, path): + ''' return true/false if this is possibly a valid file for this plugin to consume ''' + valid = False + if super(InventoryModule, self).verify_file(path): + # base class verifies that file exists and is readable by current user + if path.endswith(('cisco_aci.yaml', 'cisco_aci.yml')): + valid = True + return valid + + def parse(self, inventory, loader, path, cache=True): + + # call base method to ensure properties are available for use with other helper methods + super(InventoryModule, self).parse(inventory, loader, path, cache) + + # this method will parse 'common format' inventory sources and + # update any options declared in DOCUMENTATION as needed + config = self._read_config_data(path) + + argument_spec = aci_argument_spec() + argument_spec.update( + id=dict(type="int", aliases=["controller", "node"]), + state=dict(type="str", default="query", choices=["query"]), + keyed_groups=dict(type="list"), + plugin=dict(type="str"), + ) + + module = MockAnsibleModule( + argument_spec=argument_spec, + parameters=config, + ) + + id = module.params.get("id") + + aci = ACIModule(module) + aci.construct_url(root_class=dict(aci_class="topSystem", target_filter={"id": id})) + + aci.get_existing() + + # parse data and create inventory objects: + for device in aci.existing: + attributes = device.get('topSystem').get('attributes') + self.add_host(attributes['name'], attributes) + + def add_host(self, hostname, host_vars): + self.inventory.add_host(hostname, group='all') + + if host_vars["oobMgmtAddr"] != "0.0.0.0": + self.inventory.set_variable(hostname, + "ansible_host", + host_vars["oobMgmtAddr"]) + elif host_vars["inbMgmtAddr"] != "0.0.0.0": + self.inventory.set_variable(hostname, + "ansible_host", + host_vars["inbMgmtAddr"]) + else: + self.inventory.set_variable(hostname, + "ansible_host", + host_vars["address"]) + + for var_name, var_value in host_vars.items(): + self.inventory.set_variable(hostname, var_name, var_value) + + strict = self.get_option('strict') + + # Add variables created by the user's Jinja2 expressions to the host + self._set_composite_vars(self.get_option('compose'), host_vars, hostname, strict=True) + + # Create user-defined groups using variables and Jinja2 conditionals + self._add_host_to_composed_groups(self.get_option('groups'), host_vars, hostname, strict=strict) + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), host_vars, hostname, strict=strict) From 1b18a1299b2850a61c939a9f0620b49d96450dfe Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 3 Feb 2025 09:45:16 -0500 Subject: [PATCH 02/14] Reformatting to make black ci happy --- plugins/inventory/aci.py | 88 ++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index f349c587d..a83b4ba81 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -1,10 +1,11 @@ # Copyright (c) 2025 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -from __future__ import (absolute_import, division, print_function) +from __future__ import absolute_import, division, print_function + __metaclass__ = type -DOCUMENTATION = r''' +DOCUMENTATION = r""" name: aci short_description: Cisco aci inventory plugin extends_documentation_fragment: @@ -13,9 +14,9 @@ description: - Query details from APIC - Requires a YAML configuration file whose name ends with 'cisco_aci.(yml|yaml)' -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- plugin: cisco.aci.aci host: 192.168.1.90 @@ -28,7 +29,7 @@ keyed_groups: - prefix: role key: role -''' +""" import os import atexit @@ -37,7 +38,10 @@ import shutil import typing as t from ansible.plugins.inventory import BaseInventoryPlugin, Constructable -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + ACIModule, + aci_argument_spec, +) from ansible.module_utils.common.arg_spec import ArgumentSpecValidator from ansible.module_utils.common.text.converters import to_native from ansible.errors import AnsibleError @@ -48,7 +52,7 @@ class MockAnsibleModule(object): def __init__(self, argument_spec, parameters): - """ Mock AnsibleModule + """Mock AnsibleModule This is needed in order to use the aci methods which assume to be working with a module only. @@ -65,7 +69,9 @@ def __init__(self, argument_spec, parameters): result = validator.validate(parameters) if result.error_messages: - display.vvv("Validation failed: {0}".format(", ".join(result.error_messages))) + display.vvv( + "Validation failed: {0}".format(", ".join(result.error_messages)) + ) self.params = result.validated_parameters @@ -81,15 +87,19 @@ def tmpdir(self): try: os.makedirs(basedir, mode=0o700) except (OSError, IOError) as e: - self.warn("Unable to use %s as temporary directory, " - "failing back to system: %s" % (basedir, to_native(e))) + self.warn( + "Unable to use %s as temporary directory, " + "failing back to system: %s" % (basedir, to_native(e)) + ) basedir = None else: - self.warn("Module remote_tmp %s did not exist and was " - "created with a mode of 0700, this may cause" - " issues when running as another user. To " - "avoid this, create the remote_tmp dir with " - "the correct permissions manually" % basedir) + self.warn( + "Module remote_tmp %s did not exist and was " + "created with a mode of 0700, this may cause" + " issues when running as another user. To " + "avoid this, create the remote_tmp dir with " + "the correct permissions manually" % basedir + ) basefile = "ansible-moduletmp-%s-" % time.time() try: @@ -97,7 +107,7 @@ def tmpdir(self): except (OSError, IOError) as e: self.fail_json( msg="Failed to create remote module tmp path at dir %s " - "with prefix %s: %s" % (basedir, basefile, to_native(e)) + "with prefix %s: %s" % (basedir, basefile, to_native(e)) ) atexit.register(shutil.rmtree, tmpdir) self._tmpdir = tmpdir @@ -113,14 +123,14 @@ def fail_json(self, msg, **kwargs) -> t.NoReturn: class InventoryModule(BaseInventoryPlugin, Constructable): - NAME = 'cisco.aci.aci' + NAME = "cisco.aci.aci" def verify_file(self, path): - ''' return true/false if this is possibly a valid file for this plugin to consume ''' + """return true/false if this is possibly a valid file for this plugin to consume""" valid = False if super(InventoryModule, self).verify_file(path): # base class verifies that file exists and is readable by current user - if path.endswith(('cisco_aci.yaml', 'cisco_aci.yml')): + if path.endswith(("cisco_aci.yaml", "cisco_aci.yml")): valid = True return valid @@ -149,39 +159,45 @@ def parse(self, inventory, loader, path, cache=True): id = module.params.get("id") aci = ACIModule(module) - aci.construct_url(root_class=dict(aci_class="topSystem", target_filter={"id": id})) + aci.construct_url( + root_class=dict(aci_class="topSystem", target_filter={"id": id}) + ) aci.get_existing() # parse data and create inventory objects: for device in aci.existing: - attributes = device.get('topSystem').get('attributes') - self.add_host(attributes['name'], attributes) + attributes = device.get("topSystem").get("attributes") + self.add_host(attributes["name"], attributes) def add_host(self, hostname, host_vars): - self.inventory.add_host(hostname, group='all') + self.inventory.add_host(hostname, group="all") if host_vars["oobMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable(hostname, - "ansible_host", - host_vars["oobMgmtAddr"]) + self.inventory.set_variable( + hostname, "ansible_host", host_vars["oobMgmtAddr"] + ) elif host_vars["inbMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable(hostname, - "ansible_host", - host_vars["inbMgmtAddr"]) + self.inventory.set_variable( + hostname, "ansible_host", host_vars["inbMgmtAddr"] + ) else: - self.inventory.set_variable(hostname, - "ansible_host", - host_vars["address"]) + self.inventory.set_variable(hostname, "ansible_host", host_vars["address"]) for var_name, var_value in host_vars.items(): self.inventory.set_variable(hostname, var_name, var_value) - strict = self.get_option('strict') + strict = self.get_option("strict") # Add variables created by the user's Jinja2 expressions to the host - self._set_composite_vars(self.get_option('compose'), host_vars, hostname, strict=True) + self._set_composite_vars( + self.get_option("compose"), host_vars, hostname, strict=True + ) # Create user-defined groups using variables and Jinja2 conditionals - self._add_host_to_composed_groups(self.get_option('groups'), host_vars, hostname, strict=strict) - self._add_host_to_keyed_groups(self.get_option('keyed_groups'), host_vars, hostname, strict=strict) + self._add_host_to_composed_groups( + self.get_option("groups"), host_vars, hostname, strict=strict + ) + self._add_host_to_keyed_groups( + self.get_option("keyed_groups"), host_vars, hostname, strict=strict + ) From 1d372d8a22f77b86a7e787d7e10bb12de9152f2b Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 3 Feb 2025 09:51:43 -0500 Subject: [PATCH 03/14] re-run black with line length corrected. --- plugins/inventory/aci.py | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index a83b4ba81..5e4d4dcbb 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -69,9 +69,7 @@ def __init__(self, argument_spec, parameters): result = validator.validate(parameters) if result.error_messages: - display.vvv( - "Validation failed: {0}".format(", ".join(result.error_messages)) - ) + display.vvv("Validation failed: {0}".format(", ".join(result.error_messages))) self.params = result.validated_parameters @@ -87,10 +85,7 @@ def tmpdir(self): try: os.makedirs(basedir, mode=0o700) except (OSError, IOError) as e: - self.warn( - "Unable to use %s as temporary directory, " - "failing back to system: %s" % (basedir, to_native(e)) - ) + self.warn("Unable to use %s as temporary directory, " "failing back to system: %s" % (basedir, to_native(e))) basedir = None else: self.warn( @@ -105,10 +100,7 @@ def tmpdir(self): try: tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir) except (OSError, IOError) as e: - self.fail_json( - msg="Failed to create remote module tmp path at dir %s " - "with prefix %s: %s" % (basedir, basefile, to_native(e)) - ) + self.fail_json(msg="Failed to create remote module tmp path at dir %s " "with prefix %s: %s" % (basedir, basefile, to_native(e))) atexit.register(shutil.rmtree, tmpdir) self._tmpdir = tmpdir @@ -159,9 +151,7 @@ def parse(self, inventory, loader, path, cache=True): id = module.params.get("id") aci = ACIModule(module) - aci.construct_url( - root_class=dict(aci_class="topSystem", target_filter={"id": id}) - ) + aci.construct_url(root_class=dict(aci_class="topSystem", target_filter={"id": id})) aci.get_existing() @@ -174,13 +164,9 @@ def add_host(self, hostname, host_vars): self.inventory.add_host(hostname, group="all") if host_vars["oobMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable( - hostname, "ansible_host", host_vars["oobMgmtAddr"] - ) + self.inventory.set_variable(hostname, "ansible_host", host_vars["oobMgmtAddr"]) elif host_vars["inbMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable( - hostname, "ansible_host", host_vars["inbMgmtAddr"] - ) + self.inventory.set_variable(hostname, "ansible_host", host_vars["inbMgmtAddr"]) else: self.inventory.set_variable(hostname, "ansible_host", host_vars["address"]) @@ -190,14 +176,8 @@ def add_host(self, hostname, host_vars): strict = self.get_option("strict") # Add variables created by the user's Jinja2 expressions to the host - self._set_composite_vars( - self.get_option("compose"), host_vars, hostname, strict=True - ) + self._set_composite_vars(self.get_option("compose"), host_vars, hostname, strict=True) # Create user-defined groups using variables and Jinja2 conditionals - self._add_host_to_composed_groups( - self.get_option("groups"), host_vars, hostname, strict=strict - ) - self._add_host_to_keyed_groups( - self.get_option("keyed_groups"), host_vars, hostname, strict=strict - ) + self._add_host_to_composed_groups(self.get_option("groups"), host_vars, hostname, strict=strict) + self._add_host_to_keyed_groups(self.get_option("keyed_groups"), host_vars, hostname, strict=strict) From 8a0b95be045d8e370944c1441e3ad17b9eb812af Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 3 Feb 2025 09:54:57 -0500 Subject: [PATCH 04/14] Minor comment formating issue --- plugins/inventory/aci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index 5e4d4dcbb..753be0b2b 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -22,7 +22,7 @@ host: 192.168.1.90 username: admin # You can also use env var ACI_PASSWORD -#password: ******* +# password: ******* validate_certs: false state: query From 1eb6bf460aa2c638e527ec1da028ea30c4020271 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 3 Feb 2025 14:41:45 -0500 Subject: [PATCH 05/14] Updates based on review feedback --- plugins/inventory/aci.py | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index 753be0b2b..03733cd12 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -13,6 +13,7 @@ - constructed description: - Query details from APIC + - Gets details on all spines and leafs behind the controller. - Requires a YAML configuration file whose name ends with 'cisco_aci.(yml|yaml)' """ @@ -21,17 +22,14 @@ plugin: cisco.aci.aci host: 192.168.1.90 username: admin -# You can also use env var ACI_PASSWORD -# password: ******* +password: PASSWORD validate_certs: false -state: query keyed_groups: - prefix: role key: role """ -import os import atexit import time import tempfile @@ -81,21 +79,6 @@ def tmpdir(self): if self._tmpdir is None: basedir = None - if basedir is not None and not os.path.exists(basedir): - try: - os.makedirs(basedir, mode=0o700) - except (OSError, IOError) as e: - self.warn("Unable to use %s as temporary directory, " "failing back to system: %s" % (basedir, to_native(e))) - basedir = None - else: - self.warn( - "Module remote_tmp %s did not exist and was " - "created with a mode of 0700, this may cause" - " issues when running as another user. To " - "avoid this, create the remote_tmp dir with " - "the correct permissions manually" % basedir - ) - basefile = "ansible-moduletmp-%s-" % time.time() try: tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir) @@ -134,6 +117,7 @@ def parse(self, inventory, loader, path, cache=True): # this method will parse 'common format' inventory sources and # update any options declared in DOCUMENTATION as needed config = self._read_config_data(path) + config.update(state="query") argument_spec = aci_argument_spec() argument_spec.update( @@ -157,18 +141,19 @@ def parse(self, inventory, loader, path, cache=True): # parse data and create inventory objects: for device in aci.existing: - attributes = device.get("topSystem").get("attributes") - self.add_host(attributes["name"], attributes) + attributes = device.get("topSystem", {}).get("attributes") + if attributes.get("name"): + self.add_host(attributes.get("name"), attributes) def add_host(self, hostname, host_vars): self.inventory.add_host(hostname, group="all") - if host_vars["oobMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable(hostname, "ansible_host", host_vars["oobMgmtAddr"]) - elif host_vars["inbMgmtAddr"] != "0.0.0.0": - self.inventory.set_variable(hostname, "ansible_host", host_vars["inbMgmtAddr"]) + if host_vars.get("oobMgmtAddr", "0.0.0.0") != "0.0.0.0": + self.inventory.set_variable(hostname, "ansible_host", host_vars.get("oobMgmtAddr")) + elif host_vars.get("inbMgmtAddr", "0.0.0.0") != "0.0.0.0": + self.inventory.set_variable(hostname, "ansible_host", host_vars.get("inbMgmtAddr")) else: - self.inventory.set_variable(hostname, "ansible_host", host_vars["address"]) + self.inventory.set_variable(hostname, "ansible_host", host_vars.get("address")) for var_name, var_value in host_vars.items(): self.inventory.set_variable(hostname, var_name, var_value) From f0e99a14c87ab0ce603a1f42011b1888f36c7534 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 3 Feb 2025 14:49:41 -0500 Subject: [PATCH 06/14] No need to filter on id for inventory plugin --- plugins/inventory/aci.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index 03733cd12..f5d53c371 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -132,10 +132,8 @@ def parse(self, inventory, loader, path, cache=True): parameters=config, ) - id = module.params.get("id") - aci = ACIModule(module) - aci.construct_url(root_class=dict(aci_class="topSystem", target_filter={"id": id})) + aci.construct_url(root_class=dict(aci_class="topSystem")) aci.get_existing() From a268c670bbfa2044e28e3e9b0a2d2510af29c9d0 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 08:21:37 -0500 Subject: [PATCH 07/14] minor cleanup on tmpdir method --- plugins/inventory/aci.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index f5d53c371..6ca57a81e 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -77,13 +77,11 @@ def tmpdir(self): # the module needs to create it and clean it up once finished. # otherwise we create our own module tmp dir from the system defaults if self._tmpdir is None: - basedir = None - basefile = "ansible-moduletmp-%s-" % time.time() try: - tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir) + tmpdir = tempfile.mkdtemp(prefix=basefile) except (OSError, IOError) as e: - self.fail_json(msg="Failed to create remote module tmp path at dir %s " "with prefix %s: %s" % (basedir, basefile, to_native(e))) + self.fail_json(msg="Failed to create remote module tmp path with prefix %s: %s" % (basefile, to_native(e))) atexit.register(shutil.rmtree, tmpdir) self._tmpdir = tmpdir From b33f90544f54961dd534aaeec83c907a72e1ab26 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 10:08:42 -0500 Subject: [PATCH 08/14] Remove unused defaults and fix syntax on attribute lookup --- plugins/inventory/aci.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index 6ca57a81e..62751e2c9 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -115,12 +115,9 @@ def parse(self, inventory, loader, path, cache=True): # this method will parse 'common format' inventory sources and # update any options declared in DOCUMENTATION as needed config = self._read_config_data(path) - config.update(state="query") argument_spec = aci_argument_spec() argument_spec.update( - id=dict(type="int", aliases=["controller", "node"]), - state=dict(type="str", default="query", choices=["query"]), keyed_groups=dict(type="list"), plugin=dict(type="str"), ) @@ -137,7 +134,7 @@ def parse(self, inventory, loader, path, cache=True): # parse data and create inventory objects: for device in aci.existing: - attributes = device.get("topSystem", {}).get("attributes") + attributes = device.get("topSystem", {}).get("attributes", {}) if attributes.get("name"): self.add_host(attributes.get("name"), attributes) From f4588a769ec233ede7e6b1ede8dd29a370e8b7ba Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 10:54:33 -0500 Subject: [PATCH 09/14] Rename module and clean up argument spec Renamed the module to cisco.aci.aci_inventory and remove duplicate arg specs that we already get from including constructed doc_fragment. --- plugins/inventory/aci.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci.py index 62751e2c9..fcc6d3b66 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci.py @@ -6,7 +6,7 @@ __metaclass__ = type DOCUMENTATION = r""" - name: aci + name: aci_inventory short_description: Cisco aci inventory plugin extends_documentation_fragment: - cisco.aci.aci @@ -19,7 +19,7 @@ EXAMPLES = """ --- -plugin: cisco.aci.aci +plugin: cisco.aci.aci_inventory host: 192.168.1.90 username: admin password: PASSWORD @@ -96,7 +96,7 @@ def fail_json(self, msg, **kwargs) -> t.NoReturn: class InventoryModule(BaseInventoryPlugin, Constructable): - NAME = "cisco.aci.aci" + NAME = "cisco.aci.aci_inventory" def verify_file(self, path): """return true/false if this is possibly a valid file for this plugin to consume""" @@ -117,10 +117,6 @@ def parse(self, inventory, loader, path, cache=True): config = self._read_config_data(path) argument_spec = aci_argument_spec() - argument_spec.update( - keyed_groups=dict(type="list"), - plugin=dict(type="str"), - ) module = MockAnsibleModule( argument_spec=argument_spec, From c5b0c2d54ee00aca257534aa47e262110134e26e Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 12:04:02 -0500 Subject: [PATCH 10/14] Rename aci inventory module --- plugins/inventory/{aci.py => aci_inventory.py} | 3 --- 1 file changed, 3 deletions(-) rename plugins/inventory/{aci.py => aci_inventory.py} (96%) diff --git a/plugins/inventory/aci.py b/plugins/inventory/aci_inventory.py similarity index 96% rename from plugins/inventory/aci.py rename to plugins/inventory/aci_inventory.py index fcc6d3b66..fdb83eade 100644 --- a/plugins/inventory/aci.py +++ b/plugins/inventory/aci_inventory.py @@ -73,9 +73,6 @@ def __init__(self, argument_spec, parameters): @property def tmpdir(self): - # if _ansible_tmpdir was not set and we have a remote_tmp, - # the module needs to create it and clean it up once finished. - # otherwise we create our own module tmp dir from the system defaults if self._tmpdir is None: basefile = "ansible-moduletmp-%s-" % time.time() try: From 034b6ef318cb5652e93297242f920e5b3a3552b8 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 13:53:37 -0500 Subject: [PATCH 11/14] Include additional example --- plugins/inventory/aci_inventory.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugins/inventory/aci_inventory.py b/plugins/inventory/aci_inventory.py index fdb83eade..67a213876 100644 --- a/plugins/inventory/aci_inventory.py +++ b/plugins/inventory/aci_inventory.py @@ -19,12 +19,32 @@ EXAMPLES = """ --- +# Generate inventory and put devices into groups based on role: spine, leaf, controller + +plugin: cisco.aci.aci_inventory +host: 192.168.1.90 +username: admin +password: PASSWORD +validate_certs: false + +keyed_groups: + - prefix: role + key: role + +--- +# Generate inventory and use the compose variables to define how we want to connect + plugin: cisco.aci.aci_inventory host: 192.168.1.90 username: admin password: PASSWORD validate_certs: false +compose: + ansible_connection: "'ansible.netcommon.httpapi'" + ansible_network_os: "'cisco.aci.aci'" + ansible_host: "'192.168.1.90'" + keyed_groups: - prefix: role key: role From 93ee293ddea0d19d785490c7e1ed78fc6902f5fb Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Tue, 4 Feb 2025 14:52:02 -0500 Subject: [PATCH 12/14] fix sanity test issue with examples --- plugins/inventory/aci_inventory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/inventory/aci_inventory.py b/plugins/inventory/aci_inventory.py index 67a213876..bbe26bb31 100644 --- a/plugins/inventory/aci_inventory.py +++ b/plugins/inventory/aci_inventory.py @@ -31,7 +31,6 @@ - prefix: role key: role ---- # Generate inventory and use the compose variables to define how we want to connect plugin: cisco.aci.aci_inventory From 1efa602ce491b82160b17b77fb0c3ab3d68e48c7 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Wed, 5 Feb 2025 07:53:04 -0500 Subject: [PATCH 13/14] Remove duplicate keys in examples --- plugins/inventory/aci_inventory.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/plugins/inventory/aci_inventory.py b/plugins/inventory/aci_inventory.py index bbe26bb31..8be842caa 100644 --- a/plugins/inventory/aci_inventory.py +++ b/plugins/inventory/aci_inventory.py @@ -19,34 +19,23 @@ EXAMPLES = """ --- -# Generate inventory and put devices into groups based on role: spine, leaf, controller - +# Generate dynamic inventory of every device plugin: cisco.aci.aci_inventory host: 192.168.1.90 username: admin password: PASSWORD validate_certs: false +# (Optional) Generate inventory and put devices into groups based on role: spine, leaf, controller keyed_groups: - prefix: role key: role -# Generate inventory and use the compose variables to define how we want to connect - -plugin: cisco.aci.aci_inventory -host: 192.168.1.90 -username: admin -password: PASSWORD -validate_certs: false - +# (Optional) Generate inventory and use the compose variables to define how we want to connect compose: ansible_connection: "'ansible.netcommon.httpapi'" ansible_network_os: "'cisco.aci.aci'" ansible_host: "'192.168.1.90'" - -keyed_groups: - - prefix: role - key: role """ import atexit From bc082d5fec1439470e5f7a59dc2607dc69d49827 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Thu, 13 Feb 2025 08:35:26 -0500 Subject: [PATCH 14/14] Update plugins/inventory/aci_inventory.py Co-authored-by: Samita B --- plugins/inventory/aci_inventory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inventory/aci_inventory.py b/plugins/inventory/aci_inventory.py index 8be842caa..f45377855 100644 --- a/plugins/inventory/aci_inventory.py +++ b/plugins/inventory/aci_inventory.py @@ -7,7 +7,7 @@ DOCUMENTATION = r""" name: aci_inventory - short_description: Cisco aci inventory plugin + short_description: Cisco ACI inventory plugin extends_documentation_fragment: - cisco.aci.aci - constructed