Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ default-members = [
"lib/dsc-lib-registry",
"resources/runcommandonset",
"lib/dsc-lib-security_context",
"resources/dism_dsc",
"resources/sshdconfig",
"resources/WindowsUpdate",
"resources/windows_service",
Expand All @@ -57,8 +58,7 @@ default-members = [
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"y2j",
"xtask",
"resources/dism_dsc"
"xtask"
]

[workspace.metadata.groups]
Expand Down Expand Up @@ -197,7 +197,7 @@ quote = { version = "1.0" }
# used by other crates
rand = { version = "0.10.1" }
# dsc, dsc-lib
regex = { version = "1.12.3" }
regex = { version = "1.12.4" }
# registry, dsc-lib, dsc-lib-registry, dsctest
registry = { version = "1.3" }
# dsc
Expand Down
15 changes: 15 additions & 0 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ pub mod util;
i18n!("locales", fallback = "en-us");

fn main() {
#[cfg(windows)]
{
let mut builder = std::thread::Builder::new();
builder = builder.stack_size(2 * 1024 * 1024); // Default stack is too small on Windows causing overflow
builder.spawn(|| {
dsc_main();
}).unwrap().join().unwrap();
}
#[cfg(not(windows))]
{
dsc_main();
}
}

fn dsc_main() {
#[cfg(debug_assertions)]
check_debug();

Expand Down
1 change: 0 additions & 1 deletion dsc/tests/dsc_extension_discover.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ Describe 'Discover extension tests' {
$out.type | Should -Be 'Test/DiscoverRelative'
$out = dsc resource list 2> $TestDrive/error.log
$LASTEXITCODE | Should -Be 0
$out | Should -BeNullOrEmpty
$errorMessage = Get-Content -Path "$TestDrive/error.log" -Raw
$errorMessage | Should -BeLike '*is not an absolute path*'
} finally {
Expand Down
20 changes: 20 additions & 0 deletions dsc/tests/dsc_resource_export.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'Resource export tests' {
It "Export with <resource> accepts input '<json>' and returns filtered results" -TestCases @(
@{ resource = 'Test/ExportSchemaCommand'; json = '{ "name": "Gijs" }'; expected = @('Gijs') },
@{ resource = 'Test/ExportSchemaCommand'; json = '{ "name": "*e*" }'; expected = @('Steve', 'Tess') },
@{ resource = 'Test/ExportSchemaEmbedded'; json = '{ "name": "Gijs" }'; expected = @('Gijs') },
@{ resource = 'Test/ExportSchemaEmbedded'; json = '{ "name": "*e*" }'; expected = @('Steve', 'Tess') },
@{ resource = 'Test/ExportSchemaNoFiltering'; json = '{ "name": "Gijs" }'; expected = @('Steve', 'Tess', 'Gijs') }
){
param($resource, $json, $expected)

$output = dsc resource export -r $resource -i $json 2>$TESTDRIVE/error.log | ConvertFrom-Json
$errorlog = Get-Content "$TESTDRIVE/error.log" -Raw
$LASTEXITCODE | Should -Be 0 -Because $errorlog
$output.resources.count | Should -Be $expected.Count -Because ($output | ConvertTo-Json -Depth 4)
$output.resources.properties.name | Should -Be $expected -Because ($output | ConvertTo-Json -Depth 4)
}
}
3 changes: 2 additions & 1 deletion lib/dsc-lib-jsonschema/.versions.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"latestMajor": "V3",
"latestMinor": "V3_2",
"latestPatch": "V3_2_1",
"latestPatch": "V3_2_2",
"all": [
"V3",
"V3_2",
"V3_2_2",
"V3_2_1",
"V3_2_0",
"V3_1",
Expand Down
68 changes: 66 additions & 2 deletions lib/dsc-lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rust_i18n::t;
use serde::Deserialize;
use serde_json::{Map, Value};
use std::{collections::HashMap, env, path::Path, process::Stdio};
use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::{resource_manifest::SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which};
use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::resource_manifest::{ExportSchemaKind, SchemaArgKind}, types::{ExitCodesMap}, util::canonicalize_which};
use crate::dscerror::DscError;
use super::{
dscresource::{get_diff, redact, DscResource},
Expand Down Expand Up @@ -586,6 +586,64 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>)
}
}

fn verify_with_export_schema(input: &str, resource: &DscResource, target_resource: Option<&DscResource>) -> Result<(), DscError> {
Comment thread
michaeltlombardi marked this conversation as resolved.
let Some(manifest) = &resource.manifest else {
return Err(DscError::MissingManifest(resource.type_name.to_string()));
};

let Some(export) = manifest.export.as_ref() else {
return Err(DscError::SchemaNotAvailable(resource.type_name.to_string()));
};

let command_resource = match target_resource {
Some(r) => r,
None => resource,
};

let schema = match export.schema {
Some(ExportSchemaKind::Command(ref command)) => {
let args = process_schema_args(command.args.as_ref(), command_resource);
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?;
stdout
},
Some(ExportSchemaKind::Embedded(ref schema)) => {
serde_json::to_string(schema)?
},
Some(ExportSchemaKind::NoFiltering) => {
return Ok(());
},
None => {
if manifest.validate.is_some() {
let result = invoke_validate(resource, input, target_resource)?;
if result.valid {
return Ok(());
}

let reason = result
.reason
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| t!("dscresources.commandResource.resourceInvalidJson").to_string());
return Err(DscError::Validation(reason));
}

get_schema(resource, target_resource)?
}
};
let schema = serde_json::from_str(&schema)?;
let compiled_schema = match Validator::new(&schema) {
Ok(schema) => schema,
Err(e) => {
return Err(DscError::Schema(e.to_string()));
},
};
let json: Value = serde_json::from_str(input)?;
if let Err(err) = compiled_schema.validate(&json) {
return Err(DscError::Schema(err.to_string()));
}
Ok(())
}

/// Invoke the export operation on a resource
///
/// # Arguments
Expand Down Expand Up @@ -636,9 +694,15 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc
None => resource,
};
validate_security_context(&export.require_security_context, &command_resource.type_name, "export")?;

if let Some(input) = input {
let input = if export.schema == Some(ExportSchemaKind::NoFiltering) {
""
} else {
input
};
if !input.is_empty() {
verify_json_from_manifest(resource, input, target_resource)?;
verify_with_export_schema(input, resource, target_resource)?;

command_input = get_command_input(export.input.as_ref(), input)?;
}
Expand Down
13 changes: 13 additions & 0 deletions lib/dsc-lib/src/dscresources/resource_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ pub enum SchemaKind {
Embedded(Value),
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)]
#[dsc_repo_schema(base_name = "manifest.exportSchema", folder_path = "definitions")]
#[serde(rename_all = "camelCase")]
pub enum ExportSchemaKind {
/// The export schema is returned by running a command.
Command(SchemaCommand),
/// The export schema is embedded in the manifest.
Embedded(Value),
/// The export operation does not support filtering.
NoFiltering,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
pub struct SchemaCommand {
/// The command to run to get the schema.
Expand Down Expand Up @@ -321,6 +333,7 @@ pub struct ExportMethod {
/// The security context required to run the Export method. Default if not specified is `current`.
#[serde(rename = "requireSecurityContext", skip_serializing_if = "Option::is_none")]
pub require_security_context: Option<SecurityContextKind>,
pub schema: Option<ExportSchemaKind>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)]
Expand Down
1 change: 1 addition & 0 deletions tools/dsctest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2024"

[dependencies]
clap = { workspace = true }
regex = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
108 changes: 108 additions & 0 deletions tools/dsctest/dsctest.dsc.manifests.json
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,114 @@
]
}
}
},
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Test/ExportSchemaCommand",
"version": "0.1.0",
"description": "Test resource for export specific schema",
"export": {
"executable": "dsctest",
"args": [
"export-schema",
{
"jsonInputArg": "--input",
"mandatory": true
}
],
"schema": {
"command":{
"executable": "dsctest",
"args": [
"schema",
"-s",
"export-schema"
]
}
}
},
"schema": {
"command": {
"executable": "dsctest",
"args": [
"schema",
"-s",
"export-get-schema"
]
}
}
},
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Test/ExportSchemaEmbedded",
"version": "0.1.0",
"description": "Test resource for export specific schema",
"export": {
"executable": "dsctest",
"args": [
"export-schema",
{
"jsonInputArg": "--input",
"mandatory": true
}
],
"schema": {
"embedded": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/export-schema",
"title": "Export Schema",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Export Property",
"description": "Can contain wildcards for export filtering."
}
},
"required": [
"name"
]
}
}
},
"schema": {
"command": {
"executable": "dsctest",
"args": [
"schema",
"-s",
"export-get-schema"
]
}
}
},
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Test/ExportSchemaNoFiltering",
"version": "0.1.0",
"description": "Test resource for export specific schema",
"export": {
"executable": "dsctest",
"args": [
"export-schema",
{
"jsonInputArg": "--input",
"mandatory": true
}
],
"schema": "noFiltering"
},
"schema": {
"command": {
"executable": "dsctest",
"args": [
"schema",
"-s",
"export-get-schema"
]
}
}
}
]
}
Loading
Loading