diff --git a/action.yml b/action.yml index 0a3aeee9..35979b9f 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,10 @@ inputs: env: required: false description: Specify environment variables to pass to the docker run command + inheritEnv: + required: false + default: false + description: Inherit all environment variables of the runner CI machine. skipContainerUserIdUpdate: required: false default: false diff --git a/azdo-task/DevcontainersCi/src/main.ts b/azdo-task/DevcontainersCi/src/main.ts index 78787123..9d029636 100644 --- a/azdo-task/DevcontainersCi/src/main.ts +++ b/azdo-task/DevcontainersCi/src/main.ts @@ -44,7 +44,8 @@ export async function runMain(): Promise { const relativeConfigFile = task.getInput('configFile'); const runCommand = task.getInput('runCmd'); const envs = task.getInput('env')?.split('\n') ?? []; - const inputEnvsWithDefaults = populateDefaults(envs); + const inheritEnv = (task.getInput('inheritEnv') ?? 'false') === 'true'; + const inputEnvsWithDefaults = populateDefaults(envs, inheritEnv); const cacheFrom = task.getInput('cacheFrom')?.split('\n') ?? []; const noCache = (task.getInput('noCache') ?? 'false') === 'true'; const skipContainerUserIdUpdate = diff --git a/azdo-task/DevcontainersCi/task.json b/azdo-task/DevcontainersCi/task.json index 91709046..f8dd8bdd 100644 --- a/azdo-task/DevcontainersCi/task.json +++ b/azdo-task/DevcontainersCi/task.json @@ -64,6 +64,13 @@ "label": "Specify environment variables to pass to the docker run command", "required": false }, + { + "name": "inheritEnv", + "type": "boolean", + "label": "Inherit all environment variables of the runner CI machine", + "defaultValue": false, + "required": false + }, { "name": "push", "type": "pickList", @@ -133,4 +140,4 @@ "argumentFormat": "" } } -} \ No newline at end of file +} diff --git a/azdo-task/README.md b/azdo-task/README.md index 60a16b84..9a602c4f 100644 --- a/azdo-task/README.md +++ b/azdo-task/README.md @@ -74,6 +74,7 @@ In the example above, the devcontainer-build-run will perform the following step | 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 | +| inheritEnv | false | Inherit all environment variables of the runner CI machine | | 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. | | pushOnFailedBuild | false | If `false` (default), only push if the build is successful. Set to true to push on failed builds | | sourceBranchFilterForPush | false | Allows you to limit which branch's builds are pushed to the registry (only specified branches are allowed to push). If empty, all branches are allowed | diff --git a/common/__tests__/envvars.test.ts b/common/__tests__/envvars.test.ts index 6eb53b08..e687e4bd 100644 --- a/common/__tests__/envvars.test.ts +++ b/common/__tests__/envvars.test.ts @@ -33,21 +33,34 @@ describe('substituteValues', () => { describe('populateDefaults', () => { test('returns original inputs when fully specified', () => { const input = ['TEST_ENV1=value1', 'TEST_ENV2=value2']; - const result = populateDefaults(input); + const result = populateDefaults(input, false); expect(result).toEqual(['TEST_ENV1=value1', 'TEST_ENV2=value2']); }); test('adds process env value when set and input value not provided', () => { const input = ['TEST_ENV1', 'TEST_ENV2=value2']; process.env.TEST_ENV1 = 'TestEnvValue1'; - const result = populateDefaults(input); + const result = populateDefaults(input, false); expect(result).toEqual(['TEST_ENV1=TestEnvValue1', 'TEST_ENV2=value2']); }); test('skips value when process env value not set and input value not provided', () => { const input = ['TEST_ENV1', 'TEST_ENV2=value2']; delete process.env.TEST_ENV1; - const result = populateDefaults(input); + const result = populateDefaults(input, false); expect(result).toEqual(['TEST_ENV2=value2']); }); + + test('inherits process env when asked', () => { + const originalEnv = process.env; + try { + process.env = {TEST_ENV1: 'value1'}; + const input = ['TEST_ENV2=value2']; + const result = populateDefaults(input, true); + expect(result).toEqual(['TEST_ENV1=value1', 'TEST_ENV2=value2']); + } + finally { + process.env = originalEnv; + } + }); }); diff --git a/common/src/envvars.ts b/common/src/envvars.ts index 5200152a..73b0f1e6 100644 --- a/common/src/envvars.ts +++ b/common/src/envvars.ts @@ -29,8 +29,20 @@ function getSubstitutionValue(regexMatch: string, placeholder: string): string { // In the latter case, the corresponding returned item would be "BAR=hi" // where the value is taken from the matching process env var. // In the case of values not set in the process, they are omitted -export function populateDefaults(envs: string[]): string[] { +export function populateDefaults(envs: string[], inheritEnv: boolean): string[] { const result: string[] = []; + if (inheritEnv) { + for (const [key, value] of Object.entries(process.env)) { + switch (key) { + case 'PATH': + // don't copy these by default (user can still explicitly specify them). + break; + default: + result.push(`${key}=${value}`); + break; + } + } + } for (let i = 0; i < envs.length; i++) { const inputEnv = envs[i]; if (inputEnv.indexOf('=') >= 0) { diff --git a/docs/azure-devops-task.md b/docs/azure-devops-task.md index 60a16b84..9a602c4f 100644 --- a/docs/azure-devops-task.md +++ b/docs/azure-devops-task.md @@ -74,6 +74,7 @@ In the example above, the devcontainer-build-run will perform the following step | 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 | +| inheritEnv | false | Inherit all environment variables of the runner CI machine | | 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. | | pushOnFailedBuild | false | If `false` (default), only push if the build is successful. Set to true to push on failed builds | | sourceBranchFilterForPush | false | Allows you to limit which branch's builds are pushed to the registry (only specified branches are allowed to push). If empty, all branches are allowed | diff --git a/docs/github-action.md b/docs/github-action.md index 8e5e69d9..4a1ff4e7 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -133,6 +133,7 @@ The [`devcontainers/ci` action](https://github.com/marketplace/actions/devcontai | 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 | +| inheritEnv | false | Inherit all environment variables of the runner CI machine | | checkoutPath | false | Only used for development/testing | | push | false | Control when images are pushed. Options are `never`, `filter`, `always`. For `filter`, images are pushed if the `refFilterForPush` and `eventFilterForPush` conditions are met. Defaults to `filter` if `imageName` is set, `never` otherwise. | | refFilterForPush | false | Set the source branches (e.g. `refs/heads/main`) that are allowed to trigger a push of the dev container image. Leave empty to allow all (default) | diff --git a/github-action/src/main.ts b/github-action/src/main.ts index 3e98d59d..61a25135 100644 --- a/github-action/src/main.ts +++ b/github-action/src/main.ts @@ -53,7 +53,8 @@ export async function runMain(): Promise { ); const runCommand = core.getInput('runCmd'); const inputEnvs: string[] = core.getMultilineInput('env'); - const inputEnvsWithDefaults = populateDefaults(inputEnvs); + const inheritEnv: boolean = core.getBooleanInput('inheritEnv'); + const inputEnvsWithDefaults = populateDefaults(inputEnvs, inheritEnv); const cacheFrom: string[] = core.getMultilineInput('cacheFrom'); const noCache: boolean = core.getBooleanInput('noCache'); const skipContainerUserIdUpdate = core.getBooleanInput(