diff --git a/plugins/lookup/lookup.py b/plugins/lookup/lookup.py index f9447bfe..bfef11a2 100644 --- a/plugins/lookup/lookup.py +++ b/plugins/lookup/lookup.py @@ -157,6 +157,7 @@ def get_endpoint(nautobot, term): "device-bay-templates": {"endpoint": nautobot.dcim.device_bay_templates}, "device-bays": {"endpoint": nautobot.dcim.device_bays}, "device-types": {"endpoint": nautobot.dcim.device_types}, + "device-redundancy-groups": {"endpoint": nautobot.dcim.device_redundancy_groups}, "devices": {"endpoint": nautobot.dcim.devices}, "export-templates": {"endpoint": nautobot.dcim.export_templates}, "front-port-templates": {"endpoint": nautobot.dcim.front_port_templates}, @@ -193,6 +194,8 @@ def get_endpoint(nautobot, term): "reports": {"endpoint": nautobot.extras.reports}, "rirs": {"endpoint": nautobot.ipam.rirs}, "roles": {"endpoint": nautobot.extras.roles}, + "secrets": {"endpoint": nautobot.extras.secrets}, + "secrets-groups": {"endpoint": nautobot.extras.secrets_groups}, "services": {"endpoint": nautobot.ipam.services}, "statuses": {"endpoint": nautobot.extras.statuses}, "tags": {"endpoint": nautobot.extras.tags}, diff --git a/plugins/module_utils/dcim.py b/plugins/module_utils/dcim.py index ee2983de..0e3bbf73 100644 --- a/plugins/module_utils/dcim.py +++ b/plugins/module_utils/dcim.py @@ -19,6 +19,7 @@ NB_CONSOLE_SERVER_PORT_TEMPLATES = "console_server_port_templates" NB_DEVICE_BAYS = "device_bays" NB_DEVICE_BAY_TEMPLATES = "device_bay_templates" +NB_DEVICE_REDUNDANCY_GROUPS = "device_redundancy_groups" NB_DEVICES = "devices" NB_ROLES = "roles" NB_DEVICE_TYPES = "device_types" diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index c5d519aa..2f074efe 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -43,6 +43,7 @@ "device_bay_templates", "devices", "device_types", + "device_redundancy_groups", "front_ports", "front_port_templates", "interfaces", @@ -215,6 +216,7 @@ "device_bay_templates": "device_bay_template", "devices": "device", "device_types": "device_type", + "device_redundancy_groups": "device_redundancy_group", "front_ports": "front_port", "front_port_templates": "front_port_template", "interfaces": "interface", @@ -279,6 +281,7 @@ "device_bay": set(["name", "device"]), "device_bay_template": set(["name", "device_type"]), "device": set(["name"]), + "device_redundancy_group": set(["name"]), "device_type": set(["model"]), "front_port": set(["name", "device", "rear_port"]), "front_port_template": set(["name", "device_type", "rear_port_template"]), diff --git a/plugins/modules/device.py b/plugins/modules/device.py index 486cb1cc..9c89109a 100644 --- a/plugins/modules/device.py +++ b/plugins/modules/device.py @@ -152,6 +152,18 @@ required: false type: dict version_added: "3.0.0" + device_redundancy_group: + description: + - Device redundancy group the device will be assigned to + required: false + type: raw + version_added: "5.1.0" + device_redundancy_group_priority: + description: + - Priority in the assigned device redundancy group + required: false + type: int + version_added: "5.1.0" """ EXAMPLES = r""" @@ -276,6 +288,8 @@ def main(): tags=dict(required=False, type="list", elements="raw"), local_config_context_data=dict(required=False, type="dict"), custom_fields=dict(required=False, type="dict"), + device_redundancy_group=dict(required=False, type="raw"), + device_redundancy_group_priority=dict(required=False, type="int"), ) ) diff --git a/plugins/modules/device_redundancy_group.py b/plugins/modules/device_redundancy_group.py new file mode 100644 index 00000000..12ed6175 --- /dev/null +++ b/plugins/modules/device_redundancy_group.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Network to Code (@networktocode) +# 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""" +--- +module: device_redundancy_group +short_description: Creates or removes device redundancy groups from Nautobot +description: + - Creates or removes device redundancy groups from Nautobot +notes: + - Tags should be defined as a YAML list + - This should be ran with connection C(local) and hosts C(localhost) +author: + - Joe Wesch (@joewesch) +requirements: + - pynautobot +version_added: "5.1.0" +extends_documentation_fragment: + - networktocode.nautobot.fragments.base + - networktocode.nautobot.fragments.tags + - networktocode.nautobot.fragments.custom_fields +options: + name: + description: + - The name of the device redundancy group + required: true + type: str + version_added: "5.1.0" + status: + description: + - The status of the device redundancy group + - Required if I(state=present) and the device redundancy group does not exist yet + required: false + type: raw + version_added: "5.1.0" + description: + description: + - The description of the device redundancy group + required: false + type: str + version_added: "5.1.0" + failover_strategy: + description: + - The failover strategy of the device redundancy group + required: false + choices: + - active-active + - active-passive + type: str + version_added: "5.1.0" + secrets_group: + description: + - The secrets group of the device redundancy group + required: false + type: raw + version_added: "5.1.0" +""" + +EXAMPLES = r""" +- name: "Test Nautobot device_redundancy_group module" + connection: local + hosts: localhost + gather_facts: False + + tasks: + - name: Create device redundancy group within Nautobot with only required information + networktocode.nautobot.device_redundancy_group: + url: http://nautobot.local + token: thisIsMyToken + name: My Redundancy Group + status: Active + state: present + + - name: Create device redundancy group within Nautobot with all information + networktocode.nautobot.device_redundancy_group: + url: http://nautobot.local + token: thisIsMyToken + name: My Redundancy Group + status: Active + description: My Description + failover_strategy: active-active + secrets_group: "{{ my_secrets_group['key'] }}" + tags: + - My Tag + custom_fields: + my_field: my_value + state: present + vars: + my_secrets_group: "{{ lookup('networktocode.nautobot.lookup', 'secrets-groups', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"My Secrets Group\"') }}" + + - name: Delete device redundancy group within nautobot + networktocode.nautobot.device_redundancy_group: + url: http://nautobot.local + token: thisIsMyToken + name: My Redundancy Group + state: absent + +""" + +RETURN = r""" +device_redundancy_group: + description: Serialized object as created or already existent within Nautobot + returned: success (when I(state=present)) + type: dict +msg: + description: Message indicating failure or info about what has been achieved + returned: always + type: str +""" + +from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import NAUTOBOT_ARG_SPEC +from ansible_collections.networktocode.nautobot.plugins.module_utils.dcim import ( + NautobotDcimModule, + NB_DEVICE_REDUNDANCY_GROUPS, +) +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + + +def main(): + """ + Main entry point for module execution + """ + argument_spec = deepcopy(NAUTOBOT_ARG_SPEC) + argument_spec.update( + dict( + name=dict(required=True, type="str"), + status=dict(required=False, type="raw"), + description=dict(required=False, type="str"), + failover_strategy=dict( + required=False, + type="str", + choices=["active-active", "active-passive"], + ), + secrets_group=dict(required=False, type="raw"), + tags=dict(required=False, type="list", elements="raw"), + custom_fields=dict(required=False, type="dict"), + ) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + device_redundancy_group = NautobotDcimModule(module, NB_DEVICE_REDUNDANCY_GROUPS) + device_redundancy_group.run() + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/tests/integration/nautobot-populate.py b/tests/integration/nautobot-populate.py index 37b426d0..1e21fcf7 100755 --- a/tests/integration/nautobot-populate.py +++ b/tests/integration/nautobot-populate.py @@ -538,5 +538,41 @@ def make_nautobot_calls(endpoint, payload): ] created_relationships = make_nautobot_calls(nb.extras.relationships, relationships) +# Create Secrets +secrets = [ + { + "name": "Test Secret", + "provider": "environment-variable", + "parameters": { + "variable": "TEST_ENV_VAR", + }, + } +] +created_secrets = make_nautobot_calls(nb.extras.secrets, secrets) +test_secret = nb.extras.secrets.get(name="Test Secret") + +secrets_groups = [ + { + "name": "Test Secrets Group", + "secrets": [ + { + "secret": test_secret.id, + "access_type": "Generic", + "secret_type": "secret", + } + ], + } +] +created_secrets_groups = make_nautobot_calls(nb.extras.secrets_groups, secrets_groups) + +# Create Device Redundancy Groups +device_redundancy_groups = [ + { + "name": "My Device Redundancy Group", + "status": "Active", + } +] +created_device_redundancy_groups = make_nautobot_calls(nb.dcim.device_redundancy_groups, device_redundancy_groups) + if ERRORS: sys.exit("Errors have occurred when creating objects, and should have been printed out. Check previous output.") diff --git a/tests/integration/targets/latest/tasks/device.yml b/tests/integration/targets/latest/tasks/device.yml index 383b8a97..7850d2fa 100644 --- a/tests/integration/targets/latest/tasks/device.yml +++ b/tests/integration/targets/latest/tasks/device.yml @@ -12,6 +12,7 @@ vc1: "{{ lookup('networktocode.nautobot.lookup', 'virtual-chassis', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=VC1') }}" staged: "{{ lookup('networktocode.nautobot.lookup', 'statuses', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=Staged') }}" active: "{{ lookup('networktocode.nautobot.lookup', 'statuses', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=Active') }}" + device_redundancy_group: "{{ lookup('networktocode.nautobot.lookup', 'device-redundancy-groups', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"My Device Redundancy Group\"') }}" - name: "1 - Device with required information" networktocode.nautobot.device: @@ -95,6 +96,8 @@ virtual_chassis: "VC1" vc_position: 3 vc_priority: 15 + device_redundancy_group: "{{ device_redundancy_group['key'] }}" + device_redundancy_group_priority: 10 state: present register: test_three @@ -117,6 +120,8 @@ - test_three['device']['virtual_chassis'] == vc1['key'] - test_three['device']['vc_position'] == 3 - test_three['device']['vc_priority'] == 15 + - test_three['device']['device_redundancy_group'] == device_redundancy_group['key'] + - test_three['device']['device_redundancy_group_priority'] == 10 - test_three['msg'] == "device R1 updated" - name: "3.1 - Update device status" diff --git a/tests/integration/targets/latest/tasks/device_redundancy_group.yml b/tests/integration/targets/latest/tasks/device_redundancy_group.yml new file mode 100644 index 00000000..68546b29 --- /dev/null +++ b/tests/integration/targets/latest/tasks/device_redundancy_group.yml @@ -0,0 +1,130 @@ +--- +## +## +### PYNAUTOBOT_DEVICE_REDUNDANCY_GROUP +## +## +- set_fact: + active: "{{ lookup('networktocode.nautobot.lookup', 'statuses', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=Active') }}" + secrets_group: "{{ lookup('networktocode.nautobot.lookup', 'secrets-groups', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"Test Secrets Group\"') }}" + +- name: "1 - Create device redundancy group within Nautobot with only required information" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group + status: Active + register: test_create_min + +- name: "1 - ASSERT" + assert: + that: + - test_create_min is changed + - test_create_min['diff']['before']['state'] == "absent" + - test_create_min['diff']['after']['state'] == "present" + - test_create_min['device_redundancy_group']['name'] == "Test Device Redundancy Group" + - test_create_min['device_redundancy_group']['status'] == active['key'] + - test_create_min['msg'] == "device_redundancy_group Test Device Redundancy Group created" + +- name: "2 - Duplicate" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group + status: Active + register: test_create_idem + +- name: "2 - ASSERT" + assert: + that: + - not test_create_idem['changed'] + - test_create_idem['msg'] == "device_redundancy_group Test Device Redundancy Group already exists" + - test_create_idem['device_redundancy_group']['name'] == "Test Device Redundancy Group" + +- name: "3 - Update device redundancy group" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group + description: Test Device Redundancy Group Description + register: test_update + +- name: "3 - ASSERT" + assert: + that: + - test_update is changed + - test_update['diff']['before']['description'] == "" + - test_update['diff']['after']['description'] == "Test Device Redundancy Group Description" + +- name: "4 - Create device redundancy group with all parameters" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group 2 + status: Active + description: Test Device Redundancy Group Description 2 + failover_strategy: "active-active" + secrets_group: "{{ secrets_group['key'] }}" + register: test_create_max + +- name: "4 - ASSERT" + assert: + that: + - test_create_max is changed + - test_create_max['diff']['before']['state'] == "absent" + - test_create_max['diff']['after']['state'] == "present" + - test_create_max['device_redundancy_group']['name'] == "Test Device Redundancy Group 2" + - test_create_max['device_redundancy_group']['status'] == active['key'] + - test_create_max['msg'] == "device_redundancy_group Test Device Redundancy Group 2 created" + - test_create_max['device_redundancy_group']['description'] == "Test Device Redundancy Group Description 2" + - test_create_max['device_redundancy_group']['failover_strategy'] == "active-active" + - test_create_max['device_redundancy_group']['secrets_group'] == secrets_group['key'] + +- name: "5 - Duplicate create with all parameters" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group 2 + status: Active + description: Test Device Redundancy Group Description 2 + failover_strategy: "active-active" + secrets_group: "{{ secrets_group['key'] }}" + register: test_create_max_idem + +- name: "5 - ASSERT" + assert: + that: + - not test_create_max_idem['changed'] + - test_create_max_idem['msg'] == "device_redundancy_group Test Device Redundancy Group 2 already exists" + - test_create_max_idem['device_redundancy_group']['name'] == "Test Device Redundancy Group 2" + +- name: "6 - Delete device redundancy group" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group 2 + state: absent + register: test_delete + +- name: "6 - ASSERT" + assert: + that: + - test_delete is changed + - test_delete['diff']['before']['state'] == "present" + - test_delete['diff']['after']['state'] == "absent" + - test_delete['device_redundancy_group']['name'] == "Test Device Redundancy Group 2" + - "'deleted' in test_delete['msg']" + +- name: "7 - Delete idempotent" + networktocode.nautobot.device_redundancy_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: Test Device Redundancy Group 2 + state: absent + register: test_delete_idem + +- name: "7 - ASSERT" + assert: + that: + - not test_delete_idem['changed'] + - "'already absent' in test_delete_idem['msg']" diff --git a/tests/integration/targets/latest/tasks/main.yml b/tests/integration/targets/latest/tasks/main.yml index 365424fb..0675af6e 100644 --- a/tests/integration/targets/latest/tasks/main.yml +++ b/tests/integration/targets/latest/tasks/main.yml @@ -484,3 +484,12 @@ - plugin_bgp_asn tags: - plugin_bgp_asn + +- name: "PYNAUTOBOT_DEVICE_REDUNDANCY_GROUP TESTS" + include_tasks: + file: "device_redundancy_group.yml" + apply: + tags: + - device_redundancy_group + tags: + - device_redundancy_group diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt new file mode 100644 index 00000000..ee1e1c82 --- /dev/null +++ b/tests/sanity/ignore-2.14.txt @@ -0,0 +1 @@ +plugins/modules/device_redundancy_group.py validate-modules:no-log-needed # secrets_group is not an actual secret that requires no_log to be set diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt new file mode 100644 index 00000000..ee1e1c82 --- /dev/null +++ b/tests/sanity/ignore-2.15.txt @@ -0,0 +1 @@ +plugins/modules/device_redundancy_group.py validate-modules:no-log-needed # secrets_group is not an actual secret that requires no_log to be set