Wire Forge-native release infrastructure
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
This commit is contained in:
parent
97c569fb35
commit
002bd382e9
199 changed files with 14268 additions and 185 deletions
240
Scripts/forgejo-dispatch.sh
Executable file
240
Scripts/forgejo-dispatch.sh
Executable file
|
|
@ -0,0 +1,240 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: Scripts/forgejo-dispatch.sh <workflow> [--ref <ref>] [--inputs <json>] [--inputs-file <path>] [--return-run-info]
|
||||
|
||||
Dispatch a Burrow Forgejo workflow through the Forgejo API.
|
||||
|
||||
Environment:
|
||||
FORGEJO_BASE_URL Default: https://git.burrow.net
|
||||
FORGEJO_REPO Default: hackclub/burrow
|
||||
FORGEJO_PAT Token string
|
||||
FORGEJO_PAT_FILE Path to plaintext token
|
||||
FORGEJO_DISPATCH_HOST_FALLBACK auto, always, or never. Default: auto
|
||||
EOF
|
||||
}
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
base_url="${FORGEJO_BASE_URL:-https://git.burrow.net}"
|
||||
repo_slug="${FORGEJO_REPO:-hackclub/burrow}"
|
||||
host_fallback="${FORGEJO_DISPATCH_HOST_FALLBACK:-auto}"
|
||||
original_args=("$@")
|
||||
workflow=""
|
||||
ref="main"
|
||||
inputs_json=""
|
||||
return_run_info="false"
|
||||
|
||||
case "$host_fallback" in
|
||||
auto|always|never) ;;
|
||||
*)
|
||||
echo "FORGEJO_DISPATCH_HOST_FALLBACK must be auto, always, or never." >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
host_dispatch() {
|
||||
local reason="$1"
|
||||
local fallback="${repo_root}/Scripts/forgejo-dispatch-via-host.sh"
|
||||
|
||||
if [[ ! -x "$fallback" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Forgejo API dispatch ${reason}; retrying through the forge host." >&2
|
||||
exec "$fallback" "${original_args[@]}"
|
||||
}
|
||||
|
||||
if [[ "$host_fallback" == "always" ]]; then
|
||||
if ! host_dispatch "forced by FORGEJO_DISPATCH_HOST_FALLBACK=always"; then
|
||||
echo "Forge host dispatcher is not available." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--ref)
|
||||
ref="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--inputs)
|
||||
inputs_json="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--inputs-file)
|
||||
inputs_json="$(cat "${2:-}")"
|
||||
shift 2
|
||||
;;
|
||||
--return-run-info)
|
||||
return_run_info="true"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$workflow" ]]; then
|
||||
workflow="$1"
|
||||
shift
|
||||
else
|
||||
echo "Unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 64
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$workflow" ]]; then
|
||||
echo "Missing workflow name or file, for example build-release.yml." >&2
|
||||
usage >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
extract_dispatcher_token() {
|
||||
python3 - "$1" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
in_forgejo = False
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
if re.match(r"^[A-Za-z0-9_-]+:", line):
|
||||
in_forgejo = line.startswith("forgejo:")
|
||||
continue
|
||||
if not in_forgejo:
|
||||
continue
|
||||
match = re.match(r"\s+token:\s*['\"]?([^'\"]+)['\"]?\s*$", line)
|
||||
if match:
|
||||
print(match.group(1))
|
||||
break
|
||||
PY
|
||||
}
|
||||
|
||||
decrypt_age_secret() {
|
||||
local path="$1"
|
||||
|
||||
if [[ ! -f "$path" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v nix >/dev/null 2>&1; then
|
||||
nix run "${repo_root}#agenix" -- -d "$path" 2>/dev/null && return 0
|
||||
fi
|
||||
|
||||
if command -v agenix >/dev/null 2>&1; then
|
||||
agenix -d "$path" 2>/dev/null && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
pat="${FORGEJO_PAT:-}"
|
||||
pat_source="none"
|
||||
if [[ -n "$pat" ]]; then
|
||||
pat_source="FORGEJO_PAT"
|
||||
fi
|
||||
|
||||
if [[ -z "$pat" && -n "${FORGEJO_PAT_FILE:-}" && -f "$FORGEJO_PAT_FILE" ]]; then
|
||||
pat="$(tr -d '\r\n' < "$FORGEJO_PAT_FILE")"
|
||||
if [[ -n "$pat" ]]; then
|
||||
pat_source="FORGEJO_PAT_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$pat" ]]; then
|
||||
for default_file in \
|
||||
"${repo_root}/intake/forgejo_dispatch_pat.txt" \
|
||||
"${repo_root}/intake/forgejo_nsc_dispatcher.yaml"
|
||||
do
|
||||
if [[ -s "$default_file" ]]; then
|
||||
if [[ "$default_file" == *.yaml ]]; then
|
||||
pat="$(extract_dispatcher_token "$default_file" | tr -d '\r\n')"
|
||||
else
|
||||
pat="$(tr -d '\r\n' < "$default_file")"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$pat" ]]; then
|
||||
pat_source="$default_file"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$pat" ]]; then
|
||||
decrypted_config="$(mktemp)"
|
||||
trap 'rm -f "$decrypted_config"' EXIT
|
||||
if decrypt_age_secret "${repo_root}/secrets/infra/forgejo-nsc-dispatcher-config.age" > "$decrypted_config"; then
|
||||
pat="$(extract_dispatcher_token "$decrypted_config" | tr -d '\r\n')"
|
||||
if [[ -n "$pat" ]]; then
|
||||
pat_source="secrets/infra/forgejo-nsc-dispatcher-config.age"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$pat" ]]; then
|
||||
if [[ "$host_fallback" == "auto" ]]; then
|
||||
if host_dispatch "has no local token"; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo "Forgejo token not found. Set FORGEJO_PAT or FORGEJO_PAT_FILE." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$inputs_json" ]]; then
|
||||
if ! printf '%s\n' "$inputs_json" | jq -e 'type == "object"' >/dev/null 2>&1; then
|
||||
echo "--inputs must be a JSON object." >&2
|
||||
exit 64
|
||||
fi
|
||||
inputs_json="$(printf '%s\n' "$inputs_json" | jq -c 'with_entries(.value |= tostring)')"
|
||||
payload="$(jq -n --arg ref "$ref" --argjson inputs "$inputs_json" --argjson return_run_info "$return_run_info" \
|
||||
'{ref:$ref, inputs:$inputs} + (if $return_run_info then {return_run_info:true} else {} end)')"
|
||||
else
|
||||
payload="$(jq -n --arg ref "$ref" --argjson return_run_info "$return_run_info" \
|
||||
'{ref:$ref} + (if $return_run_info then {return_run_info:true} else {} end)')"
|
||||
fi
|
||||
|
||||
workflow_path="$(python3 - "$workflow" <<'PY'
|
||||
import sys
|
||||
import urllib.parse
|
||||
|
||||
print(urllib.parse.quote(sys.argv[1], safe=""))
|
||||
PY
|
||||
)"
|
||||
|
||||
url="${base_url%/}/api/v1/repos/${repo_slug}/actions/workflows/${workflow_path}/dispatches"
|
||||
response_file="$(mktemp)"
|
||||
trap 'rm -f "$response_file" "${decrypted_config:-}"' EXIT
|
||||
|
||||
curl_status=0
|
||||
http_code="$(
|
||||
curl -sS -o "$response_file" -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${pat}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary "$payload" \
|
||||
"$url"
|
||||
)" || curl_status="$?"
|
||||
|
||||
if [[ "$curl_status" != "0" || "$http_code" != 2* ]]; then
|
||||
if [[ "$host_fallback" == "auto" && "$pat_source" != "FORGEJO_PAT" && ( "$http_code" == "401" || "$http_code" == "403" ) ]]; then
|
||||
if host_dispatch "returned HTTP ${http_code} with ${pat_source}"; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
cat "$response_file" >&2 || true
|
||||
if [[ "$curl_status" != "0" ]]; then
|
||||
exit "$curl_status"
|
||||
fi
|
||||
exit 22
|
||||
fi
|
||||
|
||||
cat "$response_file"
|
||||
if [[ "$return_run_info" != "true" ]]; then
|
||||
echo "Dispatched ${workflow} on ${ref}."
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue