REST ❤ Swagger, 2 minutes to create an API SDK ⏱ – Codegen Demo – Part 2

Overview

After a short introduction of Swagger Codegen and the context of the demo made for a French vMUG Webinar, we will see how to apply this to VMware products like vCenter and vCloud Director.

As this post is following the steps made in the part 1, I strongly encourage you to start with the previous post.

Pre-requisites

Same as previous post + we will rely on some environment variables to set some local settings:

1echo "Exporting vCenter env..."
2export VMWARE_VCENTER_HOSTNAME="vcsa.vupti.me"
3export VMWARE_VCENTER_USERNAME="demo@vsphere.local"
4export VMWARE_VCENTER_PASSWORD='************'
5
6echo "Exporting vCD env..."
7export VMWARE_VCD_HOSTNAME="vcd.vupti.me"
8export VMWARE_VCD_USERNAME="demo@orgdemo"
9export VMWARE_VCD_PASSWORD="************"

vCenter SDK with Codegen

 1# Get the API swagger description
 2curl -sk https://$VMWARE_VCENTER_HOSTNAME/apiexplorer/json/vcenter.json  \
 3  > codegen/in/vcenter.json
 4
 5# Create a config file for our future python module
 6echo '{
 7  "packageName":"vc_client",
 8  "projectName":"vc-client",
 9  "packageVersion":"6.7.0"
10}' > codegen/in/config_vc_client.json
11
12# Generate the SDK
13docker run --rm -v ${PWD}/codegen:/local \
14  swaggerapi/swagger-codegen-cli generate \
15    -i /local/in/vcenter.json \
16    -o /local/out/python-vc \
17    -c /local/in/config_vc_client.json \
18    -l python
19
20# Install new python mdule with PIP
21pip install codegen/out/python-vc

So in the above section, we created a new API SDK for a subset of vCenter REST APIs (and we do not rely on the pyvmomi project).

We can now use it:

Prepare a client object

 1import vc_client
 2from vc_client.rest import ApiException
 3from utils import *
 4import os
 5logger = logging.getLogger("DEMO_VCENTER")
 6
 7# Configure API
 8configuration = vc_client.Configuration()
 9configuration.verify_ssl = False
10configuration.host = f"https://{os.environ.get('VMWARE_VCENTER_HOSTNAME')}/rest"
11
12# credentials
13_username = os.environ.get('VMWARE_VCENTER_USERNAME')
14_password = os.environ.get('VMWARE_VCENTER_PASSWORD')
15auth_str = basic_auth_str(_username, _password)
16
17# Create a client
18client = vc_client.ApiClient(configuration)

So we import our new vc_client module, we setup the target hostname and the authentication settings.

Get a new session

New goal: getting a session to use a cookie-based authentication instead of providing username/password for each request:

 1# Get a new session
 2try:
 3    logger.debug("Starting a new session")
 4    s = client.call_api(
 5        '/com/vmware/cis/session',
 6        "POST",
 7        header_params={
 8            "Authorization": auth_str,
 9        })
10except ApiException as e:
11    print("Exception when creating session: %s\n" % e)
12    exit(-1)
13logger.info("New session is created")
14
15# Set the cookie according to the previous request result
16logger.debug("Setting new session authorization token in cookies")
17client.cookie = s[2].get('Set-Cookie')
18logger.info(f"Client cookies updated: {client.cookie}")

As you see, we rely on our module code by using the client.call_api instruction and the cookie update: client.cookie = s[2].get('Set-Cookie').

Output:

1DEMO_VCENTER 	Starting a new session
2DEMO_VCENTER 	New session is created
3DEMO_VCENTER 	Setting new session authorization token in cookies
4DEMO_VCENTER 	Client cookies updated: vmware-api-session-id=5cebc...;Path=/rest;Secure;HttpOnly

Get some content

We will use our new session to get some data from the vCenter API.

Listing VMs:

1# List VM
2logger.debug("Listing VMs...")
3instance = vc_client.VM_Api(client)
4for vm in instance.list().value:
5    logger.info(
6        f"{vm.name}: {vm.power_state} / vCPU: {vm.cpu_count} / Mem: {vm.memory_size_mi_b} Mb"
7    )
8    keep_last = vm

Output:

 1DEMO_VCENTER 	Listing VMs...
 2DEMO_VCENTER 	LRI_RancherMaster: POWERED_ON / vCPU: 4 / Mem: 8192 Mb
 3DEMO_VCENTER 	LRI_vRO: POWERED_ON / vCPU: 2 / Mem: 6144 Mb
 4DEMO_VCENTER 	LRI_vcd: POWERED_ON / vCPU: 2 / Mem: 12288 Mb
 5DEMO_VCENTER 	LRI_rabbitmq: POWERED_ON / vCPU: 2 / Mem: 3072 Mb
 6DEMO_VCENTER 	ESXi7-01: POWERED_ON / vCPU: 2 / Mem: 4096 Mb
 7DEMO_VCENTER 	vcsa7: POWERED_ON / vCPU: 2 / Mem: 12288 Mb
 8DEMO_VCENTER 	ESXi7-02: POWERED_ON / vCPU: 2 / Mem: 4096 Mb
 9DEMO_VCENTER 	ESXi7-03: POWERED_ON / vCPU: 2 / Mem: 4096 Mb
10DEMO_VCENTER 	LRI_ubuntu01: POWERED_ON / vCPU: 2 / Mem: 1024 Mb
11DEMO_VCENTER 	LRI_photon01: POWERED_ON / vCPU: 1 / Mem: 2048 Mb
12DEMO_VCENTER 	DCScope: POWERED_ON / vCPU: 2 / Mem: 8192 Mb
13DEMO_VCENTER 	FaH_1.0.0_01: POWERED_ON / vCPU: 8 / Mem: 8192 Mb

As you can see, we kept the last VM from the list to get more details:

 1# Get more details for last VM
 2logger.debug("Getting details about last VM...")
 3vm_detailled = instance.get(keep_last.vm).value
 4logger.info("Data:\n" +
 5            json.dumps(vm_detailled.to_dict(), indent=2, default=str))
 6
 7# Get its network
 8logger.debug("Getting a specifc detail about a VM:")
 9nic = vm_detailled.nics[0].value
10logger.info(f"{keep_last.name} MAC address: {nic.mac_address}")

Output (only the VM details content):

  1{
  2  "memory": {
  3    "hot_add_limit_mi_b": null,
  4    "hot_add_enabled": false,
  5    "hot_add_increment_size_mi_b": null,
  6    "size_mi_b": 8192
  7  },
  8  "hardware": {
  9    "upgrade_status": "NONE",
 10    "upgrade_version": null,
 11    "version": "VMX_13",
 12    "upgrade_error": null,
 13    "upgrade_policy": "NEVER"
 14  },
 15  "disks": [
 16    {
 17      "value": {
 18        "label": "Hard disk 1",
 19        "scsi": {
 20          "unit": 0,
 21          "bus": 0
 22        },
 23        "capacity": 4294967296,
 24        "type": "SCSI",
 25        "sata": null,
 26        "ide": null,
 27        "backing": {
 28          "type": "VMDK_FILE",
 29          "vmdk_file": "[vsanDatastore] d40d765e-a8c1-6d10-0d40-a0369fbcc808/FaH_1.0.0_01.vmdk"
 30        }
 31      },
 32      "key": "2000"
 33    }
 34  ],
 35  "cdroms": [
 36    {
 37      "value": {
 38        "label": "CD/DVD drive 1",
 39        "backing": {
 40          "type": "CLIENT_DEVICE",
 41          "device_access_type": "PASSTHRU",
 42          "iso_file": null,
 43          "host_device": null,
 44          "auto_detect": null
 45        },
 46        "state": "NOT_CONNECTED",
 47        "type": "IDE",
 48        "sata": null,
 49        "allow_guest_control": true,
 50        "ide": {
 51          "primary": true,
 52          "master": true
 53        },
 54        "start_connected": false
 55      },
 56      "key": "3000"
 57    }
 58  ],
 59  "boot_devices": [
 60    {
 61      "type": "CDROM",
 62      "nic": null,
 63      "disks": null
 64    }
 65  ],
 66  "nics": [
 67    {
 68      "value": {
 69        "wake_on_lan_enabled": false,
 70        "state": "CONNECTED",
 71        "label": "Network adapter 1",
 72        "backing": {
 73          "network": "dvportgroup-100",
 74          "distributed_port": "69",
 75          "distributed_switch_uuid": "50 2f 42 20 b1 c3 a5 2a-0d 95 b6 0c ab 7e b5 da",
 76          "network_name": null,
 77          "opaque_network_id": null,
 78          "type": "DISTRIBUTED_PORTGROUP",
 79          "opaque_network_type": null,
 80          "connection_cookie": 1884582175,
 81          "host_device": null
 82        },
 83        "start_connected": true,
 84        "pci_slot_number": 160,
 85        "type": "VMXNET3",
 86        "upt_compatibility_enabled": true,
 87        "allow_guest_control": true,
 88        "mac_type": "ASSIGNED",
 89        "mac_address": "00:50:56:af:db:98"
 90      },
 91      "key": "4000"
 92    }
 93  ],
 94  "parallel_ports": [],
 95  "guest_os": "VMWARE_PHOTON_64",
 96  "name": "FaH_1.0.0_01",
 97  "power_state": "POWERED_ON",
 98  "sata_adapters": [],
 99  "cpu": {
100    "count": 8,
101    "hot_add_enabled": false,
102    "hot_remove_enabled": false,
103    "cores_per_socket": 1
104  },
105  "serial_ports": [],
106  "floppies": [],
107  "scsi_adapters": [
108    {
109      "value": {
110        "pci_slot_number": 16,
111        "type": "LSILOGIC",
112        "label": "SCSI controller 0",
113        "scsi": {
114          "unit": 7,
115          "bus": 0
116        },
117        "sharing": "NONE"
118      },
119      "key": "1000"
120    }
121  ],
122  "boot": {
123    "retry_delay": 10000,
124    "enter_setup_mode": false,
125    "delay": 0,
126    "type": "BIOS",
127    "network_protocol": null,
128    "efi_legacy_boot": null,
129    "retry": false
130  }
131}

vCloud Director SDK with Codegen

Last example, using vCloud Director.

 1# Get the API swagger description
 2curl -sk https://$VMWARE_VCD_HOSTNAME/api-explorer/tenant/orgdemo/cloudapi.json \
 3  > codegen/in/cloudapi.json
 4
 5# Create a config file for our future python module
 6echo '{
 7  "packageName":"vcd_client",
 8  "projectName":"vcd-client",
 9  "packageVersion":"9.7.1"
10}' > codegen/in/config_vcd_client.json
11
12# Generate the SDK
13docker run --rm -v ${PWD}/codegen:/local \
14  swaggerapi/swagger-codegen-cli generate \
15    -i /local/in/cloudapi.json \
16    -o /local/out/python-vcd \
17    -c /local/in/config_vcd_client.json \
18    -l python
19
20# Install new python mdule with PIP
21pip install codegen/out/python-vcd

Prepare a client object

We import our new vcd_client module, we setup the target hostname, the authentication settings.

 1import vcd_client
 2from vcd_client.rest import ApiException
 3from utils import *
 4import os
 5logger = logging.getLogger("DEMO_VCD")
 6
 7# Configure API
 8configuration = vcd_client.Configuration()
 9configuration.verify_ssl = False
10configuration.host = f"https://{os.environ.get('VMWARE_VCD_HOSTNAME')}/cloudapi"
11
12# credentials
13_username = os.environ.get('VMWARE_VCD_USERNAME')
14_password = os.environ.get('VMWARE_VCD_PASSWORD')
15auth_str = basic_auth_str(_username, _password)
16
17# Create a client
18client = vcd_client.ApiClient(configuration)

Get a new session

 1# Get a new session
 2try:
 3    logger.debug("Starting a new session")
 4    s = vcd_client.SessionsApi(client)
 5    s_headers = s.login_with_http_info(authorization=auth_str)[2]
 6except ApiException as e:
 7    print("Exception when creating session: %s\n" % e)
 8    exit(-1)
 9logger.info("New session is created")
10
11# Update client with access token
12logger.debug("Setting new session authorization token in headers")
13configuration.api_key_prefix['Authorization'] = 'Bearer'
14configuration.api_key['Authorization'] = s_headers.get(
15    "X-VMWARE-VCLOUD-ACCESS-TOKEN"
16)
17client = vcd_client.ApiClient(configuration)
18logger.info(f"Client credentials updated to use access token: {s_headers.get('X-VMWARE-VCLOUD-ACCESS-TOKEN')}")

Output:

1DEMO_VCD 	Starting a new session
2DEMO_VCD 	New session is created
3DEMO_VCD 	Setting new session authorization token in headers
4DEMO_VCD 	Client credentials updated to use access token: eyJhbGciOiJSUzI1NiJ9...

Get some content

We will use our session and list our rights in the current organization:

1# List rights of the current user
2logger.debug("Getting rights of the current user")
3rapi = vcd_client.RightsApi(client)
4page, page_size = 1, 25
5try:
6    for right in rapi.query_rights(page, page_size).values:
7        logger.info(" - ".join([right.name, right.id, right.right_type]))
8except ApiException as e:
9    logger.error("Exception when calling RightsApi->query_rights: %s\n" % e)

Output (only fist part):

1DEMO_VCD 	Getting rights of the current user
2DEMO_VCD 	Organization vDC Gateway: Configure DNS - urn:vcloud:right:d85b0e92-b9e8-31af-9b19-23cd00cae7e7 - MODIFY
3DEMO_VCD 	Organization vDC Gateway: View DNS - urn:vcloud:right:c6563392-f6b3-3dd6-9720-b304e6319672 - VIEW
4DEMO_VCD 	Token: Manage - urn:vcloud:right:23e7a571-4928-3c49-891f-f835474a9dc3 - MODIFY
5DEMO_VCD 	Token: Manage All - urn:vcloud:right:67878e89-9d94-302f-92fd-997313c68ee1 - MODIFY
6DEMO_VCD 	API Explorer: View - urn:vcloud:right:9ff43a6c-2c50-3b53-b00f-6f020b6bb5a0 - VIEW

Conclusion

As you see, it is very easy to generate a new API client SDK from a VMware product. Authentication could require some customization but the most limiting thing will be linked to the limited available actions through the REST API on some products.

Anyway, for the available and documented REST API parts, you can now deliver/provide a lot of SDK, even without knowing the bases of the used language.