mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-27 09:19:34 +00:00
Hanzo CI/CD / cicd (push) Waiting to run
CI/CD / gate (push) Waiting to run
Three copies of "log in to KMS and read one name": this repo's site action, this repo's build.yml, and hanzoai/gui's npm-token action. Written three times they were wrong in three different ways at once — two piped the body straight into jq, so a gateway timeout died as "Invalid numeric literal at line 1, column 6" and another reported "NPM_TOKEN absent from KMS" about a token that was sealed and read clean a minute later; the third retried only a lost connection and not a 5xx. All three looked alike in a log, which is the whole cost: an outage, a refusal and an unsealed secret were one sentence. bin/kms is that read, once, for the same reason bin/imgver is one number once. The site action already clones this repo for bin/site, so it costs nothing there. THE SCHEDULE IS SHORTER THAN WHAT IT REPLACED — 17 seconds, not 65. A retry buys a dropped response, a 502, a pod coming back. It does not buy a wedged store: that ran fifteen minutes and no schedule a build step can afford covers it. The long version only made every failing build slower, and the test that pins the behaviour slower still — 3m05s against 17s. build.yml still has its own kms(). It reads six names plus a loop behind one login and carries its own required-vs-warning policy, so it is not a drop-in, and it is the file every repo in the fleet builds through. Left deliberately, not missed. Pinned by bin/kms_test.sh, in hanzo.yml's gate beside the other bin suites.
83 lines
3.9 KiB
Bash
Executable File
83 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# kms — read one secret from Hanzo KMS. One implementation, every caller.
|
|
#
|
|
# kms <NAME> [env] # prints the value, or exits non-zero
|
|
#
|
|
# Needs KMS_CLIENT_ID and KMS_CLIENT_SECRET in the environment. KMS_ENDPOINT
|
|
# defaults to https://kms.hanzo.ai; both it and api.hanzo.ai are Ingresses onto
|
|
# the same cloud service, so the host is a preference and not a fallback.
|
|
#
|
|
# WHY THIS IS A SCRIPT AND NOT INLINE SHELL, which is the same reason imgver is:
|
|
# three callers each carried their own copy — this repo's site action, this
|
|
# repo's build.yml, and hanzoai/gui's npm-token action. Written three times it
|
|
# was wrong in three different ways at once. Two of them piped the body straight
|
|
# into jq, so a gateway timeout died as "Invalid numeric literal at line 1,
|
|
# column 6" and one reported "NPM_TOKEN absent from KMS" about a sealed secret;
|
|
# the third retried only a lost connection, not a 5xx.
|
|
#
|
|
# THE READ IS FLAT. KMS resolves a name at the credential's own scope root, so
|
|
# `HANZO_DEPLOY_TOKEN` finds the value sealed for this org, while a
|
|
# path-qualified `deploy/HANZO_DEPLOY_TOKEN` finds nothing and reads exactly like
|
|
# an absent secret.
|
|
#
|
|
# ASK AGAIN ONLY WHERE ASKING AGAIN CAN HELP: nothing arrived, 429, or any 5xx —
|
|
# the network saying "not now". 401, 403 and 404 are KMS itself answering and are
|
|
# taken on the first try.
|
|
#
|
|
# THE SCHEDULE IS SHORT ON PURPOSE — 17 seconds over four tries. It exists to
|
|
# survive a dropped response, a 502 or a pod coming back, which are seconds. It
|
|
# does NOT exist to ride out a wedged store: that lasted fifteen minutes on
|
|
# 2026-08-24 and no schedule a build step can afford would have covered it. A
|
|
# minute of waiting before the same failure only makes the build slower and the
|
|
# test that pins this behaviour slower still.
|
|
#
|
|
# EVERY FAILURE NAMES WHICH OF THE THREE IT WAS — an outage, a refusal, or an
|
|
# unsealed secret — because the whole cost of the old copies was that they all
|
|
# looked alike in a log.
|
|
set -uo pipefail
|
|
|
|
name=${1:-}
|
|
env=${2:-prod}
|
|
endpoint=${KMS_ENDPOINT:-https://kms.hanzo.ai}
|
|
[ -n "$name" ] || { echo "::error::kms: usage: kms <NAME> [env]" >&2; exit 2; }
|
|
[ -n "${KMS_CLIENT_ID:-}" ] && [ -n "${KMS_CLIENT_SECRET:-}" ] || {
|
|
echo "::error::kms: KMS_CLIENT_ID and KMS_CLIENT_SECRET are unset — set them from the org-level secrets on git.hanzo.ai" >&2
|
|
exit 1
|
|
}
|
|
|
|
body=$(mktemp); trap 'rm -f "$body"' EXIT
|
|
|
|
# ask <what> <url> [curl args...] -> body on stdout when 2xx.
|
|
#
|
|
# `|| code=000` on the ASSIGNMENT, never `|| echo 000` inside the substitution:
|
|
# curl's -w already prints 000 when the request fails, so echoing a second one
|
|
# yields "000000", which matches no case below and turns an outage into a
|
|
# refusal that never retries.
|
|
ask() {
|
|
local what=$1 url=$2; shift 2
|
|
local code
|
|
for wait in 0 2 5 10; do
|
|
[ "$wait" = 0 ] || sleep "$wait"
|
|
code=$(curl -sS -m 30 -o "$body" -w '%{http_code}' "$url" "$@") || code=000
|
|
case "$code" in
|
|
2*) cat "$body"; return 0 ;;
|
|
000|429|5*) continue ;;
|
|
*) echo "::error::kms: $endpoint refused the $what: HTTP $code — $(head -c 300 "$body")" >&2; return 1 ;;
|
|
esac
|
|
done
|
|
echo "::error::kms: $endpoint did not answer the $what — HTTP $code over four tries: $(head -c 300 "$body"). An outage, not a missing secret." >&2
|
|
return 1
|
|
}
|
|
|
|
token=$(ask login "$endpoint/v1/kms/auth/login" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" \
|
|
| jq -r '.accessToken // empty' 2>/dev/null) || exit 1
|
|
[ -n "$token" ] || { echo "::error::kms: login returned no accessToken at $endpoint/v1/kms/auth/login" >&2; exit 1; }
|
|
|
|
value=$(ask "secret $name" "$endpoint/v1/kms/secrets/$name?env=$env" \
|
|
-H "Authorization: Bearer $token" | jq -r '.value // empty' 2>/dev/null) || exit 1
|
|
[ -n "$value" ] || { echo "::error::kms: no value for $name at $endpoint/v1/kms/secrets/$name?env=$env — seal it there" >&2; exit 1; }
|
|
|
|
printf '%s' "$value"
|