Skip to content

Commit

Permalink
Add label for config file
Browse files Browse the repository at this point in the history
  • Loading branch information
chrmarti committed Feb 9, 2023
1 parent b685437 commit 4705707
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 25 deletions.
9 changes: 5 additions & 4 deletions src/spec-node/configContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as jsonc from 'jsonc-parser';

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

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

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> {
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> {
if (configFile && !/\/\.?devcontainer\.json$/.test(configFile.path)) {
throw new Error(`Filename must be devcontainer.json or .devcontainer.json (${uriToFsPath(configFile, params.common.cliHost.platform)}).`);
}
const parsedAuthority = params.parsedAuthority;
if (!parsedAuthority || isDevContainerAuthority(parsedAuthority)) {
return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, idLabels, additionalFeatures);
return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, providedIdLabels, additionalFeatures);
} else {
throw new Error(`Unexpected authority: ${JSON.stringify(parsedAuthority)}`);
}
}

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> {
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> {
const { common, workspaceMountConsistencyDefault } = params;
const { cliHost, output } = common;

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

Expand Down
4 changes: 2 additions & 2 deletions src/spec-node/devContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ export interface ProvisionOptions {
};
}

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

const result = await resolve(params, options.configFile, options.overrideConfigFile, idLabels, options.additionalFeatures ?? {});
const result = await resolve(params, options.configFile, options.overrideConfigFile, providedIdLabels, options.additionalFeatures ?? {});
output.stop(text, start);
const { dockerContainerId, composeProjectName } = result;
return {
Expand Down
31 changes: 12 additions & 19 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import yargs, { Argv } from 'yargs';
import * as jsonc from 'jsonc-parser';

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

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

async function doProvision(options: ProvisionOptions, idLabels: string[]) {
async function doProvision(options: ProvisionOptions, providedIdLabels: string[] | undefined) {
const disposables: (() => Promise<unknown> | undefined)[] = [];
const dispose = async () => {
await Promise.all(disposables.map(d => d()));
};
try {
const result = await launch(options, idLabels, disposables);
const result = await launch(options, providedIdLabels, disposables);
return {
outcome: 'success' as 'success',
dispose,
Expand Down Expand Up @@ -758,8 +758,7 @@ async function doRunUserCommands({
};
try {
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
Expand Down Expand Up @@ -822,7 +821,7 @@ async function doRunUserCommands({
substitute: value => substitute({ platform: cliHost.platform, env: cliHost.env }, value)
};

const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
if (!container) {
bailOut(common.output, 'Dev container not found.');
}
Expand Down Expand Up @@ -926,8 +925,7 @@ async function readConfiguration({
let output: Log | undefined;
try {
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
const cwd = workspaceFolder || process.cwd();
Expand Down Expand Up @@ -971,7 +969,7 @@ async function readConfiguration({
env: cliHost.env,
output
};
const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
if (container) {
configuration = addSubstitution(configuration, config => beforeContainerSubstitute(envListToObj(idLabels), config));
configuration = addSubstitution(configuration, config => containerSubstitute(cliHost.platform, configuration.config.configFilePath, envListToObj(container.Config.Env), config));
Expand Down Expand Up @@ -1111,8 +1109,7 @@ export async function doExec({
};
try {
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
const idLabels = idLabel ? (Array.isArray(idLabel) ? idLabel as string[] : [idLabel]) :
workspaceFolder ? getDefaultIdLabels(workspaceFolder) : undefined;
const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined;
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined;
Expand Down Expand Up @@ -1171,7 +1168,7 @@ export async function doExec({
substitute: value => substitute({ platform: cliHost.platform, env: cliHost.env }, value)
};

const container = containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, idLabels!);
const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath);
if (!container) {
bailOut(common.output, 'Dev container not found.');
}
Expand Down Expand Up @@ -1207,7 +1204,3 @@ export async function doExec({
};
}
}

function getDefaultIdLabels(workspaceFolder: string) {
return [`${hostFolderLabel}=${workspaceFolder}`];
}
1 change: 1 addition & 0 deletions src/spec-node/singleContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getDevcontainerMetadata, getImageBuildInfoFromDockerfile, getImageMetad
import { ensureDockerfileHasFinalStageName } from './dockerfileUtils';

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

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> {
const { common } = params;
Expand Down
38 changes: 38 additions & 0 deletions src/spec-node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { PackageConfiguration } from '../spec-utils/product';
import { ImageMetadataEntry } from './imageMetadata';
import { getImageIndexEntryForPlatform, getManifest, getRef } from '../spec-configuration/containerCollectionsOCI';
import { requestEnsureAuthenticated } from '../spec-configuration/httpOCIRegistry';
import { configFileLabel, findDevContainer, hostFolderLabel } from './singleContainer';

export { getConfigFilePath, getDockerfilePath, isDockerFileConfig, resolveConfigFilePath } from '../spec-configuration/configuration';
export { uriToFsPath, parentURI } from '../spec-configuration/configurationCommonUtils';
Expand Down Expand Up @@ -460,3 +461,40 @@ export async function getLocalCacheFolder() {
export function getEmptyContextFolder(common: ResolverParameters) {
return common.cliHost.path.join(common.persistedFolder, 'empty-folder');
}

export async function findContainerAndIdLabels(params: DockerResolverParameters | DockerCLIParameters, containerId: string | undefined, providedIdLabels: string[] | undefined, workspaceFolder: string | undefined, configFile: string | undefined, removeContainerWithOldLabels?: boolean | string) {
if (providedIdLabels) {
return {
container: containerId ? await inspectContainer(params, containerId) : await findDevContainer(params, providedIdLabels),
idLabels: providedIdLabels,
};
}
let container: ContainerDetails | undefined;
if (containerId) {
container = await inspectContainer(params, containerId);
} else if (workspaceFolder && configFile) {
container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`]);
if (!container) {
// Fall back to old labels.
container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`]);
if (container) {
if (container.Config.Labels?.[configFileLabel]) {
// But ignore containers with new labels.
container = undefined;
} else if (removeContainerWithOldLabels === true || removeContainerWithOldLabels === container.Id) {
// Remove container, so it will be rebuilt with new labels.
await dockerCLI(params, 'rm', '-f', container.Id);
container = undefined;
}
}
}
} else {
throw new Error(`Either containerId or workspaceFolder and configFile must be provided.`);
}
return {
container,
idLabels: !container || container.Config.Labels?.[configFileLabel] ?
[`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`] :
[`${hostFolderLabel}=${workspaceFolder}`],
};
}

0 comments on commit 4705707

Please sign in to comment.