Skip to content

chore(deps): Bump the actions group with 3 updates #790

chore(deps): Bump the actions group with 3 updates

chore(deps): Bump the actions group with 3 updates #790

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group: {}
workflow_dispatch: {}
permissions:
contents: read
pull-requests: read
concurrency:
group: ci-${{ github.ref }}
# Cancel in-progress PR runs but never cancel main (prevents cache corruption).
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
GOLANGCI_LINT_VERSION: "v2.12.2"
GOTESTSUM_VERSION: "v1.13.0"
K3D_VERSION: "v5.8.3"
K3S_IMAGE: "rancher/k3s:v1.35.4-k3s1"
ENVTEST_K8S_VERSION: "1.35.0"
HELM_UNITTEST_VERSION: "v0.7.2"
GOCOVER_COBERTURA_VERSION: "v1.5.0"
BENCHSTAT_VERSION: "v0.0.0-20260512194132-3cf34090a3db"
CERT_MANAGER_VERSION: "v1.17.2"
PROMETHEUS_IMAGE: "quay.io/prometheus/prometheus:v3.4.1"
PROMETHEUS_CHART_VERSION: "27.22.0"
STRESS_NG_IMAGE: "ghcr.io/alexei-led/stress-ng:0.20.01"
jobs:
# Detect which files changed so downstream jobs can skip irrelevant work.
# Docs-only or YAML-only PRs skip all Go CI (lint, test, build).
changes:
name: Detect Changes
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 5
outputs:
go: ${{ steps.filter.outputs.go }}
go-source: ${{ steps.filter.outputs.go-source }}
helm: ${{ steps.filter.outputs.helm }}
yaml: ${{ steps.filter.outputs.yaml }}
docs: ${{ steps.filter.outputs.docs }}
e2e: ${{ steps.filter.outputs.e2e }}
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
go:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'Makefile'
- 'Dockerfile'
- '.github/workflows/ci.yaml'
go-source:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'Makefile'
helm:
- 'charts/**'
yaml:
- 'config/**'
- '.github/workflows/**'
- 'charts/attune/Chart.yaml'
- 'charts/attune/values.yaml'
- 'charts/attune/ci/**'
- 'test/e2e/**/*.yaml'
docs:
- 'README.md'
- 'CONTRIBUTING.md'
- 'docs/**'
- 'examples/**'
- 'mkdocs.yml'
- 'Makefile'
- 'hack/verify-doc-defaults.sh'
- 'hack/verify-doc-tool-versions.sh'
- 'charts/attune/README.md.gotmpl'
e2e:
- 'test/e2e/**'
lint:
name: Lint
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: golangci-lint
version: ${{ env.GOLANGCI_LINT_VERSION }}
package: github.com/golangci/golangci-lint/v2/cmd/golangci-lint
- name: golangci-lint
shell: bash -Eeuo pipefail {0}
run: golangci-lint run --timeout 5m --allow-serial-runners
- name: Check go mod tidy
shell: bash -Eeuo pipefail {0}
run: |
go mod tidy
if ! git diff --quiet --exit-code go.mod go.sum; then
echo "::error::go.mod/go.sum are not tidy. Run 'go mod tidy' and commit."
git diff go.mod go.sum
exit 1
fi
- name: Check license boilerplate
shell: bash -Eeuo pipefail {0}
run: |
missing=$(find . -name '*.go' -not -path './vendor/*' -not -name 'zz_generated.*' \
-exec sh -c 'head -5 "$1" | grep -q "^Copyright" || echo "$1"' _ {} \;)
if [ -n "$missing" ]; then
echo "::error::Missing license header in: $missing"
exit 1
fi
- name: Verify doc defaults consistency
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-doc-defaults.sh
- name: Verify dashboard metrics sync
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-dashboard-metrics.sh
- name: Verify PrometheusRule metrics sync
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-prometheusrule-metrics.sh
- name: Verify Helm schema field names match CRD
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-helm-schema-fields.sh
docs-check:
name: Docs Check
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.docs == 'true' || needs.changes.outputs.helm == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: pip
- name: Install MkDocs
shell: bash -Eeuo pipefail {0}
run: pip install mkdocs-material==9.7.6
- name: Build docs site
shell: bash -Eeuo pipefail {0}
run: make docs-build
- name: Check Helm README freshness
shell: bash -Eeuo pipefail {0}
run: make helm-docs-check
- name: Verify supported tool version references
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-doc-tool-versions.sh
link-check:
name: Link Check
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.docs == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check links
uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0
with:
args: >-
--config lychee.toml
--no-progress
'README.md'
'CONTRIBUTING.md'
'SECURITY.md'
'docs/**/*.md'
'charts/attune/README.md'
fail: true
yaml-lint:
name: YAML Lint
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.yaml == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
- name: Install yamllint
shell: bash -Eeuo pipefail {0}
run: pip install yamllint
- name: YAML lint
shell: bash -Eeuo pipefail {0}
run: |
yamllint_config="${RUNNER_TEMP:-/tmp}/yamllint-${GITHUB_RUN_ID}-${GITHUB_JOB}.yaml"
cat > "$yamllint_config" <<'EOF'
extends: default
rules:
line-length:
max: 200
truthy:
check-keys: false
indentation:
spaces: 2
indent-sequences: whatever
EOF
yamllint -c "$yamllint_config" \
config/ charts/attune/Chart.yaml charts/attune/values.yaml charts/attune/ci/ test/e2e/
lint-chainsaw:
name: Lint Chainsaw Tests
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.e2e == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Chainsaw dry-run (parse validation)
shell: bash -Eeuo pipefail {0}
run: make lint-chainsaw
test-unit:
name: Unit Tests
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 15
needs: changes
if: needs.changes.outputs.go == 'true'
permissions:
contents: read
code-quality: write
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: gotestsum
version: ${{ env.GOTESTSUM_VERSION }}
package: gotest.tools/gotestsum
- uses: ./.github/actions/install-go-tool
with:
name: gocover-cobertura
version: ${{ env.GOCOVER_COBERTURA_VERSION }}
package: github.com/boumenot/gocover-cobertura
- name: Run unit tests
shell: bash -Eeuo pipefail -x {0}
run: |
gotestsum --format pkgname \
--junitfile test-results/unit.xml \
--rerun-fails --rerun-fails-max-failures=5 \
--packages="./api/... ./cmd/... ./internal/..." \
-- -race -timeout=10m \
-coverpkg=./internal/... \
-coverprofile=coverage.out \
-covermode=atomic
- name: Check coverage threshold
shell: bash -Eeuo pipefail {0}
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total: | awk '{print $3}' | tr -d '%')
echo "Total coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "::error::Coverage ${COVERAGE}% is below 80% threshold"
exit 1
fi
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: unit-test-results
path: test-results/
retention-days: 7
- name: Test summary
if: always()
uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6
with:
paths: test-results/unit.xml
- name: Convert coverage to Cobertura XML
if: always() && hashFiles('coverage.out') != ''
shell: bash -Eeuo pipefail {0}
run: gocover-cobertura < coverage.out > coverage.xml
- name: Upload coverage
if: >-
always() &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1
with:
file: coverage.xml
language: Go
label: code-coverage/go
test-bench:
name: Benchmark Tests (${{ matrix.pkg }})
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 15
needs: changes
if: needs.changes.outputs.go == 'true'
strategy:
fail-fast: false
matrix:
include:
- pkg: controller-core
path: ./internal/controller/...
bench: '^Benchmark(BuildPrometheusQuery|Reconcile$|ComputeRecommendations)'
- pkg: controller-workloads
path: ./internal/controller/...
bench: '^BenchmarkReconcile_ManyWorkloads'
- pkg: controller-policies
path: ./internal/controller/...
bench: '^BenchmarkReconcile_(ManyPolicies|Concurrent)'
- pkg: metrics
path: ./internal/metrics/...
bench: '.'
- pkg: recommendation
path: ./internal/recommendation/...
bench: '.'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: benchstat
version: v0.0.0-20260512194132-3cf34090a3db
package: golang.org/x/perf/cmd/benchstat
- name: Restore baseline benchmarks
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: bench-baseline.txt
key: bench-baseline-${{ matrix.pkg }}-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: bench-baseline-${{ matrix.pkg }}-${{ runner.os }}-
- name: Run benchmarks
shell: bash -Eeuo pipefail -x {0}
run: |
go test ${{ matrix.path }} -bench='${{ matrix.bench }}' -benchmem -run='^$' \
-count=5 -timeout=10m | tee bench-current.txt
- name: Compare with baseline
shell: bash -Eeuo pipefail {0}
run: |
if [[ -f bench-baseline.txt ]]; then
benchstat bench-baseline.txt bench-current.txt | tee bench-comparison.txt
{
echo "## Benchmark Comparison"
echo '```'
cat bench-comparison.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
else
echo "No baseline found; comparison will be available after the first main push."
echo "## Benchmark Results" >> "$GITHUB_STEP_SUMMARY"
echo "No baseline yet. One will be established on the next merge to main." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Promote current run as baseline (main only)
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
shell: bash -Eeuo pipefail {0}
run: cp bench-current.txt bench-baseline.txt
- name: Save baseline (main only)
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: bench-baseline.txt
key: bench-baseline-${{ matrix.pkg }}-${{ runner.os }}-${{ hashFiles('go.sum') }}
test-integration:
name: Integration Tests
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 15
needs: changes
if: needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: gotestsum
version: ${{ env.GOTESTSUM_VERSION }}
package: gotest.tools/gotestsum
- uses: ./.github/actions/install-go-tool
with:
name: setup-envtest
version: v0.24.1
package: sigs.k8s.io/controller-runtime/tools/setup-envtest
- name: Setup envtest assets
shell: bash -Eeuo pipefail -x {0}
run: echo "KUBEBUILDER_ASSETS=$(setup-envtest use ${{ env.ENVTEST_K8S_VERSION }} -p path)" >> "$GITHUB_ENV"
- name: Run integration tests
shell: bash -Eeuo pipefail -x {0}
run: |
gotestsum --format pkgname \
--junitfile test-results/integration.xml \
--rerun-fails --rerun-fails-max-failures=3 \
--packages="./test/integration/..." \
-- -race -timeout=15m \
-tags=integration
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: integration-test-results
path: test-results/
retention-days: 7
- name: Test summary
if: always()
uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6
with:
paths: test-results/integration.xml
# E2E tests run in a single job with one shared k3d cluster.
# Cluster setup starts immediately (needs: changes only) and overlaps with
# lint/unit. A gate step polls the GitHub API until lint+unit complete,
# then both Chainsaw and Go E2E run concurrently on the shared cluster.
# This saves ~3 min vs two separate jobs blocked on lint/unit.
test-e2e:
name: E2E
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 30
needs: [changes, lint, test-unit]
if: needs.changes.outputs.go-source == 'true' || needs.changes.outputs.e2e == 'true'
env:
K3D_CLUSTER_NAME: e2e-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: ./.github/actions/setup-e2e-cluster
with:
cluster-name: ${{ env.K3D_CLUSTER_NAME }}
kubeconfig-path: ${{ runner.temp }}/attune-e2e-${{ github.run_id }}.kubeconfig
go-version-file: go.mod
k3d-version: ${{ env.K3D_VERSION }}
k3s-image: ${{ env.K3S_IMAGE }}
cert-manager-version: ${{ env.CERT_MANAGER_VERSION }}
prometheus-image: ${{ env.PROMETHEUS_IMAGE }}
prometheus-chart-version: ${{ env.PROMETHEUS_CHART_VERSION }}
stress-ng-image: ${{ env.STRESS_NG_IMAGE }}
# Lint and unit tests are guaranteed complete via needs: [lint, test-unit].
- name: Install Chainsaw
uses: ./.github/actions/install-binary-tool
with:
name: chainsaw
version: v0.2.15
install-command: |
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH=amd64 ;;
aarch64) ARCH=arm64 ;;
esac
URL="https://github.com/kyverno/chainsaw/releases/download/v0.2.15/chainsaw_linux_${ARCH}.tar.gz"
for attempt in 1 2 3; do
if curl -fsSL "$URL" -o chainsaw.tar.gz; then
tar -xf chainsaw.tar.gz -C "$TOOL_DIR" chainsaw
rm chainsaw.tar.gz
chmod +x "$TOOL_DIR/chainsaw"
exit 0
fi
echo "::warning::Chainsaw download attempt $attempt failed, retrying in 10s..."
sleep 10
done
echo "::error::Failed to download Chainsaw after 3 attempts"
exit 1
- name: Run Chainsaw and Go E2E concurrently
shell: bash -Eeuo pipefail -x {0}
run: |
mkdir -p test-results
chainsaw test test/e2e/ --config .chainsaw.yaml 2>&1 | tee test-results/chainsaw.log &
chainsaw_pid=$!
go test -tags=e2e ./test/e2e-go/... -race -count=1 -timeout=15m -v 2>&1 | tee test-results/go-e2e.log &
go_pid=$!
chainsaw_rc=0
go_rc=0
wait $chainsaw_pid || chainsaw_rc=$?
wait $go_pid || go_rc=$?
echo "Chainsaw exit=$chainsaw_rc, Go E2E exit=$go_rc"
if (( chainsaw_rc != 0 || go_rc != 0 )); then
exit 1
fi
- name: Collect debug info on failure
if: failure()
shell: bash -Eeuo pipefail {0}
run: |
echo "=== cert-manager pods ==="
kubectl get pods -n cert-manager || true
echo "=== Operator logs ==="
kubectl logs -n attune-system -l app.kubernetes.io/name=attune --tail=300 || true
echo "=== Pod status ==="
kubectl get pods -A || true
echo "=== Events ==="
kubectl get events -A --sort-by='.lastTimestamp' | tail -50
- name: Upload debug logs on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-debug-logs
path: test-results/
retention-days: 7
if-no-files-found: ignore
- name: Cleanup k3d cluster
if: always()
shell: bash -Eeuo pipefail {0}
run: |
k3d cluster delete "$K3D_CLUSTER_NAME" 2>/dev/null || true
rm -f "$KUBECONFIG"
govulncheck:
name: Govulncheck
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: govulncheck
version: v1.3.0
package: golang.org/x/vuln/cmd/govulncheck
- name: Run govulncheck
shell: bash -Eeuo pipefail -x {0}
run: govulncheck ./...
crd-freshness:
name: CRD Freshness Check
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Regenerate manifests and deepcopy
shell: bash -Eeuo pipefail -x {0}
run: make manifests generate
- name: Check for drift
shell: bash -Eeuo pipefail {0}
run: |
if ! git diff --exit-code config/crd/ charts/attune/crds/ api/v1alpha1/zz_generated.deepcopy.go config/rbac/; then
echo "::error::Generated files are stale. Run 'make manifests generate' and commit."
exit 1
fi
helm-lint:
name: Helm Lint
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.helm == 'true' || needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
with:
version: v4.1.4
- name: Lint chart
shell: bash -Eeuo pipefail -x {0}
run: helm lint charts/attune --kube-version v1.32.0
- name: Template validation
shell: bash -Eeuo pipefail -x {0}
run: |
for f in charts/attune/ci/*.yaml; do
echo "--- Testing with $f ---"
helm template attune charts/attune -f "$f" \
--kube-version v1.32.0 \
--api-versions cert-manager.io/v1 > /dev/null
done
- uses: ./.github/actions/install-binary-tool
with:
name: helm-unittest
version: ${{ env.HELM_UNITTEST_VERSION }}
install-command: |
# Remove stale helm-unittest plugins that conflict on self-hosted runners
rm -rf "$(helm env HELM_PLUGINS)/helm-unittest.git" 2>/dev/null || true
ASSET_VERSION="${{ env.HELM_UNITTEST_VERSION }}"
ASSET_VERSION="${ASSET_VERSION#v}"
case "$(uname -s)" in
Linux) HU_OS=linux ;;
Darwin) HU_OS=macos ;;
*) echo "unsupported OS: $(uname -s)" >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64) HU_ARCH=amd64 ;;
aarch64|arm64) HU_ARCH=arm64 ;;
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL "https://github.com/helm-unittest/helm-unittest/releases/download/${{ env.HELM_UNITTEST_VERSION }}/helm-unittest-${HU_OS}-${HU_ARCH}-${ASSET_VERSION}.tgz" \
| tar xz -C "$TOOL_DIR"
- name: Link helm-unittest plugin
shell: bash -Eeuo pipefail {0}
run: |
PLUGINS_DIR="$(helm env HELM_PLUGINS)/unittest"
mkdir -p "$(dirname "$PLUGINS_DIR")"
rm -rf "$PLUGINS_DIR"
ln -s "${RUNNER_TEMP}/bin-tool-cache/helm-unittest" "$PLUGINS_DIR"
- name: Run chart unit tests
shell: bash -Eeuo pipefail -x {0}
run: helm unittest charts/attune
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/install-go-tool
with:
name: helm-docs
version: v1.14.2
package: github.com/norwoodj/helm-docs/cmd/helm-docs
- name: Helm docs freshness
shell: bash -Eeuo pipefail {0}
run: |
helm-docs --chart-search-root charts/
if ! git diff --quiet --exit-code charts/attune/README.md; then
echo "::error::Helm README is stale. Run 'make helm-docs-gen' and commit."
git diff charts/attune/README.md
exit 1
fi
- name: Verify Helm RBAC matches kustomize
shell: bash -Eeuo pipefail {0}
run: bash hack/verify-helm-rbac.sh
build:
name: Build
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 15
needs: changes
if: needs.changes.outputs.go == 'true'
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- uses: ./.github/actions/setup-clean-docker-config
- name: Verify Docker and buildx
shell: bash -Eeuo pipefail {0}
run: |
if ! docker buildx version >/dev/null 2>&1; then
echo "::error::Docker buildx is not available."
exit 1
fi
- name: Build binaries
shell: bash -Eeuo pipefail -x {0}
run: |
go build -o bin/manager ./cmd/manager/
go build -o bin/kubectl-attune ./cmd/kubectl-attune/
- name: Cross-compile kubectl plugin
shell: bash -Eeuo pipefail -x {0}
run: |
platforms=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64")
for platform in "${platforms[@]}"; do
os="${platform%/*}"
arch="${platform#*/}"
ext=""; if [ "$os" = "windows" ]; then ext=".exe"; fi
echo "::group::${os}/${arch}"
GOOS="$os" GOARCH="$arch" go build -o "bin/kubectl-attune-${os}-${arch}${ext}" ./cmd/kubectl-attune/
echo "::endgroup::"
done
- name: Build container image (no push)
shell: bash -Eeuo pipefail -x {0}
run: |
make docker-build IMG=attune:ci-${{ github.sha }}
# Gate job: always runs, aggregates all conditional job results.
# This is the ONLY required status check for branch protection,
# so docs-only PRs (where Go jobs are skipped) can still merge.
# Note: link-check is excluded from the gate because external sites
# (e.g. kubernetes.io) have transient connectivity issues on CI runners.
ci-gate:
name: CI Gate
runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
timeout-minutes: 5
if: always()
needs:
- lint
- lint-chainsaw
- docs-check
- yaml-lint
- test-unit
- test-bench
- test-integration
- test-e2e
- govulncheck
- crd-freshness
- helm-lint
- build
steps:
- name: Check job results
shell: bash -Eeuo pipefail {0}
run: |
results=( \
"${{ needs.lint.result }}" \
"${{ needs.lint-chainsaw.result }}" \
"${{ needs.docs-check.result }}" \
"${{ needs.yaml-lint.result }}" \
"${{ needs.test-unit.result }}" \
"${{ needs.test-bench.result }}" \
"${{ needs.test-integration.result }}" \
"${{ needs.test-e2e.result }}" \
"${{ needs.govulncheck.result }}" \
"${{ needs.crd-freshness.result }}" \
"${{ needs.helm-lint.result }}" \
"${{ needs.build.result }}" \
)
for r in "${results[@]}"; do
if [[ "$r" != "success" && "$r" != "skipped" ]]; then
echo "::error::CI gate failed: at least one job reported '$r'"
exit 1
fi
done
echo "All jobs passed or were skipped."