Writing Plugins

This page shows you how to write your own Buildkite plugin, and how to validate the plugin.yml file which describes it against the plugin schema.

Tutorial: write a plugin

In this tutorial we'll create a Buildkite plugin called "File Counter", which counts the number of files in the build directory once the command has finished, and creates a build annotation with the count.

pipeline.yml
steps:
  - command: ls
    plugins:
      - a-github-user/file-counter#v1.0.0:
          pattern: '*.md'

Step 1: Create a new git repository

Every Buildkite plugin is a Git repository, ending in -buildkite-plugin. This suffix is required: Buildkite automatically adds -buildkite-plugin to the plugin name specified in the pipeline.yml. Let's create a new Git repository following these naming conventions:

mkdir file-counter-buildkite-plugin
cd file-counter-buildkite-plugin
git init

Step 2: Add a plugin.yml

Next we'll create plugin.yml to describes how the plugin appears in the Buildkite Plugin Directory, what it requires, and what configuration options it accepts.

plugin.yml
name: File Counter
description: Annotates the build with a file count
author: https://github.com/a-github-user
requirements: []
configuration:
  properties:
    pattern:
      type: string
  additionalProperties: false

The configuration property defines the validation rules for the plugin configuration using the JSON Schema format. We’ve specified that the plugin has a single pattern property, of type string.

Configuration properties are available to the hook script as environment variables with the naming pattern BUILDKITE_PLUGIN_<PLUGIN_NAME>_<CONFIGURATION_PROPERTY> where <PLUGIN_NAME> is the GitHub repository slug, not the name defined in plugin.yml. In this case, the configured value of pattern will be available as BUILDKITE_PLUGIN_FILE_COUNTER_PATTERN.

Plugin properties and git

Note that if you reference a plugin using a full URL and .git extension, you need to use a slightly different syntax to get access to the configuration properties. For example, to get the value of pattern in https://github.com/my-org/my-plugin.git#v1.0.0 use BUILDKITE_PLUGIN_MY_PLUGIN_GIT_PATTERN.

Valid plugin.yml properties

Property Description
name The name of the plugin, in Title Case.
description A short sentence describing what the plugin does.
author A URL to the plugin author (e.g. website or GitHub profile).
requirements An array of commands that are expected to exist in the agent’s $PATH.
configuration A JSON Schema describing the valid configuration options available.

Step 3: Validate the plugin.yml

The Buildkite Plugin Linter helps ensure your plugin is up-to-date, and has all the required files to be listed in the plugin directory.

You can run the plugin linter with the following Docker command:

docker run -it --rm -v "$PWD:/plugin:ro" buildkite/plugin-linter --id a-github-user/file-counter

To make it easier to run this command, add it to the docker-compose.yml file:

docker-compose.yml
services:
  tests: ...
  lint:
    image: buildkite/plugin-linter
    command: ['--id', 'a-github-user/file-counter']
    volumes:
      - ".:/plugin:ro"

You can now run the tests using the following command:

docker-compose run --rm lint

Step 4: Add a hook

Plugins can implement a number of plugin hooks. For this plugin, we’ll create a post-command hook in a hooks directory:

mkdir hooks
touch hooks/post-command
chmod +x hooks/post-command
hooks/post-command
#!/bin/bash
set -euo pipefail

PATTERN="$BUILDKITE_PLUGIN_FILE_COUNTER_PATTERN"

echo "--- :1234: Counting the number of files"

COUNT=$(find . -name "$PATTERN" | wc -l)

echo "Found ${COUNT} files matching ${PATTERN}"

buildkite-agent annotate "Found ${COUNT} files matching ${PATTERN}"

Step 5: Add a test

Next step is to test the post-command hook using BATS, and the buildkite/plugin-tester Docker image.

mkdir tests
touch tests/post-command.bats
chmod +x tests/post-command.bats

Create the following tests/post-command.bats file:

tests/post-command.bats
#!/usr/bin/env bats

load '/usr/local/lib/bats/load.bash'

# Uncomment the following line to debug stub failures
# export BUILDKITE_AGENT_STUB_DEBUG=/dev/tty

@test "Creates an annotation with the file count" {
  export BUILDKITE_PLUGIN_FILE_COUNTER_PATTERN="*.bats"

  stub buildkite-agent 'annotate "Found 1 files matching *.bats" : echo Annotation created'

  run "$PWD/hooks/post-command"

  assert_success
  assert_output --partial "Found 1 files matching *.bats"
  assert_output --partial "Annotation created"

  unstub buildkite-agent
}

To run the test, run the following Docker command:

docker run -it --rm -v "$PWD:/plugin:ro" buildkite/plugin-tester
 ✓ Creates an annotation with the file count

1 test, 0 failures

To make it easier to run this command, create a Docker Compose file:

docker-compose.yml
version: '2'
services:
  tests:
    image: buildkite/plugin-tester
    volumes:
      - ".:/plugin:ro"

You can now run the tests using the following command:

docker-compose run --rm tests

Step 6: Add a readme

Next we'll add a README.md file to introduce the plugin to the world:

# File Counter Buildkite Plugin

Annotates the build with a file count.

## Example

Add the following to your `pipeline.yml`:

```yml
steps:
  - command: ls
    plugins:
      - a-github-user/file-counter#v1.0.0:
          pattern: '*.md'
```

## Configuration

### `pattern` (Required, string)

The file name pattern, for example `*.ts`. Supports any pattern supported by [find -name](http://man7.org/linux/man-pages/man1/find.1.html).

## Developing

To run the tests:

```shell
docker-compose run --rm tests
```

## Contributing

1. Fork the repo
2. Make the changes
3. Run the tests
4. Commit and push your changes
5. Send a pull request

Publish to the Buildkite Plugin Directory

To add your plugin to the Buildkite Plugin Directory, publish your repository to a public GitHub repository and add the buildkite-pluginrepository topic tag. For full instructions, see the Plugin Directory documentation.

Designing plugins: single-command plugins versus library plugins

When writing plugins, there are two patterns you can choose from:

  • A single-command plugin: a small, declarative plugin, which exposes a single command for use in your pipeline steps. Most plugins follow this pattern.
  • A library plugin, or super-plugin: this plugin type assembles multiple commands into one plugin. Refer to the library example Buildkite plugin for an example of how to set up this type of plugin.