Artifact Plugins
Extension Information
Availability | GoCD version 18.11.0 onwards |
Extension Name | artifact |
Extension API Version | 2.0 |
Background
By default, GoCD internally manages and stores artifacts. The Artifact extension allows GoCD to make use of an external artifact store for storing artifacts while retaining traceability between pipelines. For example, by using the Docker artifact registry plugin, a user can publish an image to a docker registry, and subsequently fetch it in a downstream pipeline. Since GoCD is “aware” of the artifact store with this extension, it can still maintain the Value Stream between the upstream and downstream pipelines even if the artifact is stored outside of GoCD.
Developers can start building their own artifact plugins by forking the docker plugin as an example reference implementation.
How will it help you?
A feature like this will allow for artifacts to be uploaded and stored outside of GoCD. Previously, the artifacts were only managed on the GoCD server.
Requests from the GoCD server
In order to implement an artifact 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 artifact stores through the browser.
These are messages that a plugin must implement in order to allow users to configure publishing an artifact through the browser.
These are messages that a plugin must implement in order to allow users to configure fetching an artifact through the browser.
Publish Artifact
This message is a request to the plugin to publish an artifact to the specified artifact store.
Request name
cd.go.artifact.publish-artifact
Request body
Given the following config XML snippet —
<artifactStores>
<artifactStore id="dockerhub" pluginId="cd.go.artifact.docker.registry">
<property>
<key>RegistryURL</key>
<value>https://index.docker.io/v1/</value>
</property>
<property>
<key>Username</key>
<value>boohoo</value>
</property>
<property>
<key>Password</key>
<value>password</value>
</property>
</artifactStore>
</artifactStores>
<pipelines group="first">
<pipeline name="build">
...
<job name="build-job">
...
<artifacts>
<artifact id="app-image" storeId="dockerhub">
<configuration>
<property>
<key>Image</key>
<value>gocd/gocd-demo</value>
</property>
<property>
<key>Tag</key>
<value>v${GO_PIPELINE_COUNTER}</value>
</property>
</configuration>
</artifact>
</artifacts>
</job>
...
</pipeline>
</pipelines>
The plugin will receive the following JSON body —
{
"environment_variables":{
"GO_PIPELINE_NAME":"build",
"GO_TRIGGER_USER":"admin",
"FOO": "bar"
},
"artifact_plan":{
"configuration":{
"BuildFile":"",
"Image":"gocd/gocd-demo",
"Tag":"v${GO_PIPELINE_COUNTER}"
},
"id":"app-image",
"storeId":"dockerhub"
},
"artifact_store":{
"configuration":{
"RegistryURL":"https://index.docker.io/v1/",
"Username":"boohoo",
"Password":"password"
},
"id":"dockerhub"
},
"agent_working_directory":"/Users/varshavs/gocd/agent/pipelines/build"}
The request body will contain the following JSON elements:
Key | Type | Description |
---|---|---|
environment_variables |
Object |
A map of the environment variable that were available at the time the job was triggered. |
artifact_plan |
Object |
This mainly contains the configuration properties that inform the plugin about the specific artifact that needs to be pushed. |
artifact_store |
Object |
This section contains the details about the artifact store to which the plugin has to publish the artifact. |
agent_working_directory |
String |
Since the artifact publishing happens on the go-agent side, the working directory is also passed along in the request in case the plugin wants to use it. |
Response code
The plugin is expected to return status 200
if it can understand the request.
Response Body
The plugin is expected to return a json as shown. This json is written into a file called <plugin-id>.json
on the agent and uploaded as a Build Artifact
to the GoCD server to a directory called pluggable-artifact-metadata
. This directory is never removed as part of cleaning GoCD artifacts.
Plugin response body
{
"metadata":{
"image":"gocd/gocd-demo:v23",
"digest":"sha256:f7840887b6f09f531935329a4ad1f6176866675873a8b3eed6a5894573da8247"
}
}
The content of plugin-id.json file
{
"app-image":{
"image":"gocd/gocd-demo:v23",
"digest":"sha256:f7840887b6f09f531935329a4ad1f6176866675873a8b3eed6a5894573da8247"
}
}
Note:
app-image
is the artifact id specified in the job configuration
Fetch Artifact
This message is a request to the plugin to fetch an artifact from the specified artifact store.
Request name
cd.go.artifact.fetch-artifact
Request body
Given the following config XML snippet —
<artifactStores>
<artifactStore id="dockerhub" pluginId="cd.go.artifact.docker.registry">
<property>
<key>RegistryURL</key>
<value>https://index.docker.io/v1/</value>
</property>
<property>
<key>Username</key>
<value>boohoo</value>
</property>
<property>
<key>Password</key>
<value>password</value>
</property>
</artifactStore>
</artifactStores>
<pipelines group="first">
<pipeline name="build">
...
<job name="build-job">
...
<artifacts>
<artifact type="external" id="app-image" storeId="dockerhub">
<configuration>
<property>
<key>Image</key>
<value>gocd/gocd-demo</value>
</property>
<property>
<key>Tag</key>
<value>v${GO_PIPELINE_COUNTER}</value>
</property>
</configuration>
</artifact>
</artifacts>
</job>
...
<stage name="downstream_stage">
...
<job name="downstream_job">
<tasks>
<fetchartifact artifactOrigin="external" stage="upstream_stage" job="build-job" artifactId="app-image">
<configuration>
<property>
<key>image_name</key>
<value>release-candidate</value>
</property>
</configuration>
</fetchartifact>
</tasks>
</job>
...
</stage>
</pipeline>
</pipelines>
The plugin will receive the following JSON body —
{
"fetch_artifact_configuration":{
"image_name": "release-candidate"
},
"artifact_metadata":{
"image":"gocd/gocd-demo:v23",
"digest":"sha256:f7840887b6f09f531935329a4ad1f6176866675873a8b3eed6a5894573da8247"
},
"store_configuration":{
"RegistryURL":"https://index.docker.io/v1/",
"Username":"boohoo",
"Password":"password"
},
"agent_working_directory":"/Users/varshavs/gocd/agent/pipelines/build"
}
The request body will contain the following JSON elements:
Key | Type | Description |
---|---|---|
fetch_artifact_configuration |
Object |
This mainly contains the configuration properties that inform the plugin about the specific artifact that needs to be fetched. |
artifact_metadata |
Object |
This is the information that was uploaded as a json file to the GoCD server at publish time. The json file is parsed and its contents are passed to the plugin at fetch time so that the plugin can fetch the right artifact. |
store_configuration |
Object |
This section contains the details about the artifact store from which the plugin has to fetch the artifact. |
agent_working_directory |
String |
Since the artifact download happens on the go-agent side, the working directory is also passed along in the request in case the plugin wants to use it. |
Response code
The plugin is expected to return status 200
if it can understand the request.
Response Body
The plugin can respond with a list of environment variables which will be set on the job, for all further tasks in that job to use. It can also return an empty list ([]
). Existing environment variables will be overridden, if they are returned as a part of this response.
Example response body:
[
{
"name": "IMAGE_ID",
"value": "image1/v23",
"secure": false
},
{
"name": "MY_SECRET_ENV_VAR",
"value": "some-password",
"secure": true
}
]
The response body will contain the following JSON elements:
Key | Type | Description |
---|---|---|
name |
String |
The key or name of the environment variable to set. |
value |
String |
The value of the environment variable to set. |
secure |
Boolean |
Should it be a secure environment variable or not? |
Get Plugin Capabilities
This message is a request to the plugin to provide plugin capabilities. Currently the plugin does not support any capabilities, so plugins should return an empty JSON as a response. In the future, based on these capabilities GoCD would enable or disable the plugin features for a user.
Request name
cd.go.artifact.get-capabilities
Request body
Server sends request an Empty
request body.
Response Body
The plugin is expected to return status 200
if it can understand the request, with an empty response body
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.artifact.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.
Get Store Config Metadata
This is a message that the plugin should implement, to allow users to configure global artifact stores from the Artifact Stores View in GoCD.
Request name
cd.go.artifact.store.get-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": "RegistryURL",
"metadata": {
"required": true,
"secure": false
}
},
{
"key": "Password",
"metadata": {
"required": true,
"secure": true
}
}
]
A JSON metadata object.
Get Store Config View
This is a message that the plugin should implement, to allow users to configure artifact stores from the Artifact Stores View in GoCD.
Request name
cd.go.artifact.store.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>"
}
Validate Store Config
This call is expected to validate the user inputs that form a part of the artifact store.
Request name
cd.go.artifact.store.validate
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
{
"RegistryURL": "https://index.docker.io/v1/",
"Username": "boohoo"
}
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": "RegistryURL",
"message": "'RegistryURL is invalid'"
}
]
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 Publish Artifact Metadata
This is a message that the plugin should implement, to allow users to configure external artifacts from the Job Config View in GoCD.
Request name
cd.go.artifact.publish.get-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
[
{
"key": "Image",
"metadata": {
"required": true,
"secure": false
}
},
{
"key": "Tag",
"metadata": {
"required": true,
"secure": false
}
}
]
A JSON metadata object.
Get Publish Artifact View
This is a message that the plugin should implement, to allow users to configure external artifacts from the Job Config View in GoCD.
Request name
cd.go.artifact.publish.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>"
}
Validate Publish Artifact Config
This call is expected to validate the user inputs that form a part of the publish external artifact config.
Request name
cd.go.artifact.publish.validate
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
{
"Image": "gocd/gocd-demo",
"Tag": "boohoo",
"BuildFile": "foo.json"
}
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": "Image",
"message": "Either `BuildFile` or `Image` must be specified"
},
{
"key": "BuildFile",
"message": "Either `BuildFile` or `Image` must be specified"
}
]
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 Fetch Artifact Metadata
This is a message that the plugin should implement, to allow users to configure fetch artifact task from the Task View in GoCD.
Request name
cd.go.artifact.fetch.get-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:
[
{
"key": "DestOnAgent",
"metadata": {
"required": true,
"secure": false
}
}
]
A JSON metadata object.
Get Fetch Artifact View
This is a message that the plugin should implement, to allow users to configure fetch artifact task from the Task View in GoCD.
Request name
cd.go.artifact.fetch.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>"
}
Validate Fetch Artifact Config
This call is expected to validate the user inputs that form a part of the fetch external artifact config.
Request name
cd.go.artifact.publish.validate
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
{
"Dest": "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": "Dest",
"message": "Invalid"
}
]
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.
Requests to the GoCD server
The plugin may make the following requests to the server using GoApplicationAccessor#submit(GoApiRequest)
Send Console Log
The plugin can send console logs to the server while fetching or publishing artifacts.
Request name
go.processor.artifact.console-log
Request version
The request version must be set to 1.0
.
Request body
The request body must contain the logLevel and a message string. The logLevel must be INFO
or ERROR
.
Example Request Body
{
"logLevel": "INFO",
"message" : "This is an info message."
}
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.
Console Logger Example
import com.google.gson.Gson;
import com.thoughtworks.go.plugin.api.GoApplicationAccessor;
import com.thoughtworks.go.plugin.api.GoPluginIdentifier;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.request.DefaultGoApiRequest;
import com.thoughtworks.go.plugin.api.response.DefaultGoApiResponse;
import com.thoughtworks.go.plugin.api.response.GoApiResponse;
import java.util.Collections;
public class ConsoleLogger {
private static final String SEND_CONSOLE_LOG = "go.processor.artifact.console-log";
private static final String API_VERSION = "1.0";
private GoPluginIdentifier PLUGIN_IDENTIFIER = new GoPluginIdentifier("artifact", Collections.singletonList(API_VERSION));
private static final Logger LOG = Logger.getLoggerFor(DockerRegistryArtifactPlugin.class);
private static ConsoleLogger consoleLogger;
private final GoApplicationAccessor accessor;
private ConsoleLogger(GoApplicationAccessor accessor) {
this.accessor = accessor;
}
public void info(String message) {
sendLog(new ConsoleLogMessage(ConsoleLogMessage.LogLevel.INFO, message));
}
public void error(String message) {
sendLog(new ConsoleLogMessage(ConsoleLogMessage.LogLevel.ERROR, message));
}
private void sendLog(ConsoleLogMessage consoleLogMessage) {
DefaultGoApiRequest request = new DefaultGoApiRequest(SEND_CONSOLE_LOG, API_VERSION, PLUGIN_IDENTIFIER);
request.setRequestBody(consoleLogMessage.toJSON());
GoApiResponse response = accessor.submit(request);
if (response.responseCode() != DefaultGoApiResponse.SUCCESS_RESPONSE_CODE) {
LOG.error(String.format("Failed to submit console log: %s", response.responseBody()));
}
}
public static ConsoleLogger getLogger(GoApplicationAccessor accessor) {
if (consoleLogger == null) {
synchronized (ConsoleLogger.class) {
if (consoleLogger == null) {
consoleLogger = new ConsoleLogger(accessor);
}
}
}
return consoleLogger;
}
static class ConsoleLogMessage {
private LogLevel logLevel;
private String message;
public ConsoleLogMessage(LogLevel logLevel, String message) {
this.message = message;
this.logLevel = logLevel;
}
public String toJSON() {
return new Gson().toJson(this);
}
enum LogLevel {
INFO, ERROR
}
}
}
Request/Response JSON Objects
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”.
So, the name “foobar” needs to be the same across the configuration XML, the Angular template as well as in any code that the plugin has.
Showing validation errors in the UI
We use some simple string replacement
to substituteGOINPUTNAME
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 Metadata Object
Here’s an example of the metadata object:
{
"key": {
"required": "true",
"secure": "true"
}
}
Attribute | Type | Description |
---|---|---|
key |
String | The name of the configuration property supported by an artifact store, publish artifact config and fetch artifact config. |
metadata |
Object | The metadata associated with the key. 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. |