Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f44cb4a03 | ||
|
|
192a9a2f7e | ||
|
|
3aa73cb604 | ||
|
|
e262ea40f9 | ||
|
|
84b649a685 | ||
|
|
a94028f3ad | ||
|
|
255777b81e | ||
|
|
d07661c0cc | ||
|
|
c67f9fbfcd | ||
|
|
c380563306 | ||
|
|
daacc3ca9b | ||
|
|
9231a46d2b | ||
|
|
be43b6f323 |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="ci">
|
||||
<rect width="1280" height="640" fill="#0A0A0A"/>
|
||||
<svg x="96" y="215" width="210" height="210" viewBox="0 0 67 67"><path d="M22.21 67V44.6369H0V67H22.21Z" fill="#fff"/><path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#fff"/><path d="M22.21 0H0V22.3184H22.21V0Z" fill="#fff"/><path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#fff"/><path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#fff"/></svg>
|
||||
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">ci</text>
|
||||
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">Reusable CI/CD: build/test/deploy any repo from its hanzo.yml on arc</text>
|
||||
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
|
||||
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/hanzoai</text>
|
||||
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">hanzo.ai</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
+210
-23
@@ -15,7 +15,19 @@ on:
|
||||
the build, metered as build minutes). Bring-your-own: pass the labels
|
||||
of your own self-hosted arc runners instead.
|
||||
type: string
|
||||
default: '["self-hosted","linux","amd64"]'
|
||||
default: '["hanzo-build-linux-amd64"]'
|
||||
mode:
|
||||
description: >-
|
||||
Build execution mode. `buildx` (default) runs the full buildx →
|
||||
test → deploy pipeline ON the arc runner. `delegate` instead POSTs the
|
||||
build to platform.hanzo.ai (`/v1/arcd/enqueue`) — platform builds
|
||||
in-cluster with BuildKit and rolls the operator Service CR itself, so
|
||||
the GitHub job finishes in seconds with no runner buildx. A repo opts
|
||||
in by passing `with: { mode: delegate }`; everything else is unchanged.
|
||||
Requires the `PLATFORM_BUILD_CALLBACK_TOKEN` secret (via secrets:
|
||||
inherit).
|
||||
type: string
|
||||
default: buildx
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -27,48 +39,223 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Provision parse toolchain (jq + PyYAML)
|
||||
# This reusable parses the caller's hanzo.yml with python3 + PyYAML and
|
||||
# slices JSON with jq. The stock arc runner image
|
||||
# (ghcr.io/actions/actions-runner:latest) is minimal and ships NEITHER,
|
||||
# so provision them here. Guarded (a no-op the moment a runner image bakes
|
||||
# them in) — this keeps the reusable self-contained: any org can import it
|
||||
# onto a bare runner and it just works.
|
||||
run: |
|
||||
set -e
|
||||
need=0
|
||||
python3 -c 'import yaml' 2>/dev/null || need=1
|
||||
command -v jq >/dev/null 2>&1 || need=1
|
||||
if [ "$need" = 1 ]; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq python3-yaml jq
|
||||
fi
|
||||
python3 -c 'import yaml; print("PyYAML", yaml.__version__)'
|
||||
jq --version
|
||||
|
||||
- name: Delegate build to platform (mode=delegate)
|
||||
# The GHA-escape fast path: instead of running buildx on this runner, POST
|
||||
# each image in hanzo.yml to platform.hanzo.ai's direct-enqueue webhook
|
||||
# (`/v1/arcd/enqueue`). Platform creates a build_job row, launches an
|
||||
# in-cluster BuildKit Job on its own pool, pushes to the registry, and —
|
||||
# for a system service — patches the operator Service CR to roll it. The
|
||||
# downstream is IDENTICAL to the platform GitHub-App webhook path (one
|
||||
# build path, two front doors), so a delegated build behaves exactly like
|
||||
# a platform-native one. This job then exits in seconds — no buildx, no
|
||||
# KMS, no runner-side deploy.
|
||||
if: inputs.mode == 'delegate'
|
||||
env:
|
||||
ENQUEUE_URL: ${{ vars.PLATFORM_ENQUEUE_URL || 'https://platform.hanzo.ai/v1/arcd/enqueue' }}
|
||||
ENQUEUE_TOKEN: ${{ secrets.PLATFORM_BUILD_CALLBACK_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${ENQUEUE_TOKEN:-}" ]; then
|
||||
echo "::error::mode=delegate needs the PLATFORM_BUILD_CALLBACK_TOKEN secret (secrets: inherit)"; exit 1
|
||||
fi
|
||||
REPO="${{ github.repository }}"
|
||||
SHA="${{ github.sha }}"
|
||||
SHORT=$(echo "$SHA" | cut -c1-7)
|
||||
REF="${{ github.ref }}"
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
# One enqueue per (image, platform), mirroring the buildx tag shape the
|
||||
# deploy path expects (`sha-<short>-<arch>[-<suffix>]`). Default arch is
|
||||
# amd64 (single-arch), so an existing repo's tag shape is unchanged.
|
||||
python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(open('hanzo.yml'))['images']))" | jq -c '.[]' | while read -r img; do
|
||||
name=$(echo "$img"|jq -r .name); repo=$(echo "$img"|jq -r .repo)
|
||||
ctx=$(echo "$img"|jq -r .context); df=$(echo "$img"|jq -r '.dockerfile // (.context+"/Dockerfile")')
|
||||
sfx=$(echo "$img"|jq -r '."tag-suffix" // ""')
|
||||
echo "$img" | jq -r '(.platforms // ["linux/amd64"])[]' | while read -r plat; do
|
||||
arch="${plat##*/}"
|
||||
image="${repo}:sha-${SHORT}-${arch}${sfx:+-$sfx}"
|
||||
body=$(jq -nc \
|
||||
--arg repo "$REPO" --arg sha "$SHA" --arg image "$image" \
|
||||
--arg ref "$REF" --arg branch "$BRANCH" \
|
||||
--arg dockerfile "$df" --arg context "$ctx" --arg arch "$arch" \
|
||||
'{repo:$repo,sha:$sha,image:$image,ref:$ref,branch:$branch,dockerfile:$dockerfile,context:$context,os:"linux",arch:$arch}')
|
||||
echo "::group::delegate $name → $image"
|
||||
code=$(curl -sS -o /tmp/enqueue.out -w '%{http_code}' -X POST "$ENQUEUE_URL" \
|
||||
-H "Authorization: Bearer $ENQUEUE_TOKEN" -H 'Content-Type: application/json' -d "$body")
|
||||
cat /tmp/enqueue.out; echo
|
||||
# 202 Accepted = queued; 409 = no live runner for the pool (surface it loud).
|
||||
if [ "$code" != "202" ]; then echo "::error::enqueue $image failed (HTTP $code)"; exit 1; fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
done
|
||||
|
||||
- name: Authenticated git for go modules (rate-limit + any private repo)
|
||||
if: inputs.mode != 'delegate'
|
||||
# luxfi/hanzoai/zooai Go modules are PUBLIC, so `go` resolves them through
|
||||
# the default public proxy (proxy.golang.org) + checksum db (sum.golang.org)
|
||||
# — canonical, IMMUTABLE hashes that a force-moved tag can no longer break.
|
||||
# We deliberately do NOT set GOPRIVATE: that would route these public
|
||||
# modules `direct` and bypass the sumdb, re-introducing the re-publish
|
||||
# poisoning we just removed. This step only adds a GH_PAT git credential so
|
||||
# the proxy's `direct` fallback (and any genuinely-private repo added later,
|
||||
# via a narrow GOPRIVATE) authenticates instead of hitting the anon rate
|
||||
# limit. No-op when GH_PAT is absent.
|
||||
#
|
||||
# Robustness (learned the hard way in hanzoai/iam): a plain
|
||||
# `git config --global` is defeated on shared self-hosted arc runners by
|
||||
# (a) a stale insteadOf/credential left in ~/.gitconfig by a prior run and
|
||||
# (b) actions/checkout's persisted http.<github>.extraheader (the repo-
|
||||
# scoped GITHUB_TOKEN) in the checked-out repo's local config. So use a
|
||||
# FRESH per-job GIT_CONFIG_GLOBAL + GIT_CONFIG_NOSYSTEM=1 and verify from
|
||||
# a NEUTRAL dir (matches where go clones modules — GOMODCACHE).
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
run: |
|
||||
if [ -z "${GH_PAT:-}" ]; then
|
||||
echo "GH_PAT not set — public proxy+sumdb handles everything"; exit 0
|
||||
fi
|
||||
CFG="$RUNNER_TEMP/gitconfig-private"; : > "$CFG"
|
||||
GIT_CONFIG_GLOBAL="$CFG" git config --global \
|
||||
url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "https://github.com/"
|
||||
{
|
||||
echo "GIT_CONFIG_GLOBAL=$CFG"
|
||||
echo "GIT_CONFIG_NOSYSTEM=1"
|
||||
} >> "$GITHUB_ENV"
|
||||
( cd "$RUNNER_TEMP" && GIT_CONFIG_GLOBAL="$CFG" GIT_CONFIG_NOSYSTEM=1 \
|
||||
git ls-remote https://github.com/hanzoai/authz >/dev/null 2>&1 ) \
|
||||
&& echo "git auth OK" \
|
||||
|| echo "::warning::GH_PAT set but repo probe failed"
|
||||
|
||||
- name: Log in to GHCR (automatic workflow token)
|
||||
if: inputs.mode != 'delegate'
|
||||
# GHCR push needs no stored credential: GitHub injects a per-job
|
||||
# GITHUB_TOKEN scoped to THIS repo, and `permissions: packages: write`
|
||||
# (above) lets it push the repo's own package (e.g. zooai/node →
|
||||
# ghcr.io/zooai/node, same org). First push creates the package
|
||||
# repo-linked. No KMS, no PAT for the push path.
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
|
||||
- name: Fetch deploy credentials from KMS
|
||||
id: kms
|
||||
if: inputs.mode != 'delegate'
|
||||
env:
|
||||
KMS_CLIENT_ID: ${{ secrets.KMS_CLIENT_ID }}
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
KMS_ENDPOINT: ${{ vars.KMS_ENDPOINT || 'https://kms.hanzo.ai' }}
|
||||
KMS_WORKSPACE: ${{ vars.KMS_WORKSPACE }}
|
||||
KMS_ORG: ${{ vars.KMS_ORG }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TOKEN=$(curl -sf "$KMS_ENDPOINT/api/v1/auth/universal-auth/login" \
|
||||
# This step is BEST-EFFORT (see below): GHCR push already works via the
|
||||
# workflow token, and deploy creds are optional. GitHub wraps `run:` in
|
||||
# `bash -eo pipefail`, so we MUST explicitly `set +e` — otherwise an
|
||||
# unguarded curl (e.g. a KMS secret 404 at this org/path) aborts the
|
||||
# step and fails the whole build. Keep -u/-o pipefail; drop -e.
|
||||
set +e
|
||||
set -uo pipefail
|
||||
# Canonical luxfi/kms surface — /v1/kms (the Infisical /api/* surface was
|
||||
# removed when KMS migrated to luxfi/kms, MPC-rooted). Auth = an IAM
|
||||
# client_credentials JWT minted by the per-org `<org>-kms` application;
|
||||
# secrets are org-scoped and fetched one at a time (no bulk /secrets/raw).
|
||||
#
|
||||
# This step is BEST-EFFORT: the automatic workflow token already
|
||||
# authorizes GHCR push, so a repo with no cross-org private deps and no
|
||||
# deploy can build without KMS. KMS provides only:
|
||||
# GITHUB_TOKEN — a cross-org PAT for cloning OTHER orgs' private Go
|
||||
# modules (luxfi/dex, luxfi/precompile from a zooai build), which the
|
||||
# repo-scoped automatic token cannot read. Handed to buildx as the
|
||||
# `gh_token` BuildKit secret.
|
||||
# KUBECONFIG — cluster access for the deploy step.
|
||||
ORG="$(python3 -c "import yaml;print((yaml.safe_load(open('hanzo.yml')).get('kms') or {}).get('org','') )" 2>/dev/null || true)"
|
||||
: "${ORG:=${KMS_ORG:-}}"
|
||||
ENV="$(python3 -c "import yaml;print((yaml.safe_load(open('hanzo.yml')).get('kms') or {}).get('environment','prod'))" 2>/dev/null || echo prod)"
|
||||
PATHQ="$(python3 -c "import yaml;print((yaml.safe_load(open('hanzo.yml')).get('kms') or {}).get('path','/deploy').strip('/'))" 2>/dev/null || echo deploy)"
|
||||
if [ -z "${KMS_CLIENT_ID:-}" ] || [ -z "$ORG" ]; then
|
||||
echo "::notice::KMS not configured (no KMS_CLIENT_ID or org) — skipping; GHCR push uses the workflow token"; exit 0
|
||||
fi
|
||||
TOKEN=$(curl -sf "$KMS_ENDPOINT/v1/kms/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" | jq -r '.accessToken')
|
||||
ENV="$(python3 -c "import yaml;print((yaml.safe_load(open('hanzo.yml')).get('kms') or {}).get('environment','prod'))")"
|
||||
PATHQ="$(python3 -c "import yaml;print((yaml.safe_load(open('hanzo.yml')).get('kms') or {}).get('path','/deploy'))")"
|
||||
SECRETS=$(curl -sf "$KMS_ENDPOINT/api/v3/secrets/raw?workspaceId=$KMS_WORKSPACE&secretPath=$PATHQ&environment=$ENV" \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
get() { echo "$SECRETS" | jq -r ".secrets[]|select(.secretKey==\"$1\")|.secretValue // empty"; }
|
||||
GHCR_USER=$(get GHCR_USER); GHCR_TOKEN=$(get GHCR_TOKEN); KUBECONFIG_B64=$(get KUBECONFIG)
|
||||
: "${GHCR_USER:=${{ github.actor }}}"
|
||||
if [ -z "$GHCR_TOKEN" ]; then echo "::error::GHCR_TOKEN not found in KMS"; exit 1; fi
|
||||
echo "::add-mask::$GHCR_TOKEN"
|
||||
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin
|
||||
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" | jq -r '.accessToken // empty')
|
||||
if [ -z "$TOKEN" ]; then echo "::warning::KMS login failed (org=$ORG client=$KMS_CLIENT_ID) — cross-org private deps & deploy unavailable"; exit 0; fi
|
||||
get() { curl -sf "$KMS_ENDPOINT/v1/kms/orgs/$ORG/secrets/$PATHQ/$1?env=$ENV" -H "Authorization: Bearer $TOKEN" | jq -r '.secret.value // empty'; }
|
||||
# Cross-org private Go module read token → buildx `gh_token` secret.
|
||||
# GIT_TOKEN is not a reserved name, so it's safe in GITHUB_ENV.
|
||||
GIT_TOKEN=$(get GITHUB_TOKEN)
|
||||
if [ -n "$GIT_TOKEN" ]; then echo "::add-mask::$GIT_TOKEN"; echo "GIT_TOKEN=$GIT_TOKEN" >> "$GITHUB_ENV"; fi
|
||||
KUBECONFIG_B64=$(get KUBECONFIG)
|
||||
if [ -n "$KUBECONFIG_B64" ]; then echo "$KUBECONFIG_B64" | base64 -d > "$RUNNER_TEMP/kubeconfig"; echo "kubeconfig=$RUNNER_TEMP/kubeconfig" >> "$GITHUB_OUTPUT"; fi
|
||||
|
||||
- name: Build & push images (per hanzo.yml)
|
||||
if: inputs.mode != 'delegate'
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Build-time private cross-org Go module read (the buildx `gh_token`
|
||||
# secret): prefer the KMS-fetched GIT_TOKEN, else fall back to the org
|
||||
# GH_PAT — the SAME BuildKit gh_token cloud's release.yml uses (proven
|
||||
# working). Keeps image builds green when the KMS deploy-cred fetch is
|
||||
# unavailable (a repo with a private cross-org dep like hanzoai/cloud
|
||||
# otherwise fails `go mod tidy` with git exit 128 in the buildx stage).
|
||||
# No-op for public-only builds when both are empty. Exported so the
|
||||
# `--secret id=gh_token,env=GIT_TOKEN` below reads it from the env.
|
||||
export GIT_TOKEN="${GIT_TOKEN:-${GH_PAT:-}}"
|
||||
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
IS_TAG=$([ "${{ github.ref_type }}" = "tag" ] && echo 1 || echo 0)
|
||||
VER="${{ github.ref_name }}"; VER="${VER#v}"
|
||||
python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(open('hanzo.yml'))['images']))" | jq -c '.[]' | while read -r img; do
|
||||
name=$(echo "$img"|jq -r .name); ctx=$(echo "$img"|jq -r .context)
|
||||
df=$(echo "$img"|jq -r '.dockerfile // (.context+"/Dockerfile")'); repo=$(echo "$img"|jq -r .repo)
|
||||
sfx=$(echo "$img"|jq -r '."tag-suffix"')
|
||||
TAGS="-t $repo:sha-${SHORT}-amd64-${sfx} -t $repo:${sfx}-latest"
|
||||
[ "$IS_TAG" = 1 ] && TAGS="$TAGS -t $repo:${VER}-amd64-${sfx}"
|
||||
echo "::group::build $name → $repo (${sfx})"
|
||||
docker buildx build --platform linux/amd64 --push $TAGS -f "$df" "$ctx"
|
||||
# tag-suffix is OPTIONAL (most repos ship a single variant). When set
|
||||
# (e.g. "ce"/"ee") it qualifies every tag; when absent the tags are
|
||||
# clean (no trailing dash). Build + deploy must agree on this shape.
|
||||
sfx=$(echo "$img"|jq -r '."tag-suffix" // ""')
|
||||
# platforms is OPT-IN per image in hanzo.yml (default: amd64 only, so
|
||||
# every existing repo's tag shape "-amd64" is UNCHANGED). Set e.g.
|
||||
# platforms: [linux/amd64, linux/arm64]
|
||||
# to emit a multi-arch MANIFEST LIST — one digest serving both arches.
|
||||
# DOKS has no arm64 nodes, so arm64 builds via buildx QEMU emulation
|
||||
# (binfmt set up below); pure-Go (CGO_ENABLED=0) Dockerfiles that honor
|
||||
# $TARGETARCH cross-compile natively (fast, no emulation). For true
|
||||
# native-speed arm64, register a bare-metal arm64 host (spark/GB10) as
|
||||
# the hanzo-build-linux-arm64 self-hosted runner (values-build-arm64.yaml).
|
||||
plats=$(echo "$img"|jq -r '(.platforms // ["linux/amd64"]) | join(",")')
|
||||
if [ "$plats" = "linux/amd64" ]; then
|
||||
# single-arch: keep the exact legacy tag shape (-amd64) deploys expect.
|
||||
TAGS="-t $repo:sha-${SHORT}-amd64${sfx:+-$sfx} -t $repo:${sfx:+$sfx-}latest"
|
||||
[ "$IS_TAG" = 1 ] && TAGS="$TAGS -t $repo:${VER}-amd64${sfx:+-$sfx}"
|
||||
else
|
||||
# multi-arch: one arch-neutral manifest-list tag (no -amd64 suffix).
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null 2>&1 || true
|
||||
TAGS="-t $repo:sha-${SHORT}${sfx:+-$sfx} -t $repo:${sfx:+$sfx-}latest"
|
||||
[ "$IS_TAG" = 1 ] && TAGS="$TAGS -t $repo:${VER}${sfx:+-$sfx}"
|
||||
fi
|
||||
echo "::group::build $name → $repo (${sfx}) [$plats]"
|
||||
# GIT_TOKEN (from KMS, via GITHUB_ENV) is passed as the `gh_token`
|
||||
# BuildKit secret so Dockerfiles can clone private Go modules; omitted
|
||||
# cleanly when absent (public-only builds unaffected).
|
||||
docker buildx build --platform "$plats" ${GIT_TOKEN:+--secret id=gh_token,env=GIT_TOKEN} --push $TAGS -f "$df" "$ctx"
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Test (per hanzo.yml)
|
||||
if: inputs.mode != 'delegate'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(open('hanzo.yml')).get('test') or []))" | jq -c '.[]' | while read -r t; do
|
||||
@@ -77,7 +264,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Deploy (per hanzo.yml)
|
||||
if: github.event_name != 'pull_request' && steps.kms.outputs.kubeconfig != ''
|
||||
if: inputs.mode != 'delegate' && github.event_name != 'pull_request' && steps.kms.outputs.kubeconfig != ''
|
||||
env:
|
||||
KUBECONFIG: ${{ steps.kms.outputs.kubeconfig }}
|
||||
run: |
|
||||
@@ -94,8 +281,8 @@ jobs:
|
||||
python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(open('hanzo.yml'))['deploy']['services']))" | jq -c '.[]' | while read -r s; do
|
||||
svc=$(echo "$s"|jq -r .name); imgname=$(echo "$s"|jq -r .image)
|
||||
repo=$(jq -r --arg n "$imgname" '.[]|select(.name==$n)|.repo' /tmp/imgs.json)
|
||||
sfx=$(jq -r --arg n "$imgname" '.[]|select(.name==$n)|."tag-suffix"' /tmp/imgs.json)
|
||||
ref="$repo:sha-${SHORT}-amd64-${sfx}"
|
||||
sfx=$(jq -r --arg n "$imgname" '.[]|select(.name==$n)|."tag-suffix" // ""' /tmp/imgs.json)
|
||||
ref="$repo:sha-${SHORT}-amd64${sfx:+-$sfx}"
|
||||
echo "rolling $svc → $ref"
|
||||
kubectl -n "$NS" set image "deployment/$svc" "*=$ref"
|
||||
kubectl -n "$NS" rollout status "deployment/$svc" --timeout=180s
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="ci" width="880"></p>
|
||||
|
||||
# hanzoai/ci
|
||||
|
||||
One reusable CI/CD workflow for every Hanzo / Lux / Zoo repo. Build + test +
|
||||
@@ -50,6 +52,33 @@ build minutes). To run on **your own** self-hosted arc runners, pass their label
|
||||
secrets: inherit
|
||||
```
|
||||
|
||||
## Delegate to platform (skip runner buildx)
|
||||
|
||||
By default the build runs buildx **on** the arc runner. To instead hand the build
|
||||
to **platform.hanzo.ai** — which builds in-cluster with BuildKit and rolls the
|
||||
service itself — pass `mode: delegate`:
|
||||
|
||||
```yaml
|
||||
uses: hanzoai/ci/.github/workflows/build.yml@v1
|
||||
with:
|
||||
mode: delegate
|
||||
secrets: inherit
|
||||
```
|
||||
|
||||
The GitHub job then just POSTs each image in `hanzo.yml` to platform's direct
|
||||
build webhook (`/v1/arcd/enqueue`) and exits in **seconds** — no runner buildx,
|
||||
no KMS, no runner-side deploy. Platform creates the build job, launches an
|
||||
in-cluster BuildKit Job on its own pool, pushes to the registry, and patches the
|
||||
operator `Service` CR to roll it. It's the same build path as the platform
|
||||
GitHub-App webhook — one build path, two front doors.
|
||||
|
||||
Requires one extra secret, `PLATFORM_BUILD_CALLBACK_TOKEN` (org- or repo-level,
|
||||
picked up via `secrets: inherit`). Override the endpoint with the
|
||||
`PLATFORM_ENQUEUE_URL` repo/org variable (default `https://platform.hanzo.ai/v1/arcd/enqueue`).
|
||||
|
||||
`mode: buildx` (the default) is unchanged — existing repos keep running buildx on
|
||||
arc, so delegation is strictly opt-in.
|
||||
|
||||
## Credentials
|
||||
|
||||
The only GitHub secrets a repo sets are `KMS_CLIENT_ID` / `KMS_CLIENT_SECRET`
|
||||
|
||||
Reference in New Issue
Block a user