From cfe1f07855b79c017084cf3f884c6ab32a804d6d Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 15 Dec 2023 13:44:02 +0000 Subject: [PATCH] Add configFile option --- .azure-devops/azure-pipelines.yml | 15 +++++++ .devcontainer/devcontainer-lock.json | 9 +++++ .github/workflows/ci_common.yml | 40 +++++++++++++++++++ action.yml | 4 ++ azdo-task/DevcontainersCi/src/main.ts | 6 +++ azdo-task/DevcontainersCi/task.json | 6 +++ azdo-task/README.md | 1 + common/src/dev-container-cli.ts | 16 +++++++- docs/azure-devops-task.md | 1 + docs/github-action.md | 1 + github-action/src/main.ts | 8 ++++ .../.devcontainer/subfolder/devcontainer.json | 6 +++ 12 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 .devcontainer/devcontainer-lock.json create mode 100644 github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json diff --git a/.azure-devops/azure-pipelines.yml b/.azure-devops/azure-pipelines.yml index 368f9f6a..4497d9de 100644 --- a/.azure-devops/azure-pipelines.yml +++ b/.azure-devops/azure-pipelines.yml @@ -90,6 +90,21 @@ jobs: exit 1 fi + - job: test_config_file + displayName: Test configFile option + steps: + - task: DevcontainersCi@0 + inputs: + subFolder: github-tests/Dockerfile/config-file + configFile: github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json + runCmd: echo $HOSTNAME && [[ $HOSTNAME == "my-host" ]] + - script: | + echo "'runCmdOutput' value: $runCmdOutput" + if [["$runCmdOutput" = *my-host*]]; then + echo "'runCmdOutput' output of test_config_file job doesn't contain expected value 'my-host'" + exit 1 + fi + - job: test_build_args displayName: Test build-args steps: diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..dc9fc599 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.0.11", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:464564228ccdd6028f01f8a62a3cfbaf76e9ba7953b29ac0e53ba2c262604312", + "integrity": "sha256:464564228ccdd6028f01f8a62a3cfbaf76e9ba7953b29ac0e53ba2c262604312" + } + } +} \ No newline at end of file diff --git a/.github/workflows/ci_common.yml b/.github/workflows/ci_common.yml index a58f2a56..aa4f7997 100644 --- a/.github/workflows/ci_common.yml +++ b/.github/workflows/ci_common.yml @@ -200,6 +200,7 @@ jobs: - test-gh-docker-from-docker-root - test-gh-skip-user-update - test-compose-features + - test-config-file - test-simple - test-no-run - test-platform-with-runcmd @@ -452,6 +453,45 @@ jobs: env: runCmdOutput: ${{ steps.simpletest.outputs.runCmdOutput }} + test-config-file: + name: Run test with config file + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + # if the following value is missing (i.e. not triggered via comment workflow) + # then the default checkout will apply + ref: ${{ inputs.prRef }} + + # Published action contains compiled JS, but we need to compile it here + - uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Compile GH action + run: | + (cd common && npm install && npm run build) + (cd github-action/ && npm install && npm run build && npm run package) + + - name: Run test + uses: ./ + id: configfiletest + with: + subFolder: github-tests/Dockerfile/config-file + configFile: github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json + runCmd: echo $HOSTNAME && [[ $HOSTNAME == "my-host" ]] + - name: Validate runCmdOutput output + run: | + echo "'runCmdOutput' value: $runCmdOutput" + if [["$runCmdOutput" = *my-host*]]; then + echo "'runCmdOutput' output of configfiletest step doesn't contain expected value 'my-host'" + exit 1 + fi + env: + runCmdOutput: ${{ steps.configfiletest.outputs.runCmdOutput }} + test-gh-run-args: name: Run GitHub run-args test runs-on: ubuntu-latest diff --git a/action.yml b/action.yml index f958fb20..0a3aeee9 100644 --- a/action.yml +++ b/action.yml @@ -21,6 +21,10 @@ inputs: required: false description: Specify a child folder (containing a .devcontainer) instead of using the repository root default: + configFile: + required: false + description: Specify the path to a devcontainer.json file instead of using `./.devcontainer/devcontainer.json` or `./.devcontainer.json` + default: checkoutPath: required: false description: Specify path to checked out folder if not using default (or for testing with nektos/act) diff --git a/azdo-task/DevcontainersCi/src/main.ts b/azdo-task/DevcontainersCi/src/main.ts index 93d040d4..e02ab6a4 100644 --- a/azdo-task/DevcontainersCi/src/main.ts +++ b/azdo-task/DevcontainersCi/src/main.ts @@ -41,6 +41,7 @@ export async function runMain(): Promise { const imageTag = task.getInput('imageTag'); const platform = task.getInput('platform'); const subFolder = task.getInput('subFolder') ?? '.'; + const relativeConfigFile = task.getInput('configFile'); const runCommand = task.getInput('runCmd'); const envs = task.getInput('env')?.split('\n') ?? []; const inputEnvsWithDefaults = populateDefaults(envs); @@ -62,6 +63,8 @@ export async function runMain(): Promise { const log = (message: string): void => console.log(message); const workspaceFolder = path.resolve(checkoutPath, subFolder); + const configFile = + relativeConfigFile && path.resolve(checkoutPath, relativeConfigFile); const resolvedImageTag = imageTag ?? 'latest'; const imageTagArray = resolvedImageTag.split(','); @@ -91,6 +94,7 @@ export async function runMain(): Promise { } const buildArgs: DevContainerCliBuildArgs = { workspaceFolder, + configFile, imageName: fullImageNameArray, platform, additionalCacheFroms: cacheFrom, @@ -120,6 +124,7 @@ export async function runMain(): Promise { console.log('***'); const upArgs: DevContainerCliUpArgs = { workspaceFolder, + configFile, additionalCacheFroms: cacheFrom, skipContainerUserIdUpdate, env: inputEnvsWithDefaults, @@ -141,6 +146,7 @@ export async function runMain(): Promise { console.log('***'); const execArgs: DevContainerCliExecArgs = { workspaceFolder, + configFile, command: ['bash', '-c', runCommand], env: inputEnvsWithDefaults, }; diff --git a/azdo-task/DevcontainersCi/task.json b/azdo-task/DevcontainersCi/task.json index f7a3d446..91709046 100644 --- a/azdo-task/DevcontainersCi/task.json +++ b/azdo-task/DevcontainersCi/task.json @@ -52,6 +52,12 @@ "label": "Specify a child folder (containing a .devcontainer) instead of using the repository root", "required": false }, + { + "name": "configFile", + "type": "string", + "label": "Specify the path to a devcontainer.json file instead of using `./.devcontainer/devcontainer.json` or `./.devcontainer.json`", + "required": false + }, { "name": "env", "type": "multiLine", diff --git a/azdo-task/README.md b/azdo-task/README.md index 4775fbc8..60a16b84 100644 --- a/azdo-task/README.md +++ b/azdo-task/README.md @@ -71,6 +71,7 @@ In the example above, the devcontainer-build-run will perform the following step | imageName | true | Image name to use when building the dev container image (including registry) | | imageTag | false | One or more comma-separated image tags (defaults to `latest`) | | subFolder | false | Use this to specify the repo-relative path to the folder containing the dev container (i.e. the folder that contains the `.devcontainer` folder). Defaults to repo root | +| configFile | false | Use this to specify the repo-relative path to the devcontainer.json file. Defaults to `./.devcontainer/devcontainer.json` and `./.devcontainer.json`. | | runCmd | true | The command to run after building the dev container image | | env | false | Specify environment variables to pass to the dev container when run | | push | false | One of: `never`, `filter`, `always`. When set to `filter`, the image if pushed if the `sourceBranchFilterForPush`, `buildReasonsForPush`, and `pushOnFailedBuild` conditions are met. Defaults to `filter` if `imageName` is set, `never` otherwise. | diff --git a/common/src/dev-container-cli.ts b/common/src/dev-container-cli.ts index 08af45c8..6e162820 100644 --- a/common/src/dev-container-cli.ts +++ b/common/src/dev-container-cli.ts @@ -152,6 +152,7 @@ export interface DevContainerCliBuildResult extends DevContainerCliSuccessResult {} export interface DevContainerCliBuildArgs { workspaceFolder: string; + configFile: string | undefined; imageName?: string[]; platform?: string; additionalCacheFroms?: string[]; @@ -168,6 +169,9 @@ async function devContainerBuild( '--workspace-folder', args.workspaceFolder, ]; + if (args.configFile) { + commandArgs.push('--config', args.configFile); + } if (args.imageName) { args.imageName.forEach(iName => commandArgs.push('--image-name', iName), @@ -203,6 +207,7 @@ export interface DevContainerCliUpResult extends DevContainerCliSuccessResult { } export interface DevContainerCliUpArgs { workspaceFolder: string; + configFile: string | undefined; additionalCacheFroms?: string[]; skipContainerUserIdUpdate?: boolean; env?: string[]; @@ -220,6 +225,9 @@ async function devContainerUp( args.workspaceFolder, ...remoteEnvArgs, ]; + if (args.configFile) { + commandArgs.push('--config', args.configFile); + } if (args.additionalCacheFroms) { args.additionalCacheFroms.forEach(cacheFrom => commandArgs.push('--cache-from', cacheFrom), @@ -245,6 +253,7 @@ async function devContainerUp( export interface DevContainerCliExecArgs { workspaceFolder: string; + configFile: string | undefined; command: string[]; env?: string[]; userDataFolder?: string; @@ -255,12 +264,15 @@ async function devContainerExec( ): Promise { // const remoteEnvArgs = args.env ? args.env.flatMap(e=> ["--remote-env", e]): []; // TODO - test flatMap again const remoteEnvArgs = getRemoteEnvArray(args.env); - const commandArgs = ["exec", "--workspace-folder", args.workspaceFolder, ...remoteEnvArgs, ...args.command]; + const commandArgs = ["exec", "--workspace-folder", args.workspaceFolder, ...remoteEnvArgs]; + if (args.configFile) { + commandArgs.push('--config', args.configFile); + } if (args.userDataFolder) { commandArgs.push("--user-data-folder", args.userDataFolder); } return await runSpecCliNonJsonCommand({ - args: commandArgs, + args: commandArgs.concat(args.command), log, env: {DOCKER_BUILDKIT: '1', COMPOSE_DOCKER_CLI_BUILD: '1'}, }); diff --git a/docs/azure-devops-task.md b/docs/azure-devops-task.md index 4775fbc8..60a16b84 100644 --- a/docs/azure-devops-task.md +++ b/docs/azure-devops-task.md @@ -71,6 +71,7 @@ In the example above, the devcontainer-build-run will perform the following step | imageName | true | Image name to use when building the dev container image (including registry) | | imageTag | false | One or more comma-separated image tags (defaults to `latest`) | | subFolder | false | Use this to specify the repo-relative path to the folder containing the dev container (i.e. the folder that contains the `.devcontainer` folder). Defaults to repo root | +| configFile | false | Use this to specify the repo-relative path to the devcontainer.json file. Defaults to `./.devcontainer/devcontainer.json` and `./.devcontainer.json`. | | runCmd | true | The command to run after building the dev container image | | env | false | Specify environment variables to pass to the dev container when run | | push | false | One of: `never`, `filter`, `always`. When set to `filter`, the image if pushed if the `sourceBranchFilterForPush`, `buildReasonsForPush`, and `pushOnFailedBuild` conditions are met. Defaults to `filter` if `imageName` is set, `never` otherwise. | diff --git a/docs/github-action.md b/docs/github-action.md index 3725d2f9..8e5e69d9 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -130,6 +130,7 @@ The [`devcontainers/ci` action](https://github.com/marketplace/actions/devcontai | imageName | true | Image name to use when building the dev container image (including registry) | | imageTag | false | One or more comma-separated image tags (defaults to `latest`) | | subFolder | false | Use this to specify the repo-relative path to the folder containing the dev container (i.e. the folder that contains the `.devcontainer` folder). Defaults to repo root | +| configFile | false | Use this to specify the repo-relative path to the devcontainer.json file. Defaults to `./.devcontainer/devcontainer.json` and `./.devcontainer.json`. | | runCmd | true | The command to run after building the dev container image | | env | false | Specify environment variables to pass to the dev container when run | | checkoutPath | false | Only used for development/testing | diff --git a/github-action/src/main.ts b/github-action/src/main.ts index eca23d88..2362fbf1 100644 --- a/github-action/src/main.ts +++ b/github-action/src/main.ts @@ -48,6 +48,9 @@ export async function runMain(): Promise { const imageTag = emptyStringAsUndefined(core.getInput('imageTag')); const platform = emptyStringAsUndefined(core.getInput('platform')); const subFolder: string = core.getInput('subFolder'); + const relativeConfigFile = emptyStringAsUndefined( + core.getInput('configFile'), + ); const runCommand = core.getInput('runCmd'); const inputEnvs: string[] = core.getMultilineInput('env'); const inputEnvsWithDefaults = populateDefaults(inputEnvs); @@ -72,6 +75,8 @@ export async function runMain(): Promise { const log = (message: string): void => core.info(message); const workspaceFolder = path.resolve(checkoutPath, subFolder); + const configFile = + relativeConfigFile && path.resolve(checkoutPath, relativeConfigFile); const resolvedImageTag = imageTag ?? 'latest'; const imageTagArray = resolvedImageTag.split(','); @@ -108,6 +113,7 @@ export async function runMain(): Promise { const buildResult = await core.group('🏗️ build container', async () => { const args: DevContainerCliBuildArgs = { workspaceFolder, + configFile, imageName: fullImageNameArray, platform, additionalCacheFroms: cacheFrom, @@ -142,6 +148,7 @@ export async function runMain(): Promise { const upResult = await core.group('🏃 start container', async () => { const args: DevContainerCliUpArgs = { workspaceFolder, + configFile, additionalCacheFroms: cacheFrom, skipContainerUserIdUpdate, env: inputEnvsWithDefaults, @@ -166,6 +173,7 @@ export async function runMain(): Promise { async () => { const args: DevContainerCliExecArgs = { workspaceFolder, + configFile, command: ['bash', '-c', runCommand], env: inputEnvsWithDefaults, userDataFolder, diff --git a/github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json b/github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json new file mode 100644 index 00000000..ee425d48 --- /dev/null +++ b/github-tests/Dockerfile/config-file/.devcontainer/subfolder/devcontainer.json @@ -0,0 +1,6 @@ +{ + "name": "config-file", + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "runArgs": [ + "--hostname", "my-host" + ]} \ No newline at end of file