Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,
Comment thread
trxcllnt marked this conversation as resolved.
Outdated
}, 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);
Comment thread
trxcllnt marked this conversation as resolved.
Outdated
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
Comment thread
trxcllnt marked this conversation as resolved.
Outdated
`;
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;
}