From 06a27e2457297b820c9988779500cc860684254d Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 8 Mar 2024 16:19:39 -0800 Subject: [PATCH 1/3] Add validation of resource output --- dsc_lib/src/dscresources/command_resource.rs | 23 ++++++++- dsc_lib/src/dscresources/dscresource.rs | 52 -------------------- 2 files changed, 22 insertions(+), 53 deletions(-) diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 001c280a..0b5371b5 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -6,7 +6,7 @@ use serde_json::Value; use std::{collections::HashMap, env, process::Command, io::{Write, Read}, process::Stdio}; use crate::{dscerror::DscError, dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse, ResourceTestResponse}}; use crate::configure::config_result::ResourceGetResult; -use super::{dscresource::get_diff,resource_manifest::{ResourceManifest, InputKind, ReturnKind, SchemaKind}, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; +use super::{dscresource::get_diff,resource_manifest::{Kind, ResourceManifest, InputKind, ReturnKind, SchemaKind}, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; use tracing::{error, warn, info, debug, trace}; pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; @@ -78,6 +78,11 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } + if resource.kind == Some(Kind::Resource) { + debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &resource.get.executable); + verify_json(resource, cwd, &stdout)?; + } + let result: GetResult = if let Ok(group_response) = serde_json::from_str::>(&stdout) { trace!("Group get response: {:?}", &group_response); GetResult::Group(group_response) @@ -179,6 +184,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } + if resource.kind == Some(Kind::Resource) { + debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &resource.get.executable); + verify_json(resource, cwd, &stdout)?; + } + let pre_state: Value = if exit_code == 0 { serde_json::from_str(&stdout)? } @@ -192,9 +202,15 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te if exit_code != 0 { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } + match set.returns { Some(ReturnKind::State) => { + if resource.kind == Some(Kind::Resource) { + debug!("Verifying output of set '{}' using '{}'", &resource.resource_type, &set.executable); + verify_json(resource, cwd, &stdout)?; + } + let actual_value: Value = match serde_json::from_str(&stdout){ Result::Ok(r) => {r}, Result::Err(err) => { @@ -293,6 +309,11 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } + if resource.kind == Some(Kind::Resource) { + debug!("Verifying output of test '{}' using '{}'", &resource.resource_type, &test.executable); + verify_json(resource, cwd, &stdout)?; + } + let expected_value: Value = serde_json::from_str(expected)?; match test.returns { Some(ReturnKind::State) => { diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 2ca9f990..1a588d76 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -7,7 +7,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -use tracing::{debug, trace}; use super::{command_resource, dscerror, invoke_result::{ExportResult, GetResult, ResourceTestResponse, SetResult, TestResult, ValidateResult}, resource_manifest::import_manifest}; @@ -77,54 +76,6 @@ impl DscResource { manifest: None, } } - - fn validate_input(&self, input: &str) -> Result<(), DscError> { - debug!("Validating input for resource: {}", &self.type_name); - if input.is_empty() { - return Ok(()); - } - let Some(manifest) = &self.manifest else { - return Err(DscError::MissingManifest(self.type_name.clone())); - }; - let resource_manifest = import_manifest(manifest.clone())?; - - if resource_manifest.validate.is_some() { - trace!("Using custom validation"); - let validation_result = match self.validate(input) { - Ok(validation_result) => validation_result, - Err(err) => { - return Err(DscError::Validation(format!("Validation failed: {err}"))); - }, - }; - trace!("Validation result is valid: {}", validation_result.valid); - if !validation_result.valid { - return Err(DscError::Validation("Validation failed".to_string())); - } - } - else { - trace!("Using JSON schema validation"); - let Ok(schema) = self.schema() else { - return Err(DscError::Validation("Schema not available".to_string())); - }; - - let schema = serde_json::from_str::(&schema)?; - - let Ok(compiled_schema) = jsonschema::JSONSchema::compile(&schema) else { - return Err(DscError::Validation("Schema compilation failed".to_string())); - }; - - let input = serde_json::from_str::(input)?; - if let Err(err) = compiled_schema.validate(&input) { - let mut error = format!("Resource '{}' failed validation: ", self.type_name); - for e in err { - error.push_str(&format!("\n{e} ")); - } - return Err(DscError::Validation(error)); - }; - } - - Ok(()) - } } impl Default for DscResource { @@ -201,7 +152,6 @@ pub trait Invoke { impl Invoke for DscResource { fn get(&self, filter: &str) -> Result { - self.validate_input(filter)?; match &self.implemented_as { ImplementedAs::Custom(_custom) => { Err(DscError::NotImplemented("get custom resources".to_string())) @@ -217,7 +167,6 @@ impl Invoke for DscResource { } fn set(&self, desired: &str, skip_test: bool) -> Result { - self.validate_input(desired)?; match &self.implemented_as { ImplementedAs::Custom(_custom) => { Err(DscError::NotImplemented("set custom resources".to_string())) @@ -233,7 +182,6 @@ impl Invoke for DscResource { } fn test(&self, expected: &str) -> Result { - self.validate_input(expected)?; match &self.implemented_as { ImplementedAs::Custom(_custom) => { Err(DscError::NotImplemented("test custom resources".to_string())) From f3f20c757e82fd83a10c4da789f5ec22409e49ff Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 12 Mar 2024 21:43:29 -0700 Subject: [PATCH 2/3] address Andrew's feedback adding verification of export output json --- dsc_lib/src/dscresources/command_resource.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 0b5371b5..8280abbc 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -483,6 +483,10 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> return Err(DscError::Operation(format!("Failed to parse json from export {}|{}|{} -> {err}", &export.executable, stdout, stderr))) } }; + if resource.kind == Some(Kind::Resource) { + debug!("Verifying output of export '{}' using '{}'", &resource.resource_type, &resource.get.executable); + verify_json(resource, cwd, &line)?; + } instances.push(instance); } From 72df61d584da19a29be26de7cdac130d48166575 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 13 Mar 2024 12:35:11 -0700 Subject: [PATCH 3/3] fix clippy --- dsc_lib/src/dscresources/command_resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 8280abbc..ab0cf756 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -485,7 +485,7 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> }; if resource.kind == Some(Kind::Resource) { debug!("Verifying output of export '{}' using '{}'", &resource.resource_type, &resource.get.executable); - verify_json(resource, cwd, &line)?; + verify_json(resource, cwd, line)?; } instances.push(instance); }