CI Pipeline
Reference documentation for the GitHub Actions CI workflow that runs lint, test, Docker build, CRD manifest validation, and E2E tests on every push and pull request, and publishes container images on merges to main.
Source: .github/workflows/ci.yml
Overview
The CI pipeline runs on pushes to main and pull requests targeting main. It enforces code quality, correctness, build integrity, and manifest freshness through these jobs:
| Job | Purpose | Trigger | Dependencies |
|---|---|---|---|
lint | go vet + golangci-lint v2.10.1 | PR + main | none |
test | envtest integration tests with race + coverage | PR + main | none |
build | Native per-platform image build (no push) | PR only | none |
build-push | Native per-platform image build and push | main only | none |
merge-manifest | Merge platform digests into multi-arch manifest | main only | build-push |
validate-manifests | Verify generated CRDs/deepcopy are up-to-date | PR + main | none |
e2e | End-to-end tests on a kind cluster with Chainsaw | PR + main | none |
detect-chart-changes | Path filter — skip helm-lint when charts/ unchanged | PR only | none |
helm-lint | Helm chart lint, chart-testing, and unit tests | PR only | detect-chart-changes |
Independent jobs run in parallel, minimising total pipeline time.
Triggers
on:
push:
branches: [main]
pull_request:
branches: [main]The workflow fires on direct pushes to main and on PRs targeting main.
Concurrency
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}Concurrent runs for the same branch are cancelled to save runner minutes. The cancel-in-progress expression evaluates to true for all branches except the default branch (main), ensuring main always gets a full CI pass.
Permissions
The workflow uses least-privilege permissions:
- Top-level:
contents: read— minimum needed for checkout. - Per-job: Each job explicitly declares its own permissions.
- Build job:
packages: write— required for pushing images to GHCR onmain.
Jobs
lint
Runs go vet followed by golangci-lint using the official golangci/golangci-lint-action@v7 action. The golangci-lint version is pinned to v2.10.1 to match the project's Makefile and .golangci.yml.
Go is set up using go-version-file: go.mod so the CI Go version always matches the project's go.mod directive.
test
Installs setup-envtest and downloads Kubernetes API server binaries (v1.32.0), then runs all non-e2e tests with:
-race— data race detection-covermode=atomic— thread-safe coverage for race-enabled tests-coverprofile=cover.out— coverage output
The coverage report is uploaded as a GitHub Actions artifact retained for 7 days.
build (PR only)
Runs on pull requests only. Uses a matrix strategy with native runners (same as build-push) to verify the Dockerfile compiles for both platforms. Images are not pushed.
build-push (main only)
Runs on pushes to main. Uses a matrix strategy with native runners for each platform to avoid slow QEMU emulation:
| Platform | Runner |
|---|---|
linux/amd64 | ubuntu-latest |
linux/arm64 | ubuntu-24.04-arm |
Each matrix job builds the image natively for its platform and pushes a single-platform image by digest to GHCR. The digest is exported as an artifact for the merge step.
Build args inject the version from git describe --tags --always --dirty, git SHA, and build timestamp.
merge-manifest (main only)
Downloads the per-platform digests from the build-push matrix and creates a multi-arch manifest list using docker buildx imagetools create. The manifest is tagged with:
ghcr.io/c5c3/memcached-operator:latest— rolling latest tagghcr.io/c5c3/memcached-operator:<short-sha>— pinnable commit-based tag
Tags and OCI labels are generated by docker/metadata-action@v5.
validate-manifests
Runs make manifests generate to regenerate CRD YAMLs and deepcopy code, then make verify-manifests to assert no diff exists. This catches stale generated files that were not committed after API type changes.
e2e
Runs the full Chainsaw end-to-end test suite on a kind cluster:
- Creates a kind cluster.
- Installs cert-manager v1.16.1 and the ServiceMonitor CRD.
- Builds and loads the operator image into kind.
- Deploys the operator with
make deploy. - Waits for webhook certificate readiness.
- Runs
make test-e2e(Chainsaw). - On failure, collects operator logs, pod status, and events for debugging.
detect-chart-changes (PR only)
Uses dorny/paths-filter@v3 to detect whether any files under charts/ or ct.yaml were modified. The output charts-changed gates the helm-lint job so it only runs when chart files change.
helm-lint
Depends on detect-chart-changes and only runs when chart-related files are modified. Validates the Helm chart at charts/memcached-operator/ through two checks:
chart-testing lint — Runs
ct lint --config ct.yamlusing the helm/chart-testing-action. This validatesChart.yamlstructure, runshelm lint, and checks for version increments against the target branch (main).helm-unittest — Installs the helm-unittest plugin and runs
helm unittest charts/memcached-operatorto execute template-level unit tests covering document counts, kind/apiVersion validation, toggle gating, and label correctness.
Tools are installed fresh on each run:
| Tool | Version | Source |
|---|---|---|
| Helm | v3.17.3 | azure/setup-helm@v4 |
| Python | 3.12 | actions/setup-python@v5 |
| chart-testing | latest | helm/chart-testing-action@v2 |
| helm-unittest | v1.0.3 | helm plugin install from upstream repo |
The chart-testing configuration is defined in ct.yaml at the repository root with target-branch: main and check-version-increment: true.
Image Registry
Container images are published to the GitHub Container Registry (GHCR) at:
ghcr.io/c5c3/memcached-operatorTagging Strategy
| Trigger | Tags | Workflow |
|---|---|---|
Push to main | latest, <short-sha> | ci.yml |
Tag v1.2.3 | 1.2.3, 1.2, 1 (if not v0.x) | release.yml |
All images are multi-architecture manifests supporting linux/amd64 and linux/arm64.
Makefile Integration
The CI workflow reuses the project's Makefile targets where possible:
| CI Step | Makefile Target |
|---|---|
| Generate manifests | make manifests |
| Generate deepcopy | make generate |
| Verify no drift | make verify-manifests |
| Deploy to kind | make deploy |
| Run E2E tests | make test-e2e |
| Generate install.yaml | make build-installer |
The lint and test jobs use direct go commands / actions rather than Makefile targets to avoid installing tool binaries that the actions already provide.
Local Reproduction
Each CI check can be reproduced locally using Makefile targets:
# Lint — runs go vet and golangci-lint
make lint
# Unit and integration tests with envtest and coverage
make test
# Docker image build (build-only, no push)
make docker-build
# Verify generated CRD manifests and deepcopy are up-to-date
make verify-manifests