Skip to content

Commit 0812245

Browse files
committed
Enable Include resource to take configuration and parameters as string content
1 parent 1d8398a commit 0812245

File tree

4 files changed

+256
-57
lines changed

4 files changed

+256
-57
lines changed

dsc/examples/osinfo.parameters.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"parameters": {
3+
"osFamily": "macOS"
4+
}
5+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json",
3+
"parameters": {
4+
"osFamily": {
5+
"type": "string",
6+
"defaultValue": "[concat('Win','dows')]",
7+
"allowedValues": [
8+
"Windows",
9+
"Linux",
10+
"macOS"
11+
]
12+
}
13+
},
14+
"resources": [
15+
{
16+
"name": "os",
17+
"type": "Microsoft/OSInfo",
18+
"properties": {
19+
"family": "[parameters('osFamily')]"
20+
}
21+
},
22+
{
23+
"name": "another os instance",
24+
"type": "Microsoft/OSInfo",
25+
"properties": {
26+
"family": "macOS"
27+
}
28+
},
29+
{
30+
"name": "path",
31+
"type": "Microsoft.DSC.Debug/Echo",
32+
"properties": {
33+
"output": "[envvar('PATH')]"
34+
}
35+
}
36+
]
37+
}

dsc/src/resolve.rs

Lines changed: 102 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,30 @@ use tracing::{debug, info};
1414
use crate::util::DSC_CONFIG_ROOT;
1515

1616
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
17-
pub struct Include {
17+
pub enum IncludeKind {
1818
/// The path to the file to include. Path is relative to the file containing the include
1919
/// and not allowed to reference parent directories. If a configuration document is used
2020
/// instead of a file, then the path is relative to the current working directory.
2121
#[serde(rename = "configurationFile")]
22-
pub configuration_file: String,
22+
FilePath(String),
23+
#[serde(rename = "configurationContent")]
24+
Content(String),
25+
}
26+
27+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
28+
pub enum IncludeParametersKind {
2329
#[serde(rename = "parametersFile")]
24-
pub parameters_file: Option<String>,
30+
FilePath(String),
31+
#[serde(rename = "parametersContent")]
32+
Content(String),
33+
}
34+
35+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
36+
pub struct Include {
37+
#[serde(flatten)]
38+
pub configuration: IncludeKind,
39+
#[serde(flatten)]
40+
pub parameters: Option<IncludeParametersKind>,
2541
}
2642

2743
/// Read the file specified in the Include input and return the content as a JSON string.
@@ -51,74 +67,104 @@ pub fn get_contents(input: &str) -> Result<(Option<String>, String), String> {
5167
}
5268
};
5369

54-
let include_path = normalize_path(Path::new(&include.configuration_file))?;
70+
let config_json = match include.configuration {
71+
IncludeKind::FilePath(file_path) => {
72+
let include_path = normalize_path(Path::new(&file_path))?;
5573

56-
// read the file specified in the Include input
57-
let mut buffer: Vec<u8> = Vec::new();
58-
match File::open(&include_path) {
59-
Ok(mut file) => {
60-
match file.read_to_end(&mut buffer) {
61-
Ok(_) => (),
74+
// read the file specified in the Include input
75+
let mut buffer: Vec<u8> = Vec::new();
76+
match File::open(&include_path) {
77+
Ok(mut file) => {
78+
match file.read_to_end(&mut buffer) {
79+
Ok(_) => (),
80+
Err(err) => {
81+
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToReadFile")));
82+
}
83+
}
84+
},
6285
Err(err) => {
63-
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToReadFile")));
86+
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToOpenFile")));
6487
}
6588
}
66-
},
67-
Err(err) => {
68-
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToOpenFile")));
69-
}
70-
}
71-
// convert the buffer to a string
72-
let include_content = match String::from_utf8(buffer) {
73-
Ok(input) => input,
74-
Err(err) => {
75-
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFileContent")));
76-
}
77-
};
89+
// convert the buffer to a string
90+
let include_content = match String::from_utf8(buffer) {
91+
Ok(input) => input,
92+
Err(err) => {
93+
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFileContent")));
94+
}
95+
};
7896

79-
// try to deserialize the Include content as YAML first
80-
let configuration: Configuration = match serde_yaml::from_str(&include_content) {
81-
Ok(configuration) => configuration,
82-
Err(_err) => {
83-
// if that fails, try to deserialize it as JSON
84-
match serde_json::from_str(&include_content) {
85-
Ok(configuration) => configuration,
97+
match parse_input_to_json(&include_content) {
98+
Ok(json) => json,
8699
Err(err) => {
87100
return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFile")));
88101
}
89102
}
90-
}
91-
};
103+
// // try to deserialize the Include content as YAML first
104+
// let configuration: Configuration = match serde_yaml::from_str(&include_content) {
105+
// Ok(configuration) => configuration,
106+
// Err(_err) => {
107+
// // if that fails, try to deserialize it as JSON
108+
// match serde_json::from_str(&include_content) {
109+
// Ok(configuration) => configuration,
110+
// Err(err) => {
111+
// return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFile")));
112+
// }
113+
// }
114+
// }
115+
// };
92116

93-
// serialize the Configuration as JSON
94-
let config_json = match serde_json::to_string(&configuration) {
95-
Ok(json) => json,
96-
Err(err) => {
97-
return Err(format!("JSON: {err}"));
117+
// // serialize the Configuration as JSON
118+
// match serde_json::to_string(&configuration) {
119+
// Ok(json) => json,
120+
// Err(err) => {
121+
// return Err(format!("JSON: {err}"));
122+
// }
123+
// }
124+
},
125+
IncludeKind::Content(text) => {
126+
match parse_input_to_json(&text) {
127+
Ok(json) => json,
128+
Err(err) => {
129+
return Err(format!("{}: {err}", t!("resolve.invalidFile")));
130+
}
131+
}
98132
}
99133
};
100134

101-
let parameters = if let Some(parameters_file) = include.parameters_file {
102-
// combine the path with DSC_CONFIG_ROOT
103-
let parameters_file = normalize_path(Path::new(&parameters_file))?;
104-
info!("{} '{parameters_file:?}'", t!("resolve.resolvingParameters"));
105-
match std::fs::read_to_string(&parameters_file) {
106-
Ok(parameters) => {
107-
let parameters_json = match parse_input_to_json(&parameters) {
108-
Ok(json) => json,
109-
Err(err) => {
110-
return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedParseParametersFile")));
111-
}
112-
};
113-
Some(parameters_json)
114-
},
115-
Err(err) => {
116-
return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedResolveParametersFile")));
135+
let parameters = match include.parameters {
136+
Some(IncludeParametersKind::FilePath(file_path)) => {
137+
// combine the path with DSC_CONFIG_ROOT
138+
let parameters_file = normalize_path(Path::new(&file_path))?;
139+
info!("{} '{parameters_file:?}'", t!("resolve.resolvingParameters"));
140+
match std::fs::read_to_string(&parameters_file) {
141+
Ok(parameters) => {
142+
let parameters_json = match parse_input_to_json(&parameters) {
143+
Ok(json) => json,
144+
Err(err) => {
145+
return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedParseParametersFile")));
146+
}
147+
};
148+
Some(parameters_json)
149+
},
150+
Err(err) => {
151+
return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedResolveParametersFile")));
152+
}
117153
}
154+
},
155+
Some(IncludeParametersKind::Content(text)) => {
156+
let parameters_json = match parse_input_to_json(&text) {
157+
Ok(json) => json,
158+
Err(err) => {
159+
return Err(format!("{}: {err}", t!("resolve.invalidParametersContent")));
160+
}
161+
};
162+
Some(parameters_json)
163+
},
164+
None => {
165+
debug!("{}", t!("resolve.noParameters"));
166+
None
118167
}
119-
} else {
120-
debug!("{}", t!("resolve.noParametersFile"));
121-
None
122168
};
123169

124170
Ok((parameters, config_json))

dsc/tests/dsc_include.tests.ps1

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,34 @@ Describe 'Include tests' {
1212
$logPath = Join-Path $TestDrive 'stderr.log'
1313
}
1414

15-
It 'Include config with default parameters' {
15+
It 'Include invalid config file' {
16+
$invalidConfig = @"
17+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
18+
properties:
19+
- name: osinfo
20+
type: Microsoft.DSC/Include
21+
properties:
22+
configurationFile: include/non-existing.dsc.yaml
23+
"@
24+
25+
$invalidConfigPath = Join-Path $TestDrive 'invalid_config.dsc.yaml'
26+
$invalidConfig | Set-Content -Path $invalidConfigPath
27+
28+
$config = @"
29+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
30+
resources:
31+
- name: osinfo
32+
type: Microsoft.DSC/Include
33+
properties:
34+
configurationFile: $invalidConfigPath
35+
"@
36+
$configPath = Join-Path $TestDrive 'config.dsc.yaml'
37+
$config | Set-Content -Path $configPath
38+
dsc config get -f $configPath
39+
$LASTEXITCODE | Should -Be 2
40+
}
41+
42+
It 'Include config file with default parameters' {
1643
$config = @"
1744
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
1845
resources:
@@ -35,6 +62,63 @@ Describe 'Include tests' {
3562
$out.results[0].result[0].result.actualState.family | Should -Be $expectedOS
3663
}
3764

65+
It 'Include config YAML content with default parameters' {
66+
# since this is YAML, we need to ensure correct indentation
67+
$includeContent = (Get-Content $osinfoConfigPath -Raw).Replace("`n", "`n" + (' ' * 20))
68+
69+
$config = @"
70+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
71+
resources:
72+
- name: osinfo
73+
type: Microsoft.DSC/Include
74+
properties:
75+
configurationContent: |
76+
$includeContent
77+
"@
78+
79+
$configPath = Join-Path $TestDrive 'config.dsc.yaml'
80+
$config | Set-Content -Path $configPath
81+
$out = dsc config get -f $configPath | ConvertFrom-Json
82+
$LASTEXITCODE | Should -Be 0
83+
if ($IsWindows) {
84+
$expectedOS = 'Windows'
85+
} elseif ($IsLinux) {
86+
$expectedOS = 'Linux'
87+
} else {
88+
$expectedOS = 'macOS'
89+
}
90+
$out.results[0].result[0].result.actualState.family | Should -Be $expectedOS
91+
}
92+
93+
It 'Include config JSON content with default parameters' {
94+
$osinfoJsonPath = Join-Path $PSScriptRoot '../examples/osinfo_parameters.dsc.json'
95+
96+
# for JSON, we can just have it as a single line
97+
$includeContent = (Get-Content $osinfoJsonPath -Raw).Replace("`n", "").Replace('"', '\"')
98+
99+
$config = @"
100+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
101+
resources:
102+
- name: osinfo
103+
type: Microsoft.DSC/Include
104+
properties:
105+
configurationContent: "$includeContent"
106+
"@
107+
108+
$configPath = Join-Path $TestDrive 'config.dsc.yaml'
109+
$config | Set-Content -Path $configPath
110+
$out = dsc config get -f $configPath | ConvertFrom-Json
111+
$LASTEXITCODE | Should -Be 0
112+
if ($IsWindows) {
113+
$expectedOS = 'Windows'
114+
} elseif ($IsLinux) {
115+
$expectedOS = 'Linux'
116+
} else {
117+
$expectedOS = 'macOS'
118+
}
119+
$out.results[0].result[0].result.actualState.family | Should -Be $expectedOS
120+
}
121+
38122
It 'Include config with parameters file' {
39123
$config = @"
40124
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
@@ -59,6 +143,33 @@ Describe 'Include tests' {
59143
$out.results[0].result[0].result.actualState.family | Should -Be $expectedOS
60144
}
61145

146+
It 'Include config with parameters content' {
147+
$parametersContentFile = Join-Path $PSScriptRoot '../examples/osinfo.parameters.json'
148+
$parametersContent = (Get-Content $parametersContentFile -Raw).Replace("`n", "").Replace('"', '\"')
149+
150+
$config = @"
151+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
152+
resources:
153+
- name: osinfo
154+
type: Microsoft.DSC/Include
155+
properties:
156+
configurationFile: include/osinfo_parameters.dsc.yaml
157+
parametersContent: "$parametersContent"
158+
"@
159+
$configPath = Join-Path $TestDrive 'config.dsc.yaml'
160+
$config | Set-Content -Path $configPath
161+
$out = dsc config get -f $configPath | ConvertFrom-Json
162+
$LASTEXITCODE | Should -Be 0
163+
if ($IsWindows) {
164+
$expectedOS = 'Windows'
165+
} elseif ($IsLinux) {
166+
$expectedOS = 'Linux'
167+
} else {
168+
$expectedOS = 'macOS'
169+
}
170+
$out.results[0].result[0].result.actualState.family | Should -Be $expectedOS
171+
}
172+
62173
It 'Invalid file path: <test>' -TestCases @(
63174
@{ test = 'non-existing configuration'; config = 'include/non-existing.dsc.yaml'; parameters = $null }
64175
@{ test = 'non-existing parameters'; config = 'include/osinfo_parameters.dsc.yaml'; parameters = 'include/non-existing.parameters.yaml' }

0 commit comments

Comments
 (0)