Skip to content

Commit 79bb481

Browse files
authored
Merge pull request #500 from SteveL-MSFT/include-group
Change `import` type resource to a group type resource
2 parents 05179d9 + d912615 commit 79bb481

10 files changed

+190
-132
lines changed

dsc/include.dsc.resource.json

+55-32
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,57 @@
11
{
2-
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
3-
"type": "Microsoft.DSC/Include",
4-
"version": "0.1.0",
5-
"description": "Allows including a configuration file contents into current configuration.",
6-
"kind": "Import",
7-
"resolve": {
8-
"executable": "dsc",
9-
"args": [
10-
"config",
11-
"resolve"
12-
],
13-
"input": "stdin"
14-
},
15-
"exitCodes": {
16-
"0": "Success",
17-
"1": "Invalid argument",
18-
"2": "Resource error",
19-
"3": "JSON Serialization error",
20-
"4": "Invalid input format",
21-
"5": "Resource instance failed schema validation",
22-
"6": "Command cancelled"
23-
},
24-
"schema": {
25-
"command": {
26-
"executable": "dsc",
27-
"args": [
28-
"schema",
29-
"--type",
30-
"include"
31-
]
32-
}
33-
}
2+
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
3+
"type": "Microsoft.DSC/Include",
4+
"version": "0.1.0",
5+
"description": "Allows including a configuration file with optional parameter file.",
6+
"kind": "Import",
7+
"get": {
8+
"executable": "dsc",
9+
"args": [
10+
"config",
11+
"--as-include",
12+
"--as-group",
13+
"get"
14+
],
15+
"input": "stdin"
16+
},
17+
"set": {
18+
"executable": "dsc",
19+
"args": [
20+
"config",
21+
"--as-include",
22+
"--as-group",
23+
"set"
24+
],
25+
"input": "stdin",
26+
"implementsPretest": true,
27+
"return": "state"
28+
},
29+
"test": {
30+
"executable": "dsc",
31+
"args": [
32+
"config",
33+
"--as-include",
34+
"--as-group",
35+
"test"
36+
],
37+
"input": "stdin"
38+
},
39+
"exitCodes": {
40+
"0": "Success",
41+
"1": "Invalid argument",
42+
"2": "Resource error",
43+
"3": "JSON Serialization error",
44+
"4": "Invalid input format",
45+
"5": "Resource instance failed schema validation",
46+
"6": "Command cancelled"
47+
},
48+
"validate": {
49+
"executable": "dsc",
50+
"args": [
51+
"config",
52+
"--as-include",
53+
"validate"
54+
],
55+
"input": "stdin"
3456
}
57+
}

dsc/src/args.rs

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub enum SubCommand {
5757
// Used to inform when DSC is used as a group resource to modify it's output
5858
#[clap(long, hide = true)]
5959
as_group: bool,
60+
// Used to inform when DSC is used as a include group resource
61+
#[clap(long, hide = true)]
62+
as_include: bool,
6063
},
6164
#[clap(name = "resource", about = "Invoke a specific DSC resource")]
6265
Resource {

dsc/src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,19 @@ fn main() {
6969
let mut cmd = Args::command();
7070
generate(shell, &mut cmd, "dsc", &mut io::stdout());
7171
},
72-
SubCommand::Config { subcommand, parameters, parameters_file, as_group } => {
72+
SubCommand::Config { subcommand, parameters, parameters_file, as_group, as_include } => {
7373
if let Some(file_name) = parameters_file {
7474
info!("Reading parameters from file {file_name}");
7575
match std::fs::read_to_string(&file_name) {
76-
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), &input, &as_group),
76+
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), &input, &as_group, &as_include),
7777
Err(err) => {
7878
error!("Error: Failed to read parameters file '{file_name}': {err}");
7979
exit(util::EXIT_INVALID_INPUT);
8080
}
8181
}
8282
}
8383
else {
84-
subcommand::config(&subcommand, &parameters, &input, &as_group);
84+
subcommand::config(&subcommand, &parameters, &input, &as_group, &as_include);
8585
}
8686
},
8787
SubCommand::Resource { subcommand } => {

dsc/src/subcommand.rs

+42-20
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
// Licensed under the MIT License.
33

44
use crate::args::{ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand};
5-
use crate::resolve::get_contents;
5+
use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::Stream;
88
use crate::tablewriter::Table;
9-
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};
10-
use dsc_lib::configure::{Configurator, config_doc::ExecutionKind, config_result::ResourceGetResult};
9+
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, get_schema, write_output, get_input, set_dscconfigroot, validate_json};
10+
use dsc_lib::configure::{Configurator, config_doc::{Configuration, ExecutionKind}, config_result::ResourceGetResult};
1111
use dsc_lib::dscerror::DscError;
1212
use dsc_lib::dscresources::invoke_result::ResolveResult;
1313
use dsc_lib::{
@@ -186,15 +186,27 @@ fn initialize_config_root(path: &Option<String>) -> Option<String> {
186186
}
187187

188188
#[allow(clippy::too_many_lines)]
189-
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin: &Option<String>, as_group: &bool) {
189+
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin: &Option<String>, as_group: &bool, as_include: &bool) {
190190
let (new_parameters, json_string) = match subcommand {
191191
ConfigSubCommand::Get { document, path, .. } |
192192
ConfigSubCommand::Set { document, path, .. } |
193193
ConfigSubCommand::Test { document, path, .. } |
194194
ConfigSubCommand::Validate { document, path, .. } |
195195
ConfigSubCommand::Export { document, path, .. } => {
196196
let new_path = initialize_config_root(path);
197-
(None, get_input(document, stdin, &new_path))
197+
let input = get_input(document, stdin, &new_path);
198+
if *as_include {
199+
let (new_parameters, config_json) = match get_contents(&input) {
200+
Ok((parameters, config_json)) => (parameters, config_json),
201+
Err(err) => {
202+
error!("{err}");
203+
exit(EXIT_DSC_ERROR);
204+
}
205+
};
206+
(new_parameters, config_json)
207+
} else {
208+
(None, input)
209+
}
198210
},
199211
ConfigSubCommand::Resolve { document, path, .. } => {
200212
let new_path = initialize_config_root(path);
@@ -273,31 +285,41 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin:
273285
ConfigSubCommand::Test { format, as_get, .. } => {
274286
config_test(&mut configurator, format, as_group, as_get);
275287
},
276-
ConfigSubCommand::Validate { format, .. } => {
288+
ConfigSubCommand::Validate { document, path, format} => {
277289
let mut result = ValidateResult {
278290
valid: true,
279291
reason: None,
280292
};
281-
let valid = match validate_config(&json_string) {
282-
Ok(()) => {
283-
true
284-
},
285-
Err(err) => {
286-
error!("{err}");
287-
result.valid = false;
288-
false
293+
if *as_include {
294+
let new_path = initialize_config_root(path);
295+
let input = get_input(document, stdin, &new_path);
296+
match serde_json::from_str::<Include>(&input) {
297+
Ok(_) => {
298+
// valid, so do nothing
299+
},
300+
Err(err) => {
301+
error!("Error: Failed to deserialize Include input: {err}");
302+
result.valid = false;
303+
}
289304
}
290-
};
305+
} else {
306+
match validate_config(configurator.get_config()) {
307+
Ok(()) => {
308+
// valid, so do nothing
309+
},
310+
Err(err) => {
311+
error!("{err}");
312+
result.valid = false;
313+
}
314+
};
315+
}
291316

292317
let Ok(json) = serde_json::to_string(&result) else {
293318
error!("Failed to convert validation result to JSON");
294319
exit(EXIT_JSON_ERROR);
295320
};
296321

297322
write_output(&json, format);
298-
if !valid {
299-
exit(EXIT_VALIDATION_FAILED);
300-
}
301323
},
302324
ConfigSubCommand::Export { format, .. } => {
303325
config_export(&mut configurator, format);
@@ -349,11 +371,11 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin:
349371
/// # Errors
350372
///
351373
/// * `DscError` - The error that occurred.
352-
pub fn validate_config(config: &str) -> Result<(), DscError> {
374+
pub fn validate_config(config: &Configuration) -> Result<(), DscError> {
353375
// first validate against the config schema
354376
debug!("Validating configuration against schema");
355377
let schema = serde_json::to_value(get_schema(DscType::Configuration))?;
356-
let config_value = serde_json::from_str(config)?;
378+
let config_value = serde_json::to_value(config)?;
357379
validate_json("Configuration", &schema, &config_value)?;
358380
let mut dsc = DscManager::new()?;
359381

dsc/src/util.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,8 @@ pub fn get_input(input: &Option<String>, stdin: &Option<String>, path: &Option<S
425425
};
426426

427427
if value.trim().is_empty() {
428-
debug!("Provided input is empty");
429-
return String::new();
428+
error!("Provided input is empty");
429+
exit(EXIT_INVALID_INPUT);
430430
}
431431

432432
match parse_input_to_json(&value) {

dsc/tests/dsc_args.tests.ps1

+3-3
Original file line numberDiff line numberDiff line change
@@ -186,22 +186,22 @@ resources:
186186
'' | dsc resource set -r Microsoft/OSInfo 2> $TestDrive/error.txt
187187
$err = Get-Content $testdrive/error.txt -Raw
188188
$err.Length | Should -Not -Be 0
189-
$LASTEXITCODE | Should -Be 1
189+
$LASTEXITCODE | Should -Be 4
190190
}
191191

192192
It 'input cannot be empty if neither stdin or path is provided' {
193193
dsc resource set -r Microsoft/OSInfo --input " " 2> $TestDrive/error.txt
194194
$err = Get-Content $testdrive/error.txt -Raw
195195
$err.Length | Should -Not -Be 0
196-
$LASTEXITCODE | Should -Be 1
196+
$LASTEXITCODE | Should -Be 4
197197
}
198198

199199
It 'path contents cannot be empty if neither stdin or input is provided' {
200200
Set-Content -Path $TestDrive/empty.yaml -Value " "
201201
dsc resource set -r Microsoft/OSInfo --path $TestDrive/empty.yaml 2> $TestDrive/error.txt
202202
$err = Get-Content $testdrive/error.txt -Raw
203203
$err.Length | Should -Not -Be 0
204-
$LASTEXITCODE | Should -Be 1
204+
$LASTEXITCODE | Should -Be 4
205205
}
206206

207207
It 'document cannot be empty if neither stdin or path is provided' {

dsc/tests/dsc_include.tests.ps1

+36-6
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ resources:
170170
$out.results[0].result[0].result.actualState.output | Should -Be 'one'
171171
$out.results[1].result[0].name | Should -Be 'nested'
172172
$out.results[1].result[0].type | Should -Be 'Microsoft.DSC/Include'
173-
$out.results[1].result[0].result[0].name | Should -Be 'one'
174-
$out.results[1].result[0].result[0].type | Should -Be 'Test/Echo'
175-
$out.results[1].result[0].result[0].result[0].actualState.output | Should -Be 'one'
173+
$out.results[1].result[0].result.actualState.name | Should -Be 'one'
174+
$out.results[1].result[0].result.actualState.type | Should -Be 'Test/Echo'
175+
$out.results[1].result[0].result.actualState.result.actualState.output | Should -Be 'one'
176176
}
177177

178178
It 'Set with include works' {
@@ -203,9 +203,39 @@ resources:
203203

204204
$out = dsc config set -d $includeConfig | ConvertFrom-Json
205205
$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'
206+
$out.results[0].result.beforeState[0].name | Should -Be 'one'
207+
$out.results[0].result.beforeState[0].type | Should -Be 'Test/Echo'
208+
$out.results[0].result.afterState[0].result.afterState.output | Should -Be 'Hello World'
209209
$out.hadErrors | Should -Be $false
210210
}
211+
212+
It 'Test with include works' {
213+
$includeYaml = Join-Path $PSScriptRoot ../../dsc/examples/include.dsc.yaml
214+
$out = dsc config test -p $includeYaml | ConvertFrom-Json
215+
$LASTEXITCODE | Should -Be 0
216+
$out.results[0].type | Should -BeExactly 'Microsoft.DSC/Include'
217+
$out.results[0].result[0].name | Should -BeExactly 'os'
218+
$out.results[0].result[0].type | Should -BeExactly 'Microsoft/OSInfo'
219+
$out.results[0].result[0].result.desiredState.family | Should -BeExactly 'macOS'
220+
221+
$family = if ($isWindows) {
222+
'Windows'
223+
} elseif ($IsLinux) {
224+
'Linux'
225+
} elseif ($IsMacOS) {
226+
'macOS'
227+
} else {
228+
'Unknown'
229+
}
230+
231+
$out.results[0].result[0].result.actualState.family | Should -BeExactly $family
232+
($expectedState, $expectedDiff) = if ($IsMacOS) {
233+
$true, 0
234+
} else {
235+
$false, 1
236+
}
237+
238+
$out.results[0].result[0].result.inDesiredState | Should -Be $expectedState
239+
$out.results[0].result[0].result.differingProperties.Count | Should -Be $expectedDiff
240+
}
211241
}

0 commit comments

Comments
 (0)