From 80f211ca34d9b9d4c19083a7b33f5360e7b46537 Mon Sep 17 00:00:00 2001 From: Sebastian Steins <592313+sebst@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:26:37 +0200 Subject: [PATCH 1/3] Add `cacheTo` argument to `ci` action --- action.yml | 3 +++ azdo-task/DevcontainersCi/src/docker.ts | 2 ++ azdo-task/DevcontainersCi/src/main.ts | 2 ++ azdo-task/DevcontainersCi/task.json | 6 ++++++ azdo-task/README.md | 1 + common/src/config.ts | 1 + common/src/dev-container-cli.ts | 4 ++++ common/src/docker.ts | 12 ++++++++++-- github-action/src/docker.ts | 2 ++ github-action/src/main.ts | 2 ++ 10 files changed, 33 insertions(+), 2 deletions(-) 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..90907633 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 | undefined, ): 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..0e4f4b0c 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') ?? undefined; 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..172e1f98 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": "string", + "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..d3be0b47 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; }; runArgs?: string[]; mounts?: string[]; diff --git a/common/src/dev-container-cli.ts b/common/src/dev-container-cli.ts index 0cc2c920..ebf05050 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, @@ -194,6 +195,9 @@ async function devContainerBuild( args.additionalCacheFroms.forEach(cacheFrom => commandArgs.push('--cache-from', cacheFrom), ); + if (args.cacheTo) { + commandArgs.push('--cache-to', args.cacheTo); + } } return await runSpecCliJsonCommand({ args: commandArgs, diff --git a/common/src/docker.ts b/common/src/docker.ts index ec1c1a4f..2bc2bdd7 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 | undefined, ): 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 | undefined, ): Promise { const configDockerfile = config.getDockerfile(devcontainerConfig); if (!configDockerfile) { @@ -85,8 +88,13 @@ async function buildImageBase( ); } cacheFrom.forEach(cacheValue => args.push('--cache-from', cacheValue)); - args.push('--cache-to'); - args.push('type=inline'); + if (cacheTo) { + args.push('--cache-to'); + args.push(cacheTo); + } else { + args.push('--cache-to'); + args.push('type=inline'); + } args.push('--output=type=docker'); const buildArgs = devcontainerConfig.build?.args; diff --git a/github-action/src/docker.ts b/github-action/src/docker.ts index c06428c4..3a450ac5 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 | undefined, ): 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..3a8aa4d7 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 = emptyStringAsUndefined(core.getInput('cacheTo')); 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); From ef79e83c8b211fd6f3023048c40e327091b99f04 Mon Sep 17 00:00:00 2001 From: Sebastian Steins <592313+sebst@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:01:00 +0200 Subject: [PATCH 2/3] #299 Make `cacheTo` an array input --- azdo-task/DevcontainersCi/src/docker.ts | 2 +- azdo-task/DevcontainersCi/src/main.ts | 2 +- azdo-task/DevcontainersCi/task.json | 2 +- common/src/config.ts | 2 +- common/src/dev-container-cli.ts | 6 ++++-- common/src/docker.ts | 7 +++---- docs/azure-devops-task.md | 1 + docs/github-action.md | 1 + github-action/src/docker.ts | 2 +- github-action/src/main.ts | 2 +- 10 files changed, 15 insertions(+), 12 deletions(-) diff --git a/azdo-task/DevcontainersCi/src/docker.ts b/azdo-task/DevcontainersCi/src/docker.ts index 90907633..e4987ca1 100644 --- a/azdo-task/DevcontainersCi/src/docker.ts +++ b/azdo-task/DevcontainersCi/src/docker.ts @@ -12,7 +12,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], - cacheTo: string | undefined, + cacheTo: string[], ): Promise { console.log('🏗 Building dev container...'); try { diff --git a/azdo-task/DevcontainersCi/src/main.ts b/azdo-task/DevcontainersCi/src/main.ts index 0e4f4b0c..875895fb 100644 --- a/azdo-task/DevcontainersCi/src/main.ts +++ b/azdo-task/DevcontainersCi/src/main.ts @@ -48,7 +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') ?? undefined; + const cacheTo = task.getInput('cacheTo')?.split('\n') ?? []; const skipContainerUserIdUpdate = (task.getInput('skipContainerUserIdUpdate') ?? 'false') === 'true'; diff --git a/azdo-task/DevcontainersCi/task.json b/azdo-task/DevcontainersCi/task.json index 172e1f98..65196dfb 100644 --- a/azdo-task/DevcontainersCi/task.json +++ b/azdo-task/DevcontainersCi/task.json @@ -125,7 +125,7 @@ }, { "name": "cacheTo", - "type": "string", + "type": "multiLine", "label": "Specify the image to cache the built image to", "required": false } diff --git a/common/src/config.ts b/common/src/config.ts index d3be0b47..84080545 100644 --- a/common/src/config.ts +++ b/common/src/config.ts @@ -15,7 +15,7 @@ export interface DevContainerConfig { context?: string; args?: Record; cacheFrom?: string | string[]; - cacheTo?: 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 ebf05050..c318415e 100644 --- a/common/src/dev-container-cli.ts +++ b/common/src/dev-container-cli.ts @@ -161,7 +161,7 @@ export interface DevContainerCliBuildArgs { userDataFolder?: string; output?: string, noCache?: boolean, - cacheTo?: string, + cacheTo?: string[], } async function devContainerBuild( args: DevContainerCliBuildArgs, @@ -196,7 +196,9 @@ async function devContainerBuild( commandArgs.push('--cache-from', cacheFrom), ); if (args.cacheTo) { - commandArgs.push('--cache-to', args.cacheTo); + args.cacheTo.forEach(cacheTo => + commandArgs.push('--cache-to', cacheTo), + ); } } return await runSpecCliJsonCommand({ diff --git a/common/src/docker.ts b/common/src/docker.ts index 2bc2bdd7..888e598a 100644 --- a/common/src/docker.ts +++ b/common/src/docker.ts @@ -21,7 +21,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], - cacheTo: string | undefined, + cacheTo: string[], ): Promise { const folder = path.join(checkoutPath, subFolder); const devcontainerJsonPath = path.join( @@ -63,7 +63,7 @@ async function buildImageBase( folder: string, devcontainerConfig: config.DevContainerConfig, cacheFrom: string[], - cacheTo: string | undefined, + cacheTo: string[], ): Promise { const configDockerfile = config.getDockerfile(devcontainerConfig); if (!configDockerfile) { @@ -89,8 +89,7 @@ async function buildImageBase( } cacheFrom.forEach(cacheValue => args.push('--cache-from', cacheValue)); if (cacheTo) { - args.push('--cache-to'); - args.push(cacheTo); + cacheTo.forEach(cacheValue => args.push('--cache-to', cacheValue)); } else { args.push('--cache-to'); args.push('type=inline'); 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 3a450ac5..1c65db1b 100644 --- a/github-action/src/docker.ts +++ b/github-action/src/docker.ts @@ -12,7 +12,7 @@ export async function buildImage( subFolder: string, skipContainerUserIdUpdate: boolean, cacheFrom: string[], - cacheTo: string | undefined, + cacheTo: string[], ): Promise { core.startGroup('🏗 Building dev container...'); try { diff --git a/github-action/src/main.ts b/github-action/src/main.ts index 3a8aa4d7..bce41072 100644 --- a/github-action/src/main.ts +++ b/github-action/src/main.ts @@ -57,7 +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 = emptyStringAsUndefined(core.getInput('cacheTo')); + const cacheTo: string[] = core.getMultilineInput('cacheFrom'); const skipContainerUserIdUpdate = core.getBooleanInput( 'skipContainerUserIdUpdate', ); From d29f4810682337a28d7d000ec0831abae63e210c Mon Sep 17 00:00:00 2001 From: Sebastian Steins <592313+sebst@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:09:21 +0200 Subject: [PATCH 3/3] #299 Make `cacheTo` available to `devcontainer up` and make it independant of `noCache` --- common/src/dev-container-cli.ts | 16 +++++++++++----- common/src/docker.ts | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/common/src/dev-container-cli.ts b/common/src/dev-container-cli.ts index c318415e..4ad248d2 100644 --- a/common/src/dev-container-cli.ts +++ b/common/src/dev-container-cli.ts @@ -195,11 +195,11 @@ async function devContainerBuild( args.additionalCacheFroms.forEach(cacheFrom => commandArgs.push('--cache-from', cacheFrom), ); - if (args.cacheTo) { - args.cacheTo.forEach(cacheTo => - commandArgs.push('--cache-to', cacheTo), - ); - } + } + if (args.cacheTo) { + args.cacheTo.forEach(cacheTo => + commandArgs.push('--cache-to', cacheTo), + ); } return await runSpecCliJsonCommand({ args: commandArgs, @@ -217,6 +217,7 @@ export interface DevContainerCliUpArgs { workspaceFolder: string; configFile: string | undefined; additionalCacheFroms?: string[]; + cacheTo?: string[]; skipContainerUserIdUpdate?: boolean; env?: string[]; userDataFolder?: string; @@ -241,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 888e598a..fa640781 100644 --- a/common/src/docker.ts +++ b/common/src/docker.ts @@ -89,7 +89,9 @@ async function buildImageBase( } cacheFrom.forEach(cacheValue => args.push('--cache-from', cacheValue)); if (cacheTo) { - cacheTo.forEach(cacheValue => args.push('--cache-to', cacheValue)); + coerceToArray(cacheTo).forEach(cacheValue => + args.push('--cache-to', cacheValue), + ); } else { args.push('--cache-to'); args.push('type=inline');