Skip to content
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
2 changes: 2 additions & 0 deletions src/spec-node/devContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ProvisionOptions {
additionalMounts: Mount[];
updateRemoteUserUIDDefault: UpdateRemoteUserUIDDefault;
remoteEnv: Record<string, string>;
additionalCacheFroms: string[];
}

export async function launch(options: ProvisionOptions, disposables: (() => Promise<unknown> | undefined)[]) {
Expand Down Expand Up @@ -133,6 +134,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
additionalMounts,
userRepositoryConfigurationPaths: [],
updateRemoteUserUIDDefault,
additionalCacheFroms: options.additionalCacheFroms,
};
}

Expand Down
20 changes: 15 additions & 5 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function provisionOptions(y: Argv) {
'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' },
'mount': { type: 'string', description: 'Additional mount point(s). Format: type=<bind|volume>,source=<source>,target=<target>[,external=<true|false>]' },
'remote-env': { type: 'string', description: 'Remote environment variables of the format name=value. These will be added when executing the user commands.' },
'cache-from' : {type: 'string', description: 'Additional image to use as potential layer cache during image building' },
})
.check(argv => {
const idLabels = (argv['id-label'] && (Array.isArray(argv['id-label']) ? argv['id-label'] : [argv['id-label']])) as string[] | undefined;
Expand Down Expand Up @@ -138,10 +139,12 @@ async function provision({
prebuild,
mount,
'remote-env': addRemoteEnv,
'cache-from': addCacheFrom,
}: ProvisionArgs) {

const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined;
const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : [];
const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : [];
const options: ProvisionOptions = {
dockerPath,
dockerComposePath,
Expand Down Expand Up @@ -176,6 +179,7 @@ async function provision({
}) : [],
updateRemoteUserUIDDefault,
remoteEnv: keyValuesToRecord(addRemoteEnvs),
additionalCacheFroms: addCacheFroms,
};

const result = await doProvision(options);
Expand Down Expand Up @@ -231,6 +235,7 @@ function buildOptions(y: Argv) {
'log-format': { choices: ['text' as 'text', 'json' as 'json'], default: 'text' as 'text', description: 'Log format.' },
'no-cache': { type: 'boolean', default: false, description: 'Builds the image with `--no-cache`.' },
'image-name': { type: 'string', description: 'Image name.' },
'cache-from' : {type: 'string', description: 'Additional image to use as potential layer cache' },
});
}

Expand All @@ -257,6 +262,7 @@ async function doBuild({
'log-format': logFormat,
'no-cache': buildNoCache,
'image-name': argImageName,
'cache-from': addCacheFrom,
}: BuildArgs) {
const disposables: (() => Promise<unknown> | undefined)[] = [];
const dispose = async () => {
Expand All @@ -266,6 +272,7 @@ async function doBuild({
const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg);
const configFile: URI | undefined = /* config ? URI.file(path.resolve(process.cwd(), config)) : */ undefined; // TODO
const overrideConfigFile: URI | undefined = /* overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : */ undefined;
const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : [];
const params = await createDockerParams({
dockerPath,
dockerComposePath,
Expand All @@ -291,15 +298,16 @@ async function doBuild({
additionalMounts: [],
updateRemoteUserUIDDefault: 'never',
remoteEnv: {},
additionalCacheFroms: addCacheFroms,
}, disposables);

const { common, dockerCLI, dockerComposeCLI } = params;
const { cliHost, env, output } = common;
const workspace = workspaceFromPath(cliHost.path, workspaceFolder);
const configPath = configFile ? configFile : workspace
? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath)
|| (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined))
: overrideConfigFile;
|| (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined))
: overrideConfigFile;
const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, output, undefined, overrideConfigFile) || undefined;
if (!configs) {
throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` });
Expand Down Expand Up @@ -339,7 +347,7 @@ async function doBuild({
throw new Error(`Service '${config.service}' configured in devcontainer.json not found in Docker Compose configuration.`);
}

await buildDockerCompose(config, projectName, buildParams, composeFiles, composeGlobalArgs, [config.service], params.buildNoCache || false, undefined);
await buildDockerCompose(config, projectName, buildParams, composeFiles, composeGlobalArgs, [config.service], params.buildNoCache || false, undefined, addCacheFroms);

const service = composeConfig.services[config.service];
const originalImageName = service.image || `${projectName}_${config.service}`;
Expand Down Expand Up @@ -486,6 +494,7 @@ async function doRunUserCommands({
additionalMounts: [],
updateRemoteUserUIDDefault: 'never',
remoteEnv: keyValuesToRecord(addRemoteEnvs),
additionalCacheFroms: [],
}, disposables);

const { common } = params;
Expand Down Expand Up @@ -728,6 +737,7 @@ async function doExec({
additionalMounts: [],
updateRemoteUserUIDDefault: 'never',
remoteEnv: keyValuesToRecord(addRemoteEnvs),
additionalCacheFroms: [],
}, disposables);

const { common } = params;
Expand Down
11 changes: 7 additions & 4 deletions src/spec-node/dockerCompose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,17 @@ export function getRemoteWorkspaceFolder(config: DevContainerFromDockerComposeCo
return config.workspaceFolder || '/';
}

export async function buildDockerCompose(config: DevContainerFromDockerComposeConfig, projectName: string, buildParams: DockerCLIParameters, localComposeFiles: string[], composeGlobalArgs: string[], runServices: string[], noCache: boolean, imageNameOverride?: string) {
export async function buildDockerCompose(config: DevContainerFromDockerComposeConfig, projectName: string, buildParams: DockerCLIParameters, localComposeFiles: string[], composeGlobalArgs: string[], runServices: string[], noCache: boolean, imageNameOverride?: string, additionalCacheFroms?: string[]) {
const { cliHost } = buildParams;
const args = ['--project-name', projectName, ...composeGlobalArgs];
if (imageNameOverride) {
if (imageNameOverride || (additionalCacheFroms && additionalCacheFroms.length > 0)) {
const composeOverrideFile = cliHost.path.join(await cliHost.tmpdir(), `docker-compose.devcontainer.build-${Date.now()}.yml`);
const imageNameOverrideContent = imageNameOverride ? ` image: ${imageNameOverride}` : '';
const cacheFromOverrideContent = (additionalCacheFroms && additionalCacheFroms.length > 0) ? ` cache_from: ${additionalCacheFroms.forEach(cacheFrom => ` - ${cacheFrom}`)}` : '';
const composeOverrideContent = `services:
${config.service}:
image: ${imageNameOverride}
${imageNameOverrideContent}
${cacheFromOverrideContent}
`;
await cliHost.writeFile(composeOverrideFile, Buffer.from(composeOverrideContent));
args.push('-f', composeOverrideFile);
Expand Down Expand Up @@ -163,7 +166,7 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
const { started } = await startEventSeen(params, { [projectLabel]: projectName, [serviceLabel]: config.service }, canceled, common.output, common.getLogLevel() === LogLevel.Trace); // await getEvents, but only assign started.

if (build) {
await buildDockerCompose(config, projectName, { ...buildParams, output: infoOutput }, localComposeFiles, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, undefined);
await buildDockerCompose(config, projectName, { ...buildParams, output: infoOutput }, localComposeFiles, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, undefined, params.additionalCacheFroms);
}

const service = composeConfig.services[config.service];
Expand Down
5 changes: 3 additions & 2 deletions src/spec-node/singleContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ export async function findDevContainer(params: DockerCLIParameters | DockerResol
return details.filter(container => container.State.Status !== 'removing')[0];
}

export async function buildImage(buildParams: DockerResolverParameters | DockerCLIParameters, config: DevContainerFromDockerfileConfig, baseImageName: string, noCache: boolean) {
const { cliHost, output } = 'cliHost' in buildParams ? buildParams : buildParams.common;
export async function buildImage(buildParams: DockerResolverParameters, config: DevContainerFromDockerfileConfig, baseImageName: string, noCache: boolean) {
const { cliHost, output } = buildParams.common;
const dockerfileUri = getDockerfilePath(cliHost, config);
const dockerfilePath = await uriToWSLFsPath(dockerfileUri, cliHost);
if (!cliHost.isFile(dockerfilePath)) {
Expand All @@ -185,6 +185,7 @@ export async function buildImage(buildParams: DockerResolverParameters | DockerC
}
}
}
buildParams.additionalCacheFroms.forEach(cacheFrom => args.push('--cache-from', cacheFrom));
const buildArgs = config.build?.args;
if (buildArgs) {
for (const key in buildArgs) {
Expand Down
1 change: 1 addition & 0 deletions src/spec-node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface DockerResolverParameters {
userRepositoryConfigurationPaths: string[];
additionalMounts: Mount[];
updateRemoteUserUIDDefault: UpdateRemoteUserUIDDefault;
additionalCacheFroms: string[];
}

export interface ResolverResult {
Expand Down