diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e629bb760..8af25a346 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,12 @@ "name": "devcontainers-ci", "dockerFile": "Dockerfile", "build": { - "cacheFrom": "ghcr.io/devcontainers/ci-devcontainer:latest" + "cacheFrom": "ghcr.io/devcontainers/ci-devcontainer:latest", + "args": { + // This is a temporary workaround when developing from host -> remote host -> devcontainer + // see: https://github.com/microsoft/vscode-remote-release/issues/7958 + "BUILDKIT_INLINE_CACHE": "0" + } }, "mounts": [ // Keep command history diff --git a/action.yml b/action.yml index 3af8a63eb..85b1e3b36 100644 --- a/action.yml +++ b/action.yml @@ -63,6 +63,10 @@ inputs: required: false default: false description: Builds the image with `--no-cache` (takes precedence over `cacheFrom`) + cliVersion: + required: false + default: latest + description: The version of the devcontainer CLI to use cacheTo: required: false description: Specify the image to cache the built image to diff --git a/azdo-task/DevcontainersCi/src/main.ts b/azdo-task/DevcontainersCi/src/main.ts index 875895fb3..ce58479b8 100644 --- a/azdo-task/DevcontainersCi/src/main.ts +++ b/azdo-task/DevcontainersCi/src/main.ts @@ -7,6 +7,7 @@ import { DevContainerCliBuildArgs, DevContainerCliExecArgs, DevContainerCliUpArgs, + MAJOR_VERSION_FALLBACK } from '../../../common/src/dev-container-cli'; import {isDockerBuildXInstalled, pushImage} from './docker'; @@ -23,10 +24,18 @@ export async function runMain(): Promise { ); return; } - const devContainerCliInstalled = await devcontainer.isCliInstalled(exec); + const specifiedDevContainerCliVersion = + task.getInput('cliVersion') ?? MAJOR_VERSION_FALLBACK; + const devContainerCliInstalled = await devcontainer.isCliInstalled( + exec, + specifiedDevContainerCliVersion, + ); if (!devContainerCliInstalled) { console.log('Installing @devcontainers/cli...'); - const success = await devcontainer.installCli(exec); + const success = await devcontainer.installCli( + exec, + specifiedDevContainerCliVersion, + ); if (!success) { task.setResult( task.TaskResult.Failed, diff --git a/azdo-task/README.md b/azdo-task/README.md index 0f67cfdc1..f6895c55c 100644 --- a/azdo-task/README.md +++ b/azdo-task/README.md @@ -83,7 +83,8 @@ In the example above, the devcontainer-build-run will perform the following step | 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. | +| 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. | +| cliVersion | false | The version of the [devcontainers CLI](https://github.com/devcontainers/cli) to use | ## Outputs diff --git a/common/src/dev-container-cli.ts b/common/src/dev-container-cli.ts index 4ad248d2d..5e84dd5a4 100644 --- a/common/src/dev-container-cli.ts +++ b/common/src/dev-container-cli.ts @@ -6,7 +6,7 @@ import {promisify} from 'util'; import {ExecFunction} from './exec'; import {findWindowsExecutable} from './windows'; -const cliVersion = "0"; // Use 'latest' to get latest CLI version, or pin to specific version e.g. '0.14.1' if required +export const MAJOR_VERSION_FALLBACK = '0'; export interface DevContainerCliError { outcome: 'error'; @@ -26,19 +26,17 @@ function getSpecCliInfo() { }; } -async function isCliInstalled(exec: ExecFunction): Promise { +async function isCliInstalled(exec: ExecFunction, cliVersion: string): Promise { try { const command = await findWindowsExecutable(getSpecCliInfo().command); - const {exitCode} = await exec(command, ['--help'], { - silent: true, - }); - return exitCode === 0; + const {exitCode, stdout} = await exec(command, ['--version'], {}); + return exitCode === 0 && stdout.trim() === cliVersion; } catch (error) { return false; } } const fstat = promisify(fs.stat); -async function installCli(exec: ExecFunction): Promise { +async function installCli(exec: ExecFunction, cliVersion: string): Promise { // if we have a local 'cli' folder, then use that as we're testing a private cli build let cliStat = null; try { @@ -54,7 +52,7 @@ async function installCli(exec: ExecFunction): Promise { } return exitCode === 0; } - console.log('** Installing @devcontainers/cli'); + console.log(`** Installing @devcontainers/cli@${cliVersion}`); const {exitCode, stdout, stderr} = await exec('bash', ['-c', `npm install -g @devcontainers/cli@${cliVersion}`], {}); if (exitCode != 0) { console.log(stdout); diff --git a/docs/azure-devops-task.md b/docs/azure-devops-task.md index 0f67cfdc1..f6895c55c 100644 --- a/docs/azure-devops-task.md +++ b/docs/azure-devops-task.md @@ -83,7 +83,8 @@ In the example above, the devcontainer-build-run will perform the following step | 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. | +| 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. | +| cliVersion | false | The version of the [devcontainers CLI](https://github.com/devcontainers/cli) to use | ## Outputs diff --git a/docs/github-action.md b/docs/github-action.md index 806f5fc3f..c8b89cbf4 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -142,7 +142,8 @@ The [`devcontainers/ci` action](https://github.com/marketplace/actions/devcontai | 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. | +| 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. | +| cliVersion | false | The version of the [devcontainers CLI](https://github.com/devcontainers/cli) to use | ## Outputs diff --git a/github-action/src/main.ts b/github-action/src/main.ts index 356b502b4..62a609710 100644 --- a/github-action/src/main.ts +++ b/github-action/src/main.ts @@ -7,6 +7,7 @@ import { DevContainerCliBuildArgs, DevContainerCliExecArgs, DevContainerCliUpArgs, + MAJOR_VERSION_FALLBACK } from '../../common/src/dev-container-cli'; import {isDockerBuildXInstalled, pushImage} from './docker'; @@ -33,10 +34,18 @@ export async function runMain(): Promise { ); return; } - const devContainerCliInstalled = await devcontainer.isCliInstalled(exec); + const specifiedDevContainerCliVersion = + core.getInput('cliVersion') ?? MAJOR_VERSION_FALLBACK; + const devContainerCliInstalled = await devcontainer.isCliInstalled( + exec, + specifiedDevContainerCliVersion, + ); if (!devContainerCliInstalled) { core.info('Installing @devcontainers/cli...'); - const success = await devcontainer.installCli(exec); + const success = await devcontainer.installCli( + exec, + specifiedDevContainerCliVersion, + ); if (!success) { core.setFailed('@devcontainers/cli install failed!'); return;