Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/actions/copy-images/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copy Images Action

This composite GitHub Action copies a set of container images from a
`testing registry` to a `production registry`, and signs them using `Cosign`.
It requires as input Bake's build result metadata, which is the output provided
by the [bake-action](https://github.com/docker/bake-action?tab=readme-ov-file#outputs).

---

## How it works

The action assumes a consistent naming convention between your testing and production registries.

* A production image is named like `ghcr.io/org/image`
* The corresponding testing image must include a suffix, e.g. `ghcr.io/org/image-testing`

You can customize this suffix with the `inputs.test_registry_suffix` input.

The action proceeds as follows:

1. It retrieves all image references from `inputs.bake_build_metadata`
2. It generates a list of destination images by stripping out the `test_registry_suffix` from each image
3. Each image is copied to the destination registry using `Skopeo copy`. The digest of the image is preserved.
4. Each production image is signed using `Cosign`

---

## Requirements

This composite action requires the calling workflow’s `GITHUB_TOKEN`
to have the following permissions:

```
permissions:
contents: read
packages: write
id-token: write # needed by Cosign for signing the images with GitHub OIDC Token
```

---

## Inputs

| Name | Description | Required | Default |
| ---------------------- | -------------------------------------------------- | --------- | -------------- |
| `bake_build_metadata` | The JSON build result metadata generated by Bake | ✅ Yes | — |
| `registry_user` | The user used to authenticate to the registry | ✅ Yes | — |
| `registry_token` | The token used to authenticate to the registry | ✅ Yes | — |
| `test_registry_suffix` | The suffix of the testing images | ❌ No | `-testing` |

Note:
The JSON build result metadata is provided by [bake-action](https://github.com/docker/bake-action) as an output, see
[bake-action outputs](https://github.com/docker/bake-action?tab=readme-ov-file#outputs).
Alternatively, if you are using `docker buildx bake` via commandline, you can write your build metadata to a file
by using `--metadata-file`, and then provide the content of that file as `input.bake_build_metadata`.

---

## Usage

Example usage:

```
jobs:
copytoproduction:
runs-on: ubuntu-latest
needs:
- testbuild
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Copy to production
uses: cloudnative-pg/postgres-containers/.github/actions/copy-images@main
with:
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
registry_user: ${{ github.actor }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
```

Example workflow:

```
jobs:
# Building and pushing to a testing registry
testbuild:
runs-on: ubuntu-latest
outputs:
metadata: ${{ steps.build.outputs.metadata }}
steps:
...
- uses: docker/bake-action@v6
id: build
with:
push: true

# Here's when you'd want to have one or
# multiple jobs to scan and test your images
scan-images:
...

# If the tests passed, we promote the images to the production repo
copytoproduction:
runs-on: ubuntu-latest
needs:
- testbuild
- scan-images
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Copy to production
uses: cloudnative-pg/postgres-containers/.github/actions/copy-images@main
with:
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
registry_user: ${{ github.actor }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
```
75 changes: 75 additions & 0 deletions .github/actions/copy-images/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Copy and sign images
description: Copy and sign images to the production repository
inputs:
bake_build_metadata:
description: "The JSON build metadata of Bake"
required: true
registry_user:
description: "The user used to authenticate to the registry"
required: true
registry_token:
description: "The token used to authenticate to the registry"
required: true
test_registry_suffix:
description: "The testing registry suffix"
required: false
default: '-testing'

runs:
using: composite
steps:
- name: Log in to the GitHub Container registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ inputs.registry_user }}
password: ${{ inputs.registry_token }}

- name: Copy images
shell: bash
env:
# renovate: datasource=docker depName=quay.io/skopeo/stable versioning=loose
SKOPEO_VERSION: "v1.20.0-immutable"
SUFFIX: ${{ inputs.test_registry_suffix }}
run: |
images=$(echo '${{ inputs.bake_build_metadata }}' |
jq -r '
.[] as $items |
(
$items."image.name" |
split(",")[] +
"@" +
$items."containerimage.digest"
)
'
)
for image in $images
do
testimageshaonly="${image%:*@*}@${image#*@}"
testimagenosha="${image%@*}"
prodimage="${testimagenosha/$SUFFIX/}"
echo "Copying ${testimageshaonly} to ${prodimage}"
docker run --quiet quay.io/skopeo/stable:$SKOPEO_VERSION copy -q -a \
--dest-creds ${{ inputs.registry_user }}:${{ inputs.registry_token }} \
docker://${testimageshaonly} docker://${prodimage}
done

- name: Install cosign
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3

- name: Sign images
shell: bash
env:
SUFFIX: ${{ inputs.test_registry_suffix }}
run: |
images=$(echo '${{ inputs.bake_build_metadata }}' |
jq -r --arg suffix "$SUFFIX" '.[] |
(
."image.name" |
sub(",.*";"") |
sub($suffix + ":[^@]+";"")
) + "@" + ."containerimage.digest"
'
)
echo "Signing ${images}"
cosign sign -t 5m --yes ${images}
58 changes: 8 additions & 50 deletions .github/workflows/bake_targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ jobs:
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: |
Expand All @@ -167,54 +164,15 @@ jobs:
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: Checkout Code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5

- 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}
- name: Copy to production
uses: ./.github/actions/copy-images
with:
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
registry_user: ${{ github.actor }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
9 changes: 9 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
"\\/\\/\\s*renovate:\\s*datasource=(?<datasource>.*?)\\s+(versioning=(?<versioning>.*?))?\\s+depName=(?<depName>.*?)\\s*\\n\\s*[A-Za-z0-9_-]+\\s*=\\s*\"(?<currentValue>[^\"]+)\""
],
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
},
{
"customType": "regex",
"managerFilePatterns": [
"/\\.ya?ml$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-.]+?) depName=(?<depName>[^\\s]+?)(?: (?:lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*[\"']?(?<currentValue>.+?)[\"']?\\s"
]
}
],
"packageRules": [
Expand Down