Skip to content
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

Add tags with build command #767

Merged
merged 2 commits into from
Mar 1, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Notable changes.

## March 2024

### [0.57.0]
- Fix crash updating UID/GID when the image's platform is different from the native CPU arch (https://github.com/devcontainers/cli/pull/746)
- Add tags with build command (https://github.com/devcontainers/ci/issues/271)

## February 2024

### [0.56.2]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devcontainers/cli",
"description": "Dev Containers CLI",
"version": "0.56.2",
"version": "0.57.0",
"bin": {
"devcontainer": "devcontainer.js"
},
Expand Down
10 changes: 9 additions & 1 deletion src/spec-node/containerFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,21 @@ export const getSafeId = (str: string) => str
.replace(/^[\d_]+/g, '_')
.toUpperCase();

export async function extendImage(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, imageName: string, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>, canAddLabelsToContainer: boolean) {
export async function extendImage(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, imageName: string, additionalImageNames: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>, canAddLabelsToContainer: boolean) {
const { common } = params;
const { cliHost, output } = common;

const imageBuildInfo = await getImageBuildInfoFromImage(params, imageName, config.substitute);
const extendImageDetails = await getExtendImageBuildInfo(params, config, imageName, imageBuildInfo, undefined, additionalFeatures, canAddLabelsToContainer);
if (!extendImageDetails?.featureBuildInfo) {
// no feature extensions - return
if (additionalImageNames.length) {
if (params.isTTY) {
await Promise.all(additionalImageNames.map(name => dockerPtyCLI(params, 'tag', imageName, name)));
} else {
await Promise.all(additionalImageNames.map(name => dockerCLI(params, 'tag', imageName, name)));
}
}
return {
updatedImageName: [imageName],
imageMetadata: getDevcontainerMetadata(imageBuildInfo.metadata, config, extendImageDetails?.featuresConfig),
Expand Down Expand Up @@ -99,6 +106,7 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
args.push(
'--target', featureBuildInfo.overrideTarget,
'-t', updatedImageName,
...additionalImageNames.map(name => ['-t', name]).flat(),
'-f', dockerfilePath,
emptyTempDir
);
Expand Down
19 changes: 10 additions & 9 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ContainerError } from '../spec-common/errors';
import { Log, LogDimensions, LogLevel, makeLog, mapLogLevel } from '../spec-utils/log';
import { probeRemoteEnv, runLifecycleHooks, runRemoteCommand, UserEnvProbe, setupInContainer } from '../spec-common/injectHeadless';
import { extendImage } from './containerFeatures';
import { DockerCLIParameters, dockerPtyCLI, inspectContainer } from '../spec-shutdown/dockerUtils';
import { dockerCLI, DockerCLIParameters, dockerPtyCLI, inspectContainer } from '../spec-shutdown/dockerUtils';
import { buildAndExtendDockerCompose, dockerComposeCLIConfig, getDefaultImageName, getProjectName, readDockerComposeConfig, readVersionPrefix } from './dockerCompose';
import { DevContainerFromDockerComposeConfig, DevContainerFromDockerfileConfig, getDockerComposeFilePaths } from '../spec-configuration/configuration';
import { workspaceFromPath } from '../spec-utils/workspaces';
Expand Down Expand Up @@ -583,7 +583,7 @@ async function doBuild({
omitSyntaxDirective,
}, disposables);

const { common, dockerCLI, dockerComposeCLI } = params;
const { common, dockerComposeCLI } = params;
const { cliHost, env, output } = common;
const workspace = workspaceFromPath(cliHost.path, workspaceFolder);
const configPath = configFile ? configFile : workspace
Expand All @@ -602,7 +602,7 @@ async function doBuild({
throw new ContainerError({ description: '--push true cannot be used with --output.' });
}

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

// Support multiple use of `--image-name`
Expand All @@ -614,9 +614,6 @@ async function doBuild({
let { updatedImageName } = await buildNamedImageAndExtend(params, configWithRaw as SubstitutedConfig<DevContainerFromDockerfileConfig>, additionalFeatures, false, imageNames);

if (imageNames) {
if (!buildxPush && !buildxOutput) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', updatedImageName[0], imageName)));
}
imageNameResult = imageNames;
} else {
imageNameResult = updatedImageName;
Expand Down Expand Up @@ -660,7 +657,12 @@ async function doBuild({
const originalImageName = overrideImageName || service.image || getDefaultImageName(await buildParams.dockerComposeCLI(), projectName, config.service);

if (imageNames) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', originalImageName, imageName)));
// Future improvement: Compose 2.6.0 (released 2022-05-30) added `tags` to the compose file.
if (params.isTTY) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', originalImageName, imageName)));
} else {
await Promise.all(imageNames.map(imageName => dockerCLI(params, 'tag', originalImageName, imageName)));
}
imageNameResult = imageNames;
} else {
imageNameResult = originalImageName;
Expand All @@ -672,10 +674,9 @@ async function doBuild({
}

await inspectDockerImage(params, config.image, true);
const { updatedImageName } = await extendImage(params, configWithRaw, config.image, additionalFeatures, false);
const { updatedImageName } = await extendImage(params, configWithRaw, config.image, imageNames || [], additionalFeatures, false);

if (imageNames) {
await Promise.all(imageNames.map(imageName => dockerPtyCLI(params, 'tag', updatedImageName[0], imageName)));
imageNameResult = imageNames;
} else {
imageNameResult = updatedImageName;
Expand Down
2 changes: 1 addition & 1 deletion src/spec-node/singleContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export async function buildNamedImageAndExtend(params: DockerResolverParameters,
return await buildAndExtendImage(params, configWithRaw as SubstitutedConfig<DevContainerFromDockerfileConfig>, imageNames, params.buildNoCache ?? false, additionalFeatures);
}
// image-based dev container - extend
return await extendImage(params, configWithRaw, imageNames[0], additionalFeatures, canAddLabelsToContainer);
return await extendImage(params, configWithRaw, imageNames[0], argImageNames || [], additionalFeatures, canAddLabelsToContainer);
}

async function buildAndExtendImage(buildParams: DockerResolverParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerfileConfig>, baseImageNames: string[], noCache: boolean, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
Expand Down
6 changes: 6 additions & 0 deletions src/test/cli.build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,33 +153,39 @@ describe('Dev Containers CLI', function () {
const testFolder = `${__dirname}/configs/dockerfile-with-features`;
const image1 = 'image-1';
const image2 = 'image-2';
await shellExec(`docker rmi -f ${image1} ${image2}`);
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);
await shellExec(`docker inspect --type image ${image1} ${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';
await shellExec(`docker rmi -f ${image1} ${image2}`);
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);
await shellExec(`docker inspect --type image ${image1} ${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';
await shellExec(`docker rmi -f ${image1} ${image2}`);
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);
await shellExec(`docker inspect --type image ${image1} ${image2}`);
});

it('should fail with --push true and --output', async () => {
Expand Down
Loading