diff --git a/dsc/tests/dsc_export.tests.ps1 b/dsc/tests/dsc_export.tests.ps1 index bf0325d26..2924d4bc3 100644 --- a/dsc/tests/dsc_export.tests.ps1 +++ b/dsc/tests/dsc_export.tests.ps1 @@ -105,4 +105,22 @@ Describe 'resource export tests' { $config_with_process_list.'resources' | Should -Not -BeNullOrEmpty $config_with_process_list.resources.count | Should -BeGreaterThan 1 } + + It 'Export for config preserves metadata' { + $yaml = @' + $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json + metadata: + winget: + processor: dscv3 + hello: world + resources: + - name: OS + type: Microsoft/OSInfo +'@ + $out = $yaml | dsc config export -f - | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.metadata.winget.processor | Should -BeExactly 'dscv3' + $out.metadata.hello | Should -BeExactly 'world' + $out.metadata.'Microsoft.DSC'.operation | Should -BeExactly 'export' + } } diff --git a/dsc_lib/src/configure/config_doc.rs b/dsc_lib/src/configure/config_doc.rs index e299cdbf0..a0e6edacc 100644 --- a/dsc_lib/src/configure/config_doc.rs +++ b/dsc_lib/src/configure/config_doc.rs @@ -72,6 +72,8 @@ pub struct MicrosoftDscMetadata { pub struct Metadata { #[serde(rename = "Microsoft.DSC", skip_serializing_if = "Option::is_none")] pub microsoft: Option, + #[serde(flatten)] + pub other: Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -85,7 +87,7 @@ pub struct Configuration { #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option>, + pub variables: Option>, pub resources: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, @@ -110,7 +112,7 @@ pub struct Parameter { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option>, + pub metadata: Option>, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -144,7 +146,7 @@ pub struct Resource { #[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 { diff --git a/dsc_lib/src/configure/context.rs b/dsc_lib/src/configure/context.rs index 15af8f523..fcf268d7b 100644 --- a/dsc_lib/src/configure/context.rs +++ b/dsc_lib/src/configure/context.rs @@ -4,18 +4,18 @@ use chrono::{DateTime, Local}; use crate::configure::config_doc::ExecutionKind; use security_context_lib::{get_security_context, SecurityContext}; -use serde_json::Value; +use serde_json::{Map, Value}; use std::{collections::HashMap, path::PathBuf}; use super::config_doc::{DataType, SecurityContextKind}; pub struct Context { pub execution_type: ExecutionKind, - pub references: HashMap, + pub references: Map, pub system_root: PathBuf, pub parameters: HashMap, pub security_context: SecurityContextKind, - pub variables: HashMap, + pub variables: Map, pub start_datetime: DateTime, } @@ -24,14 +24,14 @@ impl Context { pub fn new() -> Self { Self { execution_type: ExecutionKind::Actual, - references: HashMap::new(), + references: Map::new(), system_root: get_default_os_system_root(), parameters: HashMap::new(), security_context: match get_security_context() { SecurityContext::Admin => SecurityContextKind::Elevated, SecurityContext::User => SecurityContextKind::Restricted, }, - variables: HashMap::new(), + variables: Map::new(), start_datetime: chrono::Local::now(), } } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 1e002e651..bbb771cd6 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -278,7 +278,8 @@ impl Configurator { duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()), ..Default::default() } - ) + ), + other: Map::new(), } ), name: resource.name.clone(), @@ -426,7 +427,8 @@ impl Configurator { duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()), ..Default::default() } - ) + ), + other: Map::new(), } ), name: resource.name.clone(), @@ -497,7 +499,8 @@ impl Configurator { duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()), ..Default::default() } - ) + ), + other: Map::new(), } ), name: resource.name.clone(), @@ -527,6 +530,7 @@ impl Configurator { pub fn invoke_export(&mut self) -> Result { let mut result = ConfigurationExportResult::new(); let mut conf = config_doc::Configuration::new(); + conf.metadata.clone_from(&self.config.metadata); let mut progress = ProgressBar::new(self.config.resources.len() as u64, self.progress_format)?; let resources = self.config.resources.clone(); @@ -552,7 +556,17 @@ impl Configurator { progress.write_increment(1); } - conf.metadata = Some(self.get_result_metadata(Operation::Export)); + let export_metadata = self.get_result_metadata(Operation::Export); + match conf.metadata { + Some(mut metadata) => { + metadata.microsoft = export_metadata.microsoft; + conf.metadata = Some(metadata); + }, + _ => { + conf.metadata = Some(export_metadata); + }, + } + result.result = Some(conf); Ok(result) } @@ -685,7 +699,8 @@ impl Configurator { duration: Some(end_datetime.signed_duration_since(self.context.start_datetime).to_string()), security_context: Some(self.context.security_context.clone()), } - ) + ), + other: Map::new(), } }