Skip to content

Commit 78e9f2e

Browse files
author
Steve Lee (POWERSHELL HE/HIM) (from Dev Box)
committed
Update engine to call delete for resources that don't support _exist directly
1 parent 082f7d2 commit 78e9f2e

File tree

8 files changed

+178
-15
lines changed

8 files changed

+178
-15
lines changed

dsc/tests/dsc_config_set.tests.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'dsc config get tests' {
5+
It 'can use _exist with resources that support and do not support it' {
6+
$config_yaml = @"
7+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
8+
resources:
9+
- name: Exist
10+
type: Test/Exist
11+
properties:
12+
_exist: false
13+
- name: Delete
14+
type: Test/Delete
15+
properties:
16+
_exist: false
17+
"@
18+
$out = $config_yaml | dsc config set | ConvertFrom-Json
19+
$LASTEXITCODE | Should -Be 0
20+
$out.results[0].type | Should -BeExactly 'Test/Exist'
21+
$out.results[0].result.beforeState.state | Should -BeExactly 'Absent'
22+
$out.results[0].result.beforeState._exist | Should -BeFalse
23+
$out.results[0].result.afterState.state | Should -BeExactly 'Absent'
24+
$out.results[0].result.afterState._exist | Should -BeFalse
25+
$out.results[1].type | Should -BeExactly 'Test/Delete'
26+
$out.results[1].result.beforeState.deleteCalled | Should -BeTrue
27+
$out.results[1].result.beforeState._exist | Should -BeFalse
28+
$out.results[1].result.afterState.deleteCalled | Should -BeTrue
29+
$out.results[1].result.afterState._exist | Should -BeFalse
30+
}
31+
}

dsc_lib/src/configure/mod.rs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
use crate::configure::parameters::Input;
55
use crate::dscerror::DscError;
6-
use crate::dscresources::dscresource::Invoke;
6+
use crate::dscresources::dscresource::get_diff;
7+
use crate::dscresources::invoke_result::GetResult;
8+
use crate::dscresources::{dscresource::{Capability, Invoke}, invoke_result::{SetResult, ResourceSetResponse}};
79
use crate::dscresources::resource_manifest::Kind;
810
use crate::DscResource;
911
use crate::discovery::Discovery;
@@ -278,16 +280,68 @@ impl Configurator {
278280
return Err(DscError::ResourceNotFound(resource.resource_type));
279281
};
280282
debug!("resource_type {}", &resource.resource_type);
283+
284+
// see if the properties contains `_exist` and is false
285+
let exist = match &properties {
286+
Some(property_map) => {
287+
if let Some(exist) = property_map.get("_exist") {
288+
!matches!(exist, Value::Bool(false))
289+
} else {
290+
true
291+
}
292+
},
293+
_ => {
294+
true
295+
}
296+
};
297+
281298
let desired = add_metadata(&dsc_resource.kind, properties)?;
282299
trace!("desired: {desired}");
283-
let set_result = dsc_resource.set(&desired, skip_test)?;
284-
self.context.outputs.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&set_result)?);
285-
let resource_result = config_result::ResourceSetResult {
286-
name: resource.name.clone(),
287-
resource_type: resource.resource_type.clone(),
288-
result: set_result,
289-
};
290-
result.results.push(resource_result);
300+
301+
if exist || dsc_resource.capabilities.contains(&Capability::SetHandlesExist) {
302+
debug!("Resource handles _exist or _exist is true");
303+
let set_result = dsc_resource.set(&desired, skip_test)?;
304+
self.context.outputs.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&set_result)?);
305+
let resource_result = config_result::ResourceSetResult {
306+
name: resource.name.clone(),
307+
resource_type: resource.resource_type.clone(),
308+
result: set_result,
309+
};
310+
result.results.push(resource_result);
311+
} else if dsc_resource.capabilities.contains(&Capability::Delete) {
312+
debug!("Resource implements delete and _exist is false");
313+
let before_result = dsc_resource.get(&desired)?;
314+
dsc_resource.delete(&desired)?;
315+
let after_result = dsc_resource.get(&desired)?;
316+
// convert get result to set result
317+
let set_result = match before_result {
318+
GetResult::Resource(before_response) => {
319+
let after_result = match after_result {
320+
GetResult::Resource(get_response) => get_response,
321+
_ => return Err(DscError::NotSupported("Group resources not supported for delete".to_string())),
322+
};
323+
let before_value = serde_json::to_value(&before_response.actual_state)?;
324+
let after_value = serde_json::to_value(&after_result.actual_state)?;
325+
ResourceSetResponse {
326+
before_state: before_response.actual_state,
327+
after_state: after_result.actual_state,
328+
changed_properties: Some(get_diff(&before_value, &after_value)),
329+
}
330+
},
331+
GetResult::Group(_) => {
332+
return Err(DscError::NotSupported("Group resources not supported for delete".to_string()));
333+
},
334+
};
335+
self.context.outputs.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&set_result)?);
336+
let resource_result = config_result::ResourceSetResult {
337+
name: resource.name.clone(),
338+
resource_type: resource.resource_type.clone(),
339+
result: SetResult::Resource(set_result),
340+
};
341+
result.results.push(resource_result);
342+
} else {
343+
return Err(DscError::NotImplemented(format!("Resource '{}' does not support `delete` and does not handle `_exist` as false", resource.resource_type)));
344+
}
291345
}
292346

293347
mem::drop(pb_span_enter);

dsc_lib/src/dscerror.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub enum DscError {
7171
#[error("Not implemented: {0}")]
7272
NotImplemented(String),
7373

74+
#[error("Not supported: {0}")]
75+
NotSupported(String),
76+
7477
#[error("Number conversion error: {0}")]
7578
NumberConversion(#[from] std::num::TryFromIntError),
7679

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json",
3+
"type": "Test/Delete",
4+
"version": "0.1.0",
5+
"get": {
6+
"executable": "dsctest",
7+
"args": [
8+
"delete",
9+
"--input",
10+
"{json}"
11+
],
12+
"input": {
13+
"arg": "{json}"
14+
}
15+
},
16+
"delete": {
17+
"executable": "dsctest",
18+
"args": [
19+
"delete",
20+
"--input",
21+
"{json}"
22+
],
23+
"input": {
24+
"arg": "{json}"
25+
}
26+
},
27+
"schema": {
28+
"command": {
29+
"executable": "dsctest",
30+
"args": [
31+
"schema",
32+
"-s",
33+
"delete"
34+
]
35+
}
36+
}
37+
}

tools/dsctest/dscexist.dsc.resource.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"arg": "{json}"
2525
},
2626
"handlesExist": true,
27+
"implementsPretest": true,
2728
"return": "state"
2829
},
2930
"schema": {

tools/dsctest/src/args.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clap::{Parser, Subcommand, ValueEnum};
55

66
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
77
pub enum Schemas {
8+
Delete,
89
Echo,
910
Exist,
1011
Sleep,
@@ -20,12 +21,24 @@ pub struct Args {
2021

2122
#[derive(Debug, PartialEq, Eq, Subcommand)]
2223
pub enum SubCommand {
24+
#[clap(name = "delete", about = "delete operation")]
25+
Delete {
26+
#[clap(name = "input", short, long, help = "The input to the delete command as JSON")]
27+
input: String,
28+
},
29+
2330
#[clap(name = "echo", about = "Return the input")]
2431
Echo {
2532
#[clap(name = "input", short, long, help = "The input to the echo command as JSON")]
2633
input: String,
2734
},
2835

36+
#[clap(name = "exist", about = "Check if a resource exists")]
37+
Exist {
38+
#[clap(name = "input", short, long, help = "The input to the exist command as JSON")]
39+
input: String,
40+
},
41+
2942
#[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
3043
Schema {
3144
#[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]
@@ -37,10 +50,4 @@ pub enum SubCommand {
3750
#[clap(name = "input", short, long, help = "The input to the sleep command as JSON")]
3851
input: String,
3952
},
40-
41-
#[clap(name = "exist", about = "Check if a resource exists")]
42-
Exist {
43-
#[clap(name = "input", short, long, help = "The input to the exist command as JSON")]
44-
input: String,
45-
},
4653
}

tools/dsctest/src/delete.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use schemars::JsonSchema;
5+
use serde::{Deserialize, Serialize};
6+
7+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
8+
#[serde(deny_unknown_fields)]
9+
pub struct Delete {
10+
#[serde(rename = "deleteCalled", skip_serializing_if = "Option::is_none")]
11+
pub delete_called: Option<bool>,
12+
#[serde(rename = "_exist")]
13+
pub exist: bool,
14+
}

tools/dsctest/src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
// Licensed under the MIT License.
33

44
mod args;
5+
mod delete;
56
mod echo;
67
mod exist;
78
mod sleep;
89

910
use args::{Args, Schemas, SubCommand};
1011
use clap::Parser;
1112
use schemars::schema_for;
13+
use crate::delete::Delete;
1214
use crate::echo::Echo;
1315
use crate::exist::{Exist, State};
1416
use crate::sleep::Sleep;
@@ -17,6 +19,17 @@ use std::{thread, time::Duration};
1719
fn main() {
1820
let args = Args::parse();
1921
let json = match args.subcommand {
22+
SubCommand::Delete { input } => {
23+
let mut delete = match serde_json::from_str::<Delete>(&input) {
24+
Ok(delete) => delete,
25+
Err(err) => {
26+
eprintln!("Error JSON does not match schema: {err}");
27+
std::process::exit(1);
28+
}
29+
};
30+
delete.delete_called = Some(true);
31+
serde_json::to_string(&delete).unwrap()
32+
},
2033
SubCommand::Echo { input } => {
2134
let echo = match serde_json::from_str::<Echo>(&input) {
2235
Ok(echo) => echo,
@@ -45,6 +58,9 @@ fn main() {
4558
},
4659
SubCommand::Schema { subcommand } => {
4760
let schema = match subcommand {
61+
Schemas::Delete => {
62+
schema_for!(Delete)
63+
},
4864
Schemas::Echo => {
4965
schema_for!(Echo)
5066
},

0 commit comments

Comments
 (0)