From 6d674bcf627f2545a29b71cce7923410240f4833 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 19 Sep 2023 16:28:16 -0700 Subject: [PATCH 1/3] change ManifestVersion to $schema and validate --- dsc/assertion.dsc.resource.json | 2 +- dsc/group.dsc.resource.json | 2 +- dsc/parallel.dsc.resource.json | 2 +- dsc/src/subcommand.rs | 4 +-- dsc_lib/src/discovery/command_discovery.rs | 12 ++++---- dsc_lib/src/dscerror.rs | 27 +++++++++-------- dsc_lib/src/dscresources/dscresource.rs | 15 +++++----- dsc_lib/src/dscresources/resource_manifest.rs | 29 +++++++++++++++++-- osinfo/osinfo.dsc.resource.json | 2 +- .../powershellgroup.dsc.resource.json | 2 +- process/process.dsc.resource.json | 2 +- registry/registry.dsc.resource.json | 2 +- .../testGroup.dsc.resource.json | 2 +- 13 files changed, 65 insertions(+), 38 deletions(-) diff --git a/dsc/assertion.dsc.resource.json b/dsc/assertion.dsc.resource.json index 1a8bec79..f95c1780 100644 --- a/dsc/assertion.dsc.resource.json +++ b/dsc/assertion.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "DSC/AssertionGroup", "version": "0.1.0", "description": "`test` will be invoked for all resources in the supplied configuration.", diff --git a/dsc/group.dsc.resource.json b/dsc/group.dsc.resource.json index d08d440c..5ecf8d7c 100644 --- a/dsc/group.dsc.resource.json +++ b/dsc/group.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "DSC/Group", "version": "0.1.0", "description": "All resources in the supplied configuration is treated as a group.", diff --git a/dsc/parallel.dsc.resource.json b/dsc/parallel.dsc.resource.json index 8ee87872..a74ac490 100644 --- a/dsc/parallel.dsc.resource.json +++ b/dsc/parallel.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "DSC/ParallelGroup", "version": "0.1.0", "description": "All resources in the supplied configuration run concurrently.", diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 2b8c1aa2..b5242691 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -11,7 +11,7 @@ use dsc_lib::{ configure::{Configurator, ErrorAction}, DscManager, dscresources::dscresource::{ImplementedAs, Invoke}, - dscresources::resource_manifest::ResourceManifest, + dscresources::resource_manifest::{import_manifest, ResourceManifest}, }; use jsonschema::{JSONSchema, ValidationError}; use serde_yaml::Value; @@ -321,7 +321,7 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option, let Some(ref resource_manifest) = resource.manifest else { continue; }; - let manifest = match serde_json::from_value::(resource_manifest.clone()) { + let manifest = match import_manifest(resource_manifest.clone()) { Ok(resource_manifest) => resource_manifest, Err(err) => { eprintln!("Error in manifest for {0}: {err}", resource.type_name); diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 5f22ac16..52fc17f9 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::discovery::discovery_trait::{ResourceDiscovery}; +use crate::discovery::discovery_trait::ResourceDiscovery; use crate::dscresources::dscresource::{DscResource, ImplementedAs}; -use crate::dscresources::resource_manifest::ResourceManifest; +use crate::dscresources::resource_manifest::{ResourceManifest, import_manifest}; use crate::dscresources::command_resource::invoke_command; use crate::dscerror::{DscError, StreamMessage, StreamMessageType}; use std::collections::BTreeMap; @@ -71,9 +71,9 @@ impl ResourceDiscovery for CommandDiscovery { if path.is_file() { let file_name = path.file_name().unwrap().to_str().unwrap(); if file_name.to_lowercase().ends_with(".dsc.resource.json") { - let resource = import_manifest(&path)?; + let resource = load_manifest(&path)?; if resource.manifest.is_some() { - let manifest = serde_json::from_value::(resource.manifest.clone().unwrap())?; + let manifest = import_manifest(resource.manifest.clone().unwrap())?; if manifest.provider.is_some() { self.provider_resources.push(resource.type_name.clone()); } @@ -90,7 +90,7 @@ impl ResourceDiscovery for CommandDiscovery { 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())?; + let manifest = import_manifest(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)) @@ -151,7 +151,7 @@ impl ResourceDiscovery for CommandDiscovery { } } -fn import_manifest(path: &Path) -> Result { +fn load_manifest(path: &Path) -> Result { let file = File::open(path)?; let reader = BufReader::new(file); let manifest: ResourceManifest = match serde_json::from_reader(reader) { diff --git a/dsc_lib/src/dscerror.rs b/dsc_lib/src/dscerror.rs index 73d35b14..10a59ddb 100644 --- a/dsc_lib/src/dscerror.rs +++ b/dsc_lib/src/dscerror.rs @@ -25,6 +25,9 @@ pub enum DscError { #[error("Invalid configuration:\n{0}")] InvalidConfiguration(String), + #[error("Unsupported manifest version: {0}. Must be: {1}")] + InvalidManifestSchemaVersion(String, String), + #[error("IO: {0}")] Io(#[from] std::io::Error), @@ -107,15 +110,15 @@ impl StreamMessage { } /// Create a new error message - /// + /// /// # Arguments - /// + /// /// * `message` - The message to display /// * `resource_type_name` - The name of the resource type /// * `resource_path` - The path to the resource - /// + /// /// # Returns - /// + /// /// * `StreamMessage` - The new message #[must_use] pub fn new_error(message: String, resource_type_name: Option, resource_path: Option) -> StreamMessage { @@ -129,15 +132,15 @@ impl StreamMessage { } /// Create a new warning message - /// + /// /// # Arguments - /// + /// /// * `message` - The message to display /// * `resource_type_name` - The name of the resource type /// * `resource_path` - The path to the resource - /// + /// /// # Returns - /// + /// /// * `StreamMessage` - The new message #[must_use] pub fn new_warning(message: String, resource_type_name: Option, resource_path: Option) -> StreamMessage { @@ -151,14 +154,14 @@ impl StreamMessage { } /// Print the message to the console - /// + /// /// # Arguments - /// + /// /// * `error_format` - The format to use for error messages /// * `warning_format` - The format to use for warning messages - /// + /// /// # Errors - /// + /// /// * `DscError` - If there is an error writing to the console pub fn print(&self, error_format:&StreamMessageType, warning_format:&StreamMessageType) -> Result<(), DscError>{ if self.message_type == StreamMessageType::Error diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 5aacadec..33672d0f 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -2,11 +2,10 @@ // Licensed under the MIT License. use dscerror::DscError; -use resource_manifest::ResourceManifest; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use super::{command_resource, dscerror, resource_manifest, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; +use super::{command_resource, dscerror, resource_manifest::import_manifest, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] @@ -140,7 +139,7 @@ impl Invoke for DscResource { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; command_resource::invoke_get(&resource_manifest, &self.directory, filter) }, } @@ -155,7 +154,7 @@ impl Invoke for DscResource { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; command_resource::invoke_set(&resource_manifest, &self.directory, desired, skip_test) }, } @@ -172,7 +171,7 @@ impl Invoke for DscResource { }; // if test is not directly implemented, then we need to handle it here - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; if resource_manifest.test.is_none() { let get_result = self.get(expected)?; let desired_state = serde_json::from_str(expected)?; @@ -201,7 +200,7 @@ impl Invoke for DscResource { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; command_resource::invoke_validate(&resource_manifest, &self.directory, config) }, } @@ -216,7 +215,7 @@ impl Invoke for DscResource { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; command_resource::get_schema(&resource_manifest, &self.directory) }, } @@ -231,7 +230,7 @@ impl Invoke for DscResource { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; - let resource_manifest = serde_json::from_value::(manifest.clone())?; + let resource_manifest = import_manifest(manifest.clone())?; command_resource::invoke_export(&resource_manifest, &self.directory) }, } diff --git a/dsc_lib/src/dscresources/resource_manifest.rs b/dsc_lib/src/dscresources/resource_manifest.rs index 1ee852d6..b85d940e 100644 --- a/dsc_lib/src/dscresources/resource_manifest.rs +++ b/dsc_lib/src/dscresources/resource_manifest.rs @@ -6,12 +6,14 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::dscerror::DscError; + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ResourceManifest { /// The version of the resource manifest schema. - #[serde(rename = "manifestVersion")] - pub manifest_version: String, + #[serde(rename = "$schema")] + pub schema_version: String, /// The namespaced name of the resource. #[serde(rename = "type")] pub resource_type: String, @@ -168,3 +170,26 @@ pub struct ListMethod { /// The arguments to pass to the command to perform a List. pub args: Option>, } + +/// Import a resource manifest from a JSON value. +/// +/// # Arguments +/// +/// * `manifest` - The JSON value to import. +/// +/// # Returns +/// +/// * `Result` - The imported resource manifest. +/// +/// # Errors +/// +/// * `DscError` - The JSON value is invalid or the schema version is not supported. +pub fn import_manifest(manifest: Value) -> Result { + const MANIFEST_SCHEMA_VERSION: &str = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json"; + let manifest = serde_json::from_value::(manifest)?; + if !manifest.schema_version.eq(MANIFEST_SCHEMA_VERSION) { + return Err(DscError::InvalidManifestSchemaVersion(manifest.schema_version, MANIFEST_SCHEMA_VERSION.to_string())); + } + + Ok(manifest) +} diff --git a/osinfo/osinfo.dsc.resource.json b/osinfo/osinfo.dsc.resource.json index 2e60b3fc..de2fda28 100644 --- a/osinfo/osinfo.dsc.resource.json +++ b/osinfo/osinfo.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "description": "Returns information about the operating system.", "tags": [ "os", diff --git a/powershellgroup/powershellgroup.dsc.resource.json b/powershellgroup/powershellgroup.dsc.resource.json index 340197ec..a07fec6e 100644 --- a/powershellgroup/powershellgroup.dsc.resource.json +++ b/powershellgroup/powershellgroup.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "DSC/PowerShellGroup", "version": "0.1.0", "description": "Resource provider to classic DSC Powershell resources.", diff --git a/process/process.dsc.resource.json b/process/process.dsc.resource.json index ec0bad4e..88c70a2e 100644 --- a/process/process.dsc.resource.json +++ b/process/process.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "description": "Returns information about running processes.", "type": "Microsoft/Process", "version": "0.1.0", diff --git a/registry/registry.dsc.resource.json b/registry/registry.dsc.resource.json index 7d44462d..74cec84c 100644 --- a/registry/registry.dsc.resource.json +++ b/registry/registry.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "Microsoft.Windows/Registry", "description": "Registry configuration provider for the Windows Registry", "tags": [ diff --git a/test_group_resource/testGroup.dsc.resource.json b/test_group_resource/testGroup.dsc.resource.json index 2f965de3..e674bb46 100644 --- a/test_group_resource/testGroup.dsc.resource.json +++ b/test_group_resource/testGroup.dsc.resource.json @@ -1,5 +1,5 @@ { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "Test/TestGroup", "version": "0.1.0", "get": { From 1cbb569d63b492ddb3048832d861fe45a6627ac2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 19 Sep 2023 17:02:38 -0700 Subject: [PATCH 2/3] update tests with $schema --- dsc/tests/dsc_discovery.tests.ps1 | 2 +- dsc/tests/dsc_set.tests.ps1 | 2 +- test_group_resource/tests/provider.tests.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsc/tests/dsc_discovery.tests.ps1 b/dsc/tests/dsc_discovery.tests.ps1 index 57e8b59e..bee5efdb 100644 --- a/dsc/tests/dsc_discovery.tests.ps1 +++ b/dsc/tests/dsc_discovery.tests.ps1 @@ -5,7 +5,7 @@ Describe 'tests for resource discovery' { It 'Use DSC_RESOURCE_PATH instead of PATH when defined' { $resourceJson = @' { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "DSC/TestPathResource", "version": "0.1.0", "get": { diff --git a/dsc/tests/dsc_set.tests.ps1 b/dsc/tests/dsc_set.tests.ps1 index 16ba4824..e9bde5ad 100644 --- a/dsc/tests/dsc_set.tests.ps1 +++ b/dsc/tests/dsc_set.tests.ps1 @@ -70,7 +70,7 @@ Describe 'config set tests' { It 'set can be used on a resource that does not implement test' { $manifest = @' { - "manifestVersion": "1.0.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "Test/SetNoTest", "version": "0.1.0", "get": { diff --git a/test_group_resource/tests/provider.tests.ps1 b/test_group_resource/tests/provider.tests.ps1 index 9bc9a3fb..bfcd0785 100644 --- a/test_group_resource/tests/provider.tests.ps1 +++ b/test_group_resource/tests/provider.tests.ps1 @@ -22,7 +22,7 @@ Describe 'Resource provider tests' { It 'Error if provider resource is missing "requires" member' { $invalid_manifest = @' { - "manifestVersion": "1.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "Test/InvalidTestGroup", "version": "0.1.0", "get": { From af2d284114c92847b8c7382580fa897f7dd2453f Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 21 Sep 2023 09:01:55 -0700 Subject: [PATCH 3/3] fix test to use $schema --- dsc/tests/dsc_resource_input.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/tests/dsc_resource_input.tests.ps1 b/dsc/tests/dsc_resource_input.tests.ps1 index 23a196b5..862b7441 100644 --- a/dsc/tests/dsc_resource_input.tests.ps1 +++ b/dsc/tests/dsc_resource_input.tests.ps1 @@ -5,7 +5,7 @@ Describe 'tests for resource input' { BeforeAll { $manifest = @' { - "manifestVersion": "1.0.0", + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", "type": "Test/EnvVarInput", "version": "0.1.0", "get": {