Getting token within vRO polyglot code

Share on:

How to get a token and call Aria Automation API from Python, JavaScript, or PowerShell code

Introduction

Besides the default JavaScript (ES5) language Aria Automation Orchestrator allows writing code in Python, JavaScript (NodeJS), and PowerShell (called polyglot scripting). Unlike ES5 JS, these languages do not have built-in support to use Orchestrator features, ie. configuration elements, other actions, plugins.

To access Orchestrator or Automation, these actions (or scriptable tasks) need to call REST API directly. For that a token is required.

Update: a new section is added about the permissions related to the acquired token

Python

Context, Token

Let's see what we have in the context of Python runtime.

1def handler(context, inputs):
2    for item in context:
3        print(item + ": " + str(context[item]))
4    return "done"

We have a getToken function defined here, let's call it.

1def handler(context, inputs):
2    print(context["getToken"]())
3    return "done"

That looks like an access token, let's try it!

Calling Orchestrator API

When we printed the context, we saw that we have the key vcoUrl defined, its value is http://localhost:8280/vco

This is a "loopback" address where we can call Orchestrator API directly. The following code collects data of server configuration via REST API.

 1import http
 2def handler(context, inputs):
 3    token = context["getToken"]()
 4    headers = {'Authorization': "Bearer " + token }
 5
 6    conn = http.client.HTTPConnection("localhost", 8280)
 7    conn.request("GET", "/vco/api/server-configuration/settings", '', headers)
 8    res = conn.getresponse()
 9    print("HTTP status: " + str(res.status))
10    data = res.read().decode("ascii")
11    print("server configuration settings: " + data)    
12    return data

We added the token to the request headers and the call worked perfectly.

Calling Automation API

Let's try to use the same token and see if it works with Automation API as well. Calling Automation API we need the FQDN of the (tenant) Automation instance, so we add this as an input to the action with the path of the API call.

In the code we disable certificate verification.

 1import http, ssl
 2
 3def handler(context, inputs):
 4    token = context["getToken"]()
 5    headers = {'Authorization': "Bearer " + token }
 6
 7    conn = http.client.HTTPSConnection(inputs["vraHost"], context=ssl.SSLContext()) # ignore cert errors
 8    conn.request("GET", inputs["path"], '', headers)
 9    res = conn.getresponse()
10    print("HTTP status: " + str(res.status))
11    data = res.read().decode("ascii")
12    print("response data: " + data)    
13    return data

Let's call the action with the path /deployment/api/billing-metrics to gather some billing data.

We can conclude that the token is working with Automation API as well.

JavaScript (NodeJS)

Context, Token

Let's see what we have in the context of the NodeJS runtime.

1exports.handler = (context, inputs, callback) => {
2    console.log(context);
3    callback(undefined, "done");
4}

We have a getToken function again, let's call it.

1exports.handler = (context, inputs, callback) => {
2    console.log(context.getToken());
3    callback(undefined, "done");
4}

Hmm, this is not good (yet). NodeJS is asynchronous so we need to "consume" the Promise to "resolve" and collect the result.

1exports.handler = (context, inputs, callback) => {
2    context.getToken()
3        .then(token => {
4            console.log(token);
5            callback(undefined, "done");
6        })
7}

Much better!

Calling Orchestrator API

We'll use the same "loopback" address to call Orchestrator as with Python before. As we are running multiple API calls, we'll do promise chaining.

 1exports.handler = (context, inputs, callback) => {
 2    context.getToken()
 3        .then(token => {
 4            // console.log(token);
 5            return {
 6                method: "GET",
 7                headers: {
 8                    "Authorization": "Bearer " + token
 9                }
10            }
11        })
12        .then(requestOptions => fetch(context.vcoUrl + "/api/server-configuration", requestOptions))
13        .then(response => response.text())
14        .then(result => {
15            console.log(result);
16            callback(null, result);
17        })
18}

Async, Await to the rescue

We do not need asynchronous API calls against Aria API so would be nice to have a more "traditional" way of calling these URLs. Modern JavaSript provides a simplified syntax to wait for Promises: async and await. The runtime still works asynchronously, but the code is easier to write and understand. We need to add the async keyword into the handler function definition to allow await in the function body:

 1exports.handler = async (context, inputs, callback) => {
 2    let token = await context.getToken();
 3    // console.log(token);
 4    let requestOptions = {
 5        method: "GET",
 6        headers: {
 7            "Authorization": "Bearer " + token
 8        }
 9    }
10    let response = await fetch(context.vcoUrl + "/api/server-configuration", requestOptions);
11    let result = await response.text();
12    console.log(result);
13    return result;
14}

The same outpout procuded, but I prefer this code above.

Calling Automation API

We add the same inputs (vraHost, path) to the action as to the Python implementation.

We use the the simplified syntax and ignore certificate errors again.

 1process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; // disable cert verification
 2
 3exports.handler = async (context, inputs, callback) => {
 4    let token = await context.getToken();
 5    console.log(token);
 6    let requestOptions = {
 7        method: "GET",
 8        headers: {
 9            "Authorization": "Bearer " + token
10        }
11    }
12    let response = await fetch("https://" + inputs.vraHost + inputs.path, requestOptions);
13    let result = await response.text();
14    console.log(result);
15    return result;
16}

The output of Metrics API:

We got some warnings for the certificate but received the billing information via Automation API.

PowerShell

Context, Token

Let's see what we have in the context of the PowerShell runtime.

1function Handler($context, $inputs) {
2    Write-Host $context
3    return "done"
4}

I almost gave up here, as did not see any getToken function. However, if I try to call it, it is there.

1function Handler($context, $inputs) {
2    Write-Host $context.getToken()
3    return "done"
4}

Calling Automation API

We add the same inputs (vraHost, path), but change the output to Properties:

Let's skip checking certificates :)

1function Handler($context, $inputs) {
2    $token = $context.getToken()
3    $headers = @{"Authorization" = "Bearer $token"}
4    $uri = "https://" + $inputs.vraHost + $inputs.path
5    $response = Invoke-RestMethod -Uri $uri -Headers $headers -SkipCertificateCheck
6    ConvertTo-Json -Compress $response | Write-Host
7    return $response
8}

The output of Metrics API:

Multitenancy

I could not test if Automation multitenancy allows using the token within all tenants besides the default one. I let you test it and leave a comment if you have some results.

Security, Permissions

Please note that the token created of the running user's context, so its permissions inherited from the running user. API calls allowed for the user will work: but if RBAC does not allow a certain call, the response will be forbidden. Make sure to either grant permissions to the running user to access the requested resource, or run the action/workflow's scriptable task by a user permitted to make the call.

Thanks Ankush sethi for raising the point.

You can download the com.test.gettoken package containing the code from GitHub: https://github.com/kuklis/vro8-packages