Skip to content

Commit

Permalink
Remove data sub dictionary and move sub keys to top level (nautobot#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
FragmentedPacket authored Aug 5, 2021
1 parent 6b89cb4 commit af5f7b3
Show file tree
Hide file tree
Showing 66 changed files with 4,241 additions and 5,589 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ jobs:
- PYTHON_VER=3.6
install:
- pip install -U pip
- pip install poetry
- curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
- export PATH=$HOME/.poetry/bin:$PATH
- poetry config virtualenvs.create false && poetry install

before_script:
Expand Down
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace: networktocode
name: nautobot

# The version of the collection. Must be compatible with semantic versioning
version: 2.1.0
version: 3.0.0

# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@


class NautobotCircuitsModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def run(self):
"""
This function should have all necessary code for endpoints within the application
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/dcim.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@


class NautobotDcimModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def run(self):
"""
This function should have all necessary code for endpoints within the application
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@


class NautobotExtrasModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def run(self):
"""
This function should have all necessary code for endpoints within the application
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/ipam.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@


class NautobotIpamModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def _handle_state_new_present(self, nb_app, nb_endpoint, endpoint_name, name, data):
if data.get("address"):
if self.state == "present":
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/tenancy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@


class NautobotTenancyModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def run(self):
"""
This function should have all necessary code for endpoints within the application
Expand Down
263 changes: 22 additions & 241 deletions plugins/module_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@

from ansible.module_utils.common.text.converters import to_text

from ansible.module_utils._text import to_native
from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.urls import open_url

PYNAUTOBOT_IMP_ERR = None
Expand Down Expand Up @@ -442,7 +440,7 @@
)


class NautobotModule(object):
class NautobotModule:
"""
Initialize connection to Nautobot, sets AnsibleModule passed in to
self.module to be used throughout the class
Expand All @@ -451,7 +449,7 @@ class NautobotModule(object):
:params nb_client (obj): pynautobot.api object passed in (not required)
"""

def __init__(self, module, endpoint, client=None):
def __init__(self, module, endpoint, client=None, remove_keys=None):
self.module = module
self.state = self.module.params["state"]
self.check_mode = self.module.check_mode
Expand All @@ -478,11 +476,28 @@ def __init__(self, module, endpoint, client=None):
# self._validate_query_params(self.module.params["query_params"])

# These methods will normalize the regular data
cleaned_data = self._remove_arg_spec_default(module.params["data"])
cleaned_data = self._remove_arg_spec_default(module.params)
norm_data = self._normalize_data(cleaned_data)
choices_data = self._change_choices_id(self.endpoint, norm_data)
data = self._find_ids(choices_data, query_params)
self.data = self._convert_identical_keys(data)
data = self._convert_identical_keys(data)
self.data = self._build_payload(data, remove_keys)

def _build_payload(self, data, remove_keys):
"""Remove any key/value pairs that aren't relevant for interacting with Nautobot.
Args:
data ([type]): [description]
remove_keys ([type]): [description]
Returns:
[type]: [description]
"""
keys_to_remove = set(NAUTOBOT_ARG_SPEC)
if remove_keys:
keys_to_remove.extend(remove_keys)

return {k: v for k, v in data.items() if k not in keys_to_remove}

def _version_check_greater(self, greater, lesser, greater_or_equal=False):
"""Determine if first argument is greater than second argument.
Expand Down Expand Up @@ -1071,240 +1086,6 @@ def run(self):
raise NotImplementedError


class NautobotAnsibleModule(AnsibleModule):
"""
Creating this due to needing to override some functionality to provide required_together, required_if
and will be able to override more in the future.
This is due to the Nautobot modules having the module arguments within a key in the argument spec, using suboptions rather than
having all module arguments within the regular argument spec.
Didn't want to change that functionality of the Nautobot modules as its disruptive and we're required to send a specific payload
to the Nautobot API
"""

def __init__(
self,
argument_spec,
bypass_checks=False,
no_log=False,
mutually_exclusive=None,
required_together=None,
required_one_of=None,
add_file_common_args=False,
supports_check_mode=False,
required_if=None,
required_by=None,
):
super().__init__(
argument_spec,
bypass_checks=False,
no_log=False,
mutually_exclusive=mutually_exclusive,
required_together=required_together,
required_one_of=required_one_of,
add_file_common_args=False,
supports_check_mode=supports_check_mode,
required_if=required_if,
)

def _check_mutually_exclusive(self, spec, param=None):
if param is None:
param = self.params

try:
self.check_mutually_exclusive(spec, param)
except TypeError as e:
msg = to_native(e)
if self._options_context:
msg += " found in %s" % " -> ".join(self._options_context)
self.fail_json(msg=msg)

def check_mutually_exclusive(self, terms, module_parameters):
"""Check mutually exclusive terms against argument parameters
Accepts a single list or list of lists that are groups of terms that should be
mutually exclusive with one another
:arg terms: List of mutually exclusive module parameters
:arg module_parameters: Dictionary of module parameters
:returns: Empty list or raises TypeError if the check fails.
"""

results = []
if terms is None:
return results

for check in terms:
count = self.count_terms(check, module_parameters["data"])
if count > 1:
results.append(check)

if results:
full_list = ["|".join(check) for check in results]
msg = "parameters are mutually exclusive: %s" % ", ".join(full_list)
raise TypeError(to_native(msg))

return results

def _check_required_if(self, spec, param=None):
""" ensure that parameters which conditionally required are present """
if spec is None:
return

if param is None:
param = self.params

try:
self.check_required_if(spec, param)
except TypeError as e:
msg = to_native(e)
if self._options_context:
msg += " found in %s" % " -> ".join(self._options_context)
self.fail_json(msg=msg)

def check_required_if(self, requirements, module_parameters):
results = []
if requirements is None:
return results

for req in requirements:
missing = {}
missing["missing"] = []
max_missing_count = 0
is_one_of = False
if len(req) == 4:
key, val, requirements, is_one_of = req
else:
key, val, requirements = req

# is_one_of is True at least one requirement should be
# present, else all requirements should be present.
if is_one_of:
max_missing_count = len(requirements)
missing["requires"] = "any"
else:
missing["requires"] = "all"

if key in module_parameters and module_parameters[key] == val:
for check in requirements:
count = self.count_terms(check, module_parameters["data"])
if count == 0:
missing["missing"].append(check)
if len(missing["missing"]) and len(missing["missing"]) >= max_missing_count:
missing["parameter"] = key
missing["value"] = val
missing["requirements"] = requirements
results.append(missing)

if results:
for missing in results:
msg = "%s is %s but %s of the following are missing: %s" % (
missing["parameter"],
missing["value"],
missing["requires"],
", ".join(missing["missing"]),
)
raise TypeError(to_native(msg))

return results

def _check_required_one_of(self, spec, param=None):
if spec is None:
return

if param is None:
param = self.params

try:
self.check_required_one_of(spec, param)
except TypeError as e:
msg = to_native(e)
if self._options_context:
msg += " found in %s" % " -> ".join(self._options_context)
self.fail_json(msg=msg)

def check_required_one_of(self, terms, module_parameters):
"""Check each list of terms to ensure at least one exists in the given module
parameters
Accepts a list of lists or tuples
:arg terms: List of lists of terms to check. For each list of terms, at
least one is required.
:arg module_parameters: Dictionary of module parameters
:returns: Empty list or raises TypeError if the check fails.
"""

results = []
if terms is None:
return results

for term in terms:
count = self.count_terms(term, module_parameters["data"])
if count == 0:
results.append(term)

if results:
for term in results:
msg = "one of the following is required: %s" % ", ".join(term)
raise TypeError(to_native(msg))

return results

def _check_required_together(self, spec, param=None):
if spec is None:
return
if param is None:
param = self.params

try:
self.check_required_together(spec, param)
except TypeError as e:
msg = to_native(e)
if self._options_context:
msg += " found in %s" % " -> ".join(self._options_context)
self.fail_json(msg=msg)

def check_required_together(self, terms, module_parameters):
"""Check each list of terms to ensure every parameter in each list exists
in the given module parameters
Accepts a list of lists or tuples
:arg terms: List of lists of terms to check. Each list should include
parameters that are all required when at least one is specified
in the module_parameters.
:arg module_parameters: Dictionary of module parameters
:returns: Empty list or raises TypeError if the check fails.
"""

results = []
if terms is None:
return results

for term in terms:
counts = [
self.count_terms(field, module_parameters["data"]) for field in term
]
non_zero = [c for c in counts if c > 0]
if len(non_zero) > 0:
if 0 in counts:
results.append(term)
if results:
for term in results:
msg = "parameters are required together: %s" % ", ".join(term)
raise TypeError(to_native(msg))

return results

def count_terms(self, terms, module_parameters):
"""Count the number of occurrences of a key in a given dictionary
:arg terms: String or iterable of values to check
:arg module_parameters: Dictionary of module parameters
:returns: An integer that is the number of occurrences of the terms values
in the provided dictionary.
"""

if not is_iterable(terms):
terms = [terms]

return len(set(terms).intersection(module_parameters))


class NautobotApiBase:
def __init__(self, **kwargs):
self.url = kwargs.get("url") or os.getenv("NAUTOBOT_URL")
Expand Down
3 changes: 0 additions & 3 deletions plugins/module_utils/virtualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@


class NautobotVirtualizationModule(NautobotModule):
def __init__(self, module, endpoint):
super().__init__(module, endpoint)

def run(self):
"""
This function should have all necessary code for endpoints within the application
Expand Down
Loading

0 comments on commit af5f7b3

Please sign in to comment.