Skip to content
Merged
21 changes: 15 additions & 6 deletions .github/workflows/__test-workflow-docker-build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,21 +153,30 @@ jobs:
const taggedVersions = versions.filter(version => version.metadata.container.tags.length > 0);
const untaggedVersions = versions.filter(version => version.metadata.container.tags.length === 0);

// Expected tagged version is 1 for unsigned images (tag) and 2 for signed images (tag, cosing legacy tag sha256-...)
const expectedTaggedVersions = process.env.SIGN === 'true' ? 2 : 1;
const platforms = JSON.parse(process.env.PLATFORMS);
const isSinglePlatform = platforms.length === 1;
const isSigned = process.env.SIGN === 'true';

// Expected untagged versions are 1 by platform for unsigned images and number of platforms + 1 (cosing legacy tag sha256-...) for signed images
const expectedUntaggedVersions = JSON.parse(process.env.PLATFORMS).length + (process.env.SIGN === 'true' ? 1 : 0);
// Expected tagged versions:
// - Always 1 for the main tag
// - Plus 1 for cosign legacy tag (sha256-...) when signed
// Note: ghcr.io doesn't support OCI 1.1 referrers yet, so cosign falls back to legacy attachments
const expectedTaggedVersions = isSigned ? 2 : 1;

// Expected untagged versions:
// - For single platform: 0 (no multiarch manifest created)
// - For multi platform: number of platforms (one per platform)
const expectedUntaggedVersions = isSinglePlatform ? 0 : platforms.length;

assert.equal(
taggedVersions.length,
expectedTaggedVersions,
`Expected ${expectedTaggedVersions} tagged versions, but found ${taggedVersions.length}`
`Expected ${expectedTaggedVersions} tagged versions, but found ${taggedVersions.length}: ${JSON.stringify(taggedVersions, null, 2)}`
);
assert.equal(
untaggedVersions.length,
expectedUntaggedVersions,
`Expected ${expectedUntaggedVersions} untagged versions, but found ${untaggedVersions.length}`
`Expected ${expectedUntaggedVersions} untagged versions, but found ${untaggedVersions.length}: ${JSON.stringify(untaggedVersions, null, 2)}`
);

- name: Verify image manifest and platforms
Expand Down
77 changes: 56 additions & 21 deletions actions/docker/create-images-manifests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,41 +109,76 @@ runs:
throw new Error(`"built-images" input is not a valid JSON: ${error}`);
}

// Create manifest for each image
const commands = Object.keys(builtImages).map(imageName => {
const builtImage = builtImages[imageName];
// Helper function to build image tags from registry, repository and tag list
function buildImageTags(builtImage) {
return builtImage.tags.map(tag =>
`${builtImage.registry}/${builtImage.repository}:${tag}`
);
}

const imagesWithTags = builtImage.tags.map(tag => {
return `${builtImage.registry}/${builtImage.repository}:${tag}`;
});
// 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}"`);
}
}

const platformsOption = builtImage.platforms.map(platform => `--platform ${platform}`).join(" ");
// Helper function to tag single-platform image
async function tagSinglePlatformImage(builtImage, imagesWithTags) {
core.info(`Skipping multiarch manifest creation for "${builtImage.name}" (single platform: ${builtImage.platforms[0] || 'none'})`);

const tagsOption = imagesWithTags.map(image => {
return `--tag ${image}`;
}).join(" ");
validateImage(builtImage);

const sources = builtImage.images.join(" ");
const sourceImage = builtImage.images[0];
for (const targetImage of imagesWithTags) {
const tagCommand = `docker buildx imagetools create --tag ${targetImage} ${sourceImage}`;
await exec.exec(tagCommand);
core.debug(`Tag single-platform image "${builtImage.name}" ("${tagCommand}") executed`);
}

builtImage.images = imagesWithTags;
}

// Helper function to build annotations options
function buildAnnotationsOption(annotations) {
const annotationLevels = ["index"];
const annotationsOption = Object.keys(builtImage.annotations)
return Object.keys(annotations)
.map(annotation => annotationLevels
.map(annotationLevel => `--annotation "${annotationLevel}:${annotation}=${builtImage.annotations[annotation]}"`)
.map(annotationLevel => `--annotation "${annotationLevel}:${annotation}=${annotations[annotation]}"`)
)
.flat().join(" ");
.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}`;

return new Promise(async (resolve, reject) => {
try {
await exec.exec(createManifestCommand);
core.debug(`Create manifest for "${builtImage.name}" ("${createManifestCommand}") executed`);
await exec.exec(createManifestCommand);
core.debug(`Create manifest for "${builtImage.name}" ("${createManifestCommand}") executed`);

// Update builtImage with the images with tags
builtImage.images = imagesWithTags;
builtImage.images = 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.platforms.length <= 1) {
await tagSinglePlatformImage(builtImage, imagesWithTags);
} else {
await createMultiarchManifest(builtImage, imagesWithTags);
}
resolve();
} catch(error){
} catch (error) {
reject(error);
}
});
Comment thread
neilime marked this conversation as resolved.
Expand Down
7 changes: 6 additions & 1 deletion actions/docker/sign-images/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ runs:

- name: Sign the images with GitHub OIDC Token
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
COSIGN_EXPERIMENTAL: "1"
with:
github-token: ${{ inputs.github-token }}
script: |
Expand Down Expand Up @@ -88,7 +90,10 @@ runs:
// Sign the images
const annotationsArgs = tags.size > 0 ? `-a tag=${Array.from(tags).at(-1)}` : "";
const imagesArgs = Array.from(imagesToSign).join(" ");
const signImageCommand = `cosign sign ${annotationsArgs} --yes ${imagesArgs}`;
// Use OCI 1.1 referrers mode to avoid creating legacy sha256-... tags
// Note: If the registry doesn't support OCI 1.1 referrers (like ghcr.io currently),
// cosign will fall back to legacy attachments and create a sha256-... tag
const signImageCommand = `cosign sign ${annotationsArgs} --registry-referrers-mode=oci-1-1 --yes ${imagesArgs}`;

core.debug(`Signing images with command: "${signImageCommand}"`);
await exec.exec(signImageCommand);
Expand Down
6 changes: 3 additions & 3 deletions tests/charts/application/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ A Helm chart for Kubernetes

## Requirements

| Repository | Name | Version |
| ---------------------------------- | ----- | ------- |
| https://charts.bitnami.com/bitnami | mysql | 14.0.3 |
| Repository | Name | Version |
| ------------------------------------ | ----- | ------- |
| <https://charts.bitnami.com/bitnami> | MySQL | 14.0.3 |

## Values

Expand Down
8 changes: 4 additions & 4 deletions tests/charts/umbrella-application/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ An umbrella Helm chart for Kubernetes

## Requirements

| Repository | Name | Version |
| ---------------------------------- | --------------- | ------- |
| file://./charts/app | app | 0.0.0 |
| https://charts.bitnami.com/bitnami | database(mysql) | 14.0.3 |
| Repository | Name | Version |
| ------------------------------------ | --------------- | ------- |
| file://./charts/app | app | 0.0.0 |
| <https://charts.bitnami.com/bitnami> | database(MySQL) | 14.0.3 |

## Values

Expand Down
Loading