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.

  - command: ls
      - 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, create plugin.yml to describe how the plugin appears in the Buildkite plugins directory, what it requires, and what configuration options it accepts.

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

The configuration property defines the validation rules for the plugin configuration using the JSON Schema format. The plugin in this tutorial 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 (for example, 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 plugins 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:

    image: buildkite/plugin-linter
    command: ['--id', 'a-github-user/file-counter']
      - ".:/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, create a post-command hook in a hooks directory:

mkdir hooks
touch hooks/post-command
chmod +x hooks/post-command
set -euo pipefail


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

The 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:

#!/usr/bin/env bats

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

# Uncomment the following line to debug stub failures

@test "Creates an annotation with the file count" {

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

  run "$PWD/hooks/post-command"

  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:

version: '2'
    image: buildkite/plugin-tester
      - ".:/plugin:ro"

You can now run the tests using the following command:

docker-compose run --rm tests

Step 6: Add a readme

Next, 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`:

  - command: ls
      - 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:

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

Developing a plugin with a feature branch

When developing plugins, it is useful to have a quick feedback loop between making a change in your plugin code, and seeing the effects in a Buildkite pipeline. Let's say you're developing your feature on my-org/plugin#dev-branch. By default, if a Buildkite agent sees that it needs the plugin my-org/plugin#dev-branch, and it already has a checkout matching that, it will not pull any changes from the Git repository. But if you do want to see changes reflected immediately, set plugins-always-clone-fresh to true.

One way to try this is to add the following step to the Buildkite pipeline where you're testing your plugin. Configuring BUILDKITE_PLUGINS_ALWAYS_CLONE_FRESH on only one step means that other plugins, which are unlikely to be changing in the meantime, won't get unnecessarily cloned on every step invocation.

  - command: ls
      - a-github-user/file-counter#dev-branch:
          pattern: '*.md'

Publish to the Buildkite plugins directory

To add your plugin to the Buildkite plugins directory, publish your repository to a public GitHub repository and add the buildkite-plugin repository topic tag. For full instructions, see the plugins 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.