continuous-integration #21
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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/cloudnative-pg.v1.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 }} |