diff --git a/__tests__/github-builder/bake-vars.test.ts b/__tests__/github-builder/bake-vars.test.ts index ac32ebab..c0f5065e 100644 --- a/__tests__/github-builder/bake-vars.test.ts +++ b/__tests__/github-builder/bake-vars.test.ts @@ -36,6 +36,63 @@ describe('BakeVars.resolve', () => { expect(BakeVars.resolve([], {BUILDX_VERSION: 'v0.37.2', PATH: '/usr/bin', GITHUB: 'github', RUNNER: 'runner', github_SHA: 'abc123', OTHER_GITHUB_SHA: 'abc123'})).toEqual([]); }); + it('forwards metadata outputs in sorted order', () => { + const json = '{"tags":["example/app:latest","example/app:main"],"labels":{"version":"1.2.3"}}'; + expect( + BakeVars.resolve([], { + DOCKER_METADATA_OUTPUT_VERSION: '1.2.3', + DOCKER_METADATA_OUTPUT_TAG_NAMES: 'latest', + DOCKER_METADATA_OUTPUT_JSON: json, + DOCKER_METADATA_OUTPUT_BAKE_FILE_TAGS: '/tmp/tags.json', + DOCKER_METADATA_OUTPUT_BAKE_FILE_LABELS: '/tmp/labels.json', + DOCKER_METADATA_OUTPUT_BAKE_FILE_ANNOTATIONS: '/tmp/annotations.json', + DOCKER_METADATA_OUTPUT_BAKE_FILE: '/tmp/metadata.json' + }) + ).toEqual([ + 'DOCKER_METADATA_OUTPUT_BAKE_FILE=/tmp/metadata.json', + 'DOCKER_METADATA_OUTPUT_BAKE_FILE_ANNOTATIONS=/tmp/annotations.json', + 'DOCKER_METADATA_OUTPUT_BAKE_FILE_LABELS=/tmp/labels.json', + 'DOCKER_METADATA_OUTPUT_BAKE_FILE_TAGS=/tmp/tags.json', + `DOCKER_METADATA_OUTPUT_JSON=${json}`, + 'DOCKER_METADATA_OUTPUT_TAG_NAMES=latest', + 'DOCKER_METADATA_OUTPUT_VERSION=1.2.3' + ]); + }); + + it('excludes metadata settings and requires the exact output prefix', () => { + expect( + BakeVars.resolve([], { + DOCKER_METADATA_SET_OUTPUT_ENV: 'true', + DOCKER_METADATA_ANNOTATIONS_LEVELS: 'manifest,index', + DOCKER_METADATA_OUTPUT: 'excluded', + DOCKER_METADATA_OUTPUTVERSION: 'excluded', + docker_METADATA_OUTPUT_VERSION: 'excluded', + OTHER_DOCKER_METADATA_OUTPUT_VERSION: 'excluded' + }) + ).toEqual([]); + }); + + it('appends caller variables after metadata outputs', () => { + const vars = ['DOCKER_METADATA_OUTPUT_VERSION=override', 'DOCKER_METADATA_OUTPUT_VERSION=final']; + const env = {DOCKER_METADATA_OUTPUT_VERSION: '1.2.3'}; + expect(BakeVars.resolve(vars, env)).toEqual(['DOCKER_METADATA_OUTPUT_VERSION=1.2.3', ...vars]); + expect(vars).toEqual(['DOCKER_METADATA_OUTPUT_VERSION=override', 'DOCKER_METADATA_OUTPUT_VERSION=final']); + expect(env).toEqual({DOCKER_METADATA_OUTPUT_VERSION: '1.2.3'}); + }); + + it('preserves multiline metadata outputs as single variable entries', () => { + const tags = 'example/app:latest\nexample/app:main\n'; + const labels = 'org.opencontainers.image.version=1.2.3\r\norg.opencontainers.image.title= "one,two"=three \r\n'; + const annotations = 'manifest:org.opencontainers.image.version=1.2.3\nindex:org.opencontainers.image.title=example'; + expect( + BakeVars.resolve([], { + DOCKER_METADATA_OUTPUT_TAGS: tags, + DOCKER_METADATA_OUTPUT_LABELS: labels, + DOCKER_METADATA_OUTPUT_ANNOTATIONS: annotations + }) + ).toEqual([`DOCKER_METADATA_OUTPUT_ANNOTATIONS=${annotations}`, `DOCKER_METADATA_OUTPUT_LABELS=${labels}`, `DOCKER_METADATA_OUTPUT_TAGS=${tags}`]); + }); + it('forwards only the exact CI name and preserves its value', () => { expect(BakeVars.resolve([], {CI: 'false', CI_CUSTOM: 'excluded', ci: 'excluded', ACTIONS_RUNTIME_TOKEN: 'excluded'})).toEqual(['CI=false']); }); @@ -53,7 +110,7 @@ describe('BakeVars.resolve', () => { }); it('preserves empty values', () => { - expect(BakeVars.resolve(['EMPTY='], {GITHUB_EMPTY: '', RUNNER_UNDEFINED: undefined})).toEqual(['GITHUB_EMPTY=', 'RUNNER_UNDEFINED=', 'EMPTY=']); + expect(BakeVars.resolve(['EMPTY='], {DOCKER_METADATA_OUTPUT_VERSION: '', GITHUB_EMPTY: '', RUNNER_UNDEFINED: undefined})).toEqual(['DOCKER_METADATA_OUTPUT_VERSION=', 'GITHUB_EMPTY=', 'RUNNER_UNDEFINED=', 'EMPTY=']); }); it('preserves commas, quotes, equals signs and whitespace in values', () => { @@ -63,11 +120,13 @@ describe('BakeVars.resolve', () => { it('defaults to the process environment', () => { vi.stubEnv('CI', 'true'); + vi.stubEnv('DOCKER_METADATA_OUTPUT_VERSION', '1.2.3'); vi.stubEnv('GITHUB_BAKE_VARS_TEST', 'github'); vi.stubEnv('RUNNER_BAKE_VARS_TEST', 'runner'); vi.stubEnv('BAKE_VARS_TEST', 'excluded'); const vars = BakeVars.resolve([]); expect(vars).toContain('CI=true'); + expect(vars).toContain('DOCKER_METADATA_OUTPUT_VERSION=1.2.3'); expect(vars).toContain('GITHUB_BAKE_VARS_TEST=github'); expect(vars).toContain('RUNNER_BAKE_VARS_TEST=runner'); expect(vars).not.toContain('BAKE_VARS_TEST=excluded'); diff --git a/src/github-builder/bake-vars.ts b/src/github-builder/bake-vars.ts index b9f2cf8c..e6a3e2e8 100644 --- a/src/github-builder/bake-vars.ts +++ b/src/github-builder/bake-vars.ts @@ -18,7 +18,7 @@ export class BakeVars { public static resolve(vars: Array, env: NodeJS.ProcessEnv = process.env): Array { // Preserve GitHub workflow context without allowing arbitrary runner env lookup in Bake. const defaultVars = Object.keys(env) - .filter(key => key === 'CI' || key.startsWith('GITHUB_') || key.startsWith('RUNNER_')) + .filter(key => key === 'CI' || key.startsWith('DOCKER_METADATA_OUTPUT_') || key.startsWith('GITHUB_') || key.startsWith('RUNNER_')) .sort() .map(key => `${key}=${env[key] || ''}`); return [...defaultVars, ...vars];