From f12357f006c33767fb6849580d52bf313c6acc44 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 15 Oct 2025 17:36:03 -0700 Subject: [PATCH 01/10] Enable a single file to contain multiple resource manifests --- build.ps1 | 4 +- .../src/discovery/command_discovery.rs | 113 ++- lib/dsc-lib/src/dscerror.rs | 3 + .../src/dscresources/resource_manifest.rs | 6 + lib/dsc-lib/src/extensions/discover.rs | 6 +- tools/dsctest/dscdelete.dsc.resource.json | 35 - tools/dsctest/dscexist.dsc.resource.json | 37 - tools/dsctest/dscexitcode.dsc.resource.json | 28 - tools/dsctest/dscexport.dsc.resource.json | 25 - tools/dsctest/dscexporter.dsc.resource.json | 26 - tools/dsctest/dscget.dsc.resource.json | 25 - .../dscindesiredstate.dsc.resource.json | 26 - tools/dsctest/dscoperation.dsc.resource.json | 70 -- tools/dsctest/dscsleep.dsc.resource.json | 45 - tools/dsctest/dsctest.dsc.resourcelist.json | 771 ++++++++++++++++++ tools/dsctest/dsctrace.dsc.resource.json | 33 - tools/dsctest/dscwhatif.dsc.resource.json | 36 - tools/dsctest/metadata.dsc.resource.json | 57 -- .../dsctest/resourceadapter.dsc.resource.json | 96 --- tools/dsctest/version1.1.2.dsc.resource.json | 38 - tools/dsctest/version1.1.3.dsc.resource.json | 38 - tools/dsctest/version1.1.dsc.resource.json | 38 - tools/dsctest/version2.1p1.dsc.resource.json | 38 - tools/dsctest/version2.1p2.dsc.resource.json | 38 - tools/dsctest/version2.dsc.resource.json | 38 - 25 files changed, 842 insertions(+), 828 deletions(-) delete mode 100644 tools/dsctest/dscdelete.dsc.resource.json delete mode 100644 tools/dsctest/dscexist.dsc.resource.json delete mode 100644 tools/dsctest/dscexitcode.dsc.resource.json delete mode 100644 tools/dsctest/dscexport.dsc.resource.json delete mode 100644 tools/dsctest/dscexporter.dsc.resource.json delete mode 100644 tools/dsctest/dscget.dsc.resource.json delete mode 100644 tools/dsctest/dscindesiredstate.dsc.resource.json delete mode 100644 tools/dsctest/dscoperation.dsc.resource.json delete mode 100644 tools/dsctest/dscsleep.dsc.resource.json create mode 100644 tools/dsctest/dsctest.dsc.resourcelist.json delete mode 100644 tools/dsctest/dsctrace.dsc.resource.json delete mode 100644 tools/dsctest/dscwhatif.dsc.resource.json delete mode 100644 tools/dsctest/metadata.dsc.resource.json delete mode 100644 tools/dsctest/resourceadapter.dsc.resource.json delete mode 100644 tools/dsctest/version1.1.2.dsc.resource.json delete mode 100644 tools/dsctest/version1.1.3.dsc.resource.json delete mode 100644 tools/dsctest/version1.1.dsc.resource.json delete mode 100644 tools/dsctest/version2.1p1.dsc.resource.json delete mode 100644 tools/dsctest/version2.1p2.dsc.resource.json delete mode 100644 tools/dsctest/version2.dsc.resource.json diff --git a/build.ps1 b/build.ps1 index bb22c7420..2a8a0ec12 100755 --- a/build.ps1 +++ b/build.ps1 @@ -499,11 +499,11 @@ if (!$SkipBuild) { } if ($IsWindows) { - Copy-Item "*.dsc.resource.json" $target -Force -ErrorAction Ignore + Copy-Item "*.dsc.resource.*","*.dsc.resourcelist.*" $target -Force -ErrorAction Ignore } else { # don't copy WindowsPowerShell resource manifest $exclude = @('windowspowershell.dsc.resource.json', 'winpsscript.dsc.resource.json') - Copy-Item "*.dsc.resource.json" $target -Exclude $exclude -Force -ErrorAction Ignore + Copy-Item "*.dsc.resource.*","*.dsc.resourcelist.*" $target -Exclude $exclude -Force -ErrorAction Ignore } # be sure that the files that should be executable are executable diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 221a5a6a0..de1ad7315 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::discovery::{ - discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery} -}; +use crate::{discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery}}; use crate::{locked_is_empty, locked_extend, locked_clone, locked_get}; use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; -use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, SchemaKind}; +use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, ResourceManifestList, SchemaKind}; use crate::dscresources::command_resource::invoke_command; use crate::dscerror::DscError; use crate::extensions::dscextension::{self, DscExtension, Capability as ExtensionCapability}; @@ -32,6 +30,7 @@ use crate::util::get_setting; use crate::util::get_exe_path; const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; +const DSC_RESOURCE_LIST_EXTENSIONS: [&str; 3] = ["dsc.resourcelist.json", "dsc.resourcelist.yaml", ".dsc.resourcelist.yml"]; const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"]; // use BTreeMap so that the results are sorted by the typename, the Vec is sorted by version @@ -246,9 +245,10 @@ impl ResourceDiscovery for CommandDiscovery { }; let file_name_lowercase = file_name.to_lowercase(); if (kind == &DiscoveryKind::Resource && DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || + (kind == &DiscoveryKind::Resource && DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || (kind == &DiscoveryKind::Extension && DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) { trace!("{}", t!("discovery.commandDiscovery.foundResourceManifest", path = path.to_string_lossy())); - let resource = match load_manifest(&path) + let imported_manifests = match load_manifest(&path) { Ok(r) => r, Err(e) => { @@ -261,37 +261,39 @@ impl ResourceDiscovery for CommandDiscovery { }, }; - match resource { - ImportedManifest::Extension(extension) => { - if regex.is_match(&extension.type_name) { - trace!("{}", t!("discovery.commandDiscovery.extensionFound", extension = extension.type_name, version = extension.version)); - // we only keep newest version of the extension so compare the version and only keep the newest - if let Some(existing_extension) = extensions.get_mut(&extension.type_name) { - let Ok(existing_version) = Version::parse(&existing_extension.version) else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = existing_extension.type_name, version = existing_extension.version).to_string())); - }; - let Ok(new_version) = Version::parse(&extension.version) else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = extension.type_name, version = extension.version).to_string())); - }; - if new_version > existing_version { + for imported_manifest in imported_manifests { + match imported_manifest { + ImportedManifest::Extension(extension) => { + if regex.is_match(&extension.type_name) { + trace!("{}", t!("discovery.commandDiscovery.extensionFound", extension = extension.type_name, version = extension.version)); + // we only keep newest version of the extension so compare the version and only keep the newest + if let Some(existing_extension) = extensions.get_mut(&extension.type_name) { + let Ok(existing_version) = Version::parse(&existing_extension.version) else { + return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = existing_extension.type_name, version = existing_extension.version).to_string())); + }; + let Ok(new_version) = Version::parse(&extension.version) else { + return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = extension.type_name, version = extension.version).to_string())); + }; + if new_version > existing_version { + extensions.insert(extension.type_name.clone(), extension.clone()); + } + } else { extensions.insert(extension.type_name.clone(), extension.clone()); } - } else { - extensions.insert(extension.type_name.clone(), extension.clone()); } - } - }, - ImportedManifest::Resource(resource) => { - if regex.is_match(&resource.type_name) { - if let Some(ref manifest) = resource.manifest { - let manifest = import_manifest(manifest.clone())?; - if manifest.kind == Some(Kind::Adapter) { - trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name, version = resource.version)); - insert_resource(&mut adapters, &resource); + }, + ImportedManifest::Resource(resource) => { + if regex.is_match(&resource.type_name) { + if let Some(ref manifest) = resource.manifest { + let manifest = import_manifest(manifest.clone())?; + if manifest.kind == Some(Kind::Adapter) { + trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name, version = resource.version)); + insert_resource(&mut adapters, &resource); + } + // also make sure to add adapters as a resource as well + trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name, version = resource.version)); + insert_resource(&mut resources, &resource); } - // also make sure to add adapters as a resource as well - trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name, version = resource.version)); - insert_resource(&mut resources, &resource); } } } @@ -595,7 +597,7 @@ fn insert_resource(resources: &mut BTreeMap>, resource: } } -/// Loads a manifest from the given path and returns a `ManifestResource`. +/// Loads a manifest from the given path and returns a vector of `ImportedManifest`. /// /// # Arguments /// @@ -603,40 +605,37 @@ fn insert_resource(resources: &mut BTreeMap>, resource: /// /// # Returns /// -/// * `ManifestResource` if the manifest was loaded successfully. +/// * `Vec` if the manifest was loaded successfully. /// /// # Errors /// /// * Returns a `DscError` if the manifest could not be loaded or parsed. -pub fn load_manifest(path: &Path) -> Result { +pub fn load_manifest(path: &Path) -> Result, DscError> { let contents = fs::read_to_string(path)?; - if path.extension() == Some(OsStr::new("json")) { + let file_name_lowercase = path.file_name().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); + if DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { + if let Ok(manifest) = serde_json::from_str::(&contents) { + let resource = load_resource_manifest(path, &manifest)?; + return Ok(vec![ImportedManifest::Resource(resource)]); + } + } + if DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { if let Ok(manifest) = serde_json::from_str::(&contents) { let extension = load_extension_manifest(path, &manifest)?; - return Ok(ImportedManifest::Extension(extension)); + return Ok(vec![ImportedManifest::Extension(extension)]); } - let manifest = match serde_json::from_str::(&contents) { - Ok(manifest) => manifest, - Err(err) => { - return Err(DscError::Manifest(t!("discovery.commandDiscovery.invalidManifest", resource = path.to_string_lossy()).to_string(), err)); - } - }; - let resource = load_resource_manifest(path, &manifest)?; - return Ok(ImportedManifest::Resource(resource)); } - - if let Ok(manifest) = serde_yaml::from_str::(&contents) { - let resource = load_resource_manifest(path, &manifest)?; - return Ok(ImportedManifest::Resource(resource)); - } - let manifest = match serde_yaml::from_str::(&contents) { - Ok(manifest) => manifest, - Err(err) => { - return Err(DscError::Validation(t!("discovery.commandDiscovery.invalidManifest", path = path.to_string_lossy(), err = err).to_string())); + if DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { + if let Ok(manifest) = serde_json::from_str::(&contents) { + let mut resources = vec![]; + for res_manifest in manifest.resources { + let resource = load_resource_manifest(path, &res_manifest)?; + resources.push(ImportedManifest::Resource(resource)); + } + return Ok(resources); } - }; - let extension = load_extension_manifest(path, &manifest)?; - Ok(ImportedManifest::Extension(extension)) + } + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifest", resource = path.to_string_lossy()).to_string())); } fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result { diff --git a/lib/dsc-lib/src/dscerror.rs b/lib/dsc-lib/src/dscerror.rs index 2df370729..f3ef26a1d 100644 --- a/lib/dsc-lib/src/dscerror.rs +++ b/lib/dsc-lib/src/dscerror.rs @@ -50,6 +50,9 @@ pub enum DscError { #[error("{t} '{0}', {t2} {1}, {t3} {2}", t = t!("dscerror.invalidFunctionParameterCount"), t2 = t!("dscerror.expected"), t3 = t!("dscerror.got"))] InvalidFunctionParameterCount(String, usize, usize), + #[error("{0}")] + InvalidManifest(String), + #[error("{t} '{0}': {1}", t = t!("dscerror.invalidRequiredVersion"))] InvalidRequiredVersion(String, String), diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index 48f255567..7bb22077e 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -22,6 +22,12 @@ pub enum Kind { Resource, } +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct ResourceManifestList { + /// The list of resource manifests. + pub resources: Vec, +} + #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ResourceManifest { diff --git a/lib/dsc-lib/src/extensions/discover.rs b/lib/dsc-lib/src/extensions/discover.rs index 5fd2cef91..813b6578a 100644 --- a/lib/dsc-lib/src/extensions/discover.rs +++ b/lib/dsc-lib/src/extensions/discover.rs @@ -90,8 +90,10 @@ impl DscExtension { } let manifest_path = Path::new(&discover_result.manifest_path); // Currently we don't support extensions discovering other extensions - if let ImportedManifest::Resource(resource) = load_manifest(manifest_path)? { - resources.push(resource); + for imported_manifest in load_manifest(manifest_path)? { + if let ImportedManifest::Resource(resource) = imported_manifest { + resources.push(resource); + } } } } diff --git a/tools/dsctest/dscdelete.dsc.resource.json b/tools/dsctest/dscdelete.dsc.resource.json deleted file mode 100644 index 645ce21d5..000000000 --- a/tools/dsctest/dscdelete.dsc.resource.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Delete", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "delete", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "delete": { - "executable": "dsctest", - "args": [ - "delete", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "delete" - ] - } - } -} diff --git a/tools/dsctest/dscexist.dsc.resource.json b/tools/dsctest/dscexist.dsc.resource.json deleted file mode 100644 index a476d5e65..000000000 --- a/tools/dsctest/dscexist.dsc.resource.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Exist", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "exist", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "exist", - { - "jsonInputArg": "--input", - "mandatory": true - } - ], - "handlesExist": true, - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "exist" - ] - } - } -} diff --git a/tools/dsctest/dscexitcode.dsc.resource.json b/tools/dsctest/dscexitcode.dsc.resource.json deleted file mode 100644 index dd5818c58..000000000 --- a/tools/dsctest/dscexitcode.dsc.resource.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/ExitCode", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "exit-code", - { - "jsonInputArg": "--input" - } - ] - }, - "exitCodes": { - "0": "Success", - "8": "Placeholder from manifest for exit code 8" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "exit-code" - ] - } - } -} diff --git a/tools/dsctest/dscexport.dsc.resource.json b/tools/dsctest/dscexport.dsc.resource.json deleted file mode 100644 index f97525465..000000000 --- a/tools/dsctest/dscexport.dsc.resource.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Export", - "version": "0.1.0", - "export": { - "executable": "dsctest", - "args": [ - "export", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "export" - ] - } - } -} diff --git a/tools/dsctest/dscexporter.dsc.resource.json b/tools/dsctest/dscexporter.dsc.resource.json deleted file mode 100644 index 4656df109..000000000 --- a/tools/dsctest/dscexporter.dsc.resource.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Exporter", - "version": "0.1.0", - "kind": "exporter", - "export": { - "executable": "dsctest", - "args": [ - "exporter", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "exporter" - ] - } - } -} diff --git a/tools/dsctest/dscget.dsc.resource.json b/tools/dsctest/dscget.dsc.resource.json deleted file mode 100644 index eaed3821e..000000000 --- a/tools/dsctest/dscget.dsc.resource.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Get", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "get", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "get" - ] - } - } -} diff --git a/tools/dsctest/dscindesiredstate.dsc.resource.json b/tools/dsctest/dscindesiredstate.dsc.resource.json deleted file mode 100644 index be0776031..000000000 --- a/tools/dsctest/dscindesiredstate.dsc.resource.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/InDesiredState", - "version": "0.1.0", - "test": { - "executable": "dsctest", - "args": [ - "in-desired-state", - { - "jsonInputArg": "--input", - "mandatory": true - } - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "in-desired-state" - ] - } - } -} diff --git a/tools/dsctest/dscoperation.dsc.resource.json b/tools/dsctest/dscoperation.dsc.resource.json deleted file mode 100644 index faf3cc2d2..000000000 --- a/tools/dsctest/dscoperation.dsc.resource.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Operation", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "operation", - "--operation", - "get", - { - "jsonInputArg": "--input" - } - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "operation", - "--operation", - "set", - { - "jsonInputArg": "--input" - } - ] - }, - "test": { - "executable": "dsctest", - "args": [ - "operation", - "--operation", - "trace", - { - "jsonInputArg": "--input" - } - ] - }, - "delete": { - "executable": "dsctest", - "args": [ - "operation", - "--operation", - "delete", - { - "jsonInputArg": "--input" - } - ] - }, - "export": { - "executable": "dsctest", - "args": [ - "operation", - "--operation", - "export", - { - "jsonInputArg": "--input" - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "operation" - ] - } - } -} diff --git a/tools/dsctest/dscsleep.dsc.resource.json b/tools/dsctest/dscsleep.dsc.resource.json deleted file mode 100644 index 1049ae7be..000000000 --- a/tools/dsctest/dscsleep.dsc.resource.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Sleep", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "sleep", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "sleep", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "test": { - "executable": "dsctest", - "args": [ - "sleep", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "sleep" - ] - } - } -} diff --git a/tools/dsctest/dsctest.dsc.resourcelist.json b/tools/dsctest/dsctest.dsc.resourcelist.json new file mode 100644 index 000000000..e790c1328 --- /dev/null +++ b/tools/dsctest/dsctest.dsc.resourcelist.json @@ -0,0 +1,771 @@ +{ + "resources": [ + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Delete", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "delete": { + "executable": "dsctest", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "delete" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Exist", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "exist", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "exist", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "handlesExist": true, + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "exist" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/ExitCode", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "exit-code", + { + "jsonInputArg": "--input" + } + ] + }, + "exitCodes": { + "0": "Success", + "8": "Placeholder from manifest for exit code 8" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "exit-code" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Export", + "version": "0.1.0", + "export": { + "executable": "dsctest", + "args": [ + "export", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "export" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Exporter", + "version": "0.1.0", + "kind": "exporter", + "export": { + "executable": "dsctest", + "args": [ + "exporter", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "exporter" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Get", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "get", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "get" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/InDesiredState", + "version": "0.1.0", + "test": { + "executable": "dsctest", + "args": [ + "in-desired-state", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "in-desired-state" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Operation", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "operation", + "--operation", + "get", + { + "jsonInputArg": "--input" + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "operation", + "--operation", + "set", + { + "jsonInputArg": "--input" + } + ] + }, + "test": { + "executable": "dsctest", + "args": [ + "operation", + "--operation", + "trace", + { + "jsonInputArg": "--input" + } + ] + }, + "delete": { + "executable": "dsctest", + "args": [ + "operation", + "--operation", + "delete", + { + "jsonInputArg": "--input" + } + ] + }, + "export": { + "executable": "dsctest", + "args": [ + "operation", + "--operation", + "export", + { + "jsonInputArg": "--input" + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "operation" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Sleep", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "sleep", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "sleep", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "test": { + "executable": "dsctest", + "args": [ + "sleep", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "sleep" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Trace", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "trace" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "trace" + ] + }, + "test": { + "executable": "dsctest", + "args": [ + "trace" + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "trace" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/WhatIf", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "whatif" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "whatif" + ], + "return": "state" + }, + "whatIf": { + "executable": "dsctest", + "args": [ + "whatif", + "-w" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "what-if" + ] + } + } + }, + { + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", + "type": "Test/Metadata", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "metadata", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "metadata", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "metadata", + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "export": { + "executable": "dsctest", + "args": [ + "metadata", + { + "jsonInputArg": "--input", + "mandatory": true + }, + "--export" + ] + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "metadata" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Adapter", + "version": "0.1.0", + "kind": "adapter", + "adapter": { + "list": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "list", + "--input", + "{}", + "--resource-type", + "none" + ] + }, + "inputKind": "single" + }, + "get": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "get", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "set", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + }, + "test": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "test", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + }, + "export": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "export", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + }, + "validate": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "validate", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + } + ] + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "1.1.2", + "get": { + "executable": "dsctest", + "args": [ + "version", + "1.1.2" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "1.1.2" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "1.1.2" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "1.1.3", + "get": { + "executable": "dsctest", + "args": [ + "version", + "1.1.3" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "1.1.3" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "1.1.3" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "1.1.0", + "get": { + "executable": "dsctest", + "args": [ + "version", + "1.1.0" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "1.1.0" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "1.1.0" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "2.1.0-preview.1", + "get": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.1" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.1" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.1" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "2.1.0-preview.2", + "get": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.2" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.2" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "2.1.0-preview.2" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/Version", + "version": "2.0.0", + "get": { + "executable": "dsctest", + "args": [ + "version", + "2.0.0" + ] + }, + "set": { + "executable": "dsctest", + "args": [ + "version", + "2.0.0" + ], + "return": "state" + }, + "test": { + "executable": "dsctest", + "args": [ + "version", + "2.0.0" + ], + "return": "state" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "version" + ] + } + } + } + ] +} diff --git a/tools/dsctest/dsctrace.dsc.resource.json b/tools/dsctest/dsctrace.dsc.resource.json deleted file mode 100644 index 7253222e1..000000000 --- a/tools/dsctest/dsctrace.dsc.resource.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Trace", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "trace" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "trace" - ] - }, - "test": { - "executable": "dsctest", - "args": [ - "trace" - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "trace" - ] - } - } -} diff --git a/tools/dsctest/dscwhatif.dsc.resource.json b/tools/dsctest/dscwhatif.dsc.resource.json deleted file mode 100644 index 7e5b35416..000000000 --- a/tools/dsctest/dscwhatif.dsc.resource.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/WhatIf", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "whatif" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "whatif" - ], - "return": "state" - }, - "whatIf": { - "executable": "dsctest", - "args": [ - "whatif", - "-w" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "what-if" - ] - } - } -} diff --git a/tools/dsctest/metadata.dsc.resource.json b/tools/dsctest/metadata.dsc.resource.json deleted file mode 100644 index 5e5f1b3f2..000000000 --- a/tools/dsctest/metadata.dsc.resource.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", - "type": "Test/Metadata", - "version": "0.1.0", - "get": { - "executable": "dsctest", - "args": [ - "metadata", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "metadata", - { - "jsonInputArg": "--input", - "mandatory": true - } - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "metadata", - { - "jsonInputArg": "--input", - "mandatory": true - } - ] - }, - "export": { - "executable": "dsctest", - "args": [ - "metadata", - { - "jsonInputArg": "--input", - "mandatory": true - }, - "--export" - ] - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "metadata" - ] - } - } -} diff --git a/tools/dsctest/resourceadapter.dsc.resource.json b/tools/dsctest/resourceadapter.dsc.resource.json deleted file mode 100644 index f6acb9634..000000000 --- a/tools/dsctest/resourceadapter.dsc.resource.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Adapter", - "version": "0.1.0", - "kind": "adapter", - "adapter": { - "list": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "list", - "--input", - "{}", - "--resource-type", - "none" - ] - }, - "inputKind": "single" - }, - "get": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "get", - { - "jsonInputArg": "--input", - "mandatory": true - }, - { - "resourceTypeArg": "--resource-type" - } - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "set", - { - "jsonInputArg": "--input", - "mandatory": true - }, - { - "resourceTypeArg": "--resource-type" - } - ] - }, - "test": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "test", - { - "jsonInputArg": "--input", - "mandatory": true - }, - { - "resourceTypeArg": "--resource-type" - } - ] - }, - "export": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "export", - { - "jsonInputArg": "--input", - "mandatory": true - }, - { - "resourceTypeArg": "--resource-type" - } - ] - }, - "validate": { - "executable": "dsctest", - "args": [ - "adapter", - "--operation", - "validate", - { - "jsonInputArg": "--input", - "mandatory": true - }, - { - "resourceTypeArg": "--resource-type" - } - ] - } -} diff --git a/tools/dsctest/version1.1.2.dsc.resource.json b/tools/dsctest/version1.1.2.dsc.resource.json deleted file mode 100644 index 14d1a7691..000000000 --- a/tools/dsctest/version1.1.2.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "1.1.2", - "get": { - "executable": "dsctest", - "args": [ - "version", - "1.1.2" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "1.1.2" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "1.1.2" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} diff --git a/tools/dsctest/version1.1.3.dsc.resource.json b/tools/dsctest/version1.1.3.dsc.resource.json deleted file mode 100644 index 5ebd11ce2..000000000 --- a/tools/dsctest/version1.1.3.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "1.1.3", - "get": { - "executable": "dsctest", - "args": [ - "version", - "1.1.3" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "1.1.3" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "1.1.3" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} diff --git a/tools/dsctest/version1.1.dsc.resource.json b/tools/dsctest/version1.1.dsc.resource.json deleted file mode 100644 index 2ec922201..000000000 --- a/tools/dsctest/version1.1.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "1.1.0", - "get": { - "executable": "dsctest", - "args": [ - "version", - "1.1.0" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "1.1.0" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "1.1.0" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} diff --git a/tools/dsctest/version2.1p1.dsc.resource.json b/tools/dsctest/version2.1p1.dsc.resource.json deleted file mode 100644 index d75589a44..000000000 --- a/tools/dsctest/version2.1p1.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "2.1.0-preview.1", - "get": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.1" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.1" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.1" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} diff --git a/tools/dsctest/version2.1p2.dsc.resource.json b/tools/dsctest/version2.1p2.dsc.resource.json deleted file mode 100644 index 29c9d380f..000000000 --- a/tools/dsctest/version2.1p2.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "2.1.0-preview.2", - "get": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.2" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.2" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "2.1.0-preview.2" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} diff --git a/tools/dsctest/version2.dsc.resource.json b/tools/dsctest/version2.dsc.resource.json deleted file mode 100644 index 4cc24eae6..000000000 --- a/tools/dsctest/version2.dsc.resource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Test/Version", - "version": "2.0.0", - "get": { - "executable": "dsctest", - "args": [ - "version", - "2.0.0" - ] - }, - "set": { - "executable": "dsctest", - "args": [ - "version", - "2.0.0" - ], - "return": "state" - }, - "test": { - "executable": "dsctest", - "args": [ - "version", - "2.0.0" - ], - "return": "state" - }, - "schema": { - "command": { - "executable": "dsctest", - "args": [ - "schema", - "-s", - "version" - ] - } - } -} From 7a4a6f93618183ca4ee100f9f843beaaf8303bd9 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 15 Oct 2025 17:41:43 -0700 Subject: [PATCH 02/10] Update lib/dsc-lib/src/discovery/command_discovery.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/dsc-lib/src/discovery/command_discovery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index de1ad7315..b17e45a65 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -30,7 +30,7 @@ use crate::util::get_setting; use crate::util::get_exe_path; const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; -const DSC_RESOURCE_LIST_EXTENSIONS: [&str; 3] = ["dsc.resourcelist.json", "dsc.resourcelist.yaml", ".dsc.resourcelist.yml"]; +const DSC_RESOURCE_LIST_EXTENSIONS: [&str; 3] = [".dsc.resourcelist.json", ".dsc.resourcelist.yaml", ".dsc.resourcelist.yml"]; const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"]; // use BTreeMap so that the results are sorted by the typename, the Vec is sorted by version From 4e66b80a41aadddb774d2d9406ae12916042faf2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 15 Oct 2025 17:50:05 -0700 Subject: [PATCH 03/10] fix clippy --- lib/dsc-lib/locales/en-us.toml | 5 +++- .../src/discovery/command_discovery.rs | 24 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 49a7614a3..03c46df6a 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -104,7 +104,10 @@ adapterFound = "Resource adapter '%{adapter}' version %{version} found" resourceFound = "Resource '%{resource}' version %{version} found" executableNotFound = "Executable '%{executable}' not found for operation '%{operation}' for resource '%{resource}'" extensionInvalidVersion = "Extension '%{extension}' version '%{version}' is invalid" -invalidManifest = "Invalid manifest for resource '%{resource}'" +invalidResourceManifest = "Invalid manifest for resource '%{resource}'" +invalidExtensionManifest = "Invalid manifest for extension '%{extension}'" +invalidResourceListManifest = "Invalid manifest for resource list '%{resource}'" +invalidManifestFile = "Invalid manifest file '%{resource}'" extensionResourceFound = "Extension found resource '%{resource}'" callingExtension = "Calling extension '%{extension}' to discover resources" extensionFoundResources = "Extension '%{extension}' found %{count} resources" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index b17e45a65..a75bbe990 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -244,8 +244,7 @@ impl ResourceDiscovery for CommandDiscovery { continue; }; let file_name_lowercase = file_name.to_lowercase(); - if (kind == &DiscoveryKind::Resource && DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || - (kind == &DiscoveryKind::Resource && DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || + if kind == &DiscoveryKind::Resource && (DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) || (kind == &DiscoveryKind::Extension && DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) { trace!("{}", t!("discovery.commandDiscovery.foundResourceManifest", path = path.to_string_lossy())); let imported_manifests = match load_manifest(&path) @@ -618,12 +617,22 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { let resource = load_resource_manifest(path, &manifest)?; return Ok(vec![ImportedManifest::Resource(resource)]); } + if let Ok(manifest) = serde_yaml::from_str::(&contents) { + let resource = load_resource_manifest(path, &manifest)?; + return Ok(vec![ImportedManifest::Resource(resource)]); + } + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceManifest", resource = path.to_string_lossy()).to_string())); } if DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { if let Ok(manifest) = serde_json::from_str::(&contents) { let extension = load_extension_manifest(path, &manifest)?; return Ok(vec![ImportedManifest::Extension(extension)]); } + if let Ok(manifest) = serde_yaml::from_str::(&contents) { + let extension = load_extension_manifest(path, &manifest)?; + return Ok(vec![ImportedManifest::Extension(extension)]); + } + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidExtensionManifest", resource = path.to_string_lossy()).to_string())); } if DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { if let Ok(manifest) = serde_json::from_str::(&contents) { @@ -634,8 +643,17 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { } return Ok(resources); } + if let Ok(manifest) = serde_yaml::from_str::(&contents) { + let mut resources = vec![]; + for res_manifest in manifest.resources { + let resource = load_resource_manifest(path, &res_manifest)?; + resources.push(ImportedManifest::Resource(resource)); + } + return Ok(resources); + } + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceListManifest", resource = path.to_string_lossy()).to_string())); } - return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifest", resource = path.to_string_lossy()).to_string())); + Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifestFile", resource = path.to_string_lossy()).to_string())) } fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result { From da4d9ab31db7780c8cbbddb4bc03d731354e3c63 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 15 Oct 2025 17:56:32 -0700 Subject: [PATCH 04/10] update build script --- build.data.psd1 | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/build.data.psd1 b/build.data.psd1 index 6b092b6d0..6cb850404 100644 --- a/build.data.psd1 +++ b/build.data.psd1 @@ -372,25 +372,7 @@ Binaries = @('dsctest') CopyFiles = @{ All = @( - 'dscdelete.dsc.resource.json' - 'dscexist.dsc.resource.json' - 'dscexitcode.dsc.resource.json' - 'dscexport.dsc.resource.json' - 'dscexporter.dsc.resource.json' - 'dscget.dsc.resource.json' - 'dscindesiredstate.dsc.resource.json' - 'dscoperation.dsc.resource.json' - 'dscsleep.dsc.resource.json' - 'dsctrace.dsc.resource.json' - 'dscwhatif.dsc.resource.json' - 'metadata.dsc.resource.json' - 'resourceadapter.dsc.resource.json' - 'version1.1.2.dsc.resource.json' - 'version1.1.3.dsc.resource.json' - 'version1.1.dsc.resource.json' - 'version2.1p1.dsc.resource.json' - 'version2.1p2.dsc.resource.json' - 'version2.dsc.resource.json' + 'dsctest.dsc.resourcelist.json' ) } } From 4a43c5b1b14bffc538872e23e46c783038f4aa39 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 17 Oct 2025 15:21:27 -0700 Subject: [PATCH 05/10] rename to `.dsc.manifests.json` and fix various issues --- build.ps1 | 4 +- dsc/src/args.rs | 21 +-- dsc/src/util.rs | 62 +++++---- lib/dsc-lib/locales/en-us.toml | 7 +- .../src/discovery/command_discovery.rs | 125 ++++++++++++------ .../src/dscresources/command_resource.rs | 22 ++- .../src/dscresources/resource_manifest.rs | 8 +- .../src/extensions/extension_manifest.rs | 2 +- lib/dsc-lib/src/util.rs | 13 +- ...celist.json => dsctest.dsc.manifests.json} | 2 +- 10 files changed, 166 insertions(+), 100 deletions(-) rename tools/dsctest/{dsctest.dsc.resourcelist.json => dsctest.dsc.manifests.json} (99%) diff --git a/build.ps1 b/build.ps1 index 2a8a0ec12..1ee551e20 100755 --- a/build.ps1 +++ b/build.ps1 @@ -499,11 +499,11 @@ if (!$SkipBuild) { } if ($IsWindows) { - Copy-Item "*.dsc.resource.*","*.dsc.resourcelist.*" $target -Force -ErrorAction Ignore + Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Force -ErrorAction Ignore } else { # don't copy WindowsPowerShell resource manifest $exclude = @('windowspowershell.dsc.resource.json', 'winpsscript.dsc.resource.json') - Copy-Item "*.dsc.resource.*","*.dsc.resourcelist.*" $target -Exclude $exclude -Force -ErrorAction Ignore + Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Exclude $exclude -Force -ErrorAction Ignore } # be sure that the files that should be executable are executable diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 298136071..c3a769cd9 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -289,20 +289,21 @@ pub enum ResourceSubCommand { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] pub enum SchemaType { - GetResult, - SetResult, - TestResult, - ResolveResult, - DscResource, - Resource, - ResourceManifest, - Include, Configuration, ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult, - ExtensionManifest, + DscResource, ExtensionDiscoverResult, + ExtensionManifest, FunctionDefinition, - RestartRequired + GetResult, + Include, + ManifestList, + ResolveResult, + Resource, + ResourceManifest, + RestartRequired, + SetResult, + TestResult, } diff --git a/dsc/src/util.rs b/dsc/src/util.rs index cc2c4d064..7d2d07d1e 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -17,7 +17,10 @@ use dsc_lib::{ ResourceTestResult, }, }, - discovery::Discovery, + discovery::{ + command_discovery::ManifestList, + Discovery, + }, dscerror::DscError, dscresources::{ command_resource::TraceLevel, @@ -157,30 +160,6 @@ pub fn add_fields_to_json(json: &str, fields_to_add: &HashMap) - #[must_use] pub fn get_schema(schema: SchemaType) -> Schema { match schema { - SchemaType::GetResult => { - schema_for!(GetResult) - }, - SchemaType::SetResult => { - schema_for!(SetResult) - }, - SchemaType::TestResult => { - schema_for!(TestResult) - }, - SchemaType::ResolveResult => { - schema_for!(ResolveResult) - } - SchemaType::DscResource => { - schema_for!(DscResource) - }, - SchemaType::Resource => { - schema_for!(Resource) - }, - SchemaType::ResourceManifest => { - schema_for!(ResourceManifest) - }, - SchemaType::Include => { - schema_for!(Include) - }, SchemaType::Configuration => { schema_for!(Configuration) }, @@ -193,18 +172,45 @@ pub fn get_schema(schema: SchemaType) -> Schema { SchemaType::ConfigurationTestResult => { schema_for!(ConfigurationTestResult) }, - SchemaType::ExtensionManifest => { - schema_for!(ExtensionManifest) + SchemaType::DscResource => { + schema_for!(DscResource) }, SchemaType::ExtensionDiscoverResult => { schema_for!(DiscoverResult) }, + SchemaType::ExtensionManifest => { + schema_for!(ExtensionManifest) + }, SchemaType::FunctionDefinition => { schema_for!(FunctionDefinition) }, + SchemaType::GetResult => { + schema_for!(GetResult) + }, + SchemaType::Include => { + schema_for!(Include) + }, + SchemaType::ManifestList => { + schema_for!(ManifestList) + }, + SchemaType::ResolveResult => { + schema_for!(ResolveResult) + }, + SchemaType::Resource => { + schema_for!(Resource) + }, + SchemaType::ResourceManifest => { + schema_for!(ResourceManifest) + }, SchemaType::RestartRequired => { schema_for!(RestartRequired) - } + }, + SchemaType::SetResult => { + schema_for!(SetResult) + }, + SchemaType::TestResult => { + schema_for!(TestResult) + }, } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 03c46df6a..ad410767c 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -98,7 +98,7 @@ discoverResources = "Discovering '%{kind}' using filter: %{filter}" invalidAdapterFilter = "Could not build Regex filter for adapter name" progressSearching = "Searching for resources" extensionSearching = "Searching for extensions" -foundResourceManifest = "Found resource manifest: %{path}" +foundManifest = "Found manifest: %{path}" extensionFound = "Extension '%{extension}' version %{version} found" adapterFound = "Resource adapter '%{adapter}' version %{version} found" resourceFound = "Resource '%{resource}' version %{version} found" @@ -106,8 +106,8 @@ executableNotFound = "Executable '%{executable}' not found for operation '%{oper extensionInvalidVersion = "Extension '%{extension}' version '%{version}' is invalid" invalidResourceManifest = "Invalid manifest for resource '%{resource}'" invalidExtensionManifest = "Invalid manifest for extension '%{extension}'" -invalidResourceListManifest = "Invalid manifest for resource list '%{resource}'" -invalidManifestFile = "Invalid manifest file '%{resource}'" +invalidManifestList = "Invalid manifest list '%{resource}': %{err}" +invalidManifestFile = "Invalid manifest file '%{resource}': %{err}" extensionResourceFound = "Extension found resource '%{resource}'" callingExtension = "Calling extension '%{extension}' to discover resources" extensionFoundResources = "Extension '%{extension}' found %{count} resources" @@ -680,3 +680,4 @@ notFoundSetting = "Setting '%{name}' not found in %{path}" failedToGetExePath = "Can't get 'dsc' executable path" settingNotFound = "Setting '%{name}' not found" failedToAbsolutizePath = "Failed to absolutize path '%{path}'" +invalidExitCodeKey = "Invalid exit code key '%{key}'" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index a75bbe990..05d5df368 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -4,7 +4,7 @@ use crate::{discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery}}; use crate::{locked_is_empty, locked_extend, locked_clone, locked_get}; use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; -use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, ResourceManifestList, SchemaKind}; +use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, SchemaKind}; use crate::dscresources::command_resource::invoke_command; use crate::dscerror::DscError; use crate::extensions::dscextension::{self, DscExtension, Capability as ExtensionCapability}; @@ -29,9 +29,9 @@ use which::which; use crate::util::get_setting; use crate::util::get_exe_path; -const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; -const DSC_RESOURCE_LIST_EXTENSIONS: [&str; 3] = [".dsc.resourcelist.json", ".dsc.resourcelist.yaml", ".dsc.resourcelist.yml"]; const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"]; +const DSC_MANIFEST_LIST_EXTENSIONS: [&str; 3] = [".dsc.manifests.json", ".dsc.manifests.yaml", ".dsc.manifests.yml"]; +const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; // use BTreeMap so that the results are sorted by the typename, the Vec is sorted by version static ADAPTERS: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); @@ -39,6 +39,18 @@ static RESOURCES: LazyLock>>> = LazyLoc static EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); static ADAPTED_RESOURCES: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[serde(untagged)] +pub enum Manifest { + Resource(ResourceManifest), + Extension(ExtensionManifest), +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct ManifestList { + pub manifests: Vec, +} + #[derive(Clone, Serialize, Deserialize, JsonSchema)] #[schemars(transform = idiomaticize_externally_tagged_enum)] pub enum ImportedManifest { @@ -199,13 +211,17 @@ impl ResourceDiscovery for CommandDiscovery { #[allow(clippy::too_many_lines)] fn discover(&mut self, kind: &DiscoveryKind, filter: &str) -> Result<(), DscError> { - info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); + if !locked_is_empty!(RESOURCES) { + return Ok(()); + } // if kind is DscResource, we need to discover extensions first - if *kind == DiscoveryKind::Resource { + if *kind == DiscoveryKind::Resource && locked_is_empty!(EXTENSIONS) { self.discover(&DiscoveryKind::Extension, "*")?; } + info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); + let regex_str = convert_wildcard_to_regex(filter); debug!("Using regex {regex_str} as filter for adapter name"); let mut regex_builder = RegexBuilder::new(®ex_str); @@ -244,9 +260,10 @@ impl ResourceDiscovery for CommandDiscovery { continue; }; let file_name_lowercase = file_name.to_lowercase(); - if kind == &DiscoveryKind::Resource && (DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) || + if DSC_MANIFEST_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) || + (kind == &DiscoveryKind::Resource && (DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)))) || (kind == &DiscoveryKind::Extension && DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) { - trace!("{}", t!("discovery.commandDiscovery.foundResourceManifest", path = path.to_string_lossy())); + trace!("{}", t!("discovery.commandDiscovery.foundManifest", path = path.to_string_lossy())); let imported_manifests = match load_manifest(&path) { Ok(r) => r, @@ -613,45 +630,73 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { let contents = fs::read_to_string(path)?; let file_name_lowercase = path.file_name().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); if DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - if let Ok(manifest) = serde_json::from_str::(&contents) { - let resource = load_resource_manifest(path, &manifest)?; - return Ok(vec![ImportedManifest::Resource(resource)]); - } - if let Ok(manifest) = serde_yaml::from_str::(&contents) { - let resource = load_resource_manifest(path, &manifest)?; - return Ok(vec![ImportedManifest::Resource(resource)]); - } - return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceManifest", resource = path.to_string_lossy()).to_string())); + let manifest = if file_name_lowercase.ends_with(".json") { + match serde_json::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceManifest", resource = path.to_string_lossy(), err = err).to_string())); + } + } + } else { + match serde_yaml::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceManifest", resource = path.to_string_lossy(), err = err).to_string())); + } + } + }; + let resource = load_resource_manifest(path, &manifest)?; + return Ok(vec![ImportedManifest::Resource(resource)]); } if DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - if let Ok(manifest) = serde_json::from_str::(&contents) { - let extension = load_extension_manifest(path, &manifest)?; - return Ok(vec![ImportedManifest::Extension(extension)]); - } - if let Ok(manifest) = serde_yaml::from_str::(&contents) { - let extension = load_extension_manifest(path, &manifest)?; - return Ok(vec![ImportedManifest::Extension(extension)]); - } - return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidExtensionManifest", resource = path.to_string_lossy()).to_string())); + let manifest = if file_name_lowercase.ends_with(".json") { + match serde_json::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidExtensionManifest", resource = path.to_string_lossy(), err = err).to_string())); + } + } + } else { + match serde_yaml::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidExtensionManifest", resource = path.to_string_lossy(), err = err).to_string())); + } + } + }; + let extension = load_extension_manifest(path, &manifest)?; + return Ok(vec![ImportedManifest::Extension(extension)]); } - if DSC_RESOURCE_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - if let Ok(manifest) = serde_json::from_str::(&contents) { - let mut resources = vec![]; - for res_manifest in manifest.resources { - let resource = load_resource_manifest(path, &res_manifest)?; - resources.push(ImportedManifest::Resource(resource)); + if DSC_MANIFEST_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { + let manifest_list = if file_name_lowercase.ends_with(".json") { + match serde_json::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifestList", resource = path.to_string_lossy(), err = err).to_string())); + } } - return Ok(resources); - } - if let Ok(manifest) = serde_yaml::from_str::(&contents) { - let mut resources = vec![]; - for res_manifest in manifest.resources { - let resource = load_resource_manifest(path, &res_manifest)?; - resources.push(ImportedManifest::Resource(resource)); + } else { + match serde_yaml::from_str::(&contents) { + Ok(manifest) => manifest, + Err(err) => { + return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifestList", resource = path.to_string_lossy(), err = err).to_string())); + } + } + }; + let mut resources = vec![]; + for res_manifest in manifest_list.manifests { + match res_manifest { + Manifest::Extension(ext_manifest) => { + let extension = load_extension_manifest(path, &ext_manifest)?; + resources.push(ImportedManifest::Extension(extension)); + }, + Manifest::Resource(res_manifest) => { + let resource = load_resource_manifest(path, &res_manifest)?; + resources.push(ImportedManifest::Resource(resource)); + } } - return Ok(resources); } - return Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidResourceListManifest", resource = path.to_string_lossy()).to_string())); + return Ok(resources); } Err(DscError::InvalidManifest(t!("discovery.commandDiscovery.invalidManifestFile", resource = path.to_string_lossy()).to_string())) } diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index b5220fed7..aacf17fc1 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -725,6 +725,22 @@ async fn run_process_async(executable: &str, args: Option>, input: O } } +fn convert_hashmap_string_keys_to_i32(input: Option<&HashMap>) -> Result>, DscError> { + if input.is_none() { + return Ok(None); + } + + let mut output: HashMap = HashMap::new(); + for (key, value) in input.unwrap() { + if let Ok(key_int) = key.parse::() { + output.insert(key_int, value.clone()); + } else { + return Err(DscError::NotSupported(t!("util.invalidExitCodeKey", key = key).to_string())); + } + } + Ok(Some(output)) +} + /// Invoke a command and return the exit code, stdout, and stderr. /// /// # Arguments @@ -745,7 +761,9 @@ async fn run_process_async(executable: &str, args: Option>, input: O /// Will panic if tokio runtime can't be created. /// #[allow(clippy::implicit_hasher)] -pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { +pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { + let exit_codes = convert_hashmap_string_keys_to_i32(exit_codes)?; + tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on( async { trace!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?})); @@ -753,7 +771,7 @@ pub fn invoke_command(executable: &str, args: Option>, input: Option trace!("{}", t!("dscresources.commandResource.commandCwd", cwd = cwd)); } - match run_process_async(executable, args, input, cwd, env, exit_codes).await { + match run_process_async(executable, args, input, cwd, env, exit_codes.as_ref()).await { Ok((code, stdout, stderr)) => { Ok((code, stdout, stderr)) }, diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index 7bb22077e..fcd1cf10f 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -22,12 +22,6 @@ pub enum Kind { Resource, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct ResourceManifestList { - /// The list of resource manifests. - pub resources: Vec, -} - #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ResourceManifest { @@ -75,7 +69,7 @@ pub struct ResourceManifest { pub adapter: Option, /// Mapping of exit codes to descriptions. Zero is always success and non-zero is always failure. #[serde(rename = "exitCodes", skip_serializing_if = "Option::is_none")] - pub exit_codes: Option>, + pub exit_codes: Option>, // we have to make this a string key instead of i32 due to https://github.com/serde-rs/json/issues/560 /// Details how to get the schema of the resource. #[serde(skip_serializing_if = "Option::is_none")] pub schema: Option, diff --git a/lib/dsc-lib/src/extensions/extension_manifest.rs b/lib/dsc-lib/src/extensions/extension_manifest.rs index cf8d1b3a9..8f338df6c 100644 --- a/lib/dsc-lib/src/extensions/extension_manifest.rs +++ b/lib/dsc-lib/src/extensions/extension_manifest.rs @@ -35,7 +35,7 @@ pub struct ExtensionManifest { pub secret: Option, /// Mapping of exit codes to descriptions. Zero is always success and non-zero is always failure. #[serde(rename = "exitCodes", skip_serializing_if = "Option::is_none")] - pub exit_codes: Option>, + pub exit_codes: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option>, } diff --git a/lib/dsc-lib/src/util.rs b/lib/dsc-lib/src/util.rs index bae63021b..ea518c585 100644 --- a/lib/dsc-lib/src/util.rs +++ b/lib/dsc-lib/src/util.rs @@ -4,12 +4,13 @@ use crate::dscerror::DscError; use rust_i18n::t; use serde_json::Value; -use std::fs; -use std::fs::File; -use std::io::BufReader; -use std::path::PathBuf; -use std::path::Path; -use std::env; +use std::{ + fs, + fs::File, + io::BufReader, + path::{Path, PathBuf}, + env, +}; use tracing::debug; pub struct DscSettingValue { diff --git a/tools/dsctest/dsctest.dsc.resourcelist.json b/tools/dsctest/dsctest.dsc.manifests.json similarity index 99% rename from tools/dsctest/dsctest.dsc.resourcelist.json rename to tools/dsctest/dsctest.dsc.manifests.json index e790c1328..7ab9f1cf5 100644 --- a/tools/dsctest/dsctest.dsc.resourcelist.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -1,5 +1,5 @@ { - "resources": [ + "manifests": [ { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Test/Delete", From 268f38c7f1d302716b78f91822c9da44cc1ce928 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 17 Oct 2025 15:32:53 -0700 Subject: [PATCH 06/10] fix clippy --- lib/dsc-lib/src/discovery/command_discovery.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 05d5df368..b9c91861e 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -42,8 +42,8 @@ static ADAPTED_RESOURCES: LazyLock>>> = #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(untagged)] pub enum Manifest { - Resource(ResourceManifest), - Extension(ExtensionManifest), + Resource(Box), + Extension(Box), } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -629,8 +629,9 @@ fn insert_resource(resources: &mut BTreeMap>, resource: pub fn load_manifest(path: &Path) -> Result, DscError> { let contents = fs::read_to_string(path)?; let file_name_lowercase = path.file_name().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); + let extension_is_json = path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("json")); if DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - let manifest = if file_name_lowercase.ends_with(".json") { + let manifest = if extension_is_json { match serde_json::from_str::(&contents) { Ok(manifest) => manifest, Err(err) => { @@ -649,7 +650,7 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { return Ok(vec![ImportedManifest::Resource(resource)]); } if DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - let manifest = if file_name_lowercase.ends_with(".json") { + let manifest = if extension_is_json { match serde_json::from_str::(&contents) { Ok(manifest) => manifest, Err(err) => { @@ -668,7 +669,7 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { return Ok(vec![ImportedManifest::Extension(extension)]); } if DSC_MANIFEST_LIST_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { - let manifest_list = if file_name_lowercase.ends_with(".json") { + let manifest_list = if extension_is_json { match serde_json::from_str::(&contents) { Ok(manifest) => manifest, Err(err) => { From 66966c57b1973930d11f7f332c912b2b2fecf5f4 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 17 Oct 2025 15:39:15 -0700 Subject: [PATCH 07/10] fix build --- build.data.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.data.psd1 b/build.data.psd1 index 6cb850404..eb1c2818a 100644 --- a/build.data.psd1 +++ b/build.data.psd1 @@ -372,7 +372,7 @@ Binaries = @('dsctest') CopyFiles = @{ All = @( - 'dsctest.dsc.resourcelist.json' + 'dsctest.dsc.manifests.json' ) } } From 0fb518cd6fc3c8a3c80f7128dcff8bf48286ba4f Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 22 Oct 2025 12:56:54 -0700 Subject: [PATCH 08/10] separate out resources and extensions --- .../src/discovery/command_discovery.rs | 30 ++++++++----------- tools/dsctest/dsctest.dsc.manifests.json | 2 +- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index b9c91861e..46eb9fda4 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -39,16 +39,10 @@ static RESOURCES: LazyLock>>> = LazyLoc static EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); static ADAPTED_RESOURCES: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] -#[serde(untagged)] -pub enum Manifest { - Resource(Box), - Extension(Box), -} - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] pub struct ManifestList { - pub manifests: Vec, + pub resources: Option>, + pub extensions: Option>, } #[derive(Clone, Serialize, Deserialize, JsonSchema)] @@ -685,16 +679,16 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { } }; let mut resources = vec![]; - for res_manifest in manifest_list.manifests { - match res_manifest { - Manifest::Extension(ext_manifest) => { - let extension = load_extension_manifest(path, &ext_manifest)?; - resources.push(ImportedManifest::Extension(extension)); - }, - Manifest::Resource(res_manifest) => { - let resource = load_resource_manifest(path, &res_manifest)?; - resources.push(ImportedManifest::Resource(resource)); - } + if let Some(resource_manifests) = &manifest_list.resources { + for res_manifest in resource_manifests { + let resource = load_resource_manifest(path, res_manifest)?; + resources.push(ImportedManifest::Resource(resource)); + } + } + if let Some(extension_manifests) = &manifest_list.extensions { + for ext_manifest in extension_manifests { + let extension = load_extension_manifest(path, ext_manifest)?; + resources.push(ImportedManifest::Extension(extension)); } } return Ok(resources); diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 7ab9f1cf5..e790c1328 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -1,5 +1,5 @@ { - "manifests": [ + "resources": [ { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Test/Delete", From a99aeef237d06e0393d0889cce2754af2f852426 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 22 Oct 2025 15:29:11 -0700 Subject: [PATCH 09/10] remove unused macros --- lib/dsc-lib/src/discovery/command_discovery.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 46eb9fda4..d68e7edcd 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -16,7 +16,7 @@ use regex::RegexBuilder; use rust_i18n::t; use semver::{Version, VersionReq}; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::{collections::{BTreeMap, HashMap, HashSet}, sync::{LazyLock, RwLock}}; use std::env; use std::ffi::OsStr; @@ -39,13 +39,13 @@ static RESOURCES: LazyLock>>> = LazyLoc static EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); static ADAPTED_RESOURCES: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[derive(Deserialize, JsonSchema)] pub struct ManifestList { pub resources: Option>, pub extensions: Option>, } -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Deserialize, JsonSchema)] #[schemars(transform = idiomaticize_externally_tagged_enum)] pub enum ImportedManifest { Resource(DscResource), From 0dd8f2a208da0f13b0b7dee4d2dc80788d8c99a3 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 22 Oct 2025 16:01:19 -0700 Subject: [PATCH 10/10] put back one clone --- lib/dsc-lib/src/discovery/command_discovery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index d68e7edcd..03941127c 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -45,7 +45,7 @@ pub struct ManifestList { pub extensions: Option>, } -#[derive(Deserialize, JsonSchema)] +#[derive(Clone, Deserialize, JsonSchema)] #[schemars(transform = idiomaticize_externally_tagged_enum)] pub enum ImportedManifest { Resource(DscResource),