Skip to content

Commit dab10d1

Browse files
committed
retrieve exit code info from manifest if available
1 parent b26a223 commit dab10d1

File tree

7 files changed

+100
-11
lines changed

7 files changed

+100
-11
lines changed

dsc/tests/dsc.exit_code.tests.ps1

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'exit code tests' {
5+
It 'non-zero exit code in manifest has corresponding message' {
6+
$result = dsc resource get -r Test/ExitCode --input "{ exitCode: 8 }" 2>&1
7+
$result | Should -Match 'ERROR.*?[Exit code 8].*?Placeholder from manifest for exit code 8'
8+
}
9+
It 'non-zero exit code not in manifest has generic message' {
10+
$result = dsc resource get -r Test/ExitCode --input "{ exitCode: 1 }" 2>&1
11+
$result | Should -Match 'ERROR.*?Error.*?[Exit code 1]'
12+
}
13+
It 'success exit code executes without error' {
14+
$result = dsc resource get -r Test/ExitCode --input "{ exitCode: 0 }" | ConvertFrom-Json
15+
$result.actualState.exitCode | Should -Be 0
16+
$LASTEXITCODE | Should -Be 0
17+
}
18+
}

dsc_lib/src/discovery/command_discovery.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl ResourceDiscovery for CommandDiscovery {
221221
let mut adapter_resources_count = 0;
222222
// invoke the list command
223223
let list_command = manifest.adapter.unwrap().list;
224-
let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&adapter.directory), None)
224+
let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&adapter.directory), None, &manifest.exit_codes)
225225
{
226226
Ok((exit_code, stdout, stderr)) => (exit_code, stdout, stderr),
227227
Err(e) => {

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
5757
}
5858

5959
info!("Invoking get '{}' using '{}'", &resource.resource_type, &get.executable);
60-
let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
60+
let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
6161
if resource.kind == Some(Kind::Resource) {
6262
debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable);
6363
verify_json(resource, cwd, &stdout)?;
@@ -142,7 +142,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
142142
let command_input = get_command_input(&get.input, desired)?;
143143

144144
info!("Getting current state for set by invoking get {} using {}", &resource.resource_type, &get.executable);
145-
let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
145+
let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
146146

147147
if resource.kind == Some(Kind::Resource) {
148148
debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable);
@@ -172,7 +172,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
172172
}
173173

174174
info!("Invoking set '{}' using '{}'", &resource.resource_type, &set.executable);
175-
let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env)?;
175+
let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env, &resource.exit_codes)?;
176176

177177
match set.returns {
178178
Some(ReturnKind::State) => {
@@ -265,7 +265,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
265265
let command_input = get_command_input(&test.input, expected)?;
266266

267267
info!("Invoking test '{}' using '{}'", &resource.resource_type, &test.executable);
268-
let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
268+
let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
269269

270270
if resource.kind == Some(Kind::Resource) {
271271
debug!("Verifying output of test '{}' using '{}'", &resource.resource_type, &test.executable);
@@ -379,7 +379,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
379379
let command_input = get_command_input(&delete.input, filter)?;
380380

381381
info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable);
382-
let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
382+
let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
383383

384384
Ok(())
385385
}
@@ -410,7 +410,7 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) ->
410410
let command_input = get_command_input(&validate.input, config)?;
411411

412412
info!("Invoking validate '{}' using '{}'", &resource.resource_type, &validate.executable);
413-
let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
413+
let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
414414
let result: ValidateResult = serde_json::from_str(&stdout)?;
415415
Ok(result)
416416
}
@@ -431,7 +431,7 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
431431

432432
match schema_kind {
433433
SchemaKind::Command(ref command) => {
434-
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None)?;
434+
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None, &resource.exit_codes)?;
435435
Ok(stdout)
436436
},
437437
SchemaKind::Embedded(ref schema) => {
@@ -486,7 +486,7 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
486486
args = process_args(&export.args, "");
487487
}
488488

489-
let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
489+
let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
490490
let mut instances: Vec<Value> = Vec::new();
491491
for line in stdout.lines()
492492
{
@@ -532,7 +532,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re
532532
let command_input = get_command_input(&resolve.input, input)?;
533533

534534
info!("Invoking resolve '{}' using '{}'", &resource.resource_type, &resolve.executable);
535-
let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
535+
let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
536536
let result: ResolveResult = serde_json::from_str(&stdout)?;
537537
Ok(result)
538538
}
@@ -550,7 +550,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re
550550
///
551551
/// Error is returned if the command fails to execute or stdin/stdout/stderr cannot be opened.
552552
#[allow(clippy::implicit_hasher)]
553-
pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option<&str>, cwd: Option<&str>, env: Option<HashMap<String, String>>) -> Result<(i32, String, String), DscError> {
553+
pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option<&str>, cwd: Option<&str>, env: Option<HashMap<String, String>>, exit_codes: &Option<HashMap<i32, String>>) -> Result<(i32, String, String), DscError> {
554554
debug!("Invoking command '{}' with args {:?}", executable, args);
555555
let mut command = Command::new(executable);
556556
if input.is_some() {
@@ -614,6 +614,11 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
614614
};
615615

616616
if exit_code != 0 {
617+
if let Some(exit_codes) = exit_codes {
618+
if let Some(error_message) = exit_codes.get(&exit_code) {
619+
return Err(DscError::Command(executable.to_string(), exit_code, error_message.to_string()));
620+
}
621+
}
617622
return Err(DscError::Command(executable.to_string(), exit_code, cleaned_stderr));
618623
}
619624

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
3+
"type": "Test/ExitCode",
4+
"version": "0.1.0",
5+
"get": {
6+
"executable": "dsctest",
7+
"args": [
8+
"exit-code",
9+
{
10+
"jsonInputArg": "--input"
11+
}
12+
]
13+
},
14+
"exitCodes": {
15+
"0": "Success",
16+
"8": "Placeholder from manifest for exit code 8"
17+
},
18+
"schema": {
19+
"command": {
20+
"executable": "dsctest",
21+
"args": [
22+
"schema",
23+
"-s",
24+
"exit-code"
25+
]
26+
}
27+
}
28+
}

tools/dsctest/src/args.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum Schemas {
88
Delete,
99
Echo,
1010
Exist,
11+
ExitCode,
1112
Sleep,
1213
Trace,
1314
}
@@ -40,6 +41,12 @@ pub enum SubCommand {
4041
input: String,
4142
},
4243

44+
#[clap(name = "exit-code", about = "Return the exit code specified in the input")]
45+
ExitCode {
46+
#[clap(name = "input", short, long, help = "The input to the exit code command as JSON")]
47+
input: String,
48+
},
49+
4350
#[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
4451
Schema {
4552
#[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]

tools/dsctest/src/exit_code.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use schemars::JsonSchema;
5+
use serde::{Deserialize, Serialize};
6+
7+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
8+
#[serde(deny_unknown_fields)]
9+
pub struct ExitCode {
10+
#[serde(rename = "exitCode")]
11+
pub exit_code: i32,
12+
}

tools/dsctest/src/main.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod args;
55
mod delete;
66
mod echo;
77
mod exist;
8+
mod exit_code;
89
mod sleep;
910
mod trace;
1011

@@ -14,6 +15,7 @@ use schemars::schema_for;
1415
use crate::delete::Delete;
1516
use crate::echo::Echo;
1617
use crate::exist::{Exist, State};
18+
use crate::exit_code::ExitCode;
1719
use crate::sleep::Sleep;
1820
use crate::trace::Trace;
1921
use std::{thread, time::Duration};
@@ -58,6 +60,20 @@ fn main() {
5860

5961
serde_json::to_string(&exist).unwrap()
6062
},
63+
SubCommand::ExitCode { input } => {
64+
let exit_code = match serde_json::from_str::<ExitCode>(&input) {
65+
Ok(exit_code) => exit_code,
66+
Err(err) => {
67+
eprintln!("Error JSON does not match schema: {err}");
68+
std::process::exit(1);
69+
}
70+
};
71+
if exit_code.exit_code != 0 {
72+
eprintln!("Exiting with code: {}", exit_code.exit_code);
73+
std::process::exit(exit_code.exit_code);
74+
}
75+
input
76+
},
6177
SubCommand::Schema { subcommand } => {
6278
let schema = match subcommand {
6379
Schemas::Delete => {
@@ -69,6 +85,9 @@ fn main() {
6985
Schemas::Exist => {
7086
schema_for!(Exist)
7187
},
88+
Schemas::ExitCode => {
89+
schema_for!(ExitCode)
90+
},
7291
Schemas::Sleep => {
7392
schema_for!(Sleep)
7493
},

0 commit comments

Comments
 (0)