Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dsc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dsc_lib = { path = "../dsc_lib" }
indicatif = { version = "0.18" }
jsonschema = { version = "0.30", default-features = false }
path-absolutize = { version = "3.1" }
regex = "1.11"
rust-i18n = { version = "3.1" }
schemars = { version = "0.8" }
serde = { version = "1.0", features = ["derive"] }
Expand Down
9 changes: 9 additions & 0 deletions dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ tags = "Tag to search for in the resource tags"
resourceGet = "Invoke the get operation to a resource"
getAll = "Get all instances of the resource"
resource = "The name of the resource to invoke"
functionAbout = "Operations on DSC functions"
listFunctionAbout = "List or find functions"

[main]
ctrlCReceived = "Ctrl-C received"
Expand Down Expand Up @@ -109,6 +111,13 @@ tableHeader_version = "Version"
tableHeader_capabilities = "Capabilities"
tableHeader_adapter = "RequireAdapter"
tableHeader_description = "Description"
tableHeader_functionName = "Function"
tableHeader_functionCategory = "Category"
tableHeader_minArgs = "MinArgs"
tableHeader_maxArgs = "MaxArgs"
tableHeader_argTypes = "ArgTypes"
invalidFunctionFilter = "Invalid function filter"
maxInt = "maxInt"
invalidManifest = "Error in manifest for"
jsonArrayNotSupported = "JSON array output format is only supported for `--all'"

Expand Down
27 changes: 22 additions & 5 deletions dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ pub enum SubCommand {
#[clap(subcommand)]
subcommand: ExtensionSubCommand,
},
#[clap(name = "function", about = t!("args.functionAbout").to_string())]
Function {
#[clap(subcommand)]
subcommand: FunctionSubCommand,
},
#[clap(name = "resource", about = t!("args.resourceAbout").to_string())]
Resource {
#[clap(subcommand)]
Expand All @@ -94,7 +99,7 @@ pub enum SubCommand {
#[clap(name = "schema", about = t!("args.schemaAbout").to_string())]
Schema {
#[clap(name = "type", short, long, help = t!("args.schemaType").to_string(), value_enum)]
dsc_type: DscType,
dsc_type: SchemaType,
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string(), value_enum)]
output_format: Option<OutputFormat>,
},
Expand Down Expand Up @@ -170,18 +175,29 @@ pub enum ConfigSubCommand {
pub enum ExtensionSubCommand {
#[clap(name = "list", about = t!("args.listExtensionAbout").to_string())]
List {
/// Optional filter to apply to the list of extensions
/// Optional extension name to filter the list
extension_name: Option<String>,
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
output_format: Option<ListOutputFormat>,
},
}

#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum FunctionSubCommand {
#[clap(name = "list", about = t!("args.listFunctionAbout").to_string())]
List {
/// Optional function name to filter the list
function_name: Option<String>,
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
output_format: Option<ListOutputFormat>,
},
}

#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum ResourceSubCommand {
#[clap(name = "list", about = t!("args.listAbout").to_string())]
List {
/// Optional filter to apply to the list of resources
/// Optional resource name to filter the list
resource_name: Option<String>,
/// Optional adapter filter to apply to the list of resources
#[clap(short = 'a', long = "adapter", help = t!("args.adapter").to_string())]
Expand Down Expand Up @@ -258,7 +274,7 @@ pub enum ResourceSubCommand {
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum DscType {
pub enum SchemaType {
GetResult,
SetResult,
TestResult,
Expand All @@ -271,5 +287,6 @@ pub enum DscType {
ConfigurationSetResult,
ConfigurationTestResult,
ExtensionManifest,
ExtensionDiscoverResult
ExtensionDiscoverResult,
FunctionDefinition,
}
3 changes: 3 additions & 0 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ fn main() {
SubCommand::Extension { subcommand } => {
subcommand::extension(&subcommand, progress_format);
},
SubCommand::Function { subcommand } => {
subcommand::function(&subcommand);
},
SubCommand::Resource { subcommand } => {
subcommand::resource(&subcommand, progress_format);
},
Expand Down
112 changes: 108 additions & 4 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::args::{ConfigSubCommand, DscType, ExtensionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, ResourceSubCommand};
use crate::args::{ConfigSubCommand, SchemaType, ExtensionSubCommand, FunctionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, ResourceSubCommand};
use crate::resolve::{get_contents, Include};
use crate::resource_command::{get_resource, self};
use crate::tablewriter::Table;
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, validate_json, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
use dsc_lib::functions::AcceptedArgKind;
use dsc_lib::{
configure::{
config_doc::{
Expand All @@ -28,8 +29,11 @@ use dsc_lib::{
dscresources::dscresource::{Capability, ImplementedAs, Invoke},
dscresources::resource_manifest::{import_manifest, ResourceManifest},
extensions::dscextension::Capability as ExtensionCapability,
functions::FunctionDispatcher,
progress::ProgressFormat,
util::convert_wildcard_to_regex,
};
use regex::RegexBuilder;
use rust_i18n::t;
use std::{
collections::HashMap,
Expand Down Expand Up @@ -473,7 +477,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, parame
pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) -> Result<(), DscError> {
// first validate against the config schema
debug!("{}", t!("subcommand.validatingConfiguration"));
let schema = serde_json::to_value(get_schema(DscType::Configuration))?;
let schema = serde_json::to_value(get_schema(SchemaType::Configuration))?;
let config_value = serde_json::to_value(config)?;
validate_json("Configuration", &schema, &config_value)?;
let mut dsc = DscManager::new()?;
Expand Down Expand Up @@ -562,6 +566,15 @@ pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressForm
}
}

pub fn function(subcommand: &FunctionSubCommand) {
let functions = FunctionDispatcher::new();
match subcommand {
FunctionSubCommand::List { function_name, output_format } => {
list_functions(&functions, function_name.as_ref(), output_format.as_ref());
},
}
}

#[allow(clippy::too_many_lines)]
pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) {
let mut dsc = match DscManager::new() {
Expand Down Expand Up @@ -632,10 +645,10 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
let mut include_separator = false;
for manifest_resource in dsc.list_available(&DiscoveryKind::Extension, extension_name.unwrap_or(&String::from("*")), "", progress_format) {
if let ImportedManifest::Extension(extension) = manifest_resource {
let mut capabilities = "-".to_string();
let capability_types = [
(ExtensionCapability::Discover, "d"),
];
let mut capabilities = "-".repeat(capability_types.len());

for (i, (capability, letter)) in capability_types.iter().enumerate() {
if extension.capabilities.contains(capability) {
Expand Down Expand Up @@ -680,6 +693,97 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
}
}

fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, output_format: Option<&ListOutputFormat>) {
let mut write_table = false;
let mut table = Table::new(&[
t!("subcommand.tableHeader_functionCategory").to_string().as_ref(),
t!("subcommand.tableHeader_functionName").to_string().as_ref(),
t!("subcommand.tableHeader_minArgs").to_string().as_ref(),
t!("subcommand.tableHeader_maxArgs").to_string().as_ref(),
t!("subcommand.tableHeader_argTypes").to_string().as_ref(),
t!("subcommand.tableHeader_description").to_string().as_ref(),
]);
if output_format.is_none() && io::stdout().is_terminal() {
// write as table if format is not specified and interactive
write_table = true;
}
let mut include_separator = false;
let accepted_arg_types= [
(AcceptedArgKind::Array, "a"),
(AcceptedArgKind::Boolean, "b"),
(AcceptedArgKind::Number, "n"),
(AcceptedArgKind::String, "s"),
(AcceptedArgKind::Object, "o"),
];

let asterisks = String::from("*");
let name = function_name.unwrap_or(&asterisks);
let regex_str = convert_wildcard_to_regex(name);
let mut regex_builder = RegexBuilder::new(&regex_str);
regex_builder.case_insensitive(true);
let Ok(regex) = regex_builder.build() else {
error!("{}: {}", t!("subcommand.invalidFunctionFilter"), regex_str);
exit(EXIT_INVALID_ARGS);
};

let mut functions_list = functions.list();
functions_list.sort();
for function in functions_list {
if !regex.is_match(&function.name) {
continue;
}

if write_table {
// construct arg_types from '-' times number of accepted_arg_types
let mut arg_types = "-".repeat(accepted_arg_types.len());
for (i, (arg_type, letter)) in accepted_arg_types.iter().enumerate() {
if function.accepted_arg_types.contains(arg_type) {
arg_types.replace_range(i..=i, letter);
}
}

let max_args = if function.max_args == usize::MAX {
t!("subcommand.maxInt").to_string()
} else {
function.max_args.to_string()
};

table.add_row(vec![
function.category.to_string(),
function.name,
function.min_args.to_string(),
max_args,
arg_types,
function.description
]);
}
else {
let json = match serde_json::to_string(&function) {
Ok(json) => json,
Err(err) => {
error!("JSON: {err}");
exit(EXIT_JSON_ERROR);
}
};
let format = match output_format {
Some(ListOutputFormat::Json) => Some(OutputFormat::Json),
Some(ListOutputFormat::PrettyJson) => Some(OutputFormat::PrettyJson),
Some(ListOutputFormat::Yaml) => Some(OutputFormat::Yaml),
_ => None,
};
write_object(&json, format.as_ref(), include_separator);
include_separator = true;
// insert newline separating instances if writing to console
if io::stdout().is_terminal() { println!(); }
}
}

if write_table {
let truncate = output_format != Some(&ListOutputFormat::TableNoTruncate);
table.print(truncate);
}
}

fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec<String>>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) {
let mut write_table = false;
let mut table = Table::new(&[
Expand All @@ -697,7 +801,6 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_
let mut include_separator = false;
for manifest_resource in dsc.list_available(&DiscoveryKind::Resource, resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) {
if let ImportedManifest::Resource(resource) = manifest_resource {
let mut capabilities = "--------".to_string();
let capability_types = [
(Capability::Get, "g"),
(Capability::Set, "s"),
Expand All @@ -708,6 +811,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_
(Capability::Export, "e"),
(Capability::Resolve, "r"),
];
let mut capabilities = "-".repeat(capability_types.len());

for (i, (capability, letter)) in capability_types.iter().enumerate() {
if resource.capabilities.contains(capability) {
Expand Down
36 changes: 20 additions & 16 deletions dsc/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::args::{DscType, OutputFormat, TraceFormat};
use crate::args::{SchemaType, OutputFormat, TraceFormat};
use crate::resolve::Include;
use dsc_lib::configure::config_result::ResourceTestResult;
use dsc_lib::extensions::discover::DiscoverResult;
Expand All @@ -25,6 +25,7 @@ use dsc_lib::{
ResolveResult,
}, resource_manifest::ResourceManifest
},
functions::FunctionDefinition,
util::parse_input_to_json,
util::get_setting,
};
Expand Down Expand Up @@ -143,47 +144,50 @@ pub fn add_fields_to_json(json: &str, fields_to_add: &HashMap<String, String>) -
///
/// * `RootSchema` - The schema
#[must_use]
pub fn get_schema(dsc_type: DscType) -> RootSchema {
match dsc_type {
DscType::GetResult => {
pub fn get_schema(schema: SchemaType) -> RootSchema {
match schema {
SchemaType::GetResult => {
schema_for!(GetResult)
},
DscType::SetResult => {
SchemaType::SetResult => {
schema_for!(SetResult)
},
DscType::TestResult => {
SchemaType::TestResult => {
schema_for!(TestResult)
},
DscType::ResolveResult => {
SchemaType::ResolveResult => {
schema_for!(ResolveResult)
}
DscType::DscResource => {
SchemaType::DscResource => {
schema_for!(DscResource)
},
DscType::ResourceManifest => {
SchemaType::ResourceManifest => {
schema_for!(ResourceManifest)
},
DscType::Include => {
SchemaType::Include => {
schema_for!(Include)
},
DscType::Configuration => {
SchemaType::Configuration => {
schema_for!(Configuration)
},
DscType::ConfigurationGetResult => {
SchemaType::ConfigurationGetResult => {
schema_for!(ConfigurationGetResult)
},
DscType::ConfigurationSetResult => {
SchemaType::ConfigurationSetResult => {
schema_for!(ConfigurationSetResult)
},
DscType::ConfigurationTestResult => {
SchemaType::ConfigurationTestResult => {
schema_for!(ConfigurationTestResult)
},
DscType::ExtensionManifest => {
SchemaType::ExtensionManifest => {
schema_for!(ExtensionManifest)
},
DscType::ExtensionDiscoverResult => {
SchemaType::ExtensionDiscoverResult => {
schema_for!(DiscoverResult)
},
SchemaType::FunctionDefinition => {
schema_for!(FunctionDefinition)
}
}
}

Expand Down
Loading
Loading