Skip to content

Commit 251fa6f

Browse files
authored
Merge pull request #323 from SteveL-MSFT/progress
add progress bar
2 parents 5aa5a96 + 117af98 commit 251fa6f

File tree

7 files changed

+77
-42
lines changed

7 files changed

+77
-42
lines changed

dsc/tests/dsc_export.tests.ps1

+2-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Describe 'resource export tests' {
5656
$set_results.results.count | Should -BeGreaterThan 1
5757
}
5858

59-
It 'Duplicate resource types in Configuration Export should result in error' {
59+
It 'Duplicate resource types in Configuration Export should not result in error' {
6060

6161
$yaml = @'
6262
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
@@ -71,8 +71,7 @@ Describe 'resource export tests' {
7171
pid: 0
7272
'@
7373
$out = $yaml | dsc config export 2>&1
74-
$LASTEXITCODE | Should -Be 2
75-
$out | out-string | Should -BeLike '*specified multiple times*'
74+
$LASTEXITCODE | Should -Be 0
7675
}
7776

7877
It 'Export can be called on individual resource with the use of --format as a subcommand' {

dsc_lib/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ edition = "2021"
55

66
[dependencies]
77
base64 = "0.21"
8-
derive_builder ="0.12"
8+
derive_builder ="0.20"
9+
indicatif = { version = "0.17" }
910
jsonschema = "0.17"
1011
regex = "1.7"
1112
reqwest = { version = "0.11", features = ["blocking"] }
@@ -16,7 +17,7 @@ serde_yaml = { version = "0.9.3" }
1617
thiserror = "1.0"
1718
chrono = "0.4.26"
1819
tracing = "0.1.37"
19-
tree-sitter = "~0.20.10"
20+
tree-sitter = "0.20"
2021
tree-sitter-dscexpression = { path = "../tree-sitter-dscexpression" }
2122

2223
[dev-dependencies]

dsc_lib/src/configure/mod.rs

+35-35
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ use self::config_doc::{Configuration, DataType};
1212
use self::depends_on::get_resource_invocation_order;
1313
use self::config_result::{ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult, ConfigurationExportResult};
1414
use self::contraints::{check_length, check_number_limits, check_allowed_values};
15+
use indicatif::{ProgressBar, ProgressStyle};
1516
use serde_json::{Map, Value};
16-
use std::collections::{HashMap, HashSet};
17+
use std::collections::HashMap;
18+
use std::time::Duration;
1719
use tracing::{debug, trace};
1820

1921
pub mod context;
@@ -131,6 +133,15 @@ fn escape_property_values(properties: &Map<String, Value>) -> Result<Option<Map<
131133
Ok(Some(result))
132134
}
133135

136+
fn get_progress_bar(len: u64) -> Result<ProgressBar, DscError> {
137+
let pb = ProgressBar::new(len);
138+
pb.enable_steady_tick(Duration::from_millis(120));
139+
pb.set_style(ProgressStyle::with_template(
140+
"{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}"
141+
)?);
142+
Ok(pb)
143+
}
144+
134145
impl Configurator {
135146
/// Create a new `Configurator` instance.
136147
///
@@ -164,8 +175,11 @@ impl Configurator {
164175
pub fn invoke_get(&mut self, _error_action: ErrorAction, _progress_callback: impl Fn() + 'static) -> Result<ConfigurationGetResult, DscError> {
165176
let config = self.validate_config()?;
166177
let mut result = ConfigurationGetResult::new();
167-
for resource in get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)? {
168-
trace!("Get resource '{}' named: {}", resource.resource_type, resource.name);
178+
let resources = get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)?;
179+
let pb = get_progress_bar(resources.len() as u64)?;
180+
for resource in resources {
181+
pb.inc(1);
182+
pb.set_message(format!("Get '{}'", resource.name));
169183
let properties = self.invoke_property_expressions(&resource.properties)?;
170184
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type.to_lowercase()) else {
171185
return Err(DscError::ResourceNotFound(resource.resource_type));
@@ -181,6 +195,7 @@ impl Configurator {
181195
result.results.push(resource_result);
182196
}
183197

198+
pb.finish_with_message("Get configuration completed");
184199
Ok(result)
185200
}
186201

@@ -197,7 +212,11 @@ impl Configurator {
197212
pub fn invoke_set(&mut self, skip_test: bool, _error_action: ErrorAction, _progress_callback: impl Fn() + 'static) -> Result<ConfigurationSetResult, DscError> {
198213
let config = self.validate_config()?;
199214
let mut result = ConfigurationSetResult::new();
200-
for resource in get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)? {
215+
let resources = get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)?;
216+
let pb = get_progress_bar(resources.len() as u64)?;
217+
for resource in resources {
218+
pb.inc(1);
219+
pb.set_message(format!("Set '{}'", resource.name));
201220
let properties = self.invoke_property_expressions(&resource.properties)?;
202221
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type.to_lowercase()) else {
203222
return Err(DscError::ResourceNotFound(resource.resource_type));
@@ -213,6 +232,7 @@ impl Configurator {
213232
result.results.push(resource_result);
214233
}
215234

235+
pb.finish_with_message("Set configuration completed");
216236
Ok(result)
217237
}
218238

@@ -229,7 +249,11 @@ impl Configurator {
229249
pub fn invoke_test(&mut self, _error_action: ErrorAction, _progress_callback: impl Fn() + 'static) -> Result<ConfigurationTestResult, DscError> {
230250
let config = self.validate_config()?;
231251
let mut result = ConfigurationTestResult::new();
232-
for resource in get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)? {
252+
let resources = get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)?;
253+
let pb = get_progress_bar(resources.len() as u64)?;
254+
for resource in resources {
255+
pb.inc(1);
256+
pb.set_message(format!("Test '{}'", resource.name));
233257
let properties = self.invoke_property_expressions(&resource.properties)?;
234258
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type.to_lowercase()) else {
235259
return Err(DscError::ResourceNotFound(resource.resource_type));
@@ -245,6 +269,7 @@ impl Configurator {
245269
result.results.push(resource_result);
246270
}
247271

272+
pb.finish_with_message("Test configuration completed");
248273
Ok(result)
249274
}
250275

@@ -265,17 +290,13 @@ impl Configurator {
265290
pub fn invoke_export(&mut self, _error_action: ErrorAction, _progress_callback: impl Fn() + 'static) -> Result<ConfigurationExportResult, DscError> {
266291
let config = self.validate_config()?;
267292

268-
let duplicates = Self::find_duplicate_resource_types(&config);
269-
if !duplicates.is_empty()
270-
{
271-
let duplicates_string = &duplicates.join(",");
272-
return Err(DscError::Validation(format!("Resource(s) {duplicates_string} specified multiple times")));
273-
}
274-
275293
let mut result = ConfigurationExportResult::new();
276294
let mut conf = config_doc::Configuration::new();
277295

278-
for resource in get_resource_invocation_order(&config, &mut self.statement_parser, &self.context)? {
296+
let pb = get_progress_bar(config.resources.len() as u64)?;
297+
for resource in &config.resources {
298+
pb.inc(1);
299+
pb.set_message(format!("Export '{}'", resource.name));
279300
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type.to_lowercase()) else {
280301
return Err(DscError::ResourceNotFound(resource.resource_type.clone()));
281302
};
@@ -285,7 +306,7 @@ impl Configurator {
285306
}
286307

287308
result.result = Some(conf);
288-
309+
pb.finish_with_message("Export configuration completed");
289310
Ok(result)
290311
}
291312

@@ -369,27 +390,6 @@ impl Configurator {
369390
Ok(())
370391
}
371392

372-
fn find_duplicate_resource_types(config: &Configuration) -> Vec<String>
373-
{
374-
let mut map: HashMap<&String, i32> = HashMap::new();
375-
let mut result: HashSet<String> = HashSet::new();
376-
let resource_list = &config.resources;
377-
if resource_list.is_empty() {
378-
return Vec::new();
379-
}
380-
381-
for r in resource_list
382-
{
383-
let v = map.entry(&r.resource_type).or_insert(0);
384-
*v += 1;
385-
if *v > 1 {
386-
result.insert(r.resource_type.clone());
387-
}
388-
}
389-
390-
result.into_iter().collect()
391-
}
392-
393393
fn validate_config(&mut self) -> Result<Configuration, DscError> {
394394
let config: Configuration = serde_json::from_str(self.config.as_str())?;
395395

dsc_lib/src/discovery/command_discovery.rs

+31
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ use crate::dscresources::dscresource::{DscResource, ImplementedAs};
66
use crate::dscresources::resource_manifest::{ResourceManifest, import_manifest};
77
use crate::dscresources::command_resource::invoke_command;
88
use crate::dscerror::DscError;
9+
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
910
use std::collections::BTreeMap;
1011
use std::env;
1112
use std::ffi::OsStr;
1213
use std::fs::File;
1314
use std::io::BufReader;
1415
use std::path::Path;
16+
use std::time::Duration;
1517
use tracing::{debug, error, warn};
1618

1719
pub struct CommandDiscovery {
@@ -28,6 +30,26 @@ impl CommandDiscovery {
2830
{
2931
let return_all_resources = required_resource_types.len() == 1 && required_resource_types[0] == "*";
3032

33+
let multi_progress_bar = MultiProgress::new();
34+
let pb = multi_progress_bar.add(
35+
if return_all_resources {
36+
let pb = ProgressBar::new(1);
37+
pb.enable_steady_tick(Duration::from_millis(120));
38+
pb.set_style(ProgressStyle::with_template(
39+
"{spinner:.green} [{elapsed_precise:.cyan}] {msg:.yellow}"
40+
)?);
41+
pb
42+
} else {
43+
let pb = ProgressBar::new(required_resource_types.len() as u64);
44+
pb.enable_steady_tick(Duration::from_millis(120));
45+
pb.set_style(ProgressStyle::with_template(
46+
"{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}"
47+
)?);
48+
pb
49+
}
50+
);
51+
pb.set_message("Searching for resources");
52+
3153
let mut resources: BTreeMap<String, DscResource> = BTreeMap::new();
3254
let mut provider_resources: Vec<String> = Vec::new();
3355
let mut remaining_required_resource_types = required_resource_types.to_owned();
@@ -88,6 +110,7 @@ impl CommandDiscovery {
88110
{
89111
remaining_required_resource_types.retain(|x| *x != resource.type_name.to_lowercase());
90112
debug!("Found {} in {}", &resource.type_name, path.display());
113+
pb.inc(1);
91114
resources.insert(resource.type_name.to_lowercase(), resource);
92115
if remaining_required_resource_types.is_empty()
93116
{
@@ -105,6 +128,12 @@ impl CommandDiscovery {
105128
// now go through the provider resources and add them to the list of resources
106129
for provider in provider_resources {
107130
debug!("Enumerating resources for provider {}", provider);
131+
let pb_adapter = multi_progress_bar.add(ProgressBar::new(1));
132+
pb_adapter.enable_steady_tick(Duration::from_millis(120));
133+
pb_adapter.set_style(ProgressStyle::with_template(
134+
"{spinner:.green} [{elapsed_precise:.cyan}] {msg:.white}"
135+
)?);
136+
pb_adapter.set_message(format!("Enumerating resources for adapter {provider}"));
108137
let provider_resource = resources.get(&provider).unwrap();
109138
let provider_type_name = provider_resource.type_name.clone();
110139
let manifest = import_manifest(provider_resource.manifest.clone().unwrap())?;
@@ -175,10 +204,12 @@ impl CommandDiscovery {
175204
}
176205
};
177206
}
207+
pb_adapter.finish_with_message(format!("Done with {provider}"));
178208

179209
debug!("Provider {} listed {} matching resources", provider_type_name, provider_resources_count);
180210
}
181211

212+
pb.finish_with_message("Discovery complete");
182213
Ok(resources)
183214
}
184215
}

dsc_lib/src/discovery/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl Discovery {
3636

3737
let mut regex: Option<Box<Regex>> = None;
3838
let mut resources: Vec<DscResource> = Vec::new();
39-
if !type_name_filter.is_empty()
39+
if !type_name_filter.is_empty()
4040
{
4141
let regex_str = convert_wildcard_to_regex(type_name_filter);
4242
let mut regex_builder = RegexBuilder::new(regex_str.as_str());

dsc_lib/src/dscerror.rs

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use std::str::Utf8Error;
55

6+
use indicatif::style::TemplateError;
67
use reqwest::StatusCode;
78
use thiserror::Error;
89
use tracing::error;
@@ -76,6 +77,9 @@ pub enum DscError {
7677
#[error("Parser: {0}")]
7778
Parser(String),
7879

80+
#[error("Progress: {0}")]
81+
Progress(#[from] TemplateError),
82+
7983
#[error("Resource not found: {0}")]
8084
ResourceNotFound(String),
8185

tree-sitter-dscexpression/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ include = [
2020
path = "bindings/rust/lib.rs"
2121

2222
[dependencies]
23-
tree-sitter = "~0.20.10"
23+
tree-sitter = "0.20"
2424

2525
[build-dependencies]
2626
cc = "1.0"

0 commit comments

Comments
 (0)