Skip to content

Build command: Support multiple --image-name parameters #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 6, 2022
Merged
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
4 changes: 2 additions & 2 deletions src/spec-node/containerFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function extendImage(params: DockerResolverParameters, config: DevC
if (!extendImageDetails || !extendImageDetails.featureBuildInfo) {
// no feature extensions - return
return {
updatedImageName: imageName,
updatedImageName: [imageName],
collapsedFeaturesConfig: undefined,
imageDetails
};
Expand Down Expand Up @@ -93,7 +93,7 @@ export async function extendImage(params: DockerResolverParameters, config: DevC
const infoParams = { ...toExecParameters(params), output: makeLog(output, LogLevel.Info), print: 'continuous' as 'continuous' };
await dockerCLI(infoParams, ...args);
}
return { updatedImageName, collapsedFeaturesConfig, imageDetails };
return { updatedImageName:[updatedImageName], collapsedFeaturesConfig, imageDetails };
}

export async function getExtendImageBuildInfo(params: DockerResolverParameters, config: DevContainerConfig, baseName: string, imageUser: string, imageLabelDetails: () => Promise<{ definition: string | undefined; version: string | undefined }>) {
Expand Down
25 changes: 14 additions & 11 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,21 @@ async function doBuild({
throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` });
}
const { config } = configs;
let imageNameResult = '';
let imageNameResult: string[] = [''];

// Support multiple use of `--image-name`
const imageNames = (argImageName && (Array.isArray(argImageName) ? argImageName : [argImageName]) as string[]) || undefined;

if (isDockerFileConfig(config)) {

// Build the base image and extend with features etc.
const { updatedImageName } = await buildNamedImageAndExtend(params, config, argImageName);
let { updatedImageName } = await buildNamedImageAndExtend(params, config, imageNames);

if (argImageName) {
if (imageNames) {
if (!buildxPush) {
await dockerPtyCLI(params, 'tag', updatedImageName, argImageName);
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', updatedImageName[0], imageName)));
}
imageNameResult = argImageName;
imageNameResult = imageNames;
} else {
imageNameResult = updatedImageName;
}
Expand Down Expand Up @@ -374,9 +377,9 @@ async function doBuild({
const service = composeConfig.services[config.service];
const originalImageName = service.image || `${projectName}_${config.service}`;

if (argImageName) {
await dockerPtyCLI(params, 'tag', originalImageName, argImageName);
imageNameResult = argImageName;
if (imageNames) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', originalImageName, imageName)));
imageNameResult = imageNames;
} else {
imageNameResult = originalImageName;
}
Expand All @@ -388,9 +391,9 @@ async function doBuild({
if (buildxPlatform || buildxPush) {
throw new ContainerError({ description: '--platform or --push require dockerfilePath.' });
}
if (argImageName) {
await dockerPtyCLI(params, 'tag', updatedImageName, argImageName);
imageNameResult = argImageName;
if (imageNames) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', updatedImageName[0], imageName)));
imageNameResult = imageNames;
} else {
imageNameResult = updatedImageName;
}
Expand Down
20 changes: 11 additions & 9 deletions src/spec-node/singleContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function openDockerfileDevContainer(params: DockerResolverParameter
await startExistingContainer(params, idLabels, container);
} else {
const res = await buildNamedImageAndExtend(params, config);
const updatedImageName = await updateRemoteUserUID(params, config, res.updatedImageName, res.imageDetails, findUserArg(config.runArgs) || config.containerUser);
const updatedImageName = await updateRemoteUserUID(params, config, res.updatedImageName[0], res.imageDetails, findUserArg(config.runArgs) || config.containerUser);

// collapsedFeaturesConfig = async () => res.collapsedFeaturesConfig;

Expand Down Expand Up @@ -103,17 +103,17 @@ async function setupContainer(container: ContainerDetails, params: DockerResolve
function getDefaultName(config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, params: DockerResolverParameters) {
return 'image' in config ? config.image : getFolderImageName(params.common);
}
export async function buildNamedImageAndExtend(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, argImageName?: string) {
const imageName = argImageName ?? getDefaultName(config, params);
export async function buildNamedImageAndExtend(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, argImageNames?: string[]) {
const imageNames = argImageNames ?? [getDefaultName(config, params)];
params.common.progress(ResolverProgress.BuildingImage);
if (isDockerFileConfig(config)) {
return await buildAndExtendImage(params, config, imageName, params.buildNoCache ?? false);
return await buildAndExtendImage(params, config, imageNames, params.buildNoCache ?? false);
}
// image-based dev container - extend
return await extendImage(params, config, imageName, 'image' in config);
return await extendImage(params, config, imageNames[0], 'image' in config);
}

async function buildAndExtendImage(buildParams: DockerResolverParameters, config: DevContainerFromDockerfileConfig, baseImageName: string, noCache: boolean) {
async function buildAndExtendImage(buildParams: DockerResolverParameters, config: DevContainerFromDockerfileConfig, baseImageNames: string[], noCache: boolean) {
const { cliHost, output } = buildParams.common;
const dockerfileUri = getDockerfilePath(cliHost, config);
const dockerfilePath = await uriToWSLFsPath(dockerfileUri, cliHost);
Expand Down Expand Up @@ -180,7 +180,9 @@ async function buildAndExtendImage(buildParams: DockerResolverParameters, config
} else {
args.push('build');
}
args.push('-f', finalDockerfilePath, '-t', baseImageName);
args.push('-f', finalDockerfilePath);

baseImageNames.map(imageName => args.push('-t', imageName));

const target = config.build?.target;
if (target) {
Expand Down Expand Up @@ -223,10 +225,10 @@ async function buildAndExtendImage(buildParams: DockerResolverParameters, config
throw new ContainerError({ description: 'An error occurred building the image.', originalError: err, data: { fileWithError: dockerfilePath } });
}

const imageDetails = () => inspectDockerImage(buildParams, baseImageName, false);
const imageDetails = () => inspectDockerImage(buildParams, baseImageNames[0], false);

return {
updatedImageName: baseImageName,
updatedImageName: baseImageNames,
collapsedFeaturesConfig: extendImageBuildInfo?.collapsedFeaturesConfig,
imageDetails
};
Expand Down
33 changes: 33 additions & 0 deletions src/test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,39 @@ describe('Dev Containers CLI', function () {
}
assert.equal(success, false, 'expect non-successful call');
});

it('should succeed with multiple --image-name parameters when DockerFile is present', async () => {
const testFolder = `${__dirname}/configs/dockerfile-with-features`;
const image1 = 'image-1';
const image2 = 'image-2';
const res = await shellExec(`${cli} build --workspace-folder ${testFolder} --image-name ${image1} --image-name ${image2}`);
const response = JSON.parse(res.stdout);
assert.equal(response.outcome, 'success');
assert.equal(response.imageName[0], image1);
assert.equal(response.imageName[1], image2);
});

it('should succeed with multiple --image-name parameters when dockerComposeFile is present', async () => {
const testFolder = `${__dirname}/configs/compose-Dockerfile-alpine`;
const image1 = 'image-1';
const image2 = 'image-2';
const res = await shellExec(`${cli} build --workspace-folder ${testFolder} --image-name ${image1} --image-name ${image2}`);
const response = JSON.parse(res.stdout);
assert.equal(response.outcome, 'success');
assert.equal(response.imageName[0], image1);
assert.equal(response.imageName[1], image2);
});

it('should succeed with multiple --image-name parameters when image is present', async () => {
const testFolder = `${__dirname}/configs/image`;
const image1 = 'image-1';
const image2 = 'image-2';
const res = await shellExec(`${cli} build --workspace-folder ${testFolder} --image-name ${image1} --image-name ${image2}`);
const response = JSON.parse(res.stdout);
assert.equal(response.outcome, 'success');
assert.equal(response.imageName[0], image1);
assert.equal(response.imageName[1], image2);
});
});

describe('Command up', () => {
Expand Down