Skip to content

Commit

Permalink
[features test cmd] Respect image label metadata (#288)
Browse files Browse the repository at this point in the history
* respect experimentalImageMetadata labels in the devcontainer feature test cmd

* type-check

* update existing tests to support image metadata

* remove unused feature

* flip condition and rename to --skip-image-metadata

* update tests
  • Loading branch information
joshspicer authored Nov 18, 2022
1 parent add354f commit 790aa75
Show file tree
Hide file tree
Showing 21 changed files with 222 additions and 45 deletions.
8 changes: 6 additions & 2 deletions src/spec-node/featuresCLI/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export function featuresTestOptions(y: Argv) {
'skip-scenarios': { type: 'boolean', default: false, description: 'Skip all \'scenario\' style tests. Cannot be combined with \'--global--scenarios-only\'.' },
'skip-autogenerated': { type: 'boolean', default: false, description: 'Skip all \'autogenerated\' style tests.' },
'base-image': { type: 'string', alias: 'i', default: 'ubuntu:focal', description: 'Base Image. Not used for scenarios.' }, // TODO: Optionally replace 'scenario' configs with this value?
'remote-user': { type: 'string', alias: 'u', default: 'root', describe: 'Remote user. Not used for scenarios.', }, // TODO: Optionally replace 'scenario' configs with this value?
'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' },
'quiet': { type: 'boolean', alias: 'q', default: false, description: 'Quiets output' },
})
// DEPRECATED: Positional arguments don't play nice with the variadic/array --features option.
Expand Down Expand Up @@ -54,8 +55,9 @@ export interface FeaturesTestCommandInput {
globalScenariosOnly: boolean;
skipScenarios: boolean;
skipAutogenerated: boolean;
remoteUser: string;
remoteUser: string | undefined;
quiet: boolean;
skipImageMetadata: boolean;
logLevel: LogLevel;
disposables: (() => Promise<unknown> | undefined)[];
}
Expand All @@ -75,6 +77,7 @@ async function featuresTest({
'skip-autogenerated': skipAutogenerated,
'remote-user': remoteUser,
quiet,
'skip-image-metadata': skipImageMetadata,
'log-level': inputLogLevel,
}: FeaturesTestArgs) {
const disposables: (() => Promise<unknown> | undefined)[] = [];
Expand Down Expand Up @@ -104,6 +107,7 @@ async function featuresTest({
skipScenarios,
skipAutogenerated,
remoteUser,
skipImageMetadata,
disposables
};

Expand Down
39 changes: 22 additions & 17 deletions src/spec-node/featuresCLI/testCommandImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ async function runFeatureTests(args: FeaturesTestCommandInput, testResults: Test
fail('Uninitialized workspaceFolder or params');
return [];
}
await doRunAutoTest(feature, workspaceFolder, params, featureTestFolder, args, testResults);
await doRunAutoTest(feature, workspaceFolder, featureTestFolder, args, testResults);
}

// If there is a feature-scoped 'scenarios.json' with additional tests, also exec those.
Expand All @@ -157,7 +157,7 @@ async function runFeatureTests(args: FeaturesTestCommandInput, testResults: Test
return testResults;
}

async function doRunAutoTest(feature: string, workspaceFolder: string, params: DockerResolverParameters, featureTestFolder: string, args: FeaturesTestCommandInput, testResults: TestResult[] = []): Promise<TestResult[]> {
async function doRunAutoTest(feature: string, workspaceFolder: string, featureTestFolder: string, args: FeaturesTestCommandInput, testResults: TestResult[] = []): Promise<TestResult[]> {
const { cliHost } = args;
const testScriptPath = path.join(featureTestFolder, 'test.sh');
if (!(await cliHost.isFile(testScriptPath))) {
Expand All @@ -171,7 +171,7 @@ async function doRunAutoTest(feature: string, workspaceFolder: string, params: D
await cliHost.writeFile(path.join(workspaceFolder, TEST_LIBRARY_SCRIPT_NAME), Buffer.from(testLibraryScript));

// Execute Test
const result = await execTest(params, 'test.sh', workspaceFolder, cliHost);
const result = await execTest(args, 'test.sh', workspaceFolder, cliHost);
testResults.push({
testName: feature,
result,
Expand Down Expand Up @@ -232,7 +232,7 @@ async function doScenario(pathToTestDir: string, targetFeatureOrGlobal: string,
// Execute Test
testResults.push({
testName: scenarioName,
result: await execTest(params, `${scenarioName}.sh`, workspaceFolder, cliHost)
result: await execTest(args, `${scenarioName}.sh`, workspaceFolder, cliHost)
});
}
return testResults;
Expand Down Expand Up @@ -260,11 +260,11 @@ function analyzeTestResults(testResults: { testName: string; result: boolean }[]

const devcontainerTemplate = `
{
#{REMOTE_USER}
"image": "#{IMAGE}",
"features": {
#{FEATURES}
},
"remoteUser": "#{REMOTE_USER}"
}
}`;

async function createContainerFromWorkingDirectory(params: DockerResolverParameters, workspaceFolder: string, args: FeaturesTestCommandInput): Promise<LaunchResult | undefined> {
Expand All @@ -273,7 +273,7 @@ async function createContainerFromWorkingDirectory(params: DockerResolverParamet

// 2. Use 'devcontainer-cli up' to build and start a container
log('Building test container...\n', { prefix: '\n⏳', info: true });
const launchResult: LaunchResult | undefined = await launchProject(params, workspaceFolder, quiet, disposables);
const launchResult: LaunchResult | undefined = await launchProject(params, args, workspaceFolder, quiet, disposables);
if (!launchResult || !launchResult.containerId) {
fail('Failed to launch container');
return;
Expand All @@ -299,7 +299,7 @@ async function generateDefaultProjectFromFeatures(
baseImage: string,
collectionsDirectory: string,
featuresToTest: string[],
remoteUser: string
remoteUser: string | undefined
): Promise<string> {
const tmpFolder = await createTempDevcontainerFolder(cliHost);

Expand All @@ -315,8 +315,13 @@ async function generateDefaultProjectFromFeatures(

let template = devcontainerTemplate
.replace('#{IMAGE}', baseImage)
.replace('#{FEATURES}', features)
.replace('#{REMOTE_USER}', remoteUser);
.replace('#{FEATURES}', features);

if (remoteUser) {
template = template.replace('#{REMOTE_USER}', `"remoteUser": "${remoteUser}",`);
} else {
template = template.replace('#{REMOTE_USER}', '');
}

await cliHost.writeFile(`${tmpFolder}/.devcontainer/devcontainer.json`, Buffer.from(template));

Expand Down Expand Up @@ -374,7 +379,7 @@ async function generateProjectFromScenario(
return tmpFolder;
}

async function launchProject(params: DockerResolverParameters, workspaceFolder: string, quiet: boolean, disposables: (() => Promise<unknown> | undefined)[]): Promise<LaunchResult> {
async function launchProject(params: DockerResolverParameters, args: FeaturesTestCommandInput, workspaceFolder: string, quiet: boolean, disposables: (() => Promise<unknown> | undefined)[]): Promise<LaunchResult> {
const { common } = params;
let response = {} as LaunchResult;

Expand All @@ -388,7 +393,7 @@ async function launchProject(params: DockerResolverParameters, workspaceFolder:
],
remoteEnv: common.remoteEnv,
skipFeatureAutoMapping: common.skipFeatureAutoMapping,
experimentalImageMetadata: common.experimentalImageMetadata,
experimentalImageMetadata: !args.skipImageMetadata,
skipPersistingCustomizationsFromFeatures: common.skipPersistingCustomizationsFromFeatures,
log: text => quiet ? null : process.stderr.write(text),
};
Expand Down Expand Up @@ -423,22 +428,22 @@ async function launchProject(params: DockerResolverParameters, workspaceFolder:
}
}

async function execTest(params: DockerResolverParameters, testFileName: string, workspaceFolder: string, cliHost: CLIHost) {
async function execTest(testCommandArgs: FeaturesTestCommandInput, testFileName: string, workspaceFolder: string, cliHost: CLIHost) {
// Ensure all the tests scripts in the workspace folder are executable
// Update permissions on the copied files to make them readable/writable/executable by everyone
await cliHost.exec({ cmd: 'chmod', args: ['-R', '777', workspaceFolder], output: nullLog });

const cmd = `./${testFileName}`;
const args: string[] = [];
return await exec(params, cmd, args, workspaceFolder);
return await exec(testCommandArgs, cmd, args, workspaceFolder);
}

async function exec(_params: DockerResolverParameters, cmd: string, args: string[], workspaceFolder: string) {
async function exec(testCommandArgs: FeaturesTestCommandInput, cmd: string, args: string[], workspaceFolder: string) {
const execArgs = {
...staticExecParams,
'workspace-folder': workspaceFolder,
'skip-feature-auto-mapping': false,
'experimental-image-metadata': false,
'experimental-image-metadata': !testCommandArgs.skipImageMetadata,
cmd,
args,
_: [
Expand Down Expand Up @@ -485,7 +490,7 @@ async function generateDockerParams(workspaceFolder: string, args: FeaturesTestC
buildxOutput: undefined,
skipFeatureAutoMapping: false,
skipPostAttach: false,
experimentalImageMetadata: false,
skipPersistingCustomizationsFromFeatures: false,
experimentalImageMetadata: !args.skipImageMetadata,
}, disposables);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "hey",
"version": "0.0.16",
"name": "A hey world feature",
"options": {
"greeting": {
"type": "string",
"default": "Good day",
"description": "Select a pre-made greeting, or enter your own"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
set -e

echo "Activating feature 'localFeatureA'"

GREETING=${GREETING:-undefined}
echo "The provided greeting is: $GREETING"

cat > /usr/local/bin/hello \
<< EOF
#!/bin/sh
RED='\033[0;91m'
NC='\033[0m' # No Color
echo "\${RED}${GREETING}, \$(whoami)\${NC}"
EOF

chmod +x /usr/local/bin/hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Definition specific tests
check "runHelloCmd" hello

# Passed in by --remote-user flag, that overrides the base image's user of 'vscode'
# The 'simple' feature set tests the opposite.
check "ensure i am user root" bash -c "whoami | grep 'root'"

# Report result
reportResults

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -e

echo "Activating feature 'whoisremoteuser'"
echo "Activating feature 'whoisremoteuser'..."

cat > /usr/local/bin/whoisremoteuser \
<< EOF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ set -e
# Optional: Import test library
source dev-container-features-test-lib

# Definition specific tests
check "ensure i am user codespace" bash -c "whoami | grep 'codespace'"

check "_REMOTE_USER was equal to codespace" bash -c "whoisremoteuser | grep 'codespace'"


# Report result
reportResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

check "ensure i am user root" bash -c "whoami | grep 'root'"

check "_REMOTE_USER was equal to root" bash -c "whoisremoteuser | grep 'root'"

# Report result
reportResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

check "ensure i am user node" bash -c "whoami | grep 'node'"

check "_REMOTE_USER was equal to node" bash -c "whoisremoteuser | grep 'node'"

# Report result
reportResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"add_with_common_utils": {
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"remoteUser": "codespace",
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"installZsh": false,
"installOhMyZsh": false,
"upgradePackages": false,
"username": "codespace"
},
"whoisremoteuser": {}
}
},
"from_image_metadata_label_flag_enabled": {
"image": "mcr.microsoft.com/devcontainers/javascript-node:18",
"features": {
"whoisremoteuser": {}
}
},
"from_image_metadata_label_flag_disabled": {
"image": "mcr.microsoft.com/devcontainers/javascript-node:18",
"features": {
"whoisremoteuser": {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ source dev-container-features-test-lib
check "correct color" color | grep "Magenta"
check "correct greeting" hello | grep "Ciao"

check "ensure i am user vscode" bash -c "whoami | grep 'vscode'"

# Report result
reportResults
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"custom_options": {
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features" : {
"hello": {
"greeting": "Ciao"
Expand All @@ -11,7 +11,7 @@
}
},
"with_external_feature": {
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"hello": {
"greeting": "How ya doing"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ source dev-container-features-test-lib
check "correct color" color | grep "Silver"
check "correct greeting" hello | grep "How ya doing"

check "ensure i am user vscode" bash -c "whoami | grep 'vscode'"

# Report result
reportResults
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"specific_color_scenario": {
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"color": {
"favorite": "green"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ source dev-container-features-test-lib
# Definition specific tests
check "correct color" color | grep "green"

check "ensure i am user vscode" bash -c "whoami | grep 'vscode'"

# Report result
reportResults
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ source dev-container-features-test-lib
# Definition specific tests
check "runColorCmd" color

check "ensure i am user vscode" bash -c "whoami | grep 'vscode'"

# Report result
reportResults
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ source dev-container-features-test-lib
# Definition specific tests
check "runHelloCmd" hello

check "ensure i am user vscode" bash -c "whoami | grep 'vscode'"

# Report result
reportResults
Loading

0 comments on commit 790aa75

Please sign in to comment.