diff --git a/dsc/tests/dsc_args.tests.ps1 b/dsc/tests/dsc_args.tests.ps1 index 239aa784..8d1849f7 100644 --- a/dsc/tests/dsc_args.tests.ps1 +++ b/dsc/tests/dsc_args.tests.ps1 @@ -97,7 +97,7 @@ actualState: $resource = $obj | y2j | ConvertFrom-Json $resource | Should -Not -BeNullOrEmpty $resource.Type | Should -BeLike '*/*' - $resource.Kind | Should -BeIn ('resource', 'group', 'importer', 'adapter') + $resource.Kind | Should -BeIn ('resource', 'group', 'exporter', 'importer', 'adapter') } } diff --git a/dsc/tests/dsc_export.tests.ps1 b/dsc/tests/dsc_export.tests.ps1 index 16b04616..f4b5b743 100644 --- a/dsc/tests/dsc_export.tests.ps1 +++ b/dsc/tests/dsc_export.tests.ps1 @@ -135,4 +135,30 @@ Describe 'resource export tests' { $out.metadata.hello | Should -BeExactly 'world' $out.metadata.'Microsoft.DSC'.operation | Should -BeExactly 'export' } + + It 'Works with Exporter resource' { + $yaml = @' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: export this + type: Test/Exporter + properties: + typeNames: + - Test/Foo + - Test/Bar +'@ + $out = dsc config export -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.resources | Should -HaveCount 2 + $out.resources[0].type | Should -BeExactly 'Test/Foo' + $out.resources[0].name | Should -BeExactly 'test' + $out.resources[0].properties.psobject.properties | Should -HaveCount 2 + $out.resources[0].properties.foo | Should -BeExactly 'bar' + $out.resources[0].properties.hello | Should -BeExactly 'world' + $out.resources[1].type | Should -BeExactly 'Test/Bar' + $out.resources[1].name | Should -BeExactly 'test' + $out.resources[1].properties.psobject.properties | Should -HaveCount 2 + $out.resources[1].properties.foo | Should -BeExactly 'bar' + $out.resources[1].properties.hello | Should -BeExactly 'world' + } } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index bbb771cd..91fd5bc4 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::configure::config_doc::{ExecutionKind, Metadata}; +use crate::configure::config_doc::{ExecutionKind, Metadata, Resource}; use crate::configure::parameters::Input; use crate::dscerror::DscError; use crate::dscresources::invoke_result::ExportResult; @@ -62,14 +62,21 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, adap _ => resource.export(input)? }; - for (i, instance) in export_result.actual_state.iter().enumerate() { - let mut r = 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())?; - r.properties = escape_property_values(&props)?; - - conf.resources.push(r); + if resource.kind == Kind::Exporter { + for instance in &export_result.actual_state { + let resource = serde_json::from_value::(instance.clone())?; + conf.resources.push(resource); + } + } else { + for (i, instance) in export_result.actual_state.iter().enumerate() { + let mut r = 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())?; + r.properties = escape_property_values(&props)?; + + conf.resources.push(r); + } } Ok(export_result) diff --git a/dsc_lib/src/dscresources/resource_manifest.rs b/dsc_lib/src/dscresources/resource_manifest.rs index 49825a19..b432c39d 100644 --- a/dsc_lib/src/dscresources/resource_manifest.rs +++ b/dsc_lib/src/dscresources/resource_manifest.rs @@ -14,6 +14,7 @@ use crate::{dscerror::DscError, schemas::DscRepoSchema}; #[serde(rename_all = "camelCase")] pub enum Kind { Adapter, + Exporter, Group, Importer, Resource, diff --git a/tools/dsctest/dscexporter.dsc.resource.json b/tools/dsctest/dscexporter.dsc.resource.json new file mode 100644 index 00000000..4656df10 --- /dev/null +++ b/tools/dsctest/dscexporter.dsc.resource.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Exporter", + "version": "0.1.0", + "kind": "exporter", + "export": { + "executable": "dsctest", + "args": [ + "exporter", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "exporter" + ] + } + } +} diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index 8e4908ba..f61d627e 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -10,6 +10,7 @@ pub enum Schemas { ExitCode, InDesiredState, Export, + Exporter, Sleep, Trace, WhatIf, @@ -53,6 +54,11 @@ 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")] + input: String, + }, #[clap(name = "schema", about = "Get the JSON schema for a subcommand")] Schema { diff --git a/tools/dsctest/src/exporter.rs b/tools/dsctest/src/exporter.rs new file mode 100644 index 00000000..b18f42ad --- /dev/null +++ b/tools/dsctest/src/exporter.rs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct Resource { + pub name: String, + pub r#type: String, + pub properties: Map, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct Exporter { + #[serde(rename = "typeNames")] + pub type_names: Vec, +} diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index c1b3f8d5..8d516e70 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -7,6 +7,7 @@ mod exist; mod exit_code; mod in_desired_state; mod export; +mod exporter; mod sleep; mod trace; mod whatif; @@ -14,11 +15,13 @@ mod whatif; use args::{Args, Schemas, SubCommand}; use clap::Parser; use schemars::schema_for; +use serde_json::Map; use crate::delete::Delete; use crate::exist::{Exist, State}; use crate::exit_code::ExitCode; use crate::in_desired_state::InDesiredState; use crate::export::Export; +use crate::exporter::{Exporter, Resource}; use crate::sleep::Sleep; use crate::trace::Trace; use crate::whatif::WhatIf; @@ -97,6 +100,26 @@ fn main() { } String::new() }, + SubCommand::Exporter { input } => { + let exporter = match serde_json::from_str::(&input) { + Ok(exporter) => exporter, + Err(err) => { + eprintln!("Error JSON does not match schema: {err}"); + std::process::exit(1); + } + }; + for type_name in exporter.type_names { + let mut resource = Resource { + name: "test".to_string(), + r#type: type_name, + properties: Map::new(), + }; + resource.properties.insert("foo".to_string(), serde_json::Value::String("bar".to_string())); + resource.properties.insert("hello".to_string(), serde_json::Value::String("world".to_string())); + println!("{}", serde_json::to_string(&resource).unwrap()); + } + String::new() + }, SubCommand::Schema { subcommand } => { let schema = match subcommand { Schemas::Delete => { @@ -114,6 +137,9 @@ fn main() { Schemas::Export => { schema_for!(Export) }, + Schemas::Exporter => { + schema_for!(Exporter) + }, Schemas::Sleep => { schema_for!(Sleep) },