diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 45afcf1..989572e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -14,9 +14,10 @@ jobs: steps: - uses: actions/checkout@v6 - uses: docker/setup-buildx-action@v3 - # Multi-arch builds for releases happen in release.yaml via GoReleaser - # (uses native runners + Go cross-compile, not QEMU). This job is a fast - # PR smoke test — single-platform, host-arch. + # Multi-arch builds for releases happen in release.yaml via + # docker/build-push-action (uses Go cross-compile under + # BUILDPLATFORM, not QEMU). This job is a fast PR smoke test: + # single-platform, host-arch. - uses: docker/login-action@v4 if: github.event_name == 'push' with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d697e71..1405985 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -59,19 +59,50 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Sign container image tags with cosign (keyless OIDC) - env: - COSIGN_EXPERIMENTAL: "true" + - name: Compute image tags + id: image_tags run: | - IMAGE="ghcr.io/stubbi/hermes-operator" TAG="${{ github.ref_name }}" MAJOR_MINOR=$(echo "$TAG" | sed 's/^v//' | cut -d. -f1,2) - for t in "${TAG}" "${MAJOR_MINOR}" "latest"; do - DIGEST=$(docker buildx imagetools inspect "${IMAGE}:${t}" --format '{{.Manifest.Digest}}' 2>/dev/null || true) - if [ -n "$DIGEST" ]; then - cosign sign --yes "${IMAGE}@${DIGEST}" - fi - done + IMAGE="ghcr.io/stubbi/hermes-operator" + { + echo "image=${IMAGE}" + echo "major_minor=${MAJOR_MINOR}" + } >> "$GITHUB_OUTPUT" + { + echo "tags<> "$GITHUB_OUTPUT" + + - name: Build and push container image (multi-arch) + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.image_tags.outputs.tags }} + provenance: true + sbom: false + cache-from: type=gha + cache-to: type=gha,mode=max + labels: | + org.opencontainers.image.title=hermes-operator + org.opencontainers.image.version=${{ github.ref_name }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=https://github.com/stubbi/hermes-operator + org.opencontainers.image.licenses=Apache-2.0 + + - name: Sign container image with cosign (keyless OIDC) + env: + COSIGN_EXPERIMENTAL: "true" + IMAGE: ${{ steps.image_tags.outputs.image }} + DIGEST: ${{ steps.docker_build.outputs.digest }} + run: cosign sign --yes "${IMAGE}@${DIGEST}" - name: Generate SBOM for container image uses: anchore/sbom-action@v0 @@ -83,13 +114,10 @@ jobs: - name: Attest SBOM with cosign env: COSIGN_EXPERIMENTAL: "true" - run: | - IMAGE="ghcr.io/stubbi/hermes-operator" - TAG="${{ github.ref_name }}" - DIGEST=$(docker buildx imagetools inspect "${IMAGE}:${TAG}" --format '{{.Manifest.Digest}}' 2>/dev/null || true) - if [ -n "$DIGEST" ]; then - cosign attest --yes --predicate "sbom-${TAG}.spdx.json" --type spdxjson "${IMAGE}@${DIGEST}" - fi + IMAGE: ${{ steps.image_tags.outputs.image }} + DIGEST: ${{ steps.docker_build.outputs.digest }} + TAG: ${{ github.ref_name }} + run: cosign attest --yes --predicate "sbom-${TAG}.spdx.json" --type spdxjson "${IMAGE}@${DIGEST}" - name: Upload SBOM to GitHub release uses: softprops/action-gh-release@v2 diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d48f6b8..70155a3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -19,35 +19,10 @@ builds: - -X main.commit={{.Commit}} - -X main.date={{.Date}} -dockers_v2: - - id: hermes-operator - dockerfile: Dockerfile - ids: - - manager - images: - - "ghcr.io/stubbi/hermes-operator" - tags: - - "{{ .Tag }}" - - "{{ .Major }}.{{ .Minor }}" - - "latest" - platforms: - - linux/amd64 - - linux/arm64 - labels: - "org.opencontainers.image.title": "{{ .ProjectName }}" - "org.opencontainers.image.version": "{{ .Version }}" - "org.opencontainers.image.revision": "{{ .FullCommit }}" - "org.opencontainers.image.created": "{{ .Date }}" - "org.opencontainers.image.source": "{{ .GitURL }}" - "org.opencontainers.image.licenses": "Apache-2.0" - build_args: - PREBUILT_BINARY: "manager" - extra_files: - - cmd/ - - api/ - - internal/ - - go.mod - - go.sum +# Container images are built separately by docker/build-push-action in the +# release workflow. GoReleaser's dockers_v2 had trouble staging the prebuilt +# binary into the build context, and buildx with native Go cross-compile +# (BUILDPLATFORM) is faster than QEMU anyway. archives: - id: binaries diff --git a/Dockerfile b/Dockerfile index b30665f..5a660a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,22 @@ -ARG PREBUILT_BINARY="" - -# Build the manager binary -FROM golang:1.26 AS builder -ARG PREBUILT_BINARY +# Build the manager binary. Use BUILDPLATFORM so Go cross-compiles natively +# rather than running the whole compile under QEMU emulation, which is +# pathologically slow for Go. +FROM --platform=$BUILDPLATFORM golang:1.26 AS builder ARG TARGETOS ARG TARGETARCH WORKDIR /workspace -# Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN if [ -z "$PREBUILT_BINARY" ]; then go mod download; fi +RUN go mod download -# Copy the go source COPY cmd/ cmd/ COPY api/ api/ COPY internal/ internal/ -# GoReleaser will COPY a prebuilt binary in; otherwise build from source. -COPY ${PREBUILT_BINARY:-cmd/main.go} ./prebuilt-or-main -RUN set -eu; \ - if [ -n "${PREBUILT_BINARY:-}" ]; then \ - cp ./prebuilt-or-main /workspace/manager && chmod +x /workspace/manager; \ - else \ - rm ./prebuilt-or-main && \ - CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o /workspace/manager cmd/main.go; \ - fi +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ + go build -a -ldflags="-s -w" -o /workspace/manager cmd/main.go -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager .