diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b3e0c..8e9a8f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # cisco.intersight Ansible Collection Changelog +## Version 2.0.17 +- Fixes #135 to support JSON Patch of existing resources with example port_policy_json_patch.yml playbook + ## Version 2.0.16 - Fixes #133 to add support for Power Policies in Server Profiles diff --git a/galaxy.yml b/galaxy.yml index 33bf5e1..0614bdc 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -10,7 +10,7 @@ namespace: cisco name: intersight # The version of the collection. Must be compatible with semantic versioning -version: 2.0.16 +version: 2.0.17 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/playbooks/port_policy_json_patch.yml b/playbooks/port_policy_json_patch.yml new file mode 100644 index 0000000..7e3fa29 --- /dev/null +++ b/playbooks/port_policy_json_patch.yml @@ -0,0 +1,100 @@ +--- +# +# Configure Fabric Port Policies +# +- name: Configure Fabric Port Policies + hosts: localhost + connection: local + gather_facts: false + vars: + # Create an anchor for api_info that can be used throughout the file + api_info: &api_info + # if api_key vars are omitted, INTERSIGHT_API_KEY_ID, INTERSIGHT_API_PRIVATE_KEY, + # and INTERSIGHT_API_URI environment variables used for API key data + api_private_key: "{{ api_private_key | default(omit) }}" + api_key_id: "{{ api_key_id | default(omit) }}" + api_uri: "{{ api_uri | default(omit) }}" + validate_certs: "{{ validate_certs | default(omit) }}" + # Port Policy name + port_name: eth-pc + org_name: dsoper-DevNet + tasks: + # Get the Organization Moid + - name: "Get Organization Moid" + cisco.intersight.intersight_rest_api: + <<: *api_info + resource_path: /organization/Organizations + query_params: + $filter: "Name eq '{{ org_name }}'" + register: org_resp + # Config Port Policy + - name: "Configure Port Policy" + cisco.intersight.intersight_rest_api: + <<: *api_info + state: "{{ state | default('present') }}" + resource_path: /fabric/PortPolicies + query_params: + $filter: "Name eq '{{ port_name }}'" + api_body: { + "Name": "{{ port_name }}", + "DeviceModel": "UCS-FI-6454", + "Organization": { + "Moid": "{{ org_resp.api_response.Moid }}" + } + } + register: port_resp + # Config Uplink Port Channel Roles + - name: "Configure Uplink Port Channel Roles" + cisco.intersight.intersight_rest_api: + <<: *api_info + resource_path: /fabric/UplinkPcRoles + query_params: + $filter: "PortPolicy.Moid eq '{{ port_resp.api_response.Moid }}'" + api_body: { + "AdminSpeed": "Auto", + "PcId": 47, + "PortPolicy": { + "Moid": "{{ port_resp.api_response.Moid }}" + }, + "Ports": [ + { + "PortId": 47, + "SlotId": 1 + } + ] + } + when: port_resp.api_response is defined and port_resp.api_response + # JSON Patch for Uplink Port Channel Roles + - name: "JSON Patch Uplink Port Channel Roles" + cisco.intersight.intersight_rest_api: + <<: *api_info + resource_path: /fabric/UplinkPcRoles + query_params: + $filter: "PortPolicy.Moid eq '{{ port_resp.api_response.Moid }}'" + update_method: json-patch + list_body: [ + { + "op": "add", + "path": "/AdminSpeed", + "value": "Auto" + }, + { + "op": "add", + "path": "/PcId", + "value": 47 + }, + { + "op": "add", + "path": "/PortPolicy/Moid", + "value": "{{ port_resp.api_response.Moid }}" + }, + { + "op": "add", + "path": "/Ports/-", + "value": { + "PortId": 48, + "SlotId": 1 + } + } + ] + when: port_resp.api_response is defined and port_resp.api_response diff --git a/plugins/module_utils/intersight.py b/plugins/module_utils/intersight.py index 29c4deb..7585c1e 100644 --- a/plugins/module_utils/intersight.py +++ b/plugins/module_utils/intersight.py @@ -154,6 +154,7 @@ def __init__(self, module): self.private_key = self.module.params['api_private_key'] self.digest_algorithm = '' self.response_list = [] + self.update_method = '' def get_sig_b64encode(self, data): """ @@ -285,6 +286,9 @@ def intersight_call(self, http_method="", resource_path="", query_params=None, b if (moid is not None and len(moid.encode('utf-8')) != 24): raise ValueError('Invalid *moid* value!') + if (method != 'PATCH' and self.update_method == 'json-patch'): + raise ValueError('json-patch is only supported with PATCH on existing resource') + # Check for query_params, encode, and concatenate onto URL if query_params: query_path = "?" + urlencode(query_params) @@ -331,9 +335,13 @@ def intersight_call(self, http_method="", resource_path="", query_params=None, b auth_header = self.get_auth_header(auth_header, b64_signed_msg) # Generate the HTTP requests header + if self.update_method == 'json-patch': + content_type = 'application/json-patch+json' + else: + content_type = 'application/json' request_header = { 'Accept': 'application/json', - 'Content-Type': 'application/json', + 'Content-Type': content_type, 'Host': '{0}'.format(target_host), 'Date': '{0}'.format(cdate), 'Digest': 'SHA-256={0}'.format(b64_body_digest.decode('ascii')), @@ -367,6 +375,7 @@ def get_resource(self, resource_path, query_params, return_list=False): self.result['trace_id'] = response.get('trace_id') def configure_resource(self, moid, resource_path, body, query_params, update_method=''): + self.update_method = update_method if not self.module.check_mode: if moid and update_method != 'post': # update the resource - user has to specify all the props they want updated diff --git a/plugins/modules/intersight_rest_api.py b/plugins/modules/intersight_rest_api.py index 1b9e943..c05310c 100644 --- a/plugins/modules/intersight_rest_api.py +++ b/plugins/modules/intersight_rest_api.py @@ -33,8 +33,11 @@ description: - The HTTP method used for update operations. - Some Intersight resources require POST operations for modifications. + - json-patch is used for partial updates. + - json-patch is only supported for patch operations on existing resources and requires the list_body to be a list of dictionaries. + - See L(The Intersight API Docs, https://intersight.com/apidocs/introduction/methods/) for details on JSON Patch. type: str - choices: [ patch, post ] + choices: [ patch, post, json-patch ] default: patch api_body: description: @@ -145,7 +148,7 @@ def main(): argument_spec.update( resource_path=dict(type='str', required=True), query_params=dict(type='dict'), - update_method=dict(type='str', choices=['patch', 'post'], default='patch'), + update_method=dict(type='str', choices=['patch', 'post', 'json-patch'], default='patch'), api_body=dict(type='dict'), list_body=dict(type='list', elements='dict'), return_list=dict(type='bool', default=False), diff --git a/releases/cisco-intersight-2.0.17.tar.gz b/releases/cisco-intersight-2.0.17.tar.gz new file mode 100644 index 0000000..648742b Binary files /dev/null and b/releases/cisco-intersight-2.0.17.tar.gz differ