-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor arguments to command resources an mixed strings and objects for extensibility #385
Changes from 7 commits
79d30ca
32076fb
acf9032
fdab2f1
eb66390
aeabe24
6218dcf
6db080b
ba80ec7
bff600e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,7 @@ | |
"args": [ | ||
"config", | ||
"validate" | ||
] | ||
], | ||
"input": "stdin" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ | |
"args": [ | ||
"config", | ||
"validate" | ||
] | ||
], | ||
"input": "stdin" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ | |
"args": [ | ||
"config", | ||
"validate" | ||
] | ||
], | ||
"input": "stdin" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,10 @@ | |
|
||
use jsonschema::JSONSchema; | ||
use serde_json::Value; | ||
use std::{collections::HashMap, env, process::Command, io::{Write, Read}, process::Stdio}; | ||
use std::{collections::HashMap, env, io::{Read, Write}, process::{Command, Stdio}}; | ||
use crate::{dscerror::DscError, dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse, ResourceTestResponse}}; | ||
use crate::configure::config_result::ResourceGetResult; | ||
use super::{dscresource::get_diff,resource_manifest::{Kind, ResourceManifest, InputKind, ReturnKind, SchemaKind}, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; | ||
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, SetResult, TestResult, ValidateResult}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}}; | ||
use tracing::{error, warn, info, debug, trace}; | ||
|
||
pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; | ||
|
@@ -54,7 +54,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul | |
|
||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut input_filter: Option<&str> = None; | ||
let mut get_args = resource.get.args.clone(); | ||
let args = process_args(&resource.get.args, filter); | ||
if !filter.is_empty() { | ||
verify_json(resource, cwd, filter)?; | ||
|
||
|
@@ -65,14 +65,11 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul | |
InputKind::Stdin => { | ||
input_filter = Some(filter); | ||
}, | ||
InputKind::Arg(arg_name) => { | ||
replace_token(&mut get_args, &arg_name, filter)?; | ||
}, | ||
} | ||
} | ||
|
||
info!("Invoking get '{}' using '{}'", &resource.resource_type, &resource.get.executable); | ||
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, input_filter, Some(cwd), env)?; | ||
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, args, input_filter, Some(cwd), env)?; | ||
log_resource_traces(&stderr); | ||
if exit_code != 0 { | ||
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); | ||
|
@@ -114,7 +111,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul | |
/// Error returned if the resource does not successfully set the desired state | ||
#[allow(clippy::too_many_lines)] | ||
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool) -> Result<SetResult, DscError> { | ||
let Some(set) = resource.set.as_ref() else { | ||
let Some(set) = &resource.set else { | ||
return Err(DscError::NotImplemented("set".to_string())); | ||
}; | ||
verify_json(resource, cwd, desired)?; | ||
|
@@ -146,24 +143,21 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te | |
|
||
let mut get_env: Option<HashMap<String, String>> = None; | ||
let mut get_input: Option<&str> = None; | ||
let mut get_args = resource.get.args.clone(); | ||
let args = process_args(&resource.get.args, desired); | ||
match &resource.get.input { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be simpler to add this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is only for processing args, but you're right there's some redundancy. Let me pull out the common part as a function. |
||
Some(InputKind::Env) => { | ||
get_env = Some(json_to_hashmap(desired)?); | ||
}, | ||
Some(InputKind::Stdin) => { | ||
get_input = Some(desired); | ||
}, | ||
Some(InputKind::Arg(arg_token)) => { | ||
replace_token(&mut get_args, arg_token, desired)?; | ||
}, | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
|
||
info!("Getting current state for set by invoking get {} using {}", &resource.resource_type, &resource.get.executable); | ||
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, get_input, Some(cwd), get_env)?; | ||
let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, args, get_input, Some(cwd), get_env)?; | ||
log_resource_traces(&stderr); | ||
if exit_code != 0 { | ||
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); | ||
|
@@ -183,16 +177,16 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te | |
|
||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut input_desired: Option<&str> = None; | ||
let mut args = set.args.clone(); | ||
let args = process_args(&set.args, desired); | ||
match &set.input { | ||
InputKind::Env => { | ||
Some(InputKind::Env) => { | ||
env = Some(json_to_hashmap(desired)?); | ||
}, | ||
InputKind::Stdin => { | ||
Some(InputKind::Stdin) => { | ||
input_desired = Some(desired); | ||
}, | ||
InputKind::Arg(arg_token) => { | ||
replace_token(&mut args, arg_token, desired)?; | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
|
||
|
@@ -290,16 +284,16 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re | |
|
||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut input_expected: Option<&str> = None; | ||
let mut args = test.args.clone(); | ||
let args = process_args(&test.args, expected); | ||
match &test.input { | ||
InputKind::Env => { | ||
Some(InputKind::Env) => { | ||
env = Some(json_to_hashmap(expected)?); | ||
}, | ||
InputKind::Stdin => { | ||
Some(InputKind::Stdin) => { | ||
input_expected = Some(expected); | ||
}, | ||
InputKind::Arg(arg_token) => { | ||
replace_token(&mut args, arg_token, expected)?; | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
|
||
|
@@ -401,39 +395,40 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str) | |
} | ||
|
||
/// Invoke the delete operation against a command resource. | ||
/// | ||
/// | ||
/// # Arguments | ||
/// | ||
/// | ||
/// * `resource` - The resource manifest for the command resource. | ||
/// * `cwd` - The current working directory. | ||
/// * `filter` - The filter to apply to the resource in JSON. | ||
/// | ||
/// | ||
/// # Errors | ||
/// | ||
/// | ||
/// Error is returned if the underlying command returns a non-zero exit code. | ||
pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result<(), DscError> { | ||
let Some(delete) = &resource.delete else { | ||
return Err(DscError::NotImplemented("delete".to_string())); | ||
}; | ||
|
||
verify_json(resource, cwd, filter)?; | ||
|
||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut input_filter: Option<&str> = None; | ||
let mut delete_args = delete.args.clone(); | ||
verify_json(resource, cwd, filter)?; | ||
let args = process_args(&delete.args, filter); | ||
match &delete.input { | ||
InputKind::Env => { | ||
Some(InputKind::Env) => { | ||
env = Some(json_to_hashmap(filter)?); | ||
}, | ||
InputKind::Stdin => { | ||
Some(InputKind::Stdin) => { | ||
input_filter = Some(filter); | ||
}, | ||
InputKind::Arg(arg_name) => { | ||
replace_token(&mut delete_args, arg_name, filter)?; | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
|
||
info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable); | ||
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, delete_args, input_filter, Some(cwd), env)?; | ||
let (exit_code, _stdout, stderr) = invoke_command(&delete.executable, args, input_filter, Some(cwd), env)?; | ||
log_resource_traces(&stderr); | ||
if exit_code != 0 { | ||
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); | ||
|
@@ -464,7 +459,22 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) -> | |
return Err(DscError::NotImplemented("validate".to_string())); | ||
}; | ||
|
||
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, validate.args.clone(), Some(config), Some(cwd), None)?; | ||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut input_config: Option<&str> = None; | ||
let args = process_args(&validate.args, config); | ||
match &validate.input { | ||
Some(InputKind::Env) => { | ||
env = Some(json_to_hashmap(config)?); | ||
}, | ||
Some(InputKind::Stdin) => { | ||
input_config = Some(config); | ||
}, | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
|
||
let (exit_code, stdout, stderr) = invoke_command(&validate.executable, args, input_config, Some(cwd), env)?; | ||
log_resource_traces(&stderr); | ||
if exit_code != 0 { | ||
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); | ||
|
@@ -536,7 +546,32 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> | |
return Err(DscError::Operation(format!("Export is not supported by resource {}", &resource.resource_type))) | ||
}; | ||
|
||
let (exit_code, stdout, stderr) = invoke_command(&export.executable, export.args.clone(), input, Some(cwd), None)?; | ||
|
||
let mut env: Option<HashMap<String, String>> = None; | ||
let mut export_input: Option<&str> = None; | ||
let args: Option<Vec<String>>; | ||
if let Some(input) = input { | ||
if !input.is_empty() { | ||
verify_json(resource, cwd, input)?; | ||
match &export.input { | ||
Some(InputKind::Env) => { | ||
env = Some(json_to_hashmap(input)?); | ||
}, | ||
Some(InputKind::Stdin) => { | ||
export_input = Some(input); | ||
}, | ||
None => { | ||
// leave input as none | ||
}, | ||
} | ||
} | ||
|
||
args = process_args(&export.args, input); | ||
} else { | ||
args = process_args(&export.args, ""); | ||
} | ||
|
||
let (exit_code, stdout, stderr) = invoke_command(&export.executable, args, export_input, Some(cwd), env)?; | ||
log_resource_traces(&stderr); | ||
if exit_code != 0 { | ||
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 | |
Ok((exit_code, stdout, stderr)) | ||
} | ||
|
||
fn replace_token(args: &mut Option<Vec<String>>, token: &str, value: &str) -> Result<(), DscError> { | ||
fn process_args(args: &Option<Vec<ArgKind>>, value: &str) -> Option<Vec<String>> { | ||
let Some(arg_values) = args else { | ||
return Err(DscError::Operation("No args to replace".to_string())); | ||
debug!("No args to process"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kind of too generic message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wanted a debug message that may be useful in the future to know that there weren't any args |
||
return None; | ||
}; | ||
|
||
let mut found = false; | ||
let mut processed_args = Vec::<String>::new(); | ||
for arg in arg_values { | ||
if arg == token { | ||
found = true; | ||
*arg = value.to_string(); | ||
} | ||
} | ||
match arg { | ||
ArgKind::String(s) => { | ||
processed_args.push(s.clone()); | ||
}, | ||
ArgKind::Json { json_input_arg, mandatory } => { | ||
if value.is_empty() && *mandatory != Some(true) { | ||
continue; | ||
} | ||
|
||
if !found { | ||
return Err(DscError::Operation(format!("Token {token} not found in args"))); | ||
processed_args.push(json_input_arg.clone()); | ||
processed_args.push(value.to_string()); | ||
}, | ||
} | ||
} | ||
|
||
Ok(()) | ||
Some(processed_args) | ||
} | ||
|
||
fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), DscError> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it's not matching the
syntax?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a test resource that doesn't get run to validate the semver. Maybe I'll rename the type to make this clear and remove the args since they aren't used at all.