Skip to content

Commit 33073db

Browse files
v-Kaniska244abdurriqCopilot
authored
fix: set default base image in Dockerfiles and improve syntax handling (#1271)
* fix: set default base image in Dockerfiles and improve syntax handling * Retrigger test * Addressing review comments. * Retrigger tests * Fixing failing tests due to change in terraform feature to add hard dependency of github-cli feature. * Removing unrelated test fixes * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Abdurrahmaan Iqbal <abdurriq@github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 98c5265 commit 33073db

6 files changed

Lines changed: 62 additions & 9 deletions

File tree

.devcontainer/devcontainer-lock.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
"integrity": "sha256:ce078b7bf7d9ef3bcb9813b32103795d8d72172446890b64772cbe1dec6baafd"
77
}
88
}
9-
}
9+
}

scripts/updateUID.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License. See License.txt in the project root for license information.
3-
ARG BASE_IMAGE
3+
ARG BASE_IMAGE=placeholder
44
FROM $BASE_IMAGE
55

66
USER root

src/spec-configuration/containerFeaturesConfiguration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export function getContainerFeaturesBaseDockerFile(contentSourceRootPath: string
203203
204204
#{nonBuildKitFeatureContentFallback}
205205
206+
ARG _DEV_CONTAINERS_BASE_IMAGE=scratch
207+
206208
FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_feature_content_normalize
207209
USER root
208210
COPY --from=dev_containers_feature_content_source ${path.posix.join(contentSourceRootPath, 'devcontainer-features.builtin.env')} /tmp/build-features/

src/spec-node/containerFeatures.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ ${getDevcontainerMetadataLabel(getDevcontainerMetadata(imageBuildInfo.metadata,
206206
`,
207207
overrideTarget: 'dev_containers_target_stage',
208208
dockerfilePrefixContent: `${syntax ? `# syntax=${syntax}` : ''}
209-
ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
209+
ARG _DEV_CONTAINERS_BASE_IMAGE=scratch
210210
`,
211211
buildArgs: {
212212
_DEV_CONTAINERS_BASE_IMAGE: baseName,
@@ -274,7 +274,7 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
274274
skipDefaultSyntax ? (syntax ? `# syntax=${syntax}` : '') :
275275
useBuildKitBuildContexts && !(imageBuildInfo.dockerfile && supportsBuildContexts(imageBuildInfo.dockerfile)) ? '# syntax=docker/dockerfile:1.4' :
276276
syntax ? `# syntax=${syntax}` : ''}
277-
ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
277+
ARG _DEV_CONTAINERS_BASE_IMAGE=scratch
278278
`;
279279

280280
// Build devcontainer-features.env and devcontainer-features-install.sh file(s) for each features source folder

src/test/container-features/generateFeaturesConfig.test.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import { assert } from 'chai';
2-
import { generateFeaturesConfig, getFeatureLayers, FeatureSet } from '../../spec-configuration/containerFeaturesConfiguration';
2+
import { generateFeaturesConfig, getFeatureLayers, getContainerFeaturesBaseDockerFile, FeatureSet } from '../../spec-configuration/containerFeaturesConfiguration';
33
import { createPlainLog, LogLevel, makeLog } from '../../spec-utils/log';
44
import * as path from 'path';
55
import * as process from 'process';
66
import * as os from 'os';
77
import * as crypto from 'crypto';
8-
import { mkdirpLocal } from '../../spec-utils/pfs';
8+
import { mkdirpLocal, readLocalFile } from '../../spec-utils/pfs';
99
import { DevContainerConfig } from '../../spec-configuration/configuration';
1010
import { URI } from 'vscode-uri';
1111
import { getLocalCacheFolder } from '../../spec-node/utils';
12-
import { shellExec } from '../testUtils';
12+
import { findFromArgsWithoutDefault, shellExec } from '../testUtils';
1313
import { getEntPasswdShellCommand } from '../../spec-common/commonUtils';
1414

1515
export const output = makeLog(createPlainLog(text => process.stdout.write(text), () => LogLevel.Trace));
1616

17-
// Test fetching/generating the devcontainer-features.json config
17+
// Testing fetching/generating the devcontainer-features.json config
1818
describe('validate generateFeaturesConfig()', function () {
1919

20-
// Setup
20+
// Setup for tests
2121
const env = { 'SOME_KEY': 'SOME_VAL' };
2222
const platform = process.platform;
2323
const cacheFolder = path.join(os.tmpdir(), `devcontainercli-test-${crypto.randomUUID()}`);
@@ -135,4 +135,25 @@ RUN chmod -R 0755 /tmp/dev-container-features/hello_1 \\
135135
const javaSettings = java?.features[0]?.customizations?.vscode?.settings;
136136
assert.isObject(javaSettings);
137137
});
138+
});
139+
140+
describe('validate generated Dockerfiles avoid InvalidDefaultArgInFrom', function () {
141+
142+
it('feature base Dockerfile declares _DEV_CONTAINERS_BASE_IMAGE with a default before FROM', function () {
143+
const dockerfile = getContainerFeaturesBaseDockerFile('/tmp/build-features');
144+
assert.match(dockerfile, /ARG _DEV_CONTAINERS_BASE_IMAGE=\S+/, 'ARG should have a default value');
145+
assert.match(dockerfile, /FROM \$_DEV_CONTAINERS_BASE_IMAGE\b/, 'FROM should reference the ARG directly');
146+
assert.notMatch(dockerfile, /FROM \$\{_DEV_CONTAINERS_BASE_IMAGE:-/, 'should not rely on ${VAR:-default} shell fallback');
147+
assert.deepStrictEqual(findFromArgsWithoutDefault(dockerfile), []);
148+
});
149+
150+
it('validate that updateUID.Dockerfile declares BASE_IMAGE with a default before FROM', async function () {
151+
const content = (await readLocalFile('scripts/updateUID.Dockerfile')).toString();
152+
assert.match(content, /ARG BASE_IMAGE=\S+/, 'BASE_IMAGE ARG should have a default value');
153+
assert.deepStrictEqual(
154+
findFromArgsWithoutDefault(content),
155+
[],
156+
'updateUID.Dockerfile FROM references an ARG without a default'
157+
);
158+
});
138159
});

src/test/testUtils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,36 @@ export async function pathExists(cli: string, workspaceFolder: string, location:
126126
return false;
127127
}
128128
}
129+
130+
export function findFromArgsWithoutDefault(dockerfile: string): string[] {
131+
const preambleArgsWithDefault = new Set<string>();
132+
const offenders: string[] = [];
133+
let beforeFirstFrom = true;
134+
135+
for (const rawLine of dockerfile.split('\n')) {
136+
const line = rawLine.trim();
137+
138+
const argWithDefault = /^ARG\s+([A-Za-z0-9_]+)\s*=\s*\S+/.exec(line);
139+
if (argWithDefault) {
140+
if (beforeFirstFrom) {
141+
preambleArgsWithDefault.add(argWithDefault[1]);
142+
}
143+
continue;
144+
}
145+
146+
const fromMatch = /^FROM(?:\s+--platform=\S+)?\s+\$\{?([A-Za-z0-9_]+)/i.exec(line);
147+
if (fromMatch && !preambleArgsWithDefault.has(fromMatch[1])) {
148+
offenders.push(fromMatch[1]);
149+
}
150+
151+
if (/^FROM\b/i.test(line)) {
152+
beforeFirstFrom = false;
153+
}
154+
}
155+
156+
return offenders;
157+
}
158+
129159
export async function commandMarkerTests(cli: string, workspaceFolder: string, expected: { postCreate: boolean; postStart: boolean; postAttach: boolean }, message: string) {
130160
const actual = {
131161
postCreate: await pathExists(cli, workspaceFolder, '/tmp/postCreateCommand.testmarker'),

0 commit comments

Comments
 (0)