Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/rust-port-convergence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ jobs:
sudo apt-get "${apt_options[@]}" update
sudo apt-get "${apt_options[@]}" install -y podman podman-compose

- name: Set up Compose build workaround
if: matrix.runner == 'docker'
uses: ./upstream/.github/actions/setup-compose

- name: Runtime version
run: |
if [[ "${{ matrix.runner }}" == "podman" ]]; then
Expand Down
4 changes: 2 additions & 2 deletions build/generate-command-matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function parseOptionNames(functionName, sourceCache, importMap) {
}

const names = new Set();
const objectOptionRegex = /'([^']+)':\s*\{/g;
const objectOptionRegex = /'([^']+)':\s*(?:\{|[A-Za-z_$][\w$]*\s*[,}])/g;
let match;
while ((match = objectOptionRegex.exec(block)) !== null) {
names.add(match[1]);
Expand Down Expand Up @@ -180,7 +180,7 @@ function generateCommandMatrix() {
const subcommands = commands.filter(command => command.group);

return {
upstreamCommit: runGit(['rev-parse', 'HEAD:upstream']),
upstreamCommit: runGit(['-C', 'upstream', 'rev-parse', 'HEAD']),
sourcePath: path.relative(repositoryRoot, upstreamCliPath),
globalOptions: parseGlobalOptionNames(source),
topLevel,
Expand Down
8 changes: 8 additions & 0 deletions build/test-parity-artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ const path = require('node:path');
const test = require('node:test');

const { buildInventory } = require('./generate-parity-inventory');
const { generateCommandMatrix } = require('./generate-command-matrix');

const repositoryRoot = path.join(__dirname, '..');
const coverageMapPath = path.join(repositoryRoot, 'docs', 'upstream', 'test-coverage-map.json');

test('shared upstream Docker argument definitions appear in every supported command', () => {
const matrix = generateCommandMatrix();
for (const command of ['up', 'set-up', 'build', 'run-user-commands', 'read-configuration', 'exec', 'upgrade']) {
assert.ok(matrix.commands.find(entry => entry.path === command)?.options.includes('docker-arg'), command);
}
});

test('generated CLI metadata is not accepted as native option evidence', () => {
const inventory = buildInventory();
const evidence = [
Expand Down
132 changes: 96 additions & 36 deletions cmd/devcontainer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cmd/devcontainer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ base64 = "0.22"
flate2 = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
saphyr = { version = "0.3.3", default-features = false }
sha2 = "0.10"
tar = "0.4"

Expand Down
83 changes: 83 additions & 0 deletions cmd/devcontainer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ pub(crate) fn parse_global_options(args: &[String]) -> Result<ParsedGlobalOption
break;
}

if arg == "--docker-arg" {
if let Some(value) = args.get(index + 1).filter(|value| *value != "--") {
command_line.push(format!("--docker-arg={value}"));
index += 2;
continue;
}
}

if let Some(value) = arg.strip_prefix("--oci-auth-hardening=") {
let value = parse_global_bool_value(value)?;
set_oci_auth_hardening(&mut hardening_value, value)?;
Expand Down Expand Up @@ -412,6 +420,17 @@ pub(crate) fn normalize_option_aliases(command_path: &str, args: &[String]) -> V
};
let option = find_command_option(command, flag, short_alias);
if let Some(option) = option {
if option.name == "docker-arg" {
if let Some(value) = inline_value.or_else(|| {
args.get(index + 1)
.map(String::as_str)
.filter(|value| *value != "--")
}) {
normalized.push(format!("--docker-arg={value}"));
index += if inline_value.is_some() { 1 } else { 2 };
continue;
}
}
normalized.push(format!("--{}", option.name));
if let Some(value) = inline_value {
normalized.push(value.to_string());
Expand Down Expand Up @@ -605,6 +624,12 @@ fn unsupported_argument_error_for(
}

let next = args.get(index + 1);
if option.name == "docker-arg"
&& !arg.contains('=')
&& next.is_none_or(|value| value == "--")
{
return Some("Missing value for option: --docker-arg".to_string());
}
if !arg.contains('=') && option.takes_multiple_values() {
index += 1;
while args
Expand Down Expand Up @@ -636,6 +661,64 @@ mod tests {
CommandPositional, HelpLine, OciAuthOptions, ParsedGlobalOptions,
};

/// Preserve literal Docker arguments through global parsing and option normalization.
#[test]
fn docker_argument_values_are_not_devcontainer_options() {
for command in [
"up",
"set-up",
"build",
"run-user-commands",
"read-configuration",
"exec",
"upgrade",
] {
let parsed = parse_global_options(&[
command.into(),
"--docker-arg".into(),
"--oci-auth-hardening".into(),
"--docker-arg=--".into(),
"--docker-arg=".into(),
"--docker-arg=001".into(),
])
.expect("literal Docker values");
assert!(!parsed.oci_auth.hardening);
let normalized = normalize_option_aliases(command, &parsed.command_line[1..]);
assert_eq!(
normalized,
[
"--docker-arg=--oci-auth-hardening",
"--docker-arg=--",
"--docker-arg=",
"--docker-arg=001"
]
);
assert_eq!(unsupported_argument_error(command, &normalized), None);
assert_eq!(
unsupported_argument_error(command, &["--docker-arg".into()]),
Some("Missing value for option: --docker-arg".into())
);
assert_eq!(
unsupported_argument_error(command, &["--docker-arg".into(), "--".into()]),
Some("Missing value for option: --docker-arg".into())
);
let missing = normalize_option_aliases(command, &["--docker-arg".into(), "--".into()]);
assert_eq!(
unsupported_argument_error(command, &missing),
Some("Missing value for option: --docker-arg".into())
);
let missing = normalize_option_aliases(command, &["--docker-arg".into()]);
assert_eq!(
unsupported_argument_error(command, &missing),
Some("Missing value for option: --docker-arg".into())
);
let parsed =
parse_global_options(&[command.into(), "--docker-arg".into(), "--".into()])
.expect("missing argument is validated later");
assert_eq!(parsed.command_line, [command, "--docker-arg", "--"]);
}
}

#[test]
fn detects_subcommand_help_requests() {
assert!(is_command_help_request(&["--help".to_string()]));
Expand Down
Loading
Loading