Skip to content

Commit 79d30ca

Browse files
committed
Change how args get passed to commands
1 parent b44699e commit 79d30ca

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

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

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

@@ -401,15 +401,15 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str)
401401
}
402402

403403
/// Invoke the delete operation against a command resource.
404-
///
404+
///
405405
/// # Arguments
406-
///
406+
///
407407
/// * `resource` - The resource manifest for the command resource.
408408
/// * `cwd` - The current working directory.
409409
/// * `filter` - The filter to apply to the resource in JSON.
410-
///
410+
///
411411
/// # Errors
412-
///
412+
///
413413
/// Error is returned if the underlying command returns a non-zero exit code.
414414
pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result<(), DscError> {
415415
let Some(delete) = &resource.delete else {
@@ -418,7 +418,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
418418

419419
let mut env: Option<HashMap<String, String>> = None;
420420
let mut input_filter: Option<&str> = None;
421-
let mut delete_args = delete.args.clone();
421+
let mut args: Option<Vec<String>> = None;
422422
verify_json(resource, cwd, filter)?;
423423
match &delete.input {
424424
InputKind::Env => {
@@ -427,13 +427,13 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
427427
InputKind::Stdin => {
428428
input_filter = Some(filter);
429429
},
430-
InputKind::Arg(arg_name) => {
431-
replace_token(&mut delete_args, arg_name, filter)?;
430+
InputKind::Arg => {
431+
args = process_args(&delete.args, filter);
432432
},
433433
}
434434

435435
info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable);
436-
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, delete_args, input_filter, Some(cwd), env)?;
436+
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, args, input_filter, Some(cwd), env)?;
437437
log_resource_traces(&stderr);
438438
if exit_code != 0 {
439439
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -464,7 +464,22 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) ->
464464
return Err(DscError::NotImplemented("validate".to_string()));
465465
};
466466

467-
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, validate.args.clone(), Some(config), Some(cwd), None)?;
467+
let mut env: Option<HashMap<String, String>> = None;
468+
let mut input_config: Option<&str> = None;
469+
let mut args: Option<Vec<String>> = None;
470+
match &validate.input {
471+
InputKind::Env => {
472+
env = Some(json_to_hashmap(config)?);
473+
},
474+
InputKind::Stdin => {
475+
input_config = Some(config);
476+
},
477+
InputKind::Arg => {
478+
args = process_args(&validate.args, config);
479+
},
480+
}
481+
482+
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, args, input_config, Some(cwd), env)?;
468483
log_resource_traces(&stderr);
469484
if exit_code != 0 {
470485
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -536,7 +551,27 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
536551
return Err(DscError::Operation(format!("Export is not supported by resource {}", &resource.resource_type)))
537552
};
538553

539-
let (exit_code, stdout, stderr) = invoke_command(&export.executable, export.args.clone(), input, Some(cwd), None)?;
554+
let mut env: Option<HashMap<String, String>> = None;
555+
let mut export_input: Option<&str> = None;
556+
let mut args: Option<Vec<String>> = None;
557+
if let Some(input) = input {
558+
match &export.input {
559+
Some(InputKind::Env) => {
560+
env = Some(json_to_hashmap(input)?);
561+
},
562+
Some(InputKind::Stdin) => {
563+
export_input = Some(input);
564+
},
565+
Some(InputKind::Arg) => {
566+
args = process_args(&export.args, input);
567+
},
568+
None => {
569+
// leave input as none
570+
},
571+
}
572+
}
573+
574+
let (exit_code, stdout, stderr) = invoke_command(&export.executable, args, export_input, Some(cwd), env)?;
540575
log_resource_traces(&stderr);
541576
if exit_code != 0 {
542577
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
@@ -634,24 +669,30 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
634669
Ok((exit_code, stdout, stderr))
635670
}
636671

637-
fn replace_token(args: &mut Option<Vec<String>>, token: &str, value: &str) -> Result<(), DscError> {
672+
fn process_args(args: &Option<Vec<ArgKind>>, value: &str) -> Option<Vec<String>> {
638673
let Some(arg_values) = args else {
639-
return Err(DscError::Operation("No args to replace".to_string()));
674+
debug!("No args to process");
675+
return None;
640676
};
641677

642-
let mut found = false;
678+
let mut processed_args = Vec::<String>::new();
643679
for arg in arg_values {
644-
if arg == token {
645-
found = true;
646-
*arg = value.to_string();
647-
}
648-
}
680+
match arg {
681+
ArgKind::String(s) => {
682+
processed_args.push(s.clone());
683+
},
684+
ArgKind::Json { json_input_arg, mandatory } => {
685+
if value.is_empty() && *mandatory == Some(true) {
686+
continue;
687+
}
649688

650-
if !found {
651-
return Err(DscError::Operation(format!("Token {token} not found in args")));
689+
processed_args.push(json_input_arg.clone());
690+
processed_args.push(value.to_string());
691+
},
692+
}
652693
}
653694

654-
Ok(())
695+
Some(processed_args)
655696
}
656697

657698
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)