diff --git a/__tests__/buildx/imagetools.test.ts b/__tests__/buildx/imagetools.test.ts index 8dbc1a4d..8c1381ff 100644 --- a/__tests__/buildx/imagetools.test.ts +++ b/__tests__/buildx/imagetools.test.ts @@ -48,6 +48,35 @@ beforeEach(() => { fs.mkdirSync(tmpDir, {recursive: true}); }); +describe('toIndexAnnotation', () => { + it.each([ + ['org.opencontainers.image.title=example', 'index:org.opencontainers.image.title=example'], + ['manifest:key=value', 'index:key=value'], + ['index:key=value', 'index:key=value'], + ['manifest-descriptor:key=value', 'index:key=value'], + ['index-descriptor:key=value', 'index:key=value'], + ['manifest,index:key=value', 'index:key=value'], + ['manifest[linux/amd64]:key=value', 'index:key=value'], + ['manifest-descriptor[linux/arm/v7]:key=value', 'index:key=value'], + ['manifest[linux/amd64],manifest[linux/arm64]:key=value', 'index:key=value'], + ['index[linux/amd64]:key=value', 'index:key=value'], + ['key=https://example.com/path?a=b=c', 'index:key=https://example.com/path?a=b=c'], + ['manifest:key=manifest:other=value', 'index:key=manifest:other=value'], + ['custom:key=value', 'index:custom:key=value'], + ['manifest:custom:key=value', 'index:custom:key=value'], + ['key=', 'index:key='], + ['manifest:key=', 'index:key='], + ['key', 'index:key'], + ['manifest:key', 'index:key'], + ['manifest:key= value ', 'index:key= value '], + ['', 'index:'], + ['unknown,manifest:key=value', 'index:key=value'], + ['unknown,custom:key=value', 'index:unknown,custom:key=value'] + ])('converts %j to %j', (input, expected) => { + expect(ImageTools.toIndexAnnotation(input)).toBe(expected); + }); +}); + describe('inspectManifest', () => { // prettier-ignore it.each([ diff --git a/src/buildx/imagetools.ts b/src/buildx/imagetools.ts index c3656f1b..a20f43a7 100644 --- a/src/buildx/imagetools.ts +++ b/src/buildx/imagetools.ts @@ -112,6 +112,26 @@ export class ImageTools { return (await this.attestationDescriptors(opts)).map(attestation => attestation.digest); } + // Retargets an annotation to the index, dropping existing scopes and platform selectors + public static toIndexAnnotation(annotation: string): string { + const keyEnd = annotation.indexOf('='); + const rawKey = keyEnd === -1 ? annotation : annotation.substring(0, keyEnd); + const rawValue = keyEnd === -1 ? '' : annotation.substring(keyEnd); + const typeSeparator = rawKey.indexOf(':'); + if (typeSeparator !== -1) { + const typeExpr = rawKey.substring(0, typeSeparator); + const key = rawKey.substring(typeSeparator + 1); + const hasKnownType = typeExpr + .split(',') + .map(type => type.replace(/\[.*\]$/, '')) + .some(type => ['manifest', 'index', 'manifest-descriptor', 'index-descriptor'].includes(type)); + if (hasKnownType) { + return `index:${key}${rawValue}`; + } + } + return `index:${annotation}`; + } + public async create(opts: CreateOpts): Promise { const args: Array = [];