From 1bdb50fbc0665d97a39fc3c922f90a9fb8c8bcdb Mon Sep 17 00:00:00 2001 From: Stuart Leeks Date: Mon, 9 May 2022 08:19:38 +0000 Subject: [PATCH] Add --cache-from to build/up commands --- src/spec-node/devContainers.ts | 2 ++ src/spec-node/devContainersSpecCLI.ts | 20 +++++++++++++++----- src/spec-node/dockerCompose.ts | 11 +++++++---- src/spec-node/singleContainer.ts | 5 +++-- src/spec-node/utils.ts | 1 + 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/spec-node/devContainers.ts b/src/spec-node/devContainers.ts index 6f511097e..9e3a437c0 100644 --- a/src/spec-node/devContainers.ts +++ b/src/spec-node/devContainers.ts @@ -43,6 +43,7 @@ export interface ProvisionOptions { additionalMounts: Mount[]; updateRemoteUserUIDDefault: UpdateRemoteUserUIDDefault; remoteEnv: Record; + additionalCacheFroms: string[]; } export async function launch(options: ProvisionOptions, disposables: (() => Promise | undefined)[]) { @@ -133,6 +134,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables: additionalMounts, userRepositoryConfigurationPaths: [], updateRemoteUserUIDDefault, + additionalCacheFroms: options.additionalCacheFroms, }; } diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 81f6891c1..1abadd574 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -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=,source=,target=[,external=]' }, '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; @@ -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, @@ -176,6 +179,7 @@ async function provision({ }) : [], updateRemoteUserUIDDefault, remoteEnv: keyValuesToRecord(addRemoteEnvs), + additionalCacheFroms: addCacheFroms, }; const result = await doProvision(options); @@ -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' }, }); } @@ -257,6 +262,7 @@ async function doBuild({ 'log-format': logFormat, 'no-cache': buildNoCache, 'image-name': argImageName, + 'cache-from': addCacheFrom, }: BuildArgs) { const disposables: (() => Promise | undefined)[] = []; const dispose = async () => { @@ -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, @@ -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.` }); @@ -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}`; @@ -486,6 +494,7 @@ async function doRunUserCommands({ additionalMounts: [], updateRemoteUserUIDDefault: 'never', remoteEnv: keyValuesToRecord(addRemoteEnvs), + additionalCacheFroms: [], }, disposables); const { common } = params; @@ -728,6 +737,7 @@ async function doExec({ additionalMounts: [], updateRemoteUserUIDDefault: 'never', remoteEnv: keyValuesToRecord(addRemoteEnvs), + additionalCacheFroms: [], }, disposables); const { common } = params; diff --git a/src/spec-node/dockerCompose.ts b/src/spec-node/dockerCompose.ts index b6ff7f1e5..3e750f342 100644 --- a/src/spec-node/dockerCompose.ts +++ b/src/spec-node/dockerCompose.ts @@ -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); @@ -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]; diff --git a/src/spec-node/singleContainer.ts b/src/spec-node/singleContainer.ts index c78225c67..7c513e530 100644 --- a/src/spec-node/singleContainer.ts +++ b/src/spec-node/singleContainer.ts @@ -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)) { @@ -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) { diff --git a/src/spec-node/utils.ts b/src/spec-node/utils.ts index fba1ba055..b42994e0b 100644 --- a/src/spec-node/utils.ts +++ b/src/spec-node/utils.ts @@ -67,6 +67,7 @@ export interface DockerResolverParameters { userRepositoryConfigurationPaths: string[]; additionalMounts: Mount[]; updateRemoteUserUIDDefault: UpdateRemoteUserUIDDefault; + additionalCacheFroms: string[]; } export interface ResolverResult {