Skip to content

Commit

Permalink
Merge pull request PowerShell#441 from tgauth/add-resource-what-if
Browse files Browse the repository at this point in the history
Add resource support for what if
  • Loading branch information
SteveL-MSFT authored Jun 6, 2024
2 parents b26a223 + 857c03d commit acb01cd
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 12 deletions.
3 changes: 2 additions & 1 deletion dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,12 @@ fn list_resources(dsc: &mut DscManager, resource_name: &Option<String>, adapter_
write_table = true;
}
for resource in dsc.list_available_resources(&resource_name.clone().unwrap_or("*".to_string()), &adapter_name.clone().unwrap_or_default()) {
let mut capabilities = "-------".to_string();
let mut capabilities = "--------".to_string();
let capability_types = [
(Capability::Get, "g"),
(Capability::Set, "s"),
(Capability::SetHandlesExist, "x"),
(Capability::WhatIf, "w"),
(Capability::Test, "t"),
(Capability::Delete, "d"),
(Capability::Export, "e"),
Expand Down
36 changes: 36 additions & 0 deletions dsc/tests/dsc_whatif.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,40 @@ Describe 'whatif tests' {
$result | Should -Match 'ERROR.*?Not implemented.*?what-if'
$LASTEXITCODE | Should -Be 2
}

It 'actual execution of WhatIf resource' {
$config_yaml = @"
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
resources:
- name: WhatIf
type: Test/WhatIf
properties:
executionType: Actual
"@
$result = $config_yaml | dsc config set | ConvertFrom-Json
$result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'Actual'
$result.results.result.afterState.executionType | Should -BeExactly 'Actual'
$result.results.result.changedProperties | Should -Be $null
$result.hadErrors | Should -BeFalse
$result.results.Count | Should -Be 1
$LASTEXITCODE | Should -Be 0
}

It 'what-if execution of WhatIf resource' {
$config_yaml = @"
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
resources:
- name: WhatIf
type: Test/WhatIf
properties:
executionType: Actual
"@
$result = $config_yaml | dsc config set -w | ConvertFrom-Json
$result.metadata.'Microsoft.DSC'.executionType | Should -BeExactly 'WhatIf'
$result.results.result.afterState.executionType | Should -BeExactly 'WhatIf'
$result.results.result.changedProperties | Should -BeExactly 'executionType'
$result.hadErrors | Should -BeFalse
$result.results.Count | Should -Be 1
$LASTEXITCODE | Should -Be 0
}
}
3 changes: 3 additions & 0 deletions dsc_lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ fn load_manifest(path: &Path) -> Result<DscResource, DscError> {
capabilities.push(Capability::SetHandlesExist);
}
}
if manifest.what_if.is_some() {
capabilities.push(Capability::WhatIf);
}
if manifest.test.is_some() {
capabilities.push(Capability::Test);
}
Expand Down
37 changes: 26 additions & 11 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,24 @@ 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 {
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;
&resource.set
} else {
&resource.what_if
}
}
};
let Some(set) = set_method else {
return Err(DscError::NotImplemented("set".to_string()));
};
verify_json(resource, cwd, desired)?;
Expand All @@ -105,7 +121,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
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::WhatIf {
if is_synthetic_what_if {
return Ok(test_result.into());
}
let (in_desired_state, actual_state) = match test_result {
Expand All @@ -121,7 +137,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
}
};

if in_desired_state {
if in_desired_state && execution_type == &ExecutionKind::Actual {
return Ok(SetResult::Resource(ResourceSetResponse{
before_state: serde_json::from_str(desired)?,
after_state: actual_state,
Expand All @@ -130,9 +146,8 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
}
}

if ExecutionKind::WhatIf == *execution_type {
// TODO: continue execution when resources can implement what-if; only return an error here temporarily
return Err(DscError::NotImplemented("what-if not yet supported for resources that implement pre-test".to_string()));
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 {
Expand All @@ -141,7 +156,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
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 Down Expand Up @@ -171,21 +186,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
2 changes: 2 additions & 0 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum Capability {
Set,
/// The resource supports the `_exist` property directly.
SetHandlesExist,
/// The resource supports simulating configuration directly.
WhatIf,
/// The resource supports validating configuration.
Test,
/// The resource supports deleting configuration.
Expand Down
3 changes: 3 additions & 0 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(rename = "whatIf", skip_serializing_if = "Option::is_none")]
pub what_if: 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
36 changes: 36 additions & 0 deletions tools/dsctest/dscwhatif.dsc.resource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
"type": "Test/WhatIf",
"version": "0.1.0",
"get": {
"executable": "dsctest",
"args": [
"whatif"
]
},
"set": {
"executable": "dsctest",
"args": [
"whatif"
],
"return": "state"
},
"whatIf": {
"executable": "dsctest",
"args": [
"whatif",
"-w"
],
"return": "state"
},
"schema": {
"command": {
"executable": "dsctest",
"args": [
"schema",
"-s",
"what-if"
]
}
}
}
7 changes: 7 additions & 0 deletions tools/dsctest/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub enum Schemas {
Exist,
Sleep,
Trace,
WhatIf,
}

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -54,4 +55,10 @@ pub enum SubCommand {

#[clap(name = "trace", about = "The trace level")]
Trace,

#[clap(name = "whatif", about = "Check if it is a whatif operation")]
WhatIf {
#[clap(name = "whatif", short, long, help = "Run as a whatif executionType instead of actual executionType")]
what_if: bool,
}
}
13 changes: 13 additions & 0 deletions tools/dsctest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod echo;
mod exist;
mod sleep;
mod trace;
mod whatif;

use args::{Args, Schemas, SubCommand};
use clap::Parser;
Expand All @@ -16,6 +17,7 @@ use crate::echo::Echo;
use crate::exist::{Exist, State};
use crate::sleep::Sleep;
use crate::trace::Trace;
use crate::whatif::WhatIf;
use std::{thread, time::Duration};

fn main() {
Expand Down Expand Up @@ -75,6 +77,9 @@ fn main() {
Schemas::Trace => {
schema_for!(Trace)
},
Schemas::WhatIf => {
schema_for!(WhatIf)
},
};
serde_json::to_string(&schema).unwrap()
},
Expand All @@ -100,6 +105,14 @@ fn main() {
};
serde_json::to_string(&trace).unwrap()
},
SubCommand::WhatIf { what_if } => {
let result: WhatIf = if what_if {
WhatIf { execution_type: "WhatIf".to_string() }
} else {
WhatIf { execution_type: "Actual".to_string() }
};
serde_json::to_string(&result).unwrap()
},
};

println!("{json}");
Expand Down
12 changes: 12 additions & 0 deletions tools/dsctest/src/whatif.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct WhatIf {
#[serde(rename = "executionType")]
pub execution_type: String,
}

0 comments on commit acb01cd

Please sign in to comment.