Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aeb3c44
feat: implement export filter functionality for resource exports
Gijsreyn Jul 11, 2026
ba1baec
Remove comment
Gijsreyn Jul 11, 2026
00ec8fb
feat: implement export filter functionality for resource exports
Gijsreyn Jul 11, 2026
664abad
Remove comment
Gijsreyn Jul 11, 2026
87a8f3a
Fix Copilot remarks
Gijsreyn Jul 12, 2026
e992aaa
Merge branch 'gh-1486/main/add-postfilter-export' of https://github.c…
Gijsreyn Jul 12, 2026
ca50052
Wrong commit
Gijsreyn Jul 12, 2026
f7f74f8
Merge branch 'main' into gh-1486/main/add-postfilter-export
Gijsreyn Jul 14, 2026
6c176a1
Remove the wildcard support for resources
Gijsreyn Jul 14, 2026
0f5bfd4
Merge branch 'main' into gh-1486/main/add-postfilter-export
Gijsreyn Jul 15, 2026
94d221b
Fix Copilot remarks
Gijsreyn Jul 15, 2026
9e062ba
Merge branch 'main' into gh-1486/main/add-postfilter-export
Gijsreyn Jul 15, 2026
b37cab7
Attempt to increase code coverage and fix test
Gijsreyn Jul 15, 2026
f86d09c
Add additional test for coverage
Gijsreyn Jul 15, 2026
bd20f48
Add test and fix dism_dsc
Gijsreyn Jul 15, 2026
e389025
fix: correct code coverage calculation for uninstrumented files
SteveL-MSFT Jul 15, 2026
e2e005e
Merge remote-tracking branch 'upstream/main' into gh-1486/main/add-po…
Gijsreyn Jul 16, 2026
72aa474
Refactor work on services
Gijsreyn Jul 16, 2026
46be9a2
Merge branch 'gh-1486/main/add-postfilter-export' of https://github.c…
Gijsreyn Jul 16, 2026
1bea448
Remove unused key
Gijsreyn Jul 16, 2026
40dacf0
Revert change
Gijsreyn Jul 18, 2026
17a0a88
Restore native resource filtering and add engine filtering fallback
Gijsreyn Jul 19, 2026
537af5c
Merge remote-tracking branch 'upstream/main' into gh-1486/main/add-po…
Gijsreyn Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions dsc/tests/dsc_export.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,169 @@ resources:
$out.resources[0].properties.id | Should -Be 1
}
}

Describe 'export filter directive tests' {
It 'exportFilter applies equality filtering for non-string properties' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Test Export
type: Test/Export
directives:
exportFilter:
- count: 2
properties:
count: 5
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 1
$out.resources[0].properties.count | Should -Be 2
}

It 'exportFilter supports wildcards and is case-insensitive' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Test Export
type: Test/Export
directives:
exportFilter:
- name: '*STANCE3'
properties:
count: 5
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 1
$out.resources[0].properties.name | Should -BeExactly 'Instance3'
}

It 'exportFilter objects are a logical OR' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Test Export
type: Test/Export
directives:
exportFilter:
- count: 0
- name: '*stance2'
properties:
count: 5
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 2
$out.resources[0].properties.count | Should -Be 0
$out.resources[1].properties.name | Should -BeExactly 'Instance2'
}

It 'properties within an exportFilter object are a logical AND' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Test Export
type: Test/Export
directives:
exportFilter:
- count: 2
name: 'instance2'
properties:
count: 5
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 1
$out.resources[0].properties.count | Should -Be 2
}

It 'exportFilter with an AND mismatch returns no instances' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Test Export
type: Test/Export
directives:
exportFilter:
- count: 2
name: 'instance1'
properties:
count: 5
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 0
}

It 'exportFilter works for a resource that does not support filtering natively' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: No Native Filtering
type: Test/ExportSchemaNoFiltering
directives:
exportFilter:
- name: '*e*'
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 2
$out.resources.properties.name | Should -Be @('Steve', 'Tess')
}

It 'engine filters properties for a resource that does not support filtering natively' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: No Native Filtering
type: Test/ExportSchemaNoFiltering
properties:
name: '*e*'
'@
$out = dsc --trace-level info config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$errorlog = Get-Content "$TESTDRIVE/error.log" -Raw
$LASTEXITCODE | Should -Be 0 -Because $errorlog
@($out.resources).Count | Should -Be 2
$out.resources.properties.name | Should -Be @('Steve', 'Tess')
$errorlog | Should -Match 'does not support export filtering, the engine will filter the exported instances'
}

It 'engine filtered properties compose with an exportFilter directive' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: No Native Filtering
type: Test/ExportSchemaNoFiltering
directives:
exportFilter:
- name: 'te*'
properties:
name: '*e*'
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 1
$out.resources[0].properties.name | Should -BeExactly 'Tess'
}

It 'exportFilter works with an exporter resource' {
$yaml = @'
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: export this
type: Test/Exporter
directives:
exportFilter:
- type: '*Foo'
properties:
typeNames:
- Test/Foo
- Test/Bar
'@
$out = dsc config export -i $yaml 2>$TESTDRIVE/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TESTDRIVE/error.log" -Raw)
@($out.resources).Count | Should -Be 1
$out.resources[0].type | Should -BeExactly 'Test/Foo'
}
}
24 changes: 20 additions & 4 deletions dsc/tests/dsc_resource_export.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,29 @@ Describe 'Resource export tests' {
$output.resources.properties.name | Should -Be $expected -Because ($output | ConvertTo-Json -Depth 4)
}

It 'Resource not support export filtering will return error' {
It "Engine filters input '<json>' for a resource without native filtering support" -TestCases @(
@{ json = '{ "name": "Gijs" }'; expected = @('Gijs') }
@{ json = '{ "name": "*e*" }'; expected = @('Steve', 'Tess') }
@{ json = '[{ "name": "Gijs" }, { "name": "Steve" }]'; expected = @('Steve', 'Gijs') }
){
param($json, $expected)

$resource = 'Test/ExportSchemaNoFiltering'
$output = dsc --trace-level info resource export -r $resource -i $json 2>$TESTDRIVE/error.log | ConvertFrom-Json
$errorlog = Get-Content "$TESTDRIVE/error.log" -Raw
$LASTEXITCODE | Should -Be 0 -Because $errorlog
$output.resources.count | Should -Be $expected.Count -Because ($output | ConvertTo-Json -Depth 4)
$output.resources.properties.name | Should -Be $expected -Because ($output | ConvertTo-Json -Depth 4)
$errorlog | Should -Match "Resource '$resource' does not support export filtering, the engine will filter the exported instances"
}

It 'Engine filtering rejects input that is not an object or array of objects' {
$resource = 'Test/ExportSchemaNoFiltering'
$json = '{ "name": "Gijs" }'
$json = '5'

$output = dsc resource export -r $resource -i $json 2>$TESTDRIVE/error.log | ConvertFrom-Json
dsc resource export -r $resource -i $json 2>$TESTDRIVE/error.log | Out-Null
$errorlog = Get-Content "$TESTDRIVE/error.log" -Raw
$LASTEXITCODE | Should -Be 2 -Because $errorlog
$errorlog | Should -Match "Resource '$resource' does not support export filtering"
$errorlog | Should -Match 'Export filter input must be a JSON object or an array of JSON objects'
}
}
7 changes: 3 additions & 4 deletions helpers.build.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -2448,6 +2448,8 @@ function Get-CodeCoverageReport {
$currentLineNum++
} elseif ($diffLine.StartsWith('-') -and -not $diffLine.StartsWith('---')) {
# Deleted lines don't advance the new file line counter
} elseif ($diffLine.StartsWith('\')) {
# "\ No newline at end of file" marker — not a real line
} else {
$currentLineNum++
}
Expand All @@ -2469,10 +2471,7 @@ function Get-CodeCoverageReport {
}

if (-not $fileCoverage) {
# File not in coverage report means LLVM found no instrumentable
# executable code in it (e.g., struct/enum definitions with derive
# macros). Skip it rather than penalizing as uncovered.
Write-Verbose -Verbose "No LCOV data for '$file' - file has no instrumentable code, skipping"
Write-Verbose -Verbose "Skipping '$file': not in LCOV data (possibly platform-specific or not instrumented)"
continue
}

Expand Down
6 changes: 6 additions & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ dependencyNotInOrder = "Dependency not found in order"
circularDependency = "Circular dependency detected for resource named '%{resource}'"
invocationOrder = "Resource invocation order"

[configure.export_filter]
filteredInstances = "Export filter reduced %{original} instances to %{retained}"

[configure.mod]
nestedArraysNotSupported = "Nested arrays not supported"
arrayElementCouldNotTransformAsString = "Array element could not be transformed as string"
Expand All @@ -48,6 +51,9 @@ groupNotSupportedForDelete = "Group resources not supported for delete"
deleteNotSupported = "Resource '%{resource}' does not support `delete` and does not handle `_exist` as false"
expectedState = "Expected state: %{state}"
exportInput = "Export input: %{input}"
engineExportFiltering = "Resource '%{resource}' does not support export filtering, the engine will filter the exported instances"
invalidExportFilterInput = "Invalid export filter input: %{error}"
exportFilterNotObject = "Export filter input must be a JSON object or an array of JSON objects"
noParameters = "No parameters defined in configuration and no parameters input"
noParametersDefined = "No parameters defined in configuration"
processingParameter = "Processing parameter '%{name}'"
Expand Down
6 changes: 6 additions & 0 deletions lib/dsc-lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ pub struct ConfigDirective {
#[serde(rename_all = "camelCase")]
#[dsc_repo_schema(base_name = "directive", folder_path = "resource")]
pub struct ResourceDirective {
/// Filters applied by the engine to exported instances. Filters in the array are logically
/// OR'd while properties within a filter are logically AND'd. String values support the `*` wildcard.
/// If the resource performs its own filtering based on its input, this filter is applied
/// afterwards to the instances the resource returned.
#[serde(skip_serializing_if = "Option::is_none")]
pub export_filter: Option<Vec<Map<String, Value>>>,
/// Specify specific adapter type used for implicit operations
#[serde(skip_serializing_if = "Option::is_none")]
pub require_adapter: Option<FullyQualifiedTypeName>,
Expand Down
Loading
Loading