Skip to content

Commit 007b9b2

Browse files
authored
Merge pull request #863 from SteveL-MSFT/parameters-stdin
Add support to read parameters from STDIN
2 parents 07e26f9 + 4af0eb4 commit 007b9b2

File tree

7 files changed

+93
-25
lines changed

7 files changed

+93
-25
lines changed

dsc/locales/en-us.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ resource = "The name of the resource to invoke"
3737
ctrlCReceived = "Ctrl-C received"
3838
failedCtrlCHandler = "Failed to set Ctrl-C handler"
3939
failedReadingParametersFile = "Failed to read parameters file"
40+
readingParametersFromStdin = "Reading parameters from STDIN"
4041
generatingCompleter = "Generating completion script for"
4142
readingParametersFile = "Reading parameters from file"
4243
usingDscVersion = "Running DSC version"
@@ -133,3 +134,4 @@ failedToAbsolutizePath = "Error making config path absolute"
133134
failedToGetParentPath = "Error reading config path parent"
134135
dscConfigRootAlreadySet = "The current value of DSC_CONFIG_ROOT env var will be overridden"
135136
settingDscConfigRoot = "Setting DSC_CONFIG_ROOT env var as"
137+
stdinNotAllowedForBothParametersAndInput = "Cannot read from STDIN for both parameters and input."

dsc/src/main.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use args::{Args, SubCommand};
55
use clap::{CommandFactory, Parser};
66
use clap_complete::generate;
77
use rust_i18n::{i18n, t};
8-
use std::{io, process::exit};
8+
use std::{io, io::Read, process::exit};
99
use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind};
1010
use tracing::{error, info, warn, debug};
1111
use dsc_lib::progress::ProgressFormat;
1212

13+
use crate::util::EXIT_INVALID_INPUT;
14+
1315
#[cfg(debug_assertions)]
1416
use crossterm::event;
1517
#[cfg(debug_assertions)]
@@ -51,17 +53,40 @@ fn main() {
5153
},
5254
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
5355
if let Some(file_name) = parameters_file {
56+
if file_name == "-" {
57+
info!("{}", t!("main.readingParametersFromStdin"));
58+
let mut stdin = Vec::<u8>::new();
59+
let parameters = match io::stdin().read_to_end(&mut stdin) {
60+
Ok(_) => {
61+
match String::from_utf8(stdin) {
62+
Ok(input) => {
63+
input
64+
},
65+
Err(err) => {
66+
error!("{}: {err}", t!("util.invalidUtf8"));
67+
exit(EXIT_INVALID_INPUT);
68+
}
69+
}
70+
},
71+
Err(err) => {
72+
error!("{}: {err}", t!("util.failedToReadStdin"));
73+
exit(EXIT_INVALID_INPUT);
74+
}
75+
};
76+
subcommand::config(&subcommand, &Some(parameters), true, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
77+
return;
78+
}
5479
info!("{}: {file_name}", t!("main.readingParametersFile"));
5580
match std::fs::read_to_string(&file_name) {
56-
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
81+
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
5782
Err(err) => {
5883
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
5984
exit(util::EXIT_INVALID_INPUT);
6085
}
6186
}
6287
}
6388
else {
64-
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
89+
subcommand::config(&subcommand, &parameters, false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
6590
}
6691
},
6792
SubCommand::Extension { subcommand } => {

dsc/src/subcommand.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,16 @@ fn initialize_config_root(path: Option<&String>) -> Option<String> {
271271
}
272272

273273
#[allow(clippy::too_many_lines)]
274-
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) {
274+
#[allow(clippy::too_many_arguments)]
275+
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, parameters_from_stdin: bool, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) {
275276
let (new_parameters, json_string) = match subcommand {
276277
ConfigSubCommand::Get { input, file, .. } |
277278
ConfigSubCommand::Set { input, file, .. } |
278279
ConfigSubCommand::Test { input, file, .. } |
279280
ConfigSubCommand::Validate { input, file, .. } |
280281
ConfigSubCommand::Export { input, file, .. } => {
281282
let new_path = initialize_config_root(file.as_ref());
282-
let document = get_input(input.as_ref(), new_path.as_ref());
283+
let document = get_input(input.as_ref(), new_path.as_ref(), parameters_from_stdin);
283284
if *as_include {
284285
let (new_parameters, config_json) = match get_contents(&document) {
285286
Ok((parameters, config_json)) => (parameters, config_json),
@@ -295,7 +296,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounte
295296
},
296297
ConfigSubCommand::Resolve { input, file, .. } => {
297298
let new_path = initialize_config_root(file.as_ref());
298-
let document = get_input(input.as_ref(), new_path.as_ref());
299+
let document = get_input(input.as_ref(), new_path.as_ref(), parameters_from_stdin);
299300
let (new_parameters, config_json) = match get_contents(&document) {
300301
Ok((parameters, config_json)) => (parameters, config_json),
301302
Err(err) => {
@@ -391,7 +392,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounte
391392
};
392393
if *as_include {
393394
let new_path = initialize_config_root(file.as_ref());
394-
let input = get_input(input.as_ref(), new_path.as_ref());
395+
let input = get_input(input.as_ref(), new_path.as_ref(), parameters_from_stdin);
395396
match serde_json::from_str::<Include>(&input) {
396397
Ok(_) => {
397398
// valid, so do nothing
@@ -582,14 +583,14 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
582583
},
583584
ResourceSubCommand::Export { resource, input, file, output_format } => {
584585
dsc.find_resources(&[resource.to_string()], progress_format);
585-
let parsed_input = get_input(input.as_ref(), file.as_ref());
586+
let parsed_input = get_input(input.as_ref(), file.as_ref(), false);
586587
resource_command::export(&mut dsc, resource, &parsed_input, output_format.as_ref());
587588
},
588589
ResourceSubCommand::Get { resource, input, file: path, all, output_format } => {
589590
dsc.find_resources(&[resource.to_string()], progress_format);
590591
if *all { resource_command::get_all(&dsc, resource, output_format.as_ref()); }
591592
else {
592-
let parsed_input = get_input(input.as_ref(), path.as_ref());
593+
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
593594
let format = match output_format {
594595
Some(GetOutputFormat::Json) => Some(OutputFormat::Json),
595596
Some(GetOutputFormat::JsonArray) => {
@@ -605,17 +606,17 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
605606
},
606607
ResourceSubCommand::Set { resource, input, file: path, output_format } => {
607608
dsc.find_resources(&[resource.to_string()], progress_format);
608-
let parsed_input = get_input(input.as_ref(), path.as_ref());
609+
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
609610
resource_command::set(&dsc, resource, &parsed_input, output_format.as_ref());
610611
},
611612
ResourceSubCommand::Test { resource, input, file: path, output_format } => {
612613
dsc.find_resources(&[resource.to_string()], progress_format);
613-
let parsed_input = get_input(input.as_ref(), path.as_ref());
614+
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
614615
resource_command::test(&dsc, resource, &parsed_input, output_format.as_ref());
615616
},
616617
ResourceSubCommand::Delete { resource, input, file: path } => {
617618
dsc.find_resources(&[resource.to_string()], progress_format);
618-
let parsed_input = get_input(input.as_ref(), path.as_ref());
619+
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
619620
resource_command::delete(&dsc, resource, &parsed_input);
620621
},
621622
}

dsc/src/util.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), D
426426
Ok(())
427427
}
428428

429-
pub fn get_input(input: Option<&String>, file: Option<&String>) -> String {
429+
pub fn get_input(input: Option<&String>, file: Option<&String>, parameters_from_stdin: bool) -> String {
430430
trace!("Input: {input:?}, File: {file:?}");
431431
let value = if let Some(input) = input {
432432
debug!("{}", t!("util.readingInput"));
@@ -442,6 +442,10 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String {
442442
// check if need to read from STDIN
443443
if path == "-" {
444444
info!("{}", t!("util.readingInputFromStdin"));
445+
if parameters_from_stdin {
446+
error!("{}", t!("util.stdinNotAllowedForBothParametersAndInput"));
447+
exit(EXIT_INVALID_INPUT);
448+
}
445449
let mut stdin = Vec::<u8>::new();
446450
match std::io::stdin().read_to_end(&mut stdin) {
447451
Ok(_) => {
@@ -535,13 +539,13 @@ pub fn set_dscconfigroot(config_path: &str) -> String
535539

536540

537541
/// Check if the test result is in the desired state.
538-
///
542+
///
539543
/// # Arguments
540-
///
544+
///
541545
/// * `test_result` - The test result to check
542-
///
546+
///
543547
/// # Returns
544-
///
548+
///
545549
/// * `bool` - True if the test result is in the desired state, false otherwise
546550
#[must_use]
547551
pub fn in_desired_state(test_result: &ResourceTestResult) -> bool {

dsc/tests/dsc_parameters.tests.ps1

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,43 @@ Describe 'Parameters tests' {
316316

317317
$output | Should -Match "Parameter input failure:.*?$type"
318318
}
319+
320+
It 'Parameters can be read from STDIN' {
321+
$params = @{
322+
parameters = @{
323+
osFamily = 'Windows'
324+
}
325+
} | ConvertTo-Json -Compress
326+
327+
$out = $params | dsc config -f - test -f "$PSScriptRoot/../examples/osinfo_parameters.dsc.yaml" 2> $TestDrive/error.log | ConvertFrom-Json
328+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log | Out-String)
329+
$out.results[0].result.desiredState.family | Should -BeExactly 'Windows'
330+
$out.results[0].result.inDesiredState | Should -Be $IsWindows
331+
}
332+
333+
It 'Parameters and input cannot both be from STDIN' {
334+
$params = @{
335+
parameters = @{
336+
osFamily = 'Windows'
337+
}
338+
} | ConvertTo-Json -Compress
339+
340+
$out = $params | dsc config -f - test -f - 2> $TestDrive/error.log
341+
$LASTEXITCODE | Should -Be 4
342+
$out | Should -BeNullOrEmpty
343+
$errorMessage = Get-Content -Path $TestDrive/error.log -Raw
344+
$errorMessage | Should -BeLike "*ERROR*Cannot read from STDIN for both parameters and input*"
345+
}
346+
347+
It 'Invalid parameters read from STDIN result in error' {
348+
$params = @{
349+
osFamily = 'Windows'
350+
} | ConvertTo-Json -Compress
351+
352+
$out = $params | dsc config -f - test -f "$PSScriptRoot/../examples/osinfo_parameters.dsc.yaml" 2> $TestDrive/error.log
353+
$LASTEXITCODE | Should -Be 4
354+
$out | Should -BeNullOrEmpty
355+
$errorMessage = Get-Content -Path $TestDrive/error.log -Raw
356+
$errorMessage | Should -BeLike "*ERROR*Parameter input failure: JSON: missing field ````parameters````*"
357+
}
319358
}

dsc/tests/dsc_tracing.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ Describe 'tracing tests' {
8282
level: trace
8383
"@
8484

85-
$out = (dsc -l $level config get -i $configYaml 2> $null) | ConvertFrom-Json
85+
$out = (dsc -l $level config get -i $configYaml 2> $TestDrive/error.log) | ConvertFrom-Json
86+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log | Out-String)
8687
$out.results[0].result.actualState.level | Should -BeExactly $level -Because ($out | Out-String)
8788
}
8889

@@ -91,7 +92,6 @@ Describe 'tracing tests' {
9192
$out = dsc -l info -t pass-through config get -f ../examples/groups.dsc.yaml 2> $logPath
9293
foreach ($line in (Get-Content $logPath)) {
9394
$line | Should -Not -BeNullOrEmpty
94-
Write-Verbose -Verbose $line
9595
$json = $line | ConvertFrom-Json
9696
$json.timestamp | Should -Not -BeNullOrEmpty
9797
$json.level | Should -BeIn 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'

tools/dsctest/dsctrace.dsc.resource.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,19 @@
66
"executable": "dsctest",
77
"args": [
88
"trace"
9-
],
10-
"input": "stdin"
9+
]
1110
},
1211
"set": {
1312
"executable": "dsctest",
1413
"args": [
1514
"trace"
16-
],
17-
"input": "stdin"
15+
]
1816
},
1917
"test": {
2018
"executable": "dsctest",
2119
"args": [
2220
"trace"
23-
],
24-
"input": "stdin"
21+
]
2522
},
2623
"schema": {
2724
"command": {

0 commit comments

Comments
 (0)