Skip to content
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

Resource discovery optimizations #240

Merged
merged 20 commits into from
Oct 31, 2023
2 changes: 1 addition & 1 deletion dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn main() {
check_debug();

// create subscriber that writes all events to stderr
let subscriber = tracing_subscriber::fmt().pretty().with_writer(std::io::stderr).finish();
let subscriber = tracing_subscriber::fmt().pretty()/*.with_max_level(Level::DEBUG)*/.with_writer(std::io::stderr).finish();
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
if tracing::subscriber::set_global_default(subscriber).is_err() {
eprintln!("Unable to set global default subscriber");
}
Expand Down
114 changes: 72 additions & 42 deletions dsc/src/resource_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_JSON_ERROR, add_type_n
use dsc_lib::configure::config_doc::Configuration;
use dsc_lib::configure::add_resource_export_results_to_configuration;
use dsc_lib::dscresources::invoke_result::GetResult;
use dsc_lib::dscerror::DscError;
use tracing::{error, debug};

use dsc_lib::{
Expand All @@ -14,14 +15,25 @@ use dsc_lib::{
};
use std::process::exit;

pub fn get(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
/// Get operation.
///
/// # Panics
///
/// Will panic if provider-based resource is not found.
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
///
pub fn get(dsc: &DscManager, resource_str: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
// TODO: support streaming stdin which includes resource and input
let mut input = get_input(input, stdin);
let mut resource = get_resource(dsc, resource);

let Some(mut resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};

debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as);
if let Some(requires) = resource.requires {
input = add_type_name_to_json(input, resource.type_name);
resource = get_resource(dsc, &requires);
if let Some(requires) = &resource.requires {
input = add_type_name_to_json(input, resource.type_name.clone());
resource = get_resource(dsc, requires).unwrap();
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
}

match resource.get(input.as_str()) {
Expand All @@ -43,8 +55,11 @@ pub fn get(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin:
}
}

pub fn get_all(dsc: &mut DscManager, resource: &str, _input: &Option<String>, _stdin: &Option<String>, format: &Option<OutputFormat>) {
let resource = get_resource(dsc, resource);
pub fn get_all(dsc: &DscManager, resource_str: &str, _input: &Option<String>, _stdin: &Option<String>, format: &Option<OutputFormat>) {
let Some(resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};
debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as);
let export_result = match resource.export() {
Ok(export) => { export }
Expand All @@ -71,20 +86,29 @@ pub fn get_all(dsc: &mut DscManager, resource: &str, _input: &Option<String>, _s
}
}

pub fn set(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
/// Set operation.
///
/// # Panics
///
/// Will panic if provider-based resource is not found.
///
pub fn set(dsc: &DscManager, resource_str: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
let mut input = get_input(input, stdin);
if input.is_empty() {
error!("Error: Input is empty");
exit(EXIT_INVALID_ARGS);
}

let mut resource = get_resource(dsc, resource);
let Some(mut resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};

debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as);

if let Some(requires) = resource.requires {
input = add_type_name_to_json(input, resource.type_name);
resource = get_resource(dsc, &requires);
if let Some(requires) = &resource.requires {
input = add_type_name_to_json(input, resource.type_name.clone());
resource = get_resource(dsc, requires).unwrap();
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
}

match resource.set(input.as_str(), true) {
Expand All @@ -106,15 +130,24 @@ pub fn set(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin:
}
}

pub fn test(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
/// Test operation.
///
/// # Panics
///
/// Will panic if provider-based resource is not found.
///
pub fn test(dsc: &DscManager, resource_str: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
let mut input = get_input(input, stdin);
let mut resource = get_resource(dsc, resource);
let Some(mut resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};

debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as);

if let Some(requires) = resource.requires {
input = add_type_name_to_json(input, resource.type_name);
resource = get_resource(dsc, &requires);
if let Some(requires) = &resource.requires {
input = add_type_name_to_json(input, resource.type_name.clone());
resource = get_resource(dsc, requires).unwrap();
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
}

match resource.test(input.as_str()) {
Expand All @@ -136,8 +169,11 @@ pub fn test(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin:
}
}

pub fn schema(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat>) {
let resource = get_resource(dsc, resource);
pub fn schema(dsc: &DscManager, resource_str: &str, format: &Option<OutputFormat>) {
let Some(resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};
match resource.schema() {
Ok(json) => {
// verify is json
Expand All @@ -157,8 +193,11 @@ pub fn schema(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat
}
}

pub fn export(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat>) {
let dsc_resource = get_resource(dsc, resource);
pub fn export(dsc: &mut DscManager, resource_str: &str, format: &Option<OutputFormat>) {
let Some(dsc_resource) = get_resource(dsc, resource_str) else {
error!("{}", DscError::ResourceNotFound(resource_str.to_string()).to_string());
return
};

let mut conf = Configuration::new();

Expand All @@ -177,34 +216,25 @@ pub fn export(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat
write_output(&json, format);
}

pub fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource {
#[must_use]
pub fn get_resource<'a>(dsc: &'a DscManager, resource: &str) -> Option<&'a DscResource> {
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
//TODO: add dinamically generated resource to dsc
// check if resource is JSON or just a name
match serde_json::from_str(resource) {
Ok(resource) => resource,
/*match serde_json::from_str(resource) {
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
Ok(resource) =>
{

resource
},
Err(err) => {
if resource.contains('{') {
error!("Not valid resource JSON: {err}\nInput was: {resource}");
exit(EXIT_INVALID_ARGS);
}

if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
let resources: Vec<DscResource> = dsc.find_resource(resource).collect();
match resources.len() {
0 => {
error!("Error: Resource not found: '{resource}'");
exit(EXIT_INVALID_ARGS);
}
1 => resources[0].clone(),
_ => {
error!("Error: Multiple resources found");
exit(EXIT_INVALID_ARGS);
}
}
}
}
}*/

dsc.find_resource(resource)
}

fn get_input(input: &Option<String>, stdin: &Option<String>) -> String {
Expand Down
77 changes: 45 additions & 32 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use dsc_lib::{
dscresources::dscresource::{ImplementedAs, Invoke},
dscresources::resource_manifest::{import_manifest, ResourceManifest},
};
use jsonschema::JSONSchema;
use jsonschema::{JSONSchema, ValidationError};
use serde_yaml::Value;
use std::process::exit;

pub fn config_get(configurator: &Configurator, format: &Option<OutputFormat>)
pub fn config_get(configurator: &mut Configurator, format: &Option<OutputFormat>)
{
match configurator.invoke_get(ErrorAction::Continue, || { /* code */ }) {
Ok(result) => {
Expand All @@ -41,7 +41,7 @@ pub fn config_get(configurator: &Configurator, format: &Option<OutputFormat>)
}
}

pub fn config_set(configurator: &Configurator, format: &Option<OutputFormat>)
pub fn config_set(configurator: &mut Configurator, format: &Option<OutputFormat>)
{
match configurator.invoke_set(false, ErrorAction::Continue, || { /* code */ }) {
Ok(result) => {
Expand All @@ -64,7 +64,7 @@ pub fn config_set(configurator: &Configurator, format: &Option<OutputFormat>)
}
}

pub fn config_test(configurator: &Configurator, format: &Option<OutputFormat>)
pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat>)
{
match configurator.invoke_test(ErrorAction::Continue, || { /* code */ }) {
Ok(result) => {
Expand All @@ -87,7 +87,7 @@ pub fn config_test(configurator: &Configurator, format: &Option<OutputFormat>)
}
}

pub fn config_export(configurator: &Configurator, format: &Option<OutputFormat>)
pub fn config_export(configurator: &mut Configurator, format: &Option<OutputFormat>)
{
match configurator.invoke_export(ErrorAction::Continue, || { /* code */ }) {
Ok(result) => {
Expand Down Expand Up @@ -144,7 +144,7 @@ pub fn config(subcommand: &ConfigSubCommand, format: &Option<OutputFormat>, stdi
};

let json_string = serde_json_value_to_string(&json);
let configurator = match Configurator::new(&json_string) {
let mut configurator = match Configurator::new(&json_string) {
Ok(configurator) => configurator,
Err(err) => {
error!("Error: {err}");
Expand All @@ -154,23 +154,29 @@ pub fn config(subcommand: &ConfigSubCommand, format: &Option<OutputFormat>, stdi

match subcommand {
ConfigSubCommand::Get => {
config_get(&configurator, format);
config_get(&mut configurator, format);
},
ConfigSubCommand::Set => {
config_set(&configurator, format);
config_set(&mut configurator, format);
},
ConfigSubCommand::Test => {
config_test(&configurator, format);
config_test(&mut configurator, format);
},
ConfigSubCommand::Validate => {
validate_config(&json_string);
},
ConfigSubCommand::Export => {
config_export(&configurator, format);
config_export(&mut configurator, format);
}
}
}

/// Validate configuration.
///
/// # Panics
///
/// Will panic if resource is not found.
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
///
#[allow(clippy::too_many_lines)]
pub fn validate_config(config: &str) {
// first validate against the config schema
Expand Down Expand Up @@ -204,7 +210,7 @@ pub fn validate_config(config: &str) {
exit(EXIT_INVALID_INPUT);
};

let mut dsc = match DscManager::new() {
let dsc = match DscManager::new() {
Ok(dsc) => dsc,
Err(err) => {
error!("Error: {err}");
Expand All @@ -223,7 +229,7 @@ pub fn validate_config(config: &str) {
exit(EXIT_INVALID_INPUT);
});
// get the actual resource
let resource = get_resource(&mut dsc, type_name);
let resource = get_resource(&dsc, type_name).unwrap();
// see if the resource is command based
if resource.implemented_as == ImplementedAs::Command {
// if so, see if it implements validate via the resource manifest
Expand All @@ -246,7 +252,7 @@ pub fn validate_config(config: &str) {
};
if !result.valid {
let reason = result.reason.unwrap_or("No reason provided".to_string());
let type_name = resource.type_name;
let type_name = resource.type_name.clone();
error!("Resource {type_name} failed validation: {reason}");
exit(EXIT_VALIDATION_FAILED);
}
Expand All @@ -272,15 +278,18 @@ pub fn validate_config(config: &str) {
},
};
let properties = resource_block["properties"].clone();
let validation = compiled_schema.validate(&properties);
if let Err(err) = validation {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}
error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
}
let _result: Result<(), ValidationError> = match compiled_schema.validate(&properties) {
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
Ok(()) => Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}

error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
},
};
}
}
}
Expand All @@ -300,17 +309,14 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,

match subcommand {
ResourceSubCommand::List { resource_name, description, tags } => {
if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}

let mut write_table = false;
let mut table = Table::new(&["Type", "Version", "Requires", "Description"]);
if format.is_none() && atty::is(Stream::Stdout) {
// write as table if fornat is not specified and interactive
write_table = true;
}
for resource in dsc.find_resource(&resource_name.clone().unwrap_or_default()) {
for resource in dsc.list_available_resources(&resource_name.clone().unwrap_or_default()) {
// if description is specified, skip if resource description does not contain it
if description.is_some() || tags.is_some() {
let Some(ref resource_manifest) = resource.manifest else {
Expand Down Expand Up @@ -380,19 +386,26 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,
}
},
ResourceSubCommand::Get { resource, input, all } => {
if *all { resource_command::get_all(&mut dsc, resource, input, stdin, format); }
else { resource_command::get(&mut dsc, resource, input, stdin, format); };
dsc.discover_resources(&[resource.to_string()]);
if *all { resource_command::get_all(&dsc, resource, input, stdin, format); }
else {
resource_command::get(&dsc, resource, input, stdin, format);
};
},
ResourceSubCommand::Set { resource, input } => {
resource_command::set(&mut dsc, resource, input, stdin, format);
dsc.discover_resources(&[resource.to_string()]);
resource_command::set(&dsc, resource, input, stdin, format);
},
ResourceSubCommand::Test { resource, input } => {
resource_command::test(&mut dsc, resource, input, stdin, format);
dsc.discover_resources(&[resource.to_string()]);
resource_command::test(&dsc, resource, input, stdin, format);
},
ResourceSubCommand::Schema { resource } => {
resource_command::schema(&mut dsc, resource, format);
dsc.discover_resources(&[resource.to_string()]);
resource_command::schema(&dsc, resource, format);
},
ResourceSubCommand::Export { resource} => {
dsc.discover_resources(&[resource.to_string()]);
resource_command::export(&mut dsc, resource, format);
},
}
Expand Down