This section demonstrates how to build a multi-instance solution add-on including some of the most frequently used elements and actions associated with them.
Please verify the following prerequisites are met. - Setting up the Development Environment - GitHub public repositories are accessible
The vcd-ext-shell
allow vendors to use starter templates for creating solution add-ons and add-on elements. The source of the available templates is located into Cloud Director Extension Standard Library Git repository.
Every branch of the repository contains two folders at the root level - add-on
containing a solution add-on template per folder named after a use case, and element
following the same pattern but for the add-on building blocks, the element types
.
Both add-on
and element/<element type>
might contain folders starting with .
, these folders contain helper tools for building, testing and documenting an add-on or its elements.
Filesystem Layout
<branch>
- add-on/<use case>
- add-on/.<add-on centric toolchain>
- element/<element type>/<use case>
- element/<element type>/.<element centric toolchain>
The templates and tools are further organized by branches. The names of the branches follow cloud-director-X.X.X
pattern and claim that all add-ons and elements templates in that branch has been validated against an environment running Cloud Director version X.X.X
. The main
branch contains alpha versions of the add-ons and elements for the next upcoming release.
IMPORTANT: When a vendor used starter template from cloud-director-X.X.X
to bootstrap his add-on with minimal version of Cloud Director X.X.X
and receive complain from a provider running later version of Cloud Director X.Y.Z
, the one can review directly the fix made by VMware or its partner applied on the started template by comparing the branches cloud-director-X.X.X
with cloud-director-X.Y.Z
, and find out what was the exact line causing the issue.
main
cloud-director-10.4.1
...
cloud-director-10.5
Let’s create a new solution add-on based on the Skeleton
template.
$ vcd-ext-shell
no solution |> solution new --vendor <vendor name> --name skeleton --version 1.0.0 --vcd-version 10.4.1 --from-template cloud-director-10.4.1/multi-instance-skeleton --folder <project directory>
NOTE: The --folder
is optional and defaults to the current working directory.
And build its elements and actions sources into installable artifacts.
skeleton |> solution build
Then create a solution add-on instance of Skeleton
into your active
cloud director environment (set via vcd-ext-shell
/ setup
).
skeleton |> solution run create-instance --name prjpoc01 --input-provider-business-scope-property prjpoc01 --input-password We1$0me --debug
Explore the installation process and review the elements created in Cloud Director.
skeleton |> solution run get-instances --debug
DEBUG Authenticating with Cloud Director using username and password
DEBUG Looking for Solutions Add-On Instance type
DEBUG Found existing Solutions Add-On Instance type version=1.0.0
NAME READY STATUS SCOPE RESOURCES
--------------------------------------------
prjpoc01 6/6 Ready tenant 7
Finally, delete the instance.
skeleton |> solution run delete-instance --name prjpoc01 --input-justification "cleanup by developer" --debug
Multi-instance add-on feature allow vendors to design their solutions for better scalability, availability, partitioning and performance by encapsulating their minimal unit of business into a solution add-on instance. With such level of granularity providers could gain certain flexibility while designing their offerings by mixing and matching various add-ons instances to fit their scopes of business.
Examples of scopes of business: - span across multiple tenants - bound to a single tenant - managed by single tenant - bound to a cluster - span across multiple clusters - bound to a single storage profile - span across multiple storage profiles - bond to LDAP group - etc.
The Skeleton and most of the add-ons capture user input via UI Plugin and REST or MQTT APIs into a Runtime Defined Entities which later are processed by a backend service hosted on a virtual appliance under a Solution Landing Zone. The implementation of such flow further requires a right bundle with set of management rights for the created Runtime Defined Entities, a role including these rights and a Cloud Director local account assigned with the role, used back the backend virtual appliance.
The add-on defines three input fields which provider could use to tailor the instance creation.
inputs:
- name: provider-business-scope-property
title: Some Business Scope Property
required: true
description: This is some Business Scope Property required for every add-on instance
# Secure property
- name: password
title: Password
required: true
description: The password of the new local Cloud Director account about to be created and associated with a business scope
secure: true
# Property visible only on delete operation
- name: justification
title: Justification
type: String
description: Why do you delete this Solution instance
delete: true
The add-on defines that it supports multiple instances.
policies:
supportsMultipleInstances: true
NOTE: When supportsMultipleInstances
is set to true
every element part of the add-on will be provisioned only once and its resource will be shared between all add-on instances. This behavior could be changed via elements.
Add-on defines PreCreate
, PostDelete
and PreScope
triggers which will be executed just before the first element is created during installation, after the last element is deleted on add-on delete operation and whenever a scope is changed. This implementation of triggers uses a switch inside the binary actions/multipurposeaction
to decide whether it is called for install or for delete operation. It is up to the vendor to decide whether to use multiple binaries or single one with switch.
triggers:
- event: PreCreate
action: actions/multipurposeaction
timeout: 30
- event: PostDelete
action: actions/multipurposeaction
timeout: 30
- event: PreScope
action: actions/multipurposeaction
timeout: 30
The multipurposeaction
is written in GoLang but triggers can be implemented on any language as long as their build provides binaries for the three major operating systems used for development.
actions/multipurposeaction/dist/windows.exe
actions/multipurposeaction/dist/linux
actions/multipurposeaction/dist/darwin
The multipurposeaction
accepts the add-on execution context properties into its standard input
and outputs properties into its standard output
following the log line format output:{"name": "<key>", "value": "value", "secure: true|false}
// This is the body of the multipurpose action.
// It will be called multiple times for various places where it is referenced in the manifest.yaml#triggers and manifest.yaml#element#triggers.
// The multipurpose action pattern is used for convenience, source code size reduction, and improved usability.
func main() {
fmt.Println("Solution add-on trigger has been called")
inputProperties := readPropertiesFromStandardInput()
// Example: Handle solution add-on global triggers
if inputProperties.Element == elementNone && inputProperties.Event == eventPreCreate {
fmt.Println("solution pre-create event")
// Example: set or update multiple solution add-on global properties at once
OutputProperties{
{Name: "exampleKeyMap", Value: map[string]any{"k1": "v1", "k2": "v2"}, Secure: false},
{Name: "exampleKeyArrayAny", Value: []any{1, "v", true, map[string]bool{"k": true}}, Secure: false},
}.writePropertiesIntoStandardOutput()
}
if inputProperties.Element == elementNone && inputProperties.Event == eventPostDelete {
fmt.Println("solution post-delete event")
}
// Example: Handle solution add-on trigger for specific element
if inputProperties.Element == elementCloudDirectorUser && inputProperties.Event == eventPostCreate {
fmt.Println("solution pre-create event")
// Example: Set or update a solution add-on global property
OutputProperty{
Name: "api-token", Value: "XXX API Token XXX", Secure: true,
}.writePropertyIntoStandardOutput()
}
fmt.Println("Solution add-on trigger terminated")
}
UI Plugins are always deployed with the first add-on instance installation and removed with the last removed instance. They are shared between all instances. This ui-plugin
holds its sources under ui
add-on project folder and when installed will be published by default only to the provider under the default tenant but will allow later to be published to other tenants.
elements:
- name: ui
description: User interface
type: ui-plugin
spec:
publish:
provider: true
tenants: true
Similar to UI Plugins the Runtime Defined Entities are shared between all add-on instances and follow the same installation and deletion rules. This defined-entity
holds its sources under db-schema
add-on project folder and when installed will create a Runtime Defined Interface, Runtime Defined Entity and Behavior of type built-in Function as a Service.
elements:
- name: db-schemas
description: Business Objects Schemas
type: defined-entity
When a Runtime Defined Entity is created the Cloud Director implicitly creates set of management rights for that entity type with the following naming convection.
- urn:vcloud:type:<entity vendor>:<entity nss>:full_access
- urn:vcloud:type:<entity vendor>:<entity nss>:view
- urn:vcloud:type:<entity vendor>:<entity nss>:modify
- urn:vcloud:type:<entity vendor>:<entity nss>:admin
These rights frequently are bundled in to a right bundle and can be included into multiple roles or published into one or many tenants as global rights. This right bundle will be published only in the organization linked with the Solution Landing Zone. By convention the key of this organization is constant solution
.
elements:
- name: rights
description: Business objects rights
type: rights-bundle
spec:
name: Skeleton Rights
description: This rights bundle is created by Skeleton Add-On
publish:
solutionLandingZone: true
tenants: true
rights:
- urn:vcloud:type:vmware:skeleton_database_element:full_access
- urn:vcloud:type:vmware:skeleton_database_element:view
- urn:vcloud:type:vmware:skeleton_database_element:modify
- urn:vcloud:type:vmware:skeleton_database_element:admin
The rights can also be assigned directly to a role. This role will not be available neither to the global nor default(system) tenant scopes.
elements:
- name: role
description: Business objects role
type: role
spec:
name: Skeleton Role
description: This role is created by Skeleton Add-On
global: false
systemScope: false
publish:
solutionLandingZone: true
rights:
- urn:vcloud:type:vmware:skeleton_database_element:full_access
- urn:vcloud:type:vmware:skeleton_database_element:view
- urn:vcloud:type:vmware:skeleton_database_element:modify
- urn:vcloud:type:vmware:skeleton_database_element:admin
The role will be available only in the tenant referenced by the Solution Landing Zone. Each Skeleton add-on instance creates a local Cloud Director user with the newly defined role and uses a PostCreate
trigger to generate an API Token.
elements:
- name: cloud-director-user
description: User interacting with Cloud Director from backend
type: user
spec:
username: 'sva.{{ property `provider-business-scope-property` }}'
password: '{{ property `password` }}'
fullName: Skeleton backend system account
description: 'User sva.{{ property `provider-business-scope-property` }} with role {{ property `role.name` }}'
roleName: '{{ property `role.name` }}'
systemScope: false
triggers:
- event: PostCreate
# This action will output property "api-token" under the element
action: actions/multipurposeaction
timeout: 30
The API Token will be passed as OVF property alongside with the Cloud Director host and its endpoint certificates to an instance of the backend virtual appliance. The appliance will use the configuration to read and process the objects stored into the Runtime Defined Entities.
elements:
- name: backend-appliance
description: Backend processor of business objects
type: vapp
spec:
name: '{{ instance `name` }}'
ovfProperties:
- key: provider-business-scope-property
value: '{{ property `provider-business-scope-property` }}'
- key: cloud-director-host
value: '{{ property `cloudDirector.host` }}'
- key: cloud-director-host-certificates
value: '{{ property `cloudDirector.certificates` }}'
- key: api-token
value: '{{ property `api-token` }}'
hardwareCustomization:
numberOfCpus: 1
memorySize: 512
networks:
- assignment: auto
primary: true
capabilities: []
readyCondition:
# Wait for IP to be allocated.
# Note the example vApp does not contain real operating systems.
# It will be able to allocate IP only from static pool not DHCP.
#
# "ip":
timeoutMinutes: 10
NOTE: The appliance is just OVF descriptor without actual operating system. It aims to showcase the end-to-end flow.
Operations are a means of carrying out day-2 actions on the add-on at a later point in time. Since operations always invoke actions, the actions will receive the latest context for the add-on. An example is changing the domain name or certificate of Cloud Director, which can be addressed by a refresh action that calls the ‘actions/multipurposeaction’ skeleton to update the appliance with the new Cloud Director endpoint details.
operations:
- name: refresh
description: Refresh Cloud Director endpoint and certificate
inputs:
- name: justification
title: Justification
required: true
description: Justification to refresh the add-on instance
minLength: 1
maxLength: 24
action: actions/multipurposeaction
timeout: 60
An instance of Skeleton add-on can be installed and removed with the following commands.
IMPORTANT: The --encryption-key
argument is used mainly for development purposes. If set it will instruct the add-on operation to be executed on the machine where the ISO binary was executed. If skipped that operation will be sent to an agent in the Solution Landing Zone and all activities will be performed in the Cloud Director.
# Inject Skeleton add-on vendor certificate in Cloud Director certificate store
./darwin.run trust \
--accept \
--host <CLOUD_DIRECTOR_HOST> \
--username administrator \
--password 'secret' \
--certificate "$(./darwin.run get certificate --host <CLOUD_DIRECTOR_HOST>)"
# Create an instance of Skeleton add-on
./darwin.run create instance \
--accept \
--certificate "$(./darwin.run get certificate --host <CLOUD_DIRECTOR_HOST>)" \
--host <CLOUD_DIRECTOR_HOST> \
--username administrator \
--password 'secret' \
--trace \
--name prjpoc01 \
--input-provider-business-scope-property prjpoc01 \
--input-password 'We1$0me'
# Invoke an Skeleton add-on instance operation
./darwin.run invoke refresh instance \
--accept \
--certificate "$(./darwin.run get certificate --host <CLOUD_DIRECTOR_HOST>)" \
--host <CLOUD_DIRECTOR_HOST> \
--username administrator \
--password 'secret' \
--trace \
--name prjpoc01 \
--input-justification "Certificate renew"
# Delete the created instance of Skeleton add-on
./darwin.run delete instance \
--accept \
--certificate "$(./darwin.run get certificate --host <CLOUD_DIRECTOR_HOST>)" \
--host <CLOUD_DIRECTOR_HOST> \
--username administrator \
--password 'secret' \
--trace \
--name prjpoc01 \
--input-justification "cleanup by developer"
# Delete the completely the Skeleton add-on and its cache
./darwin.run delete solution \
--accept \
--certificate "$(./darwin.run get certificate --host <CLOUD_DIRECTOR_HOST>)" \
--host <CLOUD_DIRECTOR_HOST> \
--username administrator \
--password 'secret' \
--trace