diff --git a/dsc_lib/Cargo.toml b/dsc_lib/Cargo.toml index 02a4eee3..8cecb48c 100644 --- a/dsc_lib/Cargo.toml +++ b/dsc_lib/Cargo.toml @@ -12,3 +12,4 @@ schemars = { version = "0.8.12", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } thiserror = "1.0" +chrono = "0.4.26" diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index c89d8f32..617707de 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -5,7 +5,7 @@ use crate::discovery::discovery_trait::{ResourceDiscovery}; use crate::dscresources::dscresource::{DscResource, ImplementedAs}; use crate::dscresources::resource_manifest::ResourceManifest; use crate::dscresources::command_resource::invoke_command; -use crate::dscerror::DscError; +use crate::dscerror::{DscError, StreamMessage, StreamMessageType}; use std::collections::BTreeMap; use std::env; use std::fs::File; @@ -15,6 +15,7 @@ use std::path::Path; pub struct CommandDiscovery { pub resources: BTreeMap, provider_resources: Vec, + discovery_messages: Vec, // this will later be used to display only messages for resources used in specific operation initialized: bool, } @@ -23,6 +24,7 @@ impl CommandDiscovery { CommandDiscovery { resources: BTreeMap::new(), provider_resources: Vec::new(), + discovery_messages: Vec::new(), initialized: false, } } @@ -77,31 +79,51 @@ impl ResourceDiscovery for CommandDiscovery { // now go through the provider resources and add them to the list of resources for provider in &self.provider_resources { let provider_resource = self.resources.get(provider).unwrap(); + let provider_type_name = provider_resource.type_name.clone(); + let provider_path = provider_resource.path.clone(); let manifest = serde_json::from_value::(provider_resource.manifest.clone().unwrap())?; // invoke the list command let list_command = manifest.provider.unwrap().list; let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&provider_resource.directory)) { Ok((exit_code, stdout, stderr)) => (exit_code, stdout, stderr), - Err(_e) => { - //TODO: add to debug stream: println!("Could not start {}: {}", list_command.executable, e); + Err(e) => { + self.discovery_messages.push(StreamMessage::new_error( + format!("Could not start {}: {}", list_command.executable, e), + Some(provider_type_name.clone()), + Some(provider_path.clone()))); + continue; }, }; if exit_code != 0 { - return Err(DscError::Operation(format!("Failed to list resources for provider {provider}: {exit_code} {stderr}"))); + self.discovery_messages.push(StreamMessage::new_error( + format!("Provider failed to list resources with exit code {exit_code}: {stderr}"), + Some(provider_type_name.clone()), + Some(provider_path.clone()))); } + for line in stdout.lines() { match serde_json::from_str::(line){ Result::Ok(resource) => { if resource.requires.is_none() { - return Err(DscError::MissingRequires(provider.clone(), resource.type_name)); + self.discovery_messages.push(StreamMessage::new_error( + DscError::MissingRequires(provider.clone(), resource.type_name.clone()).to_string(), + Some(resource.type_name.clone()), + Some(resource.path.clone()))); + + continue; } self.resources.insert(resource.type_name.clone(), resource); }, Result::Err(err) => { - return Err(DscError::Operation(format!("Failed to parse resource from provider {provider}: {line} -> {err}"))); + self.discovery_messages.push(StreamMessage::new_error( + format!("Failed to parse resource: {line} -> {err}"), + Some(provider_type_name.clone()), + Some(provider_path.clone()))); + + continue; } }; } @@ -110,6 +132,14 @@ impl ResourceDiscovery for CommandDiscovery { self.initialized = true; Ok(()) } + + fn print_initialization_messages(&mut self, error_format:StreamMessageType, warning_format:StreamMessageType) -> Result<(), DscError>{ + for msg in &self.discovery_messages { + msg.print(&error_format, &warning_format)?; + } + + Ok(()) + } } fn import_manifest(path: &Path) -> Result { diff --git a/dsc_lib/src/discovery/discovery_trait.rs b/dsc_lib/src/discovery/discovery_trait.rs index ad772d72..78848dc1 100644 --- a/dsc_lib/src/discovery/discovery_trait.rs +++ b/dsc_lib/src/discovery/discovery_trait.rs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{dscresources::dscresource::DscResource, dscerror::DscError}; +use crate::{dscresources::dscresource::DscResource, dscerror::DscError, dscerror::StreamMessageType}; pub trait ResourceDiscovery { fn discover(&self) -> Box>; fn initialize(&mut self) -> Result<(), DscError>; + fn print_initialization_messages(&mut self, error_format:StreamMessageType, warning_format:StreamMessageType) -> Result<(), DscError>; } diff --git a/dsc_lib/src/discovery/mod.rs b/dsc_lib/src/discovery/mod.rs index 5de8fbde..a01f40e9 100644 --- a/dsc_lib/src/discovery/mod.rs +++ b/dsc_lib/src/discovery/mod.rs @@ -5,8 +5,7 @@ mod command_discovery; mod discovery_trait; use crate::discovery::discovery_trait::ResourceDiscovery; -use crate::dscerror::DscError; -use crate::dscresources::dscresource::{DscResource}; +use crate::{dscresources::dscresource::DscResource, dscerror::DscError, dscerror::StreamMessageType}; use regex::RegexBuilder; pub struct Discovery { @@ -43,6 +42,8 @@ impl Discovery { for mut discovery_type in discovery_types { discovery_type.initialize()?; + discovery_type.print_initialization_messages(StreamMessageType::Warning, StreamMessageType::Warning)?; + let discovered_resources = discovery_type.discover(); for resource in discovered_resources { resources.push(resource.clone()); diff --git a/dsc_lib/src/dscerror.rs b/dsc_lib/src/dscerror.rs index bed35c8d..50569f40 100644 --- a/dsc_lib/src/dscerror.rs +++ b/dsc_lib/src/dscerror.rs @@ -3,6 +3,7 @@ use reqwest::StatusCode; use thiserror::Error; +use chrono::{Local, DateTime}; #[derive(Error, Debug)] pub enum DscError { @@ -63,3 +64,70 @@ pub enum DscError { #[error("Validation: {0}")] Validation(String), } + +#[derive(Debug)] +#[derive(PartialEq)] +pub enum StreamMessageType { + None = 0, + Data = 1, + Error = 2, + Warning = 3, + Verbose = 4, + Custom = 5 +} + +pub struct StreamMessage { + pub message: String, + pub message_type: StreamMessageType, + pub time: DateTime, + pub resource_type_name: String, + pub resource_path: String +} + +impl Default for StreamMessage { + fn default() -> Self { + Self::new() + } +} + +impl StreamMessage { + pub fn new() -> Self { + Self { + message: String::new(), + message_type: StreamMessageType::None, + time: Local::now(), + resource_type_name: String::new(), + resource_path: String::new(), + } + } + pub fn new_error(message: String, resource_type_name: Option, resource_path: Option) -> StreamMessage { + StreamMessage { + message, + message_type: StreamMessageType::Error, + time: Local::now(), + resource_type_name: resource_type_name.unwrap_or("None".to_string()), + resource_path: resource_path.unwrap_or("None".to_string()) + } + } + pub fn new_warning(message: String, resource_type_name: Option, resource_path: Option) -> StreamMessage { + StreamMessage { + message, + message_type: StreamMessageType::Warning, + time: Local::now(), + resource_type_name: resource_type_name.unwrap_or("None".to_string()), + resource_path: resource_path.unwrap_or("None".to_string()) + } + } + pub fn print(&self, error_format:&StreamMessageType, warning_format:&StreamMessageType) -> Result<(), DscError>{ + if self.message_type == StreamMessageType::Error + { + eprintln!("{:?} -> {} {}", error_format, self.resource_type_name, self.message); + } + if self.message_type == StreamMessageType::Warning + { + eprintln!("{:?} -> {} {}", warning_format, self.resource_type_name, self.message); + } + + Ok(()) + } +} diff --git a/test_group_resource/tests/provider.tests.ps1 b/test_group_resource/tests/provider.tests.ps1 index 0d63741e..9bc9a3fb 100644 --- a/test_group_resource/tests/provider.tests.ps1 +++ b/test_group_resource/tests/provider.tests.ps1 @@ -56,7 +56,7 @@ Describe 'Resource provider tests' { $env:PATH += [System.IO.Path]::PathSeparator + (Resolve-Path (Resolve-Path $TestDrive -Relative)) $out = dsc resource list *invalid* 2>&1 - $LASTEXITCODE | Should -Be 2 + $LASTEXITCODE | Should -Be 0 ,$out | Should -Match ".*?'requires'*" } finally {