Skip to content

Commit 2c5b5c7

Browse files
authored
Merge pull request #884 from tgauth/surface-props-export
Surface _kind, _securityContext, and _name from resource during export
2 parents 7eaef7f + b801a5e commit 2c5b5c7

File tree

9 files changed

+66
-9
lines changed

9 files changed

+66
-9
lines changed

dsc/src/subcommand.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
141141
name: test_result.name,
142142
resource_type: test_result.resource_type,
143143
properties,
144-
depends_on: None,
145-
metadata: None,
144+
..Default::default()
146145
};
147146
result_configuration.resources.push(resource);
148147
}

dsc/tests/dsc_export.tests.ps1

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,24 @@ resources:
161161
$out.resources[1].properties.foo | Should -BeExactly 'bar'
162162
$out.resources[1].properties.hello | Should -BeExactly 'world'
163163
}
164+
165+
It 'Export can surface _kind, _securityContext, and _name from a resource' {
166+
$yaml = @'
167+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
168+
resources:
169+
- name: Test Export
170+
type: Test/Export
171+
properties:
172+
count: 1
173+
'@
174+
$out = dsc config export -i $yaml | ConvertFrom-Json
175+
$LASTEXITCODE | Should -Be 0
176+
$out.resources.count | Should -Be 1
177+
$out.resources[0].name | Should -BeExactly 'TestName'
178+
$out.resources[0].kind | Should -BeExactly 'TestKind'
179+
$out.resources[0].metadata.'Microsoft.DSC'.securityContext | Should -BeExactly 'elevated'
180+
$out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_kind'
181+
$out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_securityContext'
182+
$out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_name'
183+
}
164184
}

dsc_lib/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ parameterNotArray = "Parameter '%{name}' is not an array"
6666
parameterNotObject = "Parameter '%{name}' is not an object"
6767
invokePropertyExpressions = "Invoke property expressions"
6868
invokeExpression = "Invoke property expression for %{name}: %{value}"
69+
propertyNotString = "Property '%{name}' with value '%{value}' is not a string"
6970

7071
[discovery.commandDiscovery]
7172
couldNotReadSetting = "Could not read 'resourcePath' setting"

dsc_lib/src/configure/config_doc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ pub struct Resource {
134134
#[schemars(regex(pattern = r"^\[resourceId\(\s*'[a-zA-Z0-9\.]+/[a-zA-Z0-9]+'\s*,\s*'[a-zA-Z0-9 ]+'\s*\)]$"))]
135135
pub depends_on: Option<Vec<String>>,
136136
#[serde(skip_serializing_if = "Option::is_none")]
137+
pub kind: Option<String>,
138+
#[serde(skip_serializing_if = "Option::is_none")]
137139
pub properties: Option<Map<String, Value>>,
138140
#[serde(skip_serializing_if = "Option::is_none")]
139-
pub metadata: Option<Map<String, Value>>,
141+
pub metadata: Option<Metadata>,
140142
}
141143

142144
impl Default for Configuration {
@@ -191,6 +193,7 @@ impl Resource {
191193
resource_type: String::new(),
192194
name: String::new(),
193195
depends_on: None,
196+
kind: None,
194197
properties: None,
195198
metadata: None,
196199
}

dsc_lib/src/configure/mod.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,35 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf
6666
}
6767
} else {
6868
for (i, instance) in export_result.actual_state.iter().enumerate() {
69-
let mut r = config_doc::Resource::new();
69+
let mut r: Resource = config_doc::Resource::new();
7070
r.resource_type.clone_from(&resource.type_name);
71-
r.name = format!("{}-{i}", r.resource_type);
72-
let props: Map<String, Value> = serde_json::from_value(instance.clone())?;
71+
let mut props: Map<String, Value> = serde_json::from_value(instance.clone())?;
72+
if let Some(kind) = props.remove("_kind") {
73+
if !kind.is_string() {
74+
return Err(DscError::Parser(t!("configure.mod.propertyNotString", name = "_kind", value = kind).to_string()));
75+
}
76+
r.kind = kind.as_str().map(std::string::ToString::to_string);
77+
}
78+
if let Some(security_context) = props.remove("_securityContext") {
79+
let context: SecurityContextKind = serde_json::from_value(security_context)?;
80+
let metadata = Metadata {
81+
microsoft: Some(
82+
MicrosoftDscMetadata {
83+
security_context: Some(context),
84+
..Default::default()
85+
}
86+
),
87+
other: Map::new(),
88+
};
89+
r.metadata = Some(metadata);
90+
}
91+
r.name = if let Some(name) = props.remove("_name") {
92+
name.as_str()
93+
.map(std::string::ToString::to_string)
94+
.ok_or_else(|| DscError::Parser(t!("configure.mod.propertyNotString", name = "_name", value = name).to_string()))?
95+
} else {
96+
format!("{}-{i}", r.resource_type)
97+
};
7398
r.properties = escape_property_values(&props)?;
7499

75100
conf.resources.push(r);

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,8 @@ impl DscResource {
109109
let adapter_resource = Resource {
110110
name: self.type_name.clone(),
111111
resource_type: adapter.to_string(),
112-
depends_on: None,
113-
metadata: None,
114112
properties: Some(resources_map),
113+
..Default::default()
115114
};
116115
configuration.resources.push(adapter_resource);
117116
let config_json = serde_json::to_string(&configuration)?;

tools/dsctest/src/args.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub enum SubCommand {
5454
#[clap(name = "input", short, long, help = "The input to the export command as JSON")]
5555
input: String,
5656
},
57+
5758
#[clap(name = "exporter", about = "Exports different types of resources")]
5859
Exporter {
5960
#[clap(name = "input", short, long, help = "The input to the exporter command as JSON")]

tools/dsctest/src/export.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ use serde::{Deserialize, Serialize};
99
pub struct Export {
1010
/// Number of instances to return
1111
pub count: u64,
12+
#[serde(skip_serializing_if = "Option::is_none")]
13+
pub _kind: Option<String>,
14+
#[serde(skip_serializing_if = "Option::is_none")]
15+
pub _name: Option<String>,
16+
#[serde(rename = "_securityContext", skip_serializing_if = "Option::is_none")]
17+
pub _security_context: Option<String>,
1218
}

tools/dsctest/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ fn main() {
9494
};
9595
for i in 0..export.count {
9696
let instance = Export {
97-
count: i
97+
count: i,
98+
_kind: Some("TestKind".to_string()),
99+
_name: Some("TestName".to_string()),
100+
_security_context: Some("elevated".to_string()),
98101
};
99102
println!("{}", serde_json::to_string(&instance).unwrap());
100103
}

0 commit comments

Comments
 (0)