From ed88516fc71877b22231968b13c0fb4bbd892d47 Mon Sep 17 00:00:00 2001 From: Entlein Date: Fri, 24 Apr 2026 13:31:14 +0200 Subject: [PATCH] ci: add mirror-sync workflow and empty fork-ci/ overlay directory Symmetric to k8sstormcenter/storage#12. Reacts to every push on upstream-pr/** by force-updating test-mirror/ with the working tree overlaid from fork-ci/ on main. This lets our internal CI exercise upstream-bound branches without polluting the upstream diff with fork-only workflow files. fork-ci/ is intentionally empty beyond a README today: node-agent's existing build.yaml + component-tests.yaml already behave correctly on any ref. The machinery is in place so that the moment a fork-only workflow tweak is needed (e.g. a push trigger including test-mirror/** or a different image registry), it has a home that will not leak into the upstream PR diff. Requires a CROSS_REPO_PAT secret with contents:write on the fork so the workflow can force-push refs/heads/test-mirror/**. Signed-off-by: Entlein --- .github/workflows/mirror-sync.yaml | 127 +++++++++++++++++++++++++++++ fork-ci/README.md | 26 ++++++ 2 files changed, 153 insertions(+) create mode 100644 .github/workflows/mirror-sync.yaml create mode 100644 fork-ci/README.md diff --git a/.github/workflows/mirror-sync.yaml b/.github/workflows/mirror-sync.yaml new file mode 100644 index 000000000..7b84b0885 --- /dev/null +++ b/.github/workflows/mirror-sync.yaml @@ -0,0 +1,127 @@ +name: mirror-sync + +# ============================================================================= +# Test-mirror syncing for upstream-bound PR branches +# ============================================================================= +# +# Branches named `upstream-pr/` are reserved for clean contributions +# to kubescape/node-agent — we cannot touch their `.github/` without +# polluting the upstream diff. This workflow reacts to each push by creating +# (or force-updating) `test-mirror/`: same code, but with the +# fork's working CI files overlaid from the `fork-ci/` directory on `main`. +# +# At the time of introduction `fork-ci/` here is empty (only a README) — the +# existing `.github/workflows/build.yaml` + `component-tests.yaml` already +# behave correctly on any ref. The machinery exists so that the moment a +# fork-only workflow tweak is needed, it has a home that does not leak into +# the upstream diff. +# +# This keeps the upstream-pr branch pristine for the reviewer while letting +# our internal CI exercise it on every push. +# ============================================================================= + +on: + push: + branches: + - 'upstream-pr/**' + workflow_dispatch: + inputs: + source_branch: + description: 'Source branch to mirror (defaults to the one this runs on)' + required: false + type: string + +concurrency: + group: mirror-sync-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + mirror: + runs-on: ubuntu-latest + steps: + - name: Resolve source branch + id: src + env: + DISPATCH_SOURCE: ${{ github.event.inputs.source_branch }} + run: | + set -euo pipefail + if [ -n "${DISPATCH_SOURCE:-}" ]; then + echo "branch=${DISPATCH_SOURCE}" >> "$GITHUB_OUTPUT" + else + # For push events, GITHUB_REF_NAME is the branch that received the push. + echo "branch=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + fi + + - name: Fail fast if the branch is not an upstream-pr/* branch + env: + BRANCH: ${{ steps.src.outputs.branch }} + run: | + set -euo pipefail + case "$BRANCH" in + upstream-pr/*) echo "ok — mirroring $BRANCH" ;; + *) echo "refusing to mirror non-upstream-pr branch: $BRANCH"; exit 1 ;; + esac + + - name: Checkout full history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: true + token: ${{ secrets.CROSS_REPO_PAT }} + + - name: Configure bot identity + run: | + git config user.name "k8sstorm-mirror-bot" + git config user.email "noreply@k8sstormcenter.io" + + - name: Build + push test-mirror branch + env: + BRANCH: ${{ steps.src.outputs.branch }} + run: | + set -euo pipefail + MIRROR="test-mirror/${BRANCH}" + + # Start from the pushed branch tip. + git checkout "$BRANCH" + + # Overlay fork-ci/ onto the working tree. fork-ci/ lives on main and + # holds any workflow files that must appear on test-mirror branches + # but not on the upstream-pr branch itself. Today it is empty beyond + # a README — the overlay is then a no-op and the mirror branch is + # byte-identical to the source. + git checkout main -- fork-ci || { + echo "fork-ci/ missing on main; nothing to overlay" + exit 1 + } + + if [ ! -d fork-ci ]; then + echo "fork-ci/ directory missing after checkout; abort" + exit 1 + fi + + # Copy fork-ci/ payload into the tree, skipping the README which is + # documentation for the directory itself and not intended to land + # on the mirror branch. + if [ -d fork-ci/.github ] || [ -n "$(ls -A fork-ci 2>/dev/null | grep -v '^README.md$' || true)" ]; then + # shellcheck disable=SC2010 + find fork-ci -mindepth 1 -maxdepth 1 ! -name 'README.md' -exec cp -r {} . \; + fi + rm -rf fork-ci + git add -A + + # Only commit if something actually differs. With an empty fork-ci/ + # (README only) this is a no-op and we push the source tip unchanged. + if git diff --cached --quiet; then + echo "no changes after overlay — pushing source branch unchanged as mirror" + else + git commit -m "test-mirror: overlay fork-ci workflows (NOT FOR UPSTREAM)" + fi + + # Force-push to the test-mirror branch. It's a mirror: force is + # deliberate and safe because the branch is only ever written by + # this workflow. + git push --force origin "HEAD:refs/heads/${MIRROR}" + echo "pushed ${MIRROR} from $(git rev-parse HEAD)" diff --git a/fork-ci/README.md b/fork-ci/README.md new file mode 100644 index 000000000..3ab1d32df --- /dev/null +++ b/fork-ci/README.md @@ -0,0 +1,26 @@ +# fork-ci/ — workflow overlays for test-mirror branches + +This directory is a plug-in point for **fork-specific CI files that must +appear on `test-mirror/**` branches but not on `upstream-pr/**` branches**. + +At the time of introduction it is intentionally empty (beyond this README): +node-agent's existing `.github/workflows/` — `build.yaml` (workflow_dispatch +only) and `component-tests.yaml` (internally dispatched by `build.yaml`) — +already work correctly when dispatched on any ref, so the mirror branch can +use the upstream-pr branch's files as-is. + +## When to add files here + +If you ever need a workflow file that must behave differently on +test-mirror branches than on upstream-pr branches (e.g. a push trigger with +`test-mirror/**` in its branch list, or a different image registry), drop +it into `fork-ci/.github/workflows/`. The `mirror-sync.yaml` workflow on +`main` will overlay everything under `fork-ci/` onto the mirror branch at +sync time. Do **not** add such files to the upstream-pr branch directly — +they would contaminate the upstream PR diff. + +## Why the shape exists even when empty + +Matches the `fork-ci/` layout on `k8sstormcenter/storage` so there's a +single mental model across both forks. Anyone looking at the mirror-sync +workflow on either fork sees the same template.