diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 37ad5a1ed..d8ac8e48a 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -141,8 +141,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat name: test_result.name, resource_type: test_result.resource_type, properties, - depends_on: None, - metadata: None, + ..Default::default() }; result_configuration.resources.push(resource); } diff --git a/dsc/tests/dsc_export.tests.ps1 b/dsc/tests/dsc_export.tests.ps1 index f4b5b7431..01e55b2ba 100644 --- a/dsc/tests/dsc_export.tests.ps1 +++ b/dsc/tests/dsc_export.tests.ps1 @@ -161,4 +161,24 @@ resources: $out.resources[1].properties.foo | Should -BeExactly 'bar' $out.resources[1].properties.hello | Should -BeExactly 'world' } + + It 'Export can surface _kind, _securityContext, and _name from a resource' { + $yaml = @' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Test Export + type: Test/Export + properties: + count: 1 +'@ + $out = dsc config export -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.resources.count | Should -Be 1 + $out.resources[0].name | Should -BeExactly 'TestName' + $out.resources[0].kind | Should -BeExactly 'TestKind' + $out.resources[0].metadata.'Microsoft.DSC'.securityContext | Should -BeExactly 'elevated' + $out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_kind' + $out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_securityContext' + $out.resources[0].properties.psobject.properties.name | Should -Not -Contain '_name' + } } diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 2b89e7b89..1fce24a12 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -66,6 +66,7 @@ parameterNotArray = "Parameter '%{name}' is not an array" parameterNotObject = "Parameter '%{name}' is not an object" invokePropertyExpressions = "Invoke property expressions" invokeExpression = "Invoke property expression for %{name}: %{value}" +propertyNotString = "Property '%{name}' with value '%{value}' is not a string" [discovery.commandDiscovery] couldNotReadSetting = "Could not read 'resourcePath' setting" diff --git a/dsc_lib/src/configure/config_doc.rs b/dsc_lib/src/configure/config_doc.rs index b841bf2a3..ffc85fc60 100644 --- a/dsc_lib/src/configure/config_doc.rs +++ b/dsc_lib/src/configure/config_doc.rs @@ -134,9 +134,11 @@ pub struct Resource { #[schemars(regex(pattern = r"^\[resourceId\(\s*'[a-zA-Z0-9\.]+/[a-zA-Z0-9]+'\s*,\s*'[a-zA-Z0-9 ]+'\s*\)]$"))] pub depends_on: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option>, + pub metadata: Option, } impl Default for Configuration { @@ -191,6 +193,7 @@ impl Resource { resource_type: String::new(), name: String::new(), depends_on: None, + kind: None, properties: None, metadata: None, } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 3039e581a..50f175a30 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -66,10 +66,35 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf } } else { for (i, instance) in export_result.actual_state.iter().enumerate() { - let mut r = config_doc::Resource::new(); + let mut r: Resource = config_doc::Resource::new(); r.resource_type.clone_from(&resource.type_name); - r.name = format!("{}-{i}", r.resource_type); - let props: Map = serde_json::from_value(instance.clone())?; + let mut props: Map = serde_json::from_value(instance.clone())?; + if let Some(kind) = props.remove("_kind") { + if !kind.is_string() { + return Err(DscError::Parser(t!("configure.mod.propertyNotString", name = "_kind", value = kind).to_string())); + } + r.kind = kind.as_str().map(std::string::ToString::to_string); + } + if let Some(security_context) = props.remove("_securityContext") { + let context: SecurityContextKind = serde_json::from_value(security_context)?; + let metadata = Metadata { + microsoft: Some( + MicrosoftDscMetadata { + security_context: Some(context), + ..Default::default() + } + ), + other: Map::new(), + }; + r.metadata = Some(metadata); + } + r.name = if let Some(name) = props.remove("_name") { + name.as_str() + .map(std::string::ToString::to_string) + .ok_or_else(|| DscError::Parser(t!("configure.mod.propertyNotString", name = "_name", value = name).to_string()))? + } else { + format!("{}-{i}", r.resource_type) + }; r.properties = escape_property_values(&props)?; conf.resources.push(r); diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index c4119c697..0bb878ade 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -109,9 +109,8 @@ impl DscResource { let adapter_resource = Resource { name: self.type_name.clone(), resource_type: adapter.to_string(), - depends_on: None, - metadata: None, properties: Some(resources_map), + ..Default::default() }; configuration.resources.push(adapter_resource); let config_json = serde_json::to_string(&configuration)?; diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index f61d627e8..0e92764d9 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -54,6 +54,7 @@ pub enum SubCommand { #[clap(name = "input", short, long, help = "The input to the export command as JSON")] input: String, }, + #[clap(name = "exporter", about = "Exports different types of resources")] Exporter { #[clap(name = "input", short, long, help = "The input to the exporter command as JSON")] diff --git a/tools/dsctest/src/export.rs b/tools/dsctest/src/export.rs index 328b0d948..2e8d162f9 100644 --- a/tools/dsctest/src/export.rs +++ b/tools/dsctest/src/export.rs @@ -9,4 +9,10 @@ use serde::{Deserialize, Serialize}; pub struct Export { /// Number of instances to return pub count: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub _kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub _name: Option, + #[serde(rename = "_securityContext", skip_serializing_if = "Option::is_none")] + pub _security_context: Option, } diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index 8d516e702..3be103a7e 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -94,7 +94,10 @@ fn main() { }; for i in 0..export.count { let instance = Export { - count: i + count: i, + _kind: Some("TestKind".to_string()), + _name: Some("TestName".to_string()), + _security_context: Some("elevated".to_string()), }; println!("{}", serde_json::to_string(&instance).unwrap()); }