Skip to content

Commit 88acfd2

Browse files
committed
Removed percentComplete and added totalItems and completedItems, add failed property to progress
1 parent 646878c commit 88acfd2

File tree

3 files changed

+151
-43
lines changed

3 files changed

+151
-43
lines changed

dsc/tests/dsc_config_get.tests.ps1

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ Describe 'dsc config get tests' {
7979
$jp = $line | ConvertFrom-Json
8080
if ($jp.activity) { # if line is a progress message
8181
$jp.id | Should -Not -BeNullOrEmpty
82-
$jp.percentComplete | Should -BeIn (0..100)
82+
$jp.totalItems | Should -Not -BeNullOrEmpty
83+
$jp.completedItems | Should -Not -BeNullOrEmpty
8384
$ProgressMessagesFound = $true
8485
}
8586

@@ -98,6 +99,53 @@ Describe 'dsc config get tests' {
9899
$InstanceTwoFound | Should -BeTrue
99100
}
100101

102+
It 'json progress returns correctly for failed resource' {
103+
$config_yaml = @'
104+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
105+
resources:
106+
- name: Echo 1
107+
type: Microsoft.DSC.Debug/Echo
108+
properties:
109+
output: hello
110+
- name: ErrorTest
111+
type: Test/ExitCode
112+
properties:
113+
exitCode: 1
114+
'@
115+
dsc --progress-format json --trace-format json config get -i $config_yaml 2> $TestDrive/ErrorStream.txt
116+
$LASTEXITCODE | Should -Be 2
117+
$lines = Get-Content $TestDrive/ErrorStream.txt
118+
$ProgressMessagesFound = $false
119+
$InstanceOneFound = $false
120+
$InstanceTwoFound = $false
121+
foreach ($line in $lines) {
122+
$jp = $line | ConvertFrom-Json
123+
if ($jp.activity) { # if line is a progress message
124+
$jp.id | Should -Not -BeNullOrEmpty
125+
$jp.totalItems | Should -Not -BeNullOrEmpty
126+
$jp.completedItems | Should -Not -BeNullOrEmpty
127+
$ProgressMessagesFound = $true
128+
}
129+
130+
if ($null -ne $jp.result -and $jp.resourceType -eq 'Microsoft.DSC.Debug/Echo') {
131+
if ($jp.resourceName -eq 'Echo 1') {
132+
$InstanceOneFound = $true
133+
$jp.result.actualState.output | Should -BeExactly 'hello'
134+
$jp.failed | Should -BeNullOrEmpty
135+
}
136+
}
137+
elseif ($null -ne $jp.failed -and $jp.resourceType -eq 'Test/ExitCode') {
138+
if ($jp.resourceName -eq 'ErrorTest') {
139+
$InstanceTwoFound = $true
140+
$jp.result | Should -BeNullOrEmpty
141+
}
142+
}
143+
}
144+
$ProgressMessagesFound | Should -BeTrue
145+
$InstanceOneFound | Should -BeTrue
146+
$InstanceTwoFound | Should -BeTrue
147+
}
148+
101149
It 'contentVersion is ignored' {
102150
$config_yaml = @"
103151
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json

dsc_lib/src/configure/mod.rs

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ impl Configurator {
232232
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
233233
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
234234
for resource in resources {
235-
progress.set_resource(&resource.name, &resource.resource_type, None);
235+
progress.set_resource(&resource.name, &resource.resource_type);
236236
progress.write_activity(format!("Get '{}'", resource.name).as_str());
237237
let properties = self.invoke_property_expressions(resource.properties.as_ref())?;
238238
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else {
@@ -242,7 +242,14 @@ impl Configurator {
242242
let filter = add_metadata(&dsc_resource.kind, properties)?;
243243
trace!("filter: {filter}");
244244
let start_datetime = chrono::Local::now();
245-
let get_result = dsc_resource.get(&filter)?;
245+
let get_result = match dsc_resource.get(&filter) {
246+
Ok(result) => result,
247+
Err(e) => {
248+
progress.set_failed();
249+
progress.write_increment(1);
250+
return Err(e);
251+
},
252+
};
246253
let end_datetime = chrono::Local::now();
247254
match &get_result {
248255
GetResult::Resource(resource_result) => {
@@ -272,7 +279,7 @@ impl Configurator {
272279
result: get_result.clone(),
273280
};
274281
result.results.push(resource_result);
275-
progress.set_resource(&resource.name, &resource.resource_type, Some(&serde_json::to_value(get_result)?));
282+
progress.set_result(&serde_json::to_value(get_result)?);
276283
progress.write_increment(1);
277284
}
278285

@@ -295,12 +302,13 @@ impl Configurator {
295302
/// # Errors
296303
///
297304
/// This function will return an error if the underlying resource fails.
305+
#[allow(clippy::too_many_lines)]
298306
pub fn invoke_set(&mut self, skip_test: bool) -> Result<ConfigurationSetResult, DscError> {
299307
let mut result = ConfigurationSetResult::new();
300308
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
301309
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
302310
for resource in resources {
303-
progress.set_resource(&resource.name, &resource.resource_type, None);
311+
progress.set_resource(&resource.name, &resource.resource_type);
304312
progress.write_activity(format!("Set '{}'", resource.name).as_str());
305313
let properties = self.invoke_property_expressions(resource.properties.as_ref())?;
306314
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else {
@@ -331,18 +339,43 @@ impl Configurator {
331339
if exist || dsc_resource.capabilities.contains(&Capability::SetHandlesExist) {
332340
debug!("{}", t!("configure.mod.handlesExist"));
333341
start_datetime = chrono::Local::now();
334-
set_result = dsc_resource.set(&desired, skip_test, &self.context.execution_type)?;
342+
set_result = match dsc_resource.set(&desired, skip_test, &self.context.execution_type) {
343+
Ok(result) => result,
344+
Err(e) => {
345+
progress.set_failed();
346+
progress.write_increment(1);
347+
return Err(e);
348+
},
349+
};
335350
end_datetime = chrono::Local::now();
336351
} else if dsc_resource.capabilities.contains(&Capability::Delete) {
337352
if self.context.execution_type == ExecutionKind::WhatIf {
338353
// TODO: add delete what-if support
339354
return Err(DscError::NotSupported(t!("configure.mod.whatIfNotSupportedForDelete").to_string()));
340355
}
341356
debug!("{}", t!("configure.mod.implementsDelete"));
342-
let before_result = dsc_resource.get(&desired)?;
357+
let before_result = match dsc_resource.get(&desired) {
358+
Ok(result) => result,
359+
Err(e) => {
360+
progress.set_failed();
361+
progress.write_increment(1);
362+
return Err(e);
363+
},
364+
};
343365
start_datetime = chrono::Local::now();
344-
dsc_resource.delete(&desired)?;
345-
let after_result = dsc_resource.get(&desired)?;
366+
if let Err(err) = dsc_resource.delete(&desired) {
367+
progress.set_failed();
368+
progress.write_increment(1);
369+
return Err(err);
370+
}
371+
let after_result = match dsc_resource.get(&desired) {
372+
Ok(result) => result,
373+
Err(e) => {
374+
progress.set_failed();
375+
progress.write_increment(1);
376+
return Err(e);
377+
},
378+
};
346379
// convert get result to set result
347380
set_result = match before_result {
348381
GetResult::Resource(before_response) => {
@@ -394,7 +427,7 @@ impl Configurator {
394427
result: set_result.clone(),
395428
};
396429
result.results.push(resource_result);
397-
progress.set_resource(&resource.name, &resource.resource_type, Some(&serde_json::to_value(set_result)?));
430+
progress.set_result(&serde_json::to_value(set_result)?);
398431
progress.write_increment(1);
399432
}
400433

@@ -418,7 +451,7 @@ impl Configurator {
418451
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
419452
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
420453
for resource in resources {
421-
progress.set_resource(&resource.name, &resource.resource_type, None);
454+
progress.set_resource(&resource.name, &resource.resource_type);
422455
progress.write_activity(format!("Test '{}'", resource.name).as_str());
423456
let properties = self.invoke_property_expressions(resource.properties.as_ref())?;
424457
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else {
@@ -428,7 +461,14 @@ impl Configurator {
428461
let expected = add_metadata(&dsc_resource.kind, properties)?;
429462
trace!("{}", t!("configure.mod.expectedState", state = expected));
430463
let start_datetime = chrono::Local::now();
431-
let test_result = dsc_resource.test(&expected)?;
464+
let test_result = match dsc_resource.test(&expected) {
465+
Ok(result) => result,
466+
Err(e) => {
467+
progress.set_failed();
468+
progress.write_increment(1);
469+
return Err(e);
470+
},
471+
};
432472
let end_datetime = chrono::Local::now();
433473
match &test_result {
434474
TestResult::Resource(resource_test_result) => {
@@ -458,7 +498,7 @@ impl Configurator {
458498
result: test_result.clone(),
459499
};
460500
result.results.push(resource_result);
461-
progress.set_resource(&resource.name, &resource.resource_type, Some(&serde_json::to_value(test_result)?));
501+
progress.set_result( &serde_json::to_value(test_result)?);
462502
progress.write_increment(1);
463503
}
464504

@@ -484,17 +524,24 @@ impl Configurator {
484524
let mut progress = ProgressBar::new(self.config.resources.len() as u64, self.progress_format)?;
485525
let resources = self.config.resources.clone();
486526
for resource in &resources {
487-
progress.set_resource(&resource.name, &resource.resource_type, None);
527+
progress.set_resource(&resource.name, &resource.resource_type);
488528
progress.write_activity(format!("Export '{}'", resource.name).as_str());
489529
let properties = self.invoke_property_expressions(resource.properties.as_ref())?;
490530
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type) else {
491531
return Err(DscError::ResourceNotFound(resource.resource_type.clone()));
492532
};
493533
let input = add_metadata(&dsc_resource.kind, properties)?;
494534
trace!("{}", t!("configure.mod.exportInput", input = input));
495-
let export_result = add_resource_export_results_to_configuration(dsc_resource, Some(dsc_resource), &mut conf, input.as_str())?;
535+
let export_result = match add_resource_export_results_to_configuration(dsc_resource, Some(dsc_resource), &mut conf, input.as_str()) {
536+
Ok(result) => result,
537+
Err(e) => {
538+
progress.set_failed();
539+
progress.write_increment(1);
540+
return Err(e);
541+
},
542+
};
496543
self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&export_result.actual_state)?);
497-
progress.set_resource(&resource.name, &resource.resource_type, Some(&serde_json::to_value(export_result)?));
544+
progress.set_result(&serde_json::to_value(export_result)?);
498545
progress.write_increment(1);
499546
}
500547

dsc_lib/src/progress.rs

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,42 @@ pub enum ProgressFormat {
2424
}
2525

2626
#[derive(Default, Debug, Clone, Serialize)]
27+
#[serde(rename_all = "camelCase")]
2728
pub struct Progress {
2829
/// The unique identifier for the operation.
2930
pub id: String,
3031
/// The activity being performed.
3132
pub activity: Option<String>,
32-
/// The percentage of the operation that is complete from 0 to 100.
33-
#[serde(rename = "percentComplete")]
34-
pub percent_complete: u8,
33+
/// The total number of items to process.
34+
pub total_items: u64,
35+
/// The number of items processed.
36+
pub completed_items: u64,
3537
/// The status of the operation.
3638
#[serde(skip_serializing_if = "Option::is_none")]
3739
pub status: Option<String>,
3840
/// The number of seconds remaining in the operation.
39-
#[serde(rename = "secondsRemaining", skip_serializing_if = "Option::is_none")]
41+
#[serde(skip_serializing_if = "Option::is_none")]
4042
pub seconds_remaining: Option<u64>,
4143
/// The name of the resource being operated on.
42-
#[serde(rename = "resourceName", skip_serializing_if = "Option::is_none")]
44+
#[serde(skip_serializing_if = "Option::is_none")]
4345
pub resource_name: Option<String>,
4446
/// The type of the resource being operated on.
45-
#[serde(rename = "resourceType", skip_serializing_if = "Option::is_none")]
47+
#[serde(skip_serializing_if = "Option::is_none")]
4648
pub resource_type: Option<String>,
4749
/// The result of the operation.
4850
#[serde(skip_serializing_if = "Option::is_none")]
4951
pub result: Option<Value>,
52+
/// Indicates if the operation failed.
53+
#[serde(skip_serializing_if = "Option::is_none")]
54+
pub failed: Option<bool>,
5055
}
5156

5257
impl Progress {
5358
#[must_use]
54-
pub fn new() -> Progress {
59+
pub fn new(total_items: u64) -> Progress {
5560
Progress {
5661
id: Uuid::new_v4().to_string(),
62+
total_items,
5763
..Default::default()
5864
}
5965
}
@@ -62,8 +68,6 @@ impl Progress {
6268
pub struct ProgressBar {
6369
progress_value: Progress,
6470
console_bar: Span,
65-
item_count: u64,
66-
item_position: u64,
6771
format: ProgressFormat
6872
}
6973

@@ -84,21 +88,19 @@ impl ProgressBar {
8488
///
8589
/// Fails if progress style for console rendering can't be set.
8690
///
87-
pub fn new(item_count: u64, format: ProgressFormat) -> Result<ProgressBar, DscError> {
91+
pub fn new(total_items: u64, format: ProgressFormat) -> Result<ProgressBar, DscError> {
8892
let bar = warn_span!("");
8993
if format == ProgressFormat::Default {
9094
bar.pb_set_style(&ProgressStyle::with_template(
9195
"{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}"
9296
)?);
93-
bar.pb_set_length(item_count);
97+
bar.pb_set_length(total_items);
9498
let _guard = bar.enter();
9599
}
96100

97101
Ok(ProgressBar {
98-
progress_value: Progress::new(),
102+
progress_value: Progress::new(total_items),
99103
console_bar: bar,
100-
item_count,
101-
item_position: 0,
102104
format
103105
})
104106
}
@@ -114,7 +116,7 @@ impl ProgressBar {
114116
return;
115117
}
116118

117-
self.item_position += delta;
119+
self.progress_value.completed_items += delta;
118120
if self.format == ProgressFormat::Json {
119121
self.write_json();
120122
} else {
@@ -130,10 +132,29 @@ impl ProgressBar {
130132
/// * `resource_type` - The type of the resource being operated on
131133
/// * `result` - The result of the operation
132134
///
133-
pub fn set_resource(&mut self, name: &str, resource_type: &str, result: Option<&Value>) {
135+
pub fn set_resource(&mut self, name: &str, resource_type: &str) {
134136
self.progress_value.resource_name = Some(name.to_string());
135137
self.progress_value.resource_type = Some(resource_type.to_string());
136-
self.progress_value.result = result.cloned();
138+
self.progress_value.result = None;
139+
self.progress_value.failed = None;
140+
}
141+
142+
/// Set the result of the operation. This will clear any error.
143+
///
144+
/// # Arguments
145+
///
146+
/// * `result` - The result of the operation
147+
///
148+
pub fn set_result(&mut self, result: &Value) {
149+
self.progress_value.failed = None;
150+
self.progress_value.result = Some(result.clone());
151+
}
152+
153+
/// Indicate that the operation failed. This will clear any result.
154+
///
155+
pub fn set_failed(&mut self) {
156+
self.progress_value.result = None;
157+
self.progress_value.failed = Some(true);
137158
}
138159

139160
/// Set the status of the operation and write the progress
@@ -161,10 +182,10 @@ impl ProgressBar {
161182
///
162183
/// * `len` - The number of total items to complete
163184
///
164-
pub fn set_length(&mut self, len: u64) {
185+
pub fn set_total_items(&mut self, len: u64) {
165186
match self.format {
166187
ProgressFormat::Json => {
167-
self.item_count = len;
188+
self.progress_value.total_items = len;
168189
},
169190
ProgressFormat::Default => {
170191
self.console_bar.pb_set_length(len);
@@ -174,14 +195,6 @@ impl ProgressBar {
174195
}
175196

176197
fn write_json(&mut self) {
177-
if self.item_count > 0 {
178-
self.progress_value.percent_complete = if self.item_position >= self.item_count {
179-
100
180-
} else {
181-
u8::try_from((self.item_position * 100) / self.item_count).unwrap_or(100)
182-
};
183-
}
184-
185198
if let Ok(json) = serde_json::to_string(&self.progress_value) {
186199
eprintln!("{json}");
187200
} else {

0 commit comments

Comments
 (0)