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

Support project name attribute #817

Merged
merged 2 commits into from
May 3, 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
4 changes: 2 additions & 2 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`);
Expand Down
23 changes: 13 additions & 10 deletions src/spec-node/dockerCompose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.' });
Expand All @@ -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 {
Expand Down Expand Up @@ -327,7 +328,8 @@ async function checkForPersistedFile(cliHost: CLIHost, output: Log, files: strin
foundLabel: false
};
}
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>>) {

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>>) {
const { common } = params;
const { persistedFolder, output } = common;
const { cliHost: buildCLIHost } = buildParams;
Expand All @@ -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 <file> 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] } });
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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'))) {
Expand Down
13 changes: 13 additions & 0 deletions src/test/cli.up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, { 'logLevel': 'trace', extraArgs: `--docker-compose-path trigger-compose-v2` });
});
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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3.8'

name: custom-project-name

services:
app:
image: ubuntu:latest
volumes:
- ..:/workspace:cached
command: sleep infinity