Skip to content

Commit 4705707

Browse files
committed
Add label for config file
1 parent b685437 commit 4705707

File tree

5 files changed

+58
-25
lines changed

5 files changed

+58
-25
lines changed

src/spec-node/configContainer.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as jsonc from 'jsonc-parser';
99

1010
import { openDockerfileDevContainer } from './singleContainer';
1111
import { openDockerComposeDevContainer } from './dockerCompose';
12-
import { ResolverResult, DockerResolverParameters, isDockerFileConfig, runUserCommand, createDocuments, getWorkspaceConfiguration, BindMountConsistency, uriToFsPath, DevContainerAuthority, isDevContainerAuthority, SubstituteConfig, SubstitutedConfig, addSubstitution, envListToObj } from './utils';
12+
import { ResolverResult, DockerResolverParameters, isDockerFileConfig, runUserCommand, createDocuments, getWorkspaceConfiguration, BindMountConsistency, uriToFsPath, DevContainerAuthority, isDevContainerAuthority, SubstituteConfig, SubstitutedConfig, addSubstitution, envListToObj, findContainerAndIdLabels } from './utils';
1313
import { beforeContainerSubstitute, substitute } from '../spec-common/variableSubstitution';
1414
import { ContainerError } from '../spec-common/errors';
1515
import { Workspace, workspaceFromPath, isWorkspacePath } from '../spec-utils/workspaces';
@@ -21,19 +21,19 @@ import { DevContainerConfig, DevContainerFromDockerComposeConfig, DevContainerFr
2121

2222
export { getWellKnownDevContainerPaths as getPossibleDevContainerPaths } from '../spec-configuration/configurationCommonUtils';
2323

24-
export async function resolve(params: DockerResolverParameters, configFile: URI | undefined, overrideConfigFile: URI | undefined, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
24+
export async function resolve(params: DockerResolverParameters, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
2525
if (configFile && !/\/\.?devcontainer\.json$/.test(configFile.path)) {
2626
throw new Error(`Filename must be devcontainer.json or .devcontainer.json (${uriToFsPath(configFile, params.common.cliHost.platform)}).`);
2727
}
2828
const parsedAuthority = params.parsedAuthority;
2929
if (!parsedAuthority || isDevContainerAuthority(parsedAuthority)) {
30-
return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, idLabels, additionalFeatures);
30+
return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, providedIdLabels, additionalFeatures);
3131
} else {
3232
throw new Error(`Unexpected authority: ${JSON.stringify(parsedAuthority)}`);
3333
}
3434
}
3535

36-
async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAuthority: DevContainerAuthority | undefined, configFile: URI | undefined, overrideConfigFile: URI | undefined, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
36+
async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAuthority: DevContainerAuthority | undefined, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
3737
const { common, workspaceMountConsistencyDefault } = params;
3838
const { cliHost, output } = common;
3939

@@ -52,6 +52,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu
5252
throw new ContainerError({ description: `No dev container config and no workspace found.` });
5353
}
5454
}
55+
const idLabels = providedIdLabels || (await findContainerAndIdLabels(params, undefined, providedIdLabels, workspace?.rootFolderPath, configPath?.fsPath, params.removeOnStartup)).idLabels;
5556
const configWithRaw = addSubstitution(configs.config, config => beforeContainerSubstitute(envListToObj(idLabels), config));
5657
const { config } = configWithRaw;
5758

src/spec-node/devContainers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ export interface ProvisionOptions {
6565
};
6666
}
6767

68-
export async function launch(options: ProvisionOptions, idLabels: string[], disposables: (() => Promise<unknown> | undefined)[]) {
68+
export async function launch(options: ProvisionOptions, providedIdLabels: string[] | undefined, disposables: (() => Promise<unknown> | undefined)[]) {
6969
const params = await createDockerParams(options, disposables);
7070
const output = params.common.output;
7171
const text = 'Resolving Remote';
7272
const start = output.start(text);
7373

74-
const result = await resolve(params, options.configFile, options.overrideConfigFile, idLabels, options.additionalFeatures ?? {});
74+
const result = await resolve(params, options.configFile, options.overrideConfigFile, providedIdLabels, options.additionalFeatures ?? {});
7575
output.stop(text, start);
7676
const { dockerContainerId, composeProjectName } = result;
7777
return {

src/spec-node/devContainersSpecCLI.ts

+12-19
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import yargs, { Argv } from 'yargs';
99
import * as jsonc from 'jsonc-parser';
1010

1111
import { createDockerParams, createLog, experimentalImageMetadataDefault, launch, ProvisionOptions } from './devContainers';
12-
import { SubstitutedConfig, createContainerProperties, createFeaturesTempFolder, envListToObj, inspectDockerImage, isDockerFileConfig, SubstituteConfig, addSubstitution } from './utils';
12+
import { SubstitutedConfig, createContainerProperties, createFeaturesTempFolder, envListToObj, inspectDockerImage, isDockerFileConfig, SubstituteConfig, addSubstitution, findContainerAndIdLabels } from './utils';
1313
import { URI } from 'vscode-uri';
1414
import { ContainerError } from '../spec-common/errors';
1515
import { Log, LogLevel, makeLog, mapLogLevel } from '../spec-utils/log';
1616
import { probeRemoteEnv, runPostCreateCommands, runRemoteCommand, UserEnvProbe, setupInContainer } from '../spec-common/injectHeadless';
17-
import { bailOut, buildNamedImageAndExtend, findDevContainer, hostFolderLabel } from './singleContainer';
17+
import { bailOut, buildNamedImageAndExtend } from './singleContainer';
1818
import { extendImage } from './containerFeatures';
1919
import { DockerCLIParameters, dockerPtyCLI, inspectContainer } from '../spec-shutdown/dockerUtils';
2020
import { buildAndExtendDockerCompose, dockerComposeCLIConfig, getDefaultImageName, getProjectName, readDockerComposeConfig, readVersionPrefix } from './dockerCompose';
@@ -193,7 +193,7 @@ async function provision({
193193
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
194194
const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : [];
195195
const additionalFeatures = additionalFeaturesJson ? jsonc.parse(additionalFeaturesJson) as Record<string, string | boolean | Record<string, string | boolean>> : {};
196-
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) : getDefaultIdLabels(workspaceFolder!);
196+
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
197197
const options: ProvisionOptions = {
198198
dockerPath,
199199
dockerComposePath,
@@ -245,7 +245,7 @@ async function provision({
245245
skipPersistingCustomizationsFromFeatures: false,
246246
};
247247

248-
const result = await doProvision(options, idLabels);
248+
const result = await doProvision(options, providedIdLabels);
249249
const exitCode = result.outcome === 'error' ? 1 : 0;
250250
console.log(JSON.stringify(result));
251251
if (result.outcome === 'success') {
@@ -255,13 +255,13 @@ async function provision({
255255
process.exit(exitCode);
256256
}
257257

258-
async function doProvision(options: ProvisionOptions, idLabels: string[]) {
258+
async function doProvision(options: ProvisionOptions, providedIdLabels: string[] | undefined) {
259259
const disposables: (() => Promise<unknown> | undefined)[] = [];
260260
const dispose = async () => {
261261
await Promise.all(disposables.map(d => d()));
262262
};
263263
try {
264-
const result = await launch(options, idLabels, disposables);
264+
const result = await launch(options, providedIdLabels, disposables);
265265
return {
266266
outcome: 'success' as 'success',
267267
dispose,
@@ -758,8 +758,7 @@ async function doRunUserCommands({
758758
};
759759
try {
760760
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
761-
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
762-
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
761+
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
763762
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
764763
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
765764
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
@@ -822,7 +821,7 @@ async function doRunUserCommands({
822821
substitute: value => substitute({ platform: cliHost.platform, env: cliHost.env }, value)
823822
};
824823

825-
const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
824+
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
826825
if (!container) {
827826
bailOut(common.output, 'Dev container not found.');
828827
}
@@ -926,8 +925,7 @@ async function readConfiguration({
926925
let output: Log | undefined;
927926
try {
928927
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
929-
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
930-
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
928+
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
931929
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
932930
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
933931
const cwd = workspaceFolder || process.cwd();
@@ -971,7 +969,7 @@ async function readConfiguration({
971969
env: cliHost.env,
972970
output
973971
};
974-
const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
972+
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
975973
if (container) {
976974
configuration = addSubstitution(configuration, config => beforeContainerSubstitute(envListToObj(idLabels), config));
977975
configuration = addSubstitution(configuration, config => containerSubstitute(cliHost.platform, configuration.config.configFilePath, envListToObj(container.Config.Env), config));
@@ -1111,8 +1109,7 @@ export async function doExec({
11111109
};
11121110
try {
11131111
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
1114-
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
1115-
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
1112+
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
11161113
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
11171114
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
11181115
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
@@ -1171,7 +1168,7 @@ export async function doExec({
11711168
substitute: value => substitute({ platform: cliHost.platform, env: cliHost.env }, value)
11721169
};
11731170

1174-
const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
1171+
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
11751172
if (!container) {
11761173
bailOut(common.output, 'Dev container not found.');
11771174
}
@@ -1207,7 +1204,3 @@ export async function doExec({
12071204
};
12081205
}
12091206
}
1210-
1211-
function getDefaultIdLabels(workspaceFolder: string) {
1212-
return [`${hostFolderLabel}=${workspaceFolder}`];
1213-
}

src/spec-node/singleContainer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getDevcontainerMetadata, getImageBuildInfoFromDockerfile, getImageMetad
1515
import { ensureDockerfileHasFinalStageName } from './dockerfileUtils';
1616

1717
export const hostFolderLabel = 'devcontainer.local_folder'; // used to label containers created from a workspace/folder
18+
export const configFileLabel = 'devcontainer.config_file';
1819

1920
export async function openDockerfileDevContainer(params: DockerResolverParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerfileConfig | DevContainerFromImageConfig>, workspaceConfig: WorkspaceConfiguration, idLabels: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>): Promise<ResolverResult> {
2021
const { common } = params;

src/spec-node/utils.ts

+38
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { PackageConfiguration } from '../spec-utils/product';
2727
import { ImageMetadataEntry } from './imageMetadata';
2828
import { getImageIndexEntryForPlatform, getManifest, getRef } from '../spec-configuration/containerCollectionsOCI';
2929
import { requestEnsureAuthenticated } from '../spec-configuration/httpOCIRegistry';
30+
import { configFileLabel, findDevContainer, hostFolderLabel } from './singleContainer';
3031

3132
export { getConfigFilePath, getDockerfilePath, isDockerFileConfig, resolveConfigFilePath } from '../spec-configuration/configuration';
3233
export { uriToFsPath, parentURI } from '../spec-configuration/configurationCommonUtils';
@@ -460,3 +461,40 @@ export async function getLocalCacheFolder() {
460461
export function getEmptyContextFolder(common: ResolverParameters) {
461462
return common.cliHost.path.join(common.persistedFolder, 'empty-folder');
462463
}
464+
465+
export async function findContainerAndIdLabels(params: DockerResolverParameters | DockerCLIParameters, containerId: string | undefined, providedIdLabels: string[] | undefined, workspaceFolder: string | undefined, configFile: string | undefined, removeContainerWithOldLabels?: boolean | string) {
466+
if (providedIdLabels) {
467+
return {
468+
container: containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, providedIdLabels),
469+
idLabels: providedIdLabels,
470+
};
471+
}
472+
let container: ContainerDetails | undefined;
473+
if (containerId) {
474+
container = await inspectContainer(params, containerId);
475+
} else if (workspaceFolder && configFile) {
476+
container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`]);
477+
if (!container) {
478+
// Fall back to old labels.
479+
container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`]);
480+
if (container) {
481+
if (container.Config.Labels?.[configFileLabel]) {
482+
// But ignore containers with new labels.
483+
container = undefined;
484+
} else if (removeContainerWithOldLabels === true || removeContainerWithOldLabels === container.Id) {
485+
// Remove container, so it will be rebuilt with new labels.
486+
await dockerCLI(params, 'rm', '-f', container.Id);
487+
container = undefined;
488+
}
489+
}
490+
}
491+
} else {
492+
throw new Error(`Either containerId or workspaceFolder and configFile must be provided.`);
493+
}
494+
return {
495+
container,
496+
idLabels: !container || container.Config.Labels?.[configFileLabel] ?
497+
[`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`] :
498+
[`${hostFolderLabel}=${workspaceFolder}`],
499+
};
500+
}

0 commit comments

Comments
 (0)