diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 5000b20c..e77c3a73 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -4,6 +4,7 @@ _version = 1 about = "Apply configuration or invoke specific DSC resources" traceFormat = "Trace format to use" traceLevel = "Trace level to use" +progressFormat = "Progress format to use" completer = "Generate a shell completion script" configAbout = "Apply a configuration document" parameters = "Parameters to pass to the configuration as JSON or YAML" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 20016254..9da575d2 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use dsc_lib::dscresources::command_resource::TraceLevel; +use dsc_lib::util::ProgressFormat; use rust_i18n::t; use serde::Deserialize; @@ -33,6 +34,8 @@ pub struct Args { pub trace_level: Option, #[clap(short = 't', long, help = t!("args.traceFormat").to_string(), value_enum)] pub trace_format: Option, + #[clap(short = 'p', long, help = t!("args.progressFormat").to_string(), value_enum)] + pub progress_format: Option, } #[derive(Debug, PartialEq, Eq, Subcommand)] diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 983804fa..530abebf 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -8,6 +8,7 @@ use rust_i18n::{i18n, t}; use std::{io, process::exit}; use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; use tracing::{error, info, warn, debug}; +use dsc_lib::util::ProgressFormat; #[cfg(debug_assertions)] use crossterm::event; @@ -40,6 +41,8 @@ fn main() { debug!("{}: {}", t!("main.usingDscVersion"), env!("CARGO_PKG_VERSION")); + let progress_format = args.progress_format.unwrap_or( ProgressFormat::Default ); + match args.subcommand { SubCommand::Completer { shell } => { info!("{} {:?}", t!("main.generatingCompleter"), shell); @@ -50,7 +53,7 @@ fn main() { if let Some(file_name) = parameters_file { info!("{}: {file_name}", t!("main.readingParametersFile")); match std::fs::read_to_string(&file_name) { - Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include), + Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include, progress_format), Err(err) => { error!("{} '{file_name}': {err}", t!("main.failedToReadParametersFile")); exit(util::EXIT_INVALID_INPUT); @@ -58,11 +61,11 @@ fn main() { } } else { - subcommand::config(&subcommand, ¶meters, system_root.as_ref(), &as_group, &as_include); + subcommand::config(&subcommand, ¶meters, system_root.as_ref(), &as_group, &as_include, progress_format); } }, SubCommand::Resource { subcommand } => { - subcommand::resource(&subcommand); + subcommand::resource(&subcommand, progress_format); }, SubCommand::Schema { dsc_type , output_format } => { let schema = util::get_schema(dsc_type); diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index e9851424..3cf64298 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -25,6 +25,7 @@ use dsc_lib::{ }, dscresources::dscresource::{Capability, ImplementedAs, Invoke}, dscresources::resource_manifest::{import_manifest, ResourceManifest}, + util::ProgressFormat, }; use rust_i18n::t; use std::{ @@ -251,7 +252,7 @@ fn initialize_config_root(path: Option<&String>) -> Option { } #[allow(clippy::too_many_lines)] -pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounted_path: Option<&String>, as_group: &bool, as_include: &bool) { +pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounted_path: Option<&String>, as_group: &bool, as_include: &bool, progress_format: ProgressFormat) { let (new_parameters, json_string) = match subcommand { ConfigSubCommand::Get { input, file, .. } | ConfigSubCommand::Set { input, file, .. } | @@ -295,6 +296,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte } }; + configurator.set_progress_format(progress_format); + if let ConfigSubCommand::Set { what_if , .. } = subcommand { if *what_if { configurator.context.execution_type = ExecutionKind::WhatIf; @@ -382,7 +385,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte } } } else { - match validate_config(configurator.get_config()) { + match validate_config(configurator.get_config(), progress_format) { Ok(()) => { // valid, so do nothing }, @@ -450,7 +453,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte /// # Errors /// /// * `DscError` - The error that occurred. -pub fn validate_config(config: &Configuration) -> Result<(), DscError> { +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))?; @@ -476,7 +479,7 @@ pub fn validate_config(config: &Configuration) -> Result<(), DscError> { resource_types.push(type_name.to_lowercase().to_string()); } - dsc.find_resources(&resource_types); + dsc.find_resources(&resource_types, progress_format); for resource_block in resources { let Some(type_name) = resource_block["type"].as_str() else { @@ -527,7 +530,7 @@ pub fn validate_config(config: &Configuration) -> Result<(), DscError> { } #[allow(clippy::too_many_lines)] -pub fn resource(subcommand: &ResourceSubCommand) { +pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) { let mut dsc = match DscManager::new() { Ok(dsc) => dsc, Err(err) => { @@ -538,18 +541,18 @@ pub fn resource(subcommand: &ResourceSubCommand) { match subcommand { ResourceSubCommand::List { resource_name, adapter_name, description, tags, output_format } => { - list_resources(&mut dsc, resource_name.as_ref(), adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref()); + list_resources(&mut dsc, resource_name.as_ref(), adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format); }, ResourceSubCommand::Schema { resource , output_format } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); resource_command::schema(&dsc, resource, output_format.as_ref()); }, ResourceSubCommand::Export { resource, output_format } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); resource_command::export(&mut dsc, resource, output_format.as_ref()); }, ResourceSubCommand::Get { resource, input, file: path, all, output_format } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); if *all { resource_command::get_all(&dsc, resource, output_format.as_ref()); } else { let parsed_input = get_input(input.as_ref(), path.as_ref()); @@ -557,24 +560,24 @@ pub fn resource(subcommand: &ResourceSubCommand) { } }, ResourceSubCommand::Set { resource, input, file: path, output_format } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref()); resource_command::set(&dsc, resource, parsed_input, output_format.as_ref()); }, ResourceSubCommand::Test { resource, input, file: path, output_format } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref()); resource_command::test(&dsc, resource, parsed_input, output_format.as_ref()); }, ResourceSubCommand::Delete { resource, input, file: path } => { - dsc.find_resources(&[resource.to_string()]); + dsc.find_resources(&[resource.to_string()], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref()); resource_command::delete(&dsc, resource, parsed_input); }, } } -fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&OutputFormat>) { +fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&OutputFormat>, progress_format: ProgressFormat) { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_type").to_string().as_ref(), @@ -588,7 +591,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_ // write as table if format is not specified and interactive write_table = true; } - for resource in dsc.list_available_resources(resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new())) { + for resource in dsc.list_available_resources(resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) { let mut capabilities = "--------".to_string(); let capability_types = [ (Capability::Get, "g"), diff --git a/dsc/tests/dsc_config_get.tests.ps1 b/dsc/tests/dsc_config_get.tests.ps1 index 14adb978..ae98b447 100644 --- a/dsc/tests/dsc_config_get.tests.ps1 +++ b/dsc/tests/dsc_config_get.tests.ps1 @@ -56,6 +56,29 @@ Describe 'dsc config get tests' { $LASTEXITCODE | Should -Be 0 } + It 'json progress for config subcommand' { + $config_yaml = @" + `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: hello +"@ + $config_yaml | dsc --progress-format json config get -f - 2> $TestDrive/ErrorStream.txt + $LASTEXITCODE | Should -Be 0 + $lines = Get-Content $TestDrive/ErrorStream.txt + $ProgressMessagesFound = $False + foreach ($line in $lines) { + $jp = $line | ConvertFrom-Json + if ($jp.activity) { # if line is a progress message + $jp.percent_complete | Should -BeIn (0..100) + $ProgressMessagesFound = $True + } + } + $ProgressMessagesFound | Should -BeTrue + } + It 'contentVersion is ignored' { $config_yaml = @" `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index 9e97c160..bd61ba41 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -58,6 +58,21 @@ Describe 'Tests for listing resources' { } } + It 'json progress for resource subcommand' { + dsc -t json -p json resource list -a '*' 2> $TestDrive/ErrorStream.txt + $LASTEXITCODE | Should -Be 0 + $lines = Get-Content $TestDrive/ErrorStream.txt + $ProgressMessagesFound = $False + foreach ($line in $lines) { + $jp = $line | ConvertFrom-Json + if ($jp.activity) { # if line is a progress message + $jp.percent_complete | Should -BeIn (0..100) + $ProgressMessagesFound = $True + } + } + $ProgressMessagesFound | Should -BeTrue + } + It 'Capabilities are returned' { $resource = dsc resource list Microsoft/OSInfo | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 6e4c5477..7b885b63 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -12,6 +12,8 @@ use crate::dscresources::{ use crate::DscResource; use crate::discovery::Discovery; use crate::parser::Statement; +use crate::ProgressFormat; +use crate::util::ProgressBar; use self::context::Context; use self::config_doc::{Configuration, DataType, MicrosoftDscMetadata, Operation, SecurityContextKind}; use self::depends_on::get_resource_invocation_order; @@ -22,8 +24,7 @@ use security_context_lib::{SecurityContext, get_security_context}; use serde_json::{Map, Value}; use std::path::PathBuf; use std::{collections::HashMap, mem}; -use tracing::{debug, info, trace, warn_span, Span}; -use tracing_indicatif::span_ext::IndicatifSpanExt; +use tracing::{debug, info, trace}; pub mod context; pub mod config_doc; pub mod config_result; @@ -37,6 +38,7 @@ pub struct Configurator { pub context: Context, discovery: Discovery, statement_parser: Statement, + progress_format: ProgressFormat, } /// Add the results of an export operation to a configuration. @@ -134,9 +136,9 @@ fn escape_property_values(properties: &Map) -> Result Result { - // use warn_span since that is the default logging level but progress bars will be suppressed if error trace level is used - let pb_span = warn_span!(""); +fn get_progress_bar_span(len: u64, progress_format: ProgressFormat) -> Result { + let mut pb_span = ProgressBar::new(progress_format == ProgressFormat::Json); + pb_span.pb_set_style(&ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}" )?); @@ -210,6 +212,7 @@ impl Configurator { context: Context::new(), discovery, statement_parser: Statement::new()?, + progress_format: ProgressFormat::Default, }; config.validate_config()?; Ok(config) @@ -225,6 +228,11 @@ impl Configurator { &self.config } + /// Sets progress format for the configuration. + pub fn set_progress_format(&mut self, progress_format: ProgressFormat) { + self.progress_format = progress_format; + } + /// Invoke the get operation on a resource. /// /// # Returns @@ -237,11 +245,11 @@ impl Configurator { pub fn invoke_get(&mut self) -> Result { let mut result = ConfigurationGetResult::new(); let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?; - let pb_span = get_progress_bar_span(resources.len() as u64)?; - let pb_span_enter = pb_span.enter(); + let mut pb_span = get_progress_bar_span(resources.len() as u64, self.progress_format)?; + pb_span.enter(); for resource in resources { - Span::current().pb_inc(1); pb_span.pb_set_message(format!("Get '{}'", resource.name).as_str()); + pb_span.pb_inc(1); let properties = self.invoke_property_expressions(resource.properties.as_ref())?; let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); @@ -274,7 +282,6 @@ impl Configurator { result.metadata = Some( self.get_result_metadata(Operation::Get) ); - std::mem::drop(pb_span_enter); std::mem::drop(pb_span); Ok(result) } @@ -295,11 +302,11 @@ impl Configurator { pub fn invoke_set(&mut self, skip_test: bool) -> Result { let mut result = ConfigurationSetResult::new(); let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?; - let pb_span = get_progress_bar_span(resources.len() as u64)?; - let pb_span_enter = pb_span.enter(); + let mut pb_span = get_progress_bar_span(resources.len() as u64, self.progress_format)?; + pb_span.enter(); for resource in resources { - Span::current().pb_inc(1); pb_span.pb_set_message(format!("Set '{}'", resource.name).as_str()); + pb_span.pb_inc(1); let properties = self.invoke_property_expressions(resource.properties.as_ref())?; let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); @@ -386,7 +393,6 @@ impl Configurator { result.metadata = Some( self.get_result_metadata(Operation::Set) ); - mem::drop(pb_span_enter); mem::drop(pb_span); Ok(result) } @@ -403,11 +409,11 @@ impl Configurator { pub fn invoke_test(&mut self) -> Result { let mut result = ConfigurationTestResult::new(); let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?; - let pb_span = get_progress_bar_span(resources.len() as u64)?; - let pb_span_enter = pb_span.enter(); + let mut pb_span = get_progress_bar_span(resources.len() as u64, self.progress_format)?; + pb_span.enter(); for resource in resources { - Span::current().pb_inc(1); pb_span.pb_set_message(format!("Test '{}'", resource.name).as_str()); + pb_span.pb_inc(1); let properties = self.invoke_property_expressions(resource.properties.as_ref())?; let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type)); @@ -440,7 +446,6 @@ impl Configurator { result.metadata = Some( self.get_result_metadata(Operation::Test) ); - std::mem::drop(pb_span_enter); std::mem::drop(pb_span); Ok(result) } @@ -458,12 +463,12 @@ impl Configurator { let mut result = ConfigurationExportResult::new(); let mut conf = config_doc::Configuration::new(); - let pb_span = get_progress_bar_span(self.config.resources.len() as u64)?; - let pb_span_enter = pb_span.enter(); + let mut pb_span = get_progress_bar_span(self.config.resources.len() as u64, self.progress_format)?; + pb_span.enter(); let resources = self.config.resources.clone(); for resource in &resources { - Span::current().pb_inc(1); pb_span.pb_set_message(format!("Export '{}'", resource.name).as_str()); + pb_span.pb_inc(1); let properties = self.invoke_property_expressions(resource.properties.as_ref())?; let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else { return Err(DscError::ResourceNotFound(resource.resource_type.clone())); @@ -475,7 +480,6 @@ impl Configurator { conf.metadata = Some(self.get_result_metadata(Operation::Export)); result.result = Some(conf); - std::mem::drop(pb_span_enter); std::mem::drop(pb_span); Ok(result) } @@ -650,7 +654,7 @@ impl Configurator { // Perform discovery of resources used in config let required_resources = config.resources.iter().map(|p| p.resource_type.clone()).collect::>(); - self.discovery.find_resources(&required_resources); + self.discovery.find_resources(&required_resources, self.progress_format); self.config = config; Ok(()) } diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 060536e1..7162bedb 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -7,6 +7,7 @@ use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest}; use crate::dscresources::command_resource::invoke_command; use crate::dscerror::DscError; +use crate::util::ProgressFormat; use indicatif::ProgressStyle; use linked_hash_map::LinkedHashMap; use regex::RegexBuilder; @@ -23,7 +24,7 @@ use std::str::FromStr; use tracing::{debug, info, trace, warn, warn_span}; use tracing_indicatif::span_ext::IndicatifSpanExt; -use crate::util::get_setting; +use crate::util::{get_setting, ProgressBar}; use crate::util::get_exe_path; pub struct CommandDiscovery { @@ -245,7 +246,7 @@ impl ResourceDiscovery for CommandDiscovery { Ok(()) } - fn discover_adapted_resources(&mut self, name_filter: &str, adapter_filter: &str) -> Result<(), DscError> { + fn discover_adapted_resources(&mut self, name_filter: &str, adapter_filter: &str, progress_format: ProgressFormat) -> Result<(), DscError> { if self.resources.is_empty() && self.adapters.is_empty() { self.discover_resources("*")?; } @@ -270,30 +271,33 @@ impl ResourceDiscovery for CommandDiscovery { return Err(DscError::Operation("Could not build Regex filter for resource name".to_string())); }; - let pb_span = warn_span!(""); + let mut pb_span = ProgressBar::new(progress_format == ProgressFormat::Json); pb_span.pb_set_style(&ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}" )?); pb_span.pb_set_message("Searching for adapted resources"); - let _ = pb_span.enter(); + pb_span.pb_set_length(self.adapters.len() as u64); + pb_span.enter(); let mut adapted_resources = BTreeMap::>::new(); let mut found_adapter: bool = false; for (adapter_name, adapters) in &self.adapters { for adapter in adapters { + pb_span.pb_inc(1); + if !regex.is_match(adapter_name) { continue; } found_adapter = true; info!("Enumerating resources for adapter '{}'", adapter_name); - let pb_adapter_span = warn_span!(""); + let mut pb_adapter_span = ProgressBar::new(progress_format == ProgressFormat::Json); pb_adapter_span.pb_set_style(&ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise:.cyan}] {msg:.white}" )?); pb_adapter_span.pb_set_message(format!("Enumerating resources for adapter '{adapter_name}'").as_str()); - let _ = pb_adapter_span.enter(); + pb_adapter_span.enter(); let manifest = if let Some(manifest) = &adapter.manifest { if let Ok(manifest) = import_manifest(manifest.clone()) { manifest @@ -358,7 +362,7 @@ impl ResourceDiscovery for CommandDiscovery { Ok(()) } - fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str) -> Result>, DscError> { + fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Result>, DscError> { trace!("Listing resources with type_name_filter '{type_name_filter}' and adapter_name_filter '{adapter_name_filter}'"); let mut resources = BTreeMap::>::new(); @@ -369,7 +373,7 @@ impl ResourceDiscovery for CommandDiscovery { resources.append(&mut self.adapters); } else { self.discover_resources("*")?; - self.discover_adapted_resources(type_name_filter, adapter_name_filter)?; + self.discover_adapted_resources(type_name_filter, adapter_name_filter, progress_format)?; // add/update found adapted resources to the lookup_table add_resources_to_lookup_table(&self.adapted_resources); @@ -382,7 +386,7 @@ impl ResourceDiscovery for CommandDiscovery { } // TODO: handle version requirements - fn find_resources(&mut self, required_resource_types: &[String]) -> Result, DscError> + fn find_resources(&mut self, required_resource_types: &[String], progress_format: ProgressFormat) -> Result, DscError> { debug!("Searching for resources: {:?}", required_resource_types); self.discover_resources("*")?; @@ -434,7 +438,7 @@ impl ResourceDiscovery for CommandDiscovery { } } - self.discover_adapted_resources("*", &adapter_name)?; + self.discover_adapted_resources("*", &adapter_name, progress_format)?; // add/update found adapted resources to the lookup_table add_resources_to_lookup_table(&self.adapted_resources); diff --git a/dsc_lib/src/discovery/discovery_trait.rs b/dsc_lib/src/discovery/discovery_trait.rs index a6eb226f..96911e75 100644 --- a/dsc_lib/src/discovery/discovery_trait.rs +++ b/dsc_lib/src/discovery/discovery_trait.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{dscresources::dscresource::DscResource, dscerror::DscError}; +use crate::{dscresources::dscresource::DscResource, dscerror::DscError, util::ProgressFormat}; use std::collections::BTreeMap; pub trait ResourceDiscovery { fn discover_resources(&mut self, filter: &str) -> Result<(), DscError>; - fn discover_adapted_resources(&mut self, name_filter: &str, adapter_filter: &str) -> Result<(), DscError>; - fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str) -> Result>, DscError>; - fn find_resources(&mut self, required_resource_types: &[String]) -> Result, DscError>; + fn discover_adapted_resources(&mut self, name_filter: &str, adapter_filter: &str, progress_format: ProgressFormat) -> Result<(), DscError>; + fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Result>, DscError>; + fn find_resources(&mut self, required_resource_types: &[String], progress_format: ProgressFormat) -> Result, DscError>; } diff --git a/dsc_lib/src/discovery/mod.rs b/dsc_lib/src/discovery/mod.rs index 20921f7a..c216d809 100644 --- a/dsc_lib/src/discovery/mod.rs +++ b/dsc_lib/src/discovery/mod.rs @@ -5,7 +5,7 @@ mod command_discovery; mod discovery_trait; use crate::discovery::discovery_trait::ResourceDiscovery; -use crate::{dscresources::dscresource::DscResource, dscerror::DscError}; +use crate::{dscresources::dscresource::DscResource, dscerror::DscError, util::ProgressFormat}; use std::collections::BTreeMap; use tracing::error; @@ -36,7 +36,7 @@ impl Discovery { /// # Returns /// /// A vector of `DscResource` instances. - pub fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str) -> Vec { + pub fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Vec { let discovery_types: Vec> = vec![ Box::new(command_discovery::CommandDiscovery::new()), ]; @@ -45,7 +45,7 @@ impl Discovery { for mut discovery_type in discovery_types { - let discovered_resources = match discovery_type.list_available_resources(type_name_filter, adapter_name_filter) { + let discovered_resources = match discovery_type.list_available_resources(type_name_filter, adapter_name_filter, progress_format) { Ok(value) => value, Err(err) => { error!("{err}"); @@ -73,14 +73,14 @@ impl Discovery { /// # Arguments /// /// * `required_resource_types` - The required resource types. - pub fn find_resources(&mut self, required_resource_types: &[String]) { + pub fn find_resources(&mut self, required_resource_types: &[String], progress_format: ProgressFormat) { let discovery_types: Vec> = vec![ Box::new(command_discovery::CommandDiscovery::new()), ]; let mut remaining_required_resource_types = required_resource_types.to_owned(); for mut discovery_type in discovery_types { - let discovered_resources = match discovery_type.find_resources(&remaining_required_resource_types) { + let discovered_resources = match discovery_type.find_resources(&remaining_required_resource_types, progress_format) { Ok(value) => value, Err(err) => { error!("{err}"); diff --git a/dsc_lib/src/lib.rs b/dsc_lib/src/lib.rs index a01a29f2..21b22874 100644 --- a/dsc_lib/src/lib.rs +++ b/dsc_lib/src/lib.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::util::ProgressFormat; use configure::config_doc::ExecutionKind; use dscerror::DscError; use dscresources::{dscresource::{DscResource, Invoke}, invoke_result::{GetResult, SetResult, TestResult}}; @@ -41,12 +42,12 @@ impl DscManager { self.discovery.find_resource(name) } - pub fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str) -> Vec { - self.discovery.list_available_resources(type_name_filter, adapter_name_filter) + pub fn list_available_resources(&mut self, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Vec { + self.discovery.list_available_resources(type_name_filter, adapter_name_filter, progress_format) } - pub fn find_resources(&mut self, required_resource_types: &[String]) { - self.discovery.find_resources(required_resource_types); + pub fn find_resources(&mut self, required_resource_types: &[String], progress_format: ProgressFormat) { + self.discovery.find_resources(required_resource_types, progress_format); } /// Invoke the get operation on a resource. /// diff --git a/dsc_lib/src/util.rs b/dsc_lib/src/util.rs index 0ff323a9..1f70504f 100644 --- a/dsc_lib/src/util.rs +++ b/dsc_lib/src/util.rs @@ -2,7 +2,9 @@ // Licensed under the MIT License. use crate::dscerror::DscError; +use clap::ValueEnum; use serde_json::Value; +use serde::Serialize; use std::fs; use std::fs::File; use std::io::BufReader; @@ -10,6 +12,16 @@ use std::path::PathBuf; use std::path::Path; use std::env; use tracing::debug; +use tracing_indicatif::span_ext::IndicatifSpanExt; +use tracing::warn_span; +use tracing::span::Span; +use indicatif::ProgressStyle; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +pub enum ProgressFormat { + Default, + Json, +} pub struct DscSettingValue { pub setting: Value, @@ -25,6 +37,92 @@ impl Default for DscSettingValue { } } +/// Only `activity` and `percent_complete` fields are mandatory for Progress messages +#[derive(Default, Debug, Clone, Serialize)] +pub struct Progress { + pub activity: String, + pub percent_complete: u16, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seconds_remaining: Option, +} + +pub struct ProgressBar { + progress_value: Progress, + ui_bar: Span, //IndicatifSpanExt + length: u64, + position: u64, + emit_json: bool +} + +impl ProgressBar { + pub fn new(emit_json: bool) -> ProgressBar { + ProgressBar { + progress_value: Progress::default(), + ui_bar: warn_span!(""), + length: 0, + position: 0, + emit_json + } + } + + #[allow(clippy::cast_possible_truncation)] + pub fn pb_inc(&mut self, delta: u64) { + self.ui_bar.pb_inc(delta); + self.position += delta; + if self.length > 0 { + self.progress_value.percent_complete = if self.position >= self.length {100} + else { ((self.position * 100) / self.length) as u16}; + + self.emit_json(); + } + } + + pub fn pb_set_style(&mut self, style: &ProgressStyle) { + self.ui_bar.pb_set_style(style); + } + + pub fn pb_set_message(&mut self, msg: &str) { + self.ui_bar.pb_set_message(msg); + self.progress_value.activity = msg.to_string(); + self.emit_json(); + } + + #[allow(clippy::cast_possible_truncation)] + pub fn pb_set_length(&mut self, len: u64) { + self.ui_bar.pb_set_length(len); + self.length = len; + if self.length > 0 { + self.progress_value.percent_complete = if self.position >= self.length {100} + else { ((self.position * 100) / self.length) as u16}; + } + } + + #[allow(clippy::cast_possible_truncation)] + pub fn pb_set_position(&mut self, pos: u64) { + self.ui_bar.pb_set_position(pos); + self.position = pos; + if self.length > 0 { + self.progress_value.percent_complete = if self.position >= self.length {100} + else { ((self.position * 100) / self.length) as u16}; + self.emit_json(); + } + } + + pub fn enter(&self) { + _ = self.ui_bar.enter(); + } + + fn emit_json(&self) { + if self.emit_json { + if let Ok(json) = serde_json::to_string(&self.progress_value) { + eprintln!("{json}"); + } + } + } +} + /// Return JSON string whether the input is JSON or YAML /// /// # Arguments