diff --git a/dsc/tests/dsc_resource_condition.tests.ps1 b/dsc/tests/dsc_resource_condition.tests.ps1 new file mode 100644 index 000000000..beddffcf5 --- /dev/null +++ b/dsc/tests/dsc_resource_condition.tests.ps1 @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Resource condition tests' { + BeforeAll { + $configYaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: test + type: Microsoft.DSC.Debug/Echo + condition: "[equals('skip', 'yes')]" + properties: + output: "This should not be executed" + - name: test2 + type: Microsoft.DSC.Debug/Echo + condition: "[equals('no', 'no')]" + properties: + output: "This should be executed" +'@ + } + + It 'resource should be skipped for ' -TestCases @( + @{ operation = 'get'; property = 'actualState' }, + @{ operation = 'set'; property = 'afterState' }, + @{ operation = 'test'; property = 'actualState' } + ) { + param($operation, $property) + $out = dsc config $operation -i $configYaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results.count | Should -Be 1 + $out.results[0].result.$property.Output | Should -BeExactly "This should be executed" + } + + It 'resource should be skipped for export' { + $out = dsc config export -i $configYaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.resources.count | Should -Be 1 + $out.resources[0].type | Should -BeExactly 'Microsoft.DSC.Debug/Echo' + $out.resources[0].properties.output | Should -BeExactly "This should be executed" + } +} diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 449fb6a4c..350d6da8d 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -3,6 +3,7 @@ _version = 1 [configure.config_doc] configurationDocumentSchemaTitle = "Configuration document schema URI" configurationDocumentSchemaDescription = "Defines the JSON Schema the configuration document adheres to." +skippingResource = "Skipping resource '%{name}' due to condition '%{condition}' with result '%{result}'" [configure.constraints] minLengthIsNull = "Parameter '%{name}' has minimum length constraint but is null" diff --git a/dsc_lib/src/configure/config_doc.rs b/dsc_lib/src/configure/config_doc.rs index 902662528..871abdb3a 100644 --- a/dsc_lib/src/configure/config_doc.rs +++ b/dsc_lib/src/configure/config_doc.rs @@ -160,6 +160,8 @@ pub struct Resource { pub properties: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub condition: Option, } impl Default for Configuration { @@ -217,6 +219,7 @@ impl Resource { kind: None, properties: None, metadata: None, + condition: None, } } } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 30c0dc237..781517e52 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -311,6 +311,10 @@ impl Configurator { for resource in resources { progress.set_resource(&resource.name, &resource.resource_type); progress.write_activity(format!("Get '{}'", resource.name).as_str()); + if self.skip_resource(&resource)? { + progress.write_increment(1); + continue; + } let Some(dsc_resource) = discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); }; @@ -387,6 +391,10 @@ impl Configurator { for resource in resources { progress.set_resource(&resource.name, &resource.resource_type); progress.write_activity(format!("Set '{}'", resource.name).as_str()); + if self.skip_resource(&resource)? { + progress.write_increment(1); + continue; + } let Some(dsc_resource) = discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); }; @@ -535,6 +543,10 @@ impl Configurator { for resource in resources { progress.set_resource(&resource.name, &resource.resource_type); progress.write_activity(format!("Test '{}'", resource.name).as_str()); + if self.skip_resource(&resource)? { + progress.write_increment(1); + continue; + } let Some(dsc_resource) = discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); }; @@ -608,6 +620,10 @@ impl Configurator { for resource in &resources { progress.set_resource(&resource.name, &resource.resource_type); progress.write_activity(format!("Export '{}'", resource.name).as_str()); + if self.skip_resource(resource)? { + progress.write_increment(1); + continue; + } let Some(dsc_resource) = discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type.clone())); }; @@ -642,6 +658,17 @@ impl Configurator { Ok(result) } + fn skip_resource(&mut self, resource: &Resource) -> Result { + if let Some(condition) = &resource.condition { + let condition_result = self.statement_parser.parse_and_execute(condition, &self.context)?; + if condition_result != Value::Bool(true) { + info!("{}", t!("configure.config_doc.skippingResource", name = resource.name, condition = condition, result = condition_result)); + return Ok(true); + } + } + Ok(false) + } + /// Set the mounted path for the configuration. /// /// # Arguments diff --git a/dscecho/echo.dsc.resource.json b/dscecho/echo.dsc.resource.json index 829a3ce1f..37da7a8f1 100644 --- a/dscecho/echo.dsc.resource.json +++ b/dscecho/echo.dsc.resource.json @@ -30,6 +30,15 @@ } ] }, + "export": { + "executable": "dscecho", + "args": [ + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, "schema": { "command": { "executable": "dscecho"