From ffa8ef25db85dc08035749b2dae1e8c3f96ae0b4 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 23 Jul 2026 13:47:16 -0700 Subject: [PATCH 1/5] Add support to pass resource version to adapter --- dsc/tests/dsc_adapter.tests.ps1 | 34 +++++++++++++++++++ .../src/dscresources/command_resource.rs | 12 +++++++ .../src/dscresources/resource_manifest.rs | 17 +++++++++- tools/dsctest/dsctest.dsc.manifests.json | 14 ++++++++ tools/dsctest/src/adapter.rs | 24 ++++++++++--- tools/dsctest/src/args.rs | 2 ++ tools/dsctest/src/main.rs | 4 +-- 7 files changed, 99 insertions(+), 8 deletions(-) diff --git a/dsc/tests/dsc_adapter.tests.ps1 b/dsc/tests/dsc_adapter.tests.ps1 index 489b2fdd2..2583f96ad 100644 --- a/dsc/tests/dsc_adapter.tests.ps1 +++ b/dsc/tests/dsc_adapter.tests.ps1 @@ -98,6 +98,40 @@ Describe 'Tests for adapter support' { } } + It 'Specifying adapted resource version succeeds' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Test + type: Adapted/One + requireVersion: =1.0.0 + properties: + one: '1' +"@ + $out = dsc config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10 + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log | Out-String) + $out.results.Count | Should -Be 1 + $out.results[0].type | Should -BeExactly 'Adapted/One' + $out.results[0].Name | Should -Be 'Test' + $out.results[0].result.actualState.one | Should -BeExactly 'value1' + } + + It 'Specifying invalid adapted resource version fails' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Test + type: Adapted/One + requireVersion: =2.0.0 + properties: + one: '1' +"@ + $out = dsc config get -i $config_yaml 2>$TestDrive/error.log + $LASTEXITCODE | Should -Be 2 -Because (Get-Content $TestDrive/error.log | Out-String) + $errorContent = Get-Content $TestDrive/error.log -Raw + $errorContent | Should -Match "Resource not found: Adapted/One =2.0.0" -Because $errorContent + $out | Should -BeNullOrEmpty -Because $errorContent + } It 'Specifying invalid adapter via metadata fails' { $config_yaml = @" diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 206069cfe..1bdbad022 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -1004,6 +1004,10 @@ pub fn process_get_args(args: Option<&Vec>, input: &str, resource: & processed_args.push(resource_type_arg.clone()); processed_args.push(resource.type_name.to_string()); }, + GetArgKind::ResourceVersion { resource_version_arg } => { + processed_args.push(resource_version_arg.clone()); + processed_args.push(resource.version.to_string()); + }, GetArgKind::ResourcePath { resource_path_arg, include_quotes} => { processed_args.push(resource_path_arg.clone()); if *include_quotes { @@ -1034,6 +1038,10 @@ fn process_schema_args(args: Option<&Vec>, command_resource: &Dsc processed_args.push(resource_type_arg.clone()); processed_args.push(command_resource.type_name.to_string()); }, + SchemaArgKind::ResourceVersion { resource_version_arg } => { + processed_args.push(resource_version_arg.clone()); + processed_args.push(command_resource.version.to_string()); + }, } } @@ -1091,6 +1099,10 @@ fn process_set_delete_args(args: Option<&Vec>, input: &str, re processed_args.push(resource_type_arg.clone()); processed_args.push(resource.type_name.to_string()); }, + SetDeleteArgKind::ResourceVersion { resource_version_arg } => { + processed_args.push(resource_version_arg.clone()); + processed_args.push(resource.version.to_string()); + }, SetDeleteArgKind::WhatIf { what_if_arg } => { supports_whatif = true; if execution_type == &ExecutionKind::WhatIf { diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index b827e7411..5faeada16 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -129,6 +129,11 @@ pub enum GetArgKind { /// The argument that accepts the resource type name. resource_type_arg: String, }, + #[serde(rename_all = "camelCase")] + ResourceVersion { + /// The argument that accepts the resource version. + resource_version_arg: String, + }, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)] @@ -162,6 +167,11 @@ pub enum SetDeleteArgKind { /// The argument that accepts the resource type name. resource_type_arg: String, }, + #[serde(rename_all = "camelCase")] + ResourceVersion { + /// The argument that accepts the resource version. + resource_version_arg: String, + }, /// The argument is passed when the resource is invoked in what-if mode. #[serde(rename_all = "camelCase")] WhatIf { @@ -176,11 +186,16 @@ pub enum SetDeleteArgKind { pub enum SchemaArgKind { /// The argument is a string. String(String), + #[serde(rename_all = "camelCase")] ResourceType { /// The argument that accepts the resource type name. - #[serde(rename = "resourceTypeArg")] resource_type_arg: String, }, + #[serde(rename_all = "camelCase")] + ResourceVersion { + /// The argument that accepts the resource version. + resource_version_arg: String, + }, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)] diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 82c91dd54..26ede0dec 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -985,6 +985,9 @@ }, { "resourcePathArg": "--resource-path" + }, + { + "resourceVersionArg": "--resource-version" } ] }, @@ -1004,7 +1007,11 @@ { "resourcePathArg": "--resource-path", "includeQuotes": true + }, + { + "resourceVersionArg": "--resource-version" } + ] }, "test": { @@ -1019,7 +1026,11 @@ }, { "resourceTypeArg": "--resource-type" + }, + { + "resourceVersionArg": "--resource-version" } + ] }, "export": { @@ -1034,6 +1045,9 @@ }, { "resourceTypeArg": "--resource-type" + }, + { + "resourceVersionArg": "--resource-version" } ] }, diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index 9547ea787..1bd87c7ce 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -8,7 +8,7 @@ use crate::args::AdapterOperation; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] -pub struct AdaptedOne { +struct AdaptedOne { pub one: String, #[serde(rename = "_name", skip_serializing_if = "Option::is_none")] pub name: Option, @@ -16,9 +16,11 @@ pub struct AdaptedOne { pub path: Option, } +const ADAPTED_ONE_VERSION: &str = "1.0.0"; + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] -pub struct AdaptedTwo { +struct AdaptedTwo { pub two: String, #[serde(rename = "_name", skip_serializing_if = "Option::is_none")] pub name: Option, @@ -26,6 +28,8 @@ pub struct AdaptedTwo { pub path: Option, } +const ADAPTED_TWO_VERSION: &str = "2.0.0"; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct DscResource { @@ -52,13 +56,13 @@ pub struct DscResource { pub require_adapter: Option, } -pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, resource_path: &Option) -> Result { +pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, resource_path: &Option, resource_version: &Option) -> Result { match operation { AdapterOperation::List => { let resource_one = DscResource { type_name: "Adapted/One".to_string(), kind: "resource".to_string(), - version: "1.0.0".to_string(), + version: ADAPTED_ONE_VERSION.to_string(), capabilities: vec!["get".to_string(), "set".to_string(), "test".to_string(), "export".to_string()], path: "path/to/adapted/one".to_string(), directory: "path/to/adapted".to_string(), @@ -69,7 +73,7 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res let resource_two = DscResource { type_name: "Adapted/Two".to_string(), kind: "resource".to_string(), - version: "1.0.0".to_string(), + version: ADAPTED_TWO_VERSION.to_string(), capabilities: vec!["get".to_string(), "set".to_string(), "test".to_string(), "export".to_string()], path: "path/to/adapted/two".to_string(), directory: "path/to/adapted".to_string(), @@ -84,6 +88,11 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Get => { match resource_type { "Adapted/One" => { + if let Some(version) = resource_version { + if version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for Adapted/One: {version}")); + } + } let adapted_one = AdaptedOne { one: "value1".to_string(), name: None, @@ -92,6 +101,11 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res Ok(serde_json::to_string(&adapted_one).unwrap()) }, "Adapted/Two" => { + if let Some(version) = resource_version { + if version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for Adapted/Two: {version}")); + } + } let adapted_two = AdaptedTwo { two: "value2".to_string(), name: None, diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index 49206daab..18287e764 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -64,6 +64,8 @@ pub enum SubCommand { resource_type: String, #[clap(name = "resource-path", long, help = "The path to the adapted resource")] resource_path: Option, + #[clap(name = "resource-version", long, help = "The version of the adapted resource")] + resource_version: Option, #[clap(name = "operation", short, long, help = "The operation to perform")] operation: AdapterOperation, }, diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index 4a4156f7c..b2b32b4be 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -54,8 +54,8 @@ use std::{thread, time::Duration}; fn main() { let args = Args::parse(); let json = match args.subcommand { - SubCommand::Adapter { input , resource_type, resource_path, operation } => { - match adapter::adapt(&resource_type, &input, &operation, &resource_path) { + SubCommand::Adapter { input , resource_type, resource_path, resource_version, operation } => { + match adapter::adapt(&resource_type, &input, &operation, &resource_path, &resource_version) { Ok(result) => result, Err(err) => { eprintln!("Error adapting resource: {err}"); From a6536f09d3b866b36a9cbe353df98a78ceed97f9 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 23 Jul 2026 13:49:45 -0700 Subject: [PATCH 2/5] expand tests to include all operations --- dsc/tests/dsc_adapter.tests.ps1 | 43 +++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/dsc/tests/dsc_adapter.tests.ps1 b/dsc/tests/dsc_adapter.tests.ps1 index 2583f96ad..4d015f576 100644 --- a/dsc/tests/dsc_adapter.tests.ps1 +++ b/dsc/tests/dsc_adapter.tests.ps1 @@ -98,7 +98,14 @@ Describe 'Tests for adapter support' { } } - It 'Specifying adapted resource version succeeds' { + It 'Specifying adapted resource version succeeds for ' -TestCases @( + @{ operation = 'get' }, + @{ operation = 'set' }, + @{ operation = 'test' }, + @{ operation = 'export' } + ){ + param($operation) + $config_yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: @@ -108,15 +115,35 @@ Describe 'Tests for adapter support' { properties: one: '1' "@ - $out = dsc config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10 + $out = dsc config $operation -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10 $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log | Out-String) - $out.results.Count | Should -Be 1 - $out.results[0].type | Should -BeExactly 'Adapted/One' - $out.results[0].Name | Should -Be 'Test' - $out.results[0].result.actualState.one | Should -BeExactly 'value1' + switch ($operation) { + 'get' { + $out.results[0].result.actualState.one | Should -BeExactly 'value1' + } + 'set' { + $out.results[0].result.afterState.one | Should -BeExactly 'value1' + } + 'test' { + $out.results[0].result.actualState.one | Should -BeExactly 'value1' + $out.results[0].result.inDesiredState | Should -BeFalse + $out.results[0].result.differingProperties | Should -Be @('one') + } + 'export' { + $out.resources[0].properties.one | Should -BeExactly 'first1' + $out.resources[1].properties.one | Should -BeExactly 'second1' + } + } } - It 'Specifying invalid adapted resource version fails' { + It 'Specifying invalid adapted resource version fails for ' -TestCases @( + @{ operation = 'get' }, + @{ operation = 'set' }, + @{ operation = 'test' }, + @{ operation = 'export' } + ){ + param($operation) + $config_yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: @@ -126,7 +153,7 @@ Describe 'Tests for adapter support' { properties: one: '1' "@ - $out = dsc config get -i $config_yaml 2>$TestDrive/error.log + $out = dsc config $operation -i $config_yaml 2>$TestDrive/error.log $LASTEXITCODE | Should -Be 2 -Because (Get-Content $TestDrive/error.log | Out-String) $errorContent = Get-Content $TestDrive/error.log -Raw $errorContent | Should -Match "Resource not found: Adapted/One =2.0.0" -Because $errorContent From de1ec1b44fd4d0ed2109f9283520fa46b58ed23d Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 23 Jul 2026 14:11:08 -0700 Subject: [PATCH 3/5] add version check to test adapter operations --- tools/dsctest/src/adapter.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index 1bd87c7ce..5c8eca03b 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -135,11 +135,21 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Set | AdapterOperation::Test => { match resource_type { "Adapted/One" => { + if let Some(version) = resource_version { + if version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + } let adapted_one: AdaptedOne = serde_json::from_str(input) .map_err(|e| format!("Failed to parse input for Adapted/One: {e}"))?; Ok(serde_json::to_string(&adapted_one).unwrap()) }, "Adapted/Two" => { + if let Some(version) = resource_version { + if version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + } let adapted_two: AdaptedTwo = serde_json::from_str(input) .map_err(|e| format!("Failed to parse input for Adapted/Two: {e}"))?; Ok(serde_json::to_string(&adapted_two).unwrap()) @@ -155,6 +165,11 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Export => { match resource_type { "Adapted/One" => { + if let Some(version) = resource_version { + if version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + } let adapted_one = AdaptedOne { one: "first1".to_string(), name: Some("first".to_string()), @@ -170,6 +185,11 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res std::process::exit(0); }, "Adapted/Two" => { + if let Some(version) = resource_version { + if version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + } let adapted_two = AdaptedTwo { two: "first2".to_string(), name: Some("first".to_string()), From 7063218f287c284a2ab9e864fbd18cb58d78c355 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 23 Jul 2026 14:12:25 -0700 Subject: [PATCH 4/5] fix clippy --- tools/dsctest/src/adapter.rs | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index 5c8eca03b..c812061df 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -88,10 +88,8 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Get => { match resource_type { "Adapted/One" => { - if let Some(version) = resource_version { - if version != ADAPTED_ONE_VERSION { - return Err(format!("Unsupported version for Adapted/One: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for Adapted/One: {version}")); } let adapted_one = AdaptedOne { one: "value1".to_string(), @@ -101,10 +99,8 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res Ok(serde_json::to_string(&adapted_one).unwrap()) }, "Adapted/Two" => { - if let Some(version) = resource_version { - if version != ADAPTED_TWO_VERSION { - return Err(format!("Unsupported version for Adapted/Two: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for Adapted/Two: {version}")); } let adapted_two = AdaptedTwo { two: "value2".to_string(), @@ -135,20 +131,16 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Set | AdapterOperation::Test => { match resource_type { "Adapted/One" => { - if let Some(version) = resource_version { - if version != ADAPTED_ONE_VERSION { - return Err(format!("Unsupported version for {resource_type}: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); } let adapted_one: AdaptedOne = serde_json::from_str(input) .map_err(|e| format!("Failed to parse input for Adapted/One: {e}"))?; Ok(serde_json::to_string(&adapted_one).unwrap()) }, "Adapted/Two" => { - if let Some(version) = resource_version { - if version != ADAPTED_TWO_VERSION { - return Err(format!("Unsupported version for {resource_type}: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); } let adapted_two: AdaptedTwo = serde_json::from_str(input) .map_err(|e| format!("Failed to parse input for Adapted/Two: {e}"))?; @@ -165,10 +157,8 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res AdapterOperation::Export => { match resource_type { "Adapted/One" => { - if let Some(version) = resource_version { - if version != ADAPTED_ONE_VERSION { - return Err(format!("Unsupported version for {resource_type}: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); } let adapted_one = AdaptedOne { one: "first1".to_string(), @@ -185,10 +175,8 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res std::process::exit(0); }, "Adapted/Two" => { - if let Some(version) = resource_version { - if version != ADAPTED_TWO_VERSION { - return Err(format!("Unsupported version for {resource_type}: {version}")); - } + if let Some(version) = resource_version && version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); } let adapted_two = AdaptedTwo { two: "first2".to_string(), From 2406fd45681529a758ffdda7d95d2095da0bc78a Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 23 Jul 2026 14:47:49 -0700 Subject: [PATCH 5/5] fix test resource and tests --- dsc/tests/dsc_adapter.tests.ps1 | 2 +- tools/dsctest/dsctest.dsc.manifests.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsc/tests/dsc_adapter.tests.ps1 b/dsc/tests/dsc_adapter.tests.ps1 index 4d015f576..31bc09541 100644 --- a/dsc/tests/dsc_adapter.tests.ps1 +++ b/dsc/tests/dsc_adapter.tests.ps1 @@ -244,7 +244,7 @@ Describe 'Tests for adapter support' { $out = dsc -l trace resource set -r Adapted/Two -i '{"two":"2"}' 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10 $errorLog = Get-Content $TestDrive/error.log -Raw $LASTEXITCODE | Should -Be 0 -Because $errorLog - $errorLog | Should -BeLike '*Invoking command ''dsctest'' with args Some(`["adapter", "--operation", "set", "--input", "{\"two\":\"2\"}", "--resource-type", "Adapted/Two", "--resource-path", "\"*adaptedTest.dsc.adaptedResource.json\""`])*' -Because $errorLog + $errorLog | Should -BeLike '*Invoking command ''dsctest'' with args Some(`["adapter", "--operation", "set", "--input", "{\"two\":\"2\"}", "--resource-type", "Adapted/Two", "--resource-path", "\"*adaptedTest.dsc.adaptedResource.json\"", "--resource-version", "2.0.0"`])*' -Because $errorLog $out.afterState.two | Should -BeExactly 'value2' -Because $errorLog } diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 26ede0dec..258bf0d17 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -4,7 +4,7 @@ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/adaptedresource/manifest.json", "type": "Adapted/Two", "kind": "resource", - "version": "1.0.0", + "version": "2.0.0", "capabilities": [ "get", "set",