-
Notifications
You must be signed in to change notification settings - Fork 27
feat :Testing workflow to push images #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
10d7fbe
66eb0d2
097ee87
da7d3a9
d8c73c0
d982edd
fc3f26a
75b84bd
28a464d
0ae7915
9d74990
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # Manually-triggered build+push of the endpoints client image to GHCR. | ||
| # | ||
| # Run it from the Actions tab ("Run workflow"): pick the branch/tag in the UI (that | ||
| # selection is github.sha), or type an explicit SHA/branch/tag in the `ref` input. | ||
| # The image is pushed to ghcr.io/mlcommons/endpoints tagged with the short commit | ||
| # SHA and the ref name, using the automatic GITHUB_TOKEN (no stored secret needed). | ||
| name: Publish client image | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| ref: | ||
| description: "Git ref to build (branch/tag/SHA). Blank = the ref selected above." | ||
| required: false | ||
| type: string | ||
| platforms: | ||
| description: "Target platform(s)" | ||
| required: true | ||
| default: linux/amd64 | ||
| type: choice | ||
| options: | ||
| - linux/amd64 | ||
| - linux/amd64,linux/arm64 | ||
| provision_dsr1: | ||
| description: "Bake in the DeepSeek-R1 accuracy evaluator (heavier; needs build-time network)" | ||
| required: true | ||
| default: "1" | ||
| type: choice | ||
| options: | ||
| - "1" | ||
| - "0" | ||
| no_cache: | ||
| description: "Build with --no-cache (clean/reproducible, slower)" | ||
| required: true | ||
| default: "true" | ||
| type: choice | ||
| options: | ||
| - "true" | ||
| - "false" | ||
| force: | ||
| description: "Overwrite an existing :<sha> tag. platform/DSR1/cache are NOT part of the tag, so the first successful publish of a SHA wins; use force to re-publish a SHA (e.g. a different arch, or a failed run)." | ||
| required: true | ||
| default: "false" | ||
| type: choice | ||
| options: | ||
| - "false" | ||
| - "true" | ||
|
|
||
| # GITHUB_TOKEN needs packages:write to push to the org's GHCR package. | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
|
||
| jobs: | ||
| publish: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout selected ref | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| # Check out the ref *name* (not github.sha) so a local branch head exists | ||
| # and the script can resolve/tag it; blank input falls back to the | ||
| # dispatched branch/tag. Full history for `git rev-parse --short`. | ||
| ref: ${{ inputs.ref || github.ref_name }} | ||
| fetch-depth: 0 | ||
|
|
||
| # Registers QEMU binfmt handlers so the docker-container builder can build | ||
| # the non-native arch when platforms includes linux/arm64. | ||
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 | ||
|
|
||
| - name: Set up Docker Buildx | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Claude] low (design): This provisions a buildx builder that the script never uses — |
||
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | ||
|
|
||
| # Logs in as the user who triggered the run; the password is this run's | ||
| # automatic GITHUB_TOKEN (scoped by the permissions block above). | ||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Build and push | ||
| env: | ||
| ENDPOINTS_REF: ${{ inputs.ref || github.ref_name }} | ||
| PLATFORM: ${{ inputs.platforms }} | ||
| PROVISION_DSR1: ${{ inputs.provision_dsr1 }} | ||
| NO_CACHE: ${{ inputs.no_cache == 'true' && '1' || '0' }} | ||
| # Fixed flag or empty — the input value itself is never interpolated into the command. | ||
| FORCE_FLAG: ${{ inputs.force == 'true' && '--force' || '' }} | ||
| run: ./scripts/push_docker_image.sh $FORCE_FLAG | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| #!/usr/bin/env bash | ||
| # lib_registry.sh — shared registry helpers for the image push scripts. | ||
| # | ||
| # Not meant to be executed directly. `source` it from a push script. | ||
|
|
||
| # assert_gzip_layers REF — fail unless every layer of REF (walking a manifest list) | ||
| # is gzip-compressed. enroot/pyxis routes a layer to its decompressor by media type | ||
| # and does not recognise the docker-namespaced zstd type | ||
| # (application/vnd.docker.image.rootfs.diff.tar.zstd) that buildx emits for zstd base | ||
| # layers under oci-mediatypes=false; it hands the raw blob to tar, which dies with | ||
| # "tar: This does not look like a tar archive" (mlcommons/endpoints#467). Reads registry | ||
| # metadata only (no blob pull), via `docker buildx imagetools inspect`. Fails CLOSED: | ||
| # any inspect error blocks the publish rather than silently passing. | ||
| assert_gzip_layers() { | ||
| local ref="$1" | ||
| docker buildx imagetools inspect "$ref" --raw >/dev/null 2>&1 \ | ||
| || { echo "error: cannot inspect ${ref} to verify layer compression." >&2; return 2; } | ||
| # --raw yields either an image index (has .manifests) or a single manifest (has | ||
| # .layers). Parse with python for robustness across both shapes and media-type | ||
| # namespaces (docker + oci); descend one level for a manifest list. | ||
| python3 - "$ref" <<'PY' | ||
| import sys, json, subprocess | ||
| ref = sys.argv[1] | ||
| base = ref.split("@", 1)[0] | ||
| def raw(r): | ||
| return json.loads(subprocess.check_output( | ||
| ["docker", "buildx", "imagetools", "inspect", r, "--raw"])) | ||
| def layer_types(man): | ||
| return [layer["mediaType"] for layer in man.get("layers", [])] | ||
| top = raw(ref) | ||
| types = [] | ||
| if top.get("manifests"): # image index / manifest list | ||
| for child in top["manifests"]: | ||
| plat = child.get("platform", {}) | ||
| if plat.get("os") == "unknown" or plat.get("architecture") == "unknown": | ||
| continue # skip attestation manifests | ||
| types += layer_types(raw(f"{base}@{child['digest']}")) | ||
| else: # single-arch manifest | ||
| types = layer_types(top) | ||
| bad = sorted({mt for mt in types if not mt.endswith("gzip")}) | ||
| if bad: | ||
| sys.stderr.write( | ||
| f"error: {ref} has non-gzip layer(s): {', '.join(bad)}\n" | ||
| " enroot/pyxis cannot extract these (mlcommons/endpoints#467).\n" | ||
| " Rebuild via the buildx --platform path, which forces gzip.\n") | ||
| sys.exit(1) | ||
| print(f">> Verified: all {len(types)} layers of {ref} are gzip (enroot-safe).") | ||
| PY | ||
| } | ||
|
|
||
| # ref_exists_in_registry REF — probe whether REF is already published, reading registry | ||
| # metadata only (no blob pull) via `docker buildx imagetools inspect`. Return codes: | ||
| # 0 present | ||
| # 1 definitely absent (registry reported not-found) | ||
| # 2 indeterminate (auth / network / tooling error) — output echoed to stderr | ||
| # Callers enforcing an immutable tag MUST treat 2 as "block" (fail CLOSED): never | ||
| # overwrite when existence can't be verified, or the guard silently no-ops on exactly | ||
| # the hosts/creds where it can't check. | ||
| ref_exists_in_registry() { | ||
| local ref="$1" out | ||
| if out="$(docker buildx imagetools inspect "$ref" 2>&1)"; then | ||
| return 0 | ||
| fi | ||
| # Two error classes contain not-found-ish phrasing but are NOT an absent manifest, and | ||
| # classifying either as absent would let a push overwrite an immutable tag: | ||
| # - tooling failures: "docker-credential-xxx: executable file not found" | ||
| # - authorization hidden as absence: many registries (e.g. Docker Hub) answer an | ||
| # unauthorized/private repo with "repository does not exist or may require 'docker | ||
| # login': denied", which also matches "does not exist" below. | ||
| # Screen both to indeterminate FIRST so the not-found match can't fire on them (fail | ||
| # CLOSED). A truly-absent public tag (GHCR "<ref>: not found", ECR "name unknown") has | ||
| # none of these phrases and still resolves to rc=1. | ||
| if grep -qiE 'executable file not found|command not found|no such file or directory|permission denied|credential|denied|unauthorized|forbidden|requires? .*(login|auth)|may require|401|403|authentication' <<<"$out"; then | ||
| printf '%s\n' "$out" >&2 | ||
| return 2 | ||
| fi | ||
| # Registry "absent" phrasings: GHCR (`<ref>: not found`), OCI/Docker distribution | ||
| # (`manifest unknown`, `name unknown`), and ECR (`name unknown … does not exist`). | ||
| if grep -qiE 'not found|manifest unknown|manifest_unknown|name[ _]unknown|no such manifest|does not exist' <<<"$out"; then | ||
| return 1 | ||
| fi | ||
| printf '%s\n' "$out" >&2 | ||
| return 2 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Codex] medium (correctness): This checks out the dispatch ref directly, replacing the tree before the run step. Building a ref that predates this workflow means
./scripts/push_docker_image.shwon't exist and the run fails — even though the UI advertises building arbitrary refs. It also lets the target ref supply the script that runs after GHCR login.Fix: keep the checkout on the workflow's own (trusted) ref, and pass the target via
ENDPOINTS_REFso the script switches refs only for the Docker build. (Ensure the target ref is fetched — the script'sorigin/<ref>fallback needs the object present.)