From 1471dfb80d47d536308191524ddc38ce35cfabdc Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 17 Nov 2020 21:38:45 +0100 Subject: [PATCH] Handle multi-line secret value Signed-off-by: CrazyMax --- .github/workflows/ci.yml | 8 ++ README.md | 32 +++++ __tests__/buildx.test.ts | 31 ++-- __tests__/context.test.ts | 293 ++++++++++++++++++++++++++++++++++++-- dist/index.js | 41 ++++-- src/buildx.ts | 8 +- src/context.ts | 35 +++-- 7 files changed, 409 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3edd17..121d354 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,14 @@ jobs: localhost:5000/name/app:1.0.0 secrets: | GIT_AUTH_TOKEN=${{ github.token }} + "MYSECRET=aaaaaaaa + bbbbbbb + ccccccccc" + FOO=bar + "EMPTYLINE=aaaa + + bbbb + ccc" - name: Inspect run: | diff --git a/README.md b/README.md index 25b0a69..6a1aa5a 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ ___ * [Customizing](#customizing) * [inputs](#inputs) * [outputs](#outputs) +* [Notes](#notes) + * [Multi-line secret value](#multi-line-secret-value) * [Troubleshooting](#troubleshooting) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Limitation](#limitation) @@ -631,6 +633,36 @@ Following outputs are available |---------------|---------|---------------------------------------| | `digest` | String | Image content-addressable identifier also called a digest | +## Notes + +### Multi-line secret value + +To handle multi-line value for a secret, you will need to place the key-value pair between quotes: + +```yaml +secrets: | + "MYSECRET=${{ secrets.GPG_KEY }}" + GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789 + "MYSECRET=aaaaaaaa + bbbbbbb + ccccccccc" + FOO=bar + "EMPTYLINE=aaaa + + bbbb + ccc" +``` + +| Key | Value | +|--------------------|--------------------------------------------------| +| `MYSECRET` | `***********************` | +| `GIT_AUTH_TOKEN` | `abcdefghi,jklmno=0123456789` | +| `MYSECRET` | `aaaaaaaa\nbbbbbbb\nccccccccc` | +| `FOO` | `bar` | +| `EMPTYLINE` | `aaaa\n\nbbbb\nccc` | + +> Note: all quote signs need to be doubled for escaping. + ## Troubleshooting See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts index 8141ec1..9c8a16e 100644 --- a/__tests__/buildx.test.ts +++ b/__tests__/buildx.test.ts @@ -1,9 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; import * as semver from 'semver'; + import * as buildx from '../src/buildx'; -import * as docker from '../src/docker'; import * as context from '../src/context'; +import * as docker from '../src/docker'; const tmpNameSync = path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep); const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; @@ -118,15 +119,23 @@ describe('parseVersion', () => { describe('getSecret', () => { test.each([ - ['A_SECRET', 'abcdef0123456789'], - ['GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789'], - ['MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg=='] - ])('given %p key and %p secret', async (key, secret) => { - const secretArgs = await buildx.getSecret(`${key}=${secret}`); - console.log(`secretArgs: ${secretArgs}`); - expect(secretArgs).toEqual(`id=${key},src=${tmpNameSync}`); - const secretContent = await fs.readFileSync(tmpNameSync, 'utf-8'); - console.log(`secretValue: ${secretContent}`); - expect(secretContent).toEqual(secret); + ['A_SECRET=abcdef0123456789', 'A_SECRET', 'abcdef0123456789', false], + ['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', false], + ['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', false], + ['aaaaaaaa', '', '', true], + ['aaaaaaaa=', '', '', true], + ['=bbbbbbb', '', '', true] + ])('given %p key and %p secret', async (kvp, key, secret, invalid) => { + try { + const secretArgs = await buildx.getSecret(kvp); + expect(true).toBe(!invalid); + console.log(`secretArgs: ${secretArgs}`); + expect(secretArgs).toEqual(`id=${key},src=${tmpNameSync}`); + const secretContent = await fs.readFileSync(tmpNameSync, 'utf-8'); + console.log(`secretValue: ${secretContent}`); + expect(secretContent).toEqual(secret); + } catch (err) { + expect(true).toBe(invalid); + } }); }); diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index d38a570..33ee960 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -1,7 +1,115 @@ import * as fs from 'fs'; import * as path from 'path'; + import * as context from '../src/context'; +const pgp = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +PejgXO0uIRolYQ3sz2tMGhx1MfBqH64= +=WbwB +-----END PGP PRIVATE KEY BLOCK-----`; + jest.spyOn(context, 'defaultContext').mockImplementation((): string => { return 'https://github.com/docker/build-push-action.git#test-jest'; }); @@ -154,6 +262,74 @@ describe('getArgs', () => { '--push', 'https://github.com/docker/build-push-action.git#heads/master' ] + ], + [ + '0.4.2', + new Map([ + ['context', 'https://github.com/docker/build-push-action.git#heads/master'], + ['tag', 'localhost:5000/name/app:latest'], + ['platforms', 'linux/amd64,linux/arm64'], + ['secrets', `GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789 +"MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc" +FOO=bar +"EMPTYLINE=aaaa + +bbbb +ccc"`], + ['file', './test/Dockerfile'], + ['builder', 'builder-git-context-2'], + ['push', 'true'] + ]), + [ + 'buildx', + 'build', + '--platform', 'linux/amd64,linux/arm64', + '--iidfile', '/tmp/.docker-build-push-jest/iidfile', + '--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=MYSECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=FOO,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=EMPTYLINE,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--file', './test/Dockerfile', + '--builder', 'builder-git-context-2', + '--push', + 'https://github.com/docker/build-push-action.git#heads/master' + ] + ], + [ + '0.4.2', + new Map([ + ['context', 'https://github.com/docker/build-push-action.git#heads/master'], + ['tag', 'localhost:5000/name/app:latest'], + ['platforms', 'linux/amd64,linux/arm64'], + ['secrets', `GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789 +MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc +FOO=bar +EMPTYLINE=aaaa + +bbbb +ccc`], + ['file', './test/Dockerfile'], + ['builder', 'builder-git-context-2'], + ['push', 'true'] + ]), + [ + 'buildx', + 'build', + '--platform', 'linux/amd64,linux/arm64', + '--iidfile', '/tmp/.docker-build-push-jest/iidfile', + '--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=MYSECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=FOO,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--secret', 'id=EMPTYLINE,src=/tmp/.docker-build-push-jest/.tmpname-jest', + '--file', './test/Dockerfile', + '--builder', 'builder-git-context-2', + '--push', + 'https://github.com/docker/build-push-action.git#heads/master' + ] ] ])( 'given %p with %p as inputs, returns %p', @@ -172,68 +348,167 @@ describe('getArgs', () => { }); describe('getInputList', () => { - it('handles single line correctly', async () => { + it('single line correctly', async () => { await setInput('foo', 'bar'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar']); }); - it('handles multiple lines correctly', async () => { + it('multiline correctly', async () => { setInput('foo', 'bar\nbaz'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz']); }); - it('remove empty lines correctly', async () => { + it('empty lines correctly', async () => { setInput('foo', 'bar\n\nbaz'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz']); }); - it('handles comma correctly', async () => { + it('comma correctly', async () => { setInput('foo', 'bar,baz'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz']); }); - it('remove empty result correctly', async () => { + it('empty result correctly', async () => { setInput('foo', 'bar,baz,'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz']); }); - it('handles different new lines correctly', async () => { + it('different new lines correctly', async () => { setInput('foo', 'bar\r\nbaz'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz']); }); - it('handles different new lines and comma correctly', async () => { + it('different new lines and comma correctly', async () => { setInput('foo', 'bar\r\nbaz,bat'); const res = await context.getInputList('foo'); console.log(res); expect(res).toEqual(['bar', 'baz', 'bat']); }); - it('handles multiple lines and ignoring comma correctly', async () => { + it('multiline and ignoring comma correctly', async () => { setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir'); const res = await context.getInputList('cache-from', true); console.log(res); expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']); }); - it('handles different new lines and ignoring comma correctly', async () => { + it('different new lines and ignoring comma correctly', async () => { setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir'); const res = await context.getInputList('cache-from', true); console.log(res); expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']); }); + + it('multiline values', async () => { + setInput( + 'secrets', + `GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789 +"MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc" +FOO=bar` + ); + const res = await context.getInputList('secrets', true); + console.log(res); + expect(res).toEqual([ + 'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', + `MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc`, + 'FOO=bar' + ]); + }); + + it('multiline values with empty lines', async () => { + setInput( + 'secrets', + `GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789 +"MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc" +FOO=bar +"EMPTYLINE=aaaa + +bbbb +ccc"` + ); + const res = await context.getInputList('secrets', true); + console.log(res); + expect(res).toEqual([ + 'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', + `MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc`, + 'FOO=bar', + `EMPTYLINE=aaaa + +bbbb +ccc` + ]); + }); + + it('multiline values without quotes', async () => { + setInput( + 'secrets', + `GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789 +MYSECRET=aaaaaaaa +bbbbbbb +ccccccccc +FOO=bar` + ); + const res = await context.getInputList('secrets', true); + console.log(res); + expect(res).toEqual([ + 'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', + 'MYSECRET=aaaaaaaa', + 'bbbbbbb', + 'ccccccccc', + 'FOO=bar' + ]); + }); + + it('large multiline values', async () => { + setInput( + 'secrets', + `"GPG_KEY=${pgp}" +FOO=bar` + ); + const res = await context.getInputList('secrets', true); + console.log(res); + expect(res).toEqual([`GPG_KEY=${pgp}`, 'FOO=bar']); + }); + + it('multiline values escape quotes', async () => { + setInput( + 'secrets', + `GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789 +"MYSECRET=aaaaaaaa +bbbb""bbb +ccccccccc" +FOO=bar` + ); + const res = await context.getInputList('secrets', true); + console.log(res); + expect(res).toEqual([ + 'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', + `MYSECRET=aaaaaaaa +bbbb\"bbb +ccccccccc`, + 'FOO=bar' + ]); + }); }); describe('asyncForEach', () => { diff --git a/dist/index.js b/dist/index.js index cdaa425..a6a2974 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4224,9 +4224,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getImageID = exports.getImageIDFile = void 0; +const sync_1 = __importDefault(__webpack_require__(750)); const fs_1 = __importDefault(__webpack_require__(747)); const path_1 = __importDefault(__webpack_require__(622)); -const sync_1 = __importDefault(__webpack_require__(750)); const semver = __importStar(__webpack_require__(383)); const context = __importStar(__webpack_require__(842)); const exec = __importStar(__webpack_require__(757)); @@ -4251,6 +4251,9 @@ function getSecret(kvp) { const delimiterIndex = kvp.indexOf('='); const key = kvp.substring(0, delimiterIndex); const value = kvp.substring(delimiterIndex + 1); + if (key.length == 0 || value.length == 0) { + throw new Error(`${kvp} is not a valid secret`); + } const secretFile = context.tmpNameSync({ tmpdir: context.tmpDir() }); @@ -4264,7 +4267,7 @@ function isLocalOrTarExporter(outputs) { delimiter: ',', trim: true, columns: false, - relax_column_count: true + relaxColumnCount: true })) { // Local if no type is defined // https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43 @@ -12096,8 +12099,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.asyncForEach = exports.getInputList = exports.getArgs = exports.getInputs = exports.tmpNameSync = exports.tmpDir = exports.defaultContext = void 0; +const sync_1 = __importDefault(__webpack_require__(750)); const fs = __importStar(__webpack_require__(747)); const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); @@ -12197,7 +12204,12 @@ function getBuildArgs(inputs, defaultContext, buildxVersion) { args.push('--cache-to', cacheTo); })); yield exports.asyncForEach(inputs.secrets, (secret) => __awaiter(this, void 0, void 0, function* () { - args.push('--secret', yield buildx.getSecret(secret)); + try { + args.push('--secret', yield buildx.getSecret(secret)); + } + catch (err) { + core.warning(err.message); + } })); if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { args.push('--secret', yield buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); @@ -12234,14 +12246,27 @@ function getCommonArgs(inputs) { } function getInputList(name, ignoreComma) { return __awaiter(this, void 0, void 0, function* () { + let res = []; const items = core.getInput(name); if (items == '') { - return []; + return res; } - return items - .split(/\r?\n/) - .filter(x => x) - .reduce((acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), []); + for (let output of (yield sync_1.default(items, { + columns: false, + relaxColumnCount: true, + skipLinesWithEmptyValues: true + }))) { + if (output.length == 1) { + res.push(output[0]); + continue; + } + else if (!ignoreComma) { + res.push(...output); + continue; + } + res.push(output.join(',')); + } + return res.filter(item => item); }); } exports.getInputList = getInputList; diff --git a/src/buildx.ts b/src/buildx.ts index 8fb506f..36fbc14 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -1,7 +1,8 @@ +import csvparse from 'csv-parse/lib/sync'; import fs from 'fs'; import path from 'path'; -import csvparse from 'csv-parse/lib/sync'; import * as semver from 'semver'; + import * as context from './context'; import * as exec from './exec'; @@ -21,6 +22,9 @@ export async function getSecret(kvp: string): Promise { const delimiterIndex = kvp.indexOf('='); const key = kvp.substring(0, delimiterIndex); const value = kvp.substring(delimiterIndex + 1); + if (key.length == 0 || value.length == 0) { + throw new Error(`${kvp} is not a valid secret`); + } const secretFile = context.tmpNameSync({ tmpdir: context.tmpDir() }); @@ -33,7 +37,7 @@ export function isLocalOrTarExporter(outputs: string[]): Boolean { delimiter: ',', trim: true, columns: false, - relax_column_count: true + relaxColumnCount: true })) { // Local if no type is defined // https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43 diff --git a/src/context.ts b/src/context.ts index fc7a81d..f62f2b8 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,3 +1,4 @@ +import csvparse from 'csv-parse/lib/sync'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -121,7 +122,11 @@ async function getBuildArgs(inputs: Inputs, defaultContext: string, buildxVersio args.push('--cache-to', cacheTo); }); await asyncForEach(inputs.secrets, async secret => { - args.push('--secret', await buildx.getSecret(secret)); + try { + args.push('--secret', await buildx.getSecret(secret)); + } catch (err) { + core.warning(err.message); + } }); if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { args.push('--secret', await buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); @@ -156,17 +161,29 @@ async function getCommonArgs(inputs: Inputs): Promise> { } export async function getInputList(name: string, ignoreComma?: boolean): Promise { + let res: Array = []; + const items = core.getInput(name); if (items == '') { - return []; + return res; } - return items - .split(/\r?\n/) - .filter(x => x) - .reduce( - (acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), - [] - ); + + for (let output of (await csvparse(items, { + columns: false, + relaxColumnCount: true, + skipLinesWithEmptyValues: true + })) as Array) { + if (output.length == 1) { + res.push(output[0]); + continue; + } else if (!ignoreComma) { + res.push(...output); + continue; + } + res.push(output.join(',')); + } + + return res.filter(item => item); } export const asyncForEach = async (array, callback) => {