Skip to content
Open
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
1 change: 0 additions & 1 deletion dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,6 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap
(Capability::Get, "g"),
(Capability::Set, "s"),
(Capability::SetHandlesExist, "x"),
(Capability::WhatIf, "w"),
(Capability::Test, "t"),
(Capability::Delete, "d"),
(Capability::Export, "e"),
Expand Down
2 changes: 1 addition & 1 deletion dsc/tests/dsc_discovery.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ Describe 'tests for resource discovery' {
@{ operation = 'delete' }
@{ operation = 'export' }
@{ operation = 'resolve' }
@{ operation = 'whatIf' }
) {
param($operation)

Expand All @@ -212,6 +211,7 @@ Describe 'tests for resource discovery' {
$out.Count | Should -Be 1
$out.Type | Should -BeExactly 'Test/ExecutableNotFound'
$out.Kind | Should -BeExactly 'resource'
(Get-Content -Path "$testdrive/error.txt" -Raw)
(Get-Content -Path "$testdrive/error.txt" -Raw) | Should -Match "INFO.*?Executable 'doesNotExist' not found"
}
finally {
Expand Down
37 changes: 37 additions & 0 deletions dsc/tests/dsc_whatif.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,41 @@ Describe 'whatif tests' {
$result.results.Count | Should -Be 1
$LASTEXITCODE | Should -Be 0
}

It 'Test/WhatIfNative resource with set operation and WhatIfArgKind works' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: WhatIfArgKind
type: Test/WhatIfArgKind
properties:
executionType: Actual
"@
$what_if_result = $config_yaml | dsc config set -w -f - | ConvertFrom-Json
$set_result = $config_yaml | dsc config set -f - | ConvertFrom-Json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, anytime running dsc, should check $LASTEXITCODE, so there would be two checks here

$what_if_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'whatIf'
$set_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'actual'
$what_if_result.results[0].result.afterState.executionType | Should -BeExactly 'WhatIf'
$set_result.results[0].result.afterState.executionType | Should -BeExactly 'Actual'
$what_if_result.hadErrors | Should -BeFalse
$set_result.hadErrors | Should -BeFalse
}

It 'Echo resource with synthetic what-if works' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: SyntheticWhatIf
type: Microsoft.DSC.Debug/Echo
properties:
output: test
"@
$what_if_result = $config_yaml | dsc config set -w -f - | ConvertFrom-Json
$set_result = $config_yaml | dsc config set -f - | ConvertFrom-Json
$what_if_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'whatIf'
$set_result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'actual'
$what_if_result.hadErrors | Should -BeFalse
$set_result.hadErrors | Should -BeFalse
$LASTEXITCODE | Should -Be 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check exit code immediately after running dsc

}
}
1 change: 1 addition & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ invalidKey = "Unsupported value for key '%{key}'. Only string, bool, number, an
inDesiredStateNotBool = "'_inDesiredState' is not a boolean"
exportNotSupportedUsingGet = "Export is not supported by resource '%{resource}' using get operation"
runProcessError = "Failed to run process '%{executable}': %{error}"
whatIfWarning = "Resource '%{resource}' uses deprecated 'whatIf' operation. See https://github.com/PowerShell/DSC/issues/1361 for migration information."

[dscresources.dscresource]
invokeGet = "Invoking get for '%{resource}'"
Expand Down
4 changes: 0 additions & 4 deletions lib/dsc-lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,10 +770,6 @@ fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result<Ds
capabilities.push(Capability::SetHandlesExist);
}
}
if let Some(what_if) = &manifest.what_if {
verify_executable(&manifest.resource_type, "what_if", &what_if.executable, path.parent().unwrap());
capabilities.push(Capability::WhatIf);
}
if let Some(test) = &manifest.test {
verify_executable(&manifest.resource_type, "test", &test.executable, path.parent().unwrap());
capabilities.push(Capability::Test);
Expand Down
90 changes: 71 additions & 19 deletions lib/dsc-lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde_json::{Map, Value};
use std::{collections::HashMap, env, path::Path, process::Stdio};
use crate::{configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}, types::FullyQualifiedTypeName, util::canonicalize_which};
use crate::dscerror::DscError;
use super::{dscresource::{get_diff, redact}, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
use super::{dscresource::{get_diff, redact}, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{GetArgKind, SetDeleteArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
use tracing::{error, warn, info, debug, trace};
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Command};

Expand All @@ -35,7 +35,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &Path, filter: &str, target_
Some(r) => r,
None => resource.resource_type.clone(),
};
let args = process_args(get.args.as_ref(), filter, &resource_type);
let args = process_get_args(get.args.as_ref(), filter, &resource_type);
if !filter.is_empty() {
verify_json(resource, cwd, filter)?;
command_input = get_command_input(get.input.as_ref(), filter)?;
Expand Down Expand Up @@ -82,22 +82,35 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &Path, desired: &str, skip_t
debug!("{}", t!("dscresources.commandResource.invokeSet", resource = &resource.resource_type));
let operation_type: String;
let mut is_synthetic_what_if = false;

let set_method = match execution_type {
ExecutionKind::Actual => {
operation_type = "set".to_string();
&resource.set
},
ExecutionKind::WhatIf => {
operation_type = "whatif".to_string();
if resource.what_if.is_none() {
is_synthetic_what_if = true;
// Check if set supports native what-if
let has_native_whatif = resource.set.as_ref()
.map_or(false, |set| {
let (_, supports_whatif) = process_set_delete_args(set.args.as_ref(), "", &resource.resource_type, execution_type);
supports_whatif
});

if has_native_whatif {
&resource.set
} else {
&resource.what_if
if resource.what_if.is_some() {
warn!("{}", t!("dscresources.commandResource.whatIfWarning", resource = &resource.resource_type));
&resource.what_if
} else {
is_synthetic_what_if = true;
&resource.set
}
}
}
};
let Some(set) = set_method else {
let Some(set) = set_method.as_ref() else {
return Err(DscError::NotImplemented("set".to_string()));
};
verify_json(resource, cwd, desired)?;
Expand Down Expand Up @@ -144,7 +157,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &Path, desired: &str, skip_t
Some(r) => r,
None => resource.resource_type.clone(),
};
let args = process_args(get.args.as_ref(), desired, &resource_type);
let args = process_get_args(get.args.as_ref(), desired, &resource_type);
let command_input = get_command_input(get.input.as_ref(), desired)?;

info!("{}", t!("dscresources.commandResource.setGetCurrent", resource = &resource.resource_type, executable = &get.executable));
Expand Down Expand Up @@ -176,7 +189,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &Path, desired: &str, skip_t

let mut env: Option<HashMap<String, String>> = None;
let mut input_desired: Option<&str> = None;
let args = process_args(set.args.as_ref(), desired, &resource_type);
let (args, _) = process_set_delete_args(set.args.as_ref(), desired, &resource_type, execution_type);
match &set.input {
Some(InputKind::Env) => {
env = Some(json_to_hashmap(desired)?);
Expand Down Expand Up @@ -284,7 +297,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &Path, expected: &str, targ
Some(r) => r,
None => resource.resource_type.clone(),
};
let args = process_args(test.args.as_ref(), expected, &resource_type);
let args = process_get_args(test.args.as_ref(), expected, &resource_type);
let command_input = get_command_input(test.input.as_ref(), expected)?;

info!("{}", t!("dscresources.commandResource.invokeTestUsing", resource = &resource.resource_type, executable = &test.executable));
Expand Down Expand Up @@ -411,6 +424,7 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &Path, expected: &str
/// * `resource` - The resource manifest for the command resource.
/// * `cwd` - The current working directory.
/// * `filter` - The filter to apply to the resource in JSON.
/// * `execution_type` - Whether this is an actual delete or what-if.
///
/// # Errors
///
Expand All @@ -426,7 +440,8 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &Path, filter: &str, targ
Some(r) => r,
None => &resource.resource_type,
};
let args = process_args(delete.args.as_ref(), filter, resource_type);
let (args, _) = process_set_delete_args(delete.args.as_ref(), filter, resource_type, &ExecutionKind::Actual);

let command_input = get_command_input(delete.input.as_ref(), filter)?;

info!("{}", t!("dscresources.commandResource.invokeDeleteUsing", resource = resource_type, executable = &delete.executable));
Expand Down Expand Up @@ -461,7 +476,7 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &Path, config: &str, ta
Some(r) => r,
None => &resource.resource_type,
};
let args = process_args(validate.args.as_ref(), config, resource_type);
let args = process_get_args(validate.args.as_ref(), config, resource_type);
let command_input = get_command_input(validate.input.as_ref(), config)?;

info!("{}", t!("dscresources.commandResource.invokeValidateUsing", resource = resource_type, executable = &validate.executable));
Expand Down Expand Up @@ -549,9 +564,9 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &Path, input: Option<&str
command_input = get_command_input(export.input.as_ref(), input)?;
}

args = process_args(export.args.as_ref(), input, &resource_type);
args = process_get_args(export.args.as_ref(), input, &resource_type);
} else {
args = process_args(export.args.as_ref(), "", &resource_type);
args = process_get_args(export.args.as_ref(), "", &resource_type);
}

let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?;
Expand Down Expand Up @@ -596,7 +611,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &Path, input: &str) -> R
return Err(DscError::Operation(t!("dscresources.commandResource.resolveNotSupported", resource = &resource.resource_type).to_string()));
};

let args = process_args(resolve.args.as_ref(), input, &resource.resource_type);
let args = process_get_args(resolve.args.as_ref(), input, &resource.resource_type);
let command_input = get_command_input(resolve.input.as_ref(), input)?;

info!("{}", t!("dscresources.commandResource.invokeResolveUsing", resource = &resource.resource_type, executable = &resolve.executable));
Expand Down Expand Up @@ -810,7 +825,7 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
/// # Returns
///
/// A vector of strings representing the processed arguments
pub fn process_args(args: Option<&Vec<ArgKind>>, input: &str, resource_type: &str) -> Option<Vec<String>> {
pub fn process_get_args(args: Option<&Vec<GetArgKind>>, input: &str, resource_type: &str) -> Option<Vec<String>> {
let Some(arg_values) = args else {
debug!("{}", t!("dscresources.commandResource.noArgs"));
return None;
Expand All @@ -819,27 +834,64 @@ pub fn process_args(args: Option<&Vec<ArgKind>>, input: &str, resource_type: &st
let mut processed_args = Vec::<String>::new();
for arg in arg_values {
match arg {
ArgKind::String(s) => {
GetArgKind::String(s) => {
processed_args.push(s.clone());
},
ArgKind::Json { json_input_arg, mandatory } => {
GetArgKind::Json { json_input_arg, mandatory } => {
if input.is_empty() && *mandatory != Some(true) {
continue;
}

processed_args.push(json_input_arg.clone());
processed_args.push(input.to_string());
},
ArgKind::ResourceType { resource_type_arg } => {
GetArgKind::ResourceType { resource_type_arg } => {
processed_args.push(resource_type_arg.clone());
processed_args.push(resource_type.to_string());
}
},
}
}

Some(processed_args)
}

pub fn process_set_delete_args(args: Option<&Vec<SetDeleteArgKind>>, input: &str, resource_type: &str, execution_type: &ExecutionKind) -> (Option<Vec<String>>, bool) {
let Some(arg_values) = args else {
debug!("{}", t!("dscresources.commandResource.noArgs"));
return (None, false);
};

let mut processed_args = Vec::<String>::new();
let mut supports_whatif = false;
for arg in arg_values {
match arg {
SetDeleteArgKind::String(s) => {
processed_args.push(s.clone());
},
SetDeleteArgKind::Json { json_input_arg, mandatory } => {
if input.is_empty() && *mandatory != Some(true) {
continue;
}

processed_args.push(json_input_arg.clone());
processed_args.push(input.to_string());
},
SetDeleteArgKind::ResourceType { resource_type_arg } => {
processed_args.push(resource_type_arg.clone());
processed_args.push(resource_type.to_string());
},
SetDeleteArgKind::WhatIf { what_if_arg } => {
supports_whatif = true;
if execution_type == &ExecutionKind::WhatIf {
processed_args.push(what_if_arg.clone());
}
}
}
}

(Some(processed_args), supports_whatif)
}

struct CommandInput {
env: Option<HashMap<String, String>>,
stdin: Option<String>,
Expand Down
Loading