#!/usr/bin/env bash # Builds didbot-pds for linux/amd64 from a commit's tree and pushes it to # infra/pds's own ECR repository, tagged by that commit's short sha and, # optionally, by a second tag -- a release name, most often. # # scripts/publish-image.sh # HEAD, tagged by its short sha # scripts/publish-image.sh # any commit-ish # scripts/publish-image.sh # also tagged # # infra/pds/variables.tf's container_image_tag is the tag to apply that # stack with once a push has landed -- this prints it at the end. # # There is no CI job that does this: the runner .tangled/workflows/ci.yml # describes holds no AWS credential and has no safe way to hold one, so # publishing an image is an operator's own command, run with their own # credentials, same as scripts/publish-site.sh and scripts/release.sh # elsewhere in this project are. # # Credentials are the caller's own -- the environment or a profile: this # script does not assume which, only that `aws sts get-caller-identity` # answers. set -euo pipefail cd "$(dirname "$0")/.." stack=infra/pds usage() { echo "usage: scripts/publish-image.sh [] []" >&2 } rev="" release_tag="" for arg in "$@"; do case "$arg" in -h | --help) usage exit 0 ;; -*) usage exit 2 ;; *) if [ -z "$rev" ]; then rev="$arg" elif [ -z "$release_tag" ]; then release_tag="$arg" else usage exit 2 fi ;; esac done rev="${rev:-HEAD}" say() { printf '\033[1m==> %s\033[0m\n' "$*" >&2; } for tool in docker git aws tofu; do command -v "$tool" >/dev/null || { echo "publish-image: $tool is required and was not found on PATH" >&2 exit 1 } done # The release is the commit, so an image can be traced back to what built # it. `git archive` below is what keeps a dirty working tree out of it: only # what this sha actually holds is sent to the builder. sha="$(git rev-parse --verify --quiet --short=12 "$rev^{commit}")" || { echo "publish-image: $rev is not a commit" >&2 exit 1 } for path in Dockerfile Cargo.toml Cargo.lock; do git cat-file -e "$sha:$path" 2>/dev/null || { echo "publish-image: $path is not in $sha; only a commit carrying it can be published" >&2 exit 1 } done identity="$(aws sts get-caller-identity --output text --query Arn 2>&1)" || { echo "publish-image: AWS credential check failed:" >&2 echo " ${identity}" >&2 echo "publish-image: log in to the account this stack deploys to, then re-run." >&2 exit 1 } say "publishing as ${identity}" repository_url="$(tofu -chdir="$stack" output -raw ecr_repository_url 2>/dev/null)" || { echo "publish-image: no ecr_repository_url output from $stack." >&2 echo "publish-image: run \`tofu -chdir=$stack apply -target=aws_ecr_repository.pds\` first --" >&2 echo "publish-image: see docs/deployment.md's \"Bring-up\"." >&2 exit 1 } # repository_url is .dkr.ecr..amazonaws.com/; the # region is the fourth dot-separated field of the registry host. region="$(cut -d. -f4 <<<"${repository_url%%/*}")" aws ecr get-login-password --region "$region" \ | docker login --username AWS --password-stdin "${repository_url%%/*}" >/dev/null already_pushed() { aws ecr describe-images --region "$region" \ --repository-name "${repository_url#*/}" --image-ids "imageTag=$1" >/dev/null 2>&1 } if already_pushed "$sha"; then say "${repository_url}:${sha} is already in the registry" else work="$(mktemp -d -t didbot-publish-image-XXXXXX)" trap 'rm -rf "$work"' EXIT git archive "$sha" | tar -x -C "$work" tags=(--tag "${repository_url}:${sha}") [ -n "$release_tag" ] && tags+=(--tag "${repository_url}:${release_tag}") say "building and pushing ${repository_url}:${sha} for linux/amd64" docker buildx build \ --platform linux/amd64 \ "${tags[@]}" \ --push \ "$work" fi if [ -n "$release_tag" ] && ! already_pushed "$release_tag"; then # The sha build above already pushed this tag when both were built # together; this only fires when the sha was already in the registry and # the release tag is new -- retagging an existing manifest rather than # rebuilding it. say "tagging existing ${repository_url}:${sha} as ${release_tag}" docker buildx imagetools create \ --tag "${repository_url}:${release_tag}" \ "${repository_url}:${sha}" fi say "published. Apply with:" echo " tofu -chdir=${stack} apply -var container_image_tag=${release_tag:-$sha}"