Skip to content

Commit 5fe1aca

Browse files
committed
Initial work to implement delete whatif
1 parent bdc62b1 commit 5fe1aca

File tree

5 files changed

+164
-60
lines changed

5 files changed

+164
-60
lines changed

.github/workflows/rust.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Rust
22

33
on:
44
push:
5-
branches: [ "main", "release/*" ]
5+
branches: [ "*" ]
66
pull_request:
77
branches: [ "main", "release/*" ]
88
paths-ignore:
@@ -52,10 +52,10 @@ jobs:
5252
- uses: actions/checkout@v3
5353
- name: Build
5454
shell: pwsh
55-
run: ./build.ps1 -clippy
55+
run: ./build.ps1 -clippy -UseCratesIO
5656
- name: Run tests
5757
shell: pwsh
58-
run: ./build.ps1 -test
58+
run: ./build.ps1 -test -UseCratesIO
5959

6060
build-macos:
6161

dsc_lib/src/configure/mod.rs

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -444,58 +444,68 @@ impl Configurator {
444444
};
445445
end_datetime = chrono::Local::now();
446446
} else if dsc_resource.capabilities.contains(&Capability::Delete) {
447-
if self.context.execution_type == ExecutionKind::WhatIf {
448-
// TODO: add delete what-if support
449-
return Err(DscError::NotSupported(t!("configure.mod.whatIfNotSupportedForDelete").to_string()));
450-
}
451447
debug!("{}", t!("configure.mod.implementsDelete"));
452-
let before_result = match dsc_resource.get(&desired) {
453-
Ok(result) => result,
454-
Err(e) => {
455-
progress.set_failure(get_failure_from_error(&e));
456-
progress.write_increment(1);
457-
return Err(e);
458-
},
459-
};
460-
start_datetime = chrono::Local::now();
461-
if let Err(e) = dsc_resource.delete(&desired) {
462-
progress.set_failure(get_failure_from_error(&e));
463-
progress.write_increment(1);
464-
return Err(e);
465-
}
466-
let after_result = match dsc_resource.get(&desired) {
467-
Ok(result) => result,
468-
Err(e) => {
448+
if self.context.execution_type == ExecutionKind::WhatIf {
449+
// Let the resource handle WhatIf via set (-w), which may route to delete
450+
start_datetime = chrono::Local::now();
451+
set_result = match dsc_resource.set(&desired, skip_test, &self.context.execution_type) {
452+
Ok(result) => result,
453+
Err(e) => {
454+
progress.set_failure(get_failure_from_error(&e));
455+
progress.write_increment(1);
456+
return Err(e);
457+
},
458+
};
459+
end_datetime = chrono::Local::now();
460+
} else {
461+
let before_result = match dsc_resource.get(&desired) {
462+
Ok(result) => result,
463+
Err(e) => {
464+
progress.set_failure(get_failure_from_error(&e));
465+
progress.write_increment(1);
466+
return Err(e);
467+
},
468+
};
469+
start_datetime = chrono::Local::now();
470+
if let Err(e) = dsc_resource.delete(&desired) {
469471
progress.set_failure(get_failure_from_error(&e));
470472
progress.write_increment(1);
471473
return Err(e);
472-
},
473-
};
474-
// convert get result to set result
475-
set_result = match before_result {
476-
GetResult::Resource(before_response) => {
477-
let GetResult::Resource(after_result) = after_result else {
474+
}
475+
let after_result = match dsc_resource.get(&desired) {
476+
Ok(result) => result,
477+
Err(e) => {
478+
progress.set_failure(get_failure_from_error(&e));
479+
progress.write_increment(1);
480+
return Err(e);
481+
},
482+
};
483+
// convert get result to set result
484+
set_result = match before_result {
485+
GetResult::Resource(before_response) => {
486+
let GetResult::Resource(after_result) = after_result else {
487+
return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string()))
488+
};
489+
let diff = get_diff(&before_response.actual_state, &after_result.actual_state);
490+
let mut before: Map<String, Value> = serde_json::from_value(before_response.actual_state)?;
491+
// a `get` will return a `result` property, but an actual `set` will have that as `resources`
492+
if before.contains_key("result") && !before.contains_key("resources") {
493+
before.insert("resources".to_string() ,before["result"].clone());
494+
before.remove("result");
495+
}
496+
let before_value = serde_json::to_value(&before)?;
497+
SetResult::Resource(ResourceSetResponse {
498+
before_state: before_value.clone(),
499+
after_state: after_result.actual_state,
500+
changed_properties: Some(diff),
501+
})
502+
},
503+
GetResult::Group(_) => {
478504
return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string()))
479-
};
480-
let diff = get_diff(&before_response.actual_state, &after_result.actual_state);
481-
let mut before: Map<String, Value> = serde_json::from_value(before_response.actual_state)?;
482-
// a `get` will return a `result` property, but an actual `set` will have that as `resources`
483-
if before.contains_key("result") && !before.contains_key("resources") {
484-
before.insert("resources".to_string() ,before["result"].clone());
485-
before.remove("result");
486-
}
487-
let before_value = serde_json::to_value(&before)?;
488-
SetResult::Resource(ResourceSetResponse {
489-
before_state: before_value.clone(),
490-
after_state: after_result.actual_state,
491-
changed_properties: Some(diff),
492-
})
493-
},
494-
GetResult::Group(_) => {
495-
return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string()))
496-
},
497-
};
498-
end_datetime = chrono::Local::now();
505+
},
506+
};
507+
end_datetime = chrono::Local::now();
508+
}
499509
} else {
500510
return Err(DscError::NotImplemented(t!("configure.mod.deleteNotSupported", resource = resource.resource_type).to_string()));
501511
}

registry/src/main.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,26 @@ fn main() {
7575
exit(EXIT_INVALID_INPUT);
7676
}
7777
};
78+
if what_if { reg_helper.enable_what_if(); }
79+
80+
// In what-if, if the desired state is _exist: false, route to delete
7881
if what_if {
79-
reg_helper.enable_what_if();
82+
if let Ok(desired) = serde_json::from_str::<Registry>(&input) {
83+
if matches!(desired.exist, Some(false)) {
84+
match reg_helper.remove() {
85+
Ok(Some(reg_config)) => {
86+
let json = serde_json::to_string(&reg_config).unwrap();
87+
println!("{json}");
88+
},
89+
Ok(None) => {},
90+
Err(err) => {
91+
error!("{err}");
92+
exit(EXIT_REGISTRY_ERROR);
93+
}
94+
}
95+
return;
96+
}
97+
}
8098
}
8199
match reg_helper.set() {
82100
Ok(reg_config) => {
@@ -101,7 +119,11 @@ fn main() {
101119
}
102120
};
103121
match reg_helper.remove() {
104-
Ok(()) => {},
122+
Ok(Some(reg_config)) => {
123+
let json = serde_json::to_string(&reg_config).unwrap();
124+
println!("{json}");
125+
},
126+
Ok(None) => {},
105127
Err(err) => {
106128
error!("{err}");
107129
exit(EXIT_REGISTRY_ERROR);

registry/tests/registry.config.whatif.tests.ps1

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,52 @@ Describe 'registry config whatif tests' {
131131
$result.keyPath | Should -Be 'HKCU\1\2'
132132
($result.psobject.properties | Measure-Object).Count | Should -Be 1
133133
}
134+
135+
It 'Can whatif delete an existing value using _exist is false' -Skip:(!$IsWindows) {
136+
$set_json = @'
137+
{
138+
"keyPath": "HKCU\\1\\2\\3",
139+
"valueName": "Hello",
140+
"valueData": {
141+
"String": "World"
142+
}
143+
}
144+
'@
145+
registry config set --input $set_json | Out-Null
146+
147+
$whatif_delete_value = @'
148+
{
149+
"keyPath": "HKCU\\1\\2\\3",
150+
"valueName": "Hello",
151+
"_exist": false
152+
}
153+
'@
154+
$result = registry config set -w --input $whatif_delete_value | ConvertFrom-Json
155+
$LASTEXITCODE | Should -Be 0
156+
$result.keyPath | Should -Be 'HKCU\1\2\3'
157+
$result.valueName | Should -Be 'Hello'
158+
$result._metadata.whatIf | Should -Match "Would delete value 'Hello'"
159+
}
160+
161+
It 'Can whatif delete an existing subkey using _exist is false' -Skip:(!$IsWindows) {
162+
$set_key = @'
163+
{
164+
"keyPath": "HKCU\\1\\2\\3"
165+
}
166+
'@
167+
registry config set --input $set_key | Out-Null
168+
169+
$whatif_delete_key = @'
170+
{
171+
"keyPath": "HKCU\\1\\2\\3",
172+
"_exist": false
173+
}
174+
'@
175+
$result = registry config set -w --input $whatif_delete_key | ConvertFrom-Json
176+
$LASTEXITCODE | Should -Be 0
177+
$result.keyPath | Should -Be 'HKCU\1\2\3'
178+
$result._metadata.whatIf | Should -Match "Would delete subkey '3'"
179+
# For delete what-if, payload should only include keyPath (and optionally valueName when deleting a value)
180+
($result.psobject.properties | Where-Object { $_.Name -ne '_metadata' } | Measure-Object).Count | Should -Be 1
181+
}
134182
}

registry_lib/src/lib.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,45 +258,69 @@ impl RegistryHelper {
258258
/// # Errors
259259
///
260260
/// * `RegistryError` - The error that occurred.
261-
pub fn remove(&self) -> Result<(), RegistryError> {
261+
pub fn remove(&self) -> Result<Option<Registry>, RegistryError> {
262262
let (reg_key, _subkey) = match self.open(Security::AllAccess) {
263263
Ok(reg_key) => reg_key,
264264
// handle NotFound error
265265
Err(RegistryError::RegistryKeyNotFound(_)) => {
266266
eprintln!("{}", t!("registry_helper.removeErrorKeyNotExist"));
267-
return Ok(());
267+
return Ok(None);
268268
},
269-
Err(e) => return Err(e),
269+
Err(e) => return self.handle_error_or_what_if(e),
270270
};
271+
272+
// Accumulate what-if metadata like set()
273+
let mut what_if_metadata: Vec<String> = Vec::new();
274+
271275
if let Some(value_name) = &self.config.value_name {
276+
if self.what_if {
277+
what_if_metadata.push(format!("Would delete value '{value_name}'"));
278+
return Ok(Some(Registry {
279+
key_path: self.config.key_path.clone(),
280+
value_name: Some(value_name.clone()),
281+
metadata: Some(Metadata { what_if: Some(what_if_metadata) }),
282+
..Default::default()
283+
}));
284+
}
272285
match reg_key.delete_value(value_name) {
273286
Ok(()) | Err(value::Error::NotFound(_, _)) => {
274287
// if the value doesn't exist, we don't need to do anything
275288
},
276-
Err(e) => return Err(RegistryError::RegistryValue(e)),
289+
Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryValue(e)),
277290
}
278291
} else {
279292
// to delete the key, we need to open the parent key first
280293
let parent_path = get_parent_key_path(&self.config.key_path);
281294
let (hive, parent_subkey) = get_hive_from_path(parent_path)?;
282-
let parent_reg_key = hive.open(parent_subkey, Security::AllAccess)?;
295+
let parent_reg_key = match hive.open(parent_subkey, Security::AllAccess) {
296+
Ok(k) => k,
297+
Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryKey(e)),
298+
};
283299

284300
// get the subkey name
285301
let subkey_name = &self.config.key_path[parent_path.len() + 1..];
286302

303+
if self.what_if {
304+
what_if_metadata.push(format!("Would delete subkey '{subkey_name}'"));
305+
return Ok(Some(Registry {
306+
key_path: self.config.key_path.clone(),
307+
metadata: Some(Metadata { what_if: Some(what_if_metadata) }),
308+
..Default::default()
309+
}));
310+
}
287311
eprintln!("{}", t!("registry_helper.removeDeletingSubKey", name = subkey_name, parent = parent_reg_key));
288312
let Ok(subkey_name) = UCString::<u16>::from_str(subkey_name) else {
289-
return Err(RegistryError::Utf16Conversion("subkey_name".to_string()));
313+
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("subkey_name".to_string()));
290314
};
291315

292316
match parent_reg_key.delete(subkey_name, true) {
293317
Ok(()) | Err(key::Error::NotFound(_, _)) => {
294318
// if the subkey doesn't exist, we don't need to do anything
295319
},
296-
Err(e) => return Err(RegistryError::RegistryKey(e)),
320+
Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryKey(e)),
297321
}
298322
}
299-
Ok(())
323+
Ok(None)
300324
}
301325

302326
fn open(&self, permission: Security) -> Result<(RegKey, &str), RegistryError> {

0 commit comments

Comments
 (0)