Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/spec-node/dockerfileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const findFromLines = new RegExp(/^(?<line>\s*FROM.*)/, 'gmi');
const parseFromLine = /FROM\s+(?<platform>--platform=\S+\s+)?(?<image>"?[^\s]+"?)(\s+AS\s+(?<label>[^\s]+))?/i;

const fromStatement = /^\s*FROM\s+(?<platform>--platform=\S+\s+)?(?<image>"?[^\s]+"?)(\s+AS\s+(?<label>[^\s]+))?/mi;
const argEnvUserStatements = /^\s*(?<instruction>ARG|ENV|USER)\s+(?<name>[^\s=]+)([ =]+("(?<value1>\S+)"|(?<value2>\S+)))?/gmi;
const argEnvUserStatements = /^\s*(?<instruction>ARG|ENV|USER)\s+(?<arguments>[^\r\n]*)/gmi;
const directives = /^\s*#\s*(?<name>\S+)\s*=\s*(?<value>.+)/;

const argumentExpression = /\$\{?(?<variable>[a-zA-Z0-9_]+)(?<isVarExp>:(?<option>-|\+)(?<word>[^\}]+))?\}?/g;
Expand Down Expand Up @@ -135,15 +135,28 @@ function extractDirectives(preambleStr: string) {
}

function extractInstructions(stageStr: string) {
return [...stageStr.matchAll(argEnvUserStatements)]
.map(match => {
const groups = match.groups!;
return {
instruction: groups.instruction.toUpperCase(),
name: groups.name,
value: groups.value1 || groups.value2,
};
});
// Docker joins lines ending in a continuation escape before parsing an instruction.
const normalizedStageStr = stageStr.replace(/\\[ \t]*(?:\r?\n|\r)/g, ' ');
return [...normalizedStageStr.matchAll(argEnvUserStatements)].flatMap(match => {
const groups = match.groups!;
const instruction = groups.instruction.toUpperCase();
const declarations = groups.arguments.matchAll(/([^\s=]+)(?:[ =]+("\S+"|\S+))?/g);
const parsed = [];
for (const declaration of declarations) {
if (declaration[1].startsWith('#')) {
break;
}
parsed.push({
instruction,
name: declaration[1],
value: declaration[2]?.replace(/^"|"$/g, ''),
});
if (instruction !== 'ARG' && parsed.length === 1) {
break;
}
}
return parsed;
});
}

function getExpressionValue(option: string, isSet: boolean, word: string, value: string) {
Expand Down
18 changes: 16 additions & 2 deletions src/test/dockerfileUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,22 @@ FROM base-\${TARGETARCH}
});

describe('findBaseImage', () => {

it('simple FROM', async () => {
it('resolves ARG declarations split across continuation lines', () => {
const dockerfile = [
'ARG VERSION_UNUSED=x \\',
' VERSION_BASE=latest',
'FROM alpine:${VERSION_BASE}',
].join('\n');
const extracted = extractDockerfile(dockerfile);
assert.deepEqual(extracted.preamble.instructions, [
{ instruction: 'ARG', name: 'VERSION_UNUSED', value: 'x' },
{ instruction: 'ARG', name: 'VERSION_BASE', value: 'latest' },
]);
assert.strictEqual(findBaseImage(extracted, {}, undefined), 'alpine:latest');
assert.strictEqual(findBaseImage(extracted, { VERSION_BASE: '3.20' }, undefined), 'alpine:3.20');
});

it('simple FROM', async () => {
const dockerfile = `FROM image1
USER user1
`;
Expand Down