diff --git a/src/spec-node/featuresCLI/test.ts b/src/spec-node/featuresCLI/test.ts index 8909b2540..8b0f2c569 100644 --- a/src/spec-node/featuresCLI/test.ts +++ b/src/spec-node/featuresCLI/test.ts @@ -20,6 +20,7 @@ export function featuresTestOptions(y: Argv) { 'remote-user': { type: 'string', alias: 'u', describe: 'Remote user. Not used for scenarios.', }, // TODO: Optionally replace 'scenario' configs with this value? 'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level.' }, 'skip-image-metadata': { type: 'boolean', default: false, description: 'Skip applying image metadata to the resultant test image. Image metadata is potentially appended to base images built with the dev container CLI' }, + 'remote-env': { type: 'string', description: 'Remote environment variables of the format name=value. These will be added when executing the user commands.' }, 'quiet': { type: 'boolean', alias: 'q', default: false, description: 'Quiets output' }, }) // DEPRECATED: Positional arguments don't play nice with the variadic/array --features option. @@ -39,6 +40,10 @@ export function featuresTestOptions(y: Argv) { if (argv['filter-scenarios'] && argv['skip-scenarios']) { throw new Error('Cannot combine --filter-scenarios and --skip-scenarios'); } + const remoteEnvs = (argv['remote-env'] && (Array.isArray(argv['remote-env']) ? argv['remote-env'] : [argv['remote-env']])) as string[] | undefined; + if (remoteEnvs?.some(remoteEnv => !/.+=.+/.test(remoteEnv))) { + throw new Error('Unmatched argument format: remote-env must match ='); + } return true; }); @@ -56,6 +61,7 @@ export interface FeaturesTestCommandInput { skipScenarios: boolean; skipAutogenerated: boolean; remoteUser: string | undefined; + remoteEnv: string[] | undefined; quiet: boolean; skipImageMetadata: boolean; logLevel: LogLevel; @@ -76,6 +82,7 @@ async function featuresTest({ 'skip-scenarios': skipScenarios, 'skip-autogenerated': skipAutogenerated, 'remote-user': remoteUser, + 'remote-env': remoteEnvString, quiet, 'skip-image-metadata': skipImageMetadata, 'log-level': inputLogLevel, @@ -94,6 +101,8 @@ async function featuresTest({ // Prefer the new --project-folder option over the deprecated positional argument. const targetProject = collectionFolder !== '.' ? collectionFolder : collectionFolder_deprecated; + const remoteEnv = (remoteEnvString && (Array.isArray(remoteEnvString) ? remoteEnvString : [remoteEnvString])) as string[] | undefined; + const args: FeaturesTestCommandInput = { baseImage, cliHost, @@ -108,9 +117,11 @@ async function featuresTest({ skipAutogenerated, remoteUser, skipImageMetadata, + remoteEnv, disposables }; + const exitCode = await doFeaturesTestCommand(args); await dispose(); diff --git a/src/spec-node/featuresCLI/testCommandImpl.ts b/src/spec-node/featuresCLI/testCommandImpl.ts index a3a6b4a5b..d4a6017dc 100644 --- a/src/spec-node/featuresCLI/testCommandImpl.ts +++ b/src/spec-node/featuresCLI/testCommandImpl.ts @@ -6,7 +6,7 @@ import { CLIHost } from '../../spec-common/cliHost'; import { launch, ProvisionOptions, createDockerParams } from '../devContainers'; import { doExec } from '../devContainersSpecCLI'; import { LaunchResult, staticExecParams, staticProvisionParams, testLibraryScript } from './utils'; -import { DockerResolverParameters } from '../utils'; +import { DockerResolverParameters, envListToObj } from '../utils'; import { DevContainerConfig } from '../../spec-configuration/configuration'; import { FeaturesTestCommandInput } from './test'; import { cpDirectoryLocal, rmLocal } from '../../spec-utils/pfs'; @@ -485,7 +485,7 @@ async function generateDockerParams(workspaceFolder: string, args: FeaturesTestC persistedFolder: undefined, additionalMounts: [], updateRemoteUserUIDDefault: 'never', - remoteEnv: {}, + remoteEnv: envListToObj(args.remoteEnv), additionalCacheFroms: [], omitLoggerHeader: true, useBuildKit: 'auto', diff --git a/src/test/container-features/featuresCLICommands.test.ts b/src/test/container-features/featuresCLICommands.test.ts index 37b829197..7c4c8d727 100644 --- a/src/test/container-features/featuresCLICommands.test.ts +++ b/src/test/container-features/featuresCLICommands.test.ts @@ -443,6 +443,27 @@ describe('CLI features subcommands', async function () { const hasExpectedTestReport = result.stdout.includes(expectedTestReport); assert.isTrue(hasExpectedTestReport); }); + + it('lifecycle-hooks-remote-env', async function () { + const collectionFolder = `${__dirname}/example-v2-features-sets/lifecycle-hooks-remote-env`; + let success = false; + let result: ExecResult | undefined = undefined; + try { + result = await shellExec(`${cli} features test --project-folder ${collectionFolder} --remote-env MY_SECRET=you-found-my-secret-string --log-level trace `); + success = true; + + } catch (error) { + assert.fail('features test sub-command should not throw'); + } + + assert.isTrue(success); + assert.isDefined(result); + + const expectedTestReport = ` ================== TEST REPORT ================== +✅ Passed: 'puppy'`; + const hasExpectedTestReport = result.stdout.includes(expectedTestReport); + assert.isTrue(hasExpectedTestReport); + }); }); describe('features package', function () {