From f15ebfa713981ee055781dee57a94e4960bcd26d Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Sat, 23 May 2026 09:16:47 -0400 Subject: [PATCH 1/2] ci(release): parallelize image builds + add buildx cache + fix e2e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to release.yml, addressing distinct issues: 1. **Parallel matrix builds**. The build-and-push job built kagenti- operator and agentcard-signer sequentially in one runner — with QEMU-emulated arm64 builds taking ~20 minutes each, total wall time was ~42 minutes. Refactor into a matrix of two jobs (one per image) running on separate runners. Wall time drops to ~22 minutes (max of the two parallel jobs) with no other change. 2. **buildx GitHub Actions cache**. Add cache-from / cache-to with per-image scope so subsequent runs benefit from layer reuse. Warm builds drop further (~5–10 minutes typical). Per-image scope prevents cache key collisions across the matrix. 3. **e2e-helm-install image-tag override**. The chart ships with `tag: __PLACEHOLDER__`, which is substituted only in the tag-push release path. On main pushes the e2e job re-checks-out the repo (pristine values.yaml), runs `helm install` with no override, and the rendered Deployment tries to pull `:__PLACEHOLDER__` — ErrImagePull, deployment never Ready, 120s rollout timeout. Pass `--set controllerManager.container.image.tag=latest` so the e2e actually exercises the just-built image. Bonus: split the helm-chart-packaging + GitHub-Release steps into a separate `release` job (gated `if: github.ref_type == 'tag'`) since the original sequence-of-steps shape doesn't survive matrix-ization. Also harden the `gh release create` step against `Release.tag_name already exists` (occurs when a maintainer pre-creates the release out of band before pushing the tag). The e2e-helm-install bug is pre-existing (every recent main push has been failing on the rollout timeout). The build slowness is pre-existing too. Bundling the fixes lets one workflow review cover them together. Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- .github/workflows/release.yml | 98 ++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c3da9db..bfbb9bb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,8 +22,21 @@ env: CHARTS_PATH: ./charts jobs: + # Build the two images in parallel, one matrix instance per image. + # Each instance carries its own buildx cache scope so warm builds + # only re-pull what changed for that image. build-and-push: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image_config: + - name: kagenti-operator + context: ./kagenti-operator + dockerfile: ./kagenti-operator/Dockerfile + - name: agentcard-signer + context: ./kagenti-operator + dockerfile: ./kagenti-operator/cmd/agentcard-signer/Dockerfile steps: - name: Checkout uses: actions/checkout@v6 @@ -41,61 +54,56 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # --- kagenti-operator image --- - - name: Extract Docker metadata (kagenti-operator) - id: operator-meta + - name: Extract Docker metadata + id: meta uses: docker/metadata-action@v6 with: - images: ${{ env.REGISTRY }}/${{ github.repository }}/kagenti-operator + images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.image_config.name }} tags: | type=sha,prefix={{branch}}-,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}} type=semver,pattern={{version}} - - name: Build and push kagenti-operator + - name: Build and push ${{ matrix.image_config.name }} uses: docker/build-push-action@v7 with: - context: ./kagenti-operator - file: ./kagenti-operator/Dockerfile + context: ${{ matrix.image_config.context }} + file: ${{ matrix.image_config.dockerfile }} push: true platforms: linux/amd64,linux/arm64 - tags: ${{ steps.operator-meta.outputs.tags }} - labels: ${{ steps.operator-meta.outputs.labels }} - - # --- agentcard-signer image --- - - name: Extract Docker metadata (agentcard-signer) - id: signer-meta - uses: docker/metadata-action@v6 - with: - images: ${{ env.REGISTRY }}/${{ github.repository }}/agentcard-signer - tags: | - type=sha,prefix={{branch}}-,enable={{is_default_branch}} - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - - - name: Build and push agentcard-signer - uses: docker/build-push-action@v7 - with: - context: ./kagenti-operator - file: ./kagenti-operator/cmd/agentcard-signer/Dockerfile - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.signer-meta.outputs.tags }} - labels: ${{ steps.signer-meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # GitHub Actions cache, scoped per image so the two parallel + # matrix jobs don't trample each other. + cache-from: type=gha,scope=${{ matrix.image_config.name }} + cache-to: type=gha,mode=max,scope=${{ matrix.image_config.name }} + + # Release-only steps (helm chart packaging + GitHub Release). Runs + # once after both image builds complete, only on tag pushes. + release: + needs: build-and-push + if: github.ref_type == 'tag' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 - # --- Release-only steps (tag pushes) --- - name: Set up Helm - if: github.ref_type == 'tag' uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install yq - if: github.ref_type == 'tag' uses: mikefarah/yq@v4 + - name: Log in to ghcr.io + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Package and push Helm chart - if: github.ref_type == 'tag' run: | chartVersion=$(echo "${{ github.ref_name }}" | cut -c 2-) chartPackageName="kagenti-operator-chart-${chartVersion}.tgz" @@ -106,8 +114,19 @@ jobs: helm push "./${chartPackageName}" oci://${{ env.REGISTRY }}/${{ env.REPO }} - name: Create GitHub Release - if: github.ref_type == 'tag' - run: gh release create "${{ github.ref_name }}" --generate-notes + run: | + # Idempotent: the release may have been pre-created out of band + # (e.g., a maintainer ran `gh release create` before pushing the + # tag). Treat 422 "already_exists" as success so the workflow + # doesn't fail on a benign race. + if ! gh release create "${{ github.ref_name }}" --generate-notes 2> /tmp/release.err; then + if grep -q "already_exists\|Release.tag_name already exists" /tmp/release.err; then + echo "Release ${{ github.ref_name }} already exists; skipping create." + else + cat /tmp/release.err >&2 + exit 1 + fi + fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -130,8 +149,13 @@ jobs: kubectl -n cert-manager rollout status deployment/cert-manager-cainjector --timeout=90s - name: Install Helm chart + # The chart's values.yaml ships with `tag: __PLACEHOLDER__`, which + # the release job substitutes only on tag pushes. On main pushes + # we override to `:latest` (which the build-and-push job just + # tagged) so the rendered Deployment can actually pull an image. run: | - helm install kagenti-operator ./charts/kagenti-operator + helm install kagenti-operator ./charts/kagenti-operator \ + --set controllerManager.container.image.tag=latest - name: Wait for deployment rollout run: | From 1876727b7d2745f8fad217ac38bb4df1569f8af3 Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Sat, 23 May 2026 09:26:01 -0400 Subject: [PATCH 2/2] fixup(ci): use gh release view to check existence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on PR #374. The previous idempotency guard grepped stderr from `gh release create` for "already_exists" / "Release.tag_name already exists" — fragile to gh CLI message changes and locale. Replace with a `gh release view` precheck: short-circuit if the release already exists, otherwise create it. Same outcome, no string matching. Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- .github/workflows/release.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfbb9bb5..ed75a606 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,15 +117,14 @@ jobs: run: | # Idempotent: the release may have been pre-created out of band # (e.g., a maintainer ran `gh release create` before pushing the - # tag). Treat 422 "already_exists" as success so the workflow - # doesn't fail on a benign race. - if ! gh release create "${{ github.ref_name }}" --generate-notes 2> /tmp/release.err; then - if grep -q "already_exists\|Release.tag_name already exists" /tmp/release.err; then - echo "Release ${{ github.ref_name }} already exists; skipping create." - else - cat /tmp/release.err >&2 - exit 1 - fi + # tag for custom notes). Use `gh release view` to short-circuit + # rather than grepping stderr from `gh release create` — that + # text is locale-dependent and a moving target across gh + # versions. + if gh release view "${{ github.ref_name }}" >/dev/null 2>&1; then + echo "Release ${{ github.ref_name }} already exists; skipping create." + else + gh release create "${{ github.ref_name }}" --generate-notes fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}