Skip to content

Actions

A hook can be a named action instead of a command line:

after:
- uses: honeycomb
with: { dataset: purchase }

A Honeycomb marker written as curl is six lines of flags, a hand-built JSON body, a header out of an environment variable and a || echo on the end so a failed annotation does not fail the release — and every value in it is something the tool already knows: the version, the service, the environment, the side.

So the set stays small and stays about what a deploy is — say a version went out, ask whether something answers — and cmd covers everything else.

An action can also refuse while there is still a plan to refuse. A marker whose API key is nowhere in the environment fails the plan, with the name of the variable, rather than turning up as a 401 from an after hook on a release that already succeeded.

Marks the deploy on a dataset.

after:
- uses: honeycomb
with:
dataset: purchase
endpoint: https://api.eu1.honeycomb.io
url: https://github.com/evolve-platform/evolve-reference-b2b/commit/{{.version}}
Option Default
dataset the dataset to mark; __all__ marks the whole environment required
message {{.name}} {{.version}}
type groups markers so they share a colour deploy
url what the marker links to, usually the commit none
endpoint https://api.honeycomb.io
key_env which variable holds the key HONEYCOMB_API_KEY

EU tenants need endpoint: https://api.eu1.honeycomb.io.

Registers the release and then the deploy of it — two things Sentry knows, because the same release is deployed to tst and later to prd, and only the second call differs.

after:
- uses: sentry
with:
org: evolve
commit: '{{.version}}'
repository: evolve-platform/evolve-reference-b2b
Option Default
org required
project / projects one, or several {{.name}}
version what Sentry calls a release {{.version}}
environment {{.env}}
repository + commit associates the release with what is in it none
endpoint https://sentry.io/api/0
key_env SENTRY_AUTH_TOKEN

An annotation that did not arrive is reported and forgiven. An after hook runs on a deploy that has already succeeded, and pulling a working version because a note about it went missing costs more than the missing note.

That is what the || echo on the end of every curl line was already doing — in one place instead of fourteen.

Asks for one url and says whether the answer was the expected one.

strategy:
smoke:
- uses: http
with: { url: '{{url_stage "site"}}/healthz', retry: 5, delay: 2s }
Option Default
url required
method GET
headers / body none
status the one status that counts as healthy any 2xx
timeout bounds one attempt 10s
retry further attempts after the first 0
delay between attempts 3s

It replaces the row of flags this was written as — --fail --silent --show-error --max-time --retry --retry-delay --retry-connrefused — where every one had to be remembered, and leaving off --fail meant a 500 walked through the gate.

Retrying is what makes it usable as a smoke test. A side that has staged is not always answering the instant staging returns, and the first refused connection is nearly always that rather than a broken deploy. It retries on a refused connection as much as on a bad status.

A failure reports the status and what the body said, which is where a health route explains itself.

Unlike the two above, this one can fail a release — that is the entire point of it.

uses: container-app-job, cloud-run-job, ecs-task

Section titled “uses: container-app-job, cloud-run-job, ecs-task”

Runs a job once on the version being released and waits for it to finish. One action under each cloud’s own name, the same as its target type:

Cloud Runs
Azure container-app-job a Container Apps job
GCP cloud-run-job a Cloud Run job
AWS ecs-task a one-off ECS task

A deploy file already speaks its cloud’s language — its targets are container-app or cloud-run or ecs — so a neutral name would buy a portability no deploy file has. The name for another cloud is refused while planning, with the one this cloud uses.

services:
wagtail:
version: abc1234
before:
- uses: container-app-job
with: { name: suz-tst-caj-migrate }
after:
- uses: container-app-job
with:
name: suz-tst-caj-tasks
command: [python, manage.py, loadperms]
Option Default
name the job, as Terraform named it required
command the whole command line, for this one run the job’s own
container which container, in a job with sidecars as for a target
version the image tag to run {{.version}}
timeout how long the run may take before it is stopped, e.g. 15m none
base ecs-task only: the family Terraform registers the task’s shape into <name>-base
target ecs-task only: the ecs target whose cluster and network it runs in the service’s own

The image is written onto the job before it runs. A migration has to run against what is about to go out, not what is serving — and on Cloud Run an execution cannot be handed an image of its own. The job keeps that version afterwards, and a second apply of the same release does not write it again.

A command is for one run. It replaces the job’s command line, arguments included, and Terraform’s goes back once the run is over, whether it worked or not. Without one the job runs what it was declared with.

The job is Terraform’s. Its environment, secrets, identity and timeout are left alone, and a job that does not exist is refused while planning rather than created.

A timeout stops the run. Past it the execution is stopped — not walked away from, since a migration still running while the release goes ahead is what the timeout is there to prevent — and the hook fails. Without one the job’s own timeout is the only one: Container Apps and Cloud Run fail an execution when it passes.

ECS has no job resource, so a job there is a task definition family and a RunTask — what before_deploy was in ecs-deplojo:

before:
- uses: ecs-task
with:
name: ask-migrate
container: uwsgi
command: [python, manage.py, migrate, --noinput]
  • The shape is Terraform’s, registered into ask-migrate-base the same way as for an ecs target. Each run registers a revision of ask-migrate from it with the release’s image and, if given, the command — RunTask can override neither — so there is nothing to put back. Applied twice, the revision that is already there is reused.
  • The network is the service’s. The task starts in the cluster, subnets, security groups and launch type or capacity providers of the service’s own ecs target, which is the network that already reaches the database. A service with none or several, and a job in strategy.smoke, names one with target.
  • The verdict is the exit code of the container that ran, not ECS’s stopped reason, which is the same for a pass and a failure. A failure says where the awslogs stream is.
  • ECS has no task timeout, so here timeout is the only one there is. Without it a migration that hangs waits until the pipeline gives up.

base and target are refused on container-app-job and cloud-run-job, where the job is a resource of its own and needs neither.

In before a failed run calls the release off like any other hook. In after it is reported and rolls nothing back. A successful run prints nothing; a failed one names the execution, and on Cloud Run links to its logs.

smoke:
- npm run smoke -- --base-url {{url_stage "site"}}

The tool has no opinion about your test suite and does not want one.