I'm currently writing a script that allow to update Nutanix VM Categories. Categories format is key:value and a VM may have no categories, 1 or multiple.
Process to do that is quite simple : first I create a request to get an existing VM informations (GET) (then retrieve its UUID).
In a second time, i create another request to update an existing VM (PUT) by using the UUID retrieved earlier, and changing what i need to change (update category but it should be add disks, nic etc...)
At the moment, request to get VM is simple and works smoothly but I got an issue while i need to use the second request to update the VM. Here we are :
Payload used for this request looks like this in JSON
{
"spec": {
"cluster_reference": {
"kind": "cluster",
"name": "my_cluster",
"uuid": "0004f63d-6664-35ce-0126-88e9a456d3fc"
},
"name": "test-vm",
"resources": {
"num_threads_per_core": 1,
"vnuma_config": {
"num_vnuma_nodes": 0
},
"serial_port_list": [],
"nic_list": [
{
"nic_type": "NORMAL_NIC",
"uuid": "334e302a-db6a-48fb-b138-c3e3b2f1d8f6",
"ip_endpoint_list": [
{
"ip": "10.230.172.84",
"type": "ASSIGNED"
}
],
"vlan_mode": "ACCESS",
"mac_address": "50:6b:8d:c5:6c:f0",
"subnet_reference": {
"kind": "subnet",
"name": "vlanXXX",
"uuid": "6de66666-05ec-46ac-6666-aea60e8e831f"
},
"is_connected": true,
"trunked_vlan_list": []
}
],
"num_vcpus_per_socket": 1,
"num_sockets": 1,
"gpu_list": [],
"is_agent_vm": false,
"memory_size_mib": 4096,
"boot_config": {
"boot_device_order_list": [
"CDROM",
"DISK",
"NETWORK"
],
"boot_type": "LEGACY"
},
"hardware_clock_timezone": "UTC",
"power_state_mechanism": {
"guest_transition_config": {
"should_fail_on_script_failure": false,
"enable_script_exec": false
},
"mechanism": "HARD"
},
"power_state": "ON",
"machine_type": "PC",
"vga_console_enabled": true,
"memory_overcommit_enabled": false,
"disk_list": [
{
"uuid": "6f6ad0ba-5b81-487d-8c7b-6c5249a51f4b",
"disk_size_bytes": 48318382080,
"storage_config": {
"storage_container_reference": {
"kind": "storage_container",
"uuid": "13611573-1364-4065-a03d-67a7da51c14d",
"name": "SelfServiceContainer"
}
},
"device_properties": {
"disk_address": {
"device_index": 0,
"adapter_type": "SCSI"
},
"device_type": "DISK"
},
"data_source_reference": {
"kind": "image",
"uuid": "50a186ef-2538-43f3-9d6c-9150dd464ad7"
},
"disk_size_mib": 46080
}
]
},
"description": "test sync categorie"
},
"api_version": "3.1",
"metadata": {
"last_update_time": "2023-06-29T14:25:48Z",
"kind": "vm",
"uuid": "6e1b8781-8a2a-4830-a15a-0af30daccf89",
"project_reference": {
"kind": "project",
"name": "_internal",
"uuid": "706f34c2-98be-4034-92e2-859be84040f3"
},
"creation_time": "2023-06-29T14:25:48Z",
"spec_version": 3,
"categories_mapping": {
"AppType": [
"Exchange"
],
"AppTier": [
"Default"
]
},
"entity_version": "2",
"owner_reference": {
"kind": "user",
"name": "OWNER",
"uuid": "6caba9b0-00a9-57ea-8ba0-10162c0b1dd4"
},
"categories": {
"AppType": "Exchange",
"AppTier": "Default"
}
}
}
And look like this once converted in Powershell (Note : we're only interested about metadata, you'll see why)
PS C:\Users\XXX> $payload.metadata
last_update_time : 29/06/2023 14:25:48
kind : vm
uuid : 6e1b8781-8a2a-4830-a15a-0af30daccf89
project_reference : @{kind=project; name=_internal; uuid=706f34c2-98be-4034-92e2-859be84040f3}
creation_time : 29/06/2023 14:25:48
spec_version : 3
categories_mapping : @{AppType=System.Object[]; AppTier=System.Object[]}
entity_version : 2
owner_reference : @{kind=user; name=owner; uuid=6caba9b0-00a9-57ea-8ba0-10162c0b1dd4}
categories : @{AppType=Exchange; AppTier=Default}
PS C:\Users\XXX> $payload.metadata.categories
AppType AppTier
------- -------
Exchange Default
As you can see from a PS perspective, $payload
and its properties are PSCustomObject, so i can't use method like .Add(), .Remove()
etc... neither a +=
At the moment, i'm able to update a VM manually using the original JSON payload, and add category manually in the JSON format and test it through Postman. It works great.
The thing is, as you may have understand, i have to keep all the original payload from the GET method > achieve to add a new category > send a PUT request to fully update it without error.
My question is : from a PS perspective, how can I easily create and add a new key:value object and add it properly within categories by keeping the existing ? (= how to insert new value inside "categories": { "AppType": "Exchange", "AppTier": "Default" } )
PS: for references, here the Web Request i use to GET/PUT within PS. The PUT request is working but do nothing except PUT the same value as original :
#### -- HEADER
$Header_Prism = @{
'Accept' = 'application/json'
'Content-Type' = 'application/json'
'Authorization' = $basicAuthValue
}
#### --- GET VM
$get_vm = Invoke-RestMethod -Uri "https://mynutanix.net/vms/6e1b8781-6666-4830-a15a-0af30daccf89" `
-Method GET `
-Headers $Header_Prism
### --- PUT VM
$payload = $get_vm | select spec,api_version,metadata | convertTo-Json -depth 100
$put_vm = Invoke-RestMethod -Uri "https://mynutanix.net/vms/6e1b8781-6666-4830-a15a-0af30daccf89" `
-Method PUT `
-Headers $Header_Prism `
-Body $payload
Finally found out a solution thanks to those inspiring StackOverflow posts :
https://stackoverflow.com/questions/48597871/how-to-add-an-object-in-array-on-parsed-json
https://stackoverflow.com/questions/23720045/powershell-how-to-add-something-on-parsed-json
So in my case (reminder : category = key:value) i create a value to update
then use this
Add-Member
method to add the key i needNote : it seems proper to Nutanix but I have to increase
spec_version
parameters by 1And finally create the payload used in the web request
Then i'm able to launch the PUT web request successfully, and category is properly updated ! Now i'll try to improve my script efficiency, i know some stuff may be redundant or could be eased.