Skip to content

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:

JobPurposeTriggerDependencies
lintgo vet + golangci-lint v2.10.1PR + mainnone
testenvtest integration tests with race + coveragePR + mainnone
buildNative per-platform image build (no push)PR onlynone
build-pushNative per-platform image build and pushmain onlynone
merge-manifestMerge platform digests into multi-arch manifestmain onlybuild-push
validate-manifestsVerify generated CRDs/deepcopy are up-to-datePR + mainnone
e2eEnd-to-end tests on a kind cluster with ChainsawPR + mainnone
detect-chart-changesPath filter — skip helm-lint when charts/ unchangedPR onlynone
helm-lintHelm chart lint, chart-testing, and unit testsPR onlydetect-chart-changes

Independent jobs run in parallel, minimising total pipeline time.


Triggers

yaml
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

The workflow fires on direct pushes to main and on PRs targeting main.


Concurrency

yaml
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 on main.

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:

PlatformRunner
linux/amd64ubuntu-latest
linux/arm64ubuntu-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 tag
  • ghcr.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:

  1. Creates a kind cluster.
  2. Installs cert-manager v1.16.1 and the ServiceMonitor CRD.
  3. Builds and loads the operator image into kind.
  4. Deploys the operator with make deploy.
  5. Waits for webhook certificate readiness.
  6. Runs make test-e2e (Chainsaw).
  7. 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:

  1. chart-testing lint — Runs ct lint --config ct.yaml using the helm/chart-testing-action. This validates Chart.yaml structure, runs helm lint, and checks for version increments against the target branch (main).

  2. helm-unittest — Installs the helm-unittest plugin and runs helm unittest charts/memcached-operator to execute template-level unit tests covering document counts, kind/apiVersion validation, toggle gating, and label correctness.

Tools are installed fresh on each run:

ToolVersionSource
Helmv3.17.3azure/setup-helm@v4
Python3.12actions/setup-python@v5
chart-testinglatesthelm/chart-testing-action@v2
helm-unittestv1.0.3helm 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:

text
ghcr.io/c5c3/memcached-operator

Tagging Strategy

TriggerTagsWorkflow
Push to mainlatest, <short-sha>ci.yml
Tag v1.2.31.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 StepMakefile Target
Generate manifestsmake manifests
Generate deepcopymake generate
Verify no driftmake verify-manifests
Deploy to kindmake deploy
Run E2E testsmake test-e2e
Generate install.yamlmake 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:

bash
# 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