Skip to content

Commit ae50e41

Browse files
committed
Change how args get passed to commands
1 parent 082f7d2 commit ae50e41

File tree

3 files changed

+123
-67
lines changed

3 files changed

+123
-67
lines changed

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
use jsonschema::JSONSchema;
55
use serde_json::Value;
6-
use std::{collections::HashMap, env, process::Command, io::{Write, Read}, process::Stdio};
6+
use std::{collections::HashMap, env, io::{Read, Write}, process::{Command, Stdio}};
77
use crate::{dscerror::DscError, dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse, ResourceTestResponse}};
88
use crate::configure::config_result::ResourceGetResult;
9-
use super::{dscresource::get_diff,resource_manifest::{Kind, ResourceManifest, InputKind, ReturnKind, SchemaKind}, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}};
9+
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, SetResult, TestResult, ValidateResult}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
1010
use tracing::{error, warn, info, debug, trace};
1111

1212
pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;
@@ -54,7 +54,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
5454

5555
let mut env: Option<HashMap<String, String>> = None;
5656
let mut input_filter: Option<&str> = None;
57-
let mut get_args = resource.get.args.clone();
57+
let mut args: Option<Vec<String>> = None;
5858
if !filter.is_empty() {
5959
verify_json(resource, cwd, filter)?;
6060

@@ -65,14 +65,14 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
6565
InputKind::Stdin => {
6666
input_filter = Some(filter);
6767
},
68-
InputKind::Arg(arg_name) => {
69-
replace_token(&mut get_args, &arg_name, filter)?;
68+
InputKind::Arg => {
69+
args = process_args(&resource.get.args, filter);
7070
},
7171
}
7272
}
7373

7474
info!("Invoking get '{}' using '{}'", &resource.resource_type, &resource.get.executable);
75-
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, input_filter, Some(cwd), env)?;
75+
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, args, input_filter, Some(cwd), env)?;
7676
log_resource_traces(&stderr);
7777
if exit_code != 0 {
7878
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -114,7 +114,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
114114
/// Error returned if the resource does not successfully set the desired state
115115
#[allow(clippy::too_many_lines)]
116116
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool) -> Result<SetResult, DscError> {
117-
let Some(set) = resource.set.as_ref() else {
117+
let Some(set) = &resource.set else {
118118
return Err(DscError::NotImplemented("set".to_string()));
119119
};
120120
verify_json(resource, cwd, desired)?;
@@ -146,24 +146,24 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
146146

147147
let mut get_env: Option<HashMap<String, String>> = None;
148148
let mut get_input: Option<&str> = None;
149-
let mut get_args = resource.get.args.clone();
149+
let mut args: Option<Vec<String>> = None;
150150
match &resource.get.input {
151151
Some(InputKind::Env) => {
152152
get_env = Some(json_to_hashmap(desired)?);
153153
},
154154
Some(InputKind::Stdin) => {
155155
get_input = Some(desired);
156156
},
157-
Some(InputKind::Arg(arg_token)) => {
158-
replace_token(&mut get_args, arg_token, desired)?;
157+
Some(InputKind::Arg) => {
158+
args = process_args(&resource.get.args, desired);
159159
},
160160
None => {
161161
// leave input as none
162162
},
163163
}
164164

165165
info!("Getting current state for set by invoking get {} using {}", &resource.resource_type, &resource.get.executable);
166-
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, get_input, Some(cwd), get_env)?;
166+
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, args, get_input, Some(cwd), get_env)?;
167167
log_resource_traces(&stderr);
168168
if exit_code != 0 {
169169
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -183,16 +183,16 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
183183

184184
let mut env: Option<HashMap<String, String>> = None;
185185
let mut input_desired: Option<&str> = None;
186-
let mut args = set.args.clone();
186+
let mut args: Option<Vec<String>> = None;
187187
match &set.input {
188188
InputKind::Env => {
189189
env = Some(json_to_hashmap(desired)?);
190190
},
191191
InputKind::Stdin => {
192192
input_desired = Some(desired);
193193
},
194-
InputKind::Arg(arg_token) => {
195-
replace_token(&mut args, arg_token, desired)?;
194+
InputKind::Arg => {
195+
args = process_args(&set.args, desired);
196196
},
197197
}
198198

@@ -289,16 +289,16 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
289289

290290
let mut env: Option<HashMap<String, String>> = None;
291291
let mut input_expected: Option<&str> = None;
292-
let mut args = test.args.clone();
292+
let mut args: Option<Vec<String>> = None;
293293
match &test.input {
294294
InputKind::Env => {
295295
env = Some(json_to_hashmap(expected)?);
296296
},
297297
InputKind::Stdin => {
298298
input_expected = Some(expected);
299299
},
300-
InputKind::Arg(arg_token) => {
301-
replace_token(&mut args, arg_token, expected)?;
300+
InputKind::Arg => {
301+
args = process_args(&test.args, expected);
302302
},
303303
}
304304

@@ -376,15 +376,15 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
376376
}
377377

378378
/// Invoke the delete operation against a command resource.
379-
///
379+
///
380380
/// # Arguments
381-
///
381+
///
382382
/// * `resource` - The resource manifest for the command resource.
383383
/// * `cwd` - The current working directory.
384384
/// * `filter` - The filter to apply to the resource in JSON.
385-
///
385+
///
386386
/// # Errors
387-
///
387+
///
388388
/// Error is returned if the underlying command returns a non-zero exit code.
389389
pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result<(), DscError> {
390390
let Some(delete) = &resource.delete else {
@@ -393,7 +393,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
393393

394394
let mut env: Option<HashMap<String, String>> = None;
395395
let mut input_filter: Option<&str> = None;
396-
let mut get_args = resource.get.args.clone();
396+
let mut args: Option<Vec<String>> = None;
397397
verify_json(resource, cwd, filter)?;
398398
match &delete.input {
399399
InputKind::Env => {
@@ -402,13 +402,13 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
402402
InputKind::Stdin => {
403403
input_filter = Some(filter);
404404
},
405-
InputKind::Arg(arg_name) => {
406-
replace_token(&mut get_args, arg_name, filter)?;
405+
InputKind::Arg => {
406+
args = process_args(&delete.args, filter);
407407
},
408408
}
409409

410410
info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable);
411-
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, get_args, input_filter, Some(cwd), env)?;
411+
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, args, input_filter, Some(cwd), env)?;
412412
log_resource_traces(&stderr);
413413
if exit_code != 0 {
414414
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -439,7 +439,22 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) ->
439439
return Err(DscError::NotImplemented("validate".to_string()));
440440
};
441441

442-
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, validate.args.clone(), Some(config), Some(cwd), None)?;
442+
let mut env: Option<HashMap<String, String>> = None;
443+
let mut input_config: Option<&str> = None;
444+
let mut args: Option<Vec<String>> = None;
445+
match &validate.input {
446+
InputKind::Env => {
447+
env = Some(json_to_hashmap(config)?);
448+
},
449+
InputKind::Stdin => {
450+
input_config = Some(config);
451+
},
452+
InputKind::Arg => {
453+
args = process_args(&validate.args, config);
454+
},
455+
}
456+
457+
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, args, input_config, Some(cwd), env)?;
443458
log_resource_traces(&stderr);
444459
if exit_code != 0 {
445460
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -511,7 +526,27 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
511526
return Err(DscError::Operation(format!("Export is not supported by resource {}", &resource.resource_type)))
512527
};
513528

514-
let (exit_code, stdout, stderr) = invoke_command(&export.executable, export.args.clone(), input, Some(cwd), None)?;
529+
let mut env: Option<HashMap<String, String>> = None;
530+
let mut export_input: Option<&str> = None;
531+
let mut args: Option<Vec<String>> = None;
532+
if let Some(input) = input {
533+
match &export.input {
534+
Some(InputKind::Env) => {
535+
env = Some(json_to_hashmap(input)?);
536+
},
537+
Some(InputKind::Stdin) => {
538+
export_input = Some(input);
539+
},
540+
Some(InputKind::Arg) => {
541+
args = process_args(&export.args, input);
542+
},
543+
None => {
544+
// leave input as none
545+
},
546+
}
547+
}
548+
549+
let (exit_code, stdout, stderr) = invoke_command(&export.executable, args, export_input, Some(cwd), env)?;
515550
log_resource_traces(&stderr);
516551
if exit_code != 0 {
517552
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -609,24 +644,30 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
609644
Ok((exit_code, stdout, stderr))
610645
}
611646

612-
fn replace_token(args: &mut Option<Vec<String>>, token: &str, value: &str) -> Result<(), DscError> {
647+
fn process_args(args: &Option<Vec<ArgKind>>, value: &str) -> Option<Vec<String>> {
613648
let Some(arg_values) = args else {
614-
return Err(DscError::Operation("No args to replace".to_string()));
649+
debug!("No args to process");
650+
return None;
615651
};
616652

617-
let mut found = false;
653+
let mut processed_args = Vec::<String>::new();
618654
for arg in arg_values {
619-
if arg == token {
620-
found = true;
621-
*arg = value.to_string();
622-
}
623-
}
655+
match arg {
656+
ArgKind::String(s) => {
657+
processed_args.push(s.clone());
658+
},
659+
ArgKind::Json { json_input_arg, mandatory } => {
660+
if value.is_empty() && *mandatory == Some(true) {
661+
continue;
662+
}
624663

625-
if !found {
626-
return Err(DscError::Operation(format!("Token {token} not found in args")));
664+
processed_args.push(json_input_arg.clone());
665+
processed_args.push(value.to_string());
666+
},
667+
}
627668
}
628669

629-
Ok(())
670+
Some(processed_args)
630671
}
631672

632673
fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), DscError> {

dsc_lib/src/dscresources/resource_manifest.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ pub enum Kind {
1515
Resource,
1616
}
1717

18-
1918
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
2019
#[serde(deny_unknown_fields)]
2120
pub struct ResourceManifest {
@@ -80,11 +79,26 @@ pub enum ManifestSchemaUri {
8079
VSCode2023_08,
8180
}
8281

82+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
83+
#[serde(untagged)]
84+
pub enum ArgKind {
85+
/// The argument is a string.
86+
String(String),
87+
/// The argument accepts the JSON input object.
88+
Json{
89+
/// The argument that accepts the JSON input object.
90+
#[serde(rename = "jsonInputArg")]
91+
json_input_arg: String,
92+
/// Indicates if argument is mandatory which will pass an empty string if no JSON input is provided. Default is false.
93+
mandatory: Option<bool>,
94+
}
95+
}
96+
8397
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
8498
pub enum InputKind {
8599
/// The input replaces arguments with this token in the command.
86100
#[serde(rename = "arg")]
87-
Arg(String),
101+
Arg,
88102
/// The input is accepted as environmental variables.
89103
#[serde(rename = "env")]
90104
Env,
@@ -129,7 +143,7 @@ pub struct GetMethod {
129143
/// The command to run to get the state of the resource.
130144
pub executable: String,
131145
/// The arguments to pass to the command to perform a Get.
132-
pub args: Option<Vec<String>>,
146+
pub args: Option<Vec<ArgKind>>,
133147
/// How to pass optional input for a Get.
134148
#[serde(skip_serializing_if = "Option::is_none")]
135149
pub input: Option<InputKind>,
@@ -140,7 +154,7 @@ pub struct SetMethod {
140154
/// The command to run to set the state of the resource.
141155
pub executable: String,
142156
/// The arguments to pass to the command to perform a Set.
143-
pub args: Option<Vec<String>>,
157+
pub args: Option<Vec<ArgKind>>,
144158
/// How to pass required input for a Set.
145159
pub input: InputKind,
146160
/// Whether to run the Test method before the Set method. True means the resource will perform its own test before running the Set method.
@@ -159,7 +173,7 @@ pub struct TestMethod {
159173
/// The command to run to test the state of the resource.
160174
pub executable: String,
161175
/// The arguments to pass to the command to perform a Test.
162-
pub args: Option<Vec<String>>,
176+
pub args: Option<Vec<ArgKind>>,
163177
/// How to pass required input for a Test.
164178
pub input: InputKind,
165179
/// The type of return value expected from the Test method.
@@ -169,11 +183,11 @@ pub struct TestMethod {
169183

170184
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
171185
pub struct DeleteMethod {
172-
/// The command to run to test the state of the resource.
186+
/// The command to run to delete the state of the resource.
173187
pub executable: String,
174-
/// The arguments to pass to the command to perform a Test.
175-
pub args: Option<Vec<String>>,
176-
/// How to pass required input for a Test.
188+
/// The arguments to pass to the command to perform a Delete.
189+
pub args: Option<Vec<ArgKind>>,
190+
/// How to pass required input for a Delete.
177191
pub input: InputKind,
178192
}
179193

@@ -182,15 +196,19 @@ pub struct ValidateMethod { // TODO: enable validation via schema or command
182196
/// The command to run to validate the state of the resource.
183197
pub executable: String,
184198
/// The arguments to pass to the command to perform a Validate.
185-
pub args: Option<Vec<String>>,
199+
pub args: Option<Vec<ArgKind>>,
200+
/// How to pass required input for a Validate.
201+
pub input: InputKind,
186202
}
187203

188204
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
189205
pub struct ExportMethod {
190206
/// The command to run to enumerate instances of the resource.
191207
pub executable: String,
192208
/// The arguments to pass to the command to perform a Export.
193-
pub args: Option<Vec<String>>,
209+
pub args: Option<Vec<ArgKind>>,
210+
/// How to pass input for a Export.
211+
pub input: Option<InputKind>,
194212
}
195213

196214
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]

0 commit comments

Comments
 (0)