name: Build target images on: workflow_call: inputs: environment: description: "Target environment for the image build (e.g. testing, production)." required: true type: string default: "testing" postgresql_version: description: "PostgreSQL major version to pass to bake (e.g. 16, 17)." required: true type: string target: description: "Bake target name to build (defaults to 'default')." required: false default: "default" type: string bake_files: description: "Comma-separated ordered list of bake definition files to load." required: false type: string default: "./docker-bake.hcl" bake_remote_source: description: > Optional Git repository with extra bake definitions (e.g. cloudnative-pg/postgres-containers). The repository will be mounted under the `source` directory. required: false type: string secrets: SNYK_TOKEN: required: false permissions: {} jobs: testbuild: # Start by building images for testing. We want to run security checks before pushing those to production. name: PostgreSQL ${{ inputs.postgresql_version }} runs-on: ubuntu-24.04 permissions: contents: read packages: write # Required by the cosign step id-token: write outputs: metadata: ${{ steps.build.outputs.metadata }} images: ${{ steps.images.outputs.images }} steps: - name: Checkout Code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 # bake-action/subaction/matrix doesn't support remote bake files. # As a workaround, we clone the repository to allow passing the path to a local file. - name: Checkout additional remote source uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 if: ${{ inputs.bake_remote_source != '' }} with: repository: ${{ inputs.bake_remote_source }} ref: main path: source - name: List targets id: targets uses: docker/bake-action/subaction/matrix@3acf805d94d93a86cce4ca44798a76464a75b88c # v6 with: target: ${{ inputs.target }} files: ${{ inputs.bake_files }} - name: Filter by versions id: extract_targets run: | target=$(echo '${{ steps.targets.outputs.matrix }}' | jq -r '.[] | .[] | select(match("${{ inputs.postgresql_version }}"))' | xargs echo | sed 's/ /,/g') echo "Targets for PostgreSQL ${{ inputs.postgresql_version }}: $target" echo "filtered_targets=$target" >> "$GITHUB_OUTPUT" - name: Log in to the GitHub Container registry uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # TODO: review this when GitHub has linux/arm64 runners available (Q1 2025?) # https://github.com/github/roadmap/issues/970 - name: Set up QEMU uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 with: platforms: 'arm64' # TODO: remove this line once https://github.com/tonistiigi/binfmt/issues/258 # will be released image: tonistiigi/binfmt:master - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 - name: Build and push uses: docker/bake-action@3acf805d94d93a86cce4ca44798a76464a75b88c # v6 id: build env: environment: testing registry: ghcr.io/${{ github.repository_owner }} revision: ${{ github.sha }} with: push: true source: . files: ${{ inputs.bake_files }} targets: ${{ steps.extract_targets.outputs.filtered_targets }} # Get a list of the images that were built and pushed. We only care about a single tag for each image. - name: Generated images id: images run: | echo "images=$(echo '${{ steps.build.outputs.metadata }}' | jq -c '[ .[]."image.name" | sub(",.*";"") ]')" >> "$GITHUB_OUTPUT" # Even if we're testing we sign the images, so we can push them to production later if that's required - name: Install cosign uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 # See https://github.blog/security/supply-chain-security/safeguard-container-signing-capability-actions/ # and https://github.com/actions/starter-workflows/blob/main/ci/docker-publish.yml for more details on # how to use cosign. - name: Sign images run: | echo '${{ steps.build.outputs.metadata }}' | \ jq '.[] | (."image.name" | sub(",.*";"" )) + "@" + ."containerimage.digest"' | \ xargs cosign sign --yes security: name: Security checks runs-on: ubuntu-latest permissions: contents: read packages: read security-events: write needs: - testbuild strategy: matrix: image: ${{fromJson(needs.testbuild.outputs.images)}} steps: - name: Checkout Code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Security checks uses: ./.github/actions/security-scans with: image: "${{ matrix.image }}" registry_user: ${{ github.actor }} registry_token: ${{ secrets.GITHUB_TOKEN }} snyk_token: ${{ secrets.SNYK_TOKEN }} dockerfile: "./Dockerfile" # Use the metadata generated in the `testbuild` step to find all the images # that have been built. We copy them one by one to the production registry # using skopeo. Then we sign the production images too. copytoproduction: name: Copy images to production if: | github.ref == 'refs/heads/main' && ( github.event.inputs.environment == 'production' || github.event_name == 'schedule' ) runs-on: ubuntu-latest needs: - testbuild - security permissions: contents: read packages: write security-events: write # Required by the cosign step id-token: write steps: - name: Log in to the GitHub Container registry uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Copy images run: | images=$(echo '${{ needs.testbuild.outputs.metadata }}' | jq -r ' .[] as $items | ( $items."image.name" | split(",")[] + "@" + $items."containerimage.digest" ) ' ) for image in $images do testimageshaonly="${image%:*@*}@${image#*@}" testimagenosha="${image%@*}" prodimage="${testimagenosha/-testing/}" echo "Copying ${testimageshaonly} to ${prodimage}" docker run --quiet quay.io/skopeo/stable:v1.17.0-immutable copy -q -a \ --dest-creds ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} \ docker://${testimageshaonly} docker://${prodimage} done - name: Install cosign uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - name: Sign images run: | images=$(echo '${{ needs.testbuild.outputs.metadata }}' | jq -r '.[] | ( ."image.name" | sub(",.*";"") | sub("-testing:[^@]+";"") ) + "@" + ."containerimage.digest" ' ) echo "Signing ${images}" cosign sign -t 5m --yes ${images}