Skip to content
# This workflow executes the following actions:
# - Runs Golang and ShellCheck linters
# - Runs Unit tests
# - Verifies API doc and CRDs are up to date
# - Builds the operator image (no push)
name: continuous-integration
on:
push:
branches:
- main
- release-*
pull_request:
workflow_dispatch:
schedule:
- cron: '0 1 * * *'
# set up environment variables to be used across all the jobs
env:
GOLANG_VERSION: "1.21.x"
GOLANGCI_LINT_VERSION: "v1.54"
KUBEBUILDER_VERSION: "2.3.1"
CNPG_IMAGE_NAME: "ghcr.io/${{ github.repository }}-testing"
defaults:
run:
# default failure handling for shell scripts in 'run' steps
shell: 'bash -Eeuo pipefail -x {0}'
jobs:
# Trigger the workflow on release-* branches for smoke testing whenever it's a scheduled run.
# Note: this is a workaround since we can't directly schedule-run a workflow from a non default branch
smoke_test_release_branches:
runs-on: ubuntu-22.04
name: smoke test release-* branches when it's a scheduled run
if: github.event_name == 'schedule'
strategy:
fail-fast: false
matrix:
branch:
- release-1.19
- release-1.20
steps:
- name: Invoke workflow with inputs
uses: benc-uk/workflow-dispatch@v1
with:
workflow: continuous-integration
ref: ${{ matrix.branch }}
# Detects if we should skip the workflow due to being duplicated. Exceptions:
# 1. it's on 'main' branch
# 2. it's triggered by events in the 'do_not_skip' list
duplicate_runs:
runs-on: ubuntu-22.04
name: Skip duplicate runs
continue-on-error: true
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip == 'true' && github.ref != 'refs/heads/main' }}
steps:
- id: skip_check
uses: fkirc/[email protected]
with:
concurrent_skipping: 'same_content'
skip_after_successful_duplicate: 'true'
paths_ignore: '["README.md", "docs/**"]'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Classify codebase changes along 5 different dimensions based on the files
# changed in the commit/PR, and create 5 different filters which are used in
# the following jobs to decide whether the step should be skipped.
change-triage:
name: Check changed files
needs: duplicate_runs
if: ${{ needs.duplicate_runs.outputs.should_skip != 'true' }}
runs-on: ubuntu-22.04
outputs:
docs-changed: ${{ steps.filter.outputs.docs-changed }}
operator-changed: ${{ steps.filter.outputs.operator-changed }}
test-changed: ${{ steps.filter.outputs.test-changed }}
shell-script-changed: ${{ steps.filter.outputs.shell-script-changed }}
go-code-changed: ${{ steps.filter.outputs.go-code-changed }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check for changes
uses: dorny/[email protected]
id: filter
# Remember to add new folders in the operator-changed filter if needed
with:
base: ${{ (github.event_name == 'schedule') && 'main' || '' }}
filters: |
docs-changed:
- '**/*.md'
- 'docs/**'
- '.wordlist-en-custom.txt'
operator-changed:
- 'api/**'
- 'cmd/**'
- 'config/**'
- 'controllers/**'
- 'internal/**'
- 'licenses/**'
- 'pkg/**'
- '.github/workflows/continuous-delivery.yml'
- '.github/workflows/continuous-integration.yml'
- '.goreleaser.yml'
- 'Dockerfile'
- 'Makefile'
- 'go.mod'
- 'go.sum'
test-changed:
- 'tests/**'
- 'hack/**'
shell-script-changed:
- '**/*.sh'
go-code-changed:
- '**/*.go'
- '.golangci.yml'
go-linters:
name: Run linters
needs:
- duplicate_runs
- change-triage
# We need always run linter as go linter is a required check
if: needs.duplicate_runs.outputs.should_skip != 'true'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
# Disable setup-go caching. Cache is better handled by the golangci-lint action
cache: false
go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
args: --timeout 4m
- name: Check go mod tidy has no pending changes
run: |
make go-mod-check
shellcheck:
name: Run shellcheck linter
needs:
- duplicate_runs
- change-triage
# Run shellcheck linter only if shell code has changed
if: |
needs.duplicate_runs.outputs.should_skip != 'true' &&
needs.change-triage.outputs.shell-script-changed == 'true'
runs-on: ubuntu-22.04
env:
SHELLCHECK_OPTS: -a -S style
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/[email protected]
generate-unit-tests-jobs:
name: Generate jobs for unit tests
needs:
- duplicate_runs
- change-triage
# Generate unit tests jobs only if the operator or the Go codebase have changed
if: |
needs.duplicate_runs.outputs.should_skip != 'true' &&
(
needs.change-triage.outputs.operator-changed == 'true' ||
needs.change-triage.outputs.go-code-changed == 'true'
)
runs-on: ubuntu-22.04
outputs:
k8sMatrix: ${{ steps.get-k8s-versions.outputs.k8s_versions }}
latest_k8s_version: ${{ steps.get-k8s-versions.outputs.latest_k8s_version }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get k8s versions for unit test
id: get-k8s-versions
shell: bash
run: |
k8s_versions=$(jq -c '
.unit_test.max as $max |
.unit_test.min as $min |
$min | [ while(. <= $max;
. | split(".") | .[1] |= (.|tonumber|.+1|tostring) | join(".")
)
] |
.[] |= .+".x"
' < .github/k8s_versions_scope.json)
echo "k8s_versions=${k8s_versions}" >> $GITHUB_OUTPUT
latest_k8s_version=$(jq -r '.|last' <<< $k8s_versions)
echo "latest_k8s_version=${latest_k8s_version}" >> $GITHUB_OUTPUT
tests:
name: Run unit tests
needs:
- duplicate_runs
- change-triage
- generate-unit-tests-jobs
# Run unit tests only if the operator or the Go codebase have changed
if: |
needs.duplicate_runs.outputs.should_skip != 'true' &&
(
needs.change-triage.outputs.operator-changed == 'true' ||
needs.change-triage.outputs.go-code-changed == 'true'
)
runs-on: ubuntu-22.04
strategy:
matrix:
# The Unit test is performed per multiple supported k8s versions (each job for each k8s version) as below:
k8s-version: ${{ fromJSON(needs.generate-unit-tests-jobs.outputs.k8sMatrix) }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run unit tests
env:
ENVTEST_K8S_VERSION: ${{ matrix.k8s-version }}
run: |
make test
- name: Coverage Summary
if: matrix.k8s-version == needs.generate-unit-tests-jobs.outputs.latest_k8s_version
run: |
go tool cover -func=cover.out -o coverage.out
- name: Publish unit test summary on the latest k8s version
if: matrix.k8s-version == needs.generate-unit-tests-jobs.outputs.latest_k8s_version
run: |
echo "Unit test coverage: $(tail -n 1 coverage.out | awk '{print $3}')" >> $GITHUB_STEP_SUMMARY
apidoc:
name: Verify API doc is up to date
needs:
- duplicate_runs
- change-triage
# Run make apidoc if Go code or docs have changed
if: |
needs.duplicate_runs.outputs.should_skip != 'true' &&
(
needs.change-triage.outputs.go-code-changed == 'true' ||
needs.change-triage.outputs.docs-changed == 'true'
)
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run make apidoc
run: |
make apidoc
- name: Verify apidoc changes
run: |
apidoc_file_path='docs/src/api_reference.md'
if git status --porcelain $apidoc_file_path | grep '^ M'; then
echo "The API documentation doesn't reflect the current API. Please run make apidoc."
exit 1
fi
crd:
name: Verify CRD is up to date
needs:
- duplicate_runs
- change-triage
# Run make manifests if Go code have changed
if: |
needs.duplicate_runs.outputs.should_skip != 'true' &&
(
needs.change-triage.outputs.go-code-changed == 'true' ||
needs.change-triage.outputs.operator-changed == 'true'
)
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run make manifests
run: |
make manifests
- name: Check CRD manifests are up to date
run: |
crd_path='config/crd'
if git status --porcelain $crd_path | grep '^ M'; then
echo "The CRD manifests do not reflect the current API. Please run make manifests."
exit 1
fi
buildx:
name: Build containers
needs:
- go-linters
- shellcheck
- tests
- apidoc
- crd
- duplicate_runs
- change-triage
# Build containers:
# if there have been any code changes OR it is a scheduled execution
# AND
# none of the preceding jobs failed
if: |
(always() && !cancelled()) &&
(
needs.duplicate_runs.outputs.should_skip != 'true' &&
(
needs.change-triage.outputs.operator-changed == 'true' ||
needs.change-triage.outputs.test-changed == 'true' ||
needs.change-triage.outputs.shell-script-changed == 'true' ||
needs.change-triage.outputs.go-code-changed == 'true'
)
) &&
(needs.go-linters.result == 'success' || needs.go-linters.result == 'skipped') &&
(needs.shellcheck.result == 'success' || needs.shellcheck.result == 'skipped') &&
(needs.tests.result == 'success' || needs.tests.result == 'skipped') &&
(needs.apidoc.result == 'success' || needs.apidoc.result == 'skipped') &&
(needs.crd.result == 'success' || needs.crd.result == 'skipped')
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
# To identify the commit we need the history and all the tags.
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Build meta
run: |
commit_sha=${{ github.event.pull_request.head.sha || github.sha }}
commit_date=$(git log -1 --pretty=format:'%ad' --date short "${commit_sha}" || : )
# use git describe to get the nearest tag and use that to build the version (e.g. 1.4.0+dev24 or 1.4.0)
commit_version=$(git describe --tags --match 'v*' "${commit_sha}"| sed -e 's/^v//; s/-g[0-9a-f]\+$//; s/-\([0-9]\+\)$/+dev\1/')
# shortened commit sha
commit_short=$(git rev-parse --short "${commit_sha}")
echo "DATE=${commit_date}" >> $GITHUB_ENV
echo "VERSION=${commit_version}" >> $GITHUB_ENV
echo "COMMIT=${commit_short}" >> $GITHUB_ENV
echo "PUSH=false" >> $GITHUB_ENV
- name: Check PR label conditions
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
build_label=$(gh pr view "${{ github.event.number }}" --json labels -q ".labels.[].name" 2>/dev/null | grep "always push images" || :)
if [ -n "${build_label}" ]; then PUSH=true; fi
echo "PUSH=${PUSH}" >> $GITHUB_ENV
- name: Set GoReleaser environment
run: |
echo GOPATH=$(go env GOPATH) >> $GITHUB_ENV
echo PWD=$(pwd) >> $GITHUB_ENV
- name: Run GoReleaser to build kubectl-cnpg plugin
uses: goreleaser/goreleaser-action@v4
if: |
github.event_name == 'schedule' ||
(
github.event_name == 'workflow_dispatch' &&
startsWith(github.head_ref, 'release-') ||
startsWith(github.ref_name, 'release-')
)
with:
distribution: goreleaser
version: latest
args: build --skip-validate --rm-dist --id kubectl-cnpg
env:
DATE: ${{ env.DATE }}
COMMIT: ${{ env.COMMIT }}
VERSION: ${{ env.VERSION }}
# Send Slack notification if the kubectl-cnpg build fails.
# To avoid message overflow, we only report runs scheduled on main or release branches
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
if: |
failure() &&
github.organization == 'cloudnative-pg' &&
(
github.event_name == 'schedule' ||
(
github.event_name == 'workflow_dispatch' &&
startsWith(github.head_ref, 'release-') ||
startsWith(github.ref_name, 'release-')
)
)
env:
SLACK_COLOR: ${{ job.status }}
SLACK_ICON: https://avatars.githubusercontent.com/u/85171364?size=48
SLACK_USERNAME: cnpg-bot
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_MESSAGE: Building plugin `kubectl-cnpg` failed!
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: build --skip-validate --rm-dist --id manager
env:
DATE: ${{ env.DATE }}
COMMIT: ${{ env.COMMIT }}
VERSION: ${{ env.VERSION }}
- name: Docker meta
id: docker-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.CNPG_IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
- name: Detect platforms
run: |
# Keep in mind that adding more platforms (architectures) will increase the building
# time even if we use the ghcache for the building process.
platforms="linux/amd64,linux/arm64"
echo "PLATFORMS=${platforms}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
image: tonistiigi/binfmt:qemu-v6.2.0
platforms: ${{ env.PLATFORMS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and eventually push
uses: docker/build-push-action@v4
with:
platforms: ${{ env.PLATFORMS }}
context: .
push: ${{ env.PUSH }}
build-args: |
VERSION=${{ env.VERSION }}
tags: ${{ steps.docker-meta.outputs.tags }}