Scripting Guide
Scripting Guide
This guide explains the basics of how to create and structure Brigade scripts,
which can be written in either JavaScript (brigade.js
) or TypeScript
(brigade.ts
).
Brigade Scripts, Projects, and Repositories
At the very core, a Brigade script is simply a JavaScript (or TypeScript) file defined by a project, which Brigade executes in the context of a worker to handle events to which the project is subscribed.
Brigade scripts can be stored either in a project definition or in a project’s git repository. We like to think of these as cluster-oriented shell scripts: The script just ties together a bunch of other programs.
This document will walk through the example projects, with the scripts gradually increasing in complexity.
Preparation
To proceed, you will need to have access to a Brigade server and be logged in using the brig CLI. See the Quickstart if you have not already done so.
Then, each example project can be created like so:
$ brig project create -f examples/<project>/project.yaml
Let’s begin!
A Basic Brigade Script
Here is a basic brigade.js
script that just sends a single message to the
log:
console.log("Hello, World!");
First let’s create the example project:
$ brig project create --file examples/01-hello-world/project.yaml
Created project "hello-world".
This project subscribes to one event, the exec
event generated by the
brig event create
command. The events that a project subscribes to are
configured under the eventSubscriptions
section of its definition file
(e.g. project.yaml
).
Next, let’s trigger execution of the project script by creating an event of this type:
$ brig event create --project hello-world --follow
Created event "261229dc-1140-4f6a-bf91-bd2a69f31721".
Waiting for event's worker to be RUNNING...
2021-09-20T22:15:12.047Z INFO: brigade-worker version: a398ba8-dirty
Hello, World!
This example unconditionally logs “Hello, World!” in response to any event to which the project is subscribed (though, as mentioned, it is only subscribed to one type). It’s more common to incorporate event handlers to handle specific events in different ways.
Brigade Events and Event Handlers
Examples of events would include pushes to GitHub or DockerHub repositories. When a project is subscribed to an event, that project’s worker will load and execute the project’s script. Event handlers in the script define specific logic for handling matching events.
Here we see a script that consists of an event handler specifically for the
exec
event emitted by the brig
CLI. If any other event is generated for a
project, no action would occur as there is no logic defined to handle it.
const { events } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
console.log("Hello, World!");
// Optionally return a string and it will automatically be persisted
// in the event's summary field.
return "All done."
});
events.process();
There are a few things to note about this script:
- It imports the
events
object from@brigadecore/brigadier
. Almost all Brigade scripts do this. - It declares a single event handler that says “on an
exec
event, run this function”. - We’re not actually utilizing the
event
object in this script, but we will in a later example - The event handler returns a string, which is the optional event summary. This is can be utilized by gateways interested in the results or other context related to the handling of the event. The summary only has meaning to the script and possibly the gateway – otherwise, it is opaque to Brigade itself.
- Scripts with event handlers defined, such as this one, must invoke
events.process()
after registering all handlers in order to dispatch the event.
Similarly to our first script, this event handler function displays a message to a log, producing the following output:
$ brig event create --project first-event --follow
Created event "5b0bd00a-4f31-40da-ad01-0d2f62f4d70e".
Waiting for event's worker to be RUNNING...
2021-09-20T22:24:57.655Z INFO: brigade-worker version: a398ba8-dirty
Hello, World!
Note: the success/failure of an event is determined by the exit code of the Worker running the script. If the script fails or throws an error, the Worker will exit non-zero and be considered to have failed.
Since projects may subscribe to multiple types of events (from multiple sources as well), defining multiple event handlers in your script permits different types of events to be handled differently.
For example, we can can expand the example above to also provide a handler for a GitHub push event:
const { events } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
console.log("==> handling an 'exec' event")
});
events.on("brigade.sh/github", "push", async event => {
console.log(" **** I'm a GitHub 'push' handler")
});
Now if we re-run our brig
command, we will see the same output as before:
$ brig event create --project first-event --follow
Created event "6769b7fd-ddd0-4a42-aa17-723c02a2b61a".
Waiting for event's worker to be RUNNING...
2021-09-20T22:29:28.498Z INFO: brigade-worker version: a398ba8-dirty
Hello, World!
Since the event we created was from brigade.sh/cli
and of type exec
, only
the corresponding handler was executed. The handler for events from
brigade.sh/github
and of type push
was not.
See the Events doc for more info on how events are structured in Brigade.
Jobs and Containers
The Brigade worker’s image is derived from a Node.js image and containers based on that image can execute JavaScript and TypeScript only. In the frequent case that other runtimes, tools, etc. not present on the worker image are required in order to respond to an event, spawning a Job permits aspects of event-handling to be delegated to a more suitable container.
The Brigadier library exposes an API for defining and executing Jobs.
In the previous section, we focused on event handlers which just logged messages. In this section, we’ll update the event handler to create a few jobs.
To start with, let’s create a simple job that doesn’t really do any work:
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job = new Job("do-nothing", "debian", event);
await job.run();
});
events.process();
The first thing to note is that we have changed the first line. In addition
to importing the events
object, we are now also importing Job
. Job
is the
class used for creating all jobs in Brigade.
Next, inside of our exec
event handler, we create a new Job
, and we give it
three pieces of information:
- a name: The name must be unique to the event handler and should be composed
of letters, numbers, and dashes (
-
). - an image: This can be any image that your cluster can find. In the case
above, we use the image named
debian
, which is fetched straight from DockerHub. - the event itself that this handler is for
The Job is a crucial part of the Brigade ecosystem. A container is created from a Job’s image, and it’s within this container that we do the “real work”. At the beginning, we explained that we think of Brigade scripts as “shell scripts for your cluster.” When you execute a shell script, it is typically some glue code that manages calling one or more external programs in a specific way.
#!/bin/bash
ps -ef "hello" | grep chrome
The script above really just organizes the way we call two existing programs
(ps
and grep
). Brigade does the same, except instead of executing
programs, it executes containers. Each container is expressed as a Job,
which is a wrapper that knows how to execute containers.
So in our example above, we create a Job named “do-nothing” that runs a Debian Linux container and (as the name implies) does nothing.
Jobs are created and run in different steps. This way, we can do some preparation on our job (as we will see in a moment) before executing it.
Job.run()
To run a job, we use the Job’s run()
method. Behind the scenes, Brigade will
start a new debian
container (downloading the image if necessary), execute
it, and monitor it. When the container is done executing, the run is complete.
It is worth mentioning that run()
is asynchronous and actually a request
to the Brigade API server to schedule the job for execution. This execution is
subject to scheduling constraints, e.g. per configuration like maximum job
concurrency, substrate capacity, etc.
Further, note that run()
returns immediately after scheduling and the await
keyword can (and usually should) be used to, instead, block further script
execution until the job has actually been executed and reached a terminal
state.
If we run the code above, we’ll get output that looks something like this:
$ b event create --project first-job --follow
Created event "aa8fff14-0b8d-4903-9109-ccadc1d9d3fe".
Waiting for event's worker to be RUNNING...
2021-09-22T18:41:01.787Z INFO: brigade-worker version: 927850b-dirty
2021-09-22T18:41:02.130Z [job: do-nothing] INFO: Creating job do-nothing
Basically, our simple build just created an empty Debian Linux pod which had nothing to do and so exited immediately.
Adding Commands to Jobs
To make our Job do more, we can add a command to it. A command can then be
paired with a list of arguments. The command and argument arrays are added to
the job’s primaryContainer, which is the container running the image supplied
to the Job
constructor, e.g. debian
in this example. (Every job will
have one primaryContainer
and zero or more sidecarContainers
. We’ll look
at an example using sidecarContainers
later on.)
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job = new Job("my-first-job", "debian", event);
job.primaryContainer.command = ["echo"];
job.primaryContainer.arguments = ["My first job!"];
await job.run();
});
events.process();
A command can be anything that is supported by the job’s image. In the example
above, our command invokes the echo
binary with the arguments supplied.
For multiple commands, a common approach is for the command
array to consist
of one element such as ["/bin/sh"]
or ["/bin/bash"]
and then the first
element of the arguments
array be "-c"
, followed by entries comprising the
shell commands needed.
However, as construction of such commands may soon prove unwieldy due to increased complexity, we recommend writing a shell script and making this available to the script to run (e.g. placing it in the git repository associated with the project or adding it to a custom Worker image).
Let’s run the example:
$ brig event create --project first-job --follow
Created event "046c09cd-76cb-49ea-b40c-d3e0e557de62".
Waiting for event's worker to be RUNNING...
2021-09-22T20:11:10.453Z INFO: brigade-worker version: 927850b-dirty
2021-09-22T20:11:10.909Z [job: my-first-job] INFO: Creating job my-first-job
Now, to see the logs from my-first-job
, we issue the following brig command
utilizing the generated event ID.
$ brig event logs --id 046c09cd-76cb-49ea-b40c-d3e0e557de62 --job my-first-job
My first job!
Note:
job.run()
will throw an exception if the job fails. Additionally, job success/failure is determined by the exit code of the job’sprimaryContainer
.
Combining jobs into a pipeline
Now we can take things one more step and create two jobs that each do something.
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job1 = new Job("my-first-job", "debian", event);
job1.primaryContainer.command = ["echo"];
job1.primaryContainer.arguments = ["My first job!"];
await job1.run();
let job2 = new Job("my-second-job", "debian", event);
job2.primaryContainer.command = ["echo"];
job2.primaryContainer.arguments = ["My second job!"];
await job2.run();
});
events.process();
In this example we create two jobs (my-first-job
and my-second-job
). Each
starts a Debian container and prints a message, then exits. On account of the
use of await
, job2
won’t run until job1
completes, so these jobs
implicitly run sequentially.
Let’s run the example and then view each job’s logs:
$ brig event create --project simple-pipeline
Created event "58e7d3cf-b7d2-4ab7-98ad-326a99f10a25".
$ brig event logs --id 58e7d3cf-b7d2-4ab7-98ad-326a99f10a25 --job my-first-job
My first job!
$ brig event logs --id 58e7d3cf-b7d2-4ab7-98ad-326a99f10a25 --job my-second-job
My second job!
Serial and Concurrent job groups
Now that we’ve seen an example project that runs multiple jobs, let’s look at the methods we have for specifiying how the jobs run, i.e. sequentially or concurrently – or, as we’re about to see, a combination thereof.
For example, we can run two sequences of jobs concurrently:
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job1 = new Job("my-first-job", "debian", event);
job1.primaryContainer.command = ["echo"];
job1.primaryContainer.arguments = ["My first job!"];
let job2 = new Job("my-second-job", "debian", event);
job2.primaryContainer.command = ["echo"];
job2.primaryContainer.arguments = ["My second job!"];
let jobA = new Job("my-a-job", "debian", event);
jobA.primaryContainer.command = ["echo"];
jobA.primaryContainer.arguments = ["My A job!"];
let jobB = new Job("my-b-job", "debian", event);
jobB.primaryContainer.command = ["echo"];
jobB.primaryContainer.arguments = ["My B job!"];
await Job.concurrent(
Job.sequence(job1, job2),
Job.sequence(jobA, jobB)
).run();
});
events.process();
There are two things to notice in the example above:
- Both the
concurrent
andsequence
methods exist on theJob
object. - The return type for both methods is a generic “runnable”, i.e. an object
that
run()
can then be called on, just like a standalone job instance.
Here’s a breakdown of each method:
Job.sequence()
takes an array of runnables (e.g. a job or a group of jobs) and runs them in sequence. A new runnable is started only when the previous one completes. The sequence completes when the last runnable has completed (or when any runnable fails). A sequential group is itself considered a success or failure on the basis of all its jobs completing successfully.Job.concurrent()
takes an array of runnables and runs them all concurrently. When run, all runnables are started simultaneously (subject to scheduling constraints). The concurrent group completes when all Runnables have completed. A concurrent group is itself considered a success or failure on the basis of all its jobs completing successfully.
As both of these methods return a runnable, they can be chained. In the example
above, job1
and job2
run in sequence, as do jobA
and jobB
, but both
sequences are run concurrently.
This is the way script writers can control the order in which groups of jobs are run.
For example, if using Brigade to implement CI, you might wish to divide checks into those that are resource intensive (e.g. longer-running builds, integration tests) and those that are less so (e.g. linting, unit tests). Both groups can run the jobs within concurrently, but the groups themselves might be run in sequence, such that no resource intensive checks are executed unless/until all of the less resource intensive checks have passed.
Running a script from a Git Repository
Earlier we talked about how a project may have an associated Git repository. Let’s look at one such project now.
apiVersion: brigade.sh/v2
kind: Project
metadata:
id: git
description: A project with whose script is stored in a git repository
spec:
eventSubscriptions:
- source: brigade.sh/cli
types:
- exec
workerTemplate:
# logLevel: DEBUG
configFilesDirectory: examples/06-git/.brigade
git:
cloneURL: https://github.com/brigadecore/brigade.git
ref: refs/heads/v2
Notice that there is no embedded script in this project definition. Rather, the
project specifies where the Brigade config file directory
(configFilesDirectory
) should be located in the repository configured under
the git
section. (If not supplied, the default is to look for the .brigade
directory at the git repository’s root).
The config file directory is where the Brigade script is placed. In this
example, the brigade.js
script is simply a console.log()
statement.
console.log("Hello, Git!");
Let’s run the example:
$ brig event create --project git --follow
Created event "5ff386ed-060e-49fa-8292-0bade75f8840".
Waiting for event's worker to be RUNNING...
2021-09-22T21:38:10.667Z INFO: brigade-worker version: 927850b-dirty
Hello, Git!
Here’s what is happening behind the scenes when we create an event for this project: Because the project has a Git repository associated with it, Brigade is automatically fetching a clone of that repository and attaching it to the Worker in charge of running the script.
By default, the repository contents are not automatically mounted to jobs in
the project’s Brigade script. However, mounting the contents to a job is easily
accomplished via the sourceMountPath
configuration on a job’s
primaryContainer
.
The following example shows how a job can be configured to access the repo in
order to run a test target. It also configures workingDirectory
with the same
value as sourceMountPath
so that the job needn’t worry about changing into
the appropriate directory before running commands:
const { events, Job } = require("@brigadecore/brigadier");
const localPath = "/workspaces/brigade";
events.on("brigade.sh/github", "push", async event => {
let test = new Job("test", "debian", event);
test.primaryContainer.sourceMountPath = localPath;
test.primaryContainer.workingDirectory = localPath;
test.primaryContainer.command = ["bash"];
test.primaryContainer.arguments = ["-c", "make test"];
await test.run();
})
events.process();
Being able to associated a Git repository to a project is a convenient way to provide version-controlled data to our Brigade scripts. For instance, instead of embedding a project’s script and other configuration inside the project definition, these files can be fetched from source control.
Additionally, this functionality makes Brigade a great tool for executing CI pipelines, deployments, packaging tasks, end-to-end tests, and other DevOps tasks for a given repository.
Working with Event and Project Data
As we’ve seen, the event object is always passed into an event handler. This object also includes project data. Let’s look at the data we have access to.
The Brigade Event
From the event, we can find out what triggered the event, what data was sent with the event, the details of the worker in charge of running the event and more.
Here are some notable fields on the event object:
id
is a unique, per-event ID. Every time a new event is triggered, a new ID will be generated.project
is the project that registered the handler for this event. We’ll look at the fields accessible on this object below.source
is the event source. Thebrig event create
command, for example, would set source tobrigade.sh/cli
.type
is the event type. A GitHub Pull Request event, for example, would set type topull_request
.payload
contains any information that the external service sent when triggering the event. For example, a GitHub push request generates a rather large payload. Payload is an unparsed string.worker
is the Brigade worker assigned to handle the event. Among other things, git details such as the commit (revision ID) and clone URL can be found on this object.
For a full overview of the event object supplied by the brigadier library, see the Brigadier documentation.
The Project
The project object (event.project
) gives us the following fields:
id
is the project ID.secrets
is the key/value map of secrets defined on the project. These are set viabrig secret set
(see the Secrets Guide for more info).
Using Event and Project Objects
Let’s look at some examples that utilizes event and project data.
The first example extracts the payload from the event object and logs it to the console:
const { events } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
console.log("Hello, " + event.payload + "!");
});
events.process();
When we create an event with a payload for the script above, we’ll see output like this:
$ brig event create --project first-payload --payload "Brigade" --follow
Created event "05e31d97-945b-4727-b710-7d983d137d40".
Waiting for event's worker to be RUNNING...
2021-09-23T16:25:29.761Z INFO: brigade-worker version: 927850b-dirty
Hello, Brigade!
We can update the example to print the project ID as well.
const { events } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
console.log("Project: " + event.project.id);
console.log("Hello, " + event.payload + "!");
});
events.process();
The following output is generated:
$ brig event create --project first-payload --payload "Brigade" --follow
Created event "b45720c4-115c-4c9b-b668-a872479f2210".
Waiting for event's worker to be RUNNING...
2021-09-23T16:30:29.267Z INFO: brigade-worker version: 927850b-dirty
Project: first-payload
Hello, Brigade!
Note that some event and project data should be treated with care. Things like
event.project.secrets
or event.worker.git.cloneURL
might not be the sorts
of information you want accidentally displayed. Check out the Secrets guide
for examples on how to safely handle project secrets in your scripts.
Worker storage and shared workspace
Brigade offers the ability to set up storage for the worker that can then be
shared amongst jobs. This functionality isn’t enabled by default and needs to
be configured on the workerTemplate
section of the project definiton as well
as on each job in the script that requires access to the workspace.
An example demonstrating use of a shared workspace
First, the useWorkspace
field on the workerTemplate
of the project
definition must be set to true
:
workerTemplate:
useWorkspace: true
Next, for each job to use the shared workspace, provide a value for the
workspaceMountPath
field on the job’s primaryContainer
:
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job1 = new Job("first-job", "debian", event);
job1.primaryContainer.workspaceMountPath = "/share";
job1.primaryContainer.command = ["bash"];
job1.primaryContainer.arguments = ["-c", "echo 'Hello!' > /share/message"];
await job1.run();
let job2 = new Job("second-job", "debian", event);
job2.primaryContainer.workspaceMountPath = "/share";
job2.primaryContainer.command = ["cat"];
job2.primaryContainer.arguments = ["/share/message"];
await job2.run();
});
events.process();
$ brig event create --project shared-workspace
Created event "2eee9044-4469-49bd-a58b-aa659951a502".
$ brig event logs --id 2eee9044-4469-49bd-a58b-aa659951a502 --job second-job
Hello!
Note: Shared storage is dependent on the underlying Kubernetes cluster and the availability of the correct type of storage class. See the Storage doc for more information on cluster requirements.
Sidecar containers
Jobs can optionally be configured with one or more sidecar containers, which run alongside the job’s primary container. All sidecar containers will be terminated a short time after the job’s primary container completes. A few additional notes:
Regardless of whether sidecar containers are present, job success or failure is still determined solely upon the exit code of its primary container.
All the containers are networked together such that processes listening for network connections in any one of them can be addressed by processes running in the others using the local network interface.
Brigade does not understand the relationship(s) between your containers and therefore cannot coordinate startup. If, for instance, a process in the primary container should be delayed until some supplementary process in some sidecar container is up and running and listening for connections, then the script author needs to account for that themselves.
As an example, consider an event handler that needs to run tests but also needs to provision a backing database required by the tests. The backing database could run as a sidecar container while the job’s primary container is concerned with running the tests.
Another use case is a job that needs the Docker daemon. The safest way to do this is to run Docker-in-Docker, i.e. starting up the daemon within a container that can then be used as needed. A sidecar container is a perfect application for starting up the daemon whilst the job’s primary container handles the main tasks at hand.
Let’s take a look at this latter example and introduce further configuration needed for this scenario.
Docker-in-Docker
Docker-in-Docker (DinD) containers must run as privileged in order to function. This also needs to be allowed at the project level in order for jobs to run as privileged. The default is for this feature to be disallowed.
Here is an example project configuration allowing containers to run in privileged mode:
workerTemplate:
jobPolicies:
allowPrivileged: true
Each job container needing to run in this mode must also add explicit
configuration. In the example below, the docker
sidecar container has
privileged
set to true, while the primary container does not:
const { events, Job, Container } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job = new Job("dind", "docker:stable-dind", event);
job.primaryContainer.environment.DOCKER_HOST = "localhost:2375";
job.primaryContainer.command = ["sh"];
job.primaryContainer.arguments = [
"-c",
// Wait for the Docker daemon to start up
// And then pull the image
"sleep 20 && docker pull busybox"
];
// Run the Docker daemon in a sidecar container
job.sidecarContainers = {
"docker": new Container("docker:stable-dind")
};
job.sidecarContainers.docker.privileged = true
await job.run();
});
events.process();
Here’s the output from creating an event and then looking at the job logs:
$ brig event create --project dind --follow
Created event "94d0fcd5-61dc-49be-bb81-3e5784e66a4a".
Waiting for event's worker to be RUNNING...
2021-09-23T19:00:08.915Z INFO: brigade-worker version: 927850b-dirty
2021-09-23T19:00:09.428Z [job: dind] INFO: Creating job dind
$ brig event logs --id 94d0fcd5-61dc-49be-bb81-3e5784e66a4a --job dind
Using default tag: latest
latest: Pulling from library/busybox
24fb2886d6f6: Pulling fs layer
24fb2886d6f6: Verifying Checksum
24fb2886d6f6: Download complete
24fb2886d6f6: Pull complete
Digest: sha256:52f73a0a43a16cf37cd0720c90887ce972fe60ee06a687ee71fb93a7ca601df7
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
Note we could also take a look at the sidecar container logs on the job like so:
$ brig event logs --id 94d0fcd5-61dc-49be-bb81-3e5784e66a4a --job dind --container docker
time="2021-09-23T19:00:11.230907700Z" level=info msg="Starting up"
...
Accessing the host Docker socket
For security reasons, it is recommended that you use Docker-in-Docker (DinD) instead of using a host’s Docker socket directly.
However, each job also has the option to mount in a docker socket. When
enabled, the docker socket from the Kubernetes Node running the job Pod is
mounted to /var/run/docker.sock
in the job’s container. This is typically
required only for “Docker-out-of-Docker” (“DooD”) scenarios where the
container needs to use the host’s Docker daemon. This is strongly discouraged
for almost all use cases.
In order for the socket to be mounted, the Brigade project must have the
allowDockerSocket
field on the jobPolicies
section of its worker spec set
to true
. The default is false
, disallowing use of the host docker socket.
Example project configuration enabling this feature:
workerTemplate:
jobPolicies:
allowDockerSocketMount: true
Additionally, a job must declare that it needs a docker socket by setting
useHostDockerSocket
on its primaryContainer
to true:
const { events, Job } = require("@brigadecore/brigadier");
events.on("brigade.sh/cli", "exec", async event => {
let job = new Job("dood", "docker", event);
job.primaryContainer.useHostDockerSocket = true;
job.primaryContainer.command = ["docker"];
job.primaryContainer.arguments = ["ps"];
await job.run();
});
events.process();
Here’s the output when we create an event for the script above:
$ brig event create --project dood --follow
Created event "283be00c-5481-43ae-8634-bd9bd194488b".
Waiting for event's worker to be RUNNING...
2021-09-23T19:50:27.796Z INFO: brigade-worker version: cfa7e5e-dirty
2021-09-23T19:50:27.994Z [job: dood] INFO: Creating job dood
$ brig event logs --id 283be00c-5481-43ae-8634-bd9bd194488b --job dood
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
68ab8fa3f395 docker "docker ps" 1 second ago Up Less than a second k8s_dood_283be00c-5481-43ae-8634-bd9bd194488b-dood_brigade-2450aa5d-80be-442b-bdd2-425a4d85c3e9_e2bb2688-8362-4824-91a0-797d0396ba04_0
...
Note: Not all cluster providers use Docker as their container runtime. For example, KinD uses containerd and so the usual Docker socket is not available. Here we mention again that DinD is the preferred route when a Docker socket is necessary.
Conclusion
This guide covers the basics of writing Brigade scripts. Here are some links for further reading:
- If you’d like more details about the Brigadier JS/TS API, take a look at the Brigadier docs
- For a more advanced script examples and techniques, see the Advanced Scripting Guide
- Peruse the other example projects/scripts in the Examples directory. There are example projects using npm, yarn, TypeScript and more.
Happy Scripting!