Exploring Solution Add-On Elements

Elements are the smallest deployable units of solution add-on. They are entities managed by Cloud Director or 3-rd party systems uniquely identified across their solution add-on and its instances. Once an element is realized at the target system it defines a set of properties which values could be used by the other add-on elements for their realization activities, though there is a strict rule - an element properties can be consumed only by elements appearing after the definition of the element in its manifest. Elements can have associated actions to perform pre-and-post realization activities allowing the add-on vendor to gain certain control during the element realization. Such actions can also be defined on the add-on level allowing to perform generic pre-setup or clean-up activities.

Elements and their associated configuration are defined in a solution add-on manifest.yaml file with the following sections.

Vendor

Vendor section holds details about the add-on origin and its current version. This section is visible in the Solution Add-On Management UI.

It is recommended to use simple names, lower-case with hyphens for vendor and name properties as they will be used as part of the add-on identifier and the packaged ISO name.

vendor: <add-on vendor name>
name: <add-on name>
version: <add-on version (N.N.N[-W])>
vcdVersion: <minimal supported Cloud Director version of this add-on>
friendlyName: <name under which this add-on will appear in the management UI>
description: <short description of the add-on business function visible in add-on management UI>

IMPORTANT: Every solution add-on must have a unique vendor and name. Changing either of these at some point will result in a completely new add-on. Use the add-on version property to manage the current and future add-on upgrades.

Inputs

Inputs section is defined by the add-on vendor to request user input during the add-on installation and its associated day-2 operations. Inputs are referenced by add-on elements and their associated actions in the manifest.yaml and their values are substituted at the time of add-on operation execution.

inputs:
  - name: <the input field identifier used by elements for referencing the field concrete value>
    title: <the display name of this input field, visible in add-on management UI>
    description: <the description of this input field, visible in add-on management UI>
    required: <optional, if set to 'true' will force the field to be set>
    secure: <optional, if set to 'true' will encrypt the field value. Note, the value cannot be seen even by the user who specified it.>
    type: <optional, defaults to 'String', denotes the type of this input filed>
    default: <optional, if set the value will be used when consumer does not specify a value>
    validation: <optional, regular expression validating the value for the field>
    delete: <optional, if set to 'true' will make this input field available only to the add-on instance delete operation>    
    isArray: <optional, if set to 'true' the filed can accept a list of values>
    values: <optional, the finite set of values that can be set to this variable>

Elements

Elements section contains a list of elements that compound the add-on. An element is defined via properties under the manifest.yaml descriptor. Every element regardless of its type has name, description, triggers and policies properties. The type further defines whether the element will have a spec property and what will be its properties’ schema.

Some elements require a filesystem artifacts for their realizations. These artifacts by convention are located under folder with name matching the name property of its associated element. An element artifact could be an already bundled artifact or the source code and build script that will turn into bundle artifact via vcd-ext-shell build command.

elements:
    - type: ui-plugin | user | role | rights-bundle | defined-entity | defined-entity-instance | vapp | network-policy | service
      name:

      # If missing description defaults to "name"
      description: <User friendly name>
      triggers: []
      policies:
        create: once|always|reuse

Policies reflect the element realization behavior. The create policy controls whether the element have to be explicitly created or a pre-provisioned version of it can be reused.

When policies.supportsMultipleInstances is set to false - Default element policies.create is set to once - forcefully create the element resource - If policies.create is set to reuse - an existing resource with a similar spec will be associated with the element or if missing a new resource will be created

When policies.supportsMultipleInstances is set to true - Default element policies.create is set to once - forcefully create the element resource - If policies.create is set to always - every new instance of the add-on will create a separate resource

NOTE: During the add-on instance creation operation, elements are processed in ascending order one at a time. During the add-on instance deletion operation, elements are processed in reverse order.

UI Plugin

UI Plugin type represents a Cloud Director UI plugin. This element definition requires the name property to point to a project root folder <element reference name> where the source code of the plugin will be located, or its resulted artifact <element reference name>/dist/plugin.zip in case of an external build tooling is used.

The type further requires the spec property describing the UI Plugin visibility rules.

elements:
  - type: ui-plugin
    name: <element reference name>
    spec:
      # Optional arguments below
      publish:
        provider: true | false
        # tenantsAll and tenants are exclusive
        tenantsAll: true | false
        tenants:
          - <tenant short name>
          - ...

User

User type represents a Cloud Director User entity. The element type requires the spec property, it outlines the details required for the user realization.

elements:
  - type: user
    spec:
      username: text
      # Optional arguments below
      password: text
      description: text
      roleName: text
      fullName: text
      email: text

      # Default: LOCAL
      providerType: LOCAL | SAML | OAUTH
      
      # True: user will be created in the Provider Portal
      # False: user will be created in the organization specified in the Solution Landing zone
      systemScope: true | false

Role

Role type represents a Cloud Director Role entity. The element type requires the spec property, it outlines the details required for the role realization.

elements:
  - type: role
    spec:
      name: 

      # List of rights keys or ids
      # Example id: urn:vcloud:type:vmware:demo_person
      # Examples: 
      #  - "API Tokens: Manage"
      #  - "Access All Organization VDCs"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:full_access"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:view"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:modify"
      rights:
        - <RIGHT>
        - ...

      # Optional arguments below
      description:

      # Define right as system or global
      global: true | false

      # Define global right visibility
      publish: 
        solution: true | false
        # tenantsAll and tenants are exclusive
        tenantsAll: true | false
        tenants:
          - <name>
          - ...

Right bundle

Right Bundle type represents a Cloud Director Right Bundle entity. The element type requires the spec property, it outlines the details required for the right bundle realization.

elements:
  - type: rights-bundle
    spec:
      name: 

      # List of rights keys or ids
      # Example id: urn:vcloud:type:vmware:demo_person
      # Examples: 
      #  - "API Tokens: Manage"
      #  - "Access All Organization VDCs"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:full_access"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:view"
      #  - "urn:vcloud:type:vmware:<DEFINED ENTITY TYPE>:modify"
      rights:
        - <RIGHT>
        - ...
      
      # Define right bundle visibility
      description:
      publish: 
        solution: true | false
        # tenantsAll and tenants are exclusive
        tenantsAll: true | false
        tenants:
          - <name>
          - ...

Defined Entity

Defined Entity type represents a Cloud Director Runtime Defined Entity interfaces, types and behaviors. This element definition requires the name property to point to a project root folder <element reference name> where the source code of the Defined Entity definitions will be located, or their resulted artifacts <element reference name>/dist/types/*.json and <element reference name>/dist/interfaces/*.json in case of an external build tooling is used.

elements:
  - type: defined-entity
  - name: <element reference name>

Defined Entity Instance

Defined Entity Instance type represents a Cloud Director instance of a Defined Entity Type. The element type requires the spec property, it outlines the details required for the instance realization.

elements:
  - type: defined-entity-instance
    spec:
      vendor: <vendor defined by defined entity type>
      nss: <namespace defined by defined entity type>
      version: <version defined by defined entity type>
      entity: <entity body defined by defined entity type>
      
      # Optional arguments below
      name: <element reference name>

      # Defines what role should be able to manage this entity instance
      accessControlRole: <cloud director role name>

      owner:
        username: <username>
        password: <password>
        
        # Optional arguments below
        # The system scope has to be set to the one the user belongs to
        systemScope: true | false

Vapp

Vapp type represents a Cloud Director virtual appliance. The element type requires the spec property, it outlines the details required for the vApp realization.

elements:
  - type: vapp
    spec:
      # Optional arguments below

      # The name of the name pattern under which the vApp will appear in Cloud Director after addon installation.
      name: <appliance name>

      # Applicable when the vApp descriptor contains OVF properties
      ovfProperties:
        - key:
          value: number | string | boolean
        - ...

      # Further, customizes the vApp virtual machine before its power on operation invocation
      guestCustomization:
        changeSid: boolean
        joinDomainEnabled: boolean
        useOrgSettings: boolean
        domainName: string
        domainUserName: string
        domainUserPassword: string
        machineObjectOU: string
        adminPasswordEnabled: boolean
        adminPasswordAuto: boolean
        adminPassword: string
        adminAutoLogonEnabled: boolean
        adminAutoLogonCount: number
        resetPasswordRequired: boolean
        customizationScript: string
        computerName: string
      
      # Connects the vApp virtual machine to a network defined by the Solution Landing Zone matching the defined assignment and capabilities.
      networks:
        - assignment: auto | static | dynamic
          primary: true | false
          disconnected: true | false
          capabilities:
            - string
            - ...
       
      # Use storage policy defined by the Solution Landing Zone matching the defined capabilities.
      storage:
        capabilities:
          - string
          - ...
      
      # Further, customize the vApp virtual machine hardware before its power on operation invocation
      hardware:
        numberOfCpus: number
        coresPerSocket: number
        # Memory size in MB
        memorySize: number

      # Terminate the vApp element installation when the timeout has reached.
      timeoutMinutes: number

      # Element installation is considered as successful when all conditions are met in the specified by the timeout duration.
      readyCondition:
        # The vApp virtual machine received an IP
        "ip": 
        
        # The vApp virtual machine ExtraConfig contains a key
        "<key>":

        # The vApp virtual machine ExtraConfig contains a key with value
        "<key>": "<value>"

Network Policy

Network Policy type represents a Network Manager policy that defines a firewall rule. The element type requires the spec property, it outlines the details required for the policy realization.

elements:
  - type: network-policy
    spec:
      type: vcenter | esxi | nsx | compute
      # Optional arguments below
      name: <element reference name>
      sources:
        - <IP>
        - ...
      destinations:
        - <IP>
        - ...
      
      # Option: Named service
      services:
        type: HTTP| HTTPS | SSO | SSH | ICMP-ALL
      
      # Option: Custom service
      services:
        protocol: TCP | UDP
        # Optional
        # Port must be in range 1-65535
        sourcePort: number
        targetPort: number

Examples

Create an outbound firewall rule from VM1 to VM2 on port 443

- name: my-policy
  type: network-policy
  spec:
    type: compute
    vdc: my-ovdc
    sources: 
      - 192.168.1.1
    destinations: 
      - 192.168.1.2
    services:
      - targetPort: 443
        protocol: TCP

Create an inbound firewall rule to VM on port 8443

- name: my-policy
  type: network-policy
  spec:
    type: compute
    vdc: my-ovdc
    destinations: 
      - 192.168.1.1
    services:
      - targetPort: 8443
        protocol: TCP

Network Service

Network Service type represents a Network Manager service that defines a way to expose internal IP addresses to the outside world using public IPs. The element type requires the spec property, it outlines the details required for the service realization.

elements:
  - type: service
    spec:
      # Option: Named binding
      bindings:
        type: HTTP| HTTPS | SSO | SSH | ICMP-ALL

      # Option: Custom binding
      bindings:
        protocol: TCP | UDP
        port: number
        # Optional
        targetPort: number

      # Optional arguments below
      name: text
      privateIp: text
      firewallStrategy: MATCH_INTERNAL_ADDRESS | MATCH_EXTERNAL_ADDRESS

Examples

Expose a private IP through a single public IP on port 22 and port 443->8443.

- name: my-service
  type: service
  spec:
    privateIp: 192.168.1.1
    vdc: my-vdc
    bindings: 
      - port: 22
        protocol: TCP
      - port: 443
        targetPort: 8443
        protocol: TCP

Expose a private IP through a single public IP on port 8443->HTTPS(443)

- name: my-service
  type: service
  spec:
    privateIp: 192.168.1.1
    vdc: my-ovdc
    bindings: 
      - port: 8443
        type: HTTPS

Policy

Policy section is defined by the add-on vendor to state whether this add-on can install only one or many instance. This section is optional.

NOTE: The add-on management will treat the add-on as a single instance in case the section is missing from the manifest or supportsMultipleInstances property is set to false.

policies:
  supportsMultipleInstances: true

Trigger

Trigger section defines actions to be run as part of the add-on management lifecycle.

triggers:
  - event: <event type>
    action: <path under ISO>/<action handler executable>
    timeout: <timeout in seconds>
  - ...

The triggers can be implemented on any language as long as their build provides binaries for the three major operating systems for development.

<add-on project>/dist/windows.exe
<add-on project>/dist/linux
<add-on project>/dist/darwin

Triggers can be defined globally on the add-on object and locally on a particular element. This is a list of the supported events on which a trigger could be attached. Event Types - PreCreate (add-on) - PostCreate (add-on) - PreDelete (add-on) - PostDelete (add-on) - PreScope (add-on change scope day-1 and day-2 operations) - PostScope (add-on change scope day-1 and day-2 operations) - PreOperation (add-on day-2 operation) - PostOperation (add-on day-2 operation) - OnError (add-on on any failure regardless if the operation is day-1 or day-2)

Solution add-on operation executor invokes a trigger as a separate OS process and provides its execution context properties as JSON string into the standard output. If the trigger wants to output a property which can be referenced by other triggers or elements it has to be printed into the standard output following the log line format output:{"name": "<key>", "value": "value", "secure: true|false}.

The vendor have the option to create a separate binary for every trigger definition in the manifest.yaml or use one trigger with internal switch based on the execution context, or mixture of both. This is an example of a multipurpose trigger with internal switch implemented on GoLang.

// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0
package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
)

type InputProperties struct {
	Element    string         `json:"element"`
	Event      string         `json:"event"`
	Properties map[string]any `json:"properties"`
}

type OutputProperty struct {
	Name   string `json:"name"`
	Value  any    `json:"value"`
	Secure bool   `json:"secure"`
}

type OutputProperties []OutputProperty

func readPropertiesFromStandardInput() InputProperties {
	scanner := bufio.NewScanner(os.Stdin)
	if !scanner.Scan() {
		exitIfErrorExists(errors.New("no standard input"), "error reading from standard input")
	}
	inputJson := scanner.Text()

	// DEVELOPMENT ONLY! Print standard input for examination.
	// Note all secrets will be visible in the standard output log.
	fmt.Println(inputJson)

	input := InputProperties{}
	err := json.Unmarshal([]byte(inputJson), &input)
	exitIfErrorExists(err, "error reading JSON from standard input")
	return input
}

func (o OutputProperty) writePropertyIntoStandardOutput() error {
	if variableJson, err := json.Marshal(o); err != nil {
		return err
	} else {
		_, err = fmt.Println(fmt.Sprintf("output:%s", string(variableJson)))
		return err
	}
}

func (properties OutputProperties) writePropertiesIntoStandardOutput() {
	for _, property := range properties {
		if err := property.writePropertyIntoStandardOutput(); err != nil {
			fmt.Errorf("failed serializing the output for variable %s:%v", property.Name, err)
			os.Exit(1)
		}
	}
}

func exitIfErrorExists(err error, message string) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v", message, err)
		os.Exit(1)
	}
}

const eventPreCreate = "PreCreate"
const eventPostCreate = "PostCreate"
const eventPostDelete = "PostDelete"
const elementNone = ""
const elementCloudDirectorUser = "cloud-director-user"

// This is the body of the multipurpose action handler. It is going to be called multiple times with for various
// places where it is referenced by the manifest.yaml#triggers and manifest.yaml#element#triggers.
//
// Use multipurpose action pattern for convenience or source code size reduction and 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")
}

Variables

Solution add-on manifest.yaml can benefit from the data-driven templates for generating textual output. It is fully compliant with GoLang Data-Driven Templates.

Inputs and elements properties can be referenced by other elements via the data-driven templates syntax. This way a user input or a property from an already realized element can be used by an element realization handler.

Examples

Use user input property into element

inputs:
  - name: vapp-prefix
    title: Backend vApp Prefix
    required: true
    description: The backend vApps will appear with the name prefix for all instances of this add-on.

elements:
  - name: my-backend
    type: vapp
    spec:
      name: '{{ property `vapp-prefix` }}-{{ random `type:number` `min:10000` `max:99999` }}'
      networks:
        - assignment: auto
          primary: true
          capabilities: []
      readyCondition:
          "ip":
          "guestinfo.solution.key.public":
      timeoutMinutes: 50

Use realized element property into another element

  - name: "my-role"
    type: "role"
    spec:
      name: "My Role"
      description: "Used for administrative purposes"
      global: false
      systemScope: true
      rights:
        - "urn:vcloud:type:vmware:<RDE Type>:full_access"
        - "urn:vcloud:type:vmware:<RDE Type>:view"
        - "urn:vcloud:type:vmware:<RDE Type>:modify"

  - name: "my-account"
    type: "user"
    spec: 
      username: "myaccount"
      fullName: "My Service Account"
      email: "[email protected]"
      description: "My account to perform operations in vCD."
      roleName: "{{ property `my-role.name` }}"
      systemScope: true

What is Next?