I have a dict/json type of object that I captured from the output of a REST API call, let's say it's in a variable/register named ouput. The data looks like below. For completeness, I shared the entire object and did some basic cleanup. Please feel free to only focus on that one line I discuss next;-)
{
"_create_time": 1724823837185,
"_create_user": "system",
"_last_modified_time": 1736218001919,
"_last_modified_user": "nsx_policy",
"_protection": "NOT_PROTECTED",
"_revision": 1,
"_system_owned": false,
"display_name": "w1-vc1-c4n17.example.com",
"host_switch_spec": {
"host_switches": [
{
"cpu_config": [],
"ecmp_mode": "L3",
"host_switch_id": "50 01 cb 21 be 19 cb 38-78 bd 67 bc b3 0e 91 f1",
"host_switch_mode": "STANDARD",
"host_switch_name": "w1-vc1-c4-dvs",
"host_switch_profile_ids": [
{
"key": "UplinkHostSwitchProfile",
"value": "/infra/host-switch-profiles/6d7a0ed8-c86a-4190-9121-ce6f863226ae"
},
{
"key": "VtepHAHostSwitchProfile",
"value": "/infra/host-switch-profiles/0de8282e-7385-4e8e-a905-0c11960db728"
}
],
"host_switch_type": "VDS",
"ip_assignment_spec": {
"ip_pool_id": "/infra/ip-pools/w1-nsx1-esxi-teps",
"resource_type": "StaticIpPoolSpec"
},
"is_migrate_pnics": false,
"not_ready": false,
"portgroup_transport_zone_id": "/infra/sites/default/enforcement-points/default/transport-zones/77166711-1f83-407f-8c27-a4e1b8b4e1e5",
"transport_zone_endpoints": [
{
"transport_zone_id": "/infra/sites/default/enforcement-points/default/transport-zones/4b367ddf-d4ac-460b-a884-24846ff4edf6",
"transport_zone_profile_ids": [
{
"profile_id": "/infra/transport-zone-profiles/52035bb3-ab02-4a08-9884-18631312e50a",
"resource_type": "BfdHealthMonitoringProfile"
}
]
},
{
"transport_zone_id": "/infra/sites/default/enforcement-points/default/transport-zones/bc19b340-f0b5-46d8-9454-6d94d3288821",
"transport_zone_profile_ids": [
{
"profile_id": "/infra/transport-zone-profiles/52035bb3-ab02-4a08-9884-18631312e50a",
"resource_type": "BfdHealthMonitoringProfile"
}
]
}
],
"uplinks": [
{
"uplink_name": "uplink-2",
"vds_uplink_name": "uplink2"
},
{
"uplink_name": "uplink-1",
"vds_uplink_name": "uplink1"
}
]
}
],
"resource_type": "StandardHostSwitchSpec"
},
"id": "w1-vc1-c4n17-6adb838f-669c-48ca-b76f-e1238984ef93host-2171",
"is_overridden": false,
"maintenance_mode": "DISABLED",
"marked_for_delete": false,
"node_deployment_info": {
"compute_collection_id": "6adb838f-669c-48ca-b76f-e1238984ef93:domain-c2147",
"discovered_ip_addresses": [
"x.x.x.x",
"x.x.x.x"
],
"discovered_node_id": "6adb838f-669c-48ca-b76f-e1238984ef93:host-2171",
"fqdn": "w1-vc1-c4n17.example.com",
"ip_addresses": [
"10.x.x.x"
],
"managed_by_server": "10.x.x.x",
"os_type": "ESXI",
"os_version": "x.x.x"
},
"overridden": false,
"owner_id": "1ae23909-d587-4683-a515-4c2001940803",
"parent_path": "/infra/sites/default/enforcement-points/default",
"path": "/infra/sites/default/enforcement-points/default/host-transport-nodes/w1-vc1-c4n17-6adb838f-669c-48ca-b76f-e1238984ef93host-2171",
"realization_id": "c2c69a21-698a-4586-869a-7c0b0a651601",
"relative_path": "w1-vc1-c4n17-6adb838f-669c-48ca-b76f-e1238984ef93host-2171",
"remote_path": "",
"resource_type": "HostTransportNode",
"unique_id": "c2c69a21-698a-4586-869a-7c0b0a651601"
}
I'd like to update this object by changing the following line:
"host_switch_mode": "STANDARD",
to:
"host_switch_mode": "LEGACY",
And I'll feed the result json as a message body to a POST API call. I've tried anislbe's combine filter but not getting the expected result. Something like:
- Name: example post/put API call
uri:
url: "https://{{ nsx }}/policy/api/v1/infra/host-transport-node-profiles/{{ id }}"
force_basic_auth: yes
validate_certs: no
headers:
Accept: "application/json"
Content-Type: "application/json"
user: "{{ username }}"
password: "{{ password }}"
method: PUT
body: "{{ ouput | combine( {'host_switch_spec': {'host_switches': [ {'host_switch_mode': "LEGACY"},],}, }, recursive=true) }}"
status_code: "200"
body_format: json
With the combine filter, what I see is that instead of just merging in the relevant line, the entire host_switch_spec section is replaced (therefore losing other attributes such as host_switch_name, host_switch_id, etc), which is not what I want:
"host_switch_spec": {
"host_switches": [
{
"host_switch_mode": "STANDARD"
}
],
"resource_type": "StandardHostSwitchSpec"
},
What did I do wrong? I looked at the combine filter man page and tried with all the list_merge options and none of them helped either.
update:
To keep things simple, one of the complications that I did not mention in my initial post is that I have a list of those "output" data object and I need to process them in a loop like below. Say the list is in the variable "tn":
# Modify the transport node json data on the fly and feed it to the API as message body
- debug:
msg:
- "{{ item }}"
- "{{ item | combine( {'host_switch_spec': {'host_switches': [ {'host_switch_mode': mode}, ], }, },) }}"
with_items: "{{ tn }}"
loop_control:
label: "{{ item.display_name }}"
Summary:
Thank you all for your help and guidance. I have not completed the end to end testing yet, but here is what I came up with based on Vladimir Botka's fantastic examples:
# how to modify the transport node json data on the fly and feed it to the API as message body
- name: Testing switch mode update on the fly
debug:
msg:
#- "hs: {{ (item.host_switch_spec.host_switches + [ hs_update ]) | combine }}"
#- "update: {{ {'host_switch_spec': {'host_switches': [ (item.host_switch_spec.host_switches + [ hs_update ]) | combine ]}} }}"
#- "result: {{ item | combine({'host_switch_spec': {'host_switches': [ (item.host_switch_spec.host_switches + [ hs_update ]) | combine ]}}) }}"
- "{{ item | combine({'host_switch_spec': {'host_switches': [ (item.host_switch_spec.host_switches + [ hs_update ]) | combine ]}}) }}"
loop: "{{ tn }}"
loop_control:
label: "{{ item.display_name }}"
index_var: counter
vars:
hs_update:
host_switch_mode: "LEGACY"
And the result looks promising (showing last one- #24, and partial only to avoid clutter):
ok: [localhost] => (item=w11-vc1-c1n24.example.com) => {
"msg": [
"24':' STANDARD",
{
"_create_time": 1743198123451,
"_create_user": "system",
"_last_modified_time": 1743445520238,
"_last_modified_user": "system",
"_protection": "NOT_PROTECTED",
"_revision": 2,
"_system_owned": false,
"display_name": "w11-vc1-c1n24.example.com",
"host_switch_spec": {
"host_switches": [
{
"cpu_config": [],
"ecmp_mode": "L3",
"host_switch_id": "50 1e 66 c9 c5 fb da b3-5e 1f 45 fc 2a 42 99 50",
"host_switch_mode": "LEGACY",
"host_switch_name": "w11-vc1-dvs",
"host_switch_profile_ids": [
{
"key": "UplinkHostSwitchProfile",
"value": "/infra/host-switch-profiles/ace9be64-9728-4340-968e-a62dae6a3d00"
},
{
"key": "VtepHAHostSwitchProfile",
"value": "/infra/host-switch-profiles/0de8282e-7385-4e8e-a905-0c11960db728"
}
],
"host_switch_type": "VDS",
output.host_switch_spec.host_switches
is a list with one item
output.host_switch_spec.host_switches:
- cpu_config: []
ecmp_mode: L3
host_switch_id: 50 01 cb 21 be 19 cb 38-78 bd 67 bc b3 0e 91 f1
host_switch_mode: STANDARD
host_switch_name: w1-vc1-c4-dvs
...
Put into a dictionary what you want to update. For example,
hs_update:
host_switch_mode: LEGACY
combine the dictionaries
hs: "{{ (output.host_switch_spec.host_switches + [hs_update]) | combine }}"
gives
hs:
cpu_config: []
ecmp_mode: L3
host_switch_id: 50 01 cb 21 be 19 cb 38-78 bd 67 bc b3 0e 91 f1
host_switch_mode: LEGACY
host_switch_name: w1-vc1-c4-dvs
...
Note: If there were more items you'd have to create a product and map the combine function.
Create the dictionary update
update:
host_switch_spec:
host_switches: "{{ [hs] }}"
and combine the result
result: "{{ output | combine(update) }}"
gives
result:
_create_time: 1724823837185
_create_user: system
_last_modified_time: 1736218001919
_last_modified_user: nsx_policy
_protection: NOT_PROTECTED
_revision: 1
_system_owned: false
display_name: w1-vc1-c4n17.example.com
host_switch_spec:
host_switches:
- cpu_config: []
ecmp_mode: L3
host_switch_id: 50 01 cb 21 be 19 cb 38-78 bd 67 bc b3 0e 91 f1
host_switch_mode: LEGACY
host_switch_name: w1-vc1-c4-dvs
...
Example of a complete playbook for testing
- hosts: localhost
vars:
hs_update:
host_switch_mode: LEGACY
hs: "{{ (output.host_switch_spec.host_switches + [hs_update]) | combine }}"
update:
host_switch_spec:
host_switches: "{{ [hs] }}"
result: "{{ output | combine(update) }}"
tasks:
- include_vars:
file: data.json
name: output
- debug:
var: output.host_switch_spec.host_switches
- debug:
var: hs
- debug:
var: result
Q: "The 'host_switch_spec' has both a 'host_switches' and 'resource_type'"
A: Combine the dictionaries recursively
result: "{{ output | combine(update, recursive=True) }}"
gives
result:
_create_time: 1724823837185
_create_user: system
_last_modified_time: 1736218001919
_last_modified_user: nsx_policy
_protection: NOT_PROTECTED
_revision: 1
_system_owned: false
display_name: w1-vc1-c4n17.example.com
host_switch_spec:
host_switches:
- cpu_config: []
ecmp_mode: L3
host_switch_id: 50 01 cb 21 be 19 cb 38-78 bd 67 bc b3 0e 91 f1
host_switch_mode: LEGACY
host_switch_name: w1-vc1-c4-dvs
...
uplinks:
- uplink_name: uplink-2
vds_uplink_name: uplink2
- uplink_name: uplink-1
vds_uplink_name: uplink1
resource_type: StandardHostSwitchSpec
id: w1-vc1-c4n17-6adb838f-669c-48ca-b76f-e1238984ef93host-2171
is_overridden: false
maintenance_mode: DISABLED
...
Q: "List of those 'output' data objects."
A: If there are more items, for example:
output:
bar: a
foo: b
hs_spec:
hs:
- {em: L3, hm: S3}
- {em: L4, hm: S4}
- {em: L5, hm: S5}
rt: c
mm: DISABLED
update all items
hs_update:
hm: LEGACY
hs: "{{ output.hs_spec.hs | product([hs_update])
| map('flatten')
| map('combine') }}"
gives
hs:
- {em: L3, hm: LEGACY}
- {em: L4, hm: LEGACY}
- {em: L5, hm: LEGACY}
Then, combine the dictionaries
update:
hs_spec:
hs: "{{ hs }}"
result: "{{ output | combine(update, recursive=True) }}"
gives
result:
bar: a
foo: b
hs_spec:
hs:
- {em: L3, hm: LEGACY}
- {em: L4, hm: LEGACY}
- {em: L5, hm: LEGACY}
rt: c
mm: DISABLED