Skip to content

Commit f0ffdf4

Browse files
authored
Merge pull request #464 from SteveL-MSFT/include-set-test
Enable `set` and `test` for Import resources
2 parents 18b913b + a706d91 commit f0ffdf4

File tree

8 files changed

+110
-106
lines changed

8 files changed

+110
-106
lines changed

dsc/examples/osinfo_parameters.dsc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ resources:
1313
type: Microsoft/OSInfo
1414
properties:
1515
family: "[parameters('osFamily')]"
16+
- name: another os instance
17+
type: Microsoft/OSInfo
18+
properties:
19+
family: macOS

dsc/src/subcommand.rs

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ use crate::tablewriter::Table;
99
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, EXIT_VALIDATION_FAILED, get_schema, write_output, get_input, set_dscconfigroot, validate_json};
1010
use dsc_lib::configure::{Configurator, config_doc::ExecutionKind, config_result::ResourceGetResult};
1111
use dsc_lib::dscerror::DscError;
12-
use dsc_lib::dscresources::invoke_result::{
13-
GroupResourceSetResponse, GroupResourceTestResponse, ResolveResult, TestResult
14-
};
12+
use dsc_lib::dscresources::invoke_result::ResolveResult;
1513
use dsc_lib::{
1614
DscManager,
1715
dscresources::invoke_result::ValidateResult,
@@ -27,11 +25,7 @@ pub fn config_get(configurator: &mut Configurator, format: &Option<OutputFormat>
2725
match configurator.invoke_get() {
2826
Ok(result) => {
2927
if *as_group {
30-
let mut group_result = Vec::<ResourceGetResult>::new();
31-
for result in result.results {
32-
group_result.push(result);
33-
};
34-
let json = match serde_json::to_string(&group_result) {
28+
let json = match serde_json::to_string(&(result.results)) {
3529
Ok(json) => json,
3630
Err(err) => {
3731
error!("JSON Error: {err}");
@@ -66,10 +60,7 @@ pub fn config_set(configurator: &mut Configurator, format: &Option<OutputFormat>
6660
match configurator.invoke_set(false) {
6761
Ok(result) => {
6862
if *as_group {
69-
let group_result = GroupResourceSetResponse {
70-
results: result.results
71-
};
72-
let json = match serde_json::to_string(&group_result) {
63+
let json = match serde_json::to_string(&(result.results)) {
7364
Ok(json) => json,
7465
Err(err) => {
7566
error!("JSON Error: {err}");
@@ -104,23 +95,6 @@ pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat
10495
match configurator.invoke_test() {
10596
Ok(result) => {
10697
if *as_group {
107-
let mut in_desired_state = true;
108-
for test_result in &result.results {
109-
match &test_result.result {
110-
TestResult::Resource(resource_test_result) => {
111-
if !resource_test_result.in_desired_state {
112-
in_desired_state = false;
113-
break;
114-
}
115-
},
116-
TestResult::Group(group_resource_test_result) => {
117-
if !group_resource_test_result.in_desired_state {
118-
in_desired_state = false;
119-
break;
120-
}
121-
}
122-
}
123-
}
12498
let json = if *as_get {
12599
let mut group_result = Vec::<ResourceGetResult>::new();
126100
for test_result in result.results {
@@ -135,11 +109,7 @@ pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat
135109
}
136110
}
137111
else {
138-
let group_result = GroupResourceTestResponse {
139-
results: result.results,
140-
in_desired_state
141-
};
142-
match serde_json::to_string(&group_result) {
112+
match serde_json::to_string(&(result.results)) {
143113
Ok(json) => json,
144114
Err(err) => {
145115
error!("JSON Error: {err}");

dsc/tests/dsc_include.tests.ps1

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ Describe 'Include tests' {
1010
$osinfoParametersConfigPath = Get-Item (Join-Path $includePath 'osinfo.parameters.yaml')
1111

1212
$logPath = Join-Path $TestDrive 'stderr.log'
13-
14-
$includeConfig = @'
15-
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
16-
resources:
17-
- name: Echo
18-
type: Test/Echo
19-
properties:
20-
output: Hello World
21-
'@
2213
}
2314

2415
It 'Include config with default parameters' {
@@ -183,4 +174,38 @@ resources:
183174
$out.results[1].result[0].result[0].type | Should -Be 'Test/Echo'
184175
$out.results[1].result[0].result[0].result[0].actualState.output | Should -Be 'one'
185176
}
177+
178+
It 'Set with include works' {
179+
$echoConfig = @'
180+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
181+
resources:
182+
- name: one
183+
type: Test/Echo
184+
properties:
185+
output: Hello World
186+
'@
187+
188+
$echoConfigPath = Join-Path $TestDrive 'echo.dsc.yaml'
189+
$echoConfig | Set-Content -Path $echoConfigPath -Encoding utf8
190+
# need to escape backslashes for YAML
191+
$echoConfigPathParent = (Split-Path $echoConfigPath -Parent).Replace('\', '\\')
192+
$echoConfigPathLeaf = (Split-Path $echoConfigPath -Leaf).Replace('\', '\\')
193+
$directorySeparator = [System.IO.Path]::DirectorySeparatorChar.ToString().Replace('\', '\\')
194+
195+
$includeConfig = @"
196+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
197+
resources:
198+
- name: nested
199+
type: Microsoft.DSC/Include
200+
properties:
201+
configurationFile: "[concat('$echoConfigPathParent', '$directorySeparator', '$echoConfigPathLeaf')]"
202+
"@
203+
204+
$out = dsc config set -d $includeConfig | ConvertFrom-Json
205+
$LASTEXITCODE | Should -Be 0
206+
$out.results[0].result[0].name | Should -Be 'one'
207+
$out.results[0].result[0].type | Should -Be 'Test/Echo'
208+
$out.results[0].result[0].result.afterState.output | Should -Be 'Hello World'
209+
$out.hadErrors | Should -Be $false
210+
}
186211
}

dsc_lib/src/configure/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
use crate::configure::config_doc::{ExecutionKind, Metadata};
55
use crate::configure::parameters::Input;
66
use crate::dscerror::DscError;
7-
use crate::dscresources::dscresource::get_diff;
8-
use crate::dscresources::invoke_result::GetResult;
9-
use crate::dscresources::{dscresource::{Capability, Invoke}, invoke_result::{SetResult, ResourceSetResponse}};
10-
use crate::dscresources::resource_manifest::Kind;
7+
use crate::dscresources::{
8+
{dscresource::{Capability, Invoke, get_diff}, invoke_result::{SetResult, ResourceSetResponse}},
9+
invoke_result::GetResult,
10+
resource_manifest::Kind,
11+
};
1112
use crate::DscResource;
1213
use crate::discovery::Discovery;
1314
use crate::parser::Statement;

dsc_lib/src/dscerror.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ pub enum DscError {
9595
#[error("Resource not found: {0}")]
9696
ResourceNotFound(String),
9797

98+
#[error("Resource manifest not found: {0}")]
99+
ResourceManifestNotFound(String),
100+
98101
#[error("Schema: {0}")]
99102
Schema(String),
100103

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use serde_json::Value;
66
use std::{collections::HashMap, env, io::{Read, Write}, process::{Command, Stdio}};
77
use crate::{configure::{config_doc::ExecutionKind, {config_result::ResourceGetResult, parameters, Configurator}}, util::parse_input_to_json};
88
use crate::dscerror::DscError;
9-
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
9+
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
1010
use tracing::{error, warn, info, debug, trace};
1111

1212
pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;
@@ -94,7 +94,13 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
9494
/// Error returned if the resource does not successfully set the desired state
9595
#[allow(clippy::too_many_lines)]
9696
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
97-
// TODO: support import resources
97+
debug!("Invoking set for '{}'", &resource.resource_type);
98+
if resource.kind == Some(Kind::Import) {
99+
let mut configurator = get_configurator(resource, cwd, desired)?;
100+
let config_result = configurator.invoke_set(skip_test)?;
101+
return Ok(SetResult::Group(config_result.results));
102+
}
103+
98104
let operation_type: String;
99105
let mut is_synthetic_what_if = false;
100106
let set_method = match execution_type {
@@ -124,16 +130,17 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
124130
if is_synthetic_what_if {
125131
return Ok(test_result.into());
126132
}
127-
let (in_desired_state, actual_state) = match test_result {
133+
let (in_desired_state, actual_state) = match &test_result {
128134
TestResult::Group(group_response) => {
135+
let in_desired_state = get_in_desired_state(&test_result);
129136
let mut result_array: Vec<Value> = Vec::new();
130-
for result in group_response.results {
137+
for result in group_response {
131138
result_array.push(serde_json::to_value(result)?);
132139
}
133-
(group_response.in_desired_state, Value::from(result_array))
140+
(in_desired_state, Value::from(result_array))
134141
},
135142
TestResult::Resource(response) => {
136-
(response.in_desired_state, response.actual_state)
143+
(response.in_desired_state, response.actual_state.clone())
137144
}
138145
};
139146

@@ -267,7 +274,12 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
267274
///
268275
/// Error is returned if the underlying command returns a non-zero exit code.
269276
pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
270-
// TODO: support import resources
277+
debug!("Invoking test for '{}'", &resource.resource_type);
278+
if resource.kind == Some(Kind::Import) {
279+
let mut configurator = get_configurator(resource, cwd, expected)?;
280+
let config_result = configurator.invoke_test()?;
281+
return Ok(TestResult::Group(config_result.results));
282+
}
271283

272284
let Some(test) = &resource.test else {
273285
info!("Resource '{}' does not implement test, performing synthetic test", &resource.resource_type);

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use schemars::JsonSchema;
77
use serde::{Deserialize, Serialize};
88
use serde_json::Value;
99
use std::collections::HashMap;
10+
use tracing::debug;
1011

1112
use super::{command_resource, dscerror, invoke_result::{ExportResult, GetResult, ResolveResult, ResourceTestResponse, SetResult, TestResult, ValidateResult}, resource_manifest::import_manifest};
1213

@@ -187,6 +188,7 @@ pub trait Invoke {
187188

188189
impl Invoke for DscResource {
189190
fn get(&self, filter: &str) -> Result<GetResult, DscError> {
191+
debug!("Invoking get for resource: {}", self.type_name);
190192
match &self.implemented_as {
191193
ImplementedAs::Custom(_custom) => {
192194
Err(DscError::NotImplemented("get custom resources".to_string()))
@@ -202,6 +204,7 @@ impl Invoke for DscResource {
202204
}
203205

204206
fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
207+
debug!("Invoking set for resource: {}", self.type_name);
205208
match &self.implemented_as {
206209
ImplementedAs::Custom(_custom) => {
207210
Err(DscError::NotImplemented("set custom resources".to_string()))
@@ -217,6 +220,7 @@ impl Invoke for DscResource {
217220
}
218221

219222
fn test(&self, expected: &str) -> Result<TestResult, DscError> {
223+
debug!("Invoking test for resource: {}", self.type_name);
220224
match &self.implemented_as {
221225
ImplementedAs::Custom(_custom) => {
222226
Err(DscError::NotImplemented("test custom resources".to_string()))
@@ -230,7 +234,15 @@ impl Invoke for DscResource {
230234
let resource_manifest = import_manifest(manifest.clone())?;
231235
if resource_manifest.test.is_none() {
232236
let get_result = self.get(expected)?;
233-
let desired_state = serde_json::from_str(expected)?;
237+
let desired_state = if self.kind == Kind::Import {
238+
let config = self.resolve(expected)?.configuration;
239+
// TODO: implement way to resolve entire config doc including expressions and parameters
240+
// as the raw configuration (desired state) won't match the result, also convert the desired
241+
// state to a TestResult so the comparison is consistent
242+
serde_json::to_value(config["resources"].clone())?
243+
} else {
244+
serde_json::from_str(expected)?
245+
};
234246
let actual_state = match get_result {
235247
GetResult::Group(results) => {
236248
let mut result_array: Vec<Value> = Vec::new();
@@ -245,7 +257,7 @@ impl Invoke for DscResource {
245257
};
246258
let diff_properties = get_diff( &desired_state, &actual_state);
247259
let test_result = TestResult::Resource(ResourceTestResponse {
248-
desired_state: serde_json::from_str(expected)?,
260+
desired_state,
249261
actual_state,
250262
in_desired_state: diff_properties.is_empty(),
251263
diff_properties,
@@ -260,6 +272,7 @@ impl Invoke for DscResource {
260272
}
261273

262274
fn delete(&self, filter: &str) -> Result<(), DscError> {
275+
debug!("Invoking delete for resource: {}", self.type_name);
263276
match &self.implemented_as {
264277
ImplementedAs::Custom(_custom) => {
265278
Err(DscError::NotImplemented("set custom resources".to_string()))
@@ -275,6 +288,7 @@ impl Invoke for DscResource {
275288
}
276289

277290
fn validate(&self, config: &str) -> Result<ValidateResult, DscError> {
291+
debug!("Invoking validate for resource: {}", self.type_name);
278292
match &self.implemented_as {
279293
ImplementedAs::Custom(_custom) => {
280294
Err(DscError::NotImplemented("validate custom resources".to_string()))
@@ -290,6 +304,7 @@ impl Invoke for DscResource {
290304
}
291305

292306
fn schema(&self) -> Result<String, DscError> {
307+
debug!("Invoking schema for resource: {}", self.type_name);
293308
match &self.implemented_as {
294309
ImplementedAs::Custom(_custom) => {
295310
Err(DscError::NotImplemented("schema custom resources".to_string()))
@@ -305,6 +320,7 @@ impl Invoke for DscResource {
305320
}
306321

307322
fn export(&self, input: &str) -> Result<ExportResult, DscError> {
323+
debug!("Invoking export for resource: {}", self.type_name);
308324
let Some(manifest) = &self.manifest else {
309325
return Err(DscError::MissingManifest(self.type_name.clone()));
310326
};
@@ -313,6 +329,7 @@ impl Invoke for DscResource {
313329
}
314330

315331
fn resolve(&self, input: &str) -> Result<ResolveResult, DscError> {
332+
debug!("Invoking resolve for resource: {}", self.type_name);
316333
let Some(manifest) = &self.manifest else {
317334
return Err(DscError::MissingManifest(self.type_name.clone()));
318335
};

0 commit comments

Comments
 (0)