Skip to content

Commit f1be730

Browse files
authored
Merge pull request #760 from SteveL-MSFT/discovery-extension
Add support for discovery extensions
2 parents a83c425 + a2ba8ec commit f1be730

20 files changed

+970
-136
lines changed

dsc/locales/en-us.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ configAbout = "Apply a configuration document"
1010
parameters = "Parameters to pass to the configuration as JSON or YAML"
1111
parametersFile = "Parameters to pass to the configuration as a JSON or YAML file"
1212
systemRoot = "Specify the operating system root path if not targeting the current running OS"
13+
extensionAbout = "Operations on DSC extensions"
1314
resourceAbout = "Invoke a specific DSC resource"
1415
schemaAbout = "Get the JSON schema for a DSC type"
1516
schemaType = "The type of DSC schema to get"
@@ -24,6 +25,7 @@ validateAbout = "Validate the current configuration"
2425
exportAbout = "Export the current configuration"
2526
resolveAbout = "Resolve the current configuration"
2627
listAbout = "List or find resources"
28+
listExtensionAbout = "List or find extensions"
2729
adapter = "Adapter filter to limit the resource search"
2830
description = "Description keyword to search for in the resource description"
2931
tags = "Tag to search for in the resource tags"

dsc/src/args.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ pub enum SubCommand {
6464
#[clap(long, hide = true)]
6565
as_include: bool,
6666
},
67+
#[clap(name = "extension", about = t!("args.extensionAbout").to_string())]
68+
Extension {
69+
#[clap(subcommand)]
70+
subcommand: ExtensionSubCommand,
71+
},
6772
#[clap(name = "resource", about = t!("args.resourceAbout").to_string())]
6873
Resource {
6974
#[clap(subcommand)]
@@ -144,6 +149,17 @@ pub enum ConfigSubCommand {
144149
}
145150
}
146151

152+
#[derive(Debug, PartialEq, Eq, Subcommand)]
153+
pub enum ExtensionSubCommand {
154+
#[clap(name = "list", about = t!("args.listExtensionAbout").to_string())]
155+
List {
156+
/// Optional filter to apply to the list of extensions
157+
extension_name: Option<String>,
158+
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
159+
output_format: Option<OutputFormat>,
160+
},
161+
}
162+
147163
#[derive(Debug, PartialEq, Eq, Subcommand)]
148164
pub enum ResourceSubCommand {
149165
#[clap(name = "list", about = t!("args.listAbout").to_string())]

dsc/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ fn main() {
6464
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
6565
}
6666
},
67+
SubCommand::Extension { subcommand } => {
68+
subcommand::extension(&subcommand, progress_format);
69+
},
6770
SubCommand::Resource { subcommand } => {
6871
subcommand::resource(&subcommand, progress_format);
6972
},

dsc/src/subcommand.rs

Lines changed: 147 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::args::{ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand};
4+
use crate::args::{ConfigSubCommand, DscType, ExtensionSubCommand, OutputFormat, ResourceSubCommand};
55
use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::tablewriter::Table;
@@ -16,6 +16,8 @@ use dsc_lib::{
1616
config_result::ResourceGetResult,
1717
Configurator,
1818
},
19+
discovery::discovery_trait::DiscoveryKind,
20+
discovery::command_discovery::ImportedManifest,
1921
dscerror::DscError,
2022
DscManager,
2123
dscresources::invoke_result::{
@@ -25,6 +27,7 @@ use dsc_lib::{
2527
},
2628
dscresources::dscresource::{Capability, ImplementedAs, Invoke},
2729
dscresources::resource_manifest::{import_manifest, ResourceManifest},
30+
extensions::dscextension::Capability as ExtensionCapability,
2831
progress::ProgressFormat,
2932
};
3033
use rust_i18n::t;
@@ -543,6 +546,22 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat)
543546
Ok(())
544547
}
545548

549+
pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressFormat) {
550+
let mut dsc = match DscManager::new() {
551+
Ok(dsc) => dsc,
552+
Err(err) => {
553+
error!("Error: {err}");
554+
exit(EXIT_DSC_ERROR);
555+
}
556+
};
557+
558+
match subcommand {
559+
ExtensionSubCommand::List{extension_name, output_format} => {
560+
list_extensions(&mut dsc, extension_name.as_ref(), output_format.as_ref(), progress_format);
561+
},
562+
}
563+
}
564+
546565
#[allow(clippy::too_many_lines)]
547566
pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) {
548567
let mut dsc = match DscManager::new() {
@@ -592,6 +611,62 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
592611
}
593612
}
594613

614+
fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format: Option<&OutputFormat>, progress_format: ProgressFormat) {
615+
let mut write_table = false;
616+
let mut table = Table::new(&[
617+
t!("subcommand.tableHeader_type").to_string().as_ref(),
618+
t!("subcommand.tableHeader_version").to_string().as_ref(),
619+
t!("subcommand.tableHeader_capabilities").to_string().as_ref(),
620+
t!("subcommand.tableHeader_description").to_string().as_ref(),
621+
]);
622+
if format.is_none() && io::stdout().is_terminal() {
623+
// write as table if format is not specified and interactive
624+
write_table = true;
625+
}
626+
let mut include_separator = false;
627+
for manifest_resource in dsc.list_available(&DiscoveryKind::Extension, extension_name.unwrap_or(&String::from("*")), "", progress_format) {
628+
if let ImportedManifest::Extension(extension) = manifest_resource {
629+
let mut capabilities = "-".to_string();
630+
let capability_types = [
631+
(ExtensionCapability::Discover, "d"),
632+
];
633+
634+
for (i, (capability, letter)) in capability_types.iter().enumerate() {
635+
if extension.capabilities.contains(capability) {
636+
capabilities.replace_range(i..=i, letter);
637+
}
638+
}
639+
640+
if write_table {
641+
table.add_row(vec![
642+
extension.type_name,
643+
extension.version,
644+
capabilities,
645+
extension.description.unwrap_or_default()
646+
]);
647+
}
648+
else {
649+
// convert to json
650+
let json = match serde_json::to_string(&extension) {
651+
Ok(json) => json,
652+
Err(err) => {
653+
error!("JSON: {err}");
654+
exit(EXIT_JSON_ERROR);
655+
}
656+
};
657+
write_object(&json, format, include_separator);
658+
include_separator = true;
659+
// insert newline separating instances if writing to console
660+
if io::stdout().is_terminal() { println!(); }
661+
}
662+
}
663+
}
664+
665+
if write_table {
666+
table.print();
667+
}
668+
}
669+
595670
fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec<String>>, format: Option<&OutputFormat>, progress_format: ProgressFormat) {
596671
let mut write_table = false;
597672
let mut table = Table::new(&[
@@ -607,86 +682,88 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_
607682
write_table = true;
608683
}
609684
let mut include_separator = false;
610-
for resource in dsc.list_available_resources(resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) {
611-
let mut capabilities = "--------".to_string();
612-
let capability_types = [
613-
(Capability::Get, "g"),
614-
(Capability::Set, "s"),
615-
(Capability::SetHandlesExist, "x"),
616-
(Capability::WhatIf, "w"),
617-
(Capability::Test, "t"),
618-
(Capability::Delete, "d"),
619-
(Capability::Export, "e"),
620-
(Capability::Resolve, "r"),
621-
];
622-
623-
for (i, (capability, letter)) in capability_types.iter().enumerate() {
624-
if resource.capabilities.contains(capability) {
625-
capabilities.replace_range(i..=i, letter);
685+
for manifest_resource in dsc.list_available(&DiscoveryKind::Resource, resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) {
686+
if let ImportedManifest::Resource(resource) = manifest_resource {
687+
let mut capabilities = "--------".to_string();
688+
let capability_types = [
689+
(Capability::Get, "g"),
690+
(Capability::Set, "s"),
691+
(Capability::SetHandlesExist, "x"),
692+
(Capability::WhatIf, "w"),
693+
(Capability::Test, "t"),
694+
(Capability::Delete, "d"),
695+
(Capability::Export, "e"),
696+
(Capability::Resolve, "r"),
697+
];
698+
699+
for (i, (capability, letter)) in capability_types.iter().enumerate() {
700+
if resource.capabilities.contains(capability) {
701+
capabilities.replace_range(i..=i, letter);
702+
}
626703
}
627-
}
628704

629-
// if description, tags, or write_table is specified, pull resource manifest if it exists
630-
if let Some(ref resource_manifest) = resource.manifest {
631-
let manifest = match import_manifest(resource_manifest.clone()) {
632-
Ok(resource_manifest) => resource_manifest,
633-
Err(err) => {
634-
error!("{} {}: {err}", t!("subcommand.invalidManifest"), resource.type_name);
705+
// if description, tags, or write_table is specified, pull resource manifest if it exists
706+
if let Some(ref resource_manifest) = resource.manifest {
707+
let manifest = match import_manifest(resource_manifest.clone()) {
708+
Ok(resource_manifest) => resource_manifest,
709+
Err(err) => {
710+
error!("{} {}: {err}", t!("subcommand.invalidManifest"), resource.type_name);
711+
continue;
712+
}
713+
};
714+
715+
// if description is specified, skip if resource description does not contain it
716+
if description.is_some() &&
717+
(manifest.description.is_none() | !manifest.description.unwrap_or_default().to_lowercase().contains(&description.unwrap_or(&String::new()).to_lowercase())) {
635718
continue;
636719
}
637-
};
638-
639-
// if description is specified, skip if resource description does not contain it
640-
if description.is_some() &&
641-
(manifest.description.is_none() | !manifest.description.unwrap_or_default().to_lowercase().contains(&description.unwrap_or(&String::new()).to_lowercase())) {
642-
continue;
643-
}
644720

645-
// if tags is specified, skip if resource tags do not contain the tags
646-
if let Some(tags) = tags {
647-
let Some(manifest_tags) = manifest.tags else { continue; };
721+
// if tags is specified, skip if resource tags do not contain the tags
722+
if let Some(tags) = tags {
723+
let Some(manifest_tags) = manifest.tags else { continue; };
648724

649-
let mut found = false;
650-
for tag_to_find in tags {
651-
for tag in &manifest_tags {
652-
if tag.to_lowercase() == tag_to_find.to_lowercase() {
653-
found = true;
654-
break;
725+
let mut found = false;
726+
for tag_to_find in tags {
727+
for tag in &manifest_tags {
728+
if tag.to_lowercase() == tag_to_find.to_lowercase() {
729+
found = true;
730+
break;
731+
}
655732
}
656733
}
734+
if !found { continue; }
735+
}
736+
} else {
737+
// resource does not have a manifest but filtering on description or tags was requested - skip such resource
738+
if description.is_some() || tags.is_some() {
739+
continue;
657740
}
658-
if !found { continue; }
659-
}
660-
} else {
661-
// resource does not have a manifest but filtering on description or tags was requested - skip such resource
662-
if description.is_some() || tags.is_some() {
663-
continue;
664741
}
665-
}
666742

667-
if write_table {
668-
table.add_row(vec![
669-
resource.type_name,
670-
format!("{:?}", resource.kind),
671-
resource.version,
672-
capabilities,
673-
resource.require_adapter.unwrap_or_default(),
674-
resource.description.unwrap_or_default()
675-
]);
676-
}
677-
else {
678-
// convert to json
679-
let json = match serde_json::to_string(&resource) {
680-
Ok(json) => json,
681-
Err(err) => {
682-
error!("JSON: {err}");
683-
exit(EXIT_JSON_ERROR);
684-
}
685-
};
686-
write_object(&json, format, include_separator);
687-
include_separator = true;
688-
// insert newline separating instances if writing to console
689-
if io::stdout().is_terminal() { println!(); }
743+
if write_table {
744+
table.add_row(vec![
745+
resource.type_name,
746+
format!("{:?}", resource.kind),
747+
resource.version,
748+
capabilities,
749+
resource.require_adapter.unwrap_or_default(),
750+
resource.description.unwrap_or_default()
751+
]);
752+
}
753+
else {
754+
// convert to json
755+
let json = match serde_json::to_string(&resource) {
756+
Ok(json) => json,
757+
Err(err) => {
758+
error!("JSON: {err}");
759+
exit(EXIT_JSON_ERROR);
760+
}
761+
};
762+
write_object(&json, format, include_separator);
763+
include_separator = true;
764+
// insert newline separating instances if writing to console
765+
if io::stdout().is_terminal() { println!(); }
766+
}
690767
}
691768
}
692769

0 commit comments

Comments
 (0)