From 697744bb42246bc81d68fd2cffcd640babfb610f Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 3 May 2024 16:11:35 +0200 Subject: [PATCH 1/2] Support project name attribute (microsoft/vscode-remote-release#512) --- src/spec-node/devContainersSpecCLI.ts | 4 ++-- src/spec-node/dockerCompose.ts | 23 +++++++++++-------- src/test/cli.up.test.ts | 13 +++++++++++ .../.devcontainer/devcontainer.json | 5 ++++ .../.devcontainer/docker-compose.yml | 10 ++++++++ 5 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 src/test/configs/compose-with-name/.devcontainer/devcontainer.json create mode 100644 src/test/configs/compose-with-name/.devcontainer/docker-compose.yml diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 7af090500..cf6ff62b2 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -645,9 +645,9 @@ async function doBuild({ if (envFile) { composeGlobalArgs.push('--env-file', envFile); } - const projectName = await getProjectName(params, workspace, composeFiles); - + const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile); + const projectName = await getProjectName(params, workspace, composeFiles, composeConfig); const services = Object.keys(composeConfig.services || {}); if (services.indexOf(config.service) === -1) { throw new Error(`Service '${config.service}' configured in devcontainer.json not found in Docker Compose configuration.`); diff --git a/src/spec-node/dockerCompose.ts b/src/spec-node/dockerCompose.ts index f9a7c682d..8c0a410c1 100644 --- a/src/spec-node/dockerCompose.ts +++ b/src/spec-node/dockerCompose.ts @@ -43,7 +43,8 @@ async function _openDockerComposeDevContainer(params: DockerResolverParameters, const composeFiles = await getDockerComposeFilePaths(buildCLIHost, config, buildCLIHost.env, buildCLIHost.cwd); const cwdEnvFile = buildCLIHost.path.join(buildCLIHost.cwd, '.env'); const envFile = Array.isArray(config.dockerComposeFile) && config.dockerComposeFile.length === 0 && await buildCLIHost.isFile(cwdEnvFile) ? cwdEnvFile : undefined; - const projectName = await getProjectName(buildParams, workspace, composeFiles); + const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile); + const projectName = await getProjectName(buildParams, workspace, composeFiles, composeConfig); const containerId = await findComposeContainer(params, projectName, config.service); if (params.expectExistingContainer && !containerId) { throw new ContainerError({ description: 'The expected container does not exist.' }); @@ -60,7 +61,7 @@ async function _openDockerComposeDevContainer(params: DockerResolverParameters, // let collapsedFeaturesConfig: CollapsedFeaturesConfig | undefined; if (!container || container.State.Status !== 'running') { - const res = await startContainer(params, buildParams, configWithRaw, projectName, composeFiles, envFile, container, idLabels, additionalFeatures); + const res = await startContainer(params, buildParams, configWithRaw, projectName, composeFiles, envFile, composeConfig, container, idLabels, additionalFeatures); container = await inspectContainer(params, res.containerId); // collapsedFeaturesConfig = res.collapsedFeaturesConfig; // } else { @@ -327,7 +328,8 @@ async function checkForPersistedFile(cliHost: CLIHost, output: Log, files: strin foundLabel: false }; } -async function startContainer(params: DockerResolverParameters, buildParams: DockerCLIParameters, configWithRaw: SubstitutedConfig, projectName: string, composeFiles: string[], envFile: string | undefined, container: ContainerDetails | undefined, idLabels: string[], additionalFeatures: Record>) { + +async function startContainer(params: DockerResolverParameters, buildParams: DockerCLIParameters, configWithRaw: SubstitutedConfig, projectName: string, composeFiles: string[], envFile: string | undefined, composeConfig: any, container: ContainerDetails | undefined, idLabels: string[], additionalFeatures: Record>) { const { common } = params; const { persistedFolder, output } = common; const { cliHost: buildCLIHost } = buildParams; @@ -337,15 +339,13 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc common.progress(ResolverProgress.StartingContainer); - const localComposeFiles = composeFiles; // If dockerComposeFile is an array, add -f in order. https://docs.docker.com/compose/extends/#multiple-compose-files - const composeGlobalArgs = ([] as string[]).concat(...localComposeFiles.map(composeFile => ['-f', composeFile])); + const composeGlobalArgs = ([] as string[]).concat(...composeFiles.map(composeFile => ['-f', composeFile])); if (envFile) { composeGlobalArgs.push('--env-file', envFile); } const infoOutput = makeLog(buildParams.output, LogLevel.Info); - const composeConfig = await readDockerComposeConfig(buildParams, localComposeFiles, envFile); const services = Object.keys(composeConfig.services || {}); if (services.indexOf(config.service) === -1) { 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 if (!container || !didRestoreFromPersistedShare) { const noBuild = !!container; //if we have an existing container, just recreate override files but skip the build - const versionPrefix = await readVersionPrefix(buildCLIHost, localComposeFiles); + const versionPrefix = await readVersionPrefix(buildCLIHost, composeFiles); const infoParams = { ...params, common: { ...params.common, output: infoOutput } }; - 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); + 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); additionalComposeOverrideFiles.forEach(overrideFilePath => composeGlobalArgs.push('-f', overrideFilePath)); const currentImageName = overrideImageName || originalImageName; @@ -439,7 +439,7 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc description = err.cmdOutput; } - throw new ContainerError({ description, originalError: err, data: { fileWithError: localComposeFiles[0] } }); + throw new ContainerError({ description, originalError: err, data: { fileWithError: composeFiles[0] } }); } await started; @@ -632,7 +632,7 @@ export async function findComposeContainer(params: DockerCLIParameters | DockerR return list && list[0]; } -export async function getProjectName(params: DockerCLIParameters | DockerResolverParameters, workspace: Workspace, composeFiles: string[]) { +export async function getProjectName(params: DockerCLIParameters | DockerResolverParameters, workspace: Workspace, composeFiles: string[], composeConfig: any) { const { cliHost } = 'cliHost' in params ? params : params.common; const newProjectName = await useNewProjectName(params); const envName = toProjectName(cliHost.env.COMPOSE_PROJECT_NAME || '', newProjectName); @@ -653,6 +653,9 @@ export async function getProjectName(params: DockerCLIParameters | DockerResolve throw err; } } + if (composeConfig?.name) { + return toProjectName(composeConfig.name, newProjectName); + } const configDir = workspace.configFolderPath; const workingDir = composeFiles[0] ? cliHost.path.dirname(composeFiles[0]) : cliHost.cwd; // From https://github.com/docker/compose/blob/79557e3d3ab67c3697641d9af91866d7e400cfeb/compose/config/config.py#L290 if (equalPaths(cliHost.platform, workingDir, cliHost.path.join(configDir, '.devcontainer'))) { diff --git a/src/test/cli.up.test.ts b/src/test/cli.up.test.ts index 835ca90c5..d92e77975 100644 --- a/src/test/cli.up.test.ts +++ b/src/test/cli.up.test.ts @@ -92,6 +92,19 @@ describe('Dev Containers CLI', function () { assert.equal(upResult!.outcome, 'success'); }); }); + describe('for docker-compose with image without features with custom project name', () => { + let upResult: UpResult | null = null; + const testFolder = `${__dirname}/configs/compose-with-name`; + before(async () => { + // build and start the container + upResult = await devContainerUp(cli, testFolder); + }); + after(async () => await devContainerDown({ composeProjectName: upResult?.composeProjectName })); + it('should succeed', () => { + assert.equal(upResult!.outcome, 'success'); + assert.equal(upResult!.composeProjectName, 'custom-project-name'); + }); + }); // Additional tests to verify the handling of persisted files describe('for docker-compose with Dockerfile with features', () => { diff --git a/src/test/configs/compose-with-name/.devcontainer/devcontainer.json b/src/test/configs/compose-with-name/.devcontainer/devcontainer.json new file mode 100644 index 000000000..7b42997d3 --- /dev/null +++ b/src/test/configs/compose-with-name/.devcontainer/devcontainer.json @@ -0,0 +1,5 @@ +{ + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace" +} \ No newline at end of file diff --git a/src/test/configs/compose-with-name/.devcontainer/docker-compose.yml b/src/test/configs/compose-with-name/.devcontainer/docker-compose.yml new file mode 100644 index 000000000..7e4737d8b --- /dev/null +++ b/src/test/configs/compose-with-name/.devcontainer/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' + +name: custom-project-name + +services: + app: + image: ubuntu:latest + volumes: + - ..:/workspace:cached + command: sleep infinity From 65f50ccc24698b83bd2018e5d7601ad359a8f96a Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 3 May 2024 17:17:16 +0200 Subject: [PATCH 2/2] Try to fix CI --- src/test/cli.up.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/cli.up.test.ts b/src/test/cli.up.test.ts index d92e77975..cd678edeb 100644 --- a/src/test/cli.up.test.ts +++ b/src/test/cli.up.test.ts @@ -97,7 +97,7 @@ describe('Dev Containers CLI', function () { const testFolder = `${__dirname}/configs/compose-with-name`; before(async () => { // build and start the container - upResult = await devContainerUp(cli, testFolder); + upResult = await devContainerUp(cli, testFolder, { 'logLevel': 'trace', extraArgs: `--docker-compose-path trigger-compose-v2` }); }); after(async () => await devContainerDown({ composeProjectName: upResult?.composeProjectName })); it('should succeed', () => {