Skip to content

Commit a277252

Browse files
committed
add direct adapter invocation for all operations
1 parent f3843ab commit a277252

File tree

11 files changed

+360
-98
lines changed

11 files changed

+360
-98
lines changed

.vscode/launch.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"type": "lldb",
2121
"request": "attach",
2222
"pid": "${command:pickMyProcess}",
23+
"expressions": "simple",
24+
"preRunCommands": [
25+
// !! change this path if you placed the script somewhere else !!
26+
"command script import ~/.vscode/rust_prettifier_for_lldb.py"
27+
],
2328
},
2429
{
2530
"name": "(Windows) Attach",

dsc/tests/dsc_adapter.tests.ps1

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'Tests for adapter support' {
5+
Context 'Adapter support single resource' {
6+
It 'Direct resource invocation for: <operation>' -TestCases @(
7+
@{ operation = 'get' },
8+
@{ operation = 'set' },
9+
@{ operation = 'test' },
10+
@{ operation = 'export' }
11+
){
12+
param($operation)
13+
14+
$out = dsc resource $operation -r Adapted/One -i '{"one":"1"}' 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10
15+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log | Out-String)
16+
switch ($operation) {
17+
'get' {
18+
$out.actualState.one | Should -BeExactly 'value1'
19+
}
20+
'set' {
21+
$out.afterState.one | Should -BeExactly 'value1'
22+
}
23+
'test' {
24+
$out.actualState.one | Should -BeExactly 'value1'
25+
$out.inDesiredState | Should -BeFalse
26+
$out.differingProperties | Should -Be @('one')
27+
}
28+
'export' {
29+
$out.resources.count | Should -Be 2
30+
$out.resources[0].type | Should -BeExactly 'Adapted/One'
31+
$out.resources[0].name | Should -BeExactly 'first'
32+
$out.resources[0].properties.one | Should -BeExactly 'first1'
33+
$out.resources[1].type | Should -BeExactly 'Adapted/One'
34+
$out.resources[1].name | Should -BeExactly 'second'
35+
$out.resources[1].properties.one | Should -BeExactly 'second1'
36+
}
37+
}
38+
}
39+
40+
It 'Config resource invocation for: <operation>' -TestCases @(
41+
@{ operation = 'get' },
42+
@{ operation = 'set' },
43+
@{ operation = 'test' },
44+
@{ operation = 'export' }
45+
){
46+
param($operation)
47+
48+
$config_yaml = @"
49+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
50+
resources:
51+
- name: Test
52+
type: Adapted/Two
53+
properties:
54+
two: '2'
55+
"@
56+
$out = dsc config $operation -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json -Depth 10
57+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log | Out-String)
58+
switch ($operation) {
59+
'get' {
60+
$out.results.Count | Should -Be 1
61+
$out.results[0].Name | Should -Be 'Test'
62+
$out.results[0].type | Should -BeExactly 'Adapted/Two'
63+
$out.results[0].result.actualState.two | Should -BeExactly 'value2' -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
64+
}
65+
'set' {
66+
$out.results.Count | Should -Be 1
67+
$out.results[0].Name | Should -Be 'Test'
68+
$out.results[0].type | Should -BeExactly 'Adapted/Two'
69+
$out.results[0].result.afterState.two | Should -BeExactly 'value2' -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
70+
}
71+
'test' {
72+
$out.results.Count | Should -Be 1
73+
$out.results[0].Name | Should -Be 'Test'
74+
$out.results[0].type | Should -BeExactly 'Adapted/Two'
75+
$out.results[0].result.actualState.two | Should -BeExactly 'value2' -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
76+
$out.results[0].result.inDesiredState | Should -BeFalse
77+
$out.results[0].result.differingProperties | Should -Be @('two') -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
78+
}
79+
'export' {
80+
$out.resources.Count | Should -Be 2
81+
$out.resources[0].Name | Should -Be 'first'
82+
$out.resources[0].type | Should -BeExactly 'Adapted/Two'
83+
$out.resources[0].properties.two | Should -BeExactly 'first2'
84+
$out.resources[1].Name | Should -Be 'second'
85+
$out.resources[1].type | Should -BeExactly 'Adapted/Two'
86+
$out.resources[1].properties.two | Should -BeExactly 'second2'
87+
}
88+
}
89+
}
90+
}
91+
}

dsc_lib/locales/en-us.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ resourceImplementsValidate = "Resource implements validation"
195195
resourceValidationFailed = "Resource failed validation"
196196
validatingSchema = "Validating against schema"
197197
validationFailed = "Failed validation"
198+
adapterResourceNotFound = "Adapter resource '%{adapter}' not found"
199+
adapterManifestNotFound = "Adapter manifest for '%{adapter}' not found"
200+
adapterDoesNotSupportDelete = "Adapter '%{adapter}' does not support delete operation"
198201

199202
[dscresources.resource_manifest]
200203
resourceManifestSchemaTitle = "Resource manifest schema URI"

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;
2525
/// # Errors
2626
///
2727
/// Error returned if the resource does not successfully get the current state
28-
pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result<GetResult, DscError> {
28+
pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str, target_resource: Option<&str>) -> Result<GetResult, DscError> {
2929
debug!("{}", t!("dscresources.commandResource.invokeGet", resource = &resource.resource_type));
3030
let mut command_input = CommandInput { env: None, stdin: None };
3131
let Some(get) = &resource.get else {
3232
return Err(DscError::NotImplemented("get".to_string()));
3333
};
34-
let args = process_args(get.args.as_ref(), filter, &resource.resource_type);
34+
let resource_type = match target_resource {
35+
Some(r) => r,
36+
None => &resource.resource_type,
37+
};
38+
let args = process_args(get.args.as_ref(), filter, resource_type);
3539
if !filter.is_empty() {
3640
verify_json(resource, cwd, filter)?;
3741
command_input = get_command_input(get.input.as_ref(), filter)?;
@@ -74,7 +78,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
7478
///
7579
/// Error returned if the resource does not successfully set the desired state
7680
#[allow(clippy::too_many_lines)]
77-
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
81+
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind, target_resource: Option<&str>) -> Result<SetResult, DscError> {
7882
debug!("{}", t!("dscresources.commandResource.invokeSet", resource = &resource.resource_type));
7983
let operation_type: String;
8084
let mut is_synthetic_what_if = false;
@@ -101,7 +105,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
101105
// if resource doesn't implement a pre-test, we execute test first to see if a set is needed
102106
if !skip_test && set.pre_test != Some(true) {
103107
info!("{}", t!("dscresources.commandResource.noPretest", resource = &resource.resource_type));
104-
let test_result = invoke_test(resource, cwd, desired)?;
108+
let test_result = invoke_test(resource, cwd, desired, target_resource)?;
105109
if is_synthetic_what_if {
106110
return Ok(test_result.into());
107111
}
@@ -136,7 +140,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
136140
let Some(get) = &resource.get else {
137141
return Err(DscError::NotImplemented("get".to_string()));
138142
};
139-
let args = process_args(get.args.as_ref(), desired, &resource.resource_type);
143+
let resource_type = match target_resource {
144+
Some(r) => r,
145+
None => &resource.resource_type,
146+
};
147+
let args = process_args(get.args.as_ref(), desired, resource_type);
140148
let command_input = get_command_input(get.input.as_ref(), desired)?;
141149

142150
info!("{}", t!("dscresources.commandResource.setGetCurrent", resource = &resource.resource_type, executable = &get.executable));
@@ -168,7 +176,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
168176

169177
let mut env: Option<HashMap<String, String>> = None;
170178
let mut input_desired: Option<&str> = None;
171-
let args = process_args(set.args.as_ref(), desired, &resource.resource_type);
179+
let args = process_args(set.args.as_ref(), desired, resource_type);
172180
match &set.input {
173181
Some(InputKind::Env) => {
174182
env = Some(json_to_hashmap(desired)?);
@@ -228,7 +236,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
228236
},
229237
None => {
230238
// perform a get and compare the result to the expected state
231-
let get_result = invoke_get(resource, cwd, desired)?;
239+
let get_result = invoke_get(resource, cwd, desired, target_resource)?;
232240
// for changed_properties, we compare post state to pre state
233241
let actual_state = match get_result {
234242
GetResult::Group(results) => {
@@ -263,16 +271,20 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
263271
/// # Errors
264272
///
265273
/// Error is returned if the underlying command returns a non-zero exit code.
266-
pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
274+
pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, target_resource: Option<&str>) -> Result<TestResult, DscError> {
267275
debug!("{}", t!("dscresources.commandResource.invokeTest", resource = &resource.resource_type));
268276
let Some(test) = &resource.test else {
269277
info!("{}", t!("dscresources.commandResource.testSyntheticTest", resource = &resource.resource_type));
270-
return invoke_synthetic_test(resource, cwd, expected);
278+
return invoke_synthetic_test(resource, cwd, expected, target_resource);
271279
};
272280

273281
verify_json(resource, cwd, expected)?;
274282

275-
let args = process_args(test.args.as_ref(), expected, &resource.resource_type);
283+
let resource_type = match target_resource {
284+
Some(r) => r,
285+
None => &resource.resource_type,
286+
};
287+
let args = process_args(test.args.as_ref(), expected, resource_type);
276288
let command_input = get_command_input(test.input.as_ref(), expected)?;
277289

278290
info!("{}", t!("dscresources.commandResource.invokeTestUsing", resource = &resource.resource_type, executable = &test.executable));
@@ -330,7 +342,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
330342
},
331343
None => {
332344
// perform a get and compare the result to the expected state
333-
let get_result = invoke_get(resource, cwd, expected)?;
345+
let get_result = invoke_get(resource, cwd, expected, target_resource)?;
334346
let actual_state = match get_result {
335347
GetResult::Group(results) => {
336348
let mut result_array: Vec<Value> = Vec::new();
@@ -368,8 +380,8 @@ fn get_desired_state(actual: &Value) -> Result<Option<bool>, DscError> {
368380
Ok(in_desired_state)
369381
}
370382

371-
fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
372-
let get_result = invoke_get(resource, cwd, expected)?;
383+
fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str, target_resource: Option<&str>) -> Result<TestResult, DscError> {
384+
let get_result = invoke_get(resource, cwd, expected, target_resource)?;
373385
let actual_state = match get_result {
374386
GetResult::Group(results) => {
375387
let mut result_array: Vec<Value> = Vec::new();
@@ -403,17 +415,21 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str)
403415
/// # Errors
404416
///
405417
/// Error is returned if the underlying command returns a non-zero exit code.
406-
pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result<(), DscError> {
418+
pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str, target_resource: Option<&str>) -> Result<(), DscError> {
407419
let Some(delete) = &resource.delete else {
408420
return Err(DscError::NotImplemented("delete".to_string()));
409421
};
410422

411423
verify_json(resource, cwd, filter)?;
412424

413-
let args = process_args(delete.args.as_ref(), filter, &resource.resource_type);
425+
let resource_type = match target_resource {
426+
Some(r) => r,
427+
None => &resource.resource_type,
428+
};
429+
let args = process_args(delete.args.as_ref(), filter, resource_type);
414430
let command_input = get_command_input(delete.input.as_ref(), filter)?;
415431

416-
info!("{}", t!("dscresources.commandResource.invokeDeleteUsing", resource = &resource.resource_type, executable = &delete.executable));
432+
info!("{}", t!("dscresources.commandResource.invokeDeleteUsing", resource = resource_type, executable = &delete.executable));
417433
let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?;
418434

419435
Ok(())
@@ -434,17 +450,21 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
434450
/// # Errors
435451
///
436452
/// Error is returned if the underlying command returns a non-zero exit code.
437-
pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) -> Result<ValidateResult, DscError> {
453+
pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str, target_resource: Option<&str>) -> Result<ValidateResult, DscError> {
438454
trace!("{}", t!("dscresources.commandResource.invokeValidateConfig", resource = &resource.resource_type, config = &config));
439455
// TODO: use schema to validate config if validate is not implemented
440456
let Some(validate) = resource.validate.as_ref() else {
441457
return Err(DscError::NotImplemented("validate".to_string()));
442458
};
443459

444-
let args = process_args(validate.args.as_ref(), config, &resource.resource_type);
460+
let resource_type = match target_resource {
461+
Some(r) => r,
462+
None => &resource.resource_type,
463+
};
464+
let args = process_args(validate.args.as_ref(), config, resource_type);
445465
let command_input = get_command_input(validate.input.as_ref(), config)?;
446466

447-
info!("{}", t!("dscresources.commandResource.invokeValidateUsing", resource = &resource.resource_type, executable = &validate.executable));
467+
info!("{}", t!("dscresources.commandResource.invokeValidateUsing", resource = resource_type, executable = &validate.executable));
448468
let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?;
449469
let result: ValidateResult = serde_json::from_str(&stdout)?;
450470
Ok(result)
@@ -491,12 +511,12 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
491511
/// # Errors
492512
///
493513
/// Error returned if the resource does not successfully export the current state
494-
pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>) -> Result<ExportResult, DscError> {
514+
pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>, target_resource: Option<&str>) -> Result<ExportResult, DscError> {
495515
let Some(export) = resource.export.as_ref() else {
496516
// see if get is supported and use that instead
497517
if resource.get.is_some() {
498518
info!("{}", t!("dscresources.commandResource.exportNotSupportedUsingGet", resource = &resource.resource_type));
499-
let get_result = invoke_get(resource, cwd, input.unwrap_or(""))?;
519+
let get_result = invoke_get(resource, cwd, input.unwrap_or(""), target_resource)?;
500520
let mut instances: Vec<Value> = Vec::new();
501521
match get_result {
502522
GetResult::Group(group_response) => {
@@ -518,16 +538,20 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
518538

519539
let mut command_input: CommandInput = CommandInput { env: None, stdin: None };
520540
let args: Option<Vec<String>>;
541+
let resource_type = match target_resource {
542+
Some(r) => r,
543+
None => &resource.resource_type,
544+
};
521545
if let Some(input) = input {
522546
if !input.is_empty() {
523547
verify_json(resource, cwd, input)?;
524548

525549
command_input = get_command_input(export.input.as_ref(), input)?;
526550
}
527551

528-
args = process_args(export.args.as_ref(), input, &resource.resource_type);
552+
args = process_args(export.args.as_ref(), input, resource_type);
529553
} else {
530-
args = process_args(export.args.as_ref(), "", &resource.resource_type);
554+
args = process_args(export.args.as_ref(), "", resource_type);
531555
}
532556

533557
let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?;
@@ -815,7 +839,7 @@ fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(),
815839
// see if resource implements validate
816840
if resource.validate.is_some() {
817841
trace!("{}", t!("dscresources.commandResource.validateJson", json = json));
818-
let result = invoke_validate(resource, cwd, json)?;
842+
let result = invoke_validate(resource, cwd, json, None)?;
819843
if result.valid {
820844
return Ok(());
821845
}

0 commit comments

Comments
 (0)