-
Notifications
You must be signed in to change notification settings - Fork 103
Inventory plugin (DCNE-302) #721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
p3ck
wants to merge
14
commits into
CiscoDevNet:master
Choose a base branch
from
p3ck:inventory
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
b21fb3b
Inventory plugin
p3ck 1b18a12
Reformatting to make black ci happy
p3ck 1d372d8
re-run black with line length corrected.
p3ck 8a0b95b
Minor comment formating issue
p3ck 1eb6bf4
Updates based on review feedback
p3ck f0e99a1
No need to filter on id for inventory plugin
p3ck a268c67
minor cleanup on tmpdir method
p3ck b33f905
Remove unused defaults and fix syntax on attribute lookup
p3ck f4588a7
Rename module and clean up argument spec
p3ck c5b0c2d
Rename aci inventory module
p3ck 034b6ef
Include additional example
p3ck 93ee293
fix sanity test issue with examples
p3ck 1efa602
Remove duplicate keys in examples
p3ck bc082d5
Update plugins/inventory/aci_inventory.py
p3ck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# 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 | ||
- Gets details on all spines and leafs behind the controller. | ||
- 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 | ||
password: PASSWORD | ||
validate_certs: false | ||
|
||
keyed_groups: | ||
- prefix: role | ||
key: role | ||
""" | ||
|
||
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: | ||
basefile = "ansible-moduletmp-%s-" % time.time() | ||
try: | ||
tmpdir = tempfile.mkdtemp(prefix=basefile) | ||
except (OSError, IOError) as 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 | ||
|
||
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): | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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")): | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
config.update(state="query") | ||
|
||
argument_spec = aci_argument_spec() | ||
argument_spec.update( | ||
id=dict(type="int", aliases=["controller", "node"]), | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
state=dict(type="str", default="query", choices=["query"]), | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
keyed_groups=dict(type="list"), | ||
plugin=dict(type="str"), | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
module = MockAnsibleModule( | ||
argument_spec=argument_spec, | ||
parameters=config, | ||
) | ||
|
||
aci = ACIModule(module) | ||
aci.construct_url(root_class=dict(aci_class="topSystem")) | ||
|
||
aci.get_existing() | ||
|
||
# parse data and create inventory objects: | ||
for device in aci.existing: | ||
attributes = device.get("topSystem", {}).get("attributes") | ||
if attributes.get("name"): | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.add_host(attributes.get("name"), attributes) | ||
|
||
def add_host(self, hostname, host_vars): | ||
self.inventory.add_host(hostname, group="all") | ||
akinross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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.get("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) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was doing some local testing with your code and the normal arguments from the aci collection seem to work as expected.
I do however have an additional question regarding plugin usage and the arguments exposed / used for authentication. For our normal modules we also allow users the specify HTTPAPI connection plugin to limit the amount of login requests send. This allows for a user to specify some additional authentication arguments to be set in inventory which are in that case not needed to be specified in each task. See some explanation in the repository: https://github.com/CiscoDevNet/ansible-aci/blob/master/docs/optimizing.md#using-the-aci-httpapi-plugin.
Is there a way to leverage this plugin also for the inventory plugin? If not, is there a way to expose these arguments to also be valid inputs for this plugin? For instance by updating the aliases from aci_argument_spec in this plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am just looking at this documentation to understand how it works.
I do see that I probably need to rename this module though.. I see this
Do you have an opinion on what this inventory plugin should be called? cisco.aci.aci_inv ??
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally prefer cisco.aci.aci_inventory or cisco.aci.inventory if it requires to be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok - I will use cisco.aci.aci_inventory
I did some quick investigation and it looks like ansible_connection is not used from inventory module. But we can generate an inventory that will use that for the devices found..
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok thanks, should we add compose to the example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am happy to. Is there additional argument that should be set to make it a fully working example? Is the example I provided enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me the provided example was clear, so would suggest add two examples with a comment above. 1. minimal version, 2. compose example. Think that would make it easier for users to understand compose.