From 916b667cf3231db5df24112b08d4474414c729c4 Mon Sep 17 00:00:00 2001 From: Lucas Weatherhog <31103312+weatherhog@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:34:26 +0200 Subject: [PATCH 1/2] feat: add reusable gitops-validate.yaml workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract gitops-template's hand-maintained validate.yaml + basic.yml into a reusable workflow so the CI logic and action pins are maintained once, centrally (Renovate-tracked), instead of per-repo. Every repository built from the template can then replace those two workflows with a thin caller. The workflow ports the four jobs unchanged in behaviour — pre-commit, `./tools/test-all-ff validate`, the rendered-manifest dyff comment, and the `tests/ats` kind e2e — but modernises every action to its current node24 release and SHA-pins them, clearing the Node 20 and `set-output` deprecation warnings. Because a called workflow checks out the caller repo, the `tools/`/`tests/` layout resolves against the consumer with no extra wiring. Repo-specific values are inputs (defaulting to gitops-template's) and the e2e GPG key is a required secret. Relates to giantswarm/roadmap#4121. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/gitops-validate.yaml | 260 +++++++++++++++++++++++++ CHANGELOG.md | 4 + 2 files changed, 264 insertions(+) create mode 100644 .github/workflows/gitops-validate.yaml diff --git a/.github/workflows/gitops-validate.yaml b/.github/workflows/gitops-validate.yaml new file mode 100644 index 0000000..9b69507 --- /dev/null +++ b/.github/workflows/gitops-validate.yaml @@ -0,0 +1,260 @@ +name: GitOps Validate + +# Reusable workflow for validating a GitOps repository created from +# giantswarm/gitops-template. It runs the repo's pre-commit hooks, its +# `./tools/test-all-ff validate`, posts a rendered-manifest dyff comment, and +# runs the `tests/ats` kind-based e2e suite. +# +# A called (workflow_call) workflow checks out the *caller* repository, so +# `tools/test-all-ff` and `tests/ats` resolve against the consumer with no +# extra wiring. Consumers pass the GITOPS_MASTER_GPG_KEY secret and may +# override the tool-version / gitops-* inputs (defaults match gitops-template). + +on: + workflow_call: + inputs: + kubeconform_ver: + type: string + default: "0.4.13" + description: "kubeconform release version to install." + dyff_ver: + type: string + default: "1.7.1" + description: "dyff release version to install." + clusterctl_ver: + type: string + default: "1.2.0" + description: "clusterctl release version to install." + apptestctl_ver: + type: string + default: "0.18.0" + description: "apptestctl release version to install." + kind_ver: + type: string + default: "0.12.0" + description: "kind release version for the e2e cluster." + gitops_flux_app_version: + type: string + default: "1.10.0" + description: "Flux app version used by the e2e tests." + gitops_init_namespaces: + type: string + default: "default,org-org-name" + description: "Namespaces the e2e tests initialise." + gitops_ignored_objects: + type: string + default: "org-org-name/clusters-mapi-out-of-band-no-flux-direct" + description: "Objects the e2e tests ignore." + python_version: + type: string + default: "3.9" + description: "Python version for the e2e test suite." + secrets: + GITOPS_MASTER_GPG_KEY: + required: true + description: "Master GPG key used by the e2e tests to decrypt SOPS-encrypted fixtures." + +permissions: {} + +jobs: + check-pre-commit: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - run: sudo snap install shfmt + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + - name: cache pre-commit environment + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-gitops-validate-${{ hashFiles('.pre-commit-config.yaml') }} + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + + validate: + needs: check-pre-commit + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + env: + GITOPS_FLUX_APP_VERSION: ${{ inputs.gitops_flux_app_version }} + GITOPS_INIT_NAMESPACES: ${{ inputs.gitops_init_namespaces }} + GITOPS_IGNORED_OBJECTS: ${{ inputs.gitops_ignored_objects }} + steps: + - run: sudo apt-get install -y yamllint + - run: curl -s https://fluxcd.io/install.sh | sudo bash + - uses: giantswarm/install-binary-action@5bef88f65012037dd836117c8d344b21bb559854 # v4.1.0 + with: + binary: kubeconform + download_url: "https://github.com/yannh/kubeconform/releases/download/v${version}/kubeconform-linux-amd64.tar.gz" + smoke_test: "${binary} -v" + tarball_binary_path: "${binary}" + version: ${{ inputs.kubeconform_ver }} + - name: cache validation tools + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-gitops-validate-${{ hashFiles('.pre-commit-config.yaml') }} + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: run validation + uses: mathiasvr/command-output@34408ea3d0528273faff3d9e201761ae96106cd0 # v2.0.0 + id: validate + with: + run: "./tools/test-all-ff validate" + - name: Find validation comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 + # Always look up the previous validation comment, whether validation passed or not. + # See: https://docs.github.com/en/actions/learn-github-actions/expressions#always + if: always() && github.ref_name != 'main' + continue-on-error: true + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: Validation output log + - name: Delete old comment + uses: winterjung/comment@fda92dbcb5e7e79cccd55ecb107a8a3d7802a469 # v1.1.0 + if: always() && github.ref_name != 'main' + continue-on-error: true + with: + type: delete + comment_id: ${{ steps.fc.outputs.comment-id }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: Create or update validation comment + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + if: always() && github.ref_name != 'main' + with: + issue-number: ${{ github.event.pull_request.number }} + body: | +
+ Validation output log + + + ``` + ${{ steps.validate.outputs.stdout }} + ``` + +
+ + + get-diff: + needs: validate + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + env: + GITOPS_FLUX_APP_VERSION: ${{ inputs.gitops_flux_app_version }} + GITOPS_INIT_NAMESPACES: ${{ inputs.gitops_init_namespaces }} + GITOPS_IGNORED_OBJECTS: ${{ inputs.gitops_ignored_objects }} + steps: + - run: sudo apt-get install -y yamllint + - run: curl -s https://fluxcd.io/install.sh | sudo bash + - name: install dyff + uses: giantswarm/install-binary-action@5bef88f65012037dd836117c8d344b21bb559854 # v4.1.0 + with: + binary: dyff + download_url: "https://github.com/homeport/dyff/releases/download/v${version}/dyff_${version}_linux_amd64.tar.gz" + smoke_test: "${binary} version" + tarball_binary_path: "${binary}" + version: ${{ inputs.dyff_ver }} + - run: which dyff + - uses: giantswarm/install-binary-action@5bef88f65012037dd836117c8d344b21bb559854 # v4.1.0 + with: + binary: kubeconform + download_url: "https://github.com/yannh/kubeconform/releases/download/v${version}/kubeconform-linux-amd64.tar.gz" + smoke_test: "${binary} -v" + tarball_binary_path: "${binary}" + version: ${{ inputs.kubeconform_ver }} + - run: which kubeconform + - run: ls -la /opt/hostedtoolcache + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: template all for the new branch + run: ./tools/test-all-ff template > /tmp/new.yaml + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: "main" + path: "old" + - name: template all for the old branch + run: cd old/ && ../tools/test-all-ff template > /tmp/old.yaml && cd .. + - name: save the diff + uses: mathiasvr/command-output@34408ea3d0528273faff3d9e201761ae96106cd0 # v2.0.0 + id: diff + with: + run: 'dyff between -s -i -b -g /tmp/old.yaml /tmp/new.yaml && echo "No diff detected" || if [[ $? -eq 255 ]]; then echo "Diff error"; fi;' + - name: Find diff comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 + continue-on-error: true + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: Rendered manifest diff output log + - name: Delete old comment + uses: winterjung/comment@fda92dbcb5e7e79cccd55ecb107a8a3d7802a469 # v1.1.0 + continue-on-error: true + with: + type: delete + comment_id: ${{ steps.fc.outputs.comment-id }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: Create or update diff comment + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | +
+ Rendered manifest diff output log + + + ``` + ${{ steps.diff.outputs.stdout }} + ``` + +
+ + + test_on_kind: + needs: validate + runs-on: ubuntu-latest + permissions: + contents: read + env: + GITOPS_FLUX_APP_VERSION: ${{ inputs.gitops_flux_app_version }} + GITOPS_INIT_NAMESPACES: ${{ inputs.gitops_init_namespaces }} + GITOPS_IGNORED_OBJECTS: ${{ inputs.gitops_ignored_objects }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: install apptestctl + uses: giantswarm/install-binary-action@5bef88f65012037dd836117c8d344b21bb559854 # v4.1.0 + with: + binary: apptestctl + download_url: "https://github.com/giantswarm/apptestctl/releases/download/v${version}/apptestctl-v${version}-linux-amd64.tar.gz" + smoke_test: "${binary} version" + tarball_binary_path: "apptestctl-v${version}-linux-amd64/${binary}" + version: ${{ inputs.apptestctl_ver }} + - name: install clusterctl + run: curl -sSL https://github.com/kubernetes-sigs/cluster-api/releases/download/v${{ inputs.clusterctl_ver }}/clusterctl-linux-amd64 -o /usr/local/bin/clusterctl && chmod +x /usr/local/bin/clusterctl && clusterctl version + - name: Create k8s Kind Cluster + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 + with: + version: "v${{ inputs.kind_ver }}" + - name: extract kind kube.config + run: kind get kubeconfig --name 'chart-testing' > /tmp/kube.config + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "${{ inputs.python_version }}" + - name: Install pipenv + run: python -m pip install --upgrade pipenv + - name: install pipenv environment + run: cd tests/ats && pipenv install --deploy + - name: run tests + run: cd tests/ats && pipenv run pytest . + env: + KUBECONFIG: /tmp/kube.config + GITOPS_REPO_BRANCH: "${{ github.head_ref || github.ref_name }}" + GITOPS_REPO_URL: "${{ github.server_url }}/${{ github.repository }}" + GITOPS_MASTER_GPG_KEY: "${{ secrets.GITOPS_MASTER_GPG_KEY }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index b389716..c64b162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Instead this file uses a date-based structure. ## 2026-07-02 +### Added + +- `gitops-validate.yaml` — new reusable workflow for validating GitOps repositories built from `giantswarm/gitops-template` (runs pre-commit, `./tools/test-all-ff validate`, a rendered-manifest `dyff` comment, and the `tests/ats` kind e2e). Consolidates CI that was previously hand-maintained in each consumer's `validate.yaml`/`basic.yml`; all actions are on current node24 releases and SHA-pinned. Consumers call it with `uses:` and pass the `GITOPS_MASTER_GPG_KEY` secret. + ### Changed - `yaml-diff.yaml` now posts its `dyff` output inside a ` ```diff ` fenced block so GitHub colourises it — removed values render red, added values green, and each file gets a `@@ … @@` header. dyff's go-patch `-`/`+` markers are moved to column 0 (indentation preserved after the marker) so the highlighter picks them up; the rewrite only reorders leading whitespace, so the comment-size truncation limits are unaffected. dyff's own ANSI colour stays disabled (comments can't render it). Requested in giantswarm/roadmap#4121. From f8823e0564620c7d12ecbd014b488725ee0158c7 Mon Sep 17 00:00:00 2001 From: Lucas Weatherhog <31103312+weatherhog@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:12:39 +0200 Subject: [PATCH 2/2] fix(gitops-validate): satisfy zizmor and yamllint - Pass clusterctl_ver via env and use a shell variable in the install step instead of interpolating `${{ inputs.clusterctl_ver }}` directly into the `run:` block (zizmor template-injection / code-injection), which also brings the line under the 200-char yamllint limit. - Set `persist-credentials: false` on every actions/checkout (zizmor artipacked / credential-persistence), matching yaml-diff.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/gitops-validate.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gitops-validate.yaml b/.github/workflows/gitops-validate.yaml index 9b69507..816a766 100644 --- a/.github/workflows/gitops-validate.yaml +++ b/.github/workflows/gitops-validate.yaml @@ -64,6 +64,8 @@ jobs: steps: - run: sudo snap install shfmt - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - name: cache pre-commit environment uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -98,6 +100,8 @@ jobs: path: ~/.cache/pre-commit key: ${{ runner.os }}-pre-commit-gitops-validate-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: run validation uses: mathiasvr/command-output@34408ea3d0528273faff3d9e201761ae96106cd0 # v2.0.0 id: validate @@ -172,10 +176,13 @@ jobs: - run: which kubeconform - run: ls -la /opt/hostedtoolcache - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: template all for the new branch run: ./tools/test-all-ff template > /tmp/new.yaml - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + persist-credentials: false ref: "main" path: "old" - name: template all for the old branch @@ -227,6 +234,8 @@ jobs: GITOPS_IGNORED_OBJECTS: ${{ inputs.gitops_ignored_objects }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: install apptestctl uses: giantswarm/install-binary-action@5bef88f65012037dd836117c8d344b21bb559854 # v4.1.0 with: @@ -236,7 +245,12 @@ jobs: tarball_binary_path: "apptestctl-v${version}-linux-amd64/${binary}" version: ${{ inputs.apptestctl_ver }} - name: install clusterctl - run: curl -sSL https://github.com/kubernetes-sigs/cluster-api/releases/download/v${{ inputs.clusterctl_ver }}/clusterctl-linux-amd64 -o /usr/local/bin/clusterctl && chmod +x /usr/local/bin/clusterctl && clusterctl version + env: + CLUSTERCTL_VER: ${{ inputs.clusterctl_ver }} + run: | + curl -sSL "https://github.com/kubernetes-sigs/cluster-api/releases/download/v${CLUSTERCTL_VER}/clusterctl-linux-amd64" -o /usr/local/bin/clusterctl + chmod +x /usr/local/bin/clusterctl + clusterctl version - name: Create k8s Kind Cluster uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 with: