diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 62d3525b2..c4b747daa 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -109,6 +109,7 @@ setInputEmpty = "Desired input is empty" testInputEmpty = "Expected input is required" jsonError = "JSON: %{err}" routingToDelete = "Routing to delete operation because _exist is false" +syntheticWhatIf = "Resource does not natively support what-if, engine will generate synthetic what-if" [subcommand] actualStateNotObject = "actual_state is not an object" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index dd331a516..add67b0bb 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -264,6 +264,10 @@ pub enum ResourceSubCommand { input: Option, #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] + output_format: Option, + #[clap(short = 'w', long, visible_aliases = ["dry-run", "noop"], help = t!("args.whatIf").to_string())] + what_if: bool, }, #[clap(name = "schema", about = "Get the JSON schema for a resource", arg_required_else_help = true)] Schema { diff --git a/dsc/src/resource_command.rs b/dsc/src/resource_command.rs index 54a20283a..cc4c0216a 100644 --- a/dsc/src/resource_command.rs +++ b/dsc/src/resource_command.rs @@ -6,12 +6,12 @@ use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_JSON_ERROR, EXIT_DSC_R use dsc_lib::configure::config_doc::{Configuration, ExecutionKind}; use dsc_lib::configure::add_resource_export_results_to_configuration; use dsc_lib::discovery::discovery_trait::DiscoveryFilter; -use dsc_lib::dscresources::{resource_manifest::Kind, invoke_result::{GetResult, ResourceGetResponse, ResourceSetResponse, SetResult}}; +use dsc_lib::dscresources::{resource_manifest::Kind, invoke_result::{DeleteResultKind, GetResult, ResourceGetResponse, ResourceSetResponse, SetResult}}; use dsc_lib::dscresources::dscresource::{Capability, get_diff}; use dsc_lib::dscerror::DscError; use rust_i18n::t; use serde_json::Value; -use tracing::{error, debug}; +use tracing::{debug, error, info}; use dsc_lib::{ dscresources::dscresource::{Invoke, DscResource}, @@ -258,7 +258,7 @@ pub fn test(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, in } } -pub fn delete(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str) { +pub fn delete(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str, format: Option<&OutputFormat>, what_if: bool) { let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); exit(EXIT_DSC_RESOURCE_NOT_FOUND); @@ -270,8 +270,27 @@ pub fn delete(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, exit(EXIT_DSC_ERROR); } - match resource.delete(input, &ExecutionKind::Actual) { - Ok(_) => {} + let execution_kind = if what_if { ExecutionKind::WhatIf } else { ExecutionKind::Actual }; + + match resource.delete(input, &execution_kind) { + Ok(result) => { + match result { + DeleteResultKind::ResourceActual => { + }, + DeleteResultKind::ResourceWhatIf(delete_result) => { + match serde_json::to_string(&delete_result) { + Ok(json) => write_object(&json, format, false), + Err(err) => { + error!("JSON: {err}"); + exit(EXIT_JSON_ERROR); + } + } + }, + DeleteResultKind::SyntheticWhatIf(_) => { + info!("{} {}", resource.type_name, t!("resource_command.syntheticWhatIf")); + } + } + }, Err(err) => { error!("{err}"); exit(EXIT_DSC_ERROR); diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index c89ced494..fcdaf471c 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -595,13 +595,13 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat let parsed_input = get_input(input.as_ref(), path.as_ref()); resource_command::test(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref()); }, - ResourceSubCommand::Delete { resource, version, input, file: path } => { + ResourceSubCommand::Delete { resource, version, input, file: path, output_format, what_if } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.as_deref(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); exit(EXIT_DSC_ERROR); } let parsed_input = get_input(input.as_ref(), path.as_ref()); - resource_command::delete(&mut dsc, resource, version.as_deref(), &parsed_input); + resource_command::delete(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref(), *what_if); }, } } diff --git a/dsc/tests/dsc_whatif.tests.ps1 b/dsc/tests/dsc_whatif.tests.ps1 index 7bab1e669..c7102d5ee 100644 --- a/dsc/tests/dsc_whatif.tests.ps1 +++ b/dsc/tests/dsc_whatif.tests.ps1 @@ -184,4 +184,19 @@ Describe 'whatif tests' { $out.results[0].result.afterState._exist | Should -BeFalse $out.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'whatIf' } + + It 'dsc resource delete supports what-if flag' { + $result = dsc resource delete -r Test/WhatIfDelete -i '{"_exist": false}' --what-if | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $result._metadata.whatIf | Should -Not -BeNullOrEmpty + $result._metadata.whatIf | Should -Contain 'Delete what-if message 1' + $result._metadata.whatIf | Should -Contain 'Delete what-if message 2' + } + + It 'dsc resource delete synthetic what-if logs info message and produces no output' { + $result = dsc -l info resource delete -r Test/Delete -i '{"_exist": false}' --what-if 2>&1 + $LASTEXITCODE | Should -Be 0 + "$result" | Should -Match 'generate synthetic what-if' + "$result" | Should -Not -Match '^\s*\{' + } }