Leave the YAML at Home!

Hello again, Brigadiers!

We’ve heard from time to time that people aren’t always so crazy about YAML – and that’s putting it nicely. If you dare to search for “yaml sucks” in your favorite search engine, you’ll find a litany of criticism. While I don’t generally harbor such strong anti-YAML sentiment, I do feel strongly that declarative languages – YAML included – are not ideal tools for modeling complex workflows. So I thought I’d mention… Brigade involves a minimal amount of YAML – so little in fact, that it’s fair to say you can “leave the YAML at home.”

Project definitions are often quite concise and are typically the only YAML you write.

Presumably, if you’re reading our blog, you already know that Brigade is an event-driven scripting platform for Kubernetes, but in case you’re still very new to Brigade, let’s briefly discuss one of Brigade’s most fundamental concepts. Projects pair event subscriptions with configuration for an event handler called a worker. These project definitions are often quite concise and are typically the only YAML you write when using Brigade.

To demonstrate, let’s look at a real project definition. This happens to be Brigade’s own project definition!

# yaml-language-server: $schema=https://schemas.brigade.sh/schemas-v2/project.json
apiVersion: brigade.sh/v2
kind: Project
metadata:
  id: brigade
description: Brigade built with Brigade!
spec:
  eventSubscriptions:
  - source: brigade.sh/github
    qualifiers:
      repo: brigadecore/brigade
    types:
    - ci:pipeline_requested
    - ci:job_requested
    - cd:pipeline_requested
  workerTemplate:
    git:
      cloneURL: https://github.com/brigadecore/brigade.git

Besides being the only YAML you typically write when using Brigade, most of the above can be generated for you with the brig init command!

For good measure, let’s mention that project definitions such as this one can be submitted to your Brigade API server like so:

$ brig project create --file project.yaml

With this very small bit of YAML out of the way, everything else you do is with JavaScript or TypeScript. Both of these languages are enormously popular with developers and we think they’re superior tools for modeling complex workflows, especially compared to yards and yards of YAML.

Your script can be as simple as:

console.log("Hello, World!")

More commonly, scripts are written to dispatch events to a suitable handler function:

const { events } = require("@brigadecore/brigadier");

events.on("brigade.sh/github", "ci:pipeline_requested", async event => {
  // Script your CI workflow here
});

events.on("brigade.sh/github", "cd:pipeline_requested", async event => {
  // Script your CD workflow here
});

events.process();

You can describe your workflows as arbitrarily complex graphs of jobs – where, behind the scenes, each job is an assembly of one or more containers running in its own Kubernetes pod.

Here’s a simplified excerpt from Brigade’s own brigade.ts script:

import { events, Job } from "@brigadecore/brigadier"

const goImg = "brigadecore/go-tools:v0.9.0"
const srcPath = "/src"
// ...

events.on("brigade.sh/github", "ci:pipeline_requested", async event => {
  const testJob = new Job("test", goImg, event)
  testJob.primaryContainer.command = ["make"]
  testJob.primaryContainer.arguments = ["test-unit"]
  testJob.primaryContainer.sourceMountPath = srcPath
  testJob.primaryContainer.workingDirectory = srcPath

  const lintJob = new Job("lint", goImg, event)
  lintJob.primaryContainer.command = ["make"]
  lintJob.primaryContainer.arguments = ["lint"]
  lintJob.primaryContainer.sourceMountPath = srcPath
  lintJob.primaryContainer.workingDirectory = srcPath

  // ...

  await Job.concurrent(
    testJob,
    lintJob,
    // ...
  ).run()
})

// ...

events.process();

In the excerpt above, we respond to ci:pipeline_requested events by executing make test-unit and make lint concurrently (subject to scheduling constraints). Each executes in its own container, in its own pod. Those containers are both based on a brigadecore/go-tools:v0.9.0 image that’s pre-loaded with all of our team’s most frequently used Go-based tools.

The full range of what you can accomplish with Brigade is unbounded.

With JavaScript and TypeScript being Turing complete languages, the full range of what you can accomplish with Brigade is unbounded and you are free to implement your workflows using everything those languages have to offer – use try, catch, and finally to handle errors, extract your own patterns and preferences into reusable NPM modules, or consume third-party modules using either npm or yarn as your dependency manager. Whenever applicable, Brigade workers will run an npm install or yarn install before executing your script!

With all this power and flexibility of full-featured programming languages at our disposal, we think it’s time to leave the YAML at home.

Head on over to docs.brigade.sh if you’d like to learn more, and until next time, remember that maintainers are always available to chat in the #brigade channel on the Kubernetes Slack if you have questions or need help getting started.