#!/usr/bin/env bash # Push the match image and point infra at it. # # ./scripts/deploy.sh # # Runs the build and push, then writes the tag it was named after into infra's # releases.auto.tfvars.json. The result is one uncommitted infra diff; applying # it is the deploy, and this script does not apply anything. # # The headquarters repo has the same script for its two artifacts. This one # differs in two ways, both because arena ships a single image: the release id # is the full image tag rather than the build ref, and there is no separate # fail-fast build - scripts/push.sh builds before it touches the registry, and # with one artifact there is no half-finished state for a second build to # protect against. # # INFRA_DIR overrides where the infra checkout is; the default is the sibling # directory, which is how the repos are laid out. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" # Everything below fails before anything is built. A wasted image build is # fifteen minutes, and every one of these is knowable up front. : "${AWS_PROFILE:?set AWS_PROFILE; default credentials are almost never the right account}" # The variable being set says nothing about the session behind it. Ask STS # now, so an expired login fails here instead of after the image build, when # the push is the first thing to touch AWS. if ! aws sts get-caller-identity --query Account --output text >/dev/null; then echo "deploy: no working AWS session for profile $AWS_PROFILE; log in again" >&2 exit 1 fi command -v jq >/dev/null || { echo "deploy: jq is not installed; it writes releases.auto.tfvars.json" >&2 exit 1 } if ! infra="$(cd "${INFRA_DIR:-../infra}" 2>/dev/null && pwd)"; then echo "deploy: no infra checkout at ${INFRA_DIR:-../infra}; set INFRA_DIR" >&2 exit 1 fi tfvars="$infra/envs/lance.blue/releases.auto.tfvars.json" [ -f "$tfvars" ] || { echo "deploy: $infra has no envs/lance.blue/releases.auto.tfvars.json" >&2 exit 1 } # A dirty tree gets a -dirty ref, which names a build nobody else can rebuild. # push.sh refuses to push one; catching it here saves the build as well. if [ -n "$(git status --porcelain)" ]; then echo "deploy: arena has uncommitted changes; commit or stash them" >&2 exit 1 fi # Deploying off a branch is fine: the branch name is part of the image tag, so # what is serving is legible from the tag alone. main is the one branch with a # rule, because a main build is meant to be exactly what the remote has. Ask # the remote rather than trusting whatever the last fetch left behind. branch=$(git rev-parse --abbrev-ref HEAD) if [ "$branch" = main ]; then git fetch --quiet origin main if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]; then echo "deploy: on main, but HEAD is not origin/main; pull or push first" >&2 exit 1 fi fi # Nothing here cares whether infra is dirty. arena and headquarters each write # their own keys in the same file, so the second one to deploy would always # find the first one's bump sitting there uncommitted. infra's own deploy.sh is # where a clean tree is required. # versions.env is what build.sh and push.sh name the image from, so sourcing it # here is how this script knows what tag to expect back. It computes BUILD_REF # from git, which the guards above have already pinned down. # shellcheck source=../versions.env . "$ROOT/versions.env" echo "deploy: $IMAGE_TAG -> $AWS_PROFILE" echo # What the tfvars file gets is read back out of the push rather than assumed, # so a change to how the image is named fails here instead of pointing # production at a tag that does not exist. log="$(mktemp)" trap 'rm -f "$log" "$tfvars.new"' EXIT IMAGE_TAG="$IMAGE_TAG" BUILD_REF="$BUILD_REF" scripts/push.sh 2>&1 | tee "$log" # "pushed /arena:", and push.sh now emits exactly one such line. # The `latest` rejection below is kept anyway: it is the check that would catch # a moving tag being pushed again, which is the mistake worth failing on. line="$(grep -E '^pushed ' "$log" | tail -n 1 || true)" pushed="${line##*:}" if [ -z "$pushed" ] || [ "$pushed" = latest ]; then echo >&2 echo "deploy: could not read an immutable tag out of the push" >&2 echo "deploy: $tfvars not touched" >&2 exit 1 fi if [ "$pushed" != "$IMAGE_TAG" ]; then echo >&2 echo "deploy: pushed $pushed, expected $IMAGE_TAG" >&2 echo "deploy: $tfvars not touched" >&2 exit 1 fi # Written beside the file it replaces so the move is atomic; a jq that fails # halfway leaves the current release id in place. jq --arg tag "$IMAGE_TAG" '.releases.arena = $tag' "$tfvars" >"$tfvars.new" mv "$tfvars.new" "$tfvars" echo echo "Wrote releases.arena = $IMAGE_TAG" echo "To serve it:" echo " tofu -chdir=$infra/envs/lance.blue plan -out=lance.blue.tfplan" echo " tofu -chdir=$infra/envs/lance.blue apply lance.blue.tfplan"