Skip to content

Commit

Permalink
fix: handle parsing variable expression in dockerfile (#337)
Browse files Browse the repository at this point in the history
* fix: handle parsing variable expression in dockerfile

* fix: use only one regex for all three cases (regular, positive, negative) of bash variables

* fix: modify �rgumentExpression for optional braces

* test: add tests for supporting variable expressions
  • Loading branch information
ShauryaAg authored Jan 3, 2023
1 parent 0583b9c commit 338857f
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 4 deletions.
27 changes: 23 additions & 4 deletions src/spec-node/dockerfileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const parseFromLine = /FROM\s+(?<platform>--platform=\S+\s+)?"?(?<image>[^\s"]+)
const fromStatement = /^\s*FROM\s+(?<platform>--platform=\S+\s+)?"?(?<image>[^\s"]+)"?(\s+[Aa][Ss]\s+(?<label>[^\s]+))?/m;
const argEnvUserStatements = /^\s*(?<instruction>ARG|ENV|USER)\s+(?<name>[^\s=]+)([ =]+("(?<value1>\S+)"|(?<value2>\S+)))?/gm;
const directives = /^\s*#\s*(?<name>\S+)\s*=\s*(?<value>.+)/;
const variables = /\$\{?(?<variable>[a-zA-Z0-9_]+)\}?/g;

const argumentExpression = /\$\{?(?<variable>[^:\}]+)(?<isVarExp>:(?<option>-|\+)(?<word>[^\}]+))?\}?/g;

export interface Dockerfile {
preamble: {
Expand Down Expand Up @@ -138,11 +139,29 @@ function extractInstructions(stageStr: string) {
});
}

function replaceVariables(dockerfile: Dockerfile, buildArgs: Record<string, string>, baseImageEnv: Record<string, string>, str: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number) {
return [...str.matchAll(variables)]
function getExpressionValue(option: string, isSet: boolean, word: string, value: string) {
const operations: Record<string, Function> = {
'-': (isSet: boolean, word: string, value: string) => isSet ? value : word,
'+': (isSet: boolean, word: string, value: string) => isSet ? word : value,
};

return operations[option](isSet, word, value).replace(/^['"]|['"]$/g, ''); // remove quotes from start and end of the string
}

function replaceVariables(dockerfile: Dockerfile, buildArgs: Record<string, string>, baseImageEnv: Record<string, string>, str: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number) {
return [...str.matchAll(argumentExpression)]
.map(match => {
const variable = match.groups!.variable;
const value = findValue(dockerfile, buildArgs, baseImageEnv, variable, stage, beforeInstructionIndex) || '';
const isVarExp = match.groups!.isVarExp ? true : false;
let value = findValue(dockerfile, buildArgs, baseImageEnv, variable, stage, beforeInstructionIndex) || '';
if (isVarExp) {
// Handle replacing variable expressions (${var:+word}) if they exist
const option = match.groups!.option;
const word = match.groups!.word;
const isSet = value !== '';
value = getExpressionValue(option, isSet, word, value);
}

return {
begin: match.index!,
end: match.index! + match[0].length,
Expand Down
50 changes: 50 additions & 0 deletions src/test/dockerfileUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,56 @@ FROM "\${BASE_IMAGE}"
const image = findBaseImage(extracted, {}, undefined);
assert.strictEqual(image, 'ubuntu:latest');
});

describe('Variable substitution', () => {
it('Positive variable expression with value specified', async () => {
const dockerfile = `
ARG cloud
FROM \${cloud:+mcr.microsoft.com/}azure-cli:latest
`;
const extracted = extractDockerfile(dockerfile);
assert.strictEqual(extracted.stages.length, 1);
const image = findBaseImage(extracted, {
'cloud': 'true'
}, undefined);
assert.strictEqual(image, 'mcr.microsoft.com/azure-cli:latest');
});

it('Positive variable expression with no value specified', async () => {
const dockerfile = `
ARG cloud
FROM \${cloud:+mcr.microsoft.com/}azure-cli:latest
`;
const extracted = extractDockerfile(dockerfile);
assert.strictEqual(extracted.stages.length, 1);
const image = findBaseImage(extracted, {}, undefined);
assert.strictEqual(image, 'azure-cli:latest');
});

it('Negative variable expression with value specified', async () => {
const dockerfile = `
ARG cloud
FROM \${cloud:-mcr.microsoft.com/}azure-cli:latest
`;
const extracted = extractDockerfile(dockerfile);
assert.strictEqual(extracted.stages.length, 1);
const image = findBaseImage(extracted, {
'cloud': 'ghcr.io/'
}, undefined);
assert.strictEqual(image, 'ghcr.io/azure-cli:latest');
});

it('Negative variable expression with no value specified', async () => {
const dockerfile = `
ARG cloud
FROM \${cloud:-mcr.microsoft.com/}azure-cli:latest
`;
const extracted = extractDockerfile(dockerfile);
assert.strictEqual(extracted.stages.length, 1);
const image = findBaseImage(extracted, {}, undefined);
assert.strictEqual(image, 'mcr.microsoft.com/azure-cli:latest');
});
});
});

describe('findUserStatement', () => {
Expand Down

0 comments on commit 338857f

Please sign in to comment.