Skip to content

Commit 3703da2

Browse files
authored
Merge pull request #676 from SteveL-MSFT/indesiredstate
Fix when resource test returns '_inDesiredState', that takes precedence
2 parents 9550bd8 + ddc3f4a commit 3703da2

File tree

7 files changed

+116
-3
lines changed

7 files changed

+116
-3
lines changed

dsc/tests/dsc_config_test.tests.ps1

+30
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,34 @@ Describe 'dsc config test tests' {
3232
$out.results[0].result.differingProperties | Should -Contain 'resources'
3333
}
3434
}
35+
36+
It '_inDesiredState returned is used when: inDesiredState = <inDesiredState> and same = <same>' -TestCases @(
37+
@{ inDesiredState = $true; valueOne = 1; valueTwo = 2; same = $true }
38+
@{ inDesiredState = $true; valueOne = 3; valueTwo = 4; same = $false }
39+
@{ inDesiredState = $false; valueOne = 1; valueTwo = 2; same = $true }
40+
@{ inDesiredState = $false; valueOne = 3; valueTwo = 4; same = $false }
41+
) {
42+
param($inDesiredState, $valueOne, $valueTwo)
43+
44+
$configYaml = @"
45+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
46+
resources:
47+
- name: Test
48+
type: Test/InDesiredState
49+
properties:
50+
_inDesiredState: $inDesiredState
51+
valueOne: $valueOne
52+
valueTwo: $valueTwo
53+
"@
54+
55+
$out = dsc config test -i $configYaml | ConvertFrom-Json
56+
$LASTEXITCODE | Should -Be 0
57+
$out.results[0].result.inDesiredState | Should -Be $inDesiredState
58+
if ($same) {
59+
$out.results[0].result.differingProperties | Should -BeNullOrEmpty
60+
}
61+
else {
62+
$out.results[0].result.differingProperties | Should -Be @('valueOne', 'valueTwo')
63+
}
64+
}
3565
}

dsc_lib/locales/en-us.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ setUnexpectedOutput = "Command did not return expected actual output"
9999
setUnexpectedDiff = "Command did not return expected diff output"
100100
invokeTest = "Invoking test for '%{resource}'"
101101
testSyntheticTest = "Resource '%{resource}' does not implement test, performing synthetic test"
102-
invokeTestUsing = "Invoking test on '%{resource}' using '{executable}'"
102+
invokeTestUsing = "Invoking test on '%{resource}' using '%{executable}'"
103103
testVerifyOutput = "Verifying output of test on '%{resource}' using '%{executable}'"
104104
testGroupTestResponse = "Import resource kind, returning group test response"
105105
testNoActualState = "No actual state returned"
@@ -129,6 +129,7 @@ validateJson = "Validating against JSON: %{json}"
129129
resourceInvalidJson = "Resource reported input JSON is not valid"
130130
invalidArrayKey = "Unsupported array value for key '%{key}'. Only string and number is supported."
131131
invalidKey = "Unsupported value for key '%{key}'. Only string, bool, number, and array is supported."
132+
inDesiredStateNotBool = "'_inDesiredState' is not a boolean"
132133

133134
[dscresources.dscresource]
134135
invokeGet = "Invoking get for '%{resource}'"

dsc_lib/src/dscresources/command_resource.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,12 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
283283
return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &test.executable, stdout = stdout, stderr = stderr, err = err).to_string()))
284284
}
285285
};
286+
let in_desired_state = get_desired_state(&actual_value)?;
286287
let diff_properties = get_diff(&expected_value, &actual_value);
287288
Ok(TestResult::Resource(ResourceTestResponse {
288289
desired_state: expected_value,
289290
actual_state: actual_value,
290-
in_desired_state: diff_properties.is_empty(),
291+
in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()),
291292
diff_properties,
292293
}))
293294
},
@@ -302,10 +303,11 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
302303
return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoDiff").to_string()));
303304
};
304305
let diff_properties: Vec<String> = serde_json::from_str(diff_properties)?;
306+
let in_desired_state = get_desired_state(&actual_value)?;
305307
Ok(TestResult::Resource(ResourceTestResponse {
306308
desired_state: expected_value,
307309
actual_state: actual_value,
308-
in_desired_state: diff_properties.is_empty(),
310+
in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()),
309311
diff_properties,
310312
}))
311313
},
@@ -335,6 +337,19 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
335337
}
336338
}
337339

340+
fn get_desired_state(actual: &Value) -> Result<Option<bool>, DscError> {
341+
// if actual state contains _inDesiredState, we use that to determine if the resource is in desired state
342+
let mut in_desired_state: Option<bool> = None;
343+
if let Some(in_desired_state_value) = actual.get("_inDesiredState") {
344+
if let Some(desired_state) = in_desired_state_value.as_bool() {
345+
in_desired_state = Some(desired_state);
346+
} else {
347+
return Err(DscError::Operation(t!("dscresources.commandResource.inDesiredStateNotBool").to_string()));
348+
}
349+
}
350+
Ok(in_desired_state)
351+
}
352+
338353
fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
339354
let get_result = invoke_get(resource, cwd, expected)?;
340355
let actual_state = match get_result {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
3+
"type": "Test/InDesiredState",
4+
"version": "0.1.0",
5+
"test": {
6+
"executable": "dsctest",
7+
"args": [
8+
"in-desired-state",
9+
{
10+
"jsonInputArg": "--input",
11+
"mandatory": true
12+
}
13+
],
14+
"return": "state"
15+
},
16+
"schema": {
17+
"command": {
18+
"executable": "dsctest",
19+
"args": [
20+
"schema",
21+
"-s",
22+
"in-desired-state"
23+
]
24+
}
25+
}
26+
}

tools/dsctest/src/args.rs

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum Schemas {
88
Delete,
99
Exist,
1010
ExitCode,
11+
InDesiredState,
1112
Sleep,
1213
Trace,
1314
WhatIf,
@@ -41,6 +42,12 @@ pub enum SubCommand {
4142
input: String,
4243
},
4344

45+
#[clap(name = "in-desired-state", about = "Specify if the resource is in the desired state")]
46+
InDesiredState {
47+
#[clap(name = "input", short, long, help = "The input to the in desired state command as JSON")]
48+
input: String,
49+
},
50+
4451
#[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
4552
Schema {
4653
#[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]

tools/dsctest/src/in_desired_state.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use schemars::JsonSchema;
5+
use serde::{Deserialize, Serialize};
6+
7+
#[allow(clippy::struct_field_names)]
8+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
9+
#[serde(deny_unknown_fields)]
10+
pub struct InDesiredState {
11+
#[serde(rename = "_inDesiredState", skip_serializing_if = "Option::is_none")]
12+
pub in_desired_state: Option<bool>,
13+
#[serde(rename = "valueOne")]
14+
pub value_one: i32,
15+
#[serde(rename = "valueTwo")]
16+
pub value_two: i32,
17+
}

tools/dsctest/src/main.rs

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod args;
55
mod delete;
66
mod exist;
77
mod exit_code;
8+
mod in_desired_state;
89
mod sleep;
910
mod trace;
1011
mod whatif;
@@ -15,6 +16,7 @@ use schemars::schema_for;
1516
use crate::delete::Delete;
1617
use crate::exist::{Exist, State};
1718
use crate::exit_code::ExitCode;
19+
use crate::in_desired_state::InDesiredState;
1820
use crate::sleep::Sleep;
1921
use crate::trace::Trace;
2022
use crate::whatif::WhatIf;
@@ -65,6 +67,18 @@ fn main() {
6567
}
6668
input
6769
},
70+
SubCommand::InDesiredState { input } => {
71+
let mut in_desired_state = match serde_json::from_str::<in_desired_state::InDesiredState>(&input) {
72+
Ok(in_desired_state) => in_desired_state,
73+
Err(err) => {
74+
eprintln!("Error JSON does not match schema: {err}");
75+
std::process::exit(1);
76+
}
77+
};
78+
in_desired_state.value_one = 1;
79+
in_desired_state.value_two = 2;
80+
serde_json::to_string(&in_desired_state).unwrap()
81+
},
6882
SubCommand::Schema { subcommand } => {
6983
let schema = match subcommand {
7084
Schemas::Delete => {
@@ -76,6 +90,9 @@ fn main() {
7690
Schemas::ExitCode => {
7791
schema_for!(ExitCode)
7892
},
93+
Schemas::InDesiredState => {
94+
schema_for!(InDesiredState)
95+
},
7996
Schemas::Sleep => {
8097
schema_for!(Sleep)
8198
},

0 commit comments

Comments
 (0)