Skip to content

Add resource support for what if #441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 6, 2024
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
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://github.com/raw/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://github.com/raw/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://github.com/raw/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,
}
Loading