From a4e3f2a46d4b8867b830f46b2235f5339217c96b Mon Sep 17 00:00:00 2001 From: samitab Date: Wed, 18 Dec 2024 13:40:57 +1000 Subject: [PATCH 1/6] [minor_change] Added module `mso_schema_anp_epg_annotation` used to configure EPG annotations. --- plugins/module_utils/schema.py | 15 ++ .../mso_schema_template_anp_epg_annotation.py | 228 ++++++++++++++++++ .../aliases | 2 + .../tasks/main.yml | 215 +++++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 plugins/modules/mso_schema_template_anp_epg_annotation.py create mode 100644 tests/integration/targets/mso_schema_template_anp_epg_annotation/aliases create mode 100644 tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml diff --git a/plugins/module_utils/schema.py b/plugins/module_utils/schema.py index 5cb9cedb..5c1e7453 100644 --- a/plugins/module_utils/schema.py +++ b/plugins/module_utils/schema.py @@ -190,6 +190,21 @@ def set_template_anp_epg_useg_attr(self, useg_attr, fail_module=True): self.mso.fail_json(msg=msg) self.schema_objects["template_anp_epg_useg_attribute"] = match + def set_template_anp_epg_annotation(self, annotation_key, fail_module=True): + """ + Get template endpoint group annotation that matches the key of an EPG annotation. + :param useg_attr: Key of the EPG Annotation to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template EPG Annotation item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template_anp_epg"]) + kv_list = [KVPair("tagKey", annotation_key)] + match, existing = self.get_object_from_list(self.schema_objects["template_anp_epg"].details.get("tagAnnotations"), kv_list) + if not match and fail_module: + msg = "Provided Annotation Key '{0}' does not match the existing Annotation(s): {1}".format(annotation_key, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template_anp_epg_annotation"] = match + def set_template_external_epg(self, external_epg, fail_module=True): """ Get template external epg item that matches the name of an anp. diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py new file mode 100644 index 00000000..44f6fd55 --- /dev/null +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -0,0 +1,228 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: mso_schema_template_anp_epg_annotation +short_description: Manage EPG Annotations on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage Endpoint Group (EPG) Annotations on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.1 (NDO v4.3) and later. +author: +- Samita Bhattacharjee (@samiib) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the Application Network Profile (ANP). + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + required: true + annotation_key: + description: + - The key of the Annotation object. + type: str + aliases: [ key ] + annotation_value: + description: + - The value of the Annotation object. + type: str + aliases: [ value ] + state: + description: + - Use C(absent) for removing. + - Use C(query) for listing an object or multiple objects. + - Use C(present) for creating or updating. + type: str + choices: [ absent, query, present ] + default: query +extends_documentation_fragment: cisco.mso.modules +notes: +- The O(schema) and O(template) must exist before using this module in your playbook. + Use M(cisco.mso.mso_schema_template) to create the schema template. +- The O(anp) must exist before using this module in your playbook. + Use M(cisco.mso.mso_schema_template_anp) to create the ANP. +- The O(epg) must exist before using this module in your playbook. + Use M(cisco.mso.mso_schema_template_anp_epg) to create the EPG. +""" + +EXAMPLES = r""" +- name: Add an annotation with key and value + cisco.mso.mso_schema_template_anp_epg_annotation: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + annotation_key: "annotation_key_1" + annotation_value: "annotation_value_1" + state: present + +- name: Update an annotation with value + cisco.mso.mso_schema_template_anp_epg_annotation: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + annotation_key: "annotation_key_1_updated" + annotation_value: "annotation_value_1" + state: present + +- name: Query a specific annotation with key + cisco.mso.mso_schema_template_anp_epg_annotation: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + annotation_key: "annotation_key_1_updated" + state: query + register: query_one + +- name: Query all annotations + cisco.mso.mso_schema_template_anp_epg_annotation: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + state: query + register: query_all + +- name: Delete an annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + annotation_key: "annotation_key_1_updated" + state: absent +""" + +RETURN = r""" +""" + +import copy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + annotation_key=dict(type="str", aliases=["key"], no_log=False), + annotation_value=dict(type="str", aliases=["value"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["annotation_key"]], + ["state", "present", ["annotation_key", "annotation_value"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template") + anp = module.params.get("anp") + epg = module.params.get("epg") + annotation_key = module.params.get("annotation_key") + annotation_value = module.params.get("annotation_value") + state = module.params.get("state") + + mso = MSOModule(module) + + mso_schema = MSOSchema(mso, schema, template) + mso_schema.set_template(template) + mso_schema.set_template_anp(anp) + mso_schema.set_template_anp_epg(epg) + + if annotation_key: + mso_schema.set_template_anp_epg_annotation(annotation_key, False) + annotation = mso_schema.schema_objects.get("template_anp_epg_annotation") + if annotation is not None: + mso.existing = mso.previous = copy.deepcopy(annotation.details) # Query a specific Annotation + else: + epg_object = mso_schema.schema_objects["template_anp_epg"] + mso.existing = epg_object.details.get("tagAnnotations", []) # Query all + + path = "/templates/{0}/anps/{1}/epgs/{2}/tagAnnotations".format(template, anp, epg) + + ops = [] + if state == "present": + mso_values = {"tagKey": annotation_key, "tagValue": annotation_value} + mso.sanitize(mso_values) + if mso.existing: + # An error from MSO prevents the "replace" operation from updating existing annotation values. + # Remove and add operations are used in sequence to achieve the same result instead. + # append_update_ops_data(ops, mso.existing, "{0}/{1}".format(path, annotation.index), mso_values) + if annotation_value is not None and mso.existing.get("tagValue") != annotation_value: + append_remove_op(ops, path, annotation.index) + append_add_op(ops, path, mso_values) + else: + append_add_op(ops, path, mso_values) + elif state == "absent": + if mso.existing: + append_remove_op(ops, path, annotation.index) + + if not module.check_mode and ops: + mso.request(mso_schema.path, method="PATCH", data=ops) + mso.existing = mso.proposed + elif module.check_mode and state != "query": # When the state is present/absent with check mode + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +def append_add_op(ops, path, mso_values): + ops.append({"op": "add", "path": "{0}/-".format(path), "value": mso_values}) + + +def append_remove_op(ops, path, index): + ops.append({"op": "remove", "path": "{0}/{1}".format(path, index)}) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/mso_schema_template_anp_epg_annotation/aliases b/tests/integration/targets/mso_schema_template_anp_epg_annotation/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/mso_schema_template_anp_epg_annotation/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml new file mode 100644 index 00000000..af785f4e --- /dev/null +++ b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml @@ -0,0 +1,215 @@ +# Test code for the MSO modules + +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + ansible.builtin.fail: + msg: "Please define the following variables: mso_hostname, mso_username and mso_password." + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: "{{ mso_hostname }}" + username: "{{ mso_username }}" + password: "{{ mso_password }}" + validate_certs: "{{ mso_validate_certs | default(false) }}" + use_ssl: "{{ mso_use_ssl | default(true) }}" + use_proxy: "{{ mso_use_proxy | default(true) }}" + output_level: '{{ mso_output_level | default("debug") }}' + +# QUERY VERSION +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for NDO version > 4.2 + when: version.current.version is version('4.2', '>') + block: + + # SETUP + - name: Remove schemas + cisco.mso.mso_schema: &ansible_schema_absent + <<: *mso_info + schema: ansible_test + state: absent + + - name: Ensure ansible_test tenant absent + cisco.mso.mso_tenant: &ansible_tenant_absent + <<: *mso_info + tenant: ansible_test_samita + users: + - "{{ mso_username }}" + state: absent + + - name: Ensure ansible_test tenant present + cisco.mso.mso_tenant: &ansible_tenant_present + <<: *ansible_tenant_absent + state: present + + - name: Ensure ansible_test schema with ans_test_template exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: ansible_test + tenant: ansible_test_samita + template: ans_test_template + state: present + + - name: Ensure ans_test_anp exist + cisco.mso.mso_schema_template_anp: + <<: *mso_info + schema: ansible_test + template: ans_test_template + anp: ans_test_anp + state: present + + - name: Ensure ans_test_epg exist + cisco.mso.mso_schema_template_anp_epg: &ans_test_epg_present + <<: *mso_info + schema: ansible_test + template: ans_test_template + anp: ans_test_anp + epg: ans_test_epg + state: present + + # CREATE + - name: Create an EPG annotation (check mode) + cisco.mso.mso_schema_template_anp_epg_annotation: &add_annotation_1 + <<: *ans_test_epg_present + annotation_key: "annotation_key_1" + annotation_value: "annotation_value_1" + check_mode: true + register: cm_add_epg_annotation + + - name: Create an EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *add_annotation_1 + register: nm_add_epg_annotation + + - name: Create an EPG annotation again + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *add_annotation_1 + register: nm_add_epg_annotation_again + + - name: Assert that the EPG annotation was created + ansible.builtin.assert: + that: + - cm_add_epg_annotation is changed + - cm_add_epg_annotation.previous == nm_add_epg_annotation.previous == {} + - cm_add_epg_annotation.current.tagKey == cm_add_epg_annotation.proposed.tagKey == 'annotation_key_1' + - cm_add_epg_annotation.current.tagValue == cm_add_epg_annotation.proposed.tagValue == 'annotation_value_1' + - nm_add_epg_annotation is changed + - nm_add_epg_annotation.current.tagKey == 'annotation_key_1' + - nm_add_epg_annotation.current.tagValue == 'annotation_value_1' + - nm_add_epg_annotation_again is not changed + - nm_add_epg_annotation_again.sent == {} + - nm_add_epg_annotation_again.previous.tagKey == nm_add_epg_annotation_again.current.tagKey == 'annotation_key_1' + - nm_add_epg_annotation_again.previous.tagValue == nm_add_epg_annotation_again.current.tagValue == 'annotation_value_1' + + # UPDATE + - name: Update an EPG annotation (check mode) + cisco.mso.mso_schema_template_anp_epg_annotation: &update_annotation_1 + <<: *add_annotation_1 + annotation_value: "annotation_value_1_updated" + check_mode: true + register: cm_update_epg_annotation + + - name: Update an EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *update_annotation_1 + register: nm_update_epg_annotation + + - name: Update an EPG annotation again + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *update_annotation_1 + register: nm_update_epg_annotation_again + + - name: Assert that the EPG annotation was updated + ansible.builtin.assert: + that: + - cm_update_epg_annotation is changed + - cm_update_epg_annotation.previous == nm_update_epg_annotation.previous == nm_add_epg_annotation.current + - cm_update_epg_annotation.current.tagKey == cm_update_epg_annotation.proposed.tagKey == 'annotation_key_1' + - cm_update_epg_annotation.current.tagValue == cm_update_epg_annotation.proposed.tagValue == 'annotation_value_1_updated' + - nm_update_epg_annotation is changed + - nm_update_epg_annotation.current.tagKey == 'annotation_key_1' + - nm_update_epg_annotation.current.tagValue == 'annotation_value_1_updated' + - nm_update_epg_annotation_again is not changed + - nm_update_epg_annotation_again.sent == {} + - nm_update_epg_annotation_again.previous.tagKey == nm_update_epg_annotation_again.current.tagKey == 'annotation_key_1' + - nm_update_epg_annotation_again.previous.tagValue == nm_update_epg_annotation_again.current.tagValue == 'annotation_value_1_updated' + + # QUERY + - name: Create another EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *add_annotation_1 + annotation_key: "annotation_key_2" + annotation_value: "annotation_value_2" + + - name: Query all EPG annotations + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *ans_test_epg_present + state: query + register: query_all + + - name: Query one EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *ans_test_epg_present + annotation_key: "annotation_key_2" + state: query + ignore_errors: true + register: query_one + + - name: Verify query_one and query_all + ansible.builtin.assert: + that: + - query_all is not changed + - query_one is not changed + - query_all.current | length == 2 + - query_all.current.0.tagKey == "annotation_key_1" + - query_all.current.1.tagKey == "annotation_key_2" + - query_one.current.tagKey == "annotation_key_2" + - query_one.current.tagValue == "annotation_value_2" + + # DELETE + - name: Delete an EPG annotation (check mode) + cisco.mso.mso_schema_template_anp_epg_annotation: &delete_annotation_1 + <<: *add_annotation_1 + state: absent + check_mode: true + register: cm_delete_epg_annotation + + - name: Delete an EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *delete_annotation_1 + register: nm_delete_epg_annotation + + - name: Delete an EPG annotation again + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *delete_annotation_1 + register: nm_delete_epg_annotation_again + + - name: Assert that the Interface Policy group with name was deleted + ansible.builtin.assert: + that: + - cm_delete_epg_annotation is changed + - nm_delete_epg_annotation is changed + - cm_delete_epg_annotation.current == nm_delete_epg_annotation.current == {} + - cm_delete_epg_annotation.previous.tagKey == nm_delete_epg_annotation.previous.tagKey == "annotation_key_1" + - cm_delete_epg_annotation.previous.tagValue == nm_delete_epg_annotation.previous.tagValue == "annotation_value_1_updated" + - nm_delete_epg_annotation_again is not changed + - nm_delete_epg_annotation_again.previous == nm_delete_epg_annotation_again.current == {} + + # CLEANUP + - name: Remove ansible_test schema + cisco.mso.mso_schema: + <<: *ansible_schema_absent + + - name: Remove ansible_test tenant + cisco.mso.mso_tenant: + <<: *ansible_tenant_absent From 31d7ffec7d29afe119a101e2e3cd04037675106c Mon Sep 17 00:00:00 2001 From: samitab Date: Wed, 18 Dec 2024 20:12:18 +1000 Subject: [PATCH 2/6] [ignore] Added extra error test cases and updated supported ND/NDO version. --- .../mso_schema_template_anp_epg_annotation.py | 2 +- .../tasks/main.yml | 46 ++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py index 44f6fd55..af90b04d 100644 --- a/plugins/modules/mso_schema_template_anp_epg_annotation.py +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -17,7 +17,7 @@ short_description: Manage EPG Annotations on Cisco Nexus Dashboard Orchestrator (NDO). description: - Manage Endpoint Group (EPG) Annotations on Cisco Nexus Dashboard Orchestrator (NDO). -- This module is only supported on ND v3.1 (NDO v4.3) and later. +- This module is only supported on ND v3.0 (NDO v4.2) and later. author: - Samita Bhattacharjee (@samiib) options: diff --git a/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml index af785f4e..6562caab 100644 --- a/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml +++ b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml @@ -28,8 +28,8 @@ state: query register: version -- name: Execute tasks only for NDO version > 4.2 - when: version.current.version is version('4.2', '>') +- name: Execute tasks only for NDO version >= 4.2 + when: version.current.version is version('4.2', '>=') block: # SETUP @@ -42,7 +42,7 @@ - name: Ensure ansible_test tenant absent cisco.mso.mso_tenant: &ansible_tenant_absent <<: *mso_info - tenant: ansible_test_samita + tenant: ansible_test users: - "{{ mso_username }}" state: absent @@ -56,7 +56,7 @@ cisco.mso.mso_schema_template: <<: *mso_info schema: ansible_test - tenant: ansible_test_samita + tenant: ansible_test template: ans_test_template state: present @@ -152,13 +152,13 @@ annotation_value: "annotation_value_2" - name: Query all EPG annotations - cisco.mso.mso_schema_template_anp_epg_annotation: + cisco.mso.mso_schema_template_anp_epg_annotation: &query_all <<: *ans_test_epg_present state: query register: query_all - name: Query one EPG annotation - cisco.mso.mso_schema_template_anp_epg_annotation: + cisco.mso.mso_schema_template_anp_epg_annotation: &query_one <<: *ans_test_epg_present annotation_key: "annotation_key_2" state: query @@ -205,6 +205,40 @@ - nm_delete_epg_annotation_again is not changed - nm_delete_epg_annotation_again.previous == nm_delete_epg_annotation_again.current == {} + # ERRORS + - name: Delete the remaining EPG annotation + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *delete_annotation_1 + annotation_key: "annotation_key_2" + + - name: Query all EPG annotations when none exist + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *query_all + register: query_all_none + + - name: Query an EPG annotation that does not exist + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *query_one + annotation_key: "annotation_key_3" + register: query_one_none + + - name: Create an EPG annotation when EPG does not exist + cisco.mso.mso_schema_template_anp_epg_annotation: + <<: *add_annotation_1 + epg: ans_test_epg_none + ignore_errors: true + register: non_existing_epg + + - name: Assert error conditions + ansible.builtin.assert: + that: + - query_all_none is not changed + - query_all_none.current == [] + - query_one_none is not changed + - query_one_none.current == {} + - non_existing_epg is failed + - non_existing_epg.msg == "Provided EPG 'ans_test_epg_none' not matching existing epg(s){{":"}} ans_test_epg" + # CLEANUP - name: Remove ansible_test schema cisco.mso.mso_schema: From 94291f40ae3cf8d1544336fbd6323adaf800234c Mon Sep 17 00:00:00 2001 From: samitab Date: Wed, 18 Dec 2024 20:27:35 +1000 Subject: [PATCH 3/6] [ignore] Updated state option doc --- plugins/modules/mso_schema_template_anp_epg_annotation.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py index af90b04d..5b9969ef 100644 --- a/plugins/modules/mso_schema_template_anp_epg_annotation.py +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -53,12 +53,11 @@ aliases: [ value ] state: description: - - Use C(absent) for removing. + - Use C(present) or C(absent) for adding or removing. - Use C(query) for listing an object or multiple objects. - - Use C(present) for creating or updating. type: str - choices: [ absent, query, present ] - default: query + choices: [ absent, present, query ] + default: present extends_documentation_fragment: cisco.mso.modules notes: - The O(schema) and O(template) must exist before using this module in your playbook. From 7a9632396c3ad8e05b57fbaabf90bacd9452aedb Mon Sep 17 00:00:00 2001 From: samitab Date: Thu, 19 Dec 2024 19:36:57 +1000 Subject: [PATCH 4/6] [ignore] Fixed `notes` section and added `seealso` --- .../mso_schema_template_anp_epg_annotation.py | 22 ++++++++++--------- .../tasks/main.yml | 14 ++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py index 5b9969ef..f2639b15 100644 --- a/plugins/modules/mso_schema_template_anp_epg_annotation.py +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -60,12 +60,14 @@ default: present extends_documentation_fragment: cisco.mso.modules notes: -- The O(schema) and O(template) must exist before using this module in your playbook. - Use M(cisco.mso.mso_schema_template) to create the schema template. -- The O(anp) must exist before using this module in your playbook. +- The O(schema), O(template), O(anp) and O(epg) must exist before using this module in your playbook. + Use M(cisco.mso.mso_schema_template) to create the schema and template. Use M(cisco.mso.mso_schema_template_anp) to create the ANP. -- The O(epg) must exist before using this module in your playbook. Use M(cisco.mso.mso_schema_template_anp_epg) to create the EPG. +seealso: +- module: cisco.mso.mso_schema_template +- module: cisco.mso.mso_schema_template_anp +- module: cisco.mso.mso_schema_template_anp_epg """ EXAMPLES = r""" @@ -78,8 +80,8 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: "annotation_key_1" - annotation_value: "annotation_value_1" + annotation_key: annotation_key_1 + annotation_value: annotation_value_1 state: present - name: Update an annotation with value @@ -91,8 +93,8 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: "annotation_key_1_updated" - annotation_value: "annotation_value_1" + annotation_key: annotation_key_1_updated + annotation_value: annotation_value_1 state: present - name: Query a specific annotation with key @@ -104,7 +106,7 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: "annotation_key_1_updated" + annotation_key: annotation_key_1_updated state: query register: query_one @@ -129,7 +131,7 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: "annotation_key_1_updated" + annotation_key: annotation_key_1_updated state: absent """ diff --git a/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml index 6562caab..fe2f7232 100644 --- a/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml +++ b/tests/integration/targets/mso_schema_template_anp_epg_annotation/tasks/main.yml @@ -81,8 +81,8 @@ - name: Create an EPG annotation (check mode) cisco.mso.mso_schema_template_anp_epg_annotation: &add_annotation_1 <<: *ans_test_epg_present - annotation_key: "annotation_key_1" - annotation_value: "annotation_value_1" + annotation_key: annotation_key_1 + annotation_value: annotation_value_1 check_mode: true register: cm_add_epg_annotation @@ -148,8 +148,8 @@ - name: Create another EPG annotation cisco.mso.mso_schema_template_anp_epg_annotation: <<: *add_annotation_1 - annotation_key: "annotation_key_2" - annotation_value: "annotation_value_2" + annotation_key: annotation_key_2 + annotation_value: annotation_value_2 - name: Query all EPG annotations cisco.mso.mso_schema_template_anp_epg_annotation: &query_all @@ -160,7 +160,7 @@ - name: Query one EPG annotation cisco.mso.mso_schema_template_anp_epg_annotation: &query_one <<: *ans_test_epg_present - annotation_key: "annotation_key_2" + annotation_key: annotation_key_2 state: query ignore_errors: true register: query_one @@ -209,7 +209,7 @@ - name: Delete the remaining EPG annotation cisco.mso.mso_schema_template_anp_epg_annotation: <<: *delete_annotation_1 - annotation_key: "annotation_key_2" + annotation_key: annotation_key_2 - name: Query all EPG annotations when none exist cisco.mso.mso_schema_template_anp_epg_annotation: @@ -219,7 +219,7 @@ - name: Query an EPG annotation that does not exist cisco.mso.mso_schema_template_anp_epg_annotation: <<: *query_one - annotation_key: "annotation_key_3" + annotation_key: annotation_key_3 register: query_one_none - name: Create an EPG annotation when EPG does not exist From cff0de1a9780cd78fa0d4f578d77fab3c8b3cb26 Mon Sep 17 00:00:00 2001 From: samitab Date: Thu, 19 Dec 2024 19:43:17 +1000 Subject: [PATCH 5/6] [ignore] Fixed examples --- .../modules/mso_schema_template_anp_epg_annotation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py index f2639b15..4b994ab8 100644 --- a/plugins/modules/mso_schema_template_anp_epg_annotation.py +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -84,7 +84,7 @@ annotation_value: annotation_value_1 state: present -- name: Update an annotation with value +- name: Update an annotation value with key cisco.mso.mso_schema_template_anp_epg_annotation: host: mso_host username: admin @@ -93,8 +93,8 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: annotation_key_1_updated - annotation_value: annotation_value_1 + annotation_key: annotation_key_1 + annotation_value: annotation_value_1_updated state: present - name: Query a specific annotation with key @@ -106,7 +106,7 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: annotation_key_1_updated + annotation_key: annotation_key_1 state: query register: query_one @@ -131,7 +131,7 @@ template: Template 1 anp: ANP 1 epg: EPG 1 - annotation_key: annotation_key_1_updated + annotation_key: annotation_key_1 state: absent """ From 8ab2e413e18ee893945041f49151c27cb36270a4 Mon Sep 17 00:00:00 2001 From: samitab Date: Fri, 20 Dec 2024 16:19:50 +1000 Subject: [PATCH 6/6] [ignore] Use replace op to update tagValue --- .../mso_schema_template_anp_epg_annotation.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/plugins/modules/mso_schema_template_anp_epg_annotation.py b/plugins/modules/mso_schema_template_anp_epg_annotation.py index 4b994ab8..859fadea 100644 --- a/plugins/modules/mso_schema_template_anp_epg_annotation.py +++ b/plugins/modules/mso_schema_template_anp_epg_annotation.py @@ -196,17 +196,14 @@ def main(): mso_values = {"tagKey": annotation_key, "tagValue": annotation_value} mso.sanitize(mso_values) if mso.existing: - # An error from MSO prevents the "replace" operation from updating existing annotation values. - # Remove and add operations are used in sequence to achieve the same result instead. - # append_update_ops_data(ops, mso.existing, "{0}/{1}".format(path, annotation.index), mso_values) if annotation_value is not None and mso.existing.get("tagValue") != annotation_value: - append_remove_op(ops, path, annotation.index) - append_add_op(ops, path, mso_values) + ops.append({"op": "replace", "path": "{0}/{1}".format(path, annotation.index), "value": mso_values}) + else: - append_add_op(ops, path, mso_values) + ops.append({"op": "add", "path": "{0}/-".format(path), "value": mso_values}) elif state == "absent": if mso.existing: - append_remove_op(ops, path, annotation.index) + ops.append({"op": "remove", "path": "{0}/{1}".format(path, annotation.index)}) if not module.check_mode and ops: mso.request(mso_schema.path, method="PATCH", data=ops) @@ -217,13 +214,5 @@ def main(): mso.exit_json() -def append_add_op(ops, path, mso_values): - ops.append({"op": "add", "path": "{0}/-".format(path), "value": mso_values}) - - -def append_remove_op(ops, path, index): - ops.append({"op": "remove", "path": "{0}/{1}".format(path, index)}) - - if __name__ == "__main__": main()