Skip to content

Commit

Permalink
add whatif as a separate object in manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
tgauth committed Jun 5, 2024
1 parent a37e30f commit 849a35e
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 62 deletions.
4 changes: 2 additions & 2 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin:

if let ConfigSubCommand::Set { what_if , .. } = subcommand {
if *what_if {
configurator.context.execution_type = ExecutionKind::WhatIfDSC;
configurator.context.execution_type = ExecutionKind::WhatIf;
}
};

Expand Down Expand Up @@ -516,7 +516,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: &Option<String>, adapter_
(Capability::Get, "g"),
(Capability::Set, "s"),
(Capability::SetHandlesExist, "x"),
(Capability::SetHandlesWhatIf, "w"),
(Capability::WhatIf, "w"),
(Capability::Test, "t"),
(Capability::Delete, "d"),
(Capability::Export, "e"),
Expand Down
6 changes: 1 addition & 5 deletions dsc_lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ pub enum Operation {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
pub enum ExecutionKind {
Actual,
// differentiate internally whether what-if should be processed by the resource or dsc engine
#[serde(rename = "WhatIf")]
WhatIfDSC,
#[serde(rename = "WhatIf")]
WhatIfResource,
WhatIf,
}

#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
Expand Down
2 changes: 1 addition & 1 deletion dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ impl Configurator {
set_result = dsc_resource.set(&desired, skip_test, &self.context.execution_type)?;
end_datetime = chrono::Local::now();
} else if dsc_resource.capabilities.contains(&Capability::Delete) {
if self.context.execution_type == ExecutionKind::WhatIfDSC {
if self.context.execution_type == ExecutionKind::WhatIf {
// TODO: add delete what-if support
return Err(DscError::NotSupported("What-if execution not supported for delete".to_string()));
}
Expand Down
13 changes: 4 additions & 9 deletions dsc_lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::discovery::discovery_trait::ResourceDiscovery;
use crate::discovery::convert_wildcard_to_regex;
use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs};
use crate::dscresources::resource_manifest::{import_manifest, validate_semver, ArgKind, Kind, ResourceManifest};
use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest};
use crate::dscresources::command_resource::invoke_command;
use crate::dscerror::DscError;
use indicatif::ProgressStyle;
Expand Down Expand Up @@ -449,14 +449,9 @@ fn load_manifest(path: &Path) -> Result<DscResource, DscError> {
if set.handles_exist == Some(true) {
capabilities.push(Capability::SetHandlesExist);
}
if let Some(arg_values) = &set.args {
for arg in arg_values {
if let &ArgKind::WhatIf { .. } = arg {
capabilities.push(Capability::SetHandlesWhatIf);
break;
}
}
}
}
if manifest.whatif.is_some() {
capabilities.push(Capability::WhatIf);
}
if manifest.test.is_some() {
capabilities.push(Capability::Test);
Expand Down
56 changes: 32 additions & 24 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
let Some(get) = &resource.get else {
return Err(DscError::NotImplemented("get".to_string()));
};
let args = process_args(&get.args, filter, &ExecutionKind::Actual);
let args = process_args(&get.args, filter);
if !filter.is_empty() {
verify_json(resource, cwd, filter)?;
command_input = get_command_input(&get.input, filter)?;
Expand Down Expand Up @@ -95,17 +95,30 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
#[allow(clippy::too_many_lines)]
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
// TODO: support import resources

let Some(set) = &resource.set else {
return Err(DscError::NotImplemented("set".to_string()));
let operation_type: String;
let mut is_synthetic_what_if = false;
let set = match execution_type {
ExecutionKind::Actual => {
operation_type = "set".to_string();
resource.set.clone().ok_or(DscError::NotImplemented("set".to_string()))?
},
ExecutionKind::WhatIf => {
operation_type = "whatif".to_string();
if let Some(whatif) = &resource.whatif {
whatif.clone()
} else {
is_synthetic_what_if = true;
resource.set.clone().ok_or(DscError::NotImplemented("set".to_string()))?
}
}
};
verify_json(resource, cwd, desired)?;

// if resource doesn't implement a pre-test, we execute test first to see if a set is needed
if !skip_test && set.pre_test != Some(true) {
info!("No pretest, invoking test {}", &resource.resource_type);
let test_result = invoke_test(resource, cwd, desired)?;
if execution_type == &ExecutionKind::WhatIfDSC {
if is_synthetic_what_if {
return Ok(test_result.into());
}
let (in_desired_state, actual_state) = match test_result {
Expand All @@ -130,17 +143,17 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
}
}

if ExecutionKind::WhatIfDSC == *execution_type {
if is_synthetic_what_if {
return Err(DscError::NotImplemented("cannot process what-if execution type, as resource implements pre-test and does not support what-if".to_string()));
}

let Some(get) = &resource.get else {
return Err(DscError::NotImplemented("get".to_string()));
};
let args = process_args(&get.args, desired, &ExecutionKind::Actual);
let args = process_args(&get.args, desired);
let command_input = get_command_input(&get.input, desired)?;

info!("Getting current state for set by invoking get {} using {}", &resource.resource_type, &get.executable);
info!("Getting current state for {} by invoking get {} using {}", operation_type, &resource.resource_type, &get.executable);
let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;

if resource.kind == Some(Kind::Resource) {
Expand All @@ -157,7 +170,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te

let mut env: Option<HashMap<String, String>> = None;
let mut input_desired: Option<&str> = None;
let args = process_args(&set.args, desired, execution_type);
let args = process_args(&set.args, desired);
match &set.input {
Some(InputKind::Env) => {
env = Some(json_to_hashmap(desired)?);
Expand All @@ -170,21 +183,21 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
},
}

info!("Invoking set '{}' using '{}'", &resource.resource_type, &set.executable);
info!("Invoking {} '{}' using '{}'", operation_type, &resource.resource_type, &set.executable);
let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env)?;

match set.returns {
Some(ReturnKind::State) => {

if resource.kind == Some(Kind::Resource) {
debug!("Verifying output of set '{}' using '{}'", &resource.resource_type, &set.executable);
debug!("Verifying output of {} '{}' using '{}'", operation_type, &resource.resource_type, &set.executable);
verify_json(resource, cwd, &stdout)?;
}

let actual_value: Value = match serde_json::from_str(&stdout){
Result::Ok(r) => {r},
Result::Err(err) => {
return Err(DscError::Operation(format!("Failed to parse json from set {}|{}|{} -> {err}", &set.executable, stdout, stderr)))
return Err(DscError::Operation(format!("Failed to parse json from {} '{}'|'{}'|'{}' -> {err}", operation_type, &set.executable, stdout, stderr)))
}
};

Expand Down Expand Up @@ -260,7 +273,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re

verify_json(resource, cwd, expected)?;

let args = process_args(&test.args, expected, &ExecutionKind::Actual);
let args = process_args(&test.args, expected);
let command_input = get_command_input(&test.input, expected)?;

info!("Invoking test '{}' using '{}'", &resource.resource_type, &test.executable);
Expand Down Expand Up @@ -374,7 +387,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re

verify_json(resource, cwd, filter)?;

let args = process_args(&delete.args, filter, &ExecutionKind::Actual);
let args = process_args(&delete.args, filter);
let command_input = get_command_input(&delete.input, filter)?;

info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable);
Expand Down Expand Up @@ -405,7 +418,7 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) ->
return Err(DscError::NotImplemented("validate".to_string()));
};

let args = process_args(&validate.args, config, &ExecutionKind::Actual);
let args = process_args(&validate.args, config);
let command_input = get_command_input(&validate.input, config)?;

info!("Invoking validate '{}' using '{}'", &resource.resource_type, &validate.executable);
Expand Down Expand Up @@ -480,9 +493,9 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
command_input = get_command_input(&export.input, input)?;
}

args = process_args(&export.args, input, &ExecutionKind::Actual);
args = process_args(&export.args, input);
} else {
args = process_args(&export.args, "", &ExecutionKind::Actual);
args = process_args(&export.args, "");
}

let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
Expand Down Expand Up @@ -527,7 +540,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re
return Err(DscError::Operation(format!("Resolve is not supported by resource {}", &resource.resource_type)));
};

let args = process_args(&resolve.args, input, &ExecutionKind::Actual);
let args = process_args(&resolve.args, input);
let command_input = get_command_input(&resolve.input, input)?;

info!("Invoking resolve '{}' using '{}'", &resource.resource_type, &resolve.executable);
Expand Down Expand Up @@ -619,7 +632,7 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
Ok((exit_code, stdout, cleaned_stderr))
}

fn process_args(args: &Option<Vec<ArgKind>>, value: &str, execution_type: &ExecutionKind) -> Option<Vec<String>> {
fn process_args(args: &Option<Vec<ArgKind>>, value: &str) -> Option<Vec<String>> {
let Some(arg_values) = args else {
debug!("No args to process");
return None;
Expand All @@ -639,11 +652,6 @@ fn process_args(args: &Option<Vec<ArgKind>>, value: &str, execution_type: &Execu
processed_args.push(json_input_arg.clone());
processed_args.push(value.to_string());
},
ArgKind::WhatIf { what_if_input_arg } => {
if execution_type == &ExecutionKind::WhatIfResource {
processed_args.push(what_if_input_arg.clone());
}
}
}
}

Expand Down
12 changes: 3 additions & 9 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ pub enum Capability {
Set,
/// The resource supports the `_exist` property directly.
SetHandlesExist,
/// The resource supports the `what-if` execution type directly.
SetHandlesWhatIf,
/// The resource supports simulating configuration directly.
WhatIf,
/// The resource supports validating configuration.
Test,
/// The resource supports deleting configuration.
Expand Down Expand Up @@ -211,13 +211,7 @@ impl Invoke for DscResource {
return Err(DscError::MissingManifest(self.type_name.clone()));
};
let resource_manifest = import_manifest(manifest.clone())?;
let execution = if self.capabilities.contains(&Capability::SetHandlesWhatIf) && execution_type == &ExecutionKind::WhatIfDSC {
ExecutionKind::WhatIfResource
}
else {
execution_type.clone()
};
command_resource::invoke_set(&resource_manifest, &self.directory, desired, skip_test, &execution)
command_resource::invoke_set(&resource_manifest, &self.directory, desired, skip_test, execution_type)
},
}
}
Expand Down
8 changes: 3 additions & 5 deletions dsc_lib/src/dscresources/resource_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub struct ResourceManifest {
/// Details how to call the Set method of the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub set: Option<SetMethod>,
/// Details how to call the `WhatIf` method of the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub whatif: Option<SetMethod>,
/// Details how to call the Test method of the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub test: Option<TestMethod>,
Expand Down Expand Up @@ -103,11 +106,6 @@ pub enum ArgKind {
/// Indicates if argument is mandatory which will pass an empty string if no JSON input is provided. Default is false.
mandatory: Option<bool>,
},
WhatIf {
/// The argument that serves as the what-if switch.
#[serde(rename = "whatIfSwitchArg")]
what_if_input_arg: String,
}
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
Expand Down
15 changes: 10 additions & 5 deletions tools/dsctest/dscwhatif.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@
"get": {
"executable": "dsctest",
"args": [
"what-if"
"whatif"
]
},
"set": {
"executable": "dsctest",
"args": [
"what-if",
{
"whatIfSwitchArg": "--what-if"
}
"whatif"
],
"return": "state"
},
"whatif": {
"executable": "dsctest",
"args": [
"whatif",
"-w"
],
"return": "state"
},
Expand Down
4 changes: 2 additions & 2 deletions tools/dsctest/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ pub enum SubCommand {
#[clap(name = "trace", about = "The trace level")]
Trace,

#[clap(name = "what-if", about = "Check if it is a what-if operation")]
#[clap(name = "whatif", about = "Check if it is a whatif operation")]
WhatIf {
#[clap(name = "what-if", short, long, help = "Run as a what-if executionType instead of actual executionType")]
#[clap(name = "whatif", short, long, help = "Run as a whatif executionType instead of actual executionType")]
what_if: bool,
}
}

0 comments on commit 849a35e

Please sign in to comment.