Harden inter-hub production deploy trigger
This commit is contained in:
80
tools/check-oci-image.sh
Executable file
80
tools/check-oci-image.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
image_ref="${1:-}"
|
||||
|
||||
if [[ -z "$image_ref" ]]; then
|
||||
echo "usage: $0 <registry/repository:tag>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
failures=()
|
||||
|
||||
try_tool() {
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
if ! command -v "$name" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local output
|
||||
if output="$("$@" 2>&1 >/dev/null)"; then
|
||||
echo "ok: found image manifest with $name: $image_ref"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
failures+=("$name: $output")
|
||||
return 1
|
||||
}
|
||||
|
||||
try_registry_api() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ref_no_digest="${image_ref%@*}"
|
||||
local ref_without_tag tag registry repo url output
|
||||
|
||||
if [[ "$ref_no_digest" != *:* ]]; then
|
||||
failures+=("registry-api: image ref must include an explicit tag")
|
||||
return 1
|
||||
fi
|
||||
|
||||
tag="${ref_no_digest##*:}"
|
||||
ref_without_tag="${ref_no_digest%:*}"
|
||||
registry="${ref_without_tag%%/*}"
|
||||
repo="${ref_without_tag#*/}"
|
||||
|
||||
if [[ -z "$registry" || -z "$repo" || "$registry" == "$repo" ]]; then
|
||||
failures+=("registry-api: image ref must include registry and repository")
|
||||
return 1
|
||||
fi
|
||||
|
||||
url="https://${registry}/v2/${repo}/manifests/${tag}"
|
||||
if output="$(curl -fsSL \
|
||||
-H "Accept: application/vnd.oci.image.index.v1+json" \
|
||||
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
|
||||
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
|
||||
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
||||
-o /dev/null "$url" 2>&1)"; then
|
||||
echo "ok: found image manifest with registry API: $image_ref"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
failures+=("registry-api: $output")
|
||||
return 1
|
||||
}
|
||||
|
||||
try_tool skopeo skopeo inspect --raw "docker://${image_ref}" || true
|
||||
try_tool crane crane manifest "$image_ref" || true
|
||||
try_tool docker docker manifest inspect "$image_ref" || true
|
||||
try_registry_api || true
|
||||
|
||||
echo "ERROR: image manifest not found or not accessible: $image_ref" >&2
|
||||
if ((${#failures[@]} > 0)); then
|
||||
printf '%s\n' "${failures[@]}" >&2
|
||||
else
|
||||
echo "No supported manifest inspection tool was available." >&2
|
||||
fi
|
||||
exit 1
|
||||
87
tools/inter-hub-smoke.sh
Executable file
87
tools/inter-hub-smoke.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
base_url="${INTER_HUB_BASE_URL:-https://hub.coulomb.social}"
|
||||
base_url="${base_url%/}"
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
request() {
|
||||
local name="$1"
|
||||
local url="$2"
|
||||
local expected_status="$3"
|
||||
local body="$tmpdir/${name}.json"
|
||||
local status
|
||||
|
||||
status="$(curl -sS -o "$body" -w "%{http_code}" "$url")"
|
||||
if [[ "$status" != "$expected_status" ]]; then
|
||||
echo "ERROR: expected $url to return $expected_status, got $status" >&2
|
||||
cat "$body" >&2
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$body"
|
||||
}
|
||||
|
||||
hubs_body="$(request hubs "${base_url}/api/v2/hubs" 200)"
|
||||
python3 - "$hubs_body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], encoding="utf-8") as fh:
|
||||
payload = json.load(fh)
|
||||
|
||||
if not isinstance(payload, dict) or not isinstance(payload.get("data"), list):
|
||||
raise SystemExit("/api/v2/hubs did not return a paginated data list")
|
||||
|
||||
print("ok: /api/v2/hubs returned public discovery JSON")
|
||||
PY
|
||||
|
||||
widgets_body="$(request widgets "${base_url}/api/v2/widgets" 401)"
|
||||
hub_registry_body="$(request hub-registry "${base_url}/api/v2/hub-registry" 401)"
|
||||
|
||||
python3 - "$widgets_body" "$hub_registry_body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
for path, filename in (
|
||||
("/api/v2/widgets", sys.argv[1]),
|
||||
("/api/v2/hub-registry", sys.argv[2]),
|
||||
):
|
||||
with open(filename, encoding="utf-8") as fh:
|
||||
payload = json.load(fh)
|
||||
code = payload.get("code") if isinstance(payload, dict) else None
|
||||
if code != "invalid_api_key":
|
||||
raise SystemExit(f"{path} returned 401 but not invalid_api_key JSON")
|
||||
print(f"ok: {path} requires an API key")
|
||||
PY
|
||||
|
||||
openapi_body="$(request openapi "${base_url}/api/v2/openapi.json" 200)"
|
||||
python3 - "$openapi_body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
required_paths = {
|
||||
"/hubs",
|
||||
"/hub-capability-manifests",
|
||||
"/api-consumers",
|
||||
"/policy-scopes",
|
||||
"/widgets",
|
||||
"/hub-registry",
|
||||
}
|
||||
|
||||
with open(sys.argv[1], encoding="utf-8") as fh:
|
||||
payload = json.load(fh)
|
||||
|
||||
paths = payload.get("paths")
|
||||
if not isinstance(paths, dict):
|
||||
raise SystemExit("/api/v2/openapi.json did not include an OpenAPI paths object")
|
||||
|
||||
missing = sorted(required_paths - set(paths))
|
||||
if missing:
|
||||
raise SystemExit("OpenAPI missing paths: " + ", ".join(missing))
|
||||
|
||||
print("ok: /api/v2/openapi.json lists expected v2 resources")
|
||||
PY
|
||||
Reference in New Issue
Block a user