NAV Navbar
Logo
Switch version:

Elastic Agents

Current Version

This document is for version 3.0 (the latest) of the elastic agent endpoint.

Background

In the current GoCD setup, all agents are fully provisioned and always available waiting for a job. When there are no jobs to be executed in that environment or for that specific resource that the agents have, the agents stay in an idle state waiting for jobs to be assigned to it, which is a waste of infrastructure resources. It could also lead to high costs if agents are running on cloud providers.

Elastic agents are on-demand agents which are created and provisioned by an elastic-agent plugin when there are jobs to be executed, and terminated when the agents are running idle. These agents can be in a data center or in the cloud or both, and may be physical or virtual.

Developers can start building their own elastic-agent plugins by forking the skeleton plugin and looking at a sample docker plugin as an example reference implementation.

How will it help you?

A feature like this can allow for more efficient use of agent machines, can allow flexible scaling and in many cases, can reduce the cost of running agents. Imagine an automated performance test which runs occasionally and needs a lot of machines. These machines can be started at the beginning of the performance test, possibly using some cloud service, and then brought down when not needed. This feature should enable a more flexible and dynamic build grid.

Current process of handling agents

Agents are currently started manually or automatically by scripts written by users. They are registered to the GoCD server manually, on the agents page or automatically, using auto-registration.

Idle agents, after registration with the server, continuously poll the server, asking for jobs to run. When a stage is triggered, the jobs that need to be started are identified by the server. Suitable jobs are assigned to agents (depending on resources and environments). The agents pick up the jobs and run them. Once finished, they start polling the server for the next job to run.

  1. When a job is scheduled, it is considered for assignment to an agent, and it waits in the queue for an idle agent to ping the server.
  2. When an idle agent pings the server for work, assuming that resources and environments match, the agent is assigned the scheduled and waiting job.
  3. Once the agent finishes running that job, it goes back into the idle agent pool, waiting for another, suitable job to be scheduled and assigned to it.

See the image below to help make this a little more clear.

Process of handling elastic agents

Here is an example elastic profile configuration and a Job configured to use the profile for the docker plugin

<server>
...
</server>
<elastic>
  <profiles>
    <profile  id="docker.unit-test" pluginId="cd.go.contrib.elastic-agent.docker">
      <property>
        <!--
          The plugin currently only supports the `Image` property,
          which allows you to select the docker image that the build should run with
        -->
        <key>Image</key>
        <value>gocdcontrib/ubuntu-docker-elastic-agent</value>
      </property>
    </profile>
  </profiles>
</elastic>
...
<job name="defaultJob" elasticProfileId="docker.unit-test">
  <tasks>
    <exec command="mvn" args="clean test" />
  </tasks>
  <!-- Specify the id of the plugin that should manage elastic agents for this job -->
</job>

User creates an elastic agent profile specific to a plugin, a profile represents the configuration to be used to create an elastic-agent instance. Depending on the type of the plugin and the capabilities it provides, a profile configuration could contain the AMI ID, or docker image name, size of the machine, volumes to be mounted etc.

The plugin decides whether to assign an agent to a job, or to start another agent, when consulted by Go.

  1. When a job is scheduled, if its job configuration contains a elasticProfileId, the job is not considered for normal assignment as described in the previous section.
  2. The plugin corresponding for that job elasticProfileId is contacted (to create an agent), along with the corresponding profile configuration for which this assignment needs to be made.
  3. At this point the plugin may, at its discretion, create an agent or not.
    1. If it sees that there are no idle agents that satisfy the profile, it may choose to spin a new agent.
    2. If it sees an existing idle agent that satisfies the profile, the plugin may choose to not spin a new agent.
    3. If there is no more capacity available to create new agents, the plugin may choose to not spin a new agent. (This is same as point above).
  4. In case the job is not assigned after a specified interval, the server waits some time, and then comes back to the plugin, asking it to choose, again.
  5. When an elastic-agent pings the server for work, and if there is a job that requires an elastic-agent, then the corresponding plugin for that job is contacted (to check if the server should assign work to the agent), along with the profile configuration for which this assignment needs to be made.
Lax matching of agents to jobs
Job Configuration Agent Configuration Should assign work?
<profile id="aws.small" pluginId="aws">
  <property>
    <key>instance-type</key>
    <value>m1.small</value>
  </property>
</profile>
<job name="unit-tests"
     elasticProfileId="aws.small">
</job>
<profile id="aws.large" pluginId="aws">
  <property>
    <key>instance-type</key>
    <value>m1.xlarge</value>
  </property>
</profile>

<!-- Agent for profile "aws.large" -->
<agent elasticPluginId="aws"/>

Yes

Since the job requires a smaller agent, and a larger agent is available.
<profile id="aws.small" pluginId="aws">
  <property>
    <key>instance-type</key>
    <value>m1.small</value>
  </property>
</profile>
...
<job name="unit-tests"
     elasticProfileId="aws.small">
</job>
<profile id="aws.small-us-east" pluginId="aws">
  <property>
    <key>aws-region</key>
    <value>us-east-1</value>
  </property>
  <property>
    <key>instance-type</key>
    <value>m1.small</value>
  </property>
</profile>
<!-- Agent for profile "aws.small-us-east" -->
<agent elasticPluginId="aws"/>

Yes

Since the job does not care which region it runs in.

See this image to help make this a little more clear.

Getting started

Plugins in GoCD are implemented in Java and packaged as a JAR file.

Structure of a GoCD Plugin

A plugin for GoCD is a JAR file with the following structure:

plugin.jar
|
|-- plugin.xml
|-- com
|   \-- example
|       \-- go
|           \-- testplugin
|               \-- DockerElasticAgentPlugin.class
|               \-- x.class
|               \-- y.class
\-- lib
    \-- dependency-1.jar
    \-- dependency-2.jar

The plugin jar is a self contained-jar. It is expected to contain the following inside it:

The plugin metadata plugin.xml

Here is an example plugin.xml:

<?xml version="1.0" encoding="utf-8" ?>
<!-- Your plugin id and version
     of this XML document, the
     version must be set to "1" -->
<go-plugin
  id="com.example.rocket.launcher"
  version="1">
  <about>
    <!-- The name of your plugin -->
    <name>Launches rockets</name>
    <!--  The version of your plugin -->
    <version>0.0.1</version>
    <!-- The minimum version of GoCD that this plugin requires -->
    <target-go-version>16.8.0</target-go-version>
    <!-- A longer description of your plugin  -->
    <description>Launches rockets, spaceships and other things to a destination of your choice.</description>

    <!-- Tell us who you are -->
    <vendor>
      <name>ACME Corp</name>
      <url>https://www.example.com</url>
    </vendor>

    <!-- If this plugin only supports certain OSes -->
    <target-os>
      <value>Linux</value>
      <value>Windows</value>
    </target-os>
  </about>
</go-plugin>

This is an XML file that should be present in the plugin jar file at the top level.

You can find the XML Schema for the plugins in the main repository.

The plugin extension class

Add this to your maven pom.xml:

<dependency>
  <groupId>cd.go.plugin</groupId>
  <artifactId>go-plugin-api</artifactId>
  <version>18.6.0</version>
</dependency>

In order to use the plugin extension class, you must add the following to your maven dependencies. If you’re using gradle, then use cd.go.plugin:go-plugin-api:18.6.0. You can find the latest version of the plugin in maven central.

An example plugin class implementation:

package com.example.go.testplugin;

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.*;
import com.thoughtworks.go.plugin.api.exceptions.*;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerElasticAgentPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;

  // this method is executed once at startup
  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  // a GoPluginIdentifier tells GoCD what kind of a plugin this is
  // and what version(s) of the request/response API it supports
  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"))
  }

  // handle the request and return a response
  // the response is very much like a HTTP response —
  // it has a status code, a response body and optional headers
  public GoPluginApiResponse handle(GoPluginApiRequest request) throws UnhandledRequestTypeException {
    if (request.requestName().equals("go.plugin-settings.get-view")) {
      String viewHtml = read(getClass().getResourceAsStream("/plugin-settings.template.html"));
      return new DefaultGoPluginApiResponse(200, viewHtml);
    if (request.requestName().equals("go.plugin-settings.validate-configuration")) {
      List errors = validate(request);
      return new DefaultGoPluginApiResponse(200, new Gson().toJson(errors));
    } else {
      throw new UnhandledRequestTypeException(request.requestName());
    }
  }
}

The plugin extension class is a Java class that implements the GoPlugin interface.

The other types in the example are:

Type Description
GoApplicationAccessor So that the plugin can make requests to the GoCD application to get additional information that is not provided by each request. For example, the plugin can ask for settings, credentials etc.
GoPluginIdentifier Provides information about the type of plugin and the version of the request/response it supports.
GoPluginApiRequest Represents the request message sent from GoCD to a plugin. The message will have a name and an optional JSON request body and the version of the extension.
GoPluginApiResponse Represents the response message as a result of processing the GoPluginApiRequest. Similar to GoPluginApiRequest, the response will have a status code and an optional JSON response body.

If you’re familiar with http request/responses handled by a web application, you will find this very familiar.

The plugin dependencies

Any dependencies that your plugin requires, should go into the lib directory inside the plugin JAR.

Requests from the GoCD server

In order to implement an elastic agent extension point the following messages must be implemented by the plugin.

These are general purpose messages that a plugin must implement to allow users to configure the plugin through the browser.

These are messages that a plugin must implement in order to allow users to configure elastic profiles through the browser.

If a plugin supports supports status reports, apart from the above, the plugin must implement the following messages. The plugin should use the supports_status_report capability to expose this feature.

Get Plugin Capabilities

This message is a request to the plugin to provide plugin capabilities. Based on these capabilities GoCD would enable or disable the plugin features for a user.

Request name

cd.go.elastic-agent.get-capabilities

Request body

Server sends request with Empty request body.

Response Body

An example response body:

{
  "supports_status_report": true,
  "supports_agent_status_report": true
}

The response body will contain the following JSON elements:

Key Type Description
supports_status_report String Whether plugin supports Plugin status report depends on this boolean value
supports_agent_status_report String Whether plugin supports Agent status report depends on this boolean value

The plugin is expected to return status 200 if it can understand the request.

Create Agent

This message is a request to the plugin to create an agent for a job that has been scheduled.

Request name

cd.go.elastic-agent.create-agent

Request body

Given the following config XML snippet —

<server agentAutoRegisterKey="1e0e05fc-eb45-11e5-bc83-93882adfccf6" />
<elastic>
  <profiles>
    <profile id="gocd-dev-build" pluginId="plugin-id">
      <property>
        <key>Image</key>
        <value>gocd/gocd-agent-alpine-3.5:v18.1.0</value>
      </property>
      <property>
        <key>MaxMemory</key>
        <value>500Mb</value>
      </property>
    </profile>
  </profiles>
</elastic>
<pipelines group="first">
  <pipeline name="build">
    ...
    <job name="test-job" elasticProfileId="gocd-dev-build">
      ...
    </job>
    ...
  </pipeline>
</pipelines>

The plugin will receive the following JSON body —

{
  "auto_register_key": "1e0e05fc-eb45-11e5-bc83-93882adfccf6",
  "environment": "prod",
  "job_identifier": {
    "job_id": 100,
    "job_name": "test-job",
    "pipeline_counter": 1,
    "pipeline_label": "build",
    "pipeline_name": "build",
    "stage_counter": "1",
    "stage_name": "test-stage"
  },
  "properties": {
    "Image": "gocd/gocd-agent-alpine-3.5:v18.1.0",
    "MaxMemory": "500Mb"
  }
}

The request body will contain the following JSON elements:

Key Type Description
auto_register_key String The key that an agent should use, if it should be auto-registered with the server. The plugin is expected to use the key to create an appropriate autoregister.properties file on the agent instance, before it starts the agent process. See the auto-register documentation for more information.
environment String The environment that this job belongs to. Agents are expected to auto-register using this environment so that they can be assigned to the correct job. See the environments section to know more about environments.
properties Object Jobs that require elastic agents, will have an <agentConfig/> element on it. This object represents the key value pairs that form this configuration.
job_identifier Object Job identifier of the job for which this call is being made.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

Can be left blank, the server does not parse any response body returned.

Should Assign Work

When there are multiple agents available to run a job, the server will ask the plugin if jobs should be assigned to a particular agent. The request will contain information about the agent, the job configuration and the environment that the agent belongs to. This allows plugin to decide if proposed agent is suitable to schedule a job on it. For example, plugin can check if flavor or region of VM is suitable.

Request name

cd.go.elastic-agent.should-assign-work

Request body

Given the following config XML snippet —

<server agentAutoRegisterKey="1e0e05fc-eb45-11e5-bc83-93882adfccf6" />
<profile id="ec2.small-us-east" pluginId="com.example.ec2">
  <property>
    <key>ami-id</key>
    <value>ami-6ac7408f</value>
  </property>
  <property>
    <key>region</key>
    <value>us-east-1</value>
  </property>
</profile>
<pipelines>
  <pipeline name='build'>
    <job name="run-upgrade" runOnAllAgents="true" timeout='30' elasticProfileId="ec2.small-us-east">
      <tasks>
        <ant target="upgrade" />
      </tasks>
    </job>
  </pipeline>
</pipelines>
<environments>
  <environment name="staging">
    <pipelines>
      <pipeline name="build" />
    </pipelines>
  </environment>
</environment>
<agents>
  <agent elasticAgentId='i-283432d4' elasticPluginId='com.example.go.testplugin' />
</agents>

The plugin will receive the following JSON body —

{
  "agent": {
    "agent_id": "i-283432d4",
    "agent_state": "Idle",
    "build_state": "Idle",
    "config_state": "Enabled"
  },
  "environment": "staging",
  "job_identifier": {
    "job_id": 100,
    "job_name": "run-upgrade",
    "pipeline_counter": 1,
    "pipeline_label": "build",
    "pipeline_name": "build",
    "stage_counter": "1",
    "stage_name": "test-stage"
  },
  "properties": {
    "ami-id": "ami-6ac7408f",
    "region": "us-east-1"
  }
}

The request body will contain the following JSON elements:

Key Type Description
environment String The environment that this job belongs to. See the environments section to know more about environments.
agent Object An object describing the elastic agent.
properties Object Jobs that require elastic agents, will have an elasticPluginId attribute on it, which refers to elastic <profile/> element. This object represents the key value pairs from the <profile/> element.
job_identifier Object Job identifier of the job for which this call is being made.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

The server must return a JSON string response with a boolean — true or false to indicate whether the agent should be assigned work.

Server Ping

Each elastic agent plugin will receive a periodic signal at regular intervals for it to perform any cleanup operations. Plugins may use this message to disable and/or terminate agents at their discretion.

Request name

cd.go.elastic-agent.server-ping

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

Can be left blank, the server does not parse any response body returned.

Get Settings View

This is an optional message that the plugin may implement, should users want to configure the plugin from the GoCD admin page.

Request name

go.plugin-settings.get-view

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

A JSON settings view object

An example response body:

{
  "template": "<div>some html</div>"
}

Get Plugin Configuration

This is an optional message that the plugin may implement, should users want to configure the plugin from the GoCD admin page. This message allows the server to query a plugin about what properties are supported by this plugin.

Request name

go.plugin-settings.get-configuration

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

A JSON plugin settings configuration object.

Validate Plugin Configuration

If a plugin requires any configuration, this message must be implemented in order to validate the configuration.

Request name

go.plugin-settings.validate-configuration

Request body

An example validation request body

{
  "plugin-settings": {
      "server_url": {
        "value": "http://localhost.com"
      },
      "username": {
        "value": "user"
      },
      "password": {
        "value": "password"
      }
  }
}

The request body will contain a JSON with an attribute plugin-settings, which contains an object with the configuration keys and values that the plugin is expected to validate.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

The plugin should respond with JSON array response for each configuration key that has a validation error

[
  {
   "key": "server_url",
   "message": "Server URL cannot be localhost"
  }
]

If any of the input keys have a validation error on them, the plugin is expected to return a list of validation error objects. If the configuration is valid, the plugin should return an empty JSON array.

Get Plugin Icon

This call is expected to return the icon for the plugin, so as to make it easy for users to identify the plugin.

Request name

cd.go.elastic-agent.get-icon

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

An example plugin response body:

{
  "content_type": "image/svg+xml",
  "data": "PHN2ZyB2ZXJzaW9u..."
}

The plugin is expected to return an image object.

Validate Profile

This call is expected to validate the user inputs that form a part of the elastic agent profile.

Request name

cd.go.elastic-agent.validate-profile

Request body

The request body will contain a JSON object with the keys and values that form part of the profile.

An example validation request body for the docker elastic agent plugin

{
  "Image": "alpine:latest",
  "MaxMemory": "foo"
}

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

The plugin should respond with JSON array response for each configuration key that has a validation error

[
  {
   "key": "MaxMemory",
   "message": "'foo' is not a valid value for `MaxMemory`."
  }
]

If any of the input keys have a validation error on them, the plugin is expected to return a list of validation error objects. If the profile is valid, the plugin should return an empty JSON array.

Get Profile View

This is a message that the plugin should, to allow users to configure profiles from the Elastic Profiles View in GoCD.

Request name

cd.go.elastic-agent.get-profile-view

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

A JSON settings view object

An example response body:

{
  "template": "<div>some html</div>"
}

Get Profile Metadata

This is a message that the plugin should, to allow users to configure profiles from the Elastic Profiles View in GoCD.

Request name

cd.go.elastic-agent.get-profile-metadata

Request body

The server will not provide a request body.

Response code

The plugin is expected to return status 200 if it can understand the request.

Response Body

An example response body for a docker plugin

[
  {
    "key": "Image",
    "metadata": {
      "required": true,
      "secure": false
    }
  }
]

A JSON profile metadata object.

Get agent status report

If plugin supports status report, this message must be implemented to report the status of a particular elastic agent brought up by the plugin to run a job. The purpose of this call is to provide specific information about the current state of the elastic agent.

Request name

cd.go.elastic-agent.agent-status-report

Request body

An example request body for a docker plugin

{
  "job_identifier": {
    "pipeline_name": "test-pipeline",
    "pipeline_label": "Test Pipeline",
    "pipeline_counter": 1,
    "stage_name": "test-stage",
    "stage_counter": "1",
    "job_name": "test-job",
    "job_id": 100
  },
  "elastic_agent_id": "46d7aa499c9f44958e118ffb7f975c52"
}

The request body will contain the following JSON elements:

Key Type Description
job_identifier Object This key contains the job identifier information for which user has requested status report.
elastic_agent_id String This key contains the elastic agent id, this is available only when agent is registered with GoCD server.

Response code

The plugin is expected to return status 200 JSON object of view and in following format.

Response Body

An example response body:

{
  "view": "status-report-html-view"
}

The request body will contain the following JSON elements:

Key Type Description
view String Agent status report view html. GoCD server will render this view as a agent status report.

Get plugin status report

If plugin supports status report, this message must be implemented to provide the overall status of the environment.

Request name

cd.go.elastic-agent.status-report

Request body

Server sends request with Empty request body.

Response code

The plugin is expected to return status 200 JSON object of view and in following format.

Response Body

An example response body:

{
  "view": "status-report-html-view"
}

The request body will contain the following JSON elements:

Key Type Description
view String Plugin should return html view containing information about elastic agents.

Requests to the GoCD server

The plugin may make the following requests to the server using GoApplicationAccessor#submit(GoApiRequest)

List Agents

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.Extension;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;
  public static final Logger LOG = Logger.getLoggerFor(DockerPlugin.class);

  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"))
  }

  private List listAgents() {
    // create a request
    DefaultGoApiRequest request = new DefaultGoApiRequest(
      "go.processor.elastic-agents.list-agents",
      "1.0",
      pluginIdentifier()
    );

    // submit the request
    GoApiResponse response = accessor.submit(request);

    // check status
    if (response.responseCode() != 200) {
      LOG.error("The server sent an unexpected status code " + response.responseCode() + " with the response body " + response.responseBody());
    }

    // parse the response, using a json parser of your choice
    List agents = new Gson().fromJson(response.responseBody(), ArrayList.class);
    return agents;
  }
}

This messages allows a plugin to query the server for a list of elastic agents that belong to a particular plugin.

Request name

go.processor.elastic-agents.list-agents

Request body

Can be left blank, the server does not parse the request body.

Response code

The server is expected to return status 200 if it could process the request.

Response Body

The server will send a list of agents.

Disable Agents

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.Extension;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;
  public static final Logger LOG = Logger.getLoggerFor(DockerPlugin.class);

  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"))
  }

  private disableAgents(List agents) {
    // create a request
    DefaultGoApiRequest request = new DefaultGoApiRequest(
      "go.processor.elastic-agents.disable-agents",
      "1.0",
      pluginIdentifier()
    );

    // set the request body
    request.setRequestBody(new Gson().toJson(agents));

    // submit the request
    GoApiResponse response = accessor.submit(request);

    // check status
    if (response.responseCode() != 200) {
      LOG.error("The server sent an unexpected status code " + response.responseCode() + " with the response body " + response.responseBody());
    }
  }
}

Before a plugin terminates an agent instance, it must first disable it in order to prevent jobs from being assigned to it. This call is the equivalent of setting isDisabled on an <agent/> element in the config XML. More information about the agent config here, can be found in the configuration reference.

Agents in any state can be disabled, this will prevent jobs from being assigned to it. Any jobs the agent is running will eventually complete.

Request name

go.processor.elastic-agents.disable-agents

Request body

The body must contain a list of agents.

Response code

The server is expected to return status 200 if it could process the request.

Response Body

The server will not send a response body.

Delete Agents

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.Extension;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;
  public static final Logger LOG = Logger.getLoggerFor(DockerPlugin.class);

  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"))
  }

  private deleteAgents(List agents) {
    // first terminate the instance from AWS, or a cloud provider of your choice
    aws.terminateInstances(agents);
    // ensure that they are really terminated,
    // to prevent stray agents from re-registering
    aws.waitForTermination(agents);

    // create a request
    DefaultGoApiRequest request = new DefaultGoApiRequest(
      "go.processor.elastic-agents.disable-agents",
      "1.0",
      pluginIdentifier()
    );

    // set the request body
    request.setRequestBody(new Gson().toJson(agents));

    // submit the request
    GoApiResponse response = accessor.submit(request);

    // check status
    if (response.responseCode() != 200) {
      LOG.error("The server sent an unexpected status code " + response.responseCode() + " with the response body " + response.responseBody());
    }
  }
}

Before the delete-agent message is sent to the server, the plugin MUST ensure that the agent is terminated. This call is the equivalent of removing the <agent/> element in the config XML. More information about the agent config here, can be found in the configuration reference.

Request name

go.processor.elastic-agents.delete-agents

Request body

The body must contain a list of agents that should be removed from the config XML.

Response code

The server is expected to return status 200 if it could process the request.

Response Body

The server will not send a response body.

Get Plugin Settings

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.Extension;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerElasticAgentPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;
  public static final Logger LOG = Logger.getLoggerFor(DockerElasticAgentPlugin.class);

  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"))
  }

  private PluginSettings getSettings() {
    Gson gson = new Gson();
    // create a request
    DefaultGoApiRequest request = new DefaultGoApiRequest(
      "go.processor.plugin-settings.get",
      "1.0",
      pluginIdentifier()
    );

    // set the request body
    Map<String, String> map = new HashMap<>();
    map.put("plugin-id", "com.example.rocket.launcher");
    request.setRequestBody(gson.toJson(map));

    // submit the request
    GoApiResponse response = accessor.submit(request);

    // check status
    if (response.responseCode() != 200) {
      LOG.error("The server sent an unexpected status code " + response.responseCode() + " with the response body " + response.responseBody());
    }

    // parse the response, using a json parser of your choice
    return gson.fromJson(response.responseBody(), PluginSettings.class);
  }
}

This messages allows a plugin to query the server to get the user configured settings for this plugin.

Request name

go.processor.plugin-settings.get

Request body

An example request body:

{
  "plugin-id": "sample-plugin-id"
}

Must be a JSON object with a key plugin-id with the value being the ID of your plugin.

Response code

The server is expected to return status 200 if it could process the request.

Response Body

An example response body:

{
  "server_url": "https://build.go.cd",
  "username": "view",
  "password": "password"
}

The server will send a map of settings.

Get Server Info

This messages allows a plugin to query the server to get some metadata about the server.

Available since v17.9.0.

Request name

go.processor.server-info.get

Request version

The request version must be set to 1.0.

Request body

The plugin should not provide a request body.

Response code

The server is expected to return status 200 if it could process the request.

Response Body

The plugin will provide a server info object.

An example response body:

{
  "server_id": "df0cb9be-2696-4689-8d46-1ef3c4e4447c",
  "site_url": "http://example.com:8153/go",
  "secure_site_url": "https://example.com:8154/go"
}

Add Server Health Messages

import com.thoughtworks.go.plugin.api.*;
import com.thoughtworks.go.plugin.api.annotation.Extension;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.*;
import com.thoughtworks.go.plugin.api.response.*;
import com.google.gson.Gson;
import java.util.*;

@Extension
public class DockerElasticAgentPlugin implements GoPlugin {
  private GoApplicationAccessor accessor;
  public static final Logger LOG = Logger.getLoggerFor(DockerElasticAgentPlugin.class);

  public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) {
    this.accessor = accessor;
  }

  public GoPluginIdentifier pluginIdentifier() {
    return new GoPluginIdentifier("elastic-agent", Arrays.asList("1.0"));
  }

  private void addErrorsAndWarnings() {
    Gson gson = new Gson();
    // create a request
    DefaultGoApiRequest request = new DefaultGoApiRequest(
      "go.processor.server-health.add-messages",
      "1.0",
      pluginIdentifier()
    );

    // set the request body
    List<Map<String, String>> messages = new ArrayList<>();

    Map<String, String> message1 = new HashMap<>();
    message1.put("type", "warning");
    message1.put("message", "A warning message from the plugin.");

    Map<String, String> message2 = new HashMap<>();
    message2.put("type", "error");
    message2.put("message", "An error message from the plugin.");

    messages.add(message1);
    messages.add(message2);

    request.setRequestBody(gson.toJson(messages));

    // submit the request
    GoApiResponse response = accessor.submit(request);

    // check status
    if (response.responseCode() != 200) {
      LOG.error("The server sent an unexpected status code " + response.responseCode() + " with the response body " + response.responseBody());
    }
  }
}

This message allows a plugin to add error and warning messages to be shown in GoCD. Any previous messages sent by the plugin will be cleared and replaced with the newly specified messages (or cleared if the body is an empty list).

Available since v18.3.0.

Request

Name: go.processor.server-health.add-messages

Version: 1.0

Request body

An example request body:

[
  {
    "type": "warning",
    "message": "A warning message from the plugin."
  },
  {
    "type": "error",
    "message": "An error message from the plugin."
  }
]

Must be a JSON array made up of JSON objects as described below:

Key Type Description
type String Should be either warning or error, corresponding to the type of message to be shown.
message String A message to be shown in the “Errors and Warnings” box.

Response code

The server is expected to return status 200 if it could process the request. It is expected to return status 500 if it failed to process the request.

Response Body

An example response body for a failure:

{
  "message": "An error occurred ..."
}

The server will respond with a single JSON object with an error message with the key message, if it is unable to process the request. If successful, the response body will be empty.

Request/Response JSON Objects

The Elastic Agent Object

Here’s an example of the elastic agent object:

{
  "agent_id": "i-283432d4",
  "agent_state": "Idle",
  "build_state": "Idle",
  "config_state": "Enabled"
}

Attribute Type Description
agent_id String The elastic agent ID. This is the value of the elasticAgentId attribute of the <agent/> element in the config XML.
agent_state String The state that an agent is in. Can be one of Idle, Building, LostContact, Missing, Unknown.
build_state String The state the build is in. Can be one of Idle, Building, Cancelled, Unknown.
config_state String The state of the agent in the config file. This is the value of the isDisabled attribute of the <agent/> element in the config XML. Can be one of Pending, Enabled, Disabled.

The Settings View Object

Here’s an example of the settings view object:

{
  "template": "<div class=\"form_item_block\">...</div>"
}

Attribute Type Description
template String A string containing an HTML AngularJS based view.

This template is an AngularJS based template.

GoCD uses Angular JS as its template engine for the plugin UI. This allows plugin authors to use a limited set of AngularJS features to specify how the UI of their plugins looks.

Getting started with AngularJS based templates

Given a configuration:

<configuration>
  <property>
    <key>username</key>
    <value>alice</username>
  </property>
</configuration>

This gets converted into the following JSON representation in the browser:

{
  "username": "alice"
}

The AngularJS template is expected to bind to the JSON object shown above:

<div class="form_item_block">
  <label>Username:<span class='asterix'>*</span></label>
  <input ng-model="username" />
</div>

When an Angular template is used in a Go plugin, to define the configuration UI, the configuration key which is stored in the configuration XML is used everywhere and is expected to be consistent. Since Angular works off of JSON, GoCD will make sure that the key in the JSON provided to the Angular template is the same as the key in the configuration XML.

Suppose the key of the configuration property stored in the XML is “username”, with value, “alice”, then Go will make sure that the value is available to the template as “username” when used in an Angular-specific HTML attribute like “ng-model”.

Showing validation errors in the UI

We use some simple string replacement
to substitute GOINPUTNAME with a unique identifier
for your plugin in order to render
any server side errors

<div class="form_item_block">
  <label>Username:<span class='asterix'>*</span></label>
  <input ng-model="username" />
  <span class="form_error" ng-show="GOINPUTNAME[username].$error.server">
    {{ GOINPUTNAME[username].$error.server}}
  </span>
</div>

In case of validation errors returned by go.plugin-settings.validate-configuration, the error messages needs to be populated on the UI, use the snippet here to show the validation errors.

The Plugin Settings Configuration Object

Here’s an example of the plugin settings configuration object:

{
  "server_url": {
    "display-name": "Server URL",
    "display-order": "0"
  },
  "username": {
    "required": false,
    "display-name": "Username",
    "display-order": "1"
  },
  "password": {
    "secure": true,
    "required": false,
    "display-name": "Password",
    "display-order": "2"
  }
}

Attribute Type Description
display-name String The name of the property.
default-value String The default value of the property.
display-order String A string containing a numerical value.
required Boolean If the field is mandatory.
secure Boolean If the data in the field should be stored encrypted.

The Validation Error Object

Here’s an example of the validation error object:

[
  {
    "key": "email_address",
    "message": "Email address is invalid"
  },
  {
    "key": "password",
    "message": "Password must be provided"
  }
]

Attribute Type Description
key String The name of configuration key that has an error.
message String The error message associated with that key.

The Image Object

Here’s an example of the image object:

{
  "content_type": "image/svg+xml",
  "data": "...."
}

Attribute Type Description
content_type String A valid content type for the image. Please make sure the content type is supported by most browsers.
data String A base-64 encoded (single-line non-chunking) byte array of the byte-sequence that composes the image.

The Profile Metadata Object

Here’s an example of the profile metadata object:

{
  "content_type": "image/svg+xml",
  "data": "...."
}

Attribute Type Description
key String The name of the configuration property supported by an elastic profile.
metadata Object The metadata associated with the key used in the elastic profile. Valid keys are required and secure.

The server info object

Here’s an example of the server info object:

{
  "server_id": "df0cb9be-2696-4689-8d46-1ef3c4e4447c",
  "site_url": "http://example.com:8153/go",
  "secure_site_url": "https://example.com:8154/go"
}

Attribute Type Description
server_id String This contains a unique identifier for this server.
site_url String This contains the site url configured for this server.
secure_site_url String This contains the secure site url configured for this server.

Glossary