Skip to content

Commit 415d60e

Browse files
authored
Merge pull request #171 from anmenaga/export_support
Export scenario support
2 parents 4a2a391 + 1c6b8c8 commit 415d60e

File tree

14 files changed

+447
-16
lines changed

14 files changed

+447
-16
lines changed

dsc/src/args.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ pub enum ConfigSubCommand {
5050
Test,
5151
#[clap(name = "validate", about = "Validate the current configuration", hide = true)]
5252
Validate,
53+
#[clap(name = "export", about = "Export the current configuration")]
54+
Export
5355
}
5456

5557
#[derive(Debug, PartialEq, Eq, Subcommand)]
@@ -65,6 +67,8 @@ pub enum ResourceSubCommand {
6567
},
6668
#[clap(name = "get", about = "Invoke the get operation to a resource", arg_required_else_help = true)]
6769
Get {
70+
#[clap(short, long, help = "Get all instances of the resource")]
71+
all: bool,
6872
#[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `get` on")]
6973
resource: String,
7074
#[clap(short, long, help = "The input to pass to the resource as JSON")]
@@ -89,6 +93,11 @@ pub enum ResourceSubCommand {
8993
#[clap(short, long, help = "The name of the resource to get the JSON schema")]
9094
resource: String,
9195
},
96+
#[clap(name = "export", about = "Retrieve all resource instances", arg_required_else_help = true)]
97+
Export {
98+
#[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `export` on")]
99+
resource: String,
100+
},
92101
}
93102

94103
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]

dsc/src/resource_command.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
use crate::args::OutputFormat;
55
use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_JSON_ERROR, add_type_name_to_json, write_output};
6+
use dsc_lib::configure::config_doc::Configuration;
7+
use dsc_lib::configure::add_resource_export_results_to_configuration;
8+
use dsc_lib::dscresources::invoke_result::GetResult;
69

710
use dsc_lib::{
811
dscresources::dscresource::{Invoke, DscResource},
@@ -42,6 +45,34 @@ pub fn get(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin:
4245
}
4346
}
4447

48+
pub fn get_all(dsc: &mut DscManager, resource: &str, _input: &Option<String>, _stdin: &Option<String>, format: &Option<OutputFormat>) {
49+
let resource = get_resource(dsc, resource);
50+
51+
let export_result = match resource.export() {
52+
Ok(export) => { export }
53+
Err(err) => {
54+
eprintln!("Error: {err}");
55+
exit(EXIT_DSC_ERROR);
56+
}
57+
};
58+
59+
for instance in export_result.actual_state
60+
{
61+
let get_result = GetResult {
62+
actual_state: instance.clone(),
63+
};
64+
65+
let json = match serde_json::to_string(&get_result) {
66+
Ok(json) => json,
67+
Err(err) => {
68+
eprintln!("JSON Error: {err}");
69+
exit(EXIT_JSON_ERROR);
70+
}
71+
};
72+
write_output(&json, format);
73+
}
74+
}
75+
4576
pub fn set(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
4677
let mut input = get_input(input, stdin);
4778
let mut resource = get_resource(dsc, resource);
@@ -129,6 +160,23 @@ pub fn schema(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat
129160
}
130161
}
131162

163+
pub fn export(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat>) {
164+
let dsc_resource = get_resource(dsc, resource);
165+
166+
let mut conf = Configuration::new();
167+
168+
add_resource_export_results_to_configuration(&dsc_resource, &mut conf);
169+
170+
let json = match serde_json::to_string(&conf) {
171+
Ok(json) => json,
172+
Err(err) => {
173+
eprintln!("JSON Error: {err}");
174+
exit(EXIT_JSON_ERROR);
175+
}
176+
};
177+
write_output(&json, format);
178+
}
179+
132180
pub fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource {
133181
// check if resource is JSON or just a name
134182
match serde_json::from_str(resource) {

dsc/src/subcommand.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,35 @@ pub fn config_test(configurator: Configurator, format: &Option<OutputFormat>)
8686
}
8787
}
8888

89+
pub fn config_export(configurator: Configurator, format: &Option<OutputFormat>)
90+
{
91+
match configurator.invoke_export(ErrorAction::Continue, || { /* code */ }) {
92+
Ok(result) => {
93+
let json = match serde_json::to_string(&result.result) {
94+
Ok(json) => json,
95+
Err(err) => {
96+
eprintln!("JSON Error: {err}");
97+
exit(EXIT_JSON_ERROR);
98+
}
99+
};
100+
write_output(&json, format);
101+
if result.had_errors {
102+
103+
for msg in result.messages
104+
{
105+
eprintln!("{:?} message {}", msg.level, msg.message);
106+
};
107+
108+
exit(EXIT_DSC_ERROR);
109+
}
110+
},
111+
Err(err) => {
112+
eprintln!("Error: {err}");
113+
exit(EXIT_DSC_ERROR);
114+
}
115+
}
116+
}
117+
89118
pub fn config(subcommand: &ConfigSubCommand, format: &Option<OutputFormat>, stdin: &Option<String>) {
90119
if stdin.is_none() {
91120
eprintln!("Configuration must be piped to STDIN");
@@ -134,6 +163,9 @@ pub fn config(subcommand: &ConfigSubCommand, format: &Option<OutputFormat>, stdi
134163
},
135164
ConfigSubCommand::Validate => {
136165
validate_config(&json_string);
166+
},
167+
ConfigSubCommand::Export => {
168+
config_export(configurator, format);
137169
}
138170
}
139171
}
@@ -353,8 +385,9 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,
353385
table.print();
354386
}
355387
},
356-
ResourceSubCommand::Get { resource, input } => {
357-
resource_command::get(&mut dsc, resource, input, stdin, format);
388+
ResourceSubCommand::Get { resource, input, all } => {
389+
if *all { resource_command::get_all(&mut dsc, resource, input, stdin, format); }
390+
else { resource_command::get(&mut dsc, resource, input, stdin, format); };
358391
},
359392
ResourceSubCommand::Set { resource, input } => {
360393
resource_command::set(&mut dsc, resource, input, stdin, format);
@@ -365,5 +398,8 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,
365398
ResourceSubCommand::Schema { resource } => {
366399
resource_command::schema(&mut dsc, resource, format);
367400
},
401+
ResourceSubCommand::Export { resource} => {
402+
resource_command::export(&mut dsc, resource, format);
403+
},
368404
}
369405
}

dsc/tests/dsc_export.tests.ps1

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'resource export tests' {
5+
6+
It 'Export can be called on individual resource' {
7+
8+
$out = dsc resource export -r Microsoft/Process
9+
$LASTEXITCODE | Should -Be 0
10+
$config_with_process_list = $out | ConvertFrom-Json
11+
$config_with_process_list.'$schema' | Should -BeExactly 'https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json'
12+
$config_with_process_list.'resources' | Should -Not -BeNullOrEmpty
13+
$config_with_process_list.resources.count | Should -BeGreaterThan 1
14+
}
15+
16+
It 'get --all can be called on individual resource' {
17+
18+
$out = dsc resource get --all -r Microsoft/Process
19+
$LASTEXITCODE | Should -Be 0
20+
$process_list = $out | ConvertFrom-Json
21+
$process_list.resources.count | Should -BeGreaterThan 1
22+
$process_list | % {$_.actualState | Should -Not -BeNullOrEmpty}
23+
}
24+
25+
It 'Export can be called on a configuration' {
26+
27+
$yaml = @'
28+
$schema: https://schemas.microsoft.com/dsc/2023/03/configuration.schema.json
29+
resources:
30+
- name: Processes
31+
type: Microsoft/Process
32+
properties:
33+
pid: 0
34+
'@
35+
$out = $yaml | dsc config export
36+
$LASTEXITCODE | Should -Be 0
37+
$config_with_process_list = $out | ConvertFrom-Json
38+
$config_with_process_list.'$schema' | Should -BeExactly 'https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json'
39+
$config_with_process_list.'resources' | Should -Not -BeNullOrEmpty
40+
$config_with_process_list.resources.count | Should -BeGreaterThan 1
41+
}
42+
43+
It 'Configuration Export can be piped to configuration Set' -Skip:(!$IsWindows) {
44+
45+
$yaml = @'
46+
$schema: https://schemas.microsoft.com/dsc/2023/03/configuration.schema.json
47+
resources:
48+
- name: Processes
49+
type: Microsoft/Process
50+
properties:
51+
pid: 0
52+
'@
53+
$out = $yaml | dsc config export | dsc config set
54+
$LASTEXITCODE | Should -Be 0
55+
$set_results = $out | ConvertFrom-Json
56+
$set_results.results.count | Should -BeGreaterThan 1
57+
}
58+
59+
It 'Duplicate resource types in Configuration Export should result in error' {
60+
61+
$yaml = @'
62+
$schema: https://schemas.microsoft.com/dsc/2023/03/configuration.schema.json
63+
resources:
64+
- name: Processes
65+
type: Microsoft/Process
66+
properties:
67+
pid: 0
68+
- name: Processes
69+
type: Microsoft/Process
70+
properties:
71+
pid: 0
72+
'@
73+
$out = $yaml | dsc config export 2>&1
74+
$LASTEXITCODE | Should -Be 2
75+
$out | Should -BeLike '*specified multiple times*'
76+
}
77+
}

dsc_lib/src/configure/config_doc.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,34 @@ impl Default for Configuration {
9191
}
9292
}
9393
}
94+
95+
impl Configuration {
96+
#[must_use]
97+
pub fn new() -> Self {
98+
Self {
99+
schema: SCHEMA.to_string(),
100+
parameters: None,
101+
variables: None,
102+
resources: Vec::new(),
103+
metadata: None,
104+
}
105+
}
106+
}
107+
108+
impl Resource {
109+
#[must_use]
110+
pub fn new() -> Self {
111+
Self {
112+
resource_type: String::new(),
113+
name: String::new(),
114+
depends_on: None,
115+
properties: None,
116+
}
117+
}
118+
}
119+
120+
impl Default for Resource {
121+
fn default() -> Self {
122+
Self::new()
123+
}
124+
}

dsc_lib/src/configure/config_result.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66
use crate::dscresources::invoke_result::{GetResult, SetResult, TestResult};
7+
use crate::configure::config_doc;
78

89
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
910
pub enum MessageLevel {
@@ -126,3 +127,29 @@ impl Default for ConfigurationTestResult {
126127
Self::new()
127128
}
128129
}
130+
131+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
132+
#[serde(deny_unknown_fields)]
133+
pub struct ConfigurationExportResult {
134+
pub result: Option<config_doc::Configuration>,
135+
pub messages: Vec<ResourceMessage>,
136+
#[serde(rename = "hadErrors")]
137+
pub had_errors: bool,
138+
}
139+
140+
impl ConfigurationExportResult {
141+
#[must_use]
142+
pub fn new() -> Self {
143+
Self {
144+
result: None,
145+
messages: Vec::new(),
146+
had_errors: false,
147+
}
148+
}
149+
}
150+
151+
impl Default for ConfigurationExportResult {
152+
fn default() -> Self {
153+
Self::new()
154+
}
155+
}

0 commit comments

Comments
 (0)