💾 Archived View for gmi.runtimeterror.dev › getting-started-vra-rest-api › index.gmi captured on 2024-08-18 at 17:36:48. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-08-18)

-=-=-=-=-=-=-

💻 [runtimeterror $]

2022-06-03

Getting Started with the vRealize Automation REST API

I've been doing a bit of work lately to make my vRealize Automation setup more flexible and dynamic and less dependent upon hardcoded values. To that end, I thought it was probably about time to learn how to interact with the vRA REST API. I wrote this post to share what I've learned and give a quick crash course on how to start doing things with the API.

Exploration Toolkit

Swagger

It can be difficult to figure out where to start when learning a new API. Fortunately, VMware thoughtfully included a Swagger [1] specification for the API so that we can explore it in an interactive way. This is available for on-prem vRA environments at `https://{vra-fqdn}/automation-ui/api-docs/` (so `https://vra.lab.bowdre.net/automation-ui/api-docs/` in my case). You can also browse most of it online at www.mgmt.cloud.vmware.com/automation-ui/api-docs/ [2]. Playing with Swagger on your on-prem instance will even let you perform requests straight from the browser, which can be a great way to gain familiarity with how requests should be structured.

[1] Swagger

[2] www.mgmt.cloud.vmware.com/automation-ui/api-docs/

Image: The vRA Automation UI API Docs

I'm ultimately going to be working with the Infrastructure as a Service API but before I can do that I'll need to talk to the Identity API to log in. So let's start the exploration there, with the Login Controller.

Image: Login Controller Request

That tells me that I'll need to send a `POST` request to the endpoint at `/csp/gateway/am/api/login`, and I'll need to include `username`, `password`, `domain`, and `scope` in the request body. I can click the **Try it out** button to take this endpoint for a spin and just insert appropriate values in the request:

Image: Trying it out

After hitting **Execute**, the Swagger UI will populate the *Responses* section with some useful information, like how the request would be formatted for use with `curl`:

Image: curl request format

So I could easily replicate this using the `curl` utility by just copying and pasting the following into a shell:

curl -X 'POST' \ 
  'https://vra.lab.bowdre.net/csp/gateway/am/api/login' \
  -H 'accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
  "username": "vra",
  "password": "********",
  "domain": "lab.bowdre.net",
  "scope": ""
}'

Scrolling further reveals the authentication token returned by the identity service:

Image: Authentication token

I can copy the contents of that `cspAuthToken` field and use it for authenticating other API operations. For instance, I'll go to the Infrastructure as a Service API Swagger UI and click the **Authorize** button at the top of the screen:

Image: Authorize me!

And then paste the token into the header as `Bearer [token]`:

Image: Bearer bearing the token

Now I can go find an IaaS API that I'm interested in querying (like `/iaas/api/flavor-profiles` to see which flavor mappings are defined in vRA), and hit **Try it out** and then **Execute**:

Image: Using Swagger to query for flavor mappings

And here's the result:

{
  "content": [
    {
      "flavorMappings": {
        "mapping": {
          "1vCPU | 2GB [tiny]": { 
            "cpuCount": 1,
            "memoryInMB": 2048
          },
          "1vCPU | 1GB [micro]": { 
            "cpuCount": 1,
            "memoryInMB": 1024
          },
          "2vCPU | 4GB [small]": { 
            "cpuCount": 2,
            "memoryInMB": 4096
          }
        }, 
        "_links": {
          "region": {
            "href": "/iaas/api/regions/3617c011-39db-466e-a7f3-029f4523548f"
          }
        }
      },
      "externalRegionId": "Datacenter:datacenter-39056",
      "cloudAccountId": "75d29635-f128-4b85-8cf9-95a9e5981c68",
      "name": "",
      "id": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9-3617c011-39db-466e-a7f3-029f4523548f",
      "updatedAt": "2022-05-05",
      "organizationId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",
      "orgId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",
      "_links": {
        "self": {
          "href": "/iaas/api/flavor-profiles/61ebe5bf-5f55-4dee-8533-7ad05c067dd9-3617c011-39db-466e-a7f3-029f4523548f"
        },
        "region": {
          "href": "/iaas/api/regions/3617c011-39db-466e-a7f3-029f4523548f"
        } 
      }
    },
    {
      "flavorMappings": {
        "mapping": {
          "2vCPU | 8GB [medium]": { 
            "cpuCount": 2,
            "memoryInMB": 8192
          },
          "1vCPU | 2GB [tiny]": { 
            "cpuCount": 1,
            "memoryInMB": 2048
          },
          "8vCPU | 16GB [giant]": { 
            "cpuCount": 8,
            "memoryInMB": 16384
          },
          "1vCPU | 1GB [micro]": { 
            "cpuCount": 1,
            "memoryInMB": 1024
          },
          "2vCPU | 4GB [small]": { 
            "cpuCount": 2,
            "memoryInMB": 4096
          },
          "4vCPU | 12GB [large]": { 
            "cpuCount": 4,
            "memoryInMB": 12288
          }
        }, 
        "_links": {
          "region": {
            "href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"
          }
        }
      }, 
      "externalRegionId": "Datacenter:datacenter-1001",
      "cloudAccountId": "75d29635-f128-4b85-8cf9-95a9e5981c68",
      "name": "",
      "id": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9-c0d2a662-9ee5-4a27-9a9e-e92a72668136",
      "updatedAt": "2022-05-05",
      "organizationId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",
      "orgId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",
      "_links": {
        "self": {
          "href": "/iaas/api/flavor-profiles/61ebe5bf-5f55-4dee-8533-7ad05c067dd9-c0d2a662-9ee5-4a27-9a9e-e92a72668136"
        },
        "region": {
          "href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"
        }
      } 
    }
  ],
  "totalElements": 2,
  "numberOfElements": 2
}

So that API call will tell me about the Flavor Profiles as well as which Region the profiles belong to.

As you can see, Swagger can really help to jump-start the exploration of a new API, but it can get a bit clumsy for repeated queries. And while I _could_ just use `curl` for further API exercises, I'd rather use a tool built specifically for API tomfoolery:

HTTPie

HTTPie [3] is a handy command-line utility optimized for interacting with web APIs. This will make things easier as I dig deeper.

[3] HTTPie

Installing the Debian package [4] is a piece of ~~cake~~ _pie_

curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo apt-key add -

sudo curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list

sudo apt update

sudo apt install httpie

=> https://httpie.io/docs/cli/debian-and-ubuntu [4] Debian package
Once installed, running `http` will give me a quick overview of how to use this new tool:

http

usage:

http [METHOD] URL [REQUEST_ITEM ...]

error:

the following arguments are required: URL

for more information:

run 'http --help' or visit https://httpie.io/docs/cli

HTTPie cleverly interprets anything passed after the URL as a request item [5], and it determines the item type based on a simple key/value syntax:
> Each request item is simply a key/value pair separated with the following characters: `:` (headers), `=` (data field, e.g., JSON, form), `:=` (raw data field), `==` (query parameters), `@` (file upload).
=> https://httpie.io/docs/cli/request-items [5] request item
So my earlier request for an authentication token becomes:

https POST vra.lab.bowdre.net/csp/gateway/am/api/login username='vra' password='********' domain='lab.bowdre.net'

<-- note -->
If your vRA endpoint is using a self-signed or otherwise untrusted certificate, pass the HTTPie option `--verify=no` to ignore certificate errors:

https --verify=no POST [URL] [REQUEST_ITEMS]

<-- /note -->
Running that will return a bunch of interesting headers but I'm mainly interested in the response body:

{

"cspAuthToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjI4NDY0MjAzMzA2NDQwMTQ2NDQifQ.eyJpc3MiOiJDTj1QcmVsdWRlIElkZW50aXR5IFNlcnZpY2UsT1U9Q01CVSxPPVZNd2FyZSxMPVNvZmlh[...]HBOQQwEepXTNAaTv9gWMKwvPzktmKWyJFmC64FGomRyRyWiJMkLy3xmvYQERwxaDj_15-npz4Csv5FwcXt0fa"

}

There's the auth token that I'll need for subsequent requests. I'll store that in a variable so that it's easier to wield:

token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjI4NDY0MjAzMzA2NDQwMTQ2NDQifQ.eyJpc3MiOiJDTj1QcmVsdWRlIElkZW50aXR5IFNlcnZpY2UsT1U9Q01CVSxPPVZNd2FyZSxMPVNvZmlh[...]HBOQQwEepXTNAaTv9gWMKwvPzktmKWyJFmC64FGomRyRyWiJMkLy3xmvYQERwxaDj_15-npz4Csv5FwcXt0fa

So now if I want to find out which images have been configured in vRA, I can ask:

https GET vra.lab.bowdre.net/iaas/api/images "Authorization: Bearer $token"

<-- note -->
Remember from above that HTTPie will automatically insert key/value pairs separated by a colon into the request header.
<-- /note -->
And I'll get back some headers followed by an JSON object detailing the defined image mappings broken up by region:

{

"content": [

{

"_links": {

"region": {

"href": "/iaas/api/regions/3617c011-39db-466e-a7f3-029f4523548f"

}

},

"externalRegionId": "Datacenter:datacenter-39056",

"mapping": {

"Photon 4": {

"_links": {

"region": {

"href": "/iaas/api/regions/3617c011-39db-466e-a7f3-029f4523548f"

}

},

"cloudConfig": "",

"constraints": [],

"description": "photon-arm",

"externalId": "50023810-ae56-3c58-f374-adf6e0645886",

"externalRegionId": "Datacenter:datacenter-39056",

"id": "8885e87d8a5898cf12b5abc3e5c715e5a65f7179",

"isPrivate": false,

"name": "photon-arm",

"osFamily": "LINUX"

}

}

},

{

"_links": {

"region": {

"href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"

}

},

"externalRegionId": "Datacenter:datacenter-1001",

"mapping": {

"Photon 4": {

"_links": {

"region": {

"href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"

}

},

"cloudConfig": "",

"constraints": [],

"description": "photon",

"externalId": "50028cf1-88b8-52e8-58a1-b8354d4207b0",

"externalRegionId": "Datacenter:datacenter-1001",

"id": "d417648249e9740d7561188fa2a3a3ab4e8ccf85",

"isPrivate": false,

"name": "photon",

"osFamily": "LINUX"

},

"Windows Server 2019": {

"_links": {

"region": {

"href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"

}

},

"cloudConfig": "",

"constraints": [],

"description": "ws2019",

"externalId": "500235ad-1022-fec3-8ad1-00433beee103",

"externalRegionId": "Datacenter:datacenter-1001",

"id": "7e05f4e57ac55135cf7a7f8b951aa8ccfcc335d8",

"isPrivate": false,

"name": "ws2019",

"osFamily": "WINDOWS"

}

}

}

],

"numberOfElements": 2,

"totalElements": 2

}

This doesn't give me the *name* of the regions, but I could use the `_links.region.href` data to quickly match up images which exist in a given region.
You'll notice that HTTPie also prettifies the JSON response to make it easy for humans to parse. This is great for experimenting with requests against different API endpoints and getting a feel for what data can be found where. And firing off tests in HTTPie can be a lot quicker (and easier to format) than with other tools.
Now let's take what we've learned and see about implementing it as vRO actions.
### vRealize Orchestrator actions
My immediate goal for this exercise is create a set of vRealize Orchestrator actions which take in a zone/location identifier from the Cloud Assembly request and return a list of images which are available for deployment there. I'll start with some utility actions to do the heavy lifting, and  then I'll be able to call them from other actions as things get more complicated/interesting. Before I can do that, though, I'll need to add the vRA instance as an HTTP REST endpoint in vRO.
<-- note -->
A lot of what follows was borrowed *heavily* from a very helpful post by Oktawiusz Poranski over at Automate Clouds [6] so be sure to check out that site for more great tips on working with APIs!
<-- /note -->
=> https://automateclouds.com/2021/vrealize-automation-8-rest-api-how-to/ [6] very helpful post by Oktawiusz Poranski over at Automate Clouds
### Creating the endpoint
I can use the predefined **Add a REST host** workflow to create the needed endpoint.
=> add_rest_host_properties.png Image: Add a REST host workflow - host properties
Configuring the host properties here is very simple: just give it a name and the URL of the vRA instance.
=> add_rest_host_auth.png Image: Add a REST host workflow - authentication
On the Authentication tab, I set the auth type to `NONE`; I'll handle the authentication steps directly.
With those parameters in place I can kick off the workflow. Once it completes, I can check **Administration > Inventory > HTTP-REST** to see the newly-created endpoint:
=> new_rest_host_in_inventory.png Image: New HTTP-REST endpoint
### Creating a Configuration for the endpoint
I don't want to hardcode any endpoints or credentials into my vRO actions so I'm also going to create a Configuration to store those details. This will make it easy to reference those same details from multiple actions as well.
To create a new Configuration, I'll head to **Assets > Configurations**, select a folder (or create a new one) where I want the Configuration to live, and then click the **New Configuration** button.
=> config_element_1.png Image: Location of the Configurations
I'm going to call this new Configuration `Endpoints` since I plan to use it for holding host/credentials for additional endpoints in the future. After giving it a name, I'll go ahead and click **Save** to preserve my efforts thus far.
=> config_element_2.png Image: Creating the new Configuration
I'll then click over to the **Variables** tab and create a new variable to store my vRA endpoint details; I'll call it `vRAHost`, and hit the *Type* dropdown and select **New Composite Type**.
=> vrahost_variable_1.png Image: Creating the new variable
This new composite type will let me use a single variable to store multiple values - basically everything I'll need to interact with a single REST endpoint:

| Variable | Type |

|:--- |:--- |

| `host` | `REST:RESTHost` |

| `username` | `string` |

| `domain` | `string` |

| `password` | `SecureString` |


=> new_composite_type.png Image: Creating a new composite type
I can then map the appropriate values for this new variable and hit **Create**.
=> vrahost_variable_2.png Image: Assigning values to the vRAHost
I make sure to **Save** my work and then gaze at the new Configuration's majesty:
=> created_configuration.png Image: Newly-created Configuration
Okay, enough prep work - let's get into some Actions!
### Utility actions
### `getConfigValue` action
I'll head into **Library > Actions** to create a new action inside my `com.virtuallypotato.utility` module. This action's sole purpose will be to extract the details out of the configuration element I just created. Right now I'm only concerned with retrieving the one `vRAHost` configuration but I'm a fan of using generic pluggable modules where possible. This one will work to retrieve the value of *any* variable defined in *any* configuration element so I'll call it `getConfigValue`.
=> getConfigValue_action.png Image: getConfigValue action

| Input | Type | Description |

|:--- |:--- |:--- |

| `path` | `string` | Path to Configuration folder |

| `configurationName` | `string` | Name of Configuration |

| `variableName` | `string` | Name of desired variable inside Configuration |


/*

JavaScript: getConfigValue action

Inputs: path (string), configurationName (string), variableName (string)

Return type: string

var configElement = null;

for each (configElement in Server.getConfigurationElementCategoryWithPath(path).configurationElements) {

if (configElement.name.indexOf(configurationName) === 0) { break; };

}

var attribValue = configElement.getAttributeWithKey(variableName).value;

return attribValue;

### `vraLogin` action
Next, I'll create another action in my `com.virtuallypotato.utility` module which will use the `getConfigValue` action to retrieve the endpoint details. It will then submit a `POST` request to that endpoint to log in, and it will return the authentication token. Later actions will be able to call upon `vraLogin` to grab a token and then pass that back to the IaaS API in the request headers - but I'm getting ahead of myself. Let's get the login sorted:
=> vraLogin_action.png Image: vraLogin action

/*

JavaScript: vraLogin action

Inputs: none

Return type: string

var restHost = System.getModule("com.virtuallypotato.utility").getConfigValue("vPotato", "Endpoints", "vRAHost");

var host = restHost.host;

var loginObj = {

domain: restHost.domain,

password: restHost.password,

username: restHost.username

};

var loginJson = JSON.stringify(loginObj);

var request = host.createRequest("POST", "/csp/gateway/am/api/login", loginJson);

request.setHeader("Content-Type", "application/json");

var response = request.execute();

var token = JSON.parse(response.contentAsString).cspAuthToken;

System.debug("Created vRA API session: " + token);

return token;

### `vraLogout` action
I like to clean up after myself so I'm also going to create a `vraLogout` action in my `com.virtuallypotato.utility` module to tear down the API session when I'm finished with it.
=> vraLogout_action.png Image: vraLogout action

| Input | Type | Description |

|:--- |:--- |:--- |

| `token` | `string` | Auth token of the session to destroy |


/*

JavaScript: vraLogout action

Inputs: token (string)

Return type: string

var host = System.getModule("com.virtuallypotato.utility").getConfigValue("vPotato", "Endpoints", "vRAHost").host;

var logoutObj = {

idToken: token

};

var logoutJson = JSON.stringify(logoutObj);

var request = host.createRequest("POST", "/csp/gateway/am/api/auth/logout", logoutJson);

request.setHeader("Content-Type", "application/json");

request.execute().statusCode;

System.debug("Terminated vRA API session: " + token);

### `vraExecute` action
My final "utility" action for this effort will run in between `vraLogin` and `vraLogout`, and it will handle making the actual API call and returning the results. This way I won't have to implement the API handler in every single action which needs to talk to the API - they can just call my new action, `vraExecute`.
=> vraExecute_action.png Image: vraExecute action

| Input | Type | Description |

|:--- |:--- |:--- |

|`token`|`string`|Auth token from `vraLogin`|

|`method`|`string`|REST Method (`GET`, `POST`, etc.)|

|`uri`|`string`|Path to API controller (`/iaas/api/flavor-profiles`)|

|`content`|`string`|Any additional data to pass with the request|


/*

JavaScript: vraExecute action

Inputs: token (string), method (string), uri (string), content (string)

Return type: string

var host = System.getModule("com.virtuallypotato.utility").getConfigValue("vPotato", "Endpoints", "vRAHost").host;

System.log(host);

if (content) {

var request = host.createRequest(method, uri, content);

} else {

var request = host.createRequest(method, uri);

}

request.setHeader("Content-Type", "application/json");

request.setHeader("Authorization", "Bearer " + token);

var response = request.execute();

var statusCode = response.statusCode;

var responseContent = response.contentAsString;

if (statusCode > 399) {

System.error(responseContent);

throw "vraExecute action failed, status code: " + statusCode;

}

return responseContent;

### Bonus: `vraTester` action
That's it for the core utility actions - but wouldn't it be great to know that this stuff works before moving on to handling the request input? Enter `vraTester`! It will be handy to have an action I can test vRA REST requests in before going all-in on a solution.
This action will:

Other actions wanting to interact with the vRA REST API will follow the same basic formula, though with some more logic and capability baked in.
Anyway, here's my first swing:

/*

JavaScript: vraTester action

Inputs: none

Return type: string

var token = System.getModule('com.virtuallypotato.utility').vraLogin();

var result = JSON.parse(System.getModule('com.virtuallypotato.utility').vraExecute(token, 'GET', '/iaas/api/zones')).content;

System.log(JSON.stringify(result));

System.getModule('com.virtuallypotato.utility').vraLogout(token);

return JSON.stringify(result);

Pretty simple, right? Let's see if it works:
=> vraTester_action.png Image: vraTester action
It did! Though that result is a bit hard to parse visually, so I'm going to prettify it a bit:

[

{

"tags": [],

"tagsToMatch": [

{

"key": "compute",

"value": "nuc"

}

],

"placementPolicy": "DEFAULT",

"customProperties": {

"zone_overlapping_migrated": "true"

},

"folder": "vRA_Deploy",

"externalRegionId": "Datacenter:datacenter-1001",

"cloudAccountId": "75d29635-f128-4b85-8cf9-95a9e5981c68",

"name": "NUC",

"id": "3d4f048a-385d-4759-8c04-117a170d060c",

"updatedAt": "2022-06-02",

"organizationId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",

"orgId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",

"_links": {

"projects": {

"hrefs": [

"/iaas/api/projects/9c3d1e73-1276-42e7-8d8e-cac40251a29e"

]

},

"computes": {

"href": "/iaas/api/zones/3d4f048a-385d-4759-8c04-117a170d060c/computes"

},

"self": {

"href": "/iaas/api/zones/3d4f048a-385d-4759-8c04-117a170d060c"

},

"region": {

"href": "/iaas/api/regions/c0d2a662-9ee5-4a27-9a9e-e92a72668136"

},

"cloud-account": {

"href": "/iaas/api/cloud-accounts/75d29635-f128-4b85-8cf9-95a9e5981c68"

}

}

},

{

"tags": [],

"tagsToMatch": [

{

"key": "compute",

"value": "qtz"

}

],

"placementPolicy": "DEFAULT",

"customProperties": {

"zone_overlapping_migrated": "true"

},

"externalRegionId": "Datacenter:datacenter-39056",

"cloudAccountId": "75d29635-f128-4b85-8cf9-95a9e5981c68",

"name": "QTZ",

"id": "84470591-74a2-4659-87fd-e5d174a679a2",

"updatedAt": "2022-06-02",

"organizationId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",

"orgId": "61ebe5bf-5f55-4dee-8533-7ad05c067dd9",

"_links": {

"projects": {

"hrefs": [

"/iaas/api/projects/9c3d1e73-1276-42e7-8d8e-cac40251a29e"

]

},

"computes": {

"href": "/iaas/api/zones/84470591-74a2-4659-87fd-e5d174a679a2/computes"

},

"self": {

"href": "/iaas/api/zones/84470591-74a2-4659-87fd-e5d174a679a2"

},

"region": {

"href": "/iaas/api/regions/3617c011-39db-466e-a7f3-029f4523548f"

},

"cloud-account": {

"href": "/iaas/api/cloud-accounts/75d29635-f128-4b85-8cf9-95a9e5981c68"

}

}

}

]

I can see that it returned two Cloud Zones, named `NUC` (for my NUC 9 host [7]) and `QTZ` (for my Quartz64 SBC running ESXi-ARM [8]). Each Zone also includes data about other objects associated with the Zone, such as that `_links.region.href` property I mentioned earlier.
=> /vmware-home-lab-on-intel-nuc-9/ [7] NUC 9 host
=> /esxi-arm-on-quartz64/ [8] Quartz64 SBC running ESXi-ARM
My compute targets live at the Zone level, each Zone lives inside a given Region, and the Image Profiles reside within a Region. See where this is going? I now have the information I need to link a Zone to the Image Profiles available within that Zone.
### Input actions
So now I'm ready to work on the actions that will handle passing information between the vRA REST API and the Cloud Assembly deployment request form. For organization purposes, I'll stick them in a new module which I'll call `com.virtuallypotato.inputs`. And for the immediate purposes, I'm going to focus on just two fields in the request form: `Zone` (for where the VM should be created) and `Image` (for what VM template it will be spawned from). The `Zone` dropdown will be automatically populated when the form loads, and `Image` options will show up as soon as a the user has selected a Zone.
### `vraGetZones` action
This action will basically just repeat the call that I tested above in `vraTester`, but parse the result to extract just the Zone names. It will pop those into an array of strings which can be rendered as a dropdown on the Cloud Assembly side.
=> vraGetZones_action.png Image: vraGetZones action

/*

JavaScript: vraGetZones action

Inputs: none

Return type: Array/string

var zoneNames = new Array();

var token = System.getModule("com.virtuallypotato.utility").vraLogin();

var zones = JSON.parse(System.getModule("com.virtuallypotato.utility").vraExecute(token, "GET", "/iaas/api/zones", null)).content;

zones.forEach(

function (zone) {

zoneNames.push(zone.name);

}

);

zoneNames.sort();

System.getModule("com.virtuallypotato.utility").vraLogout(token);

return zoneNames;

### `vraGetImages` action
Once the user has selected a Zone from the dropdown, the `vraGetImages` action will first contact the same `/iaas/api/zones` API to get the same list of available Zones. It will look through that list to find the one with the matching name, and then extract the `._links.region.href` URI for the Zone.
Next it will reach out to `/iaas/api/images` to retrieve all the available images. For each image, it will compare its associated `._links.region.href` URI to that of the designated Zone; if there's a match, the action will add the image to an array of strings which will then be returned back to the request form.
Oh, and the whole thing is wrapped in a conditional so that the code only executes when `zoneName` has been set on the request form; otherwise it simply returns an empty string.

| Input | Type | Description |

|:--- |:--- |:--- |

| `zoneName` | `string` | The name of the Zone selected in the request form |


/* JavaScript: vraGetImages action

Inputs: zoneName (string)

Return type: array/string

if (!(zoneName == "" || zoneName == null)) {

var arrImages = new Array();

var regionUri = null;

var token = System.getModule("com.virtuallypotato.utility").vraLogin();

var zones = JSON.parse(System.getModule("com.virtuallypotato.utility").vraExecute(token, "GET", "/iaas/api/zones", null)).content;

System.debug("Zones: " + JSON.stringify(zones));

for each (zone in zones) {

if (zone.name === zoneName) {

System.debug("Matching zone: " + zone.name);

regionUri = zone._links.region.href;

}

if (regionUri != null) { break; };

}

System.debug("Matching region URI: " + regionUri);

var images = JSON.parse(System.getModule("com.virtuallypotato.utility").vraExecute(token, "GET", "/iaas/api/images", null)).content;

System.debug("Images: " + JSON.stringify(images));

images.forEach(

function (image) {

if (image._links.region.href === regionUri) {

System.debug("Images in region: " + JSON.stringify(image.mapping));

for (var i in image.mapping) {

System.debug("Image: " + i);

arrImages.push(i);

}

}

}

);

arrImages.sort();

System.getModule("com.virtuallypotato.utility").vraLogout(token);

return arrImages;

} else {

return [""];

}

I'll use the **Debug** button to test this action real quick-like, providing the `NUC` Zone as the input:
=> vraGetImages_action.png Image: vraGetImages action
It works! Well, at least when called directly. Let's see how it does when called from Cloud Assembly.
### Cloud Assembly request
For now I'm really only testing using my new vRO actions so my Cloud Template is going to be pretty basic. I'm not even going to add any resources to the template; I don't even need it to be deployable.
=> blank_template.png Image: Completely blank template
What I do need are two inputs. I'd normally just write the inputs directly as YAML, but the syntax for referencing vRO actions can be a bit tricky and I don't want to mess it up. So I pop over to the **Inputs** tab in the editor pane on the right and click the **New Cloud Template Input** button.
=> need_input.png Image: Need input!
I'll start with an input called `zoneName`, which will be a string:
=> zoneName_input_1.png Image: zoneName input
I'll then click on **More Options** and scroll to the bottom to select that the field data should come from an *External Source*:
=> zoneName_input_2.png Image: zoneName more options
I click the **Select** button and can then search for the `vraGetZones` action I wish to use:
=> zoneName_input_3.png Image: zoneName selecting the action
And then hit **Create** to add the new input to the template.
Next I'll repeat the same steps to create a new `image` input. This time, though, when I select the `vraGetImages` action I'll also need to select another input to bind to the `zoneName` parameter:
=> image_input.png Image: Binding the input
The full code for my template now looks like this:

formatVersion: 1

inputs:

zoneName:

type: string

title: Zone

$dynamicEnum: /data/vro-actions/com.virtuallypotato.inputs/vraGetZones

image:

type: string

title: Image

$dynamicEnum: /data/vro-actions/com.virtuallypotato.inputs/vraGetImages?zoneName={{zoneName}}

resources: {}

And I can use the **Test** button at the bottom of the Cloud Assembly template editor to confirm that everything works:
=> recording.gif Image: Testing the request fields
It does!
### Conclusion
This has been a very quick introduction on how to start pulling data from the vRA APIs, but it (hopefully) helps to consolidate all the knowledge and information I had to find when I started down this path - and maybe it will give you some ideas on how you can use this ability within your own vRA environment.
In the near future, I'll also have a post on how to do the same sort of things with the vCenter REST API, and I hope to follow that up with a deeper dive on all the tricks I've used to make my request forms as dynamic as possible with the absolute minimum of hardcoded data in the templates. Let me know in the comments if there are any particular use cases you'd like me to explore further.
Until next time!


---


=> mailto:wheel.east.brief@clkdmail.com?subject=Re%3A%20Getting%20Started%20with%20the%20vRealize%20Automation%20REST%20API 📧 Reply by email


## Related articles

=> /dynamic-robots-txt-hugo-external-data-sources/index.gmi Generate a Dynamic robots.txt File in Hugo with External Data Sources
=> /automate-packer-builds-github-actions/index.gmi Automate Packer Builds with GithHub Actions
=> /kudos-with-cabin/index.gmi Kudos With Cabin
---

=> / Home
=> https://runtimeterror.dev/getting-started-vra-rest-api/ This page on the big web