diff --git a/action.yml b/action.yml index 35979b9f..3af8a63e 100644 --- a/action.yml +++ b/action.yml @@ -63,6 +63,9 @@ inputs: required: false default: false description: Builds the image with `--no-cache` (takes precedence over `cacheFrom`) + cacheTo: + required: false + description: Specify the image to cache the built image to outputs: runCmdOutput: description: The output of the command specified in the runCmd input diff --git a/azdo-task/DevcontainersCi/src/docker.ts b/azdo-task/DevcontainersCi/src/docker.ts index d0ce7485..e4987ca1 100644 --- a/azdo-task/DevcontainersCi/src/docker.ts +++ b/azdo-task/DevcontainersCi/src/docker.ts @@ -12,6 +12,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], + cacheTo: string[], ): Promise { console.log('🏗 Building dev container...'); try { @@ -23,6 +24,7 @@ export async function buildImage( subFolder, skipContainerUserIdUpdate, cacheFrom, + cacheTo, ); } catch (error) { task.setResult(task.TaskResult.Failed, error); diff --git a/azdo-task/DevcontainersCi/src/main.ts b/azdo-task/DevcontainersCi/src/main.ts index 9d029636..875895fb 100644 --- a/azdo-task/DevcontainersCi/src/main.ts +++ b/azdo-task/DevcontainersCi/src/main.ts @@ -48,6 +48,7 @@ export async function runMain(): Promise { const inputEnvsWithDefaults = populateDefaults(envs, inheritEnv); const cacheFrom = task.getInput('cacheFrom')?.split('\n') ?? []; const noCache = (task.getInput('noCache') ?? 'false') === 'true'; + const cacheTo = task.getInput('cacheTo')?.split('\n') ?? []; const skipContainerUserIdUpdate = (task.getInput('skipContainerUserIdUpdate') ?? 'false') === 'true'; @@ -101,6 +102,7 @@ export async function runMain(): Promise { additionalCacheFroms: cacheFrom, output: buildxOutput, noCache, + cacheTo, }; console.log('\n\n'); diff --git a/azdo-task/DevcontainersCi/task.json b/azdo-task/DevcontainersCi/task.json index f8dd8bdd..65196dfb 100644 --- a/azdo-task/DevcontainersCi/task.json +++ b/azdo-task/DevcontainersCi/task.json @@ -122,6 +122,12 @@ "type": "boolean", "label": "Builds the image with `--no-cache` (takes precedence over `cacheFrom`)", "required": false + }, + { + "name": "cacheTo", + "type": "multiLine", + "label": "Specify the image to cache the built image to", + "required": false } ], "outputVariables": [{ diff --git a/azdo-task/README.md b/azdo-task/README.md index 9a602c4f..f0ab9c01 100644 --- a/azdo-task/README.md +++ b/azdo-task/README.md @@ -82,6 +82,7 @@ In the example above, the devcontainer-build-run will perform the following step | skipContainerUserIdUpdate | false | For non-root Dev Containers (i.e. where `remoteUser` is specified), the action attempts to make the container user UID and GID match those of the host user. Set this to true to skip this step (defaults to false) | | cacheFrom | false | Specify additional images to use for build caching | | noCache | false | Builds the image with `--no-cache` (takes precedence over `cacheFrom`) | +| cacheTo | false | Specify the image to cache the built image to | platform | false | Platforms for which the image should be built. If omitted, defaults to the platform of the GitHub Actions Runner. Multiple platforms should be comma separated. | ## Outputs diff --git a/common/src/config.ts b/common/src/config.ts index eb68fe16..84080545 100644 --- a/common/src/config.ts +++ b/common/src/config.ts @@ -15,6 +15,7 @@ export interface DevContainerConfig { context?: string; args?: Record; cacheFrom?: string | string[]; + cacheTo?: string | string[]; }; runArgs?: string[]; mounts?: string[]; diff --git a/common/src/dev-container-cli.ts b/common/src/dev-container-cli.ts index 0cc2c920..4ad248d2 100644 --- a/common/src/dev-container-cli.ts +++ b/common/src/dev-container-cli.ts @@ -161,6 +161,7 @@ export interface DevContainerCliBuildArgs { userDataFolder?: string; output?: string, noCache?: boolean, + cacheTo?: string[], } async function devContainerBuild( args: DevContainerCliBuildArgs, @@ -195,6 +196,11 @@ async function devContainerBuild( commandArgs.push('--cache-from', cacheFrom), ); } + if (args.cacheTo) { + args.cacheTo.forEach(cacheTo => + commandArgs.push('--cache-to', cacheTo), + ); + } return await runSpecCliJsonCommand({ args: commandArgs, log, @@ -211,6 +217,7 @@ export interface DevContainerCliUpArgs { workspaceFolder: string; configFile: string | undefined; additionalCacheFroms?: string[]; + cacheTo?: string[]; skipContainerUserIdUpdate?: boolean; env?: string[]; userDataFolder?: string; @@ -235,6 +242,11 @@ async function devContainerUp( commandArgs.push('--cache-from', cacheFrom), ); } + if (args.cacheTo) { + args.cacheTo.forEach(cacheTo => + commandArgs.push('--cache-to', cacheTo), + ); + } if (args.userDataFolder) { commandArgs.push("--user-data-folder", args.userDataFolder); } diff --git a/common/src/docker.ts b/common/src/docker.ts index ec1c1a4f..fa640781 100644 --- a/common/src/docker.ts +++ b/common/src/docker.ts @@ -21,6 +21,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], + cacheTo: string[], ): Promise { const folder = path.join(checkoutPath, subFolder); const devcontainerJsonPath = path.join( @@ -37,6 +38,7 @@ export async function buildImage( folder, devcontainerConfig, cacheFrom, + cacheTo, ); if (!devcontainerConfig.remoteUser || skipContainerUserIdUpdate == true) { @@ -61,6 +63,7 @@ async function buildImageBase( folder: string, devcontainerConfig: config.DevContainerConfig, cacheFrom: string[], + cacheTo: string[], ): Promise { const configDockerfile = config.getDockerfile(devcontainerConfig); if (!configDockerfile) { @@ -85,8 +88,14 @@ async function buildImageBase( ); } cacheFrom.forEach(cacheValue => args.push('--cache-from', cacheValue)); - args.push('--cache-to'); - args.push('type=inline'); + if (cacheTo) { + coerceToArray(cacheTo).forEach(cacheValue => + args.push('--cache-to', cacheValue), + ); + } else { + args.push('--cache-to'); + args.push('type=inline'); + } args.push('--output=type=docker'); const buildArgs = devcontainerConfig.build?.args; diff --git a/docs/azure-devops-task.md b/docs/azure-devops-task.md index 9a602c4f..0f67cfdc 100644 --- a/docs/azure-devops-task.md +++ b/docs/azure-devops-task.md @@ -82,6 +82,7 @@ In the example above, the devcontainer-build-run will perform the following step | skipContainerUserIdUpdate | false | For non-root Dev Containers (i.e. where `remoteUser` is specified), the action attempts to make the container user UID and GID match those of the host user. Set this to true to skip this step (defaults to false) | | cacheFrom | false | Specify additional images to use for build caching | | noCache | false | Builds the image with `--no-cache` (takes precedence over `cacheFrom`) | +| cacheTo | false | Specify the image to cache the built image to | | platform | false | Platforms for which the image should be built. If omitted, defaults to the platform of the GitHub Actions Runner. Multiple platforms should be comma separated. | ## Outputs diff --git a/docs/github-action.md b/docs/github-action.md index 5e069888..806f5fc3 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -141,6 +141,7 @@ The [`devcontainers/ci` action](https://github.com/marketplace/actions/devcontai | skipContainerUserIdUpdate | false | For non-root Dev Containers (i.e. where `remoteUser` is specified), the action attempts to make the container user UID and GID match those of the host user. Set this to true to skip this step (defaults to false) | | cacheFrom | false | Specify additional images to use for build caching | | noCache | false | Builds the image with `--no-cache` (takes precedence over `cacheFrom`) | +| cacheTo | false | Specify the image to cache the built image to | | platform | false | Platforms for which the image should be built. If omitted, defaults to the platform of the GitHub Actions Runner. Multiple platforms should be comma separated. | ## Outputs diff --git a/github-action/src/docker.ts b/github-action/src/docker.ts index c06428c4..1c65db1b 100644 --- a/github-action/src/docker.ts +++ b/github-action/src/docker.ts @@ -12,6 +12,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], + cacheTo: string[], ): Promise { core.startGroup('🏗 Building dev container...'); try { @@ -23,6 +24,7 @@ export async function buildImage( subFolder, skipContainerUserIdUpdate, cacheFrom, + cacheTo, ); } catch (error) { core.setFailed(error); diff --git a/github-action/src/main.ts b/github-action/src/main.ts index 61a25135..bce41072 100644 --- a/github-action/src/main.ts +++ b/github-action/src/main.ts @@ -57,6 +57,7 @@ export async function runMain(): Promise { const inputEnvsWithDefaults = populateDefaults(inputEnvs, inheritEnv); const cacheFrom: string[] = core.getMultilineInput('cacheFrom'); const noCache: boolean = core.getBooleanInput('noCache'); + const cacheTo: string[] = core.getMultilineInput('cacheFrom'); const skipContainerUserIdUpdate = core.getBooleanInput( 'skipContainerUserIdUpdate', ); @@ -121,6 +122,7 @@ export async function runMain(): Promise { userDataFolder, output: buildxOutput, noCache, + cacheTo, }; const result = await devcontainer.build(args, log);