Workflow extensions

Since 7.0

In 7.0 This new mechanism replaces the old workflow system in 6.4. This new version allows the admin to execute custom code or ansible when other significant events occour not just pre-and post deploy.

Refer to Managing extensions for more details on how to register and publish an extension.

Supported workflow stages

The following values are accepted as events (stages) that trigger the execution of the attached tasks.

  • preDeploy - Executed in the top part of the deploy graph

  • postDeploy - Executed at the end of the deploy graph

  • serverRegistered - Executed after a server is registered

  • serverDecommissioned - Executed after a server is decommissioned

  • switchRegistered - Executed after a switch is registered

  • switchDecommissioned - Executed after a switch is decommissioned

  • serverInstanceGroupCreateDNS - Executed when DNS entries are created for servers instance groups

  • serverInstanceGroupUpdateDNS - Executed when DNS entries are updated for servers instance groups

  • serverInstanceGroupDeleteDNS - Executed when DNS entries are deleted for servers instance groups

  • serverInstanceUpdateDNS - Executed when DNS entries are created and updated for server instances

  • serverInstanceDeleteDNS - Executed when DNS entries are deleted for server instances

  • serverCreateDNS - Executed when DNS entries are created for servers’s BMCs

  • serverDeleteDNS - Executed when DNS entries are deleted for servers’s BMCs

  • switchCreateDNS - Executed when DNS entries are created for switch’s Management Interface

  • switchDeleteDNS - Executed when DNS entries are deleted for switch’s Management Interface

Context objects

Additional information is provided to the task execution with contextual information that depends on the event (stage). See below on the task types on how to access these objects.

  • For serverRegistered, serverDecommissioned, switchRegistered: The Server and Network objects are available. The user can refer to the Server and NetworkDevice objects’ parameters depending on the asset that is being changed. Refer to your environment’s [API documentation].

  • For serverInstanceGroupCreateDNS, serverInstanceGroupUpdateDNS, serverInstanceGroupDeleteDN,serverInstanceUpdateDNS, serverInstanceDeleteDNS: A server DNS record set object similar to this:

    "serverInstanceGroupDNSRecordSet": {
      "zone": {
        "zoneName": "eveng-qa02.metalcloud.io",
        "soaEmail": "admin.eveng-qa02.metalcloud.io",
        "nameServers": [
          "ns1.evenq-qa02.metalcloud.io"
        ],
        "ttl": 3600,
        "isDefault": true
      },
      "infrastructureId": 3870,
      "serverInstanceGroup": {
        "label": "instance-array-3386"
      },
      "hostname": "lambda",
      "fqdn": "lambda.eveng-qa02.metalcloud.io",
      "ips": [
        {
          "status": "allocated",
          "ip": "10.20.50.36"
        }
      ]
    }
    
  • For serverCreateDNS, serverDeleteDNS an object similar to is provided in variables.json:

      {
        "serverDNSRecordSet": {
          "zone": {
            "zoneName": "us08.metalsoft.io",
            "soaEmail": "admin.us08.metalsoft.io",
            "nameServers": ["n1.metalsoft.io"],
            "ttl": 3600,
            "isDefault": true
          },
          "serverId": 10,
          "serverSerialNumber": "serial-number",
          "managementAddress": "192.168.100.100",
          "hostname": "server-10",
          "fqdn": "server-10.us08.metalsoft.io",
          "ip": {
            "status": "allocated",
            "ip": "192.168.100.100"
          },
          "operation": "create"
        }
      }
    
  • For switchCreateDNS, switchDeleteDNS the following payload is provided:

    "switchDNSRecordSet": {
      "zone": {
        "zoneName": "us08.metalsoft.io",
        "soaEmail": "admin.us08.metalsoft.io",
        "nameServers": ["n1.metalsoft.io"],
        "ttl": 3600,
        "isDefault": true
      },
      "switchId": 10,
      "managementAddress": "192.168.100.100",
      "hostname": "switch-10",
      "fqdn": "switch-10.us08.metalsoft.io",
      "ip": {
        "status": "allocated",
        "ip": "192.168.100.100"
      },
      "operation": "create"
    }
    

Supported task types

The following are task types that can be used:

  • Ansible taskType: ExtensionTaskAnsible: This will execute an ansible playbook from a directory that can include multiple modules etc. provided as a zip available at a provided URL. We call this an Ansible Bundle. The Bundle is downloaded by the Site Controller and Ansible is invoked on the Site Controller to execute the playbook.

Note that the Ansible Runner capability and container must exist on the Site Controller.

Example:

{
  "label": "create-dns-records-for-instance-group",
  "taskType": "ExtensionTaskAnsible",
  "options": {
    "asset": "power-dns-configuration",
    "playbook": "deploy_dns_flexible.yaml"
  }
}

An asset of type Bundle with the label equal to the asset param (in the example it is power-dns-configuration) referred in the task must exist in the assets section of the file. For example:

 "assets": [
  {
    "label": "power-dns-configuration",
    "name": "power-dns-configuration",
    "assetType": "Bundle",
    "url": "https://repo.metalsoft.io/.extensions_ms/workflows/power_dns.zip"
  }
],

Note that the asset will be pulled from the URL from the Site Controller and extracted in a directory before execution.

Options

  • asset - The asset to call

  • playbook - The playbook to execute that must exist within the asset bundle.

  • executionTimeout - Timeout for the execution

  • executionTimeoutTick - How often to retry in case of an error

Variables

When the Ansible bundle is executed the following variables.yaml will be available in the directory. The content will depend on the execution stage as described above at the Context Objects section.

  • HTTP request taskType ExtensionTaskWebhook:

    Performs an HTTP request to a given endpoint with specific options.

    {
      "label": "testInitial",
      "taskType": "ExtensionTaskWebhook",
      "options": {
        "endpoint":"www.google.com",
        "method":"GET",
        "requestTemplate":"test"
      }
    }
    

    Options

    • endpoint - The endpoint to call. Must be a valid URL.

    • method - The method to use, one of GET, POST, PATCH, DELETE

    • headers - An array of headers to send such as: headers: {"Content-type": "application/json", "header2": "value2"}

    • requestTemplate - This is the payload of the request. It can be a Nunjucks (a subset of Jinja2) template. See the Context Objects for available objects according to the stage. Example: "requestTemplate": "{\"server_vendor\": {{ server.serverVendor }}, \"property2\": \"value2\"}"

    • expectedResponseStatuses - This is an array of codes such as [200,201]

  • SSH execution Execute a command via SSH.

{
       "label": "show running config",
       "taskType": "ExtensionTaskSsh",
       "options": {
         "host": "192.168.100.100",
         "port": 22,
         "timeout": 1800,
         "commandTemplate": "show running config"
       }
 }

Options

  • host - The host to ssh to

  • port - The port to ssh to

  • timeout - The timeout for executing the command

  • commandTemplate It can be a Nunjucks (a subset of Jinja2) template.See the Context Objects for available objects according to the stage.

An end-to-end example is available here:

The following example includes everything including the ansible bundles

Example extension definition

Example extension:

{
  "name": "testExtensionType",
  "label": "test",
  "description": "My App Description",
  "kind": "workflow",
  "definition": {
    "kind": "ExtensionDefinition",
    "schemaVersion": "1.1",
    "name": "string",
    "label": "string",
    "extensionType": "string",
    "vendor": "string",
    "extensionVersion": "string",
    "description": "string",
    "icon": "string",
    "dependencies": {
      "controllerVersion": "string"
      
    },
    "inputs": [
    ],
    "outputs": [
    ],
    "assets": [
    ],
    "onAssetChange": [
      {
        "stage": "server Registered",
        "tasks": [
          {
            "label": "testInitial",
            "taskType": "ExtensionTaskWebhook",
            "options": {
             "endpoint":"www.google.com",
"method":"GET",
"requestTemplate":"test"
            }
          }
        ]
      }
    ]
  }
}

More complex example

{
  "onCreate": [
    {
      "stage": "preDeploy",
      "tasks": [
        {
          "label": "string",
          "taskType": "ExtensionTaskAnsible",
          "options": {
            "asset": "string",
            "playbook": "string",
            "executionTimeout": 1,
            "executionTimeoutTick": 1,
            "version": "string"
          }
        }
      ]
    },
    {
      "stage": "postDeploy",
      "tasks": [
        {
          "label": "string",
          "taskType": "ExtensionTaskAnsible",
          "options": {
            "asset": "string",
            "playbook": "string",
            "executionTimeout": 1,
            "executionTimeoutTick": 1,
            "version": "string"
          }
        }
      ]
    }
  ],
  "onAssetChange": [
    {
      "stage": "serverRegistered",
      "tasks": [
        {
          "label": "string",
          "taskType": "ExtensionTaskAnsible",
          "options": {
            "asset": "string",
            "playbook": "string",
            "executionTimeout": 1,
            "executionTimeoutTick": 1,
            "version": "string"
          }
        }
      ]
    },
    {
      "stage": "serverDecommissioned",
      "tasks": [
        {
          "label": "string",
          "taskType": "ExtensionTaskAnsible",
          "options": {
            "asset": "string",
            "playbook": "string",
            "executionTimeout": 1,
            "executionTimeoutTick": 1,
            "version": "string"
          }
        }
      ]
    }
  ]
}