diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index ccfe3dfbf..ad95d039c 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -319,15 +319,17 @@ def write_file(module, url, dest, content, resp, tmpsrc=None): def format_interface_descriptions(interface_descriptions, node=None): - formated_interface_descriptions = [ - { - "nodeID": node if node is not None else interface_description.get("node"), - "interfaceID": interface_description.get("interface_id", interface_description.get("interfaceID")), - "description": interface_description.get("description"), - } - for interface_description in interface_descriptions - ] - + if interface_descriptions: + formated_interface_descriptions = [ + { + "nodeID": node if node is not None else interface_description.get("node"), + "interfaceID": interface_description.get("interface_id", interface_description.get("interfaceID")), + "description": interface_description.get("description"), + } + for interface_description in interface_descriptions + ] + else: + formated_interface_descriptions = [] return formated_interface_descriptions diff --git a/plugins/modules/ndo_port_channel_interface.py b/plugins/modules/ndo_port_channel_interface.py index bc1c77303..3869701a9 100644 --- a/plugins/modules/ndo_port_channel_interface.py +++ b/plugins/modules/ndo_port_channel_interface.py @@ -25,86 +25,86 @@ author: - Gaspard Micol (@gmicol) options: - template: - description: - - The name of the template. - - The template must be a Fabric Resource template. - type: str - required: true - port_channel_interface: - description: - - The name of the Port Channel Interface. - type: str - aliases: [ name, port_channel ] - port_channel_interface_uuid: - description: - - The UUID of the Port Channel Interface. - - This parameter can be used instead of O(port_channel_interface) - when an existing Virtual Port Channel Interface is updated. - - This parameter is required when parameter O(port_channel_interface) is updated. - type: str - aliases: [ uuid, port_channel_uuid ] + template: + description: + - The name of the template. + - The template must be a Fabric Resource template. + type: str + required: true + name: + description: + - The name of the Port Channel Interface. + type: str + aliases: [ port_channel_interface, port_channel ] + uuid: description: + - The UUID of the Port Channel Interface. + - This parameter can be used instead of O(port_channel_interface) + when an existing Virtual Port Channel Interface is updated. + - This parameter is required when parameter O(port_channel_interface) is updated. + type: str + aliases: [ port_channel_interface_uuid, port_channel_uuid ] + description: + description: + - The description of the Port Channel Interface. + type: str + node: + description: + - The node ID. + - This is only required when creating a new Port Channel Interface. + type: str + interfaces: + description: + - The list of used Interface IDs. + - Ranges of Interface IDs can be used. + - This is only required when creating a new Port Channel Interface. + type: list + elements: str + aliases: [ members ] + interface_policy_group_uuid: + description: + - The UUID of the Port Channel Interface Policy Group. + - This is only required when creating a new Port Channel Interface. + type: str + aliases: [ policy_uuid, interface_policy_uuid, interface_setting_uuid ] + interface_policy_group: + description: + - The Port Channel Interface Policy Group. + - This parameter can be used instead of O(interface_policy_group_uuid). + - If both parameter are used, O(interface_policy_group) will be ignored. + type: dict + suboptions: + name: description: - - The description of the Port Channel Interface. + - The name of the Interface Policy Group. type: str - node: + template: description: - - The node ID. - - This is only required when creating a new Port Channel Interface. + - The name of the template in which the Interface Policy Group has been created. type: str - interfaces: - description: - - The list of used Interface IDs. - - Ranges of Interface IDs can be used. - - This is only required when creating a new Port Channel Interface. - type: list - elements: str - aliases: [ members ] - interface_policy_group_uuid: + aliases: [ policy, interface_policy, interface_setting ] + interface_descriptions: + description: + - The list of interface descriptions. + type: list + elements: dict + suboptions: + interface_id: description: - - The UUID of the Port Channel Interface Setting Policy. - - This is only required when creating a new Port Channel Interface. + - The interface ID. type: str - aliases: [ policy_uuid, interface_policy_uuid, interface_setting_uuid ] - interface_policy_group: - description: - - The name of the Port Channel Interface Policy Group. - - This parameter can be used instead of O(interface_policy_group_uuid). - - If both parameter are used, O(interface_policy_group) will be ignored. - type: dict - suboptions: - name: - description: - - The name of the Port Channel Interface Policy Group. - type: str - template: - description: - - The name of the template in which the Port Channel Interface Policy Group has been created. - type: str - aliases: [ policy, interface_policy, interface_setting ] - interface_descriptions: + description: description: - - The list of interface descriptions. - type: list - elements: dict - suboptions: - interface_id: - description: - - The interface ID. - type: str - description: - description: - - The description of the interface. - type: str - state: - description: - - Use C(absent) for removing. - - Use C(query) for listing an object or multiple objects. - - Use C(present) for creating or updating. + - The description of the interface. type: str - choices: [ absent, query, present ] - default: query + 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 """ @@ -116,7 +116,7 @@ password: SomeSecretPassword template: ansible_fabric_resource_template description: My Ansible Port Channel - port_channel_interface: ansible_port_channel_interface + name: ansible_port_channel_interface node: 101 interfaces: - 1/1 @@ -132,38 +132,39 @@ - interface_id: 1/11 description: My third Ansible Interface state: present + register: port_channel_interface_1 -- name: Update a Port Channel Interface's name +- name: Update a Port Channel Interface's name with UUID cisco.mso.ndo_port_channel_interface: host: mso_host username: admin password: SomeSecretPassword template: ansible_fabric_resource_template - port_channel_interface: ansible_port_channel_interface_changed - port_channel_interface_uuid: 0135c73f-4427-4109-9eea-5110ecdf10ea + name: ansible_port_channel_interface_changed + uuid: "{{ port_channel_interface_1.current.uuid }}" state: present -- name: Query a Port Channel Interface using its name in the template +- name: Query a Port Channel Interface with name cisco.mso.ndo_port_channel_interface: host: mso_host username: admin password: SomeSecretPassword template: ansible_fabric_resource_template - port_channel_interface: ansible_port_channel_interface_changed + name: ansible_port_channel_interface_changed state: query register: query_one -- name: Query a Port Channel Interface using its UUID in the template +- name: Query a Port Channel Interface with UUID cisco.mso.ndo_port_channel_interface: host: mso_host username: admin password: SomeSecretPassword template: ansible_fabric_resource_template - port_channel_interface_uuid: 0135c73f-4427-4109-9eea-5110ecdf10ea + uuid: "{{ port_channel_interface_1.current.uuid }}" state: query register: query_one -- name: Query all Port Channel Interfaces in the template +- name: Query all Port Channel Interfaces in a Fabric Resource Template cisco.mso.ndo_port_channel_interface: host: mso_host username: admin @@ -172,22 +173,22 @@ state: query register: query_all -- name: Delete a Port Channel Interface using its name +- name: Delete a Port Channel Interface with name cisco.mso.ndo_port_channel_interface: host: mso_host username: admin password: SomeSecretPassword template: ansible_fabric_resource_template - port_channel_interface: ansible_port_channel_interface_changed + name: ansible_port_channel_interface_changed state: absent -- name: Delete a Port Channel Interface using its UUID +- name: Delete a Port Channel Interface with UUID cisco.mso.ndo_port_channel_interface: host: mso_host username: admin password: SomeSecretPassword template: ansible_fabric_resource_template - port_channel_interface_uuid: 0135c73f-4427-4109-9eea-5110ecdf10ea + uuid: "{{ port_channel_interface_1.current.uuid }}" state: absent """ @@ -211,8 +212,8 @@ def main(): argument_spec = mso_argument_spec() argument_spec.update( template=dict(type="str", required=True), - port_channel_interface=dict(type="str", aliases=["name", "port_channel"]), - port_channel_interface_uuid=dict(type="str", aliases=["uuid", "port_channel_uuid"]), + name=dict(type="str", aliases=["port_channel_interface", "port_channel"]), + uuid=dict(type="str", aliases=["port_channel_interface_uuid", "port_channel_uuid"]), description=dict(type="str"), node=dict(type="str"), interfaces=dict(type="list", elements="str", aliases=["members"]), @@ -240,16 +241,16 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ["state", "absent", ["port_channel_interface", "port_channel_interface_uuid"], True], - ["state", "present", ["port_channel_interface", "port_channel_interface_uuid"], True], + ["state", "absent", ["name", "uuid"], True], + ["state", "present", ["name", "uuid"], True], ], ) mso = MSOModule(module) template = module.params.get("template") - port_channel_interface = module.params.get("port_channel_interface") - port_channel_interface_uuid = module.params.get("port_channel_interface_uuid") + name = module.params.get("name") + uuid = module.params.get("uuid") description = module.params.get("description") node = module.params.get("node") interfaces = module.params.get("interfaces") @@ -260,84 +261,93 @@ def main(): interface_descriptions = module.params.get("interface_descriptions") state = module.params.get("state") - ops = [] - match = None - mso_template = MSOTemplate(mso, "fabric_resource", template) mso_template.validate_template("fabricResource") object_description = "Port Channel Interface" path = "/fabricResourceTemplate/template/portChannels" existing_port_channel_interfaces = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}).get("portChannels", []) - if port_channel_interface or port_channel_interface_uuid: + + if state in ["query", "absent"] and existing_port_channel_interfaces == []: + mso.exit_json() + elif state == "query" and not (name or uuid): + mso.existing = existing_port_channel_interfaces + elif existing_port_channel_interfaces and (name or uuid): match = mso_template.get_object_by_key_value_pairs( object_description, existing_port_channel_interfaces, - [KVPair("uuid", port_channel_interface_uuid) if port_channel_interface_uuid else KVPair("name", port_channel_interface)], + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], ) if match: mso.existing = mso.previous = copy.deepcopy(match.details) - else: - mso.existing = mso.previous = existing_port_channel_interfaces + + ops = [] if state == "present": + if uuid and not mso.existing: + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) if interface_policy_group and not interface_policy_group_uuid: fabric_policy_template = MSOTemplate(mso, "fabric_policy", interface_policy_group.get("template")) fabric_policy_template.validate_template("fabricPolicy") interface_policy_group_uuid = fabric_policy_template.get_interface_policy_group_uuid(interface_policy_group.get("name")) - if match: - if port_channel_interface and match.details.get("name") != port_channel_interface: - ops.append(dict(op="replace", path="{0}/{1}/name".format(path, match.index), value=port_channel_interface)) - match.details["name"] = port_channel_interface + if mso.existing: + proposed_payload = copy.deepcopy(match.details) + + if name and mso.existing.get("name") != name: + ops.append(dict(op="replace", path="{0}/{1}/name".format(path, match.index), value=name)) + proposed_payload["name"] = name - if description is not None and match.details.get("description") != description: + if description is not None and mso.existing.get("description") != description: ops.append(dict(op="replace", path="{0}/{1}/description".format(path, match.index), value=description)) - match.details["description"] = description + proposed_payload["description"] = description node_changed = False - if node and match.details.get("node") != node: + if node and mso.existing.get("node") != node: ops.append(dict(op="replace", path="{0}/{1}/node".format(path, match.index), value=node)) - match.details["node"] = node + proposed_payload["node"] = node node_changed = True - if interface_policy_group_uuid and match.details.get("policy") != interface_policy_group_uuid: + if interface_policy_group_uuid and mso.existing.get("policy") != interface_policy_group_uuid: ops.append(dict(op="replace", path="{0}/{1}/policy".format(path, match.index), value=interface_policy_group_uuid)) - match.details["policy"] = interface_policy_group_uuid + proposed_payload["policy"] = interface_policy_group_uuid - if interfaces and interfaces != match.details.get("memberInterfaces"): + if interfaces and interfaces != mso.existing.get("memberInterfaces"): ops.append(dict(op="replace", path="{0}/{1}/memberInterfaces".format(path, match.index), value=interfaces)) - match.details["memberInterfaces"] = interfaces + proposed_payload["memberInterfaces"] = interfaces - if interface_descriptions or (node_changed and match.details.get("interfaceDescriptions")): + if interface_descriptions or (node_changed and mso.existing.get("interfaceDescriptions")): if node_changed and interface_descriptions is None: - interface_descriptions = format_interface_descriptions(match.details["interfaceDescriptions"], node) + interface_descriptions = format_interface_descriptions(mso.existing["interfaceDescriptions"], node) else: - interface_descriptions = format_interface_descriptions(interface_descriptions, match.details["node"]) - if interface_descriptions != match.details.get("interfaceDescriptions"): + interface_descriptions = format_interface_descriptions(interface_descriptions, proposed_payload["node"]) + if interface_descriptions != mso.existing.get("interfaceDescriptions"): ops.append(dict(op="replace", path="{0}/{1}/interfaceDescriptions".format(path, match.index), value=interface_descriptions)) - match.details["interfaceDescriptions"] = interface_descriptions - elif interface_descriptions == [] and match.details["interfaceDescriptions"]: + proposed_payload["interfaceDescriptions"] = interface_descriptions + elif interface_descriptions == [] and mso.existing.get("interfaceDescriptions"): ops.append(dict(op="remove", path="{0}/{1}/interfaceDescriptions".format(path, match.index))) + proposed_payload["interfaceDescriptions"] = [] - mso.sanitize(match.details) + mso.sanitize(proposed_payload, collate=True) else: if not node: mso.fail_json(msg=("ERROR: Missing parameter 'node' for creating a Port Channel Interface")) - payload = {"name": port_channel_interface, "node": node, "memberInterfaces": interfaces, "policy": interface_policy_group_uuid} - if description is not None: - payload["description"] = description - if interface_descriptions: - interface_descriptions = format_interface_descriptions(interface_descriptions, node) - payload["interfaceDescriptions"] = interface_descriptions - ops.append(dict(op="add", path="{0}/-".format(path), value=payload)) + payload = { + "name": name, + "node": node, + "memberInterfaces": interfaces, + "policy": interface_policy_group_uuid, + "description": description, + "interfaceDescriptions": format_interface_descriptions(interface_descriptions, node), + } mso.sanitize(payload) + ops.append(dict(op="add", path="{0}/-".format(path), value=mso.sent)) elif state == "absent": - if match: + if mso.existing: ops.append(dict(op="remove", path="{0}/{1}".format(path, match.index))) if not module.check_mode and ops: @@ -346,7 +356,7 @@ def main(): match = mso_template.get_object_by_key_value_pairs( object_description, port_channel_interfaces, - [KVPair("uuid", port_channel_interface_uuid) if port_channel_interface_uuid else KVPair("name", port_channel_interface)], + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], ) if match: mso.existing = match.details