mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](80c7e94dd9...dc80280410)
---
updated-dependencies:
- dependency-name: docker/metadata-action
dependency-version: 6.2.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
271 lines
13 KiB
YAML
271 lines
13 KiB
YAML
name: Docker Build & Push
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
pull_request:
|
|
# workflow_dispatch is allowed for dry-run testing only. Publishing is still
|
|
# exclusively tag-driven so that every signed image corresponds 1:1 to a
|
|
# published `gitnexus@X.Y.Z` on npm. dry_run:true (the default) skips all
|
|
# push, sign, and attestation steps — the build runs but nothing is published.
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: 'Build only — skip push, signing, and attestations'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
workflow_call:
|
|
inputs:
|
|
tag:
|
|
description: >-
|
|
The full v-prefixed tag to build (e.g. v1.2.3-rc.1).
|
|
The tag must already exist in the repo and its tree must contain
|
|
a gitnexus/package.json whose version matches the tag.
|
|
required: true
|
|
type: string
|
|
# Explicit secret contract — callers pass these by name. Replaces the
|
|
# blanket `secrets: inherit` pattern (zizmor `secrets-inherit` audit).
|
|
# GHCR auth uses the implicit GITHUB_TOKEN; only Docker Hub credentials
|
|
# need to be passed through.
|
|
secrets:
|
|
DOCKERHUB_USERNAME:
|
|
required: true
|
|
DOCKERHUB_TOKEN:
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Concurrency convention: see CONTRIBUTING.md → "GitHub Actions — Concurrency Convention".
|
|
# Tag refs are unique per release, so distinct tags run in parallel.
|
|
# Re-pushes of the same tag serialize. cancel-in-progress: false — never cancel a publish mid-flight.
|
|
# Hardcoded `docker-build-push-` prefix (not `${{ github.workflow }}`) when invoked as a reusable
|
|
# workflow: in called-workflow context `github.workflow` is ambiguous and could resolve to the
|
|
# caller's name, sharing a concurrency group with the caller → deadlock.
|
|
# Direct tag-push invocations use `docker-build-push-<ref>`; workflow_call invocations get a
|
|
# per-run-unique group (they are already serialized by the caller's own concurrency group).
|
|
concurrency:
|
|
group: ${{ (github.event_name == 'push') && format('docker-build-push-{0}', github.ref) || format('docker-build-push-nested-{0}', github.run_id) }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build-push:
|
|
name: Build & Push ${{ matrix.image.name }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
# Required for Cosign keyless signing via the OIDC token exchange,
|
|
# and for build provenance / SBOM attestations.
|
|
id-token: write
|
|
attestations: write
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image:
|
|
# Static UI bundle. Small, fast image. Drop-in replacement for the
|
|
# legacy single-image setup at the same `gitnexus` repository slug
|
|
# is intentionally avoided — the UI now lives at `gitnexus-web` and
|
|
# the CLI/server takes the canonical `gitnexus` slug below.
|
|
- name: gitnexus-web
|
|
dockerfile: Dockerfile.web
|
|
slug: gitnexus-web
|
|
# CLI / `gitnexus serve` backend. Heavy native deps (tree-sitter,
|
|
# onnxruntime-node) live only in this image.
|
|
- name: gitnexus
|
|
dockerfile: Dockerfile.cli
|
|
slug: gitnexus
|
|
|
|
steps:
|
|
# Only the workflow_call path requires a non-empty `inputs.tag` — callers
|
|
# (publish.yml in RC mode) must pass the RC tag explicitly. On direct
|
|
# tag pushes the tag comes from `github.ref`, so `inputs.tag` is always
|
|
# empty and validating it here would break every real release (#1064).
|
|
# The downstream "Verify tag matches gitnexus/package.json version" step
|
|
# handles both event types by falling back to GITHUB_REF.
|
|
- name: Validate tag input
|
|
if: github.event_name == 'workflow_call'
|
|
shell: bash
|
|
env:
|
|
TAG_INPUT: ${{ inputs.tag }}
|
|
run: |
|
|
if [ -z "${TAG_INPUT}" ]; then
|
|
echo "::error::No tag provided to docker.yml — refusing to build/push."
|
|
exit 1
|
|
fi
|
|
|
|
# When triggered by workflow_call the caller passes the RC tag as an input;
|
|
# we check out that tag so the Dockerfile and package.json match the built image.
|
|
# For tag-push events github.ref is already the tag ref — no override needed.
|
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
ref: ${{ inputs.tag || github.ref }}
|
|
|
|
# ── Lock the docker image version to the npm package version ──────────
|
|
# Mirrors the check in publish.yml: refuse to build unless the git tag
|
|
# exactly matches `gitnexus/package.json`'s version. This guarantees
|
|
# `ghcr.io/<owner>/gitnexus:X.Y.Z` always corresponds to the same
|
|
# `gitnexus@X.Y.Z` published to npm — no drift, no surprises.
|
|
- name: Verify tag matches gitnexus/package.json version
|
|
id: version
|
|
if: github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request'
|
|
shell: bash
|
|
env:
|
|
# For workflow_call the tag comes from the caller input; for push events
|
|
# it is derived from GITHUB_REF (set to empty so the else-branch fires).
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
if [ -n "$INPUT_TAG" ]; then
|
|
TAG_VERSION="${INPUT_TAG#v}"
|
|
else
|
|
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
|
|
fi
|
|
if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
echo "::error::Tag does not follow semver: v$TAG_VERSION"
|
|
exit 1
|
|
fi
|
|
PKG_VERSION=$(node -p "require('./gitnexus/package.json').version")
|
|
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
echo "::error::Tag version (v$TAG_VERSION) does not match gitnexus/package.json version ($PKG_VERSION)"
|
|
exit 1
|
|
fi
|
|
echo "version=$PKG_VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "Version verified: $PKG_VERSION"
|
|
|
|
# Required for multi-platform (linux/arm64) emulation.
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
|
|
|
- name: Install Cosign
|
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Docker Hub is a mirror of GHCR: same tags, same digests, same Cosign
|
|
# signatures. GHCR remains authoritative (it is the registry the
|
|
# ClusterImagePolicy globs against by default), but Docker Hub is the
|
|
# registry most users reach for first, so we publish there too.
|
|
# Requires repo secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (a scoped
|
|
# access token, NOT the account password) with write access to the
|
|
# `akonlabs/gitnexus` and `akonlabs/gitnexus-web` repos.
|
|
- name: Log in to Docker Hub
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
# Computes image tags and labels from the verified semver tag:
|
|
# v1.2.3 → :1.2.3, :1.2, :1, :latest (auto, only for non-prerelease)
|
|
# v1.2.3-rc.1 → :1.2.3-rc.1 only (prereleases never become :latest)
|
|
# `:latest` is only emitted for tag pushes thanks to `flavor: latest=auto`,
|
|
# ensuring it always points at a real npm-published version.
|
|
#
|
|
# For workflow_call invocations github.ref is the caller's branch ref, so
|
|
# the type=semver patterns would not match. In that case we add an explicit
|
|
# type=raw tag using the version already verified above, so the same
|
|
# image-naming rules apply regardless of how the workflow was triggered.
|
|
# NOTE: We check `inputs.tag` rather than `github.event_name` because in a
|
|
# reusable workflow the github context is inherited from the caller —
|
|
# `github.event_name` would still be "push", not "workflow_call".
|
|
- name: Extract Docker metadata
|
|
id: meta
|
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
with:
|
|
# Dual-registry publish. metadata-action expands the same tag set
|
|
# against every image ref listed here, and build-push-action pushes
|
|
# one build to all of them, so the GHCR and Docker Hub images share
|
|
# a digest and are byte-identical. The Docker Hub namespace
|
|
# (`akonlabs`) is hardcoded because it differs from the GitHub org
|
|
# (`abhigyanpatwari`) — `github.repository_owner` would produce the
|
|
# wrong ref.
|
|
images: |
|
|
ghcr.io/${{ github.repository_owner }}/${{ matrix.image.slug }}
|
|
docker.io/akonlabs/${{ matrix.image.slug }}
|
|
flavor: latest=auto
|
|
tags: |
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=raw,value=${{ steps.version.outputs.version }},enable=${{ inputs.tag != '' }}
|
|
|
|
# Transient 502s from GHCR / Docker Hub / GHA cache during multi-platform
|
|
# exports are retried inside `.github/actions/docker-build-push-retry`
|
|
# (see docker/build-push-action#1422 — retry policy stays out of the
|
|
# upstream action). `ignore-error=true` on cache-to avoids cache export
|
|
# flakes failing an otherwise successful push.
|
|
- name: Build and push
|
|
id: build
|
|
uses: ./.github/actions/docker-build-push-retry
|
|
with:
|
|
context: .
|
|
file: ${{ matrix.image.dockerfile }}
|
|
platforms: linux/amd64,linux/arm64
|
|
push: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=${{ matrix.image.slug }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.image.slug }},ignore-error=true
|
|
|
|
# Cosign keyless signing. Each pushed tag is signed by the workflow's
|
|
# OIDC identity, so consumers can verify the image with the strict,
|
|
# fully-anchored identity regex (kept in sync with README.md and
|
|
# deploy/kubernetes/cluster-image-policy.yaml — update all three together).
|
|
# NOTE: `${...}` expression syntax is NOT evaluated inside YAML comments, so
|
|
# the example below uses literal `<owner>/<repo>` placeholders that consumers
|
|
# substitute themselves; the canonical, fully-rendered command lives in README.md.
|
|
# cosign verify ghcr.io/<owner>/<slug>:<tag> \
|
|
# --certificate-identity-regexp '^https://github\.com/<owner>/<repo>/\.github/workflows/docker\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$' \
|
|
# --certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
# Do NOT relax to `@.*` — that accepts signatures from any ref, including
|
|
# unprotected branches and PRs, and defeats the supply-chain guarantee.
|
|
- name: Sign image with Cosign (keyless)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
env:
|
|
# Cosign v2 (installed by sigstore/cosign-installer above) makes
|
|
# keyless the default. COSIGN_EXPERIMENTAL is a v1-only opt-in flag
|
|
# that is now deprecated/no-op, so it is intentionally omitted.
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
TAGS: ${{ steps.meta.outputs.tags }}
|
|
run: |
|
|
# Sign every tag at the same digest so consumers can verify by tag or by digest.
|
|
# Use `while read` instead of `for $TAGS` to be robust against tags that
|
|
# could ever contain whitespace (the metadata-action output is newline-
|
|
# separated, not space-separated).
|
|
while IFS= read -r tag; do
|
|
[[ -n "$tag" ]] && cosign sign --yes "${tag}@${DIGEST}"
|
|
done <<< "$TAGS"
|
|
|
|
# Attach the SBOM produced by buildx as a verifiable attestation on the
|
|
# digest. Attestations are pushed as OCI referrers to the registry named
|
|
# in `subject-name`, so we call the action once per registry. The digest
|
|
# is identical across registries (same build, same push), so consumers
|
|
# pulling from either GHCR or Docker Hub see the same provenance.
|
|
- name: Generate build provenance attestation (GHCR)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
|
with:
|
|
subject-name: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.slug }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
- name: Generate build provenance attestation (Docker Hub)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
|
with:
|
|
subject-name: docker.io/akonlabs/${{ matrix.image.slug }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|