Skip to content

Commit b7fb42b

Browse files
authored
Support project name attribute (microsoft/vscode-remote-release#512)
1 parent c1c8b08 commit b7fb42b

File tree

5 files changed

+43
-12
lines changed

5 files changed

+43
-12
lines changed

src/spec-node/devContainersSpecCLI.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,9 +645,9 @@ async function doBuild({
645645
if (envFile) {
646646
composeGlobalArgs.push('--env-file', envFile);
647647
}
648-
const projectName = await getProjectName(params, workspace, composeFiles);
649-
648+
650649
const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile);
650+
const projectName = await getProjectName(params, workspace, composeFiles, composeConfig);
651651
const services = Object.keys(composeConfig.services || {});
652652
if (services.indexOf(config.service) === -1) {
653653
throw new Error(`Service '${config.service}' configured in devcontainer.json not found in Docker Compose configuration.`);

src/spec-node/dockerCompose.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ async function _openDockerComposeDevContainer(params: DockerResolverParameters,
4343
const composeFiles = await getDockerComposeFilePaths(buildCLIHost, config, buildCLIHost.env, buildCLIHost.cwd);
4444
const cwdEnvFile = buildCLIHost.path.join(buildCLIHost.cwd, '.env');
4545
const envFile = Array.isArray(config.dockerComposeFile) && config.dockerComposeFile.length === 0 && await buildCLIHost.isFile(cwdEnvFile) ? cwdEnvFile : undefined;
46-
const projectName = await getProjectName(buildParams, workspace, composeFiles);
46+
const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile);
47+
const projectName = await getProjectName(buildParams, workspace, composeFiles, composeConfig);
4748
const containerId = await findComposeContainer(params, projectName, config.service);
4849
if (params.expectExistingContainer && !containerId) {
4950
throw new ContainerError({ description: 'The expected container does not exist.' });
@@ -60,7 +61,7 @@ async function _openDockerComposeDevContainer(params: DockerResolverParameters,
6061

6162
// let collapsedFeaturesConfig: CollapsedFeaturesConfig | undefined;
6263
if (!container || container.State.Status !== 'running') {
63-
const res = await startContainer(params, buildParams, configWithRaw, projectName, composeFiles, envFile, container, idLabels, additionalFeatures);
64+
const res = await startContainer(params, buildParams, configWithRaw, projectName, composeFiles, envFile, composeConfig, container, idLabels, additionalFeatures);
6465
container = await inspectContainer(params, res.containerId);
6566
// collapsedFeaturesConfig = res.collapsedFeaturesConfig;
6667
// } else {
@@ -327,7 +328,8 @@ async function checkForPersistedFile(cliHost: CLIHost, output: Log, files: strin
327328
foundLabel: false
328329
};
329330
}
330-
async function startContainer(params: DockerResolverParameters, buildParams: DockerCLIParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerComposeConfig>, projectName: string, composeFiles: string[], envFile: string | undefined, container: ContainerDetails | undefined, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
331+
332+
async function startContainer(params: DockerResolverParameters, buildParams: DockerCLIParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerComposeConfig>, projectName: string, composeFiles: string[], envFile: string | undefined, composeConfig: any, container: ContainerDetails | undefined, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
331333
const { common } = params;
332334
const { persistedFolder, output } = common;
333335
const { cliHost: buildCLIHost } = buildParams;
@@ -337,15 +339,13 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
337339

338340
common.progress(ResolverProgress.StartingContainer);
339341

340-
const localComposeFiles = composeFiles;
341342
// If dockerComposeFile is an array, add -f <file> in order. https://docs.docker.com/compose/extends/#multiple-compose-files
342-
const composeGlobalArgs = ([] as string[]).concat(...localComposeFiles.map(composeFile => ['-f', composeFile]));
343+
const composeGlobalArgs = ([] as string[]).concat(...composeFiles.map(composeFile => ['-f', composeFile]));
343344
if (envFile) {
344345
composeGlobalArgs.push('--env-file', envFile);
345346
}
346347

347348
const infoOutput = makeLog(buildParams.output, LogLevel.Info);
348-
const composeConfig = await readDockerComposeConfig(buildParams, localComposeFiles, envFile);
349349
const services = Object.keys(composeConfig.services || {});
350350
if (services.indexOf(config.service) === -1) {
351351
throw new ContainerError({ description: `Service '${config.service}' configured in devcontainer.json not found in Docker Compose configuration.`, data: { fileWithError: composeFiles[0] } });
@@ -391,9 +391,9 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
391391
if (!container || !didRestoreFromPersistedShare) {
392392
const noBuild = !!container; //if we have an existing container, just recreate override files but skip the build
393393

394-
const versionPrefix = await readVersionPrefix(buildCLIHost, localComposeFiles);
394+
const versionPrefix = await readVersionPrefix(buildCLIHost, composeFiles);
395395
const infoParams = { ...params, common: { ...params.common, output: infoOutput } };
396-
const { imageMetadata, additionalComposeOverrideFiles, overrideImageName, labels } = await buildAndExtendDockerCompose(configWithRaw, projectName, infoParams, localComposeFiles, envFile, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, persistedFolder, featuresBuildOverrideFilePrefix, versionPrefix, additionalFeatures, true, params.additionalCacheFroms, noBuild);
396+
const { imageMetadata, additionalComposeOverrideFiles, overrideImageName, labels } = await buildAndExtendDockerCompose(configWithRaw, projectName, infoParams, composeFiles, envFile, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, persistedFolder, featuresBuildOverrideFilePrefix, versionPrefix, additionalFeatures, true, params.additionalCacheFroms, noBuild);
397397
additionalComposeOverrideFiles.forEach(overrideFilePath => composeGlobalArgs.push('-f', overrideFilePath));
398398

399399
const currentImageName = overrideImageName || originalImageName;
@@ -439,7 +439,7 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
439439
description = err.cmdOutput;
440440
}
441441

442-
throw new ContainerError({ description, originalError: err, data: { fileWithError: localComposeFiles[0] } });
442+
throw new ContainerError({ description, originalError: err, data: { fileWithError: composeFiles[0] } });
443443
}
444444

445445
await started;
@@ -632,7 +632,7 @@ export async function findComposeContainer(params: DockerCLIParameters | DockerR
632632
return list && list[0];
633633
}
634634

635-
export async function getProjectName(params: DockerCLIParameters | DockerResolverParameters, workspace: Workspace, composeFiles: string[]) {
635+
export async function getProjectName(params: DockerCLIParameters | DockerResolverParameters, workspace: Workspace, composeFiles: string[], composeConfig: any) {
636636
const { cliHost } = 'cliHost' in params ? params : params.common;
637637
const newProjectName = await useNewProjectName(params);
638638
const envName = toProjectName(cliHost.env.COMPOSE_PROJECT_NAME || '', newProjectName);
@@ -653,6 +653,9 @@ export async function getProjectName(params: DockerCLIParameters | DockerResolve
653653
throw err;
654654
}
655655
}
656+
if (composeConfig?.name) {
657+
return toProjectName(composeConfig.name, newProjectName);
658+
}
656659
const configDir = workspace.configFolderPath;
657660
const workingDir = composeFiles[0] ? cliHost.path.dirname(composeFiles[0]) : cliHost.cwd; // From https://github.com/docker/compose/blob/79557e3d3ab67c3697641d9af91866d7e400cfeb/compose/config/config.py#L290
658661
if (equalPaths(cliHost.platform, workingDir, cliHost.path.join(configDir, '.devcontainer'))) {

src/test/cli.up.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ describe('Dev Containers CLI', function () {
9292
assert.equal(upResult!.outcome, 'success');
9393
});
9494
});
95+
describe('for docker-compose with image without features with custom project name', () => {
96+
let upResult: UpResult | null = null;
97+
const testFolder = `${__dirname}/configs/compose-with-name`;
98+
before(async () => {
99+
// build and start the container
100+
upResult = await devContainerUp(cli, testFolder, { 'logLevel': 'trace', extraArgs: `--docker-compose-path trigger-compose-v2` });
101+
});
102+
after(async () => await devContainerDown({ composeProjectName: upResult?.composeProjectName }));
103+
it('should succeed', () => {
104+
assert.equal(upResult!.outcome, 'success');
105+
assert.equal(upResult!.composeProjectName, 'custom-project-name');
106+
});
107+
});
95108

96109
// Additional tests to verify the handling of persisted files
97110
describe('for docker-compose with Dockerfile with features', () => {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dockerComposeFile": "docker-compose.yml",
3+
"service": "app",
4+
"workspaceFolder": "/workspace"
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: '3.8'
2+
3+
name: custom-project-name
4+
5+
services:
6+
app:
7+
image: ubuntu:latest
8+
volumes:
9+
- ..:/workspace:cached
10+
command: sleep infinity

0 commit comments

Comments
 (0)