Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion dsc/assertion.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"pass-through",
"config",
"--as-group",
"--as-assert",
"test",
"--as-get",
{
Expand All @@ -26,6 +27,7 @@
"pass-through",
"config",
"--as-group",
"--as-assert",
"test",
{
"jsonInputArg": "--input",
Expand All @@ -42,6 +44,7 @@
"pass-through",
"config",
"--as-group",
"--as-assert",
"test",
"--as-config",
{
Expand All @@ -59,7 +62,8 @@
"4": "Invalid input format",
"5": "Resource instance failed schema validation",
"6": "Command cancelled",
"7": "Resource not found"
"7": "Resource not found",
"8": "Assertion failed"
},
"validate": {
"executable": "dsc",
Expand Down
4 changes: 2 additions & 2 deletions dsc/examples/assertion.dsc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ resources:
keyPath: HKLM\Software\Microsoft\Windows NT\CurrentVersion
valueName: SystemRoot
valueData:
# this is deliberately set to z: drive so that the assertion fails
String: Z:\Windows
# this is deliberately set to L: drive so that the assertion fails
String: L:\Windows
1 change: 1 addition & 0 deletions dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ testInputEmpty = "Expected input is required"
[subcommand]
actualStateNotObject = "actual_state is not an object"
unexpectedTestResult = "Unexpected Group TestResult"
assertionFailed = "Assertion failed for resource '%{resource_type}'"
message = "message"
currentDirectory = "current directory"
noParameters = "No parameters specified"
Expand Down
2 changes: 2 additions & 0 deletions dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub enum SubCommand {
// Used to inform when DSC is used as a group resource to modify it's output
#[clap(long, hide = true)]
as_group: bool,
#[clap(long, hide = true)]
as_assert: bool,
// Used to inform when DSC is used as a include group resource
#[clap(long, hide = true)]
as_include: bool,
Expand Down
6 changes: 3 additions & 3 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,19 @@ fn main() {
let mut cmd = Args::command();
generate(shell, &mut cmd, "dsc", &mut io::stdout());
},
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_include } => {
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
if let Some(file_name) = parameters_file {
info!("{}: {file_name}", t!("main.readingParametersFile"));
match std::fs::read_to_string(&file_name) {
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include, progress_format),
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
Err(err) => {
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
exit(util::EXIT_INVALID_INPUT);
}
}
}
else {
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_include, progress_format);
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
}
},
SubCommand::Resource { subcommand } => {
Expand Down
24 changes: 20 additions & 4 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::args::{ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand};
use crate::resolve::{get_contents, Include};
use crate::resource_command::{get_resource, self};
use crate::tablewriter::Table;
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, get_schema, write_object, get_input, set_dscconfigroot, validate_json};
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, validate_json, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
use dsc_lib::{
configure::{
config_doc::{
Expand Down Expand Up @@ -106,7 +106,7 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat>
}
}

pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool)
pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool, as_assert: &bool)
{
match configurator.invoke_test() {
Ok(result) => {
Expand All @@ -115,6 +115,10 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
let mut result_configuration = Configuration::new();
result_configuration.resources = Vec::new();
for test_result in result.results {
if *as_assert && !in_desired_state(&test_result) {
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
exit(EXIT_DSC_ASSERTION_FAILED);
}
let properties = match test_result.result {
TestResult::Resource(test_response) => {
if test_response.actual_state.is_object() {
Expand Down Expand Up @@ -150,6 +154,10 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
else if *as_get {
let mut group_result = Vec::<ResourceGetResult>::new();
for test_result in result.results {
if *as_assert && !in_desired_state(&test_result) {
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
exit(EXIT_DSC_ASSERTION_FAILED);
}
group_result.push(test_result.into());
}
match serde_json::to_string(&group_result) {
Expand All @@ -161,6 +169,14 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
}
}
else {
if *as_assert {
for test_result in &result.results {
if !in_desired_state(test_result) {
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
exit(EXIT_DSC_ASSERTION_FAILED);
}
}
}
match serde_json::to_string(&(result.results)) {
Ok(json) => json,
Err(err) => {
Expand Down Expand Up @@ -252,7 +268,7 @@ fn initialize_config_root(path: Option<&String>) -> Option<String> {
}

#[allow(clippy::too_many_lines)]
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounted_path: Option<&String>, as_group: &bool, as_include: &bool, progress_format: ProgressFormat) {
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) {
let (new_parameters, json_string) = match subcommand {
ConfigSubCommand::Get { input, file, .. } |
ConfigSubCommand::Set { input, file, .. } |
Expand Down Expand Up @@ -363,7 +379,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounte
config_set(&mut configurator, output_format.as_ref(), as_group);
},
ConfigSubCommand::Test { output_format, as_get, as_config, .. } => {
config_test(&mut configurator, output_format.as_ref(), as_group, as_get, as_config);
config_test(&mut configurator, output_format.as_ref(), as_group, as_get, as_config, as_assert);
},
ConfigSubCommand::Validate { input, file, output_format} => {
let mut result = ValidateResult {
Expand Down
29 changes: 29 additions & 0 deletions dsc/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::args::{DscType, OutputFormat, TraceFormat};
use crate::resolve::Include;
use dsc_lib::configure::config_result::ResourceTestResult;
use dsc_lib::{
configure::{
config_doc::Configuration,
Expand Down Expand Up @@ -54,6 +55,7 @@ pub const EXIT_INVALID_INPUT: i32 = 4;
pub const EXIT_VALIDATION_FAILED: i32 = 5;
pub const EXIT_CTRL_C: i32 = 6;
pub const EXIT_DSC_RESOURCE_NOT_FOUND: i32 = 7;
pub const EXIT_DSC_ASSERTION_FAILED: i32 = 8;

pub const DSC_CONFIG_ROOT: &str = "DSC_CONFIG_ROOT";
pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL";
Expand Down Expand Up @@ -530,3 +532,30 @@ pub fn set_dscconfigroot(config_path: &str) -> String

full_path.to_string_lossy().into_owned()
}


/// Check if the test result is in the desired state.
///
/// # Arguments
///
/// * `test_result` - The test result to check
///
/// # Returns
///
/// * `bool` - True if the test result is in the desired state, false otherwise
#[must_use]
pub fn in_desired_state(test_result: &ResourceTestResult) -> bool {
match &test_result.result {
TestResult::Resource(result) => {
result.in_desired_state
},
TestResult::Group(results) => {
for result in results {
if !in_desired_state(result) {
return false;
}
}
true
}
}
}
20 changes: 20 additions & 0 deletions dsc/tests/dsc_assertion.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'Assertion resource tests' -Skip:(!$IsWindows) {
It 'Example works for <operation>' -TestCases @(
@{ operation = 'get' }
@{ operation = 'set' }
@{ operation = 'test' }
# TODO: Add export to test when https://github.com/PowerShell/DSC/issues/428 is fixed
# @{ operation = 'export' }
) {
param($operation)
$jsonPath = Join-Path $PSScriptRoot '../examples/assertion.dsc.yaml'
$out = dsc config $operation -f $jsonPath 2> "$TestDrive/trace.log"
$LASTEXITCODE | Should -Be 2
$out | Should -BeNullOrEmpty
$log = Get-Content "$TestDrive/trace.log" -Raw
$log | Should -Match '.*Assertion failed.*'
}
}
13 changes: 6 additions & 7 deletions dsc/tests/dsc_config_test.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ Describe 'dsc config test tests' {
family: Windows
'@

$out = dsc config test -i $configYaml | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0

$out = dsc config test -i $configYaml 2> "$TestDrive/trace.log" | ConvertFrom-Json
if ($IsWindows) {
$LASTEXITCODE | Should -Be 0
$out.results[0].result.inDesiredState | Should -BeTrue
}
else {
$out.results[0].result.inDesiredState | Should -BeFalse
$out.results[0].result.differingProperties | Should -Contain 'resources'
} else {
$LASTEXITCODE | Should -Be 2
$log = Get-Content "$TestDrive/trace.log" -Raw
$log | Should -Match '.*Assertion failed.*'
}
}

Expand Down