Skip to content

Commit ea44de0

Browse files
authored
ci: add a composite action for copying and signing images to production (#341)
Closes #340 Signed-off-by: Niccolò Fei <[email protected]>
1 parent 5351798 commit ea44de0

File tree

4 files changed

+212
-50
lines changed

4 files changed

+212
-50
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copy Images Action
2+
3+
This composite GitHub Action copies a set of container images from a
4+
`testing registry` to a `production registry`, and signs them using `Cosign`.
5+
It requires as input Bake's build result metadata, which is the output provided
6+
by the [bake-action](https://github.com/docker/bake-action?tab=readme-ov-file#outputs).
7+
8+
---
9+
10+
## How it works
11+
12+
The action assumes a consistent naming convention between your testing and production registries.
13+
14+
* A production image is named like `ghcr.io/org/image`
15+
* The corresponding testing image must include a suffix, e.g. `ghcr.io/org/image-testing`
16+
17+
You can customize this suffix with the `inputs.test_registry_suffix` input.
18+
19+
The action proceeds as follows:
20+
21+
1. It retrieves all image references from `inputs.bake_build_metadata`
22+
2. It generates a list of destination images by stripping out the `test_registry_suffix` from each image
23+
3. Each image is copied to the destination registry using `Skopeo copy`. The digest of the image is preserved.
24+
4. Each production image is signed using `Cosign`
25+
26+
---
27+
28+
## Requirements
29+
30+
This composite action requires the calling workflow’s `GITHUB_TOKEN`
31+
to have the following permissions:
32+
33+
```
34+
permissions:
35+
contents: read
36+
packages: write
37+
id-token: write # needed by Cosign for signing the images with GitHub OIDC Token
38+
```
39+
40+
---
41+
42+
## Inputs
43+
44+
| Name | Description | Required | Default |
45+
| ---------------------- | -------------------------------------------------- | --------- | -------------- |
46+
| `bake_build_metadata` | The JSON build result metadata generated by Bake | ✅ Yes ||
47+
| `registry_user` | The user used to authenticate to the registry | ✅ Yes ||
48+
| `registry_token` | The token used to authenticate to the registry | ✅ Yes ||
49+
| `test_registry_suffix` | The suffix of the testing images | ❌ No | `-testing` |
50+
51+
Note:
52+
The JSON build result metadata is provided by [bake-action](https://github.com/docker/bake-action) as an output, see
53+
[bake-action outputs](https://github.com/docker/bake-action?tab=readme-ov-file#outputs).
54+
Alternatively, if you are using `docker buildx bake` via commandline, you can write your build metadata to a file
55+
by using `--metadata-file`, and then provide the content of that file as `input.bake_build_metadata`.
56+
57+
---
58+
59+
## Usage
60+
61+
Example usage:
62+
63+
```
64+
jobs:
65+
copytoproduction:
66+
runs-on: ubuntu-latest
67+
needs:
68+
- testbuild
69+
permissions:
70+
contents: read
71+
packages: write
72+
id-token: write
73+
steps:
74+
- name: Copy to production
75+
uses: cloudnative-pg/postgres-containers/.github/actions/copy-images@main
76+
with:
77+
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
78+
registry_user: ${{ github.actor }}
79+
registry_token: ${{ secrets.GITHUB_TOKEN }}
80+
```
81+
82+
Example workflow:
83+
84+
```
85+
jobs:
86+
# Building and pushing to a testing registry
87+
testbuild:
88+
runs-on: ubuntu-latest
89+
outputs:
90+
metadata: ${{ steps.build.outputs.metadata }}
91+
steps:
92+
...
93+
- uses: docker/bake-action@v6
94+
id: build
95+
with:
96+
push: true
97+
98+
# Here's when you'd want to have one or
99+
# multiple jobs to scan and test your images
100+
scan-images:
101+
...
102+
103+
# If the tests passed, we promote the images to the production repo
104+
copytoproduction:
105+
runs-on: ubuntu-latest
106+
needs:
107+
- testbuild
108+
- scan-images
109+
permissions:
110+
contents: read
111+
packages: write
112+
id-token: write
113+
steps:
114+
- name: Copy to production
115+
uses: cloudnative-pg/postgres-containers/.github/actions/copy-images@main
116+
with:
117+
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
118+
registry_user: ${{ github.actor }}
119+
registry_token: ${{ secrets.GITHUB_TOKEN }}
120+
```
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Copy and sign images
2+
description: Copy and sign images to the production repository
3+
inputs:
4+
bake_build_metadata:
5+
description: "The JSON build metadata of Bake"
6+
required: true
7+
registry_user:
8+
description: "The user used to authenticate to the registry"
9+
required: true
10+
registry_token:
11+
description: "The token used to authenticate to the registry"
12+
required: true
13+
test_registry_suffix:
14+
description: "The testing registry suffix"
15+
required: false
16+
default: '-testing'
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- name: Log in to the GitHub Container registry
22+
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
23+
with:
24+
registry: ghcr.io
25+
username: ${{ inputs.registry_user }}
26+
password: ${{ inputs.registry_token }}
27+
28+
- name: Copy images
29+
shell: bash
30+
env:
31+
# renovate: datasource=docker depName=quay.io/skopeo/stable versioning=loose
32+
SKOPEO_VERSION: "v1.20.0-immutable"
33+
SUFFIX: ${{ inputs.test_registry_suffix }}
34+
run: |
35+
images=$(echo '${{ inputs.bake_build_metadata }}' |
36+
jq -r '
37+
.[] as $items |
38+
(
39+
$items."image.name" |
40+
split(",")[] +
41+
"@" +
42+
$items."containerimage.digest"
43+
)
44+
'
45+
)
46+
for image in $images
47+
do
48+
testimageshaonly="${image%:*@*}@${image#*@}"
49+
testimagenosha="${image%@*}"
50+
prodimage="${testimagenosha/$SUFFIX/}"
51+
echo "Copying ${testimageshaonly} to ${prodimage}"
52+
docker run --quiet quay.io/skopeo/stable:$SKOPEO_VERSION copy -q -a \
53+
--dest-creds ${{ inputs.registry_user }}:${{ inputs.registry_token }} \
54+
docker://${testimageshaonly} docker://${prodimage}
55+
done
56+
57+
- name: Install cosign
58+
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
59+
60+
- name: Sign images
61+
shell: bash
62+
env:
63+
SUFFIX: ${{ inputs.test_registry_suffix }}
64+
run: |
65+
images=$(echo '${{ inputs.bake_build_metadata }}' |
66+
jq -r --arg suffix "$SUFFIX" '.[] |
67+
(
68+
."image.name" |
69+
sub(",.*";"") |
70+
sub($suffix + ":[^@]+";"")
71+
) + "@" + ."containerimage.digest"
72+
'
73+
)
74+
echo "Signing ${images}"
75+
cosign sign -t 5m --yes ${images}

.github/workflows/bake_targets.yml

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,6 @@ jobs:
152152
snyk_token: ${{ secrets.SNYK_TOKEN }}
153153
dockerfile: "./Dockerfile"
154154

155-
# Use the metadata generated in the `testbuild` step to find all the images
156-
# that have been built. We copy them one by one to the production registry
157-
# using skopeo. Then we sign the production images too.
158155
copytoproduction:
159156
name: Copy images to production
160157
if: |
@@ -167,54 +164,15 @@ jobs:
167164
permissions:
168165
contents: read
169166
packages: write
170-
security-events: write
171167
# Required by the cosign step
172168
id-token: write
173169
steps:
174-
- name: Log in to the GitHub Container registry
175-
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
176-
with:
177-
registry: ghcr.io
178-
username: ${{ github.actor }}
179-
password: ${{ secrets.GITHUB_TOKEN }}
180-
181-
- name: Copy images
182-
run: |
183-
images=$(echo '${{ needs.testbuild.outputs.metadata }}' |
184-
jq -r '
185-
.[] as $items |
186-
(
187-
$items."image.name" |
188-
split(",")[] +
189-
"@" +
190-
$items."containerimage.digest"
191-
)
192-
'
193-
)
194-
for image in $images
195-
do
196-
testimageshaonly="${image%:*@*}@${image#*@}"
197-
testimagenosha="${image%@*}"
198-
prodimage="${testimagenosha/-testing/}"
199-
echo "Copying ${testimageshaonly} to ${prodimage}"
200-
docker run --quiet quay.io/skopeo/stable:v1.17.0-immutable copy -q -a \
201-
--dest-creds ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} \
202-
docker://${testimageshaonly} docker://${prodimage}
203-
done
204-
205-
- name: Install cosign
206-
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
170+
- name: Checkout Code
171+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
207172

208-
- name: Sign images
209-
run: |
210-
images=$(echo '${{ needs.testbuild.outputs.metadata }}' |
211-
jq -r '.[] |
212-
(
213-
."image.name" |
214-
sub(",.*";"") |
215-
sub("-testing:[^@]+";"")
216-
) + "@" + ."containerimage.digest"
217-
'
218-
)
219-
echo "Signing ${images}"
220-
cosign sign -t 5m --yes ${images}
173+
- name: Copy to production
174+
uses: ./.github/actions/copy-images
175+
with:
176+
bake_build_metadata: "${{ needs.testbuild.outputs.metadata }}"
177+
registry_user: ${{ github.actor }}
178+
registry_token: ${{ secrets.GITHUB_TOKEN }}

renovate.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
"\\/\\/\\s*renovate:\\s*datasource=(?<datasource>.*?)\\s+(versioning=(?<versioning>.*?))?\\s+depName=(?<depName>.*?)\\s*\\n\\s*[A-Za-z0-9_-]+\\s*=\\s*\"(?<currentValue>[^\"]+)\""
2020
],
2121
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
22+
},
23+
{
24+
"customType": "regex",
25+
"managerFilePatterns": [
26+
"/\\.ya?ml$/"
27+
],
28+
"matchStrings": [
29+
"# 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"
30+
]
2231
}
2332
],
2433
"packageRules": [

0 commit comments

Comments
 (0)