Files

516 lines
20 KiB
Markdown

---
title: Hack
---
## Contributing
Welcome to Hacking the Planet with `abra`! We're looking forward to see what you come up. If you have any questions, don't hesitate to ask 💖 However, please keep in mind that if any of your changes seems a bit controversial, it's probably best to come have a chat first to avoid heartache.
In general, we're into the idea of "Optimistic Merging" (instead of "Pessimistic Merging") based on our understanding of [C4](https://hintjens.gitbooks.io/social-architecture/content/chapter4.html) (described further down under "Development Process" and also [in this blog post](http://hintjens.com/blog:106)).
In other words, we're happy to grant contributors "the commit bit" (read/write permissions on our shared Git repositories) more or less as soon as you start to submit changes, write recipes, organise or in general, help out in the project. You don't have to prove anything, we can work and learn together! Mistakes are allowed and there are no "stupid questions".
We maintain a "team" called "Co-operators" on our 2 main repositories:
* [`git.coopcloud.tech/org/toolshed`](https://git.coopcloud.tech/toolshed/)
* [`git.coopcloud.tech/org/coop-cloud`](https://git.coopcloud.tech/coop-cloud/)
This gives you read/write access to all the repositories of the organisation.
Any existing contributor can add you.
## Quick start
Get a fresh copy of the `abra` source code from [here](https://git.coopcloud.tech/toolshed/abra).
Install [direnv](https://direnv.net), run `cp .envrc.sample .envrc`, then run `direnv allow` in this directory. Or you can run `go env -w GOPRIVATE=coopcloud.tech` but I'm not sure how persistent this is.
Install [Go >= 1.16](https://golang.org/doc/install) and then:
- `make build` to build. If this fails, run `go mod tidy`.
- `./abra` to run commands
- `make test` will run tests
- `make install` will install abra to `$GOPATH/bin`
- `go get <package>`, `go mod tidy` and `go mod vendor` to add a new dependency
Our [Drone CI configuration](https://git.coopcloud.tech/toolshed/abra/src/branch/main/.drone.yml) runs a number of checks on each pushed commit. See the [Makefile](https://git.coopcloud.tech/toolshed/abra/src/branch/main/Makefile) for more handy targets.
Please use the [conventional commit format](https://www.conventionalcommits.org/en/v1.0.0/) for your commits so we can automate our change log.
### Super quick-start (Ubuntu Server)
```bash
cd
sudo apt update && DEBIAN_FRONTEND=noninteractive sudo apt install -y golang make git
git clone https://git.coopcloud.tech/toolshed/abra.git && cd abra
make build
mkdir -p ~/.local/bin/
ln -sF $PWD/abra ~/.local/bin/abradev
if [[ "$PATH" != *".local/bin"* ]]; then export PATH="$PATH:~/.local/bin/"; echo 'export PATH=$PATH:~/.local/bin' >> ~/.bashrc; fi
# Set up Spanish auto-completion
LANG=es abra autocompletar bash | sed 's/abra/abradev/g' | sudo tee /etc/bash_completion.d/abra-dev
abradev --help
```
## Unit tests
### Run tests
Run the entire suite.
```
make test
```
### Filter tests
Run a specific test.
```
go test ./pkg/recipe -v -run TestGetVersionLabelLocalDoesNotUseTimeoutLabel
```
## Integration tests
### Running on the CI server
Based on [R020](https://docs.coopcloud.tech/federation/resolutions/passed/020/), we have automated running the integration test suite. Here's the TLDR;
* We have a donated CI server (tysm `@mirsal` 💝) standing at the ready, `int.coopcloud.tech`.
* We run the entire integration suite nightly via our Drone CI/CD configuration [here](https://git.coopcloud.tech/toolshed/abra/src/branch/main/.drone.yml) (see "`name: integration test`" stanza)
* Here is the script that is run on the remote server: [`run-ci-int`](https://git.coopcloud.tech/toolshed/abra/src/branch/main/scripts/tests/run-ci-int)
What follows is a listing of how this was achieved so that we can collectivise the maintenance.
On the server, we have:
* Created an `abra` user with `docker` permissions
* Ran `apt install bats bats-file bats-assert bats-support jq make git golang-1.21 wget bash`
* Installed `bats-core` from source, following the instructions below
* Docker was already installed on the machine, so nothing to do there
* `docker login` with the `thecoopcloud` details so we don't get rate limited
The drone configuration was wired up as follows:
* Generated a SSH key and put the public key part in `~/.ssh/authorize_keys`
* Added that public key part as a "deploy key" in the abra repo (so we can do `ssh://` git remote pulls)
* Added the private key part as a Drone secret which is available in build so that the build can SSH over to the server to run commands. That was done like so: `drone secret add --repository toolshed/abra --name abra_int_private_key --data @id_ed25519`
* In order to specify a cron timing, you need to create it with the Drone CLI: `drone cron add "toolshed/abra" "integration" @daily --branch main`
Please ask `@decentral1se` or on the Matrix channels for SSH access to the machine.
### Running manually on the CI server
It's convenient to be able reproduce the CI server environment yourself by SSHing to the machine and running the integration tests. Here's how you do it.
SSH config details:
```
Host int.coopcloud.tech
Hostname 51.159.168.99
User root
Port 22
IdentityFile ~/.ssh/<private-key-part>
```
Once you're in, you can run the following:
```
sudo -su abra
cd
tmux ls # if there is a session, run: tmux attach
# this way, we don't crash into each other
# when we're running tests
./run-ci-int
```
You can also `cd abra` and run `bats ...` directly to trigger specific subsets
of tests. You'll need to export the env vars at the bottom of the `run-ci-int`
script to reproduce the same settings.
```
export ABRA_DIR="$HOME/.abra_test"
export TERM=xterm
export TEST_SERVER=default
export ABRA_CI=1
```
And then ensuring a clean state and running with the same flags:
```
rm -rf "$ABRA_DIR"
bats -Tp tests/integration --filter-tags \!dns --print-output-on-failure
```
See the [`run-ci-int`](https://git.coopcloud.tech/toolshed/abra/src/branch/main/scripts/tests/run-ci-int) script for more.
See below for more tips on how to run the tests.
### Running them locally
#### Install dependencies
We use [`bats`](https://bats-core.readthedocs.io/en/stable/) to run the tests. You can install the required dependencies with the following. You also need a working installation of Docker and Go >= 1.16 (not covered in this section).
##### Fedora
```
sudo dnf install bats
```
Unfortunately, the Fedora `bats` package doesn't include the libraries we need, so we need to clone those manually:
```
mkdir -p ~/.local/share/bats/
cd ~/.local/share/bats
git clone https://github.com/bats-core/bats-assert.git
git clone https://github.com/bats-core/bats-file.git
git clone https://github.com/bats-core/bats-support.git
```
Then, before running tests, set `export BATS_LIB_PATH=~/.local/share/bats/`
##### Debian
```
apt install bats bats-file bats-assert bats-support jq make git
```
#### Setup Test Server
For some tests an actual server is needed, where apps can be deployed. You can either use a local one or a remote test server. There is also a way to run or skip tests that require a remote server. This is covered below in the [filtering tests](#filter-tests_1) section.
##### Remote swarm
!!! warning
Right now, the test suite will check for a local swarm, see [abra/#769](https://git.coopcloud.tech/toolshed/abra/issues/769)
```
export TEST_SERVER="test.example.com"
export ABRA_DIR="$HOME/.abra_test"
```
There should also be a DNS A record for `*.test.example.com` which points to the same server so that the test suite can deploy apps freely. The test suite does not deploy Traefik for you.
##### Local swarm
!!! note
This is currently necessary even if you only want to run tests which don't need a server
When running the test suite locally you need a running docker swarm setup:
```
docker swarm init
docker network create -d overlay proxy
```
To use the local swarm set the following env var:
```
export TEST_SERVER=default
export ABRA_DIR="$HOME/.abra_test"
```
Make sure that the user running the tests can access the docker socket e.g. by adding it to the `docker` group (security considerations apply).
### Run tests
!!! note
You need to remember to compile your current version of `abra` via `make` before running the tests. See [abra/#770](https://git.coopcloud.tech/toolshed/abra/issues/770)
Now you can run the whole test suite:
```
bats -Tp tests/integration
```
Or you can run a single test file:
```
bats -Tp tests/integration/app_check.bats
```
### Tagging tests
When a test actually deploys something, we tag it as "slow". When the test requires public DNS, we use "dns". There may be more tags we write more tests.
```
# bats test_tags=slow,dns
@test "..." {
...
}
```
Then we can use [filters](#filter-tests) (see below) to pick out a subset of tests which do/do not use a live server. Feel free to come up with your own tags. See the `bats-core` [docs](https://bats-core.readthedocs.io/en/stable/writing-tests.html#tagging-tests) for more.
### Filter tests
You can run a specific file.
```
bats -Tp tests/integration/app_check.bats
```
For example, if you want to check that all `abra recipe ...` tests remain working.
```
bats -Tp tests/integration/recipe_*
```
You can filter on test names to run specific kinds of tests.
```
bats -Tp tests/integration --filter "validate app argument"
```
You can filter on tags.
```
bats -Tp tests/integration --filter-tags \!slow # only fast tests
bats -Tp tests/integration --filter-tags slow # only slow tests
bats -Tp tests/integration --filter-tags slow,\!dns # slow but no DNS tests
```
You can also only run the previously failed tests.
```
mkdir -p tests/integration/.bats/run-logs
bats -Tp tests/integration # run tests
bats -Tp tests/integration --filter-status failed # re-run only failed
```
### Debug tests
If you're running into issues and want to debug stuff, you can pass `-x` to `bats` to trace all commands run in the test. You can add `echo '...' >&3` debug statements to your test to output stuff also.
## Internationalisation (`i18n`)
`abra` can be translated into other languages. We use a combination of [`gettext`](https://www.gnu.org/software/gettext/), [`weblate`](https://translate.coopcloud.tech) and some [intermediate automation](https://git.coopcloud.tech/toolshed/abra/src/commit/20909695e0e05c6251029dba270b3d4741aeb7a8/.drone.yml#L10-L29) to help developers and translators work together conveniently.
### Developer workflow
You just hack on `abra` as you normally would.
If you need to add a string, use `i18n.G` to wrap it. See [`gotext`](https://github.com/leonelquinteros/gotext) for the full API.
For example.
```go
i18n.G("my string")
i18n.G("my string with err: %s", err)
log.Debug(i18n.G("my string"))
log.Info(i18n.G("my string with err: %s", err)) # N.B no log.Infof usage here
```
Then you need to update the `pkg/i18n/locales/abra.pot` file with your new strings for the translators.
```bash
apt install -y gettext
go install -v -x github.com/snapcore/snapd/i18n/xgettext-go@2.57.1
make i18n
```
Commit the changes. Ignore `*.mo` changes if they only update the generation timestamp.
#### Resolving a merge conflict
```
git remote add weblate https://translate.coopcloud.tech/git/co-op-cloud/abra/
git remote update weblate
git merge weblate/main
```
Once you've resolved the conflict and pushed it, you'll need admin permissions on the Weblate repository to unlock it.
### Translator workflow
You can translate strings on [Weblate (`translate.coopcloud.tech`)](https://translate.coopcloud.tech).
It's also possible to translate using [`poedit`](https://poedit.net). Weblate is the recommended approach.
All translation files are located in [`pkg/i18n/locales`](https://git.coopcloud.tech/toolshed/abra/src/branch/main/pkg/i18n/locales). Once translations are updated in weblate, they will be incorporated into the next release of `abra` automatically.
In general, the workflow for translators is:
- Code freeze announced for `abra` before release. Strings are not updated by developers and merge conflicts are avoided.
- Translation work proceeds on [`translate.coopcloud.tech/projects/co-op-cloud/abra`](https://translate.coopcloud.tech/projects/co-op-cloud/abra/)
- Translators ensure that the latest translation changes are synchronised to the `abra` git repository via [the settings](https://translate.coopcloud.tech/projects/co-op-cloud/abra/#repository)
- Translators and developers will [install `abra` locally](/abra/hack/#super-quick-start-ubuntu-server) and test the latest changes
- If all is well, we can release `abra`
### Updating `abradev`
If you followed the [Super quick-start instructions](/abra/hack/#super-quick-start-ubuntu-server) to install `abra`, then these are the instructions you need to update your local `abra` to get the latest changes from Weblate. Make sure to check that the latest Weblate changes are synchronised with the `abra` repository.
```
cd
cd abra
git pull origin main
make build
```
### End-user workflow
You simply export the `LANG` env var to match your desired translation.
```
export LANG=es
abra -h
```
## Using the `abra` public API
Warning, there is currently no stability promise for the `abra` public API! Most of the internals are exposed in order to allow a free hand for developers to try build stuff. If people start to build things then we can start the discussion on what is useful to have open/closed and keep stable etc. Please let us know if you depend on the APIs!
The `pkg.go.dev` documentation is [here](https://pkg.go.dev/coopcloud.tech/abra). Here's a brief example to get you going:
```go
package main
import (
"context"
"fmt"
"log"
abraClient "coopcloud.tech/abra/pkg/client"
dockerClient "github.com/docker/docker/client"
)
func getClient(serverName string) (*dockerClient.Client, error) {
cl, err := abraClient.New(serverName)
if err != nil {
return nil, fmt.Errorf("getClient: %s", err)
}
return cl, nil
}
func main() {
cl, err := getClient("foo.example.com")
if err != nil {
log.Fatal(err)
}
// do stuff with the client...
// https://pkg.go.dev/github.com/docker/docker/client
}
```
Some tools that are making use of the API so far are:
* [`kadabra`](https://git.coopcloud.tech/toolshed/kadabra)
## Cross-compiling
If there's no official release for the architecture you use, you can cross-compile `abra` very easily. Clone the source code from [here](https://git.coopcloud.tech/toolshed/abra) and then:
- enter the `abra` directory
- run `git tag -l` to see the list of tags, choose the latest one
- run `git checkout <tag>`, where `<tag>` is the latest version
- run `GOOS=<os> GOARCH=<arch> [GOARM=<arm>] make build`. You only have to use `GOARM` if you're building for ARM, this specifies the ARM version (5,6,7 etc). See [this](https://go.dev/doc/install/source#environment) for a list of all supported OS'es and architectures.
## Building in Docker
If you are living under a curse of constant Go environment problems, it might be easier to build `abra` using Docker:
```
sudo setenforce 0 # SELinux probably won't allow Docker to access files
docker run -it -v $PWD:/abra golang:1.19.6 bash
cd /abra
. .envrc
git config --global --add safe.directory /abra # work around funky file permissions
make build
```
## Release management
We use [goreleaser](https://goreleaser.com) to help us automate releases. We use [semver](https://semver.org) for versioning all releases of the tool. While we are still in the public beta release phase, we will maintain a `0.y.z-beta` format. Change logs are generated from our commit logs. We are still working this out and aim to refine our release praxis as we go.
For developers, while using this `-beta` format, the `y` part is the "major" version part. So, if you make breaking changes, you increment that and _not_ the `x` part. So, if you're on `0.1.0-beta`, then you'd go to `0.1.1-beta` for a backwards compatible change and `0.2.0-beta` for a backwards incompatible change.
### Making a new release
- Run the [integration test suite](#integration-tests) and the unit tests (`make test`) (takes a while!)
- Change `ABRA_VERSION` in [`scripts/installer/installer`](https://git.coopcloud.tech/toolshed/abra/src/branch/main/scripts/installer/installer) to match the new tag (use [semver](https://semver.org))
- Commit that change (e.g. `git commit -m 'chore: publish next tag x.y.z-beta'`)
- Make a new tag (e.g. `git tag -a x.y.z-beta`)
- Push the new tag (e.g. `git push && git push --tags`)
- Wait until the build finishes successfully on [build.coopcloud.tech](https://build.coopcloud.tech/toolshed/abra)
- Set your `GITEA_TOKEN` in your `.envrc` and run `make release` to release `abra`
- Deploy the new installer script (e.g. `cd ./scripts/installer && make`)
- Clean up the changelog on the [releases
page](https://git.coopcloud.tech/toolshed/abra/releases) so it includes most
relevant changes. [See below](/abra/hack/#release-notes-text) for the
announcement text that needs to be inserted above the generated changelog.
You can use something like `git shortlog -e -s -n 0.XX.0-beta..HEAD` to get
the list of authors.
- Make sure there is [a migration guide](/abra/upgrade/#migration-guides)
written in the docs (if necessary)
- Check the release worked, (e.g. `abra upgrade; abra -v`)
- Share [the standard announcement](/abra/hack/#general-announcement-text) on
our General chat and [fedi account](https://social.coop/@coopcloud)
- Run away!
#### Release notes text
```
## Changelog
> 🎺🎺🎺 [`0.XX.x-beta` 👉 `0.XX.0-beta` **migration guide**](XXX) 🎺🎺🎺
`abra` update `HOWTO` documentation is [here](https://docs.coopcloud.tech/abra/upgrade/).
The project with all changes and discussions is [here](XXX).
A huge thanks to all our `abra` hackers for this release 💖
$git_shortlog_output
```
#### General announcement text
```
📢📢📢 abra v0.XX is finally here 📢📢📢
TLDR; `abra upgrade` 👍
Upgrade docs:
https://docs.coopcloud.tech/abra/upgrade/
Changelog:
https://git.coopcloud.tech/toolshed/abra/releases/tag/0.XX.0-beta
0.XX.x-beta 👉 0.XX.x-beta migration guide:
https://docs.coopcloud.tech/abra/upgrade/#XXx-beta-0XXx-beta
A huge thanks to everyone who helped get this release done ❤️‍🔥
Happy Hacking 🫂
-- $handle
```
## Fork maintenance
### `godotenv`
We maintain a fork of [godotenv](https://git.coopcloud.tech/toolshed/godotenv) because we need inline comment parsing for environment files. You can upgrade the version here by running `go get git.coopcloud.tech/toolshed/godotenv@0<COMMID>` where `<commit>` is the latest commit you want to pin to. See [`abra#391`](https://git.coopcloud.tech/toolshed/abra/pulls/391) for more.
### `docker/cli`
We maintain a fork of [docker-cli](https://git.coopcloud.tech/toolshed/docker-cli/) because we needed to [patch port exposing](https://git.coopcloud.tech/toolshed/docker-cli/commit/2fbb22f65866ac97b47e4d47d8f855fee8c98e2a) to fix this [bug](https://github.com/docker/cli/issues/2407). Once the [fix was upstreamed](https://github.com/docker/cli/pull/6799) we can remove this fork again.
### `docker/client`
A number of modules in [pkg/upstream](https://git.coopcloud.tech/toolshed/abra/src/branch/main/pkg/upstream) are copy/pasta'd from the upstream [docker/docker/client](https://pkg.go.dev/github.com/docker/docker/client). We had to do this because upstream are not exposing their API as public.
### `spf13/cobra`
Our command library doesn't support `i18n` so we need to implement a work-around specifically for translating the `--help` command. See [`spf13/cobra#2359`](https://github.com/spf13/cobra/issues/2359) for more.
### `github.com/schultz-is/passgen`
Due to [`toolshed/organising#358`](https://git.coopcloud.tech/toolshed/organising/issues/358).