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
2 changes: 1 addition & 1 deletion src/spec-node/configContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu

const { dockerCLI, dockerComposeCLI } = params;
const { env } = common;
const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo };
const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo };
await ensureNoDisallowedFeatures(cliParams, config, additionalFeatures, idLabels);

await runInitializeCommand({ ...params, common: { ...common, output: common.lifecycleHook.output } }, config.initializeCommand, common.lifecycleHook.onDidInput);
Expand Down
16 changes: 12 additions & 4 deletions src/spec-node/devContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,12 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
output: common.output,
}, dockerPath, dockerComposePath);

const platformInfo = (() => {
const buildPlatformInfo = {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
};

const targetPlatformInfo = (() => {
if (common.buildxPlatform) {
const slash1 = common.buildxPlatform.indexOf('/');
const slash2 = common.buildxPlatform.indexOf('/', slash1 + 1);
Expand Down Expand Up @@ -204,7 +209,8 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
dockerComposeCLI,
env: cliHost.env,
output,
platformInfo
buildPlatformInfo,
targetPlatformInfo
}));

const dockerEngineVer = await dockerEngineVersion({
Expand All @@ -213,7 +219,8 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
dockerComposeCLI,
env: cliHost.env,
output,
platformInfo
buildPlatformInfo,
targetPlatformInfo
});

return {
Expand Down Expand Up @@ -246,7 +253,8 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
additionalLabels: options.additionalLabels,
buildxOutput: common.buildxOutput,
buildxCacheTo: common.buildxCacheTo,
platformInfo
buildPlatformInfo,
targetPlatformInfo
};
}

Expand Down
12 changes: 7 additions & 5 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ async function doBuild({
throw new ContainerError({ description: '--push true cannot be used with --output.' });
}

const buildParams: DockerCLIParameters = { cliHost, dockerCLI: params.dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo };
const buildParams: DockerCLIParameters = { cliHost, dockerCLI: params.dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo };
await ensureNoDisallowedFeatures(buildParams, config, additionalFeatures, undefined);

// Support multiple use of `--image-name`
Expand Down Expand Up @@ -1058,16 +1058,18 @@ async function readConfiguration({
env: cliHost.env,
output,
}, dockerCLI, dockerComposePath || 'docker-compose');
const buildPlatformInfo = {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
};
const params: DockerCLIParameters = {
cliHost,
dockerCLI,
dockerComposeCLI,
env: cliHost.env,
output,
platformInfo: {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
}
buildPlatformInfo,
targetPlatformInfo: buildPlatformInfo
};
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
if (container) {
Expand Down
4 changes: 2 additions & 2 deletions src/spec-node/dockerCompose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const serviceLabel = 'com.docker.compose.service';
export async function openDockerComposeDevContainer(params: DockerResolverParameters, workspace: Workspace, config: SubstitutedConfig<DevContainerFromDockerComposeConfig>, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
const { common, dockerCLI, dockerComposeCLI } = params;
const { cliHost, env, output } = common;
const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo };
const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo };
return _openDockerComposeDevContainer(params, buildParams, workspace, config, getRemoteWorkspaceFolder(config.config), idLabels, additionalFeatures);
}

Expand Down Expand Up @@ -155,7 +155,7 @@ export async function buildAndExtendDockerCompose(configWithRaw: SubstitutedConf
const { cliHost, env, output } = common;
const { config } = configWithRaw;

const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI: dockerComposeCLIFunc, env, output, platformInfo: params.platformInfo };
const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI: dockerComposeCLIFunc, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo };
const composeConfig = await readDockerComposeConfig(cliParams, localComposeFiles, envFile);
const composeService = composeConfig.services[config.service];

Expand Down
4 changes: 2 additions & 2 deletions src/spec-node/dockerfileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function findUserStatement(dockerfile: Dockerfile, buildArgs: Record<stri
return undefined;
}

export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record<string, string>, target: string | undefined) {
export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record<string, string>, target: string | undefined, globalBuildxPlatformArgs: Record<string, string> = {}) {
let stage: Stage | undefined = target ? dockerfile.stagesByLabel[target] : dockerfile.stages[dockerfile.stages.length - 1];
const seen = new Set<Stage>();
while (stage) {
Expand All @@ -109,7 +109,7 @@ export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record<string,
}
seen.add(stage);

const image = replaceVariables(dockerfile, buildArgs, /* not available in FROM instruction */ {}, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length);
const image = replaceVariables(dockerfile, buildArgs, globalBuildxPlatformArgs, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length);
const nextStage = dockerfile.stagesByLabel[image];
if (!nextStage) {
return image;
Expand Down
33 changes: 28 additions & 5 deletions src/spec-node/imageMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { ContainerError } from '../spec-common/errors';
import { PlatformInfo } from '../spec-common/commonUtils';
import { LifecycleCommand, LifecycleHooksInstallMap } from '../spec-common/injectHeadless';
import { DevContainerConfig, DevContainerConfigCommand, DevContainerFromDockerComposeConfig, DevContainerFromDockerfileConfig, DevContainerFromImageConfig, getDockerComposeFilePaths, getDockerfilePath, HostGPURequirements, HostRequirements, isDockerFileConfig, PortAttributes, UserEnvProbe } from '../spec-configuration/configuration';
import { Feature, FeaturesConfig, Mount, parseMount, SchemaFeatureLifecycleHooks } from '../spec-configuration/containerFeaturesConfiguration';
Expand Down Expand Up @@ -349,7 +350,7 @@ export async function getImageBuildInfo(params: DockerResolverParameters | Docke
const cwdEnvFile = cliHost.path.join(cliHost.cwd, '.env');
const envFile = Array.isArray(config.dockerComposeFile) && config.dockerComposeFile.length === 0 && await cliHost.isFile(cwdEnvFile) ? cwdEnvFile : undefined;
const composeFiles = await getDockerComposeFilePaths(cliHost, config, cliHost.env, cliHost.cwd);
const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env: cliHost.env, output, platformInfo: params.platformInfo };
const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env: cliHost.env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo };

const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile);
const services = Object.keys(composeConfig.services || {});
Expand Down Expand Up @@ -394,18 +395,40 @@ export async function getImageBuildInfoFromImage(params: DockerResolverParameter
export async function getImageBuildInfoFromDockerfile(params: DockerResolverParameters | DockerCLIParameters, dockerfile: string, dockerBuildArgs: Record<string, string>, targetStage: string | undefined, substitute: SubstituteConfig) {
const { output } = 'output' in params ? params : params.common;
const omitSyntaxDirective = 'common' in params ? !!params.common.omitSyntaxDirective : false;
return internalGetImageBuildInfoFromDockerfile(imageName => inspectDockerImage(params, imageName, true), dockerfile, dockerBuildArgs, targetStage, substitute, output, omitSyntaxDirective);
return internalGetImageBuildInfoFromDockerfile(imageName => inspectDockerImage(params, imageName, true), dockerfile, dockerBuildArgs, targetStage, substitute, output, omitSyntaxDirective, params.buildPlatformInfo, params.targetPlatformInfo);
}

export async function internalGetImageBuildInfoFromDockerfile(inspectDockerImage: (imageName: string) => Promise<ImageDetails>, dockerfileText: string, dockerBuildArgs: Record<string, string>, targetStage: string | undefined, substitute: SubstituteConfig, output: Log, omitSyntaxDirective: boolean): Promise<ImageBuildInfo> {
export async function internalGetImageBuildInfoFromDockerfile(inspectDockerImage: (imageName: string) => Promise<ImageDetails>, dockerfileText: string, dockerBuildArgs: Record<string, string>, targetStage: string | undefined, substitute: SubstituteConfig, output: Log, omitSyntaxDirective: boolean, buildPlatform: PlatformInfo, targetPlatform: PlatformInfo): Promise<ImageBuildInfo> {
const dockerfile = extractDockerfile(dockerfileText);
if (dockerfile.preamble.directives.syntax && omitSyntaxDirective) {
output.write(`Omitting syntax directive '${dockerfile.preamble.directives.syntax}' from Dockerfile.`, LogLevel.Trace);
delete dockerfile.preamble.directives.syntax;
}
const baseImage = findBaseImage(dockerfile, dockerBuildArgs, targetStage);
// https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#automatic-platform-args-in-the-global-scope
const globalBuildxPlatformArgs = {
// platform of the node performing the build.
BUILDPLATFORM: [buildPlatform.os, buildPlatform.arch, buildPlatform.variant].filter(Boolean).join("/"),
// OS component of BUILDPLATFORM
BUILDOS: buildPlatform.os,
// architecture component of BUILDPLATFORM
BUILDARCH: buildPlatform.arch,
// variant component of BUILDPLATFORM
BUILDVARIANT: buildPlatform.variant ?? "",
// platform of the build result. Eg linux/amd64, linux/arm/v7, windows/amd64.
TARGETPLATFORM: [targetPlatform.os, targetPlatform.arch, targetPlatform.variant].filter(Boolean).join("/"),
// OS component of TARGETPLATFORM
TARGETOS: targetPlatform.os,
// architecture component of TARGETPLATFORM
TARGETARCH: targetPlatform.arch,
// variant component of TARGETPLATFORM
TARGETVARIANT: targetPlatform.variant ?? "",
};
const baseImage = findBaseImage(dockerfile, dockerBuildArgs, targetStage, globalBuildxPlatformArgs);
const imageDetails = baseImage && await inspectDockerImage(baseImage) || undefined;
const dockerfileUser = findUserStatement(dockerfile, dockerBuildArgs, envListToObj(imageDetails?.Config.Env), targetStage);
const dockerfileUser = findUserStatement(dockerfile, dockerBuildArgs, {
...envListToObj(imageDetails?.Config.Env),
...globalBuildxPlatformArgs,
}, targetStage);
const user = dockerfileUser || imageDetails?.Config.User || 'root';
const metadata = imageDetails ? getImageMetadata(imageDetails, substitute, output) : { config: [], raw: [], substitute };
return {
Expand Down
10 changes: 6 additions & 4 deletions src/spec-node/upgradeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,18 @@ async function featuresUpgrade({
env: cliHost.env,
output,
}, dockerPath, dockerComposePath);
const buildPlatformInfo = {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
};
const dockerParams: DockerCLIParameters = {
cliHost,
dockerCLI: dockerPath,
dockerComposeCLI,
env: cliHost.env,
output,
platformInfo: {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
}
buildPlatformInfo,
targetPlatformInfo: buildPlatformInfo,
};

const workspace = workspaceFromPath(cliHost.path, workspaceFolder);
Expand Down
5 changes: 3 additions & 2 deletions src/spec-node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ export interface DockerResolverParameters {
additionalLabels: string[];
buildxOutput: string | undefined;
buildxCacheTo: string | undefined;
platformInfo: PlatformInfo;
buildPlatformInfo: PlatformInfo;
targetPlatformInfo: PlatformInfo;
}

export interface ResolverResult {
Expand Down Expand Up @@ -250,7 +251,7 @@ export async function inspectDockerImage(params: DockerResolverParameters | Dock
throw inspectErr;
}
try {
return await inspectImageInRegistry(output, params.platformInfo, imageName);
return await inspectImageInRegistry(output, params.targetPlatformInfo, imageName);
} catch (inspectErr2) {
output.write(`Error fetching image details: ${inspectErr2?.message}`, LogLevel.Info);
}
Expand Down
3 changes: 2 additions & 1 deletion src/spec-shutdown/dockerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export interface DockerCLIParameters {
dockerComposeCLI: () => Promise<DockerComposeCLI>;
env: NodeJS.ProcessEnv;
output: Log;
platformInfo: PlatformInfo;
buildPlatformInfo: PlatformInfo;
targetPlatformInfo: PlatformInfo;
}

export interface PartialExecParameters {
Expand Down
37 changes: 35 additions & 2 deletions src/test/dockerfileUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ FROM ubuntu:latest as dev
const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => {
assert.strictEqual(imageName, 'ubuntu:latest');
return details;
}, dockerfile, {}, undefined, testSubstitute, nullLog, false);
}, dockerfile, {}, undefined, testSubstitute, nullLog, false, {} as any, {} as any);
assert.strictEqual(info.user, 'imageUser');
assert.strictEqual(info.metadata.config.length, 1);
assert.strictEqual(info.metadata.config[0].id, 'testid-substituted');
Expand Down Expand Up @@ -206,11 +206,44 @@ USER dockerfileUserB
const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => {
assert.strictEqual(imageName, 'ubuntu:latest');
return details;
}, dockerfile, {}, undefined, testSubstitute, nullLog, false);
}, dockerfile, {}, undefined, testSubstitute, nullLog, false, {} as any, {} as any);
assert.strictEqual(info.user, 'dockerfileUserB');
assert.strictEqual(info.metadata.config.length, 0);
assert.strictEqual(info.metadata.raw.length, 0);
});

it('for a USER in a multiarch image', async () => {
const dockerfile = `
FROM ubuntu:latest as base-amd64
USER amd64_user

FROM ubuntu:latest as base-arm64
USER arm64_user

FROM base-\${TARGETARCH}

ARG TARGETARCH
`;
const details: ImageDetails = {
Id: '123',
Config: {
User: 'imageUser',
Env: null,
Labels: null,
Entrypoint: null,
Cmd: null
},
Os: 'linux',
Architecture: 'amd64'
};
const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => {
assert.strictEqual(imageName, 'ubuntu:latest');
return details;
}, dockerfile, {}, undefined, testSubstitute, nullLog, false, {} as any, { os: 'linux', arch: 'amd64' });
assert.strictEqual(info.user, 'amd64_user');
assert.strictEqual(info.metadata.config.length, 0);
assert.strictEqual(info.metadata.raw.length, 0);
});
});

describe('findBaseImage', () => {
Expand Down
10 changes: 6 additions & 4 deletions src/test/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,18 @@ export async function createCLIParams(hostPath: string) {
env: cliHost.env,
output,
}, 'docker', 'docker-compose');
const buildPlatformInfo = {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
};
const cliParams: DockerCLIParameters = {
cliHost,
dockerCLI: 'docker',
dockerComposeCLI,
env: {},
output,
platformInfo: {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
}
buildPlatformInfo,
targetPlatformInfo: buildPlatformInfo,
};
return cliParams;
}