From ab21588e0f7d0e8c92b832d4f493478e4155aef4 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:54:57 +0100 Subject: [PATCH 1/4] (GH-1655) Allow adapted resources to define the return kind for set and test --- CHANGELOG.md | 17 +++ .../discovery/dsc_adaptedResource.tests.ps1 | 107 ++++++++++++++++++ lib/dsc-lib/locales/en-us.toml | 1 + .../dscresources/adapted_resource_manifest.rs | 8 +- .../src/dscresources/command_resource.rs | 44 ++++++- tools/dsctest/dsctest.dsc.manifests.json | 78 +++++++++++++ tools/dsctest/src/adapter.rs | 11 ++ 7 files changed, 262 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ced61d69..947aab98c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,22 @@ changes since the last release, see the [diff on GitHub][unreleased]. +- Added the `return` property to the `set` and `test` operations in adapted resource manifests. + Resource authors can set it to `state` or `stateAndDiff` to declare what the adapter emits when + it invokes that operation for the adapted resource. Previously, every adapted resource inherited + the return kind from the manifest of its adapter. When an adapted resource manifest defines + `return` for an operation, DSC uses that value instead of the one in the adapter's manifest. + Adapted resources that don't define the property continue to inherit it from the adapter. The + override applies when the adapter is invoked for a single resource instance, which is the case + for adapters that set `inputKind` to `single`. + +
Related work items + + - Issues: [#1655][#1655] + - PRs: _None_ + +
+ - Added what-if support to the MCP server tools. The `invoke_dsc_config` tool now accepts a `what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if` for the `set` and `delete` operations. This mirrors the `--what-if` flag on the `dsc config set`, @@ -2029,6 +2045,7 @@ Version `3.0.0` is the first generally available release of DSC. [#1557]: https://github.com/PowerShell/DSC/issues/1557 [#1558]: https://github.com/PowerShell/DSC/issues/1558 [#1562]: https://github.com/PowerShell/DSC/issues/1562 +[#1655]: https://github.com/PowerShell/DSC/issues/1655 [#1729]: https://github.com/PowerShell/DSC/issues/1729 [#1755]: https://github.com/PowerShell/DSC/issues/1755 [#1758]: https://github.com/PowerShell/DSC/issues/1758 diff --git a/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 b/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 index 681665715..e5b69d7d0 100644 --- a/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 +++ b/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 @@ -54,4 +54,111 @@ Describe 'Tests for adapted resources' { } } } + + Context 'Return kind for set and test' { + BeforeAll { + $desired = @{ one = 'desired' } | ConvertTo-Json -Compress + } + + It 'Inherits the return kind of the adapter for when the adapted resource does not define it' -TestCases @( + @{ operation = 'test'; stateProperty = 'actualState' } + @{ operation = 'set'; stateProperty = 'afterState' } + ) { + param($operation, $stateProperty) + + $out = dsc resource $operation -r Adapted/Three -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.txt -Raw) + $out.$stateProperty.one | Should -BeExactly 'value3' + } + + It 'Uses return kind state from the adapted resource manifest for test' { + $out = dsc -l debug resource test -r Adapted/ReturnState -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.actualState.one | Should -BeExactly 'fromOperation' + $out.inDesiredState | Should -BeFalse + $out.differingProperties | Should -Be @('one') + $errorTxt | Should -BeLike "*Adapted resource 'Adapted/ReturnState' defines return kind 'state' for operation 'test'*" + } + + It 'Uses return kind stateAndDiff from the adapted resource manifest for test' { + $out = dsc -l debug resource test -r Adapted/ReturnStateAndDiff -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.actualState.one | Should -BeExactly 'fromOperation' + $out.inDesiredState | Should -BeFalse + $out.differingProperties | Should -Be @('fromAdapter') + $errorTxt | Should -BeLike "*Adapted resource 'Adapted/ReturnStateAndDiff' defines return kind 'stateAndDiff' for operation 'test'*" + } + + It 'Uses return kind state from the adapted resource manifest for set' { + $out = dsc -l debug resource set -r Adapted/ReturnState -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.beforeState.one | Should -BeExactly 'fromGet' + $out.afterState.one | Should -BeExactly 'fromOperation' + $out.changedProperties | Should -Be @('one') + $errorTxt | Should -BeLike "*Adapted resource 'Adapted/ReturnState' defines return kind 'state' for operation 'set'*" + } + + It 'Uses return kind stateAndDiff from the adapted resource manifest for set' { + $out = dsc -l debug resource set -r Adapted/ReturnStateAndDiff -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.beforeState.one | Should -BeExactly 'fromGet' + $out.afterState.one | Should -BeExactly 'fromOperation' + $out.changedProperties | Should -Be @('fromAdapter') + $errorTxt | Should -BeLike "*Adapted resource 'Adapted/ReturnStateAndDiff' defines return kind 'stateAndDiff' for operation 'set'*" + } + + It 'Uses the return kind from the adapted resource manifest in a configuration' { + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + resources = @( + @{ + name = 'state and diff' + type = 'Adapted/ReturnStateAndDiff' + properties = @{ one = 'desired' } + } + ) + } | ConvertTo-Json -Depth 5 -Compress + $out = dsc config test -i $config 2>$TestDrive/error.txt | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.txt -Raw) + $out.results[0].result.actualState.one | Should -BeExactly 'fromOperation' + $out.results[0].result.differingProperties | Should -Be @('fromAdapter') + } + + It 'Rejects an adapted resource manifest with an invalid return kind for ' -TestCases @( + @{ operation = 'set' } + @{ operation = 'test' } + ) { + param($operation) + + $manifest = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json' + type = 'Adapted/InvalidReturn' + kind = 'resource' + version = '1.0.0' + capabilities = @('get', 'set', 'test') + requireAdapter = 'Test/Adapter' + path = 'invalidReturn.dsc.adaptedResource.json' + $operation = @{ return = 'diff' } + schema = @{ embedded = @{ type = 'object' } } + } | ConvertTo-Json -Depth 5 + $manifestDir = Join-Path $TestDrive "invalidReturn-$operation" + $null = New-Item -Path $manifestDir -ItemType Directory + Set-Content -Path (Join-Path $manifestDir 'invalidReturn.dsc.adaptedResource.json') -Value $manifest + + $oldPath = $env:DSC_RESOURCE_PATH + try { + $env:DSC_RESOURCE_PATH = $manifestDir + [System.IO.Path]::PathSeparator + (Split-Path (Get-Command dsctest).Source) + $out = dsc resource list 'Adapted/InvalidReturn' 2>$TestDrive/error.txt + $out | Should -BeNullOrEmpty + Get-Content -Path $TestDrive/error.txt -Raw | Should -BeLike '*unknown variant*diff*' + } + finally { + $env:DSC_RESOURCE_PATH = $oldPath + } + } + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index dc66170cc..aa453911b 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -238,6 +238,7 @@ exportNotSupportedUsingGet = "Export is not supported by resource '%{resource}' runProcessError = "Failed to run process '%{executable}': %{error}" whatIfWarning = "Resource '%{resource}' uses deprecated 'whatIf' operation. See https://github.com/PowerShell/DSC/issues/1361 for migration information." securityContextRequired = "Operation '%{operation}' for resource '%{resource}' requires security context '%{context}'" +adaptedReturnKind = "Adapted resource '%{resource}' defines return kind '%{kind}' for operation '%{operation}', overriding the adapter manifest" noAdaptedContent = "No adapted content available for resource '%{resource}'" invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}" exportFilteringNotSupported = "Resource '%{resource}' does not support export filtering" diff --git a/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs b/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs index aff0cba3b..f8e762068 100644 --- a/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::{ - configure::config_doc::SecurityContextKind, dscresources::{dscresource::ResourceCapability, resource_manifest::{Kind, ResourceManifest}}, schemas::dsc_repo::DscRepoSchema, types::{FullyQualifiedTypeName, ResourceVersion}, + configure::config_doc::SecurityContextKind, dscresources::{dscresource::ResourceCapability, resource_manifest::{Kind, ResourceManifest, ReturnKind}}, schemas::dsc_repo::DscRepoSchema, types::{FullyQualifiedTypeName, ResourceVersion}, }; use rust_i18n::t; use schemars::JsonSchema; @@ -30,6 +30,9 @@ pub struct GetOperation { #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct SetOperation { pub require_security_context: Option, + /// The type of return value the adapter emits when it invokes the Set operation for this resource. When specified, this overrides the `return` property of the Set method in the adapter's manifest. + #[serde(rename = "return", skip_serializing_if = "Option::is_none")] + pub returns: Option, } #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -44,6 +47,9 @@ pub struct DeleteOperation { #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct TestOperation { pub require_security_context: Option, + /// The type of return value the adapter emits when it invokes the Test operation for this resource. When specified, this overrides the `return` property of the Test method in the adapter's manifest. + #[serde(rename = "return", skip_serializing_if = "Option::is_none")] + pub returns: Option, } #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 01cd3f3ae..3a68316a3 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -227,10 +227,11 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, contex let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, resource.directory.as_deref(), env, manifest.exit_codes.as_ref())?; + let returns = resolve_return_kind(context.target_resource.as_ref(), set.returns.as_ref(), &Operation::Set); let return_kind = if context.execution_type == ExecutionKind::WhatIf { - set.what_if_returns.as_ref().or(set.returns.as_ref()) + set.what_if_returns.as_ref().or(returns) } else { - set.returns.as_ref() + returns }; match return_kind { @@ -348,7 +349,7 @@ pub fn invoke_test(resource: &DscResource, expected: &str, context: &Context) -> } let mut expected_value: Value = serde_json::from_str(expected)?; - match test.returns { + match resolve_return_kind(context.target_resource.as_ref(), test.returns.as_ref(), &Operation::Test) { Some(ReturnKind::State) => { if command_resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.testVerifyOutput", resource = &command_resource.type_name, executable = &test.executable)); @@ -1367,6 +1368,43 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str "" } +/// Resolve the kind of output DSC expects from the command for a `set` or `test` operation. +/// +/// An adapted resource manifest can define `return` for its `set` and `test` operations. When +/// it does, that value takes precedence over the `return` defined for the same method in the +/// manifest of the adapter that invokes the command. Otherwise the adapted resource inherits +/// the value from the adapter's manifest. +/// +/// # Arguments +/// +/// * `target_resource` - The adapted resource the command is invoked for, if any. +/// * `manifest_return` - The return kind defined for the method in the manifest of the resource +/// or adapter that owns the command. +/// * `operation` - The operation being invoked. Only [`Operation::Set`] and [`Operation::Test`] +/// can be overridden by an adapted resource manifest. +/// +/// # Returns +/// +/// The return kind from the adapted resource manifest when it defines one for the operation, +/// otherwise `manifest_return`. +fn resolve_return_kind<'a>(target_resource: Option<&'a DscResource>, manifest_return: Option<&'a ReturnKind>, operation: &Operation) -> Option<&'a ReturnKind> { + let adapted_return = target_resource + .and_then(|resource| resource.adapted_manifest.as_ref()) + .and_then(|adapted_manifest| match operation { + Operation::Set => adapted_manifest.set.as_ref().and_then(|set| set.returns.as_ref()), + Operation::Test => adapted_manifest.test.as_ref().and_then(|test| test.returns.as_ref()), + _ => None, + }); + if let (Some(resource), Some(return_kind)) = (target_resource, adapted_return) { + let kind = match return_kind { + ReturnKind::State => "state", + ReturnKind::StateAndDiff => "stateAndDiff", + }; + debug!("{}", t!("dscresources.commandResource.adaptedReturnKind", resource = &resource.type_name, operation = operation, kind = kind)); + } + adapted_return.or(manifest_return) +} + pub(crate)fn validate_security_context(target_resource: Option<&DscResource>, required_security_context: &Option, resource_type: &str, operation: &Operation) -> Result<(), DscError> { if let Some(resource) = target_resource && let Some(adapted_manifest) = &resource.adapted_manifest { let require_security_context = match operation { diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 0bf980174..bf51272d7 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -331,6 +331,84 @@ } } } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "Adapted/ReturnState", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test" + ], + "description": "An adapted resource that overrides the return kind of the adapter with 'state'.", + "author": "DSC Team", + "requireAdapter": "Test/Adapter", + "path": "adaptedTest.dsc.adaptedResource.json", + "set": { + "return": "state" + }, + "test": { + "return": "state" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "Adapted/ReturnState", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "one": { + "type": "string", + "title": "Property One", + "description": "This is property one of the adapted resource." + } + } + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "Adapted/ReturnStateAndDiff", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test" + ], + "description": "An adapted resource that overrides the return kind of the adapter with 'stateAndDiff'.", + "author": "DSC Team", + "requireAdapter": "Test/Adapter", + "path": "adaptedTest.dsc.adaptedResource.json", + "set": { + "return": "stateAndDiff" + }, + "test": { + "return": "stateAndDiff" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "Adapted/ReturnStateAndDiff", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "one": { + "type": "string", + "title": "Property One", + "description": "This is property one of the adapted resource." + } + } + } + } } ], "resources": [ diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index 2b0b7136d..6bb29ac0c 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -149,6 +149,9 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res }; Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) }, + "Adapted/ReturnState" | "Adapted/ReturnStateAndDiff" => { + Ok(serde_json::json!({ "one": "fromGet" }).to_string()) + }, _ => Err(format!("Unknown resource type: {resource_type}")), } }, @@ -190,6 +193,14 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res .map_err(|e| format!("Failed to parse input for Adapted/SecurityContextCurrent: {e}"))?; Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) }, + "Adapted/ReturnState" => { + Ok(serde_json::json!({ "one": "fromOperation" }).to_string()) + }, + "Adapted/ReturnStateAndDiff" => { + println!("{}", serde_json::json!({ "one": "fromOperation" })); + println!("{}", serde_json::json!(["fromAdapter"])); + Ok(String::new()) + }, _ => Err(format!("Unknown resource type: {resource_type}")), } }, From f9d1dac7845ff13b34c07f0e1336bbf9f3d8db02 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:59:05 +0100 Subject: [PATCH 2/4] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 947aab98c..d578260d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,7 +119,7 @@ changes since the last release, see the [diff on GitHub][unreleased].
Related work items - Issues: [#1655][#1655] - - PRs: _None_ + - PRs: [#1802][#1802]
@@ -2050,3 +2050,4 @@ Version `3.0.0` is the first generally available release of DSC. [#1755]: https://github.com/PowerShell/DSC/issues/1755 [#1758]: https://github.com/PowerShell/DSC/issues/1758 [#1759]: https://github.com/PowerShell/DSC/issues/1759 +[#1802]: https://github.com/PowerShell/DSC/issues/1802 From 5e49ccb5f25562b444e372c8817f149bdb84c955 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:45:02 +0100 Subject: [PATCH 3/4] Add support for adapted resources to define return kinds and extend WhatIf operation --- .../src/dscresources/command_resource.rs | 13 +- tools/dsctest/dsctest.dsc.manifests.json | 145 ++++++++++++++++++ tools/dsctest/src/adapter.rs | 16 +- tools/dsctest/src/args.rs | 1 + 4 files changed, 171 insertions(+), 4 deletions(-) diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 3a68316a3..f5e016a56 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -101,6 +101,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, contex }; let operation_type: String; let mut is_synthetic_what_if = false; + let mut is_legacy_what_if = false; let command_resource = match context.target_resource.as_ref() { Some(target) => target, None => resource @@ -124,6 +125,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, contex &manifest.set } else if manifest.what_if.is_some() { warn!("{}", t!("dscresources.commandResource.whatIfWarning", resource = &command_resource.type_name)); + is_legacy_what_if = true; &manifest.what_if } else { is_synthetic_what_if = true; @@ -227,7 +229,13 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, contex let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, resource.directory.as_deref(), env, manifest.exit_codes.as_ref())?; - let returns = resolve_return_kind(context.target_resource.as_ref(), set.returns.as_ref(), &Operation::Set); + // The adapted resource manifest defines `return` for the `set` method. The legacy `whatIf` + // method is a separate command with its own `return`, so the override doesn't apply to it. + let returns = if is_legacy_what_if { + set.returns.as_ref() + } else { + resolve_return_kind(context.target_resource.as_ref(), set.returns.as_ref(), &Operation::Set) + }; let return_kind = if context.execution_type == ExecutionKind::WhatIf { set.what_if_returns.as_ref().or(returns) } else { @@ -1375,6 +1383,9 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str /// manifest of the adapter that invokes the command. Otherwise the adapted resource inherits /// the value from the adapter's manifest. /// +/// The override describes the output of the adapter's `set` and `test` methods. Don't call this +/// function for the legacy `whatIf` method, which defines its own `return`. +/// /// # Arguments /// /// * `target_resource` - The adapted resource the command is invoked for, if any. diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index bf51272d7..34900e599 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -409,6 +409,78 @@ } } } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "AdaptedReturn/Inherit", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test" + ], + "description": "An adapted resource that inherits the return kind 'stateAndDiff' its adapter defines.", + "author": "DSC Team", + "requireAdapter": "Test/AdapterReturn", + "path": "adaptedTest.dsc.adaptedResource.json", + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "AdaptedReturn/Inherit", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "one": { + "type": "string", + "title": "Property One", + "description": "This is property one of the adapted resource." + } + } + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "AdaptedReturn/Override", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test" + ], + "description": "An adapted resource that overrides the return kind 'stateAndDiff' its adapter defines with 'state'.", + "author": "DSC Team", + "requireAdapter": "Test/AdapterReturn", + "path": "adaptedTest.dsc.adaptedResource.json", + "set": { + "return": "state" + }, + "test": { + "return": "state" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "AdaptedReturn/Override", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "one": { + "type": "string", + "title": "Property One", + "description": "This is property one of the adapted resource." + } + } + } + } } ], "resources": [ @@ -1446,6 +1518,79 @@ } } }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/AdapterReturn", + "version": "0.1.0", + "kind": "adapter", + "description": "An adapter that defines the return kind 'stateAndDiff' for set, test and the legacy whatIf method.", + "adapter": { + "inputKind": "single" + }, + "get": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "get", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "set", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ], + "return": "stateAndDiff" + }, + "whatIf": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "what-if", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ], + "return": "stateAndDiff" + }, + "test": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "test", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ], + "return": "stateAndDiff" + } + }, { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Test/Adapter", diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index 6bb29ac0c..0e09e6a20 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -149,7 +149,7 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res }; Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) }, - "Adapted/ReturnState" | "Adapted/ReturnStateAndDiff" => { + "Adapted/ReturnState" | "Adapted/ReturnStateAndDiff" | "AdaptedReturn/Inherit" | "AdaptedReturn/Override" => { Ok(serde_json::json!({ "one": "fromGet" }).to_string()) }, _ => Err(format!("Unknown resource type: {resource_type}")), @@ -193,10 +193,10 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res .map_err(|e| format!("Failed to parse input for Adapted/SecurityContextCurrent: {e}"))?; Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) }, - "Adapted/ReturnState" => { + "Adapted/ReturnState" | "AdaptedReturn/Override" => { Ok(serde_json::json!({ "one": "fromOperation" }).to_string()) }, - "Adapted/ReturnStateAndDiff" => { + "Adapted/ReturnStateAndDiff" | "AdaptedReturn/Inherit" => { println!("{}", serde_json::json!({ "one": "fromOperation" })); println!("{}", serde_json::json!(["fromAdapter"])); Ok(String::new()) @@ -204,6 +204,16 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res _ => Err(format!("Unknown resource type: {resource_type}")), } }, + AdapterOperation::WhatIf => { + match resource_type { + "AdaptedReturn/Inherit" | "AdaptedReturn/Override" => { + println!("{}", serde_json::json!({ "one": "fromWhatIf" })); + println!("{}", serde_json::json!(["fromWhatIf"])); + Ok(String::new()) + }, + _ => Err(format!("Unknown resource type: {resource_type}")), + } + }, AdapterOperation::Delete => { match resource_type { "Adapted/One" => { diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index 54f5f609d..e593dcdda 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -51,6 +51,7 @@ pub enum AdapterOperation { Export, Validate, Schema, + WhatIf, } #[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] From 79e9a220c88356bdca2e8f61cd73db29a0d3fe5a Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:45:26 +0100 Subject: [PATCH 4/4] Add new tests --- .../discovery/dsc_adaptedResource.tests.ps1 | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 b/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 index e5b69d7d0..543242e72 100644 --- a/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 +++ b/dsc/tests/discovery/dsc_adaptedResource.tests.ps1 @@ -128,6 +128,43 @@ Describe 'Tests for adapted resources' { $out.results[0].result.differingProperties | Should -Be @('fromAdapter') } + It 'Inherits the return kind the adapter defines for when the adapted resource does not define it' -TestCases @( + @{ operation = 'test'; stateProperty = 'actualState'; diffProperty = 'differingProperties' } + @{ operation = 'set'; stateProperty = 'afterState'; diffProperty = 'changedProperties' } + ) { + param($operation, $stateProperty, $diffProperty) + + $out = dsc -l debug resource $operation -r AdaptedReturn/Inherit -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.$stateProperty.one | Should -BeExactly 'fromOperation' + $out.$diffProperty | Should -Be @('fromAdapter') + $errorTxt | Should -Not -BeLike "*Adapted resource 'AdaptedReturn/Inherit' defines return kind*" + } + + It 'Prefers the return kind of the adapted resource over the one the adapter defines for ' -TestCases @( + @{ operation = 'test'; stateProperty = 'actualState'; diffProperty = 'differingProperties' } + @{ operation = 'set'; stateProperty = 'afterState'; diffProperty = 'changedProperties' } + ) { + param($operation, $stateProperty, $diffProperty) + + $out = dsc -l debug resource $operation -r AdaptedReturn/Override -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.$stateProperty.one | Should -BeExactly 'fromOperation' + $out.$diffProperty | Should -Be @('one') + $errorTxt | Should -BeLike "*Adapted resource 'AdaptedReturn/Override' defines return kind 'state' for operation '$operation'*" + } + + It 'Uses the return kind of the legacy whatIf method of the adapter instead of the one the adapted resource defines for set' { + $out = dsc -l debug resource set --what-if -r AdaptedReturn/Override -i $desired 2>$TestDrive/error.txt | ConvertFrom-Json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorTxt + $out.afterState.one | Should -BeExactly 'fromWhatIf' + $out.changedProperties | Should -Be @('fromWhatIf') + $errorTxt | Should -Not -BeLike "*Adapted resource 'AdaptedReturn/Override' defines return kind 'state' for operation 'set'*" + } + It 'Rejects an adapted resource manifest with an invalid return kind for ' -TestCases @( @{ operation = 'set' } @{ operation = 'test' } @@ -150,7 +187,10 @@ Describe 'Tests for adapted resources' { Set-Content -Path (Join-Path $manifestDir 'invalidReturn.dsc.adaptedResource.json') -Value $manifest $oldPath = $env:DSC_RESOURCE_PATH + $oldRestrictedPath = $env:DSC_RESTRICTED_PATH try { + # DSC_RESTRICTED_PATH takes precedence over DSC_RESOURCE_PATH + $env:DSC_RESTRICTED_PATH = $null $env:DSC_RESOURCE_PATH = $manifestDir + [System.IO.Path]::PathSeparator + (Split-Path (Get-Command dsctest).Source) $out = dsc resource list 'Adapted/InvalidReturn' 2>$TestDrive/error.txt $out | Should -BeNullOrEmpty @@ -158,6 +198,7 @@ Describe 'Tests for adapted resources' { } finally { $env:DSC_RESOURCE_PATH = $oldPath + $env:DSC_RESTRICTED_PATH = $oldRestrictedPath } } }