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
Merged
124 changes: 74 additions & 50 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,24 @@ use dsc_lib::{
};
use std::process::exit;

pub fn get(dsc: &mut DscManager, resource: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
pub fn get(dsc: &DscManager, resource_type: &str, input: &Option<String>, stdin: &Option<String>, format: &Option<OutputFormat>) {
// 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_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.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());
if let Some(pr) = get_resource(dsc, requires) {
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
resource = pr;
} else {
error!("Provider {} not found", requires);
return;
};
}

match resource.get(input.as_str()) {
Expand All @@ -43,8 +54,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_type: &str, _input: &Option<String>, _stdin: &Option<String>, format: &Option<OutputFormat>) {
let Some(resource) = get_resource(dsc, resource_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.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 +85,34 @@ 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_type: &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_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.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());
if let Some(pr) = get_resource(dsc, requires) {
resource = pr;
} else {
error!("Provider {} not found", requires);
return;
};
}

match resource.set(input.as_str(), true) {
Expand All @@ -106,15 +134,29 @@ 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_type: &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_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.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());
if let Some(pr) = get_resource(dsc, requires) {
resource = pr;
} else {
error!("Provider {} not found", requires);
return;
};
}

match resource.test(input.as_str()) {
Expand All @@ -136,8 +178,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_type: &str, format: &Option<OutputFormat>) {
let Some(resource) = get_resource(dsc, resource_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.to_string()).to_string());
return
};
match resource.schema() {
Ok(json) => {
// verify is json
Expand All @@ -157,12 +202,15 @@ 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_type: &str, format: &Option<OutputFormat>) {
let Some(dsc_resource) = get_resource(dsc, resource_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.to_string()).to_string());
return
};

let mut conf = Configuration::new();

if let Err(err) = add_resource_export_results_to_configuration(&dsc_resource, &mut conf) {
if let Err(err) = add_resource_export_results_to_configuration(dsc_resource, &mut conf) {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
Expand All @@ -177,34 +225,10 @@ 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 {
// check if resource is JSON or just a name
match serde_json::from_str(resource) {
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);
}
}
}
}
#[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
dsc.find_resource(String::from(resource).to_lowercase().as_str())
}

fn get_input(input: &Option<String>, stdin: &Option<String>) -> String {
Expand Down
54 changes: 31 additions & 23 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use jsonschema::JSONSchema;
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,24 @@ 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.
#[allow(clippy::too_many_lines)]
pub fn validate_config(config: &str) {
// first validate against the config schema
Expand Down Expand Up @@ -204,7 +205,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 +224,10 @@ pub fn validate_config(config: &str) {
exit(EXIT_INVALID_INPUT);
});
// get the actual resource
let resource = get_resource(&mut dsc, type_name);
let Some(resource) = get_resource(&dsc, type_name) else {
error!("Error: Resource type not found");
exit(EXIT_DSC_ERROR);
};
// 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 +250,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 Down Expand Up @@ -280,7 +284,7 @@ pub fn validate_config(config: &str) {
}
error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
}
};
}
}
}
Expand All @@ -300,17 +304,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 +381,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_lowercase().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_lowercase().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_lowercase().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_lowercase().to_string()]);
resource_command::schema(&dsc, resource, format);
},
ResourceSubCommand::Export { resource} => {
dsc.discover_resources(&[resource.to_lowercase().to_string()]);
resource_command::export(&mut dsc, resource, format);
},
}
Expand Down
2 changes: 1 addition & 1 deletion dsc/tests/dsc_args.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Describe 'config argument tests' {
'@ }
) {
param($text)
$output = $text | dsc resource get -r *registry
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
$output = $text | dsc resource get -r Microsoft.Windows/Registry
$output = $output | ConvertFrom-Json
$output.actualState.'$id' | Should -BeExactly 'https://developer.microsoft.com/json-schemas/windows/registry/20230303/Microsoft.Windows.Registry.schema.json'
$output.actualState.keyPath | Should -BeExactly 'HKLM\Software\Microsoft\Windows NT\CurrentVersion'
Expand Down
3 changes: 1 addition & 2 deletions dsc/tests/dsc_get.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Describe 'config get tests' {
It 'should get from registry using <type> resource' -Skip:(!$IsWindows) -TestCases @(
@{ type = 'string' }
@{ type = 'json' }
) {
param($type)

Expand Down Expand Up @@ -46,7 +45,7 @@ Describe 'config get tests' {
"Name": "ProductName"
}
'@
$json | dsc resource get -r *registry
$json | dsc resource get -r Microsoft.Windows/registry
$LASTEXITCODE | Should -Be 2
}
}
2 changes: 1 addition & 1 deletion dsc/tests/dsc_schema.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Describe 'config schema tests' {
It 'return resource schema' -Skip:(!$IsWindows) {
$schema = dsc resource schema -r *registry
$schema = dsc resource schema -r Microsoft.Windows/registry
$LASTEXITCODE | Should -Be 0
$schema | Should -Not -BeNullOrEmpty
$schema = $schema | ConvertFrom-Json
Expand Down
Loading