vCD UI Extensibility API Client: Part Three – Data Management

Welcome back to the third part of the three-part series describing how to use the new API Client provided in the UI Extensibility Seed Project.  In this post, I demonstrate how to use the data management functionality provided by the API Client.

Retrieve Data

The API Client provides two methods to retrieve data from the vCloud API, get and getEntity.  Both methods return an observable.

get

The Client API get method allows developers to retrieve data from the vCloud API using the proper URL endpoint.  The get method returns an observable that when subscribed to, returns the requested data.
this.client
  .get('api/org')
  .subscribe(org => {
    console.log(org);
  });

getEntity

The Client API getEntity method allows developers to retrieve data from the vCloud API using an entity id.  The Client API uses the entity id to determine the proper link and then automatically retrieves the associated data.  The getEntity method returns an observable that when subscribed to, returns the requested data.
this.client
  .getEntity("urn:vcloud:org:625ddb93-fc23-4145-accf-53898e3201d4")
  .subscribe(org=> {
    console.log(org);
  });

Sync vs Async

The vCloud API provides endpoints for creating, updating and deleting records.  Each of these endpoints are configured to respond either synchronously or asynchronously.  To determine the expected output types, look at the Output Type specified in the User Operations, Admin Operations or Extension Operations section of the vCloud API Reference.  If the expected Output Type is a TaskType, then you will use the Async version of the method.  Otherwise, use the Sync version of the method.

The Client API includes discrete methods to handle synchronous and asynchronous requests:
Synchronous:
  • createSync()
  • updateSync()
  • deleteSync()
Asynchronous:
  • createAsync()
  • updateAsync()
  • deleteAsync()

Regardless of whether you use the synchronous or asynchronous version of the methods listed above, the browser will always send the request in an asynchronous manner.

The Sync version of each of these methods returns an observable for the specified object type.  The Async version, instead returns a TaskType object.

Task Helpers

The API Client includes a few helper methods to simplify working with tasks.

isTaskComplete

The isTaskComplete helper method takes a TaskType object and returns true if the status is equal to any of the following: success, error, canceled or aborted.
this.client.isTaskComplete(Task)

updateTask

The updateTask helper method takes a TaskType object and uses the href attribute to retrieve the most current task information.
this.client
  .updateTask(Task)
  .subscribe(latestTask => {
    console.log(latestTask);
  })

Create

The Client API provides two methods to create new records: createSync and createAsync.  Both require that you create the desired dataset to send to the vCloud API.  Use Input Type parameter listed in the Operations sections of the vCloud API Reference to help define that dataset.

Here we will create a new organization using the createSync method.

Import the AdminOrgType, OrgSettingsType, OrgGeneralSettingsType bindings, because our dataset requires them:
import { AdminOrgType, OrgSettingsType, OrgGeneralSettingsType } from "@vcd/bindings/vcloud/api/rest/schema_v1_5";
The dataset for the new organization can be defined using either JSON or by creating objects:
let newOrg = <AdminOrgType> { 
  name: 'TestOrg',
  fullName: 'Test Organization',
  isEnabled: false,
  settings <OrgSettingsType> {
    orgGeneralSettings: <OrgGeneralSettingsType> {
      canPublishCatalogs: true,
      canPublishExternally: true,
      deployedVMQuota: 0
    }
  }
}
let newOrg = new AdminOrgType;
newOrg.name = 'TestOrg'; 
newOrg.fullName = 'Test Organization'; 
newOrg.isEnabled = false;
newOrg.settings = new OrgSettingsType; 
newOrg.settings.orgGeneralSettings = new OrgGeneralSettingsType;
newOrg.settings.orgGeneralSettings.canPublishCatalogs = true;
newOrg.settings.orgGeneralSettings.canPublishExternally = true;
newOrg.settings.orgGeneralSettings.deployedVMQuota = 0;
Send the dataset synchronously using the createSync method:
this.client
  .createSync<AdminOrgType>('api/admin/orgs', newOrg)
  .subscribe(org => {
    console.log(org);
  });

Update

The Client API provides two methods to update records, updateSync and updateAsync.  Prior to doing an update, you typically need to use one of either the get or getEntity methods to retrieve the current dataset.  Once you have the dataset, apply your changes and then send the entire dataset back to the proper vCloud API endpoint:
this.client
  .get<AdminOrgType>('api/admin/org/1084874f-9de0-4643-b935-37bf19654e3e')
  .subscribe(org => {
    org.name='TestOrgUpdated';
    org.fullName='Test Organization Updated';

    this.client
      .updateSync<AdminOrgType>(
        'api/admin/org/1084874f-9de0-4643-b935-37bf19654e3e',
        org)
      .subscribe(updatedOrg => {
        console.log(updatedOrg);
      })
  });

Delete

The Client API provides three methods to delete records: removeItem, deleteSync and deleteAsync.

The removeItem method automatically deletes the record if the specified object contains a remove link.
this.client
  .get<AdminOrgType>('api/admin/org/1084874f-9de0-4643-b935-37bf19654e3e')
  .subscribe(org => {
    this.client
      .removeItem(org)
      .subscribe(resp => {
        console.log(resp);
      })
  });
Use either the deleteSync or deleteAsync methods to delete records when you know the proper vCloud API endpoint:
this.client
  .deleteSync('api/admin/org/1084874f-9de0-4643-b935-37bf19654e3e')
  .subscribe(resp => {
    console.log(resp)
  });

Conclusion

I hope that this series has helped provide some insight into how to use the UI Extensibility API Client.  The API Client already provides a lot of functionality out of the box.  However, the development effort continues at a rapid pace and I expect that more is coming soon.  Please download the Seed Project and start creating new and exciting vCloud Director Extensions.

vCD UI Extensibility API Client: Part Two – Typed Queries

Welcome back to the second part of the three-part series describing how to use the new API Client provided in the UI Extensibility Seed Project.  In this post, I demonstrate how to use the Typed Query functionality provided by the API Client.

Getting Started

To get started, let’s create a simple Typed Query that returns a list of all organizations available in your vCD instance.

Import the VcdApiClient and Query modules:
import { VcdApiClient } from '@vcd/sdk';
import { Query } from "@vcd/sdk/query";
Include the VcdApiClient in the constructor:
export class SimpleComponent implements OnInit {
  constructor(
    @Inject(EXTENSION_ASSET_URL) public assetUrl:string,
    private client:VcdApiClient
  ) {}
Use the Query.Builder method to build a Typed Query of type organization:
let queryRequest = Query.Builder
  .ofType('organization');
Send the request to the vCloud API, by subscribing to the query:
this.client.query(queryRequest)
  .subscribe(queryResult => {
    console.log(queryResult);
  });

Build and deploy the extension into your vCD environment (Publishing vCloud Director User Interface Extensions). Next, log into your vCD HTML5 client and then navigate to the new extension. Finally, open your browser console to see the queryResult containing all of the organizations in your vCD instance:Browser Developer Console

Query Builder

The example provided above is a very simple example.  The API Client Query Builder offers numerous helper methods to fine tune your Typed Queries.  In this section, I will describe and provide examples for each of the various methods available for defining your Typed Queries.

ofType

Use the ofType method to specify the desired Query Type that you are looking to request.

As seen previously, we used the ofType method to retrieve a list of all organizations:
let queryRequest = Query.Builder
  .ofType('organization');
There are many Query Types available besides organization, including the following:
  • providerVdc
  • adminOrgVdc
  • externalNetwork
  • datastore
  • task
  • edgeGateway
  • orgVdcNetwork

To find the complete list of available Query Types, send a GET request to https://{{vCD-Cell}}/api/query.

format

The format method specifies the format type returned in the results.  The following formats are available for use:
  • idrecords
  • records
  • references
By default, Query Builder uses the idrecords format.  Use the format method to specify the desired format:
let queryRequest = Query.Builder
  .ofType('organization')
  .format('records');

For more information about each of these format types, see the vCloud API Guide on the vCloud Documentation site.

links

The links method specifies whether to include the link elements in the results.

By default, Query Builder includes the link elements.  Use the links method set to false to remove them:
let queryRequest = Query.Builder
  .ofType('organization')
  .links(false);

pageSize

The pageSize method specifies how many records should be returned in the results.

By default, Query Builder returns 25 records.  Use the pageSize method to specify the desired number of results:
let queryResult = Query.Builder
  .ofType('organization')
  .pageSize(10);

fields

The fields method allows you to specify the fields that you want included in the results.

Use the fields method to specify the list of fields:
let queryResult = Query.Builder
  .ofType('organization')
  .fields('isEnabled','name');

sort

The sort method allows you to specify the order that you would like the results returned in.

In this example, we choose to use the QueryResultOrgRecordType object, rather then using hard coded strings.  Import the object:
import { QueryResultOrgRecordType } from "@vcd/bindings/vcloud/api/rest/schema_v1_5";

Create a dictionary containing the desired field.  If you would like to sort the results in descending order, set reverse to true.  For example:

let queryResult = Query.Builder
  .ofType('organization')
  .sort(
    {field:QueryResultOrgRecordType.Fields.IS_ENABLED},
    {field:QueryResultOrgRecordType.Fields.NAME, reverse:true});

filter

The vCloud API allows developers to include filter expressions which limit the result set to only include results based on the specified criteria.  The API Client includes a Filter Builder to help simplify defining the filter criteria.

The code below demonstrates how to request organizations that are currently enabled.  Disabled organizations will be ignored and not included in the result set.

Import the Filter functionality:
import { Filter } from "@vcd/sdk/query";
Create a new Filter Builder object:
let builder = new Filter.Builder();
Define the filter criteria to only return records that are enabled:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.IS_ENABLED)
    .equalTo(true)
  .query();
Use Query Builder’s filter method to include the filter criteria:
let queryResult = Query.Builder
  .ofType('organization')
  .filter(filter);
Subscribe to the query to retrieve the filtered results:
this.client
  .query(queryRequest)
  .subscribe(queryResult => {
    console.log(queryResult);
  });

Filter Syntax

Filter Builder can be used to create complex filter criteria to retrieve any desired result set.  Listed below are the various methods available in Filter Builder.

To create a valid filter, you must first specify the field with the is method and then specify a comparison method.  All possible comparison methods are listed below.

Comparisons
equalTo(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.IS_ENABLED)
    .equalTo(true)
  .query();
notEqualTo(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.IS_ENABLED)
    .notEqualTo(true)
  .query();
lessThan(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NUMBER_OF_VDCS)
    .lessThan(2)
  .query();
lessOrEqualTo(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NUMBER_OF_VDCS)
    .lessOrEqualTo(2)
  .query();
greaterThan(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NUMBER_OF_VDCS)
    .greaterThan(1)
  .query();
greaterOrEqualTo(value):
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NUMBER_OF_VDCS)
    .greaterOrEqualTo(1)
  .query();
like(value, Filter.MatchMode.START) – Matches strings that start with the specified value:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('org', Filter.MatchMode.START)
  .query();
like(value, Filter.MatchMode.END) – Matches strings that end with the specified value:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('gold', Filter.MatchMode.END)
  .query();
like(value, Filter.MatchMode.ANYWHERE) – Matches strings that contain the specified value:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('org', Filter.MatchMode.ANYWHERE)
  .query();
Logical Operators

Logical Operators allow you to chain numerous comparisons together using either and or or operations.

and() – Perform an and operation between the previous criteria element in the chain and the one immediately following:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.IS_ENABLED)
    .equalTo(true)
  .and()
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('gold', Filter.MatchMode.START),
  .query();
and(criteria1, criteria2, [criteriaN]) – Perform an and operation between the provided list of criteria elements:
let filter = builder
  .and(
    builder.is(QueryResultOrgRecordType.Fields.IS_ENABLED)
      .equalTo(true),
    builder.is(QueryResultOrgRecordType.Fields.NAME)
      .like('gold', Filter.MatchMode.START)
  ).query();
or() – Perform an or operation between the previous criteria element in the chain and the one immediately following:
let filter = builder
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('gold', Filter.MatchMode.START),
  .or()
  .is(QueryResultOrgRecordType.Fields.NAME)
    .like('silver', Filter.MatchMode.START),
  .query();
or(criteria1, criteria2, [criteriaN]) – Preform an or operation between the provided list of criteria elements:
let filter = builder
  .or(
    builder.is(QueryResultOrgRecordType.Fields.NAME)
      .like('gold', Filter.MatchMode.START),
    builder.is(QueryResultOrgRecordType.Fields.NAME)
      .like('silver', Filter.MatchMode.START),
  ).query();

Paging

When a Typed Query is sent to the vCloud API, the response includes links used to navigate between pages of data.  The API Client includes helper functions which make it easy to navigate between the pages and to add navigational buttons/links to tables.

hasFirstPage – Determine if there is a first page
this.client.hasFirstPage(queryResult)
hasPreviousPage – Determine if there is a previous page
this.client.hasPreviousPage(queryResult)
hasNextPage – Determine if there is a next page
this.client.hasNextPage(queryResult)
hasLastPage – Determine if there is a last page
this.client.hasLastPage(queryResult)
firstPage – Make request to the vCloud API to retrieve the first page
this.client.firstPage(queryResult)
  .subscribe(queryResult => {
    console.log(queryResult);
  });
previousPage – Make request to the vCloud API to retrieve the previous page
this.client.previousPage(queryResult)
  .subscribe(queryResult => {
    console.log(queryResult);
  });
nextPage – Make request to the vCloud API to retrieve the next page
this.client.nextPage(queryResult)
  .subscribe(queryResult => {
    console.log(queryResult);
  });
lastPage – Make request to the vCloud API to retrieve the last page
this.client.lastPage(queryResult)
  .subscribe(queryResult => {
    console.log(queryResult);
  });

Conclusion

Hopefully this post has given you some idea of how to use the Typed Query functionality included in the UI Extensibility API Client.

The next post in this series will describe how to manage data using the API Client.

vCD UI Extensibility API Client: Part One – Preparation
vCD UI Extensibility API Client: Part Two – Typed Queries

vCD UI Extensibility API Client: Part One – Preparation

The vCD UI Extensibility Seed Project now includes a new API Client. The API Client allows developers to natively communicate between vCD UI Extensions and the vCloud API.

The API Client consists of the following two packages:

  • @vcd/bindings – A collection of Typescript objects that represent the vCD API’s requests and responses.
  • @vcd/sdk – An Angular module that provides a REST API client with consistent handling of patterns and behaviors specific to the vCloud REST API.  This module also provides various utilities and tools, such as query and data management.

This is the first post in a three-part vCD UI Extensibility API Client series. This initial post describes how to prepare your development environment to begin to use the new API Client.

Download the Seed Project

Use the git clone command to download the Seed Project from GitHub (vcd-ext-sdk):
git clone https://github.com/vmware/vcd-ext-sdk.git project
cd project

Build

In order to build the API Client packages, you must first generate the Java API bindings.  Inside the project root directory, run the following commands to generate the Java API bindings:
cd java
mvn install
Use the resulting Java API bindings to generate the source files for the API Client TypeScript bindings :
cd ../ui/api-client
mvn generate-sources

Install

Now that we have generated all of the source files, we can build the API Client TypeScript packages. Initialize the top level project and then initialize and build @vcd/bindings and @vcd/sdk in the ./ui/api-client directory:
yarn
yarn bootstrap

Test

At this point, the API Client is fully ready to use. To test it, we use the default Seed Project, which already contains sample code demonstrating how to display the currently logged in organization and username.

import { Component, Inject, OnInit } from "@angular/core";
import { VcdApiClient } from '@vcd/sdk';
import { EXTENSION_ASSET_URL } from '@vcd/sdk/common';
import { Observable } from "rxjs";

@Component({
  selector: "plugin-simple",
  templateUrl: "./simple.component.html",
  styleUrls: ['./simple.component.scss'],
  host: {'class': 'content-container'}
})
export class SimpleComponent implements OnInit {
  username: Observable;
  tenant: Observable;

  constructor(@Inject(EXTENSION_ASSET_URL) public assetUrl: string, private client: VcdApiClient) {}

  ngOnInit(): void {
    this.tenant = this.client.organization;
    this.username = this.client.username;
  }
}
<div>
  <p><i>Logged into {{tenant | async}} as {{username | async}}</i></p>
  <h1>{{"tos.title" | translate}}</h1>
  <p>{{"tos.1" | translate}}</p>
  <p>{{"tos.2" | translate}}</p>
  <img src="{{assetUrl}}/monkey.jpg" alt="Thoughtful Primate">
</div>

Line 16 of the simple.component.ts file adds the VcdApiClient to the Component constructor.  This automatically initializes the API Client. Lines  19 and 20 retrieve the organization and username from the API Client and then store them.  Finally, line 2 of the simple.component.html displays the organization and username information.

To test that it is working, initialize and build the environment:
cd ../vcd-plugin-seed
yarn
yarn run build
Edit your ui_ext_api.ini file with the proper values for your environment:
vi ui_ext_api.ini
Deploy your plugin to vCD:
yarn run deploy
You should now see the following screen:

ui-ext-api-client-default-page

Notice that the current organization name and username displays in the top-left corner of the screen.

Conclusion

At this point, we have fully configured the API Client for usage in your custom extension.  The next part of this vCD UI Extensibility API Client series will demonstrate how to use the API Client to execute Typed Queries.

vCloud Director API Authentication

vCloud Director version 9.1 (API version 30.0) added the ability to use Bearer  Authentication for user authentication.  Bearer Authentication is a very simple standard authentication method used in many API environments.  Previously, vCloud Director used the x-vcloud-authorization header as the authorization token.  As of the vCD 9.1 release, using the x-vcloud-authorization header has been deprecated and will possibly be removed in future vCloud Director versions.

How to authenticate using Bearer Tokens

  1. Make a POST request to /api/sessions/.  Make sure to include the proper Accept header: “application/*+xml;version=30.0”, along with your username@Organization and password.
    curl-30.0-auth-headerscurl-30.0-auth-xml
  2. The response from the /api/sessions request will include a X-VMWARE-VCLOUD-ACCESS-TOKEN header.
  3. For all subsequent vCloud Director requests, send an Authorization header containing Bearer along with the value returned in the X-VMWARE-VCLOUD-ACCESS-TOKEN header.curl-30.0-org