222 lines
6.3 KiB
Bash
Executable File
222 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
|
source_env=${1:-}
|
|
|
|
if [[ -z "$source_env" || ! -f "$source_env" ]]; then
|
|
echo "Usage: $0 TEST_ENV_FILE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
for command in docker gzip jq realpath sha256sum; do
|
|
command -v "$command" >/dev/null 2>&1 || {
|
|
echo "Required command is unavailable: $command" >&2
|
|
exit 2
|
|
}
|
|
done
|
|
|
|
if [[ -n "$(git -C "$ROOT" status --porcelain --untracked-files=normal)" ]]; then
|
|
echo "Refusing to build test images from a dirty checkout." >&2
|
|
exit 2
|
|
fi
|
|
|
|
commit=$(git -C "$ROOT" rev-parse --verify HEAD)
|
|
short_commit=${commit:0:12}
|
|
artifact_root=${WNH_TEST_RELEASE_ARTIFACT_ROOT:-"$ROOT/output/test-releases"}
|
|
case "$artifact_root" in
|
|
/*) ;;
|
|
*)
|
|
echo "WNH_TEST_RELEASE_ARTIFACT_ROOT must be an absolute path." >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
mkdir -p "$artifact_root"
|
|
artifact_root=$(realpath --canonicalize-existing "$artifact_root")
|
|
release_dir="$artifact_root/$commit"
|
|
archive="$release_dir/who_need_help-$commit-test-images-linux-amd64.tar.gz"
|
|
checksum="$archive.sha256"
|
|
manifest="$release_dir/who_need_help-$commit-test-images.manifest"
|
|
|
|
mkdir -p "$release_dir"
|
|
chmod 700 "$artifact_root" "$release_dir"
|
|
|
|
build_env=$(mktemp "$release_dir/.test-image-build.XXXXXX")
|
|
cleanup() {
|
|
trap - EXIT HUP INT TERM
|
|
rm -f "$build_env"
|
|
}
|
|
trap cleanup EXIT HUP INT TERM
|
|
install -m 600 "$source_env" "$build_env"
|
|
|
|
read_value() {
|
|
local key=$1
|
|
awk -v key="$key" '
|
|
index($0, key "=") == 1 {
|
|
print substr($0, length(key) + 2)
|
|
found = 1
|
|
exit
|
|
}
|
|
END { if (!found) exit 1 }
|
|
' "$build_env"
|
|
}
|
|
|
|
replace_value() {
|
|
local key=$1 value=$2 temporary
|
|
temporary=$(mktemp "$release_dir/.test-image-env.XXXXXX")
|
|
chmod 600 "$temporary"
|
|
awk -v key="$key" -v value="$value" '
|
|
index($0, key "=") == 1 { print key "=" value; found = 1; next }
|
|
{ print }
|
|
END { if (!found) exit 1 }
|
|
' "$build_env" >"$temporary"
|
|
mv "$temporary" "$build_env"
|
|
chmod 600 "$build_env"
|
|
}
|
|
|
|
[[ "$(read_value DEPLOYMENT_ENV)" == test ]] || {
|
|
echo "The image build input is not a test environment." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(read_value DATABASE_MODE)" == container ]] || {
|
|
echo "The verified test image workflow expects DATABASE_MODE=container." >&2
|
|
exit 2
|
|
}
|
|
|
|
replace_value APP_IMAGE "who-need-help:test-$short_commit"
|
|
replace_value SOCKET_PROXY_IMAGE \
|
|
"who-need-help:socket-proxy-test-$short_commit"
|
|
replace_value POSTGIS_IMAGE "who-need-help:postgis-test-$short_commit"
|
|
|
|
topology=$(read_value APP_TOPOLOGY)
|
|
case "$topology" in
|
|
compact)
|
|
build_services=(migrate db)
|
|
;;
|
|
split)
|
|
build_services=(docker-api-proxy proxy migrate db)
|
|
;;
|
|
*)
|
|
echo "APP_TOPOLOGY must be compact or split." >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
app_image=$(read_value APP_IMAGE)
|
|
postgis_image=$(read_value POSTGIS_IMAGE)
|
|
images=("$app_image" "$postgis_image")
|
|
if [[ "$topology" == split ]]; then
|
|
socket_proxy_image=$(read_value SOCKET_PROXY_IMAGE)
|
|
proxy_image=$(
|
|
"$ROOT/scripts/compose.sh" "$build_env" config --format json |
|
|
jq -er '.services.proxy.image'
|
|
)
|
|
images+=("$socket_proxy_image" "$proxy_image")
|
|
fi
|
|
|
|
if [[ -e "$archive" || -e "$checksum" || -e "$manifest" ]]; then
|
|
[[ -f "$archive" && -f "$checksum" && -f "$manifest" ]] || {
|
|
echo "The test image package is incomplete; refusing to overwrite it." >&2
|
|
exit 2
|
|
}
|
|
(
|
|
cd "$release_dir"
|
|
sha256sum --check "$(basename -- "$checksum")" >/dev/null
|
|
)
|
|
echo "Test image package already exists; verifying all metadata."
|
|
else
|
|
"$ROOT/scripts/compose.sh" "$build_env" build "${build_services[@]}"
|
|
|
|
manifest_tmp=$(mktemp "$release_dir/.test-images-manifest.XXXXXX")
|
|
archive_tmp=$(mktemp "$release_dir/.test-images-archive.XXXXXX")
|
|
trap 'rm -f "$build_env" "$manifest_tmp" "$archive_tmp"' EXIT HUP INT TERM
|
|
|
|
{
|
|
printf 'format=1\n'
|
|
printf 'deployment=test\n'
|
|
printf 'commit=%s\n' "$commit"
|
|
printf 'platform=linux/amd64\n'
|
|
printf 'topology=%s\n' "$topology"
|
|
printf 'image_count=%s\n' "${#images[@]}"
|
|
for image in "${images[@]}"; do
|
|
platform=$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image")
|
|
[[ "$platform" == linux/amd64 ]] || {
|
|
echo "Test image has an unexpected platform: $image ($platform)" >&2
|
|
exit 2
|
|
}
|
|
image_id=$(docker image inspect --format '{{.Id}}' "$image")
|
|
printf 'image=%s|%s\n' "$image" "$image_id"
|
|
done
|
|
} >"$manifest_tmp"
|
|
|
|
docker save "${images[@]}" | gzip -n -9 >"$archive_tmp"
|
|
mv "$archive_tmp" "$archive"
|
|
mv "$manifest_tmp" "$manifest"
|
|
chmod 600 "$archive" "$manifest"
|
|
hash=$(sha256sum "$archive" | awk '{print $1}')
|
|
printf '%s %s\n' "$hash" "$(basename -- "$archive")" >"$checksum"
|
|
chmod 600 "$checksum"
|
|
fi
|
|
|
|
(
|
|
cd "$release_dir"
|
|
sha256sum --check "$(basename -- "$checksum")" >/dev/null
|
|
)
|
|
gzip -t "$archive"
|
|
|
|
manifest_value() {
|
|
local key=$1
|
|
awk -F= -v key="$key" '
|
|
$1 == key { count += 1; value = substr($0, length(key) + 2) }
|
|
END {
|
|
if (count != 1) exit 1
|
|
print value
|
|
}
|
|
' "$manifest"
|
|
}
|
|
|
|
[[ "$(manifest_value format)" == 1 ]] || {
|
|
echo "Test image manifest format is unsupported." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(manifest_value deployment)" == test ]] || {
|
|
echo "Test image manifest has the wrong deployment identity." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(manifest_value commit)" == "$commit" ]] || {
|
|
echo "Test image manifest commit does not match the current commit." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(manifest_value platform)" == linux/amd64 ]] || {
|
|
echo "Test image manifest platform is not linux/amd64." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(manifest_value topology)" == "$topology" ]] || {
|
|
echo "Test image manifest topology does not match the environment." >&2
|
|
exit 2
|
|
}
|
|
[[ "$(manifest_value image_count)" == "${#images[@]}" ]] || {
|
|
echo "Test image manifest count does not match the required images." >&2
|
|
exit 2
|
|
}
|
|
test "$(grep -c '^image=' "$manifest")" = "${#images[@]}"
|
|
for image in "${images[@]}"; do
|
|
awk -F'|' -v image="$image" '
|
|
$1 == "image=" image && $2 ~ /^sha256:[0-9a-f]+$/ { found = 1 }
|
|
END { if (!found) exit 1 }
|
|
' "$manifest"
|
|
done
|
|
|
|
archive_hash=$(sha256sum "$archive" | awk '{print $1}')
|
|
expected_checksum="$archive_hash $(basename -- "$archive")"
|
|
[[ "$(cat -- "$checksum")" == "$expected_checksum" ]] || {
|
|
echo "Test image checksum metadata does not name the exact archive." >&2
|
|
exit 2
|
|
}
|
|
|
|
cleanup
|
|
printf 'Test image archive: %s\n' "$archive"
|
|
printf 'Image archive checksum: %s\n' "$checksum"
|
|
printf 'Image manifest: %s\n' "$manifest"
|