diff --git a/plugins/module_utils/dcim.py b/plugins/module_utils/dcim.py index 975c2269..0b7a3fc5 100644 --- a/plugins/module_utils/dcim.py +++ b/plugins/module_utils/dcim.py @@ -49,6 +49,7 @@ NB_RACK_GROUPS = "rack_groups" NB_REAR_PORTS = "rear_ports" NB_REAR_PORT_TEMPLATES = "rear_port_templates" +NB_SOFTWARE_VERSIONS = "software_versions" NB_VIRTUAL_CHASSIS = "virtual_chassis" @@ -117,6 +118,8 @@ def run(self): name = self.module.params["data"]["master"] elif data.get("id"): name = data["id"] + elif endpoint_name == "software_version": + name = data["version"] elif endpoint_name == "cable": if self.module.params["termination_a"].get("name"): termination_a_name = self.module.params["termination_a"]["name"] diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index 8f675aa5..0b4966f7 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -77,6 +77,7 @@ "rack_groups", "rear_ports", "rear_port_templates", + "software_versions", "virtual_chassis", ], extras=[ @@ -167,6 +168,7 @@ rear_port_template="name", rir="name", route_targets="name", + software_version="version", status="name", tenant="name", tenant_group="name", @@ -343,6 +345,7 @@ "route_targets": "route_target", "services": "services", "static_group_associations": "static_group_association", + "software_versions": "software_version", "statuses": "statuses", "tags": "tags", "teams": "team", @@ -449,6 +452,7 @@ "role": set(["name"]), "route_target": set(["name"]), "services": set(["device", "virtual_machine", "name", "port", "protocol"]), + "software_version": set(["version", "platform"]), "static_group_association": set(["dynamic_group", "associated_object_type", "associated_object_id"]), "statuses": set(["name"]), "tags": set(["name"]), diff --git a/plugins/modules/software_version.py b/plugins/modules/software_version.py new file mode 100644 index 00000000..05bef18a --- /dev/null +++ b/plugins/modules/software_version.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2025, 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: software_version +short_description: Creates, updates or removes software versions from Nautobot +description: + - Creates, updates or removes software versions from Nautobot +notes: + - This module requires Nautobot v2.2+ + - This should be ran with connection C(local) and hosts C(localhost) + - Tags should be defined as a YAML list +author: + - Joe Wesch (@joewesch) +requirements: + - pynautobot +version_added: "5.7.0" +extends_documentation_fragment: + - networktocode.nautobot.fragments.base + - networktocode.nautobot.fragments.tags + - networktocode.nautobot.fragments.custom_fields +options: + version: + description: + - The version of the software + required: true + type: str + platform: + description: + - The platform the software will be applied to + - Required if I(state=present) and does not exist yet + required: false + type: raw + status: + description: + - The status of the software + - Required if I(state=present) and does not exist yet + required: false + type: str + alias: + description: + - Optional alternative label for this version + required: false + type: str + release_date: + description: + - The date the software was released + required: false + type: str + end_of_support_date: + description: + - The date the software will no longer be supported + required: false + type: str + documentation_url: + description: + - URL to the software documentation + required: false + type: str + long_term_support: + description: + - Whether the software is long term support + required: false + type: bool + pre_release: + description: + - Whether the software is pre-release + required: false + type: bool +""" + +EXAMPLES = r""" +--- +- name: Create a software version + networktocode.nautobot.software_version: + url: http://nautobot.local + token: thisIsMyToken + version: 1.0.0 + platform: Cisco IOS + status: Active + alias: My Alias + release_date: 2024-01-01 + end_of_support_date: 2024-12-31 + documentation_url: https://example.com + long_term_support: true + pre_release: false + state: present + +- name: Update a software version + networktocode.nautobot.software_version: + url: http://nautobot.local + token: thisIsMyToken + version: 1.0.0 + state: present + +- name: Delete a software version + networktocode.nautobot.software_version: + url: http://nautobot.local + token: thisIsMyToken + version: 1.0.0 + state: absent +""" + +RETURN = r""" +software_version: + 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, + TAGS_ARG_SPEC, + CUSTOM_FIELDS_ARG_SPEC, +) +from ansible_collections.networktocode.nautobot.plugins.module_utils.dcim import ( + NautobotDcimModule, + NB_SOFTWARE_VERSIONS, +) +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(deepcopy(TAGS_ARG_SPEC)) + argument_spec.update(deepcopy(CUSTOM_FIELDS_ARG_SPEC)) + argument_spec.update( + dict( + version=dict(required=True, type="str"), + platform=dict(required=False, type="raw"), + status=dict(required=False, type="str"), + alias=dict(required=False, type="str"), + release_date=dict(required=False, type="str"), + end_of_support_date=dict(required=False, type="str"), + documentation_url=dict(required=False, type="str"), + long_term_support=dict(required=False, type="bool"), + pre_release=dict(required=False, type="bool"), + ) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + software_version = NautobotDcimModule(module, NB_SOFTWARE_VERSIONS) + software_version.run() + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/tests/integration/nautobot-populate.py b/tests/integration/nautobot-populate.py index 78fa84f3..42ee7a33 100755 --- a/tests/integration/nautobot-populate.py +++ b/tests/integration/nautobot-populate.py @@ -227,6 +227,9 @@ def make_nautobot_calls(endpoint, payload): cisco_manu = nb.dcim.manufacturers.get(name="Cisco") arista_manu = nb.dcim.manufacturers.get(name="Arista") +# Create Platforms +platforms = [{"name": "Cisco IOS", "manufacturer": cisco_manu.id, "network_driver": "cisco_ios", "napalm_driver": "ios"}] +created_platforms = make_nautobot_calls(nb.dcim.platforms, platforms) # Create Device Types device_types = [ diff --git a/tests/integration/targets/latest/tasks/main.yml b/tests/integration/targets/latest/tasks/main.yml index b7903116..b6e69ea9 100644 --- a/tests/integration/targets/latest/tasks/main.yml +++ b/tests/integration/targets/latest/tasks/main.yml @@ -628,6 +628,14 @@ tags: - vlan_location + - name: "PYNAUTOBOT_SOFTWARE_VERSION TESTS" + include_tasks: + file: "software_version.yml" + apply: + tags: + - software_version + tags: + - software_version ########################## ## diff --git a/tests/integration/targets/latest/tasks/software_version.yml b/tests/integration/targets/latest/tasks/software_version.yml new file mode 100644 index 00000000..295b449f --- /dev/null +++ b/tests/integration/targets/latest/tasks/software_version.yml @@ -0,0 +1,151 @@ +--- +- set_fact: + cisco_ios_platform: "{{ lookup('networktocode.nautobot.lookup', 'platforms', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"Cisco IOS\"') }}" + +- name: "SOFTWARE VERSION 1: Necessary info creation" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.0.0" + platform: "Cisco IOS" + status: "Active" + state: present + register: test_one + +- name: "SOFTWARE VERSION 1: ASSERT - Necessary info creation" + assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['software_version']['version'] == "1.0.0" + - test_one['msg'] == "software_version 1.0.0 created" + +- name: "SOFTWARE VERSION 2: Create duplicate" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.0.0" + platform: "Cisco IOS" + state: present + register: test_two_duplicate + +- name: "SOFTWARE VERSION 2: ASSERT - Create duplicate" + assert: + that: + - not test_two_duplicate['changed'] + - test_two_duplicate['software_version']['version'] == "1.0.0" + - test_two_duplicate['msg'] == "software_version 1.0.0 already exists" + +- name: "SOFTWARE VERSION 3: Create with all info" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + platform: "Cisco IOS" + status: "Active" + release_date: "2024-01-01" + end_of_support_date: "2025-01-01" + documentation_url: "https://example.com" + long_term_support: true + pre_release: false + state: present + register: test_three + +- name: "SOFTWARE VERSION 3: ASSERT - Create with all info" + assert: + that: + - test_three is changed + - test_three['diff']['before']['state'] == "absent" + - test_three['diff']['after']['state'] == "present" + - test_three['software_version']['version'] == "1.1.0" + - test_three['software_version']['platform'] == cisco_ios_platform['key'] + - test_three['software_version']['status'] == active['key'] + - test_three['software_version']['release_date'] == "2024-01-01" + - test_three['software_version']['end_of_support_date'] == "2025-01-01" + - test_three['software_version']['documentation_url'] == "https://example.com" + - test_three['software_version']['long_term_support'] == true + - test_three['software_version']['pre_release'] == false + - test_three['msg'] == "software_version 1.1.0 created" + +- name: "SOFTWARE VERSION 4: Create with all info duplicate" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + platform: "Cisco IOS" + status: "Active" + release_date: "2024-01-01" + end_of_support_date: "2025-01-01" + documentation_url: "https://example.com" + long_term_support: true + pre_release: false + state: present + register: test_four + +- name: "SOFTWARE VERSION 4: ASSERT - Create with all info duplicate" + assert: + that: + - not test_four['changed'] + - test_four['software_version']['version'] == "1.1.0" + - test_four['msg'] == "software_version 1.1.0 already exists" + +- name: "SOFTWARE VERSION 5: Update" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + end_of_support_date: "2026-01-01" + register: test_five + +- name: "SOFTWARE VERSION 5: ASSERT - Update" + assert: + that: + - test_five is changed + - test_five['diff']['before']['end_of_support_date'] == "2025-01-01" + - test_five['diff']['after']['end_of_support_date'] == "2026-01-01" + +- name: "SOFTWARE VERSION 6: Update duplicate" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + end_of_support_date: "2026-01-01" + register: test_six + +- name: "SOFTWARE VERSION 6: ASSERT - Update duplicate" + assert: + that: + - not test_six['changed'] + - test_six['software_version']['version'] == "1.1.0" + - test_six['msg'] == "software_version 1.1.0 already exists" + +- name: "SOFTWARE VERSION 7: Delete" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + state: absent + register: test_seven + +- name: "SOFTWARE VERSION 7: ASSERT - Delete" + assert: + that: + - test_seven is changed + - test_seven['diff']['before']['state'] == "present" + - test_seven['diff']['after']['state'] == "absent" + - test_seven['msg'] == "software_version 1.1.0 deleted" + +- name: "SOFTWARE VERSION 8: Delete non-existent" + networktocode.nautobot.software_version: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + version: "1.1.0" + state: absent + register: test_eight + +- name: "SOFTWARE VERSION 8: ASSERT - Delete non-existent" + assert: + that: + - not test_eight['changed'] + - test_eight['msg'] == "software_version 1.1.0 already absent"