#!/usr/bin/env bash # scripts/deploy.sh writes the release id production is pinned to, so the # thing worth testing is what it refuses. # # Every guard here fails before anything is built or pushed, which is also why # they can be tested at all: no docker, no AWS, no network. The one case that # gets past them uses a stub push.sh, which is enough to cover the two steps # that come after - reading the tag back out of the push, and the jq write. # # Not covered: the build and push themselves. Those need a daemon and a # registry, and ./scripts/test.sh is where that lives. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT pass=0; fail=0 check() { if "${@:2}"; then echo "ok $1"; pass=$((pass+1)); else echo "FAIL $1"; fail=$((fail+1)); fi; } # deploy.sh asks STS whether the session behind AWS_PROFILE works, and these # tests have no AWS. A stub answers for it, and STUB_STS_FAIL makes the stub # refuse - which is also how that guard gets tested. Survives setup(), which # only rebuilds the fake repos. mkdir -p "$TMP/bin" cat > "$TMP/bin/aws" <<'STUB' #!/usr/bin/env bash [ -z "${STUB_STS_FAIL:-}" ] || exit 255 echo 123456789012 STUB chmod +x "$TMP/bin/aws" export PATH="$TMP/bin:$PATH" # A fake arena beside a fake infra, which is the layout deploy.sh assumes. # Rebuilt for every case so one case cannot leave state for the next. setup() { rm -rf "$TMP/arena" "$TMP/infra" "$TMP/remote" mkdir -p "$TMP/arena/scripts" "$TMP/infra/envs/lance.blue" cp "$ROOT/scripts/deploy.sh" "$TMP/arena/scripts/deploy.sh" cp "$ROOT/versions.env" "$TMP/arena/versions.env" # Stands in for the real push: prints the line deploy.sh parses, in the # format scripts/push.sh prints it. cat > "$TMP/arena/scripts/push.sh" <<'STUB' #!/usr/bin/env bash echo "pushing local -> registry" [ -n "${STUB_ALSO_LATEST:-}" ] && echo "pushed 1.dkr.ecr.us-east-1.amazonaws.com/lance-blue/arena:latest" echo "pushed 1.dkr.ecr.us-east-1.amazonaws.com/lance-blue/arena:${STUB_TAG:-$IMAGE_TAG}" STUB chmod +x "$TMP/arena/scripts/push.sh" cat > "$TMP/infra/envs/lance.blue/releases.auto.tfvars.json" <<'JSON' { "releases": { "api": "main-keepme", "arena": "mm0.0.0-sur0.0.0-main-old", "site": "main-keepme" } } JSON git init --quiet --bare "$TMP/remote" git -C "$TMP/arena" init --quiet -b main git -C "$TMP/arena" config user.email t@t git -C "$TMP/arena" config user.name t git -C "$TMP/arena" add -A git -C "$TMP/arena" commit --quiet -m "arena" git -C "$TMP/arena" remote add origin "$TMP/remote" git -C "$TMP/arena" push --quiet -u origin main git -C "$TMP/infra" init --quiet -b main git -C "$TMP/infra" config user.email t@t git -C "$TMP/infra" config user.name t git -C "$TMP/infra" add -A git -C "$TMP/infra" commit --quiet -m "infra" } # Runs deploy.sh against the fake pair. Returns its exit status; output is # captured so a failing case does not spray the log. deploy() { ( cd "$TMP/arena" && env AWS_PROFILE=test INFRA_DIR="$TMP/infra" "$@" \ ./scripts/deploy.sh ) > "$TMP/out" 2>&1 } arena_tag() { jq -r '.releases.arena' "$TMP/infra/envs/lance.blue/releases.auto.tfvars.json"; } command -v jq >/dev/null || { echo "SKIP: jq is not installed"; exit 0; } # --- the guards ------------------------------------------------------------- setup ( cd "$TMP/arena" && env -u AWS_PROFILE INFRA_DIR="$TMP/infra" ./scripts/deploy.sh ) >/dev/null 2>&1 check "refuses to run without AWS_PROFILE" test $? -ne 0 setup deploy STUB_STS_FAIL=1 check "refuses when STS says the session is dead" test $? -ne 0 setup ( cd "$TMP/arena" && env AWS_PROFILE=test INFRA_DIR="$TMP/nope" ./scripts/deploy.sh ) >/dev/null 2>&1 check "refuses when the infra checkout is not there" test $? -ne 0 setup rm "$TMP/infra/envs/lance.blue/releases.auto.tfvars.json" deploy check "refuses when infra has no releases file" test $? -ne 0 setup echo dirt > "$TMP/arena/untracked" deploy check "refuses a dirty arena tree" test $? -ne 0 # main is the one branch that has to match the remote, and it has to match it # exactly: a main build is meant to be what everyone else already has. setup git -C "$TMP/arena" commit --quiet --allow-empty -m "not pushed" deploy check "refuses a main that is ahead of origin/main" test $? -ne 0 setup git -C "$TMP/arena" commit --quiet --allow-empty -m "pushed" git -C "$TMP/arena" push --quiet origin main git -C "$TMP/arena" reset --quiet --hard HEAD~1 deploy check "refuses a main that is behind origin/main" test $? -ne 0 # Any other branch deploys, pushed or not. The branch name is in the tag, so # what is serving is legible without asking the remote anything. setup git -C "$TMP/arena" checkout --quiet -b side git -C "$TMP/arena" commit --quiet --allow-empty -m "not pushed" deploy check "deploys an unpushed branch that is not main" test $? -eq 0 check " and the tag names the branch" \ bash -c '[[ "$1" == mm*-sur*-side-* ]]' _ "$(arena_tag)" # --- past the guards -------------------------------------------------------- setup deploy check "writes the pushed tag when everything is in order" test $? -eq 0 tag="$(arena_tag)" check " the tag is this commit's, not the old one" \ bash -c '[ "$1" != "mm0.0.0-sur0.0.0-main-old" ] && [[ "$1" == mm*-sur*-main-* ]]' _ "$tag" check " the other releases are untouched" \ bash -c 'jq -e ".releases.api == \"main-keepme\" and .releases.site == \"main-keepme\"" "$1" >/dev/null' _ \ "$TMP/infra/envs/lance.blue/releases.auto.tfvars.json" # arena and headquarters write different keys in the same file, so whichever # deploys second always finds the first one's bump sitting there uncommitted. setup echo dirt > "$TMP/infra/untracked" deploy check "deploys into an infra tree that is already dirty" test $? -eq 0 check " and writes the tag anyway" test "$(arena_tag)" != "mm0.0.0-sur0.0.0-main-old" # The moving tag must never reach a task definition. push.sh no longer pushes # one, so the stub here is a regression guard rather than a description of it: # if a `latest` push ever comes back, deploy.sh still refuses to write it. setup STUB_ALSO_LATEST=1 deploy check "ignores the latest tag and takes the immutable one" test $? -eq 0 check " latest is not what got written" test "$(arena_tag)" != latest # A push that names something other than what was asked for means the image is # not where the tfvars would claim it is. setup deploy STUB_TAG=mm9.9.9-sur9.9.9-main-somethingelse check "refuses a push that named a different tag" test $? -ne 0 check " and leaves the release id alone" test "$(arena_tag)" = "mm0.0.0-sur0.0.0-main-old" echo echo "$pass passed, $fail failed" [ "$fail" -eq 0 ]