199 lines
5.6 KiB
Bash
Executable File
199 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Obtain a verified artifact for an exact git commit.
|
|
# Build may have happened locally or in CI; this script only reports identity
|
|
# after the artifact exists. Does not print tokens.
|
|
#
|
|
# Usage:
|
|
# obtain.sh [--sha SHA] [--branch NAME] [--root DIR] [--timeout SEC]
|
|
#
|
|
# BUILD_VENUE (makefile.builder, default local):
|
|
# local HEAD must be SHA; run `make -f makefile.builder docker` or `deb`
|
|
# ci wait for the forge pipeline of this SHA, then inspect the image
|
|
# (GitHub Actions, GitLab CI, or Gitea Actions)
|
|
#
|
|
# Exit:
|
|
# 0 artifact verified; prints ARTIFACT= / DIGEST= / SHA= / VENUE=
|
|
# 1 no pipeline, job failed, inspect failed
|
|
# 2 usage / config
|
|
# 3 forge CLI cannot read the project (ci venue)
|
|
|
|
usage() {
|
|
sed -n '2,20p' "$0"
|
|
}
|
|
|
|
sha=
|
|
branch=
|
|
root=
|
|
timeout_sec=1200
|
|
|
|
while (($#)); do
|
|
case "$1" in
|
|
--sha) sha=$2; shift 2 ;;
|
|
--branch) branch=$2; shift 2 ;;
|
|
--root) root=$2; shift 2 ;;
|
|
--timeout) timeout_sec=$2; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Error: unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$root" ]]; then
|
|
root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$root" ]]; then
|
|
echo "Error: --root must be a git project" >&2
|
|
exit 2
|
|
fi
|
|
cd "$root"
|
|
|
|
if [[ -z "$sha" ]]; then
|
|
sha=$(git rev-parse HEAD)
|
|
else
|
|
sha=$(git rev-parse "$sha")
|
|
fi
|
|
|
|
# Load .env.builder without printing values; shell values win. Do not read `.env`.
|
|
if [[ -f "$root/.env.builder" ]]; then
|
|
while IFS='=' read -r key value; do
|
|
key=${key%%[[:space:]]*}
|
|
[[ -z "$key" || "$key" == \#* ]] && continue
|
|
if [[ -n "${!key:-}" ]]; then
|
|
continue
|
|
fi
|
|
value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'}
|
|
[[ -z "$value" ]] && continue
|
|
printf -v "$key" '%s' "$value"
|
|
export "$key"
|
|
done < <(grep -v '^[[:space:]]*$' "$root/.env.builder")
|
|
fi
|
|
|
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
version_sh=$script_dir/version.sh
|
|
wait_ci=$script_dir/wait_ci.sh
|
|
|
|
venue=${BUILD_VENUE:-}
|
|
registry=${DOCKER_REGISTRY:-}
|
|
repository=${DOCKER_REPOSITORY:-}
|
|
if [[ -f "$root/makefile.builder" ]]; then
|
|
if [[ -z "$venue" ]]; then
|
|
venue=$(make -s -C "$root" -f makefile.builder venue 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$registry" ]]; then
|
|
registry=$(make -s -C "$root" -f makefile.builder print-DOCKER_REGISTRY 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$repository" ]]; then
|
|
repository=$(make -s -C "$root" -f makefile.builder print-DOCKER_REPOSITORY 2>/dev/null || true)
|
|
fi
|
|
fi
|
|
venue=${venue:-local}
|
|
|
|
docker_tag_for_sha() {
|
|
local tmp tag
|
|
tmp=$(mktemp -d "${TMPDIR:-/tmp}/builder-obtain.XXXXXX")
|
|
git -C "$root" worktree add --detach "$tmp" "$sha" >/dev/null
|
|
if [[ -n "$branch" ]]; then
|
|
tag=$(CI_COMMIT_BRANCH="$branch" "$version_sh" -C "$tmp" --docker)
|
|
else
|
|
tag=$("$version_sh" -C "$tmp" --docker)
|
|
fi
|
|
git -C "$root" worktree remove --force "$tmp" >/dev/null
|
|
printf '%s' "$tag"
|
|
}
|
|
|
|
inspect_image() {
|
|
local image=$1 out digest
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "Error: docker is required to inspect $image" >&2
|
|
exit 1
|
|
fi
|
|
out=$(docker buildx imagetools inspect "$image" 2>/dev/null \
|
|
|| docker manifest inspect "$image" 2>/dev/null \
|
|
|| true)
|
|
if [[ -z "$out" ]]; then
|
|
echo "Error: cannot inspect $image" >&2
|
|
exit 1
|
|
fi
|
|
digest=$(printf '%s\n' "$out" | awk '/^Digest:/ {print $2; exit}')
|
|
if [[ -z "$digest" ]]; then
|
|
digest=$(python3 -c 'import json,sys
|
|
try:
|
|
d=json.load(sys.stdin)
|
|
except Exception:
|
|
sys.exit(0)
|
|
print(d.get("digest") or "")' <<<"$out")
|
|
fi
|
|
if [[ -z "$digest" ]]; then
|
|
echo "Error: inspect of $image had no digest" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s' "$digest"
|
|
}
|
|
|
|
case "$venue" in
|
|
ci)
|
|
"$wait_ci" --sha "$sha" --root "$root" --timeout "$timeout_sec"
|
|
if [[ ! -x "$version_sh" ]]; then
|
|
echo "Error: version.sh not found: $version_sh" >&2
|
|
exit 2
|
|
fi
|
|
if [[ -z "$registry" || -z "$repository" ]]; then
|
|
echo "Error: DOCKER_REGISTRY and DOCKER_REPOSITORY are required to inspect the CI image" >&2
|
|
exit 2
|
|
fi
|
|
tag=$(docker_tag_for_sha)
|
|
image="${registry}/${repository}:${tag}"
|
|
digest=$(inspect_image "$image")
|
|
echo "ARTIFACT=$image"
|
|
echo "DIGEST=$digest"
|
|
echo "SHA=$sha"
|
|
echo "VENUE=ci"
|
|
echo "TAG=$tag"
|
|
;;
|
|
local)
|
|
head=$(git rev-parse HEAD)
|
|
if [[ "$head" != "$sha" ]]; then
|
|
echo "Error: local obtain requires HEAD=$sha (HEAD is $head)" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! -f "$root/makefile.builder" ]]; then
|
|
echo "Error: no makefile.builder" >&2
|
|
exit 2
|
|
fi
|
|
if make -s -C "$root" -f makefile.builder -n docker >/dev/null 2>&1; then
|
|
make -C "$root" -f makefile.builder docker
|
|
if [[ -n "${IMAGE_TAG:-}" ]]; then
|
|
tag=$IMAGE_TAG
|
|
elif [[ -x "$version_sh" ]]; then
|
|
tag=$("$version_sh" --docker)
|
|
else
|
|
echo "Error: cannot derive IMAGE_TAG" >&2
|
|
exit 2
|
|
fi
|
|
name=$(make -s -C "$root" -f makefile.builder print-PROJECT_NAME 2>/dev/null || basename "$root")
|
|
image="${name}:${tag}"
|
|
if [[ -n "$registry" && -n "$repository" ]]; then
|
|
image="${registry}/${repository}:${tag}"
|
|
fi
|
|
echo "ARTIFACT=$image"
|
|
echo "DIGEST=local"
|
|
echo "SHA=$sha"
|
|
echo "VENUE=local"
|
|
echo "TAG=$tag"
|
|
elif make -s -C "$root" -f makefile.builder -n deb >/dev/null 2>&1; then
|
|
make -C "$root" -f makefile.builder deb
|
|
echo "ARTIFACT=deb"
|
|
echo "SHA=$sha"
|
|
echo "VENUE=local"
|
|
else
|
|
echo "Error: local venue has no docker or deb target" >&2
|
|
exit 2
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Error: BUILD_VENUE must be local or ci (got ${venue})" >&2
|
|
exit 2
|
|
;;
|
|
esac
|