Files
.pouch/skills/builder/scripts/obtain.sh
T
laily 9ba9b3da0f feat(builder): query CI via project status script
When BUILD_VENUE=ci, init confirms tea/glab/gh/curl (or a custom
command) and writes .pouch/builder/ci-status.sh. wait_ci.sh only
polls that script and no longer guesses forge CLIs.
2026-10-15 09:56:51 +08:00

198 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 this SHA via the project CI_STATUS_SCRIPT, then inspect the image
#
# Exit:
# 0 artifact verified; prints ARTIFACT= / DIGEST= / SHA= / VENUE=
# 1 no pipeline, job failed, inspect failed
# 2 usage / config
# 3 CI status script cannot query (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