From 24f2ce44232e9a7b3c9fcafb2ebaaf3de6575cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cdsoper2=E2=80=9D?= <“dsoper@cisco.com”> Date: Mon, 9 Sep 2024 10:04:22 -0500 Subject: [PATCH 1/2] Fixes #135 to support JSON Patch of existing resources with example port_policy_json_patch.yml playbook --- CHANGELOG.md | 3 + galaxy.yml | 2 +- playbooks/port_policy_json_patch.yml | 100 ++++++++++++++++++++++++ plugins/module_utils/intersight.py | 11 ++- plugins/modules/intersight_rest_api.py | 6 +- releases/cisco-intersight-2.0.17.tar.gz | Bin 0 -> 997741 bytes 6 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 playbooks/port_policy_json_patch.yml create mode 100644 releases/cisco-intersight-2.0.17.tar.gz 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..ed28429 100644 --- a/plugins/modules/intersight_rest_api.py +++ b/plugins/modules/intersight_rest_api.py @@ -33,8 +33,10 @@ description: - The HTTP method used for update operations. - Some Intersight resources require POST operations for modifications. + - json-patch is used for partial updates. See L(https://intersight.com/apidocs/introduction/methods/) for details on JSON Patch. + - json-patch is only supported for patch operations on existing resources and requires the list_body to be a list of dictionaries. type: str - choices: [ patch, post ] + choices: [ patch, post, json-patch ] default: patch api_body: description: @@ -145,7 +147,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 0000000000000000000000000000000000000000..648742b72e115c0156a42d03cce71b8e65d700dc GIT binary patch literal 997741 zcmV(zK<2+6iwFoj4&P=1|6^%$V{a{KZggdGb7^O2bS*M2FfK7SE_7jX0PKAQToqf_ zzG5MYVq&3)iU~}IU?5?Dk|GF-lYqcE$T^gVg@v6c1_l-(wkUQ7il|@^ieh31ChC6; z>U;D1emC&GzTf{n_vbx_IWuS0+Iy{MJ
Thr7=59VxqgGY vjUS=q5W ~brI}T)
zg>7~};JcTRWxb)ywf7{eSjSP-fkrMAZNi5ri1^a1JGo=!pshuCciZaEXU>KhHLYn(
z-h?Xw1cZ>XEHCCiY{AJG**#&2Us*KnhsmI_r8|~^6afRi``iK#(_{Gr9OB}Rg{JFo
zpozuglshzzPl(EO1u(li?8YIyI1z^BasKg+YL~|$!-H4Nab?Jt0
z%f3`9OMsZ7lv(lwiO~WMz{Nwvfde$cQLI3axUx0?LZS>v2W4SG%QUBWt}%uMN}>=#`tZ^NpS?~iSrbM4lA9n<)Z%Oh{v|vDWoEUxkRAyM8g>f9VRjl3DtNq~H_wL#BNv>Aw$x^FP4V@e(tUiBz
zzJ9ywyE&$-TEE^!;7Gatx&j(-v3*-#v+8qWY)m%Reo?=;i_}}E%`~RZf%1`0SM3Wj
z>sB)Q)d*Ab>QA;!xRPnr!^phWXZys@#7v&nI*-VCGHh>izbdW
N-Z+H3`TwcgII?OXmY5RIO)uRYoI4F#z1+Dr6cXXm%~
zTG~!a_?}+EI}30&@@-IIsRM*(D0ejUdH_T_(&5
ByK-J9TJ;F}TU3?X{GYB27!tSd&~;?z*2*g1
zcRwF~l6yF!?6mzMtEjk@;lw4R${m1PRVJ5DajI)nrVr1C7Me4`U&+KDI%mN%W?yI;
z+S#6)yzzPa{kakTQ=<#zulHF)rl~Ijd^&~5igN<
z+Ns|NK8nEkoEKc5155cHyIW&V@|w}))yD$wzZpXGQ0p-yl_)t=wmeb~JwGMU&7NgY
zy?Y>xI9}X@Atf5^eS-CSXmXRV@HjCCpA>CbQMdA=^;XsBitrxWapva%@x+4`(ER%Kcy8Vx
zO~l9>we7vP#0d&!K*LVOSw$>Ldcuy6vl*RrWzvHXp7;=TGhbbdR(f}j)!bB6_A$x|
zrU~=7N*xMTa9nCfK)ZcN@75c|`e0o3BK0>26PYSlefpHBob9c2B6Q6PNGr#)yai7z
zFww9dh-wx{C=~RyNlT0z%o+43cKSF=wg>RjChhN28Jh+kN#2o#1f)tp-Duu^j#w;(
zo;ne%NPjGzIccVA8x