Elastic Agents
Current Version
This document is for version 4.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.
- 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.
- When an idle agent pings the server for work, assuming that resources and environments match, the agent is assigned the scheduled and waiting job.
- 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.
- 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.
- 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.
- At this point the plugin may, at its discretion, create an agent or not.
- If it sees that there are no idle agents that satisfy the profile, it may choose to spin a new agent.
- If it sees an existing idle agent that satisfies the profile, the plugin may choose to not spin a new agent.
- 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).
- 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.
- 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.
| Job Configuration | Agent Configuration | Should assign work? |
|---|---|---|
|
|
Yes Since the job requires a smaller agent, and a larger agent is available. |
|
|
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:
plugin.xmlโ The metadata of your plugin.- The plugin extension class (
DockerElasticAgentPlugin.classin the example shown) and any other classes that form your plugin. - Any optional dependencies your plugin may requires (inside the
lib).
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.10.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.10.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.
Job Completion
The intent on this message is to notify the plugin on completion of the job. The plugin may choose to terminate the elastic agent or keep it running in case the same agent can be used for another job configuration.
Request name
cd.go.elastic-agent.job-completion
Request body
Given the following config XML snippet โ
<server agentAutoRegisterKey="1e0e05fc-eb45-11e5-bc83-93882adfccf6" />
<pipelines group="first">
<pipeline name="up42">
<stage name="up42_stage">
<jobs>
<job name="up42_job" elasticProfileId="profile1" />
</jobs>
</stage>
</pipeline>
</pipelines>
<agents>
<agent hostname="1697e6b164f7" ipaddress="172.17.0.6" uuid="a45e3ca1-4419-4bd5-b18b-882f75ffd4c2" elasticAgentId="GoCD18efbeef995e40f688cd92dc22a4d332" elasticPluginId="cd.exmaple.elastic-agent" />
</agents>
The plugin will receive the following JSON body โ
{
"elastic_agent_id": "GoCD18efbeef995e40f688cd92dc22a4d332",
"job_identifier": {
"job_id": 100,
"job_name": "up42_job",
"pipeline_counter": 1,
"pipeline_label": "up42",
"pipeline_name": "up42",
"stage_counter": "1",
"stage_name": "up42_stage"
}
}
The request body will contain the following JSON elements:
| Key | Type | Description |
|---|---|---|
elastic_agent_id |
String |
The Elastic agent ID of the agent on which the job has completed. |
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.
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
- Disable Agents
- Delete Agents
- Get Plugin Settings
- Get Server Info
- Add Server Health Messages
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 substituteGOINPUTNAMEwith 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
Scheduled: The state that a job is in, before it is assigned (see โAssignedโ) to an agent. A job can stay in a scheduled state for a long time, if all suitable (see โSuitableโ) agents are busy running other jobs.
Assigned: The state that a job is in, when a suitable (see โSuitableโ) agent has been decided for it. The job state changes to โPreparingโ once the agent actually picks up the job, and starts checking out materials, etc. in preparation for the job.
Suitable: An agent is said to be โsuitableโ for a job, if it has all the resources that the job needs, and is in the environment that the pipeline of this job is in.