Skip to content

CP-43412: Clarify version input in Manual Prepare Release workflow #4066

CP-43412: Clarify version input in Manual Prepare Release workflow

CP-43412: Clarify version input in Manual Prepare Release workflow #4066

Workflow file for this run

name: DockerBuild
on:
# The documentation for 'merge_group' leaves a lot to be desired, but
# it appears as if they are more similar to a 'push' then a 'pull_request'
# when it comes to the general details about how the function and what
# information you have available when they are running.
merge_group:
types: [checks_requested]
push:
branches:
- develop
- main
tags:
- "*"
# tag is pr-<number>
pull_request:
release:
types:
- created
- published
- released
concurrency:
# This concurrency group ensures that only one job in the group runs at a time.
# If a new job is triggered, the previous one will be canceled.
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name != 'release' || github.ref != 'refs/heads/develop' || github.ref != 'refs/heads/main' || !startsWith(github.ref, 'refs/tags/v') }}
env:
REGISTRY_PROD_ADDR: ghcr.io
IMAGE_NAME: ${{ github.repository }}/cloudzero-agent
UNTESTED_IMAGE_NAME: "Cloudzero/untested-cloudzero/untested-cloudzero-agent"
jobs:
docker-build:
name: Build Docker image for ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
permissions:
contents: read
packages: write
id-token: write
outputs:
image-repo: ${{ env.REGISTRY_PROD_ADDR }}
image-path: ${{ env.UNTESTED_IMAGE_NAME }}
image-tag: ${{ steps.meta.outputs.version }}
steps:
# Checkout the repository code
- name: SETUP - Checkout
id: checkout_code
uses: actions/checkout@v5
- name: Prepare environment for current platform
id: prepare
run: |
platform="${{ matrix.platform }}"
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
- name: Set up Docker Context for Buildx
id: buildx-context
run: |
docker context create builders
- # Install buildx for multi-platform builds
name: SETUP - Docker Buildx
id: install_buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
with:
endpoint: builders
driver-opts: network=host
platforms: ${{ matrix.platform }}
- name: Get raw repo name
run: |
echo "REPO_NAME=${GITHUB_REPOSITORY#"$GITHUB_REPOSITORY_OWNER"/}" >> "$GITHUB_ENV"
export REPO_NAME="${GITHUB_REPOSITORY#"$GITHUB_REPOSITORY_OWNER"/}"
# Format the image names for OCI compliance (all lowercase)
- name: INPUT PREP - image name formatting
id: image_name
run: |
IMAGE_NAME="${{ env.IMAGE_NAME }}"
echo "UNTESTED_IMAGE_NAME=${UNTESTED_IMAGE_NAME,,}" >>"${GITHUB_ENV}"
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>"${GITHUB_ENV}"
# Extract metadata (tags, labels) the docker image build
# No tags of other metadata at this stage, as we are pushing by digest
- name: INPUT PREP - Basic Docker metadata for initial build
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
# ONLY use the untested registry address for the image until it is tested
images: ${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: INPUT PREP - Set build time revision
run: |
REVISION=$(git rev-parse --short HEAD)
TAG="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
{
echo "REVISION=${REVISION}"
echo "TAG=${TAG}"
echo "BUILD_TIME=${BUILD_TIME}"
} >> "${GITHUB_ENV}"
- name: TEST - Build image
id: build_image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
env:
VALIDATOR_DOCKERFILE: docker/Dockerfile
VALIDATOR_CONTEXT: .
with:
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
cache-from: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
context: ${{ env.VALIDATOR_CONTEXT }}
file: ${{ env.VALIDATOR_DOCKERFILE }}
build-args: |
BUILD_TIME=${{ env.BUILD_TIME }}
REVISION=${{ env.REVISION }}
TAG=${{ env.TAG }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build_image.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
# This job merges the Docker manifests for the different platforms built in the previous job.
name: Merge Docker manifests
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
needs: ["docker-build"]
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
# Format the image names for OCI compliance (all lowercase)
- name: INPUT PREP - image name formatting
id: image_name
run: |
IMAGE_NAME="${{ env.IMAGE_NAME }}"
echo "UNTESTED_IMAGE_NAME=${UNTESTED_IMAGE_NAME,,}" >>"${GITHUB_ENV}"
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>"${GITHUB_ENV}"
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
with:
driver-opts: |
network=host
# Extract metadata (tags, labels) the docker image build
# This is the full metadata for the manifest
- name: INPUT PREP - Extract Docker metadata from git repository
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
env:
VALIDATOR_IMAGE_DESCRIPTION: "CloudZero Agent Validator"
with:
# ONLY use the untested registry address for the image until it is tested
images: ${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}
# Tag generation rules:
# 1. branch name (used for develop or main)
# 2. PR number (used for PRs)
# 3. version to match the semver pattern for the chart
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
labels: |
maintainer=CloudZero
org.opencontainers.image.description=${{ env.VALIDATOR_IMAGE_DESCRIPTION }}
org.opencontainers.image.vendor=CloudZero
image.name=${{ env.REGISTRY_PROD_ADDR }}/${{ env.IMAGE_NAME }}
# https://github.com/docker/metadata-action?tab=readme-ov-file#latest-tag
# should only occur when a semver or raw when we are on master
flavor: |
latest=false
- name: Get execution timestamp with RFC3339 format
# This step gets the current execution timestamp in RFC3339 format.
# It uses the date command to get the current UTC time and formats it as a string.
# The timestamp is used for annotating the Docker manifest list.
id: timestamp
run: |
echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$GITHUB_OUTPUT"
- name: Create manifest list and push
# This step creates a manifest list for the Docker images built for different platforms.
# It uses the docker buildx imagetools create command to create the manifest list.
# The manifest list is annotated with metadata such as description, creation timestamp, and source URL.
# The annotations are obtained from the metadata generated in the previous steps.
# The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags.
working-directory: ${{ runner.temp }}/digests
id: manifest-annotate
continue-on-error: true
run: |
ls -lFa
# Create arrays for arguments to avoid word splitting issues
readarray -t tags < <(jq -cr '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
readarray -t digests < <(printf '%s\n' *)
# Build the command with proper argument arrays
docker_args=()
for tag in "${tags[@]}"; do
docker_args+=("-t" "$tag")
done
for digest in "${digests[@]}"; do
docker_args+=("${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}@sha256:$digest")
done
docker buildx imagetools create \
"${docker_args[@]}" \
--annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
--annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
--annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
--annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}'
- name: Create manifest list and push without annotations
# This step creates a manifest list for the Docker images built for different platforms.
# It uses the docker buildx imagetools create command to create the manifest list.
# The manifest list is created without annotations if the previous step fails.
# The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags.
if: steps.manifest-annotate.outcome == 'failure'
working-directory: /tmp/digests
run: |
# Create arrays for arguments to avoid word splitting issues
readarray -t tags < <(jq -cr '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
readarray -t digests < <(printf '%s\n' *)
# Build the command with proper argument arrays
docker_args=()
for tag in "${tags[@]}"; do
docker_args+=("-t" "$tag")
done
for digest in "${digests[@]}"; do
docker_args+=("${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}@sha256:$digest")
done
docker buildx imagetools create "${docker_args[@]}"
- name: Inspect image
# This step inspects the created manifest list to verify its contents.
# It uses the docker buildx imagetools inspect command to display information about the manifest list.
# The inspection output will show the platforms and tags associated with the manifest list.
id: inspect
run: |
docker buildx imagetools inspect '${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}:${{ steps.meta.outputs.version }}'
- name: SECURITY - Grype Docker Image Scan
uses: anchore/scan-action@v7
with:
image: ${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
fail-build: true
severity-cutoff: high
k8s-version-matrix-tests:
# These should match the permissions in the called workflow.
permissions:
contents: read
packages: read
needs: ["docker-build", "docker-merge"]
uses: ./.github/workflows/test-matrix-k8s.yaml
with:
image-repo: ${{ needs.docker-build.outputs.image-repo }}
image-path: ${{ needs.docker-build.outputs.image-path }}
image-tag: ${{ needs.docker-build.outputs.image-tag }}
secrets: inherit
replicated-compatibility-matrix-tests:
# These should match the permissions in the called workflow.
permissions:
contents: read
packages: read
needs: ["docker-build", "docker-merge"]
# We primarily want to run this when we are in a merge queue.
if: github.event_name == 'merge_group'
uses: ./.github/workflows/test-matrix-replicated-k8s-clusters.yaml
with:
image-repo: ${{ needs.docker-build.outputs.image-repo }}
image-path: ${{ needs.docker-build.outputs.image-path }}
image-tag: ${{ needs.docker-build.outputs.image-tag }}
secrets: inherit
test-versions-status-reporting:
runs-on: ubuntu-latest
permissions:
statuses: write
needs: ["k8s-version-matrix-tests"]
if: ${{ always() }}
steps:
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: ${{ needs.k8s-version-matrix-tests.outputs.version-test-status == 'success' }}
with:
name: "k8s-version-matrix-tests"
status: "success"
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: ${{ needs.k8s-version-matrix-tests.outputs.version-test-status != 'success' }}
with:
name: "k8s-version-matrix-tests"
status: "failure"
test-replicated-status-reporting:
runs-on: ubuntu-latest
permissions:
statuses: write
needs: ["replicated-compatibility-matrix-tests"]
if: ${{ always() && github.event_name == 'merge_group' }}
steps:
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: ${{ needs.replicated-compatibility-matrix-tests.outputs.cluster-test-status == 'success' }}
with:
name: "replicated-compatibility-matrix-tests"
status: "success"
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: ${{ needs.replicated-compatibility-matrix-tests.outputs.cluster-test-status != 'success' }}
with:
name: "replicated-compatibility-matrix-tests"
status: "failure"
###########################################################################
# PRODUCTION ONLY STEPS BEYOND THIS POINT
#
release-image:
needs:
[
"docker-build",
"docker-merge",
"k8s-version-matrix-tests",
"replicated-compatibility-matrix-tests",
]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
# Format the image names for OCI compliance (all lowercase)
- name: INPUT PREP - image name formatting
id: image_name
run: |
IMAGE_NAME="${{ env.IMAGE_NAME }}"
echo "UNTESTED_IMAGE_NAME=${UNTESTED_IMAGE_NAME,,}" >>"${GITHUB_ENV}"
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>"${GITHUB_ENV}"
# install regctl for registry management operations
- name: PRODUCTION STEP - Install Regctl for registry management
if: github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
id: install_regctl
uses: iarekylew00t/regctl-installer@v4
# Login to the production registry
- name: PRODUCTION STEP - login to container registry
if: github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
id: prod_registry_login
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
regctl registry login ${{ env.REGISTRY_PROD_ADDR }} \
--user "${{ github.actor }}" \
--pass-stdin
# Promote the untested image to production
# only allow on main, develop branches, or a version tag
- name: PRODUCTION STEP - Publish Image to Production
if: github.event_name == 'release' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
id: prod_publish_image
run: |
regctl image copy \
${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}:${{ needs.docker-build.outputs.image-tag }} \
${{ env.REGISTRY_PROD_ADDR }}/${{ env.IMAGE_NAME }}:${{ needs.docker-build.outputs.image-tag }}
if [[ ${{ needs.docker-build.outputs.image-tag }} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
regctl image copy \
${{ env.REGISTRY_PROD_ADDR }}/${{ env.UNTESTED_IMAGE_NAME }}:${{ needs.docker-build.outputs.image-tag }} \
${{ env.REGISTRY_PROD_ADDR }}/${{ env.IMAGE_NAME }}:latest
fi
docker-build-status-reporting:
runs-on: ubuntu-latest
permissions:
statuses: write
needs:
[
"docker-build",
"docker-merge",
"k8s-version-matrix-tests",
"replicated-compatibility-matrix-tests",
"release-image",
]
if: ${{ always() }}
steps:
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: |
!contains(needs.*.result, 'failure') &&
!contains(needs.*.result, 'cancelled')
with:
name: "docker-build"
status: "success"
- uses: spkane/commit-status-updater@a794d785537a8c22dec94d11ceb034021a8fd114
if: |
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')
with:
name: "docker-build"
status: "failure"