From a6345ceae6b62658f45346afd9b51f893c6ddff3 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Wed, 13 May 2026 08:48:21 +0200 Subject: [PATCH 1/3] fix: Short-circuit adapter discovery when a native resource type has no matching version --- dsc/tests/dsc_config_version.tests.ps1 | 22 ++++++++++++ lib/dsc-lib/locales/en-us.toml | 1 + .../src/discovery/command_discovery.rs | 36 +++++++++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/dsc/tests/dsc_config_version.tests.ps1 b/dsc/tests/dsc_config_version.tests.ps1 index bd109edd7..4f61969fd 100644 --- a/dsc/tests/dsc_config_version.tests.ps1 +++ b/dsc/tests/dsc_config_version.tests.ps1 @@ -91,4 +91,26 @@ Describe 'Tests for resource versioning' { $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw) $out.results[0].result.actualState.version | Should -BeExactly '1.1.2' } + + It 'Skips adapter discovery when a native resource type version requirement cannot be satisfied' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Test Version + type: Test/Version + requireVersion: '=99.0.0' + properties: + version: '99.0.0' +"@ + $out = dsc -l trace config get -i $config_yaml 2> $TestDrive/error.log + $LASTEXITCODE | Should -Not -Be 0 + + $traces = Get-Content $TestDrive/error.log -Raw + + $traces | Should -Match "Skipping adapter search for resource 'Test/Version'" + $traces | Should -Not -Match 'Searching for adapted resources' + $traces | Should -Not -Match 'Enumerating resources for adapter' + $traces | Should -Match "Test/Version" + $traces | Should -Match "=99.0.0" + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 81083031c..0e6310e33 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -132,6 +132,7 @@ importExtensionsEmpty = "Import extension '%{extension}' has no import extension searchingForResources = "Searching for resources: %{resources}" foundResourceWithVersion = "Found matching resource '%{resource}' version %{version}" foundNonAdapterResources = "Found %{count} non-adapter resources" +skipAdapterSearchForNativeType = "Skipping adapter search for resource '%{resource}': type is registered as a native command-based resource; version requirement does not match any installed version" resourceMissingRequireAdapter = "Resource '%{resource}' is missing 'require_adapter' field." extensionDiscoverFailed = "Extension '%{extension}' failed to discover resources: %{error}" conditionNotBoolean = "Condition '%{condition}' did not evaluate to a boolean" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 4da974bc7..53e0a1d85 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -504,11 +504,43 @@ impl ResourceDiscovery for CommandDiscovery { return Ok(found_resources); } + // Determine which still-unsatisfied filters actually require adapter discovery. + // + // If a filter's resource type is already known to native discovery (present in + // RESOURCES or ADAPTERS) and the user did not pin an adapter via `requireAdapter`, + // then any unsatisfied state is necessarily a version-requirement mismatch + let adapter_filter_candidates: Vec<&DiscoveryFilter> = required_resource_types + .iter() + .filter(|filter| { + if required_resources.get(*filter).copied().unwrap_or(false) { + return false; + } + if filter.require_adapter().is_some() { + return true; + } + let type_known_natively = locked_get!(RESOURCES, filter.resource_type()).is_some() + || locked_get!(ADAPTERS, filter.resource_type()).is_some(); + if type_known_natively { + debug!( + "{}", + t!("discovery.commandDiscovery.skipAdapterSearchForNativeType", + resource = filter.resource_type()) + ); + return false; + } + true + }) + .collect(); + + if adapter_filter_candidates.is_empty() { + return Ok(found_resources); + } + // store the keys of the ADAPTERS into a vec let mut adapters: Vec = locked_clone!(ADAPTERS).keys().cloned().collect(); // sort the adapters by ones specified in the required resources first - for filter in required_resource_types { + for filter in &adapter_filter_candidates { if let Some(required_adapter) = filter.require_adapter() { if !adapters.contains(required_adapter) { return Err(DscError::AdapterNotFound(required_adapter.to_string())); @@ -522,7 +554,7 @@ impl ResourceDiscovery for CommandDiscovery { for adapter_name in &adapters { self.discover_adapted_resources(&TypeNameFilter::default(), &adapter_name.clone().into())?; add_resources_to_lookup_table(&locked_clone!(ADAPTED_RESOURCES)); - for filter in required_resource_types { + for filter in &adapter_filter_candidates { if let Some(adapted_resources) = locked_get!(ADAPTED_RESOURCES, filter.resource_type()) { filter_resources(&mut found_resources, &mut required_resources, &adapted_resources, filter); } From ab4ab4ed5994c231b567e92e28910cbf87796b62 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Wed, 13 May 2026 08:48:21 +0200 Subject: [PATCH 2/3] fix: Short-circuit adapter discovery when a native resource type has no matching version --- dsc/tests/dsc_config_version.tests.ps1 | 22 ++++++++++++ lib/dsc-lib/locales/en-us.toml | 1 + .../src/discovery/command_discovery.rs | 36 +++++++++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/dsc/tests/dsc_config_version.tests.ps1 b/dsc/tests/dsc_config_version.tests.ps1 index bd109edd7..4f61969fd 100644 --- a/dsc/tests/dsc_config_version.tests.ps1 +++ b/dsc/tests/dsc_config_version.tests.ps1 @@ -91,4 +91,26 @@ Describe 'Tests for resource versioning' { $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw) $out.results[0].result.actualState.version | Should -BeExactly '1.1.2' } + + It 'Skips adapter discovery when a native resource type version requirement cannot be satisfied' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Test Version + type: Test/Version + requireVersion: '=99.0.0' + properties: + version: '99.0.0' +"@ + $out = dsc -l trace config get -i $config_yaml 2> $TestDrive/error.log + $LASTEXITCODE | Should -Not -Be 0 + + $traces = Get-Content $TestDrive/error.log -Raw + + $traces | Should -Match "Skipping adapter search for resource 'Test/Version'" + $traces | Should -Not -Match 'Searching for adapted resources' + $traces | Should -Not -Match 'Enumerating resources for adapter' + $traces | Should -Match "Test/Version" + $traces | Should -Match "=99.0.0" + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 81083031c..0e6310e33 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -132,6 +132,7 @@ importExtensionsEmpty = "Import extension '%{extension}' has no import extension searchingForResources = "Searching for resources: %{resources}" foundResourceWithVersion = "Found matching resource '%{resource}' version %{version}" foundNonAdapterResources = "Found %{count} non-adapter resources" +skipAdapterSearchForNativeType = "Skipping adapter search for resource '%{resource}': type is registered as a native command-based resource; version requirement does not match any installed version" resourceMissingRequireAdapter = "Resource '%{resource}' is missing 'require_adapter' field." extensionDiscoverFailed = "Extension '%{extension}' failed to discover resources: %{error}" conditionNotBoolean = "Condition '%{condition}' did not evaluate to a boolean" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 4da974bc7..53e0a1d85 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -504,11 +504,43 @@ impl ResourceDiscovery for CommandDiscovery { return Ok(found_resources); } + // Determine which still-unsatisfied filters actually require adapter discovery. + // + // If a filter's resource type is already known to native discovery (present in + // RESOURCES or ADAPTERS) and the user did not pin an adapter via `requireAdapter`, + // then any unsatisfied state is necessarily a version-requirement mismatch + let adapter_filter_candidates: Vec<&DiscoveryFilter> = required_resource_types + .iter() + .filter(|filter| { + if required_resources.get(*filter).copied().unwrap_or(false) { + return false; + } + if filter.require_adapter().is_some() { + return true; + } + let type_known_natively = locked_get!(RESOURCES, filter.resource_type()).is_some() + || locked_get!(ADAPTERS, filter.resource_type()).is_some(); + if type_known_natively { + debug!( + "{}", + t!("discovery.commandDiscovery.skipAdapterSearchForNativeType", + resource = filter.resource_type()) + ); + return false; + } + true + }) + .collect(); + + if adapter_filter_candidates.is_empty() { + return Ok(found_resources); + } + // store the keys of the ADAPTERS into a vec let mut adapters: Vec = locked_clone!(ADAPTERS).keys().cloned().collect(); // sort the adapters by ones specified in the required resources first - for filter in required_resource_types { + for filter in &adapter_filter_candidates { if let Some(required_adapter) = filter.require_adapter() { if !adapters.contains(required_adapter) { return Err(DscError::AdapterNotFound(required_adapter.to_string())); @@ -522,7 +554,7 @@ impl ResourceDiscovery for CommandDiscovery { for adapter_name in &adapters { self.discover_adapted_resources(&TypeNameFilter::default(), &adapter_name.clone().into())?; add_resources_to_lookup_table(&locked_clone!(ADAPTED_RESOURCES)); - for filter in required_resource_types { + for filter in &adapter_filter_candidates { if let Some(adapted_resources) = locked_get!(ADAPTED_RESOURCES, filter.resource_type()) { filter_resources(&mut found_resources, &mut required_resources, &adapted_resources, filter); } From e7f18d2da0f576b11c7a6e5423199d8def5b14b3 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 15 May 2026 06:50:44 +0200 Subject: [PATCH 3/3] Take suggestion Copilot --- lib/dsc-lib/locales/en-us.toml | 2 +- lib/dsc-lib/src/discovery/command_discovery.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 0e6310e33..83316a729 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -132,7 +132,7 @@ importExtensionsEmpty = "Import extension '%{extension}' has no import extension searchingForResources = "Searching for resources: %{resources}" foundResourceWithVersion = "Found matching resource '%{resource}' version %{version}" foundNonAdapterResources = "Found %{count} non-adapter resources" -skipAdapterSearchForNativeType = "Skipping adapter search for resource '%{resource}': type is registered as a native command-based resource; version requirement does not match any installed version" +skipAdapterSearchForNativeType = "Skipping adapter search for resource '%{resource}' (requested version: '%{required_version}'): type is known as a native command-based resource and no matching version was found natively" resourceMissingRequireAdapter = "Resource '%{resource}' is missing 'require_adapter' field." extensionDiscoverFailed = "Extension '%{extension}' failed to discover resources: %{error}" conditionNotBoolean = "Condition '%{condition}' did not evaluate to a boolean" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 53e0a1d85..7c891af61 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -521,10 +521,13 @@ impl ResourceDiscovery for CommandDiscovery { let type_known_natively = locked_get!(RESOURCES, filter.resource_type()).is_some() || locked_get!(ADAPTERS, filter.resource_type()).is_some(); if type_known_natively { + let required_version = filter.require_version() + .map_or_else(|| "*".to_string(), std::string::ToString::to_string); debug!( "{}", t!("discovery.commandDiscovery.skipAdapterSearchForNativeType", - resource = filter.resource_type()) + resource = filter.resource_type(), + required_version = required_version) ); return false; } @@ -558,11 +561,11 @@ impl ResourceDiscovery for CommandDiscovery { if let Some(adapted_resources) = locked_get!(ADAPTED_RESOURCES, filter.resource_type()) { filter_resources(&mut found_resources, &mut required_resources, &adapted_resources, filter); } - if required_resources.values().all(|&v| v) { + if adapter_filter_candidates.iter().all(|f| required_resources.get(*f).copied().unwrap_or(false)) { break; } } - if required_resources.values().all(|&v| v) { + if adapter_filter_candidates.iter().all(|f| required_resources.get(*f).copied().unwrap_or(false)) { break; } }