-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathaction.yml
More file actions
238 lines (209 loc) · 9.28 KB
/
action.yml
File metadata and controls
238 lines (209 loc) · 9.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
---
name: "Docker - Create images manifests"
description: |
Action to create built images manifests.
It uses the Docker Buildx plugin to create manifests for the built images.
It requires the Docker Buildx plugin to be installed and configured.
It supports creating manifests for multiple images and platforms at once.
author: hoverkraft
branding:
icon: package
color: blue
inputs:
oci-registry:
description: "OCI registry where to pull and push images"
default: "ghcr.io"
required: true
oci-registry-username:
description: Username used to log against the OCI registry.
See https://github.com/docker/login-action#usage.
default: ${{ github.repository_owner }}
required: true
oci-registry-password:
description: |
Password or personal access token used to log against the OCI registry.
Can be passed in using `secrets.GITHUB_TOKEN`.
See https://github.com/docker/login-action#usage.
default: ${{ github.token }}
required: true
built-images:
description: |
Built images data.
Example:
```json
{
"application": {
"name": "application",
"registry": "ghcr.io",
"repository": "my-org/my-repo/application",
"tags": ["pr-63-5222075","pr-63"],
"images": [
"ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
"ghcr.io/my-org/my-repo/application@sha256:0f5aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f402",
],
"annotations": {
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
"org.opencontainers.image.description": "Application image"
},
"platforms": ["linux/amd64", "linux/arm64"],
"multi-platform": true
}
}
```
required: true
outputs:
built-images:
description: |
Built images data.
Example:
```json
{
"application": {
"name": "application",
"registry": "ghcr.io",
"repository": "my-org/my-repo/application",
"tags": ["pr-63-5222075","pr-63"],
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
"images": [
"ghcr.io/my-org/my-repo/application:pr-63-5222075@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
"ghcr.io/my-org/my-repo/application:pr-63@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"
],
"annotations": {
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
"org.opencontainers.image.description": "Application image"
},
"platforms": ["linux/amd64", "linux/arm64"]
}
}
```
value: ${{ steps.get-built-images-digest.outputs.built-images }}
runs:
using: "composite"
steps:
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with:
# FIXME: upgrade version when available (https://github.com/docker/buildx/releases)
version: v0.30.1
# FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit)
driver-opts: |
image=moby/buildkit:v0.26.2
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ${{ inputs.oci-registry }}
username: ${{ inputs.oci-registry-username }}
password: ${{ inputs.oci-registry-password }}
- id: create-images-manifests
name: Create images manifests and push
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const builtImagesInput = `${{ inputs.built-images }}`;
let builtImages = null;
try {
builtImages = JSON.parse(builtImagesInput);
} catch (error) {
throw new Error(`"built-images" input is not a valid JSON: ${error}`);
}
// Helper function to validate image data
function validateImage(builtImage) {
if (!builtImage.images || builtImage.images.length === 0) {
throw new Error(`No images found for "${builtImage.name}"`);
}
}
// Helper function to build annotations options
function buildAnnotationsOption(annotations) {
const annotationLevels = ["index"];
return Object.keys(annotations)
.map(annotation => annotationLevels
.map(annotationLevel => `--annotation "${annotationLevel}:${annotation}=${annotations[annotation]}"`)
)
.flat()
.join(" ");
}
// Helper function to create multiarch manifest
async function createMultiarchManifest(builtImage, imagesWithTags) {
const platformsOption = builtImage.platforms.map(platform => `--platform ${platform}`).join(" ");
const tagsOption = imagesWithTags.map(image => `--tag ${image}`).join(" ");
const sources = builtImage.images.join(" ");
const annotationsOption = buildAnnotationsOption(builtImage.annotations);
const createManifestCommand = `docker buildx imagetools create ${platformsOption} ${annotationsOption} ${tagsOption} ${sources}`;
await exec.exec(createManifestCommand);
core.debug(`Create manifest for "${builtImage.name}" ("${createManifestCommand}") executed`);
builtImage.images = imagesWithTags;
}
// Helper function to build image tags
function buildImageTags(builtImage) {
const imagesWithTags = [];
for (const tag of builtImage.tags) {
const imageWithTag = `${builtImage.registry}/${builtImage.repository}:${tag}`;
imagesWithTags.push(imageWithTag);
}
return imagesWithTags;
}
// Process each image
const commands = Object.keys(builtImages).map(imageName => {
const builtImage = builtImages[imageName];
const imagesWithTags = buildImageTags(builtImage);
return new Promise(async (resolve, reject) => {
try {
if(!builtImage["multi-platform"]) {
core.debug(`Skipping manifest creation for "${builtImage.name}" as "multi-platform" is not set to true`);
builtImage.images = imagesWithTags;
return resolve();
}
await createMultiarchManifest(builtImage, imagesWithTags);
resolve();
} catch (error) {
reject(error);
}
});
});
await Promise.all(commands);
core.debug("Manifest created and pushed");
core.setOutput("built-images", JSON.stringify(builtImages));
- name: Get built images digest
id: get-built-images-digest
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const builtImagesOutputs = ${{ toJson(steps.create-images-manifests.outputs.built-images) }};
let builtImages = null;
try {
builtImages = JSON.parse(builtImagesOutputs);
} catch (error) {
throw new Error(`"built-images" output is not a valid JSON: ${error}`);
}
const getImageDigest = async function(image) {
// Check if the image already has a digest
if (image.match(/@/)) {
core.debug(`Image "${image}" already has a digest, skipping inspection.`);
return image;
}
const inspectImageCommand = `docker buildx imagetools inspect ${image}`;
core.debug(`Inspecting image "${image}" with command: "${inspectImageCommand}"`);
const { stdout } = await exec.getExecOutput(inspectImageCommand);
core.debug(`Inspect image "${image}" ("${inspectImageCommand}") executed: ${stdout}`);
if (!stdout) {
throw new Error(`Failed to retrieve manifest for image "${image}": "${inspectImageCommand}" returned empty output`);
}
// Retrieve digest from the manifest
const digestRegex = /Digest:\s+([a-z0-9]+:[a-z0-9]{64})/;
const digestMatch = stdout.match(digestRegex);
if (!digestMatch || digestMatch.length < 2) {
throw new Error(`Failed to retrieve digest for image "${image}": "${inspectImageCommand}" returned unexpected output: ${stdout}`);
}
const digest = digestMatch[1];
if (!digest) {
throw new Error(`Failed to retrieve digest for image "${image}": "${inspectImageCommand}" returned empty digest`);
}
core.debug(`Digest for image "${image}" is "${digest}"`);
return digest;
}
await Promise.all(Object.keys(builtImages).map(async (imageName) => {
const builtImage = builtImages[imageName];
const digest = await getImageDigest(builtImage.images[0]);
// Update built image with the digest
builtImage.digest = digest;
builtImage.images = builtImage.images.map(image => `${image}@${digest}`);
}));
core.setOutput("built-images", JSON.stringify(builtImages));