From 545f7cd6eafe2e6027de09ebe31675f8a17690c5 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:23:01 +0100 Subject: [PATCH] buildx: hasAttestationType and resolveAttestationAttrs funcs Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/buildx/inputs.test.ts | 35 +++++++++++++++++++++++++++++ __tests__/util.test.ts | 30 +++++++++++++++++++++++++ src/buildx/inputs.ts | 40 +++++++++++++++++++++++++++++++++ src/util.ts | 22 ++++++++++++++++++ 4 files changed, 127 insertions(+) diff --git a/__tests__/buildx/inputs.test.ts b/__tests__/buildx/inputs.test.ts index fcca3626..15bd9d68 100644 --- a/__tests__/buildx/inputs.test.ts +++ b/__tests__/buildx/inputs.test.ts @@ -244,6 +244,41 @@ describe('hasDockerExporter', () => { }); }); +describe('hasAttestationType', () => { + // prettier-ignore + test.each([ + ['type=provenance,mode=min', 'provenance', true], + ['type=sbom,true', 'sbom', true], + ['type=foo,bar', 'provenance', false], + ])('given %p for %p returns %p', async (attrs: string, name: string, expected: boolean) => { + expect(Inputs.hasAttestationType(name, attrs)).toEqual(expected); + }); +}); + +describe('resolveAttestationAttrs', () => { + // prettier-ignore + test.each([ + [ + 'type=provenance,mode=min', + 'type=provenance,mode=min' + ], + [ + 'type=provenance,true', + 'type=provenance,disabled=false' + ], + [ + 'type=provenance,false', + 'type=provenance,disabled=true' + ], + [ + '', + '' + ], + ])('given %p', async (input: string, expected: string) => { + expect(Inputs.resolveAttestationAttrs(input)).toEqual(expected); + }); +}); + describe('hasGitAuthTokenSecret', () => { // prettier-ignore test.each([ diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 84504187..f88bcd75 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -279,6 +279,36 @@ describe('hash', () => { }); }); +// https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob_test.go#L36-L58 +describe('parseBool', () => { + [ + {input: '', expected: false, throwsError: true}, + {input: 'asdf', expected: false, throwsError: true}, + {input: '0', expected: false, throwsError: false}, + {input: 'f', expected: false, throwsError: false}, + {input: 'F', expected: false, throwsError: false}, + {input: 'FALSE', expected: false, throwsError: false}, + {input: 'false', expected: false, throwsError: false}, + {input: 'False', expected: false, throwsError: false}, + {input: '1', expected: true, throwsError: false}, + {input: 't', expected: true, throwsError: false}, + {input: 'T', expected: true, throwsError: false}, + {input: 'TRUE', expected: true, throwsError: false}, + {input: 'true', expected: true, throwsError: false}, + {input: 'True', expected: true, throwsError: false} + ].forEach(({input, expected, throwsError}) => { + test(`parseBool("${input}")`, () => { + if (throwsError) { + // eslint-disable-next-line jest/no-conditional-expect + expect(() => Util.parseBool(input)).toThrow(); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(Util.parseBool(input)).toBe(expected); + } + }); + }); +}); + // See: https://github.com/actions/toolkit/blob/a1b068ec31a042ff1e10a522d8fdf0b8869d53ca/packages/core/src/core.ts#L89 function getInputName(name: string): string { return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; diff --git a/src/buildx/inputs.ts b/src/buildx/inputs.ts index b77c00b4..77e5b5be 100644 --- a/src/buildx/inputs.ts +++ b/src/buildx/inputs.ts @@ -21,6 +21,7 @@ import {parse} from 'csv-parse/sync'; import {Context} from '../context'; import {GitHub} from '../github'; +import {Util} from '../util'; const parseKvp = (kvp: string): [string, string] => { const delimiterIndex = kvp.indexOf('='); @@ -176,6 +177,45 @@ export class Inputs { return false; } + public static hasAttestationType(name: string, attrs: string): boolean { + const records = parse(attrs, { + delimiter: ',', + trim: true, + columns: false, + relaxColumnCount: true + }); + for (const record of records) { + for (const [key, value] of record.map((chunk: string) => chunk.split('=').map(item => item.trim()))) { + if (key == 'type' && value == name) { + return true; + } + } + } + return false; + } + + public static resolveAttestationAttrs(attrs: string): string { + const records = parse(attrs, { + delimiter: ',', + trim: true, + columns: false, + relaxColumnCount: true + }); + const res: Array = []; + for (const record of records) { + for (const attr of record) { + try { + // https://github.com/docker/buildx/blob/8abef5908705e49f7ba88ef8c957e1127b597a2a/util/buildflags/attests.go#L13-L21 + const v = Util.parseBool(attr); + res.push(`disabled=${!v}`); + } catch (err) { + res.push(attr); + } + } + } + return res.join(','); + } + public static hasGitAuthTokenSecret(secrets: string[]): boolean { for (const secret of secrets) { if (secret.startsWith('GIT_AUTH_TOKEN=')) { diff --git a/src/util.ts b/src/util.ts index f9c41130..e3479cf7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -144,4 +144,26 @@ export class Util { public static hash(input: string): string { return crypto.createHash('sha256').update(input).digest('hex'); } + + // https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob.go#L7-L18 + public static parseBool(str: string): boolean { + switch (str) { + case '1': + case 't': + case 'T': + case 'true': + case 'TRUE': + case 'True': + return true; + case '0': + case 'f': + case 'F': + case 'false': + case 'FALSE': + case 'False': + return false; + default: + throw new Error(`parseBool syntax error: ${str}`); + } + } }