You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .github/skills/create-dsc-resource/SKILL.md
+299Lines changed: 299 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -262,3 +262,302 @@ someError = "Failed to do something: %{error}"
262
262
#### Build and Deployment
263
263
264
264
- The resource should be built using `build.ps1 -project <resource_name>` from the root of the repository, which will handle building the Rust code and ensure it is found in PATH for testing
265
+
266
+
## What-If support
267
+
268
+
Follow this pattern exactly when adding what-if (a.k.a. `dsc config set --what-if`) support to any resource so the implementation path, manifest changes, tests, and naming stay consistent across the repository.
269
+
270
+
What-if must:
271
+
272
+
1. Project the **final state** the resource would produce, without mutating the system.
273
+
2. Echo back the relevant input fields (`keyPath`, `valueName`, `valueData`, etc.) so the engine can diff before/after.
274
+
3. Attach human-readable "would do …" messages under `_metadata.whatIf` (an array of strings).
275
+
4. Exit `0` on success — what-if is not an error path.
276
+
277
+
### 1. Resource manifest changes
278
+
279
+
In the resource's `*.dsc.resource.json`, add `whatIfArg` to the `set` (and `delete`, if it supports what-if) args array, and declare `whatIfReturns: "state"` on `set`:
280
+
281
+
```json
282
+
"set": {
283
+
"executable": "<resource_name>",
284
+
"args": [
285
+
"config", "set",
286
+
{ "jsonInputArg": "--input", "mandatory": true },
287
+
{ "whatIfArg": "--what-if" }
288
+
],
289
+
"whatIfReturns": "state"
290
+
},
291
+
"delete": {
292
+
"executable": "<resource_name>",
293
+
"args": [
294
+
"config", "delete",
295
+
{ "jsonInputArg": "--input", "mandatory": true },
296
+
{ "whatIfArg": "--what-if" }
297
+
]
298
+
}
299
+
```
300
+
301
+
-`whatIfArg` is the literal CLI flag DSC will append when the user runs `dsc config set --what-if`. Always use `"--what-if"` (long form) for consistency across resources.
302
+
-`whatIfReturns: "state"` tells DSC the executable prints the projected post-state JSON on stdout (same shape as `get`/`set` returns).
303
+
- The `--list` (bulk) variant of a resource uses the same two manifest additions; do not invent new flag names.
304
+
305
+
### 2. CLI args (`args.rs`) changes
306
+
307
+
Add a `-w` / `--what-if` boolean to every `ConfigSubCommand` variant that can support what-if:
308
+
309
+
```rust
310
+
#[derive(Debug, PartialEq, Eq, Subcommand)]
311
+
pubenumConfigSubCommand {
312
+
#[clap(name ="set", about = t!("args.configSetAbout").to_string())]
313
+
Set {
314
+
#[clap(short, long, required = true, help = t!("args.configArgsInputHelp").to_string())]
315
+
input:String,
316
+
#[clap(short ='w', long, help = t!("args.configArgsWhatIfHelp").to_string())]
317
+
what_if:bool,
318
+
},
319
+
#[clap(name ="delete", about = t!("args.configDeleteAbout").to_string())]
320
+
Delete {
321
+
#[clap(short, long, required = true, help = t!("args.configArgsInputHelp").to_string())]
322
+
input:String,
323
+
#[clap(short ='w', long, help = t!("args.configArgsWhatIfHelp").to_string())]
324
+
what_if:bool,
325
+
},
326
+
}
327
+
```
328
+
329
+
Naming is fixed: clap field is `what_if`, short flag is `-w`, long flag is `--what-if`, help key is `args.configArgsWhatIfHelp`.
330
+
331
+
### 3. `main.rs` dispatch
332
+
333
+
In each `Set` / `Delete` arm, destructure `what_if`, call `helper.enable_what_if()` when true, and print the returned projected state on stdout. **Never** mutate state when `what_if` is true.
- Change `set()` (and `remove()`) to return `Result<Option<T>, Error>` where `Some(T)` is the projected state when `what_if` is true.
367
+
- Inside `set` / `remove`, build a `Vec<String> what_if_metadata`, push localized "Would …" strings at each side-effecting branch, and **short-circuit** before the real OS call when `self.what_if`:
- Return the projected state with the metadata attached:
378
+
379
+
```rust
380
+
returnOk(Some(<ResourceState> {
381
+
// identity + projected fields the engine needs to diff
382
+
metadata:ifwhat_if_metadata.is_empty() {
383
+
None
384
+
} else {
385
+
Some(Metadata { what_if:Some(what_if_metadata) })
386
+
},
387
+
..Default::default()
388
+
}));
389
+
```
390
+
391
+
- Add a `handle_error_or_what_if(error)` helper that, in what-if mode, turns an error into a projected state whose `_metadata.whatIf` contains the error message, instead of failing the run:
- Handle the `_exist: false` delete case inside `set()` by routing through `remove()` (with `what_if` honored), so users get a single what-if message describing the deletion.
407
+
408
+
### 5. Types (`config.rs` / `types.rs`) changes
409
+
410
+
Add a `_metadata` field of type `Option<Metadata>` to every public state struct, and define `Metadata` exactly once per crate:
- Always call the executable directly (`<resource> config set -w --input ...`), **not** via `dsc resource`, so the test pins the CLI contract used by the manifest.
522
+
- Use `-w` (not `--what-if`) in tests to lock in the short flag.
523
+
- Redirect stderr with `2>$null` to keep test output clean; for failing-test debugging, prefer `2>$testdrive/error.log` + `-Because`.
524
+
- Pipe through `ConvertFrom-Json` and assert on `_metadata.whatIf` entries with `Should -Match`.
525
+
- Always include at least one assertion that the system state did **not** change (compare `config get` before/after).
526
+
- Always include both `set -w` (with and without `_exist: false`) and `delete -w` coverage if the manifest exposes `delete` what-if.
527
+
- Top-level `Describe` block uses `-Skip:(!$IsWindows)` for Windows-only resources (or the appropriate platform guard).
| Test file (single) |`<resource>.config.whatif.tests.ps1`|
547
+
| Test file (list) |`<resource>list_whatif.tests.ps1`|
548
+
| Describe block title |`'<resource> config whatif tests'`|
549
+
550
+
### 9. Implementation checklist
551
+
552
+
When asked to add what-if to a new resource, perform these steps in order:
553
+
554
+
1. Update the resource manifest: add `whatIfArg` to `set` (and `delete`) args, add `whatIfReturns: "state"` to `set`.
555
+
2. Add `what_if: bool` to the relevant clap `ConfigSubCommand` variants in `args.rs`.
556
+
3. Destructure `what_if` in `main.rs`, call `helper.enable_what_if()`, print projected state JSON.
557
+
4. Add `what_if` field, `enable_what_if()` method, and `handle_error_or_what_if()` helper to the resource library struct.
558
+
5. Change `set()` / `remove()` to short-circuit OS mutations when `what_if`, accumulate `Vec<String>` of "Would …" messages, return `Some(state)` with `_metadata.whatIf` attached.
559
+
6. Add `_metadata: Option<Metadata>` to public state structs and define `Metadata { what_if: Option<Vec<String>> }` with the JSON renames shown above.
560
+
7. Add `whatIf*` localized strings under `[<resource>_helper]` and `args.configArgsWhatIfHelp` in `locales/en-us.toml`.
561
+
8. Create `<resource>.config.whatif.tests.ps1` (and the list variant if applicable) following the test template; cover create, update, delete-via-`_exist`, and `delete -w`.
562
+
9. Build with `./build.ps1 -project <resource_name>` and run the new Pester file.
0 commit comments