diff --git a/plugins/module_utils/netbox_extras.py b/plugins/module_utils/netbox_extras.py index 36f4bf864..64eb9c8b9 100644 --- a/plugins/module_utils/netbox_extras.py +++ b/plugins/module_utils/netbox_extras.py @@ -14,6 +14,7 @@ NB_CONFIG_CONTEXTS = "config_contexts" NB_TAGS = "tags" NB_CUSTOM_FIELDS = "custom_fields" +NB_CUSTOM_FIELD_CHOICE_SETS = "custom_field_choice_sets" NB_CUSTOM_LINKS = "custom_links" NB_EXPORT_TEMPLATES = "export_templates" NB_JOURNAL_ENTRIES = "journal_entries" diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index 04c327ff8..0d358f8e1 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -82,6 +82,7 @@ "config_templates": {}, "tags": {}, "custom_fields": {}, + "custom_field_choice_sets": {}, "custom_links": {}, "export_templates": {}, "journal_entries": {}, @@ -145,6 +146,7 @@ contact_group="name", contact_role="name", custom_field="name", + choice_set="name", custom_link="name", device="name", device_role="slug", @@ -226,6 +228,7 @@ "config_context": "config_contexts", "config_template": "config_templates", "contact_groups": "contact_groups", + "choice_set": "custom_field_choice_sets", "dcim.consoleport": "console_ports", "dcim.consoleserverport": "console_server_ports", "dcim.frontport": "front_ports", @@ -338,6 +341,7 @@ "contact_groups": "contact_group", "contact_roles": "contact_role", "custom_fields": "custom_field", + "custom_field_choice_sets": "choice_set", "custom_links": "custom_link", "device_bays": "device_bay", "device_bay_templates": "device_bay_template", @@ -438,6 +442,8 @@ "contact_group": set(["name"]), "contact_role": set(["name"]), "custom_field": set(["name"]), + "custom_field_choice_set": set(["name"]), + "choice_set": set(["name"]), "custom_link": set(["name"]), "dcim.consoleport": set(["name", "device"]), "dcim.consoleserverport": set(["name", "device"]), diff --git a/plugins/modules/netbox_custom_field.py b/plugins/modules/netbox_custom_field.py index eadfad6d5..ed9c8ed0a 100644 --- a/plugins/modules/netbox_custom_field.py +++ b/plugins/modules/netbox_custom_field.py @@ -132,12 +132,11 @@ - The regular expression to enforce on text fields required: false type: str - choices: + choice_set: description: - - List of available choices (for selection fields) + - The name of the choice set to use (for selection fields) required: false - type: list - elements: str + type: str required: true """ @@ -157,6 +156,18 @@ name: A Custom Field type: text + - name: Create a custom field of type selection + netbox.netbox.netbox_custom_field: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: "Custom_Field" + content_types: + - dcim.device + - virtualization.virtualmachine + type: select + choice_set: A Choice Set name + - name: Update the custom field to make it required netbox.netbox.netbox_custom_field: netbox_url: http://netbox.local @@ -258,7 +269,10 @@ def main(): validation_minimum=dict(required=False, type="int"), validation_maximum=dict(required=False, type="int"), validation_regex=dict(required=False, type="str"), - choices=dict(required=False, type="list", elements="str"), + choice_set=dict( + required=False, + type="str", + ), ), ) ) diff --git a/plugins/modules/netbox_custom_field_choice_set.py b/plugins/modules/netbox_custom_field_choice_set.py new file mode 100644 index 000000000..95ebbd15a --- /dev/null +++ b/plugins/modules/netbox_custom_field_choice_set.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2024, Philipp Rintz (@p-rintz) +# 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: netbox_custom_field_choice_set +short_description: Creates, updates or deletes custom field choice sets within Netbox +description: + - Creates, updates or removes custom fields choice sets from Netbox +notes: + - This should be run with connection C(local) and hosts C(localhost) +author: + - Philipp Rintz (@p-rintz) +requirements: + - pynetbox +version_added: "3.6.0" +extends_documentation_fragment: + - netbox.netbox.common +options: + data: + type: dict + description: + - Defines the choice set + suboptions: + name: + description: + - Name of the choice set + required: true + type: str + description: + description: + - Description of the choice set + required: false + type: str + extra_choices: + description: + - List of available choices in the choice set + required: false + default: [] + type: list + elements: list + base_choices: + description: + - Selection of base choice to use in the choice set + required: false + type: str + choices: + - IATA + - ISO_3166 + - UN_LOCODE + order_alphabetically: + description: + - Order the choices alphabetically + required: false + type: bool + required: true +""" + +EXAMPLES = r""" +- name: "Test Netbox custom_field_choice_set module" + connection: local + hosts: localhost + tasks: + - name: Create a choice set with choices + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: "ChoiceSetName" + description: "Choice Set Description" + extra_choices: + - ['choice1', 'label1'] + - ['choice2', 'label2'] + + - name: Create a choice set with a base choice + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: "ChoiceSetName" + description: "Choice Set Description" + order_alphabetically: true + base_choices: "IATA" +""" + +RETURN = r""" +custom_field_choice_set: + description: Serialized object as created/existent/updated/deleted within NetBox + returned: always + type: dict +msg: + description: Message indicating failure or info about what has been achieved + returned: always + type: str +""" + +from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import ( + NetboxAnsibleModule, + NETBOX_ARG_SPEC, +) +from ansible_collections.netbox.netbox.plugins.module_utils.netbox_extras import ( + NetboxExtrasModule, + NB_CUSTOM_FIELD_CHOICE_SETS, +) +from copy import deepcopy + + +def main(): + """ + Main entry point for module execution + """ + argument_spec = deepcopy(NETBOX_ARG_SPEC) + argument_spec.update( + dict( + data=dict( + type="dict", + required=True, + options=dict( + name=dict(required=True, type="str"), + description=dict(required=False, type="str"), + base_choices=dict( + required=False, + type="str", + choices=[ + "IATA", + "ISO_3166", + "UN_LOCODE", + ], + ), + extra_choices=dict( + required=False, + default=[], + type="list", + elements="list", + ), + order_alphabetically=dict(required=False, type="bool"), + ), + ) + ) + ) + + required_if = [ + ("state", "present", ["name"]), + ("state", "absent", ["name"]), + ] + + module = NetboxAnsibleModule( + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if + ) + + netbox_custom_field_choice_set = NetboxExtrasModule( + module, NB_CUSTOM_FIELD_CHOICE_SETS + ) + netbox_custom_field_choice_set.run() + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/tests/integration/targets/v3.6/tasks/main.yml b/tests/integration/targets/v3.6/tasks/main.yml index 1b232374e..5329b4917 100644 --- a/tests/integration/targets/v3.6/tasks/main.yml +++ b/tests/integration/targets/v3.6/tasks/main.yml @@ -310,3 +310,8 @@ include_tasks: "netbox_config_template.yml" tags: - netbox_config_template + +- name: "NETBOX_CUSTOM_FIELD_CHOICE_SET" + include_tasks: "netbox_custom_field_choice_set.yml" + tags: + - netbox_custom_field_choice_set diff --git a/tests/integration/targets/v3.6/tasks/netbox_custom_field_choice_set.yml b/tests/integration/targets/v3.6/tasks/netbox_custom_field_choice_set.yml new file mode 100644 index 000000000..f76743271 --- /dev/null +++ b/tests/integration/targets/v3.6/tasks/netbox_custom_field_choice_set.yml @@ -0,0 +1,106 @@ +--- +## +## +### NETBOX_CUSTOM_FIELD_CHOICE_SET +## +## +- name: "CUSTOM_FIELD_CHOICE_SET 1: Base Choice set creation" + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "A_ChoiceSet" + description: "Added a description" + base_choices: "IATA" + state: present + register: test_one + +- name: "CUSTOM_FIELD_CHOICE_SET 1: ASSERT - Base Choice set creation" + assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['choice_set']['name'] == "A_ChoiceSet" + - test_one['choice_set']['description'] == "Added a description" + - test_one['choice_set']['display'] == "A_ChoiceSet" + - test_one['choice_set']['order_alphabetically'] == false + - test_one['choice_set']['extra_choices'] == [] + - test_one['msg'] == "choice_set A_ChoiceSet created" + +- name: "CUSTOM_FIELD_CHOICE_SET 2: Create duplicate" + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "A_ChoiceSet" + description: "Added a description" + base_choices: "IATA" + state: present + register: test_two + +- name: "CUSTOM_FIELD_CHOICE_SET 2: ASSERT - Create duplicate" + assert: + that: + - not test_two['changed'] + - test_two['choice_set']['name'] == "A_ChoiceSet" + - test_two['msg'] == "choice_set A_ChoiceSet already exists" + +- name: "CUSTOM_FIELD_CHOICE_SET 3: Update data and change base_choice" + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "A_ChoiceSet" + description: "Added a description" + base_choices: "ISO_3166" + state: present + register: test_three + +- name: "CUSTOM_FIELD_CHOICE_SET 3: ASSERT - Updated" + assert: + that: + - test_three is changed + - test_three['diff']['after']['base_choices'] == "ISO_3166" + - test_three['choice_set']['name'] == "A_ChoiceSet" + - test_three['msg'] == "choice_set A_ChoiceSet updated" + +- name: "CUSTOM_FIELD_CHOICE_SET 4: Update extra choice and order alphabetically" + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "A_ChoiceSet" + description: "Added a description" + order_alphabetically: true + extra_choices: + - ['test', 'label'] + - ['test2', 'label2'] + state: present + register: test_four + +- name: "CUSTOM_FIELD_CHOICE_SET 4: ASSERT - Change extra choice and order alphabetically" + assert: + that: + - test_four is changed + - test_four['diff']['after']['extra_choices'] == [["test","label"],["test2","label2"]] + - test_four['diff']['after']['order_alphabetically'] == true + - test_four['choice_set']['name'] == "A_ChoiceSet" + - test_four['msg'] == "choice_set A_ChoiceSet updated" + +- name: "CUSTOM_FIELD_CHOICE_SET 5: Delete" + netbox.netbox.netbox_custom_field_choice_set: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "A_ChoiceSet" + state: absent + register: test_five + +- name: "CUSTOM_FIELD_CHOICE_SET 5: ASSERT - Deleted" + assert: + that: + - test_five is changed + - test_five['diff']['after']['state'] == "absent" + - test_five['choice_set']['name'] == "A_ChoiceSet" + - test_five['msg'] == "choice_set A_ChoiceSet deleted"