DockerBuild #3531
Workflow file for this run
This file contains hidden or 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
| 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@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.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@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.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@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.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@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.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@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.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" |