diff --git a/dsc/tests/dsc_resource_set.tests.ps1 b/dsc/tests/dsc_resource_set.tests.ps1 index dbf7887c2..4f3503a20 100644 --- a/dsc/tests/dsc_resource_set.tests.ps1 +++ b/dsc/tests/dsc_resource_set.tests.ps1 @@ -31,7 +31,7 @@ Describe 'Invoke a resource set directly' { $result = dsc resource set $alias -r Test/WhatIf --input '{"executionType":"Actual"}' | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $result.afterState.executionType | Should -BeExactly 'WhatIf' - $result.changedProperties | Should -BeExactly 'executionType' + $result.changedProperties | Should -Contain 'executionType' } It 'actual execution of WhatIf resource' { diff --git a/dsc/tests/dsc_whatif.tests.ps1 b/dsc/tests/dsc_whatif.tests.ps1 index ba838f8b6..554ab934d 100644 --- a/dsc/tests/dsc_whatif.tests.ps1 +++ b/dsc/tests/dsc_whatif.tests.ps1 @@ -181,4 +181,44 @@ Describe 'whatif tests' { $out.results[0].result.afterState._exist | Should -BeFalse $out.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'whatIf' } + + It 'Test/WhatIfReturnDiff resource returns state and diff in what-if mode' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: WhatIfReturn StateAndDiff + type: Test/WhatIfReturnDiff + properties: + executionType: Actual +"@ + $what_if_result = $config_yaml | dsc config set -w -f - | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $what_if_result.hadErrors | Should -BeFalse + $what_if_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'whatIf' + $what_if_result.results[0].result.beforeState.executionType | Should -BeExactly 'Actual' + $what_if_result.results[0].result.afterState.executionType | Should -BeExactly 'WhatIf' + $what_if_result.results[0].result.afterState.fromResource | Should -BeExactly 'ResourceProvidedDiff' + $what_if_result.results[0].result.changedProperties | Should -BeExactly 'fromResource' + $what_if_result.results.Count | Should -Be 1 + } + + It 'Test/WhatIfReturnDiff resource returns state only in actual mode' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: WhatIfReturn StateAndDiff + type: Test/WhatIfReturnDiff + properties: + executionType: Actual +"@ + $actual_result = $config_yaml | dsc config set -f - | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $actual_result.hadErrors | Should -BeFalse + $actual_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'actual' + $actual_result.results[0].result.beforeState.executionType | Should -BeExactly 'Actual' + $actual_result.results[0].result.afterState.executionType | Should -BeExactly 'Actual' + $actual_result.results[0].result.afterState.fromResource | Should -BeNullOrEmpty + $actual_result.results[0].result.changedProperties | Should -Be $null + $actual_result.results.Count | Should -Be 1 + } } diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index b2906c5bc..9825415d5 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -256,7 +256,13 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(&resource.directory), env, manifest.exit_codes.as_ref())?; - match set.returns { + let return_kind = if execution_type == &ExecutionKind::WhatIf { + set.what_if_returns.as_ref().or(set.returns.as_ref()) + } else { + set.returns.as_ref() + }; + + match return_kind { Some(ReturnKind::State) => { if resource.kind == Kind::Resource { diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index 886c789f0..57433da29 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -241,6 +241,9 @@ pub struct SetMethod { /// The type of return value expected from the Set method. #[serde(rename = "return", skip_serializing_if = "Option::is_none")] pub returns: Option, + /// The type of return value expected from the Set method when running in what-if mode. When specified, this overrides the `return` property during what-if execution. + #[serde(rename = "whatIfReturns", skip_serializing_if = "Option::is_none")] + pub what_if_returns: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)] diff --git a/resources/registry/registry.dsc.resource.json b/resources/registry/registry.dsc.resource.json index 8a5490869..40f416f61 100644 --- a/resources/registry/registry.dsc.resource.json +++ b/resources/registry/registry.dsc.resource.json @@ -25,8 +25,12 @@ { "jsonInputArg": "--input", "mandatory": true + }, + { + "whatIfArg": "-w" } - ] + ], + "whatIfReturns": "state" }, "delete": { "executable": "registry", @@ -36,21 +40,11 @@ { "jsonInputArg": "--input", "mandatory": true - } - ] - }, - "whatIf": { - "executable": "registry", - "args": [ - "config", - "set", - "-w", + }, { - "jsonInputArg": "--input", - "mandatory": true + "whatIfArg": "-w" } - ], - "return": "state" + ] }, "exitCodes": { "0": "Success", diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index dd3b5a386..aaeff203c 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -950,6 +950,40 @@ ] } } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/WhatIfReturnDiff", + "version": "0.1.0", + "description": "Test resource for validating whatIfReturn with stateAndDiff return type", + "get": { + "executable": "dsctest", + "args": [ + "whatif" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "whatif", + { + "whatIfArg": "-w" + }, + "--state-and-diff" + ], + "return": "state", + "whatIfReturns": "stateAndDiff" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "what-if" + ] + } + } } ] } diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index fd7b7d56e..b266ca6f4 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -147,6 +147,8 @@ pub enum SubCommand { WhatIf { #[clap(name = "whatif", short, long, help = "Run as a whatif executionType instead of actual executionType")] what_if: bool, + #[clap(name = "state-and-diff", short, long, help = "Output stateAndDiff format (state on first line, diff array on second line)")] + state_and_diff: bool, }, #[clap(name = "whatif-delete", about = "Check if it is a whatif delete operation")] diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index 5da8dc48a..0c9526631 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -325,13 +325,42 @@ fn main() { }; serde_json::to_string(&version).unwrap() }, - SubCommand::WhatIf { what_if } => { + SubCommand::WhatIf { what_if, state_and_diff } => { let result: WhatIf = if what_if { - WhatIf { execution_type: "WhatIf".to_string(), exist: None } + if state_and_diff { + WhatIf { + execution_type: "WhatIf".to_string(), + exist: None, + from_resource: Some("ResourceProvidedDiff".to_string()) + } + } else { + WhatIf { + execution_type: "WhatIf".to_string(), + exist: None, + from_resource: None + } + } } else { - WhatIf { execution_type: "Actual".to_string(), exist: None } + WhatIf { + execution_type: "Actual".to_string(), + exist: None, + from_resource: None + } }; - serde_json::to_string(&result).unwrap() + + // Output state as first line + let state_json = serde_json::to_string(&result).unwrap(); + println!("{state_json}"); + + // If state_and_diff is requested and it's a what-if operation, output diff on second line + if state_and_diff && what_if { + let diff = vec!["fromResource"]; + let diff_json = serde_json::to_string(&diff).unwrap(); + println!("{diff_json}"); + } + + // Return empty string since we already printed + String::new() }, SubCommand::WhatIfDelete { what_if } => { let result = if what_if { diff --git a/tools/dsctest/src/whatif.rs b/tools/dsctest/src/whatif.rs index 06850d4b1..586753089 100644 --- a/tools/dsctest/src/whatif.rs +++ b/tools/dsctest/src/whatif.rs @@ -11,4 +11,6 @@ pub struct WhatIf { pub execution_type: String, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option, + #[serde(rename = "fromResource", skip_serializing_if = "Option::is_none")] + pub from_resource: Option, }