2023-03-15 08:21:42 +00:00
# Albatros
A lightweight and (quasi-)stateless CI built on top of Nomad.
Our main principle: offload as much work to Nomad as possible.
2023-03-15 09:08:18 +00:00
We don't want to build an abstraction on top of it and hide
Nomad internals, but instead expose them as directly and transparently
as possible, so that you can benefit from all the features of this software.
**Albatros is a specialized CI for Nomad**
When we can't offload our work directly to Nomad, we should consider
offloading it to Gitea, Consul, and the others. At a last resort,
we might do it in Albatros...
2023-03-15 08:21:42 +00:00
2023-04-28 16:20:21 +00:00
## Builds
```
go build -tags containers_image_docker_daemon_stub,containers_image_storage_stub,containers_image_openpgp bin/alba.go
```
2023-03-15 08:21:42 +00:00
## Deploy
2023-03-15 14:34:52 +00:00
Requirements: Nomad, Consul, Gitea
2023-03-15 09:16:49 +00:00
2023-03-22 15:33:51 +00:00
Prepare your nomad cluster:
2023-03-15 08:21:42 +00:00
```
nomad namespace apply -description "Continuous Integration" ci
nomad run hcl/builder.hcl
```
2023-03-22 15:33:51 +00:00
Run from the terminal directly:
```
export ALBATROS_URL="https://albatros.deuxfleurs.fr"
export NOMAD_ADDR=...
export NOMAD_CLIENT_CERT=...
export NOMAD_CLIENT_KEY=...
export NOMAD_CACERT=...
export CONSUL_HTTP_ADDR=...
export CONSUL_CLIENT_CERT=...
export CONSUL_CLIENT_KEY=...
export CONSUL_CACERT=...
./albatros
```
Run from docker:
```
docker run --rm -it dxflrs/albatros:xxx
```
where `xxx` is the commit sha you want.
2023-03-24 11:14:33 +00:00
## Bring Your Own Builder
One aspect that I don't like with traditional CI is that the way you can
configure builds is very constrained by the vendor implementation choices.
Like it or not, but Albatros has very few constraints in term of what build infrastructure should look like:
- It must be a Nomad parameterized job
- Your job must accept four mandatory meta parameters: `REPO_URL` , `COMMIT` , `BRANCH` , `FLAVOR`
- Your job may receive a payload containing the secrets stored in your job definition (subject to change)
- It must have have a task group named `runner` and, inside this group, a task named `executor`
And... that's basically all you need.
### A NixOS builder with local cache
2023-03-22 15:33:51 +00:00
2023-03-24 16:04:19 +00:00
In the `hcl/` folder, you will find the definition of a builder named `nixcache` .
This builder has the following features:
- It creates a persisted nix store at `/var/cache/albatros` on your host if it does not exist yet.
- The nix store is mounted read-only during the build
- A nix daemon is run in a separate container, you can interact with it though its unix socket (everything is already setup for you). Simply run `nix build` .
- All your builds are automatically added as a nix gcroots by the nix daemon, `keep_derivation` is activated so your build dependencies are kept locally.
- At the end, all gcroots older than 7 days are deleted, and then the `nix store gc` is called effectively wiping data that were previously attached to old gcroots.
2023-03-22 15:33:51 +00:00
2023-03-15 09:08:18 +00:00
## Register a build
Add to Consul a key in albatros hierarchy
named after your repo URL as base64. Example:
```
albatros/aHR0cHM6Ly9naXQuZGV1eGZsZXVycy5mci9xdWVudGluL2FsYmF0cm9zLmdpdA==
```
2023-03-15 19:12:52 +00:00
The key must contain a JSON file with your desired token, gitea info, trust conditions, and secrets:
2023-03-15 09:08:18 +00:00
```json
{
2023-03-15 14:34:52 +00:00
"hook": {
"token": "s3cr3t"
},
"gitea": {
"url": "https://git.deuxfleurs.fr",
"token": "c0ffee..."
},
"trusted": {
"senders": [ "quentin", "lx" ]
},
2023-05-04 13:16:57 +00:00
"inject": "set +x\nexport SECRET1=xx\nexport SECRET2=yy"
2023-03-15 09:08:18 +00:00
}
```
2023-03-15 19:12:52 +00:00
*Register an access token if Gitea for your Albatros by creating an application token.
The URL is `/user/settings/applications` . Name your app `albatros` for example, the token will be displayed once in light blue.
Put it in the JSON file.*
2023-03-15 09:28:09 +00:00
Your secret will be injected in your build environment only
when trustig condition are matched. It wil be available in a dedicated
file. Its path is communicated through an environment variable (see below).
For now, we can only check that based on sender's login.
2023-03-15 09:08:18 +00:00
Then you can trigger a build as follow:
```
$ curl -d @example/albatros .json http://localhost:8080/hook?token=s3cr3t& flavor=default
builder/dispatch-1678866433-15aad86a
```
You need to pass your token, and you can optionally pass a flavor, that can
be used later by your build script.
As you can see, you now have an identifier representing your job, you
can use it to follow your logs (don't forget to urlencode it):
```
2023-03-15 10:37:01 +00:00
$ curl http://localhost:8080/build?job=builder%2Fdispatch-1678866433-15aad86a& log=stderr
2023-03-15 14:34:52 +00:00
+ go build
...
2023-03-15 09:08:18 +00:00
```
Of course, most of that will be handled by Gitea.
2023-03-15 09:12:30 +00:00
## Writing your build script
You must create an executable `.albatros` file at the root of your repository.
Then, you can use the interpreter you want to execute it, let use `bash`
as an example, but please use python, javascript, or anything else that has
proper error handling...
```bash
#!/usr/bin/env bash
echo "Building commit $COMMIT"
go build
```
During the build, the following environment variables are available:
```bash
REPO_URL=https://git.deuxfleurs.fr/quentin/albatros.git
COMMIT=3fff73597f8ca18ef04c0d9bf64132ba55aadcaa
BRANCH=main
FLAVOR=default
2023-03-15 09:28:09 +00:00
SECRET_PATH=/var/run/secrets/albatros/secret.txt
2023-03-15 09:12:30 +00:00
```
2023-03-15 09:08:18 +00:00
## Security model
Albatros only tries to protect your secrets.
To achieve that, we only inject them in the build script
if the job has been `sent` (triggered) by a trusted sender.
This decision is taken by looking at the webhook payload content.
To protect against sender impersonification, your webhook
must be called only by trusted code, and more generally, your token
must remain secret.
We assume otherwise that anyone can trigger the webhook
through Gitea while replacing the content of the build script
by a malicious software. Signing the build script is tempting
but it will not prevent someone to put its malicious code, for example
in a Rust project, in the `build.rs` file. So you must
assume that your CI will execute untrusted code.
To protect against undesired code execution,
you must harden your environment, for example by using
VMs instead of containers, timeouts, and restricting
some IO. All of that must be handled by Nomad. Also,
be careful to the local network in which your workload
will be executed.
2023-03-15 19:04:57 +00:00
## Roadmap
2023-03-15 08:21:42 +00:00
2023-03-15 19:04:57 +00:00
Don't get me wrong, the 1.0 version
is not ambitious at all and will have to
few features for many of you. Still,
I think it will be enough for us.
### v1.0
2023-03-15 19:07:22 +00:00
See this v1.0 as a MVP that will serve Deuxfleurs needs,
nothing more. Don't have any expectation in term
of code quality, abstraction or anything else.
2023-03-16 09:46:07 +00:00
- [X] Read Nomad+Consul config from environment variables
2023-03-16 09:18:52 +00:00
- [X] Inject secrets only when the sender is trusted
2023-03-15 19:04:57 +00:00
- [ ] Test PR behavior
2023-05-04 13:16:57 +00:00
- [ ] Handle tags
2023-03-15 19:04:57 +00:00
### Ideas
Ideas are just... ideas. They are pure speculation,
and if they are here, it means that will and time
to implement them has not been found yet...
2023-03-15 19:07:22 +00:00
- [ ] Validate the gitea payload with hmac
- [ ] Refactor the code
2023-03-15 08:21:42 +00:00
- [ ] Register the builder programatically
2023-03-15 09:12:30 +00:00
- [ ] Allow users to define their own set of builders (ones with more CPU+RAM, etc.)
2023-03-15 10:45:51 +00:00
- [ ] Allow users to choose their image/rootfs
2023-03-15 14:34:52 +00:00
- [ ] Make Gitea optional
2023-03-15 19:04:57 +00:00
- [ ] Your idea