Skip to content

Commit 4750f77

Browse files
authored
Merge pull request #1069 from tgauth/add-input-metadata-support
pass input metadata to resource
2 parents 1c86365 + bc0e747 commit 4750f77

File tree

7 files changed

+197
-86
lines changed

7 files changed

+197
-86
lines changed

dsc/locales/en-us.toml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ noResources = "Resources not specified"
100100
resourceTypeNotSpecified = "Resource type not specified"
101101
validatingResource = "Validating resource named"
102102
resourceNotFound = "Resource type not found"
103-
resourceImplementsValidate = "Resource implements validation"
104-
noReason = "No reason provided"
105-
resourceValidationFailed = "Resource failed validation"
106-
resourceDoesNotImplementValidate = "Resource does not implement validation, using schema"
107-
noSchemaOrValidate = "Resource does not have a schema nor supports validation"
108-
noManifest = "Resource does not have a manifest"
109103
tableHeader_type = "Type"
110104
tableHeader_kind = "Kind"
111105
tableHeader_version = "Version"
@@ -127,9 +121,6 @@ failedToConvertJsonToString = "Failed to convert JSON to string"
127121
failedToReadTracingSetting = "Could not read 'tracing' setting"
128122
invalidTraceLevel = "Default to 'warn', invalid DSC_TRACE_LEVEL value"
129123
failedToSetTracing = "Unable to set global default tracing subscriber. Tracing is disabled."
130-
validatingSchema = "Validating against schema"
131-
failedToCompileSchema = "JSON Schema Compilation"
132-
validationFailed = "Failed validation"
133124
readingInput = "Reading input from command line parameter"
134125
inputIsFile = "Document provided is a file path, use '--file' instead"
135126
readingInputFromFile = "Reading input from file"

dsc/src/subcommand.rs

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::args::{ConfigSubCommand, SchemaType, ExtensionSubCommand, FunctionSub
55
use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::tablewriter::Table;
8-
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, validate_json, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
8+
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
99
use dsc_lib::functions::FunctionArgKind;
1010
use dsc_lib::{
1111
configure::{
@@ -26,8 +26,8 @@ use dsc_lib::{
2626
TestResult,
2727
ValidateResult,
2828
},
29-
dscresources::dscresource::{Capability, ImplementedAs, Invoke},
30-
dscresources::resource_manifest::{import_manifest, ResourceManifest},
29+
dscresources::dscresource::{Capability, ImplementedAs, validate_json, validate_properties},
30+
dscresources::resource_manifest::import_manifest,
3131
extensions::dscextension::Capability as ExtensionCapability,
3232
functions::FunctionDispatcher,
3333
progress::ProgressFormat,
@@ -514,34 +514,7 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat)
514514

515515
// see if the resource is command based
516516
if resource.implemented_as == ImplementedAs::Command {
517-
// if so, see if it implements validate via the resource manifest
518-
if let Some(manifest) = resource.manifest.clone() {
519-
// convert to resource_manifest
520-
let manifest: ResourceManifest = serde_json::from_value(manifest)?;
521-
if manifest.validate.is_some() {
522-
debug!("{}: {type_name} ", t!("subcommand.resourceImplementsValidate"));
523-
// get the resource's part of the config
524-
let resource_config = resource_block["properties"].to_string();
525-
let result = resource.validate(&resource_config)?;
526-
if !result.valid {
527-
let reason = result.reason.unwrap_or(t!("subcommand.noReason").to_string());
528-
let type_name = resource.type_name.clone();
529-
return Err(DscError::Validation(format!("{}: {type_name} {reason}", t!("subcommand.resourceValidationFailed"))));
530-
}
531-
}
532-
else {
533-
// use schema validation
534-
trace!("{}: {type_name}", t!("subcommand.resourceDoesNotImplementValidate"));
535-
let Ok(schema) = resource.schema() else {
536-
return Err(DscError::Validation(format!("{}: {type_name}", t!("subcommand.noSchemaOrValidate"))));
537-
};
538-
let schema = serde_json::from_str(&schema)?;
539-
540-
validate_json(&resource.type_name, &schema, &resource_block["properties"])?;
541-
}
542-
} else {
543-
return Err(DscError::Validation(format!("{}: {type_name}", t!("subcommand.noManifest"))));
544-
}
517+
validate_properties(resource, &resource_block["properties"])?;
545518
}
546519
}
547520

dsc/src/util.rs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,10 @@ use dsc_lib::{
4141
parse_input_to_json,
4242
},
4343
};
44-
use jsonschema::Validator;
4544
use path_absolutize::Absolutize;
4645
use rust_i18n::t;
4746
use schemars::{Schema, schema_for};
4847
use serde::Deserialize;
49-
use serde_json::Value;
5048
use std::collections::HashMap;
5149
use std::env;
5250
use std::io::{IsTerminal, Read};
@@ -423,39 +421,6 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op
423421
info!("Trace-level is {:?}", tracing_setting.level);
424422
}
425423

426-
/// Validate the JSON against the schema.
427-
///
428-
/// # Arguments
429-
///
430-
/// * `source` - The source of the JSON
431-
/// * `schema` - The schema to validate against
432-
/// * `json` - The JSON to validate
433-
///
434-
/// # Returns
435-
///
436-
/// Nothing on success.
437-
///
438-
/// # Errors
439-
///
440-
/// * `DscError` - The JSON is invalid
441-
pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), DscError> {
442-
debug!("{}: {source}", t!("util.validatingSchema"));
443-
trace!("JSON: {json}");
444-
trace!("Schema: {schema}");
445-
let compiled_schema = match Validator::new(schema) {
446-
Ok(compiled_schema) => compiled_schema,
447-
Err(err) => {
448-
return Err(DscError::Validation(format!("{}: {err}", t!("util.failedToCompileSchema"))));
449-
}
450-
};
451-
452-
if let Err(err) = compiled_schema.validate(json) {
453-
return Err(DscError::Validation(format!("{}: '{source}' {err}", t!("util.validationFailed"))));
454-
}
455-
456-
Ok(())
457-
}
458-
459424
pub fn get_input(input: Option<&String>, file: Option<&String>, parameters_from_stdin: bool) -> String {
460425
trace!("Input: {input:?}, File: {file:?}");
461426
let value = if let Some(input) = input {

dsc/tests/dsc_metadata.tests.ps1

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,73 @@
22
# Licensed under the MIT License.
33

44
Describe 'metadata tests' {
5+
It 'metadata not provided if not declared in resource schema' {
6+
$configYaml = @'
7+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
8+
resources:
9+
- name: test
10+
type: Microsoft.DSC.Debug/Echo
11+
metadata:
12+
ignoreKey: true
13+
properties:
14+
output: hello world
15+
'@
16+
$out = dsc config get -i $configYaml 2>$TestDrive/error.log | ConvertFrom-Json
17+
$LASTEXITCODE | Should -Be 0
18+
(Get-Content $TestDrive/error.log) | Should -BeLike "*WARN*Will not add '_metadata' to properties because resource schema does not support it*"
19+
$out.results.result.actualState.output | Should -BeExactly 'hello world'
20+
}
21+
22+
It 'resource can provide high-level metadata for <operation>' -TestCases @(
23+
@{ operation = 'get' }
24+
@{ operation = 'set' }
25+
@{ operation = 'test' }
26+
) {
27+
param($operation)
28+
29+
$configYaml = @'
30+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
31+
resources:
32+
- name: test
33+
type: Test/Metadata
34+
metadata:
35+
hello: world
36+
myNumber: 42
37+
properties:
38+
'@
39+
40+
$out = dsc config $operation -i $configYaml 2>$TestDrive/error.log | ConvertFrom-Json
41+
$LASTEXITCODE | Should -Be 0
42+
$out.results.count | Should -Be 1
43+
$out.results[0].metadata.hello | Should -BeExactly 'world'
44+
$out.results[0].metadata.myNumber | Should -Be 42
45+
}
46+
47+
It 'resource can provide high-level metadata for export' {
48+
$configYaml = @'
49+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
50+
resources:
51+
- name: test
52+
type: Test/Metadata
53+
metadata:
54+
hello: There
55+
myNumber: 16
56+
properties:
57+
'@
58+
$out = dsc config export -i $configYaml 2>$TestDrive/error.log | ConvertFrom-Json
59+
$LASTEXITCODE | Should -Be 0
60+
$out.resources.count | Should -Be 3
61+
$out.resources[0].metadata.hello | Should -BeExactly 'There'
62+
$out.resources[0].metadata.myNumber | Should -Be 16
63+
$out.resources[0].name | Should -BeExactly 'Metadata example 1'
64+
$out.resources[1].metadata.hello | Should -BeExactly 'There'
65+
$out.resources[1].metadata.myNumber | Should -Be 16
66+
$out.resources[1].name | Should -BeExactly 'Metadata example 2'
67+
$out.resources[2].metadata.hello | Should -BeExactly 'There'
68+
$out.resources[2].metadata.myNumber | Should -Be 16
69+
$out.resources[2].name | Should -BeExactly 'Metadata example 3'
70+
}
71+
572
It 'resource can provide metadata for <operation>' -TestCases @(
673
@{ operation = 'get' }
774
@{ operation = 'set' }

dsc_lib/locales/en-us.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ propertyNotString = "Property '%{name}' with value '%{value}' is not a string"
7171
metadataMicrosoftDscIgnored = "Resource returned '_metadata' property 'Microsoft.DSC' which is ignored"
7272
metadataNotObject = "Resource returned '_metadata' property which is not an object"
7373
metadataRestartRequiredInvalid = "Resource returned '_metadata' property '_restartRequired' which contains invalid value: %{value}"
74+
schemaExcludesMetadata = "Will not add '_metadata' to properties because resource schema does not support it"
7475

7576
[discovery.commandDiscovery]
7677
couldNotReadSetting = "Could not read 'resourcePath' setting"
@@ -176,6 +177,15 @@ diffKeyMissing = "diff: key '%{key}' missing"
176177
diffKeyNotObject = "diff: key '%{key}' is not an object"
177178
diffArraySize = "diff: arrays have different lengths"
178179
diffMissingItem = "diff: actual array missing expected item"
180+
failedToCompileSchema = "JSON Schema Compilation"
181+
noManifest = "Resource does not have a manifest"
182+
noReason = "No reason provided"
183+
noSchemaOrValidate = "Resource does not have a schema nor supports validation"
184+
resourceDoesNotImplementValidate = "Resource does not implement validation, using schema"
185+
resourceImplementsValidate = "Resource implements validation"
186+
resourceValidationFailed = "Resource failed validation"
187+
validatingSchema = "Validating against schema"
188+
validationFailed = "Failed validation"
179189

180190
[dscresources.resource_manifest]
181191
resourceManifestSchemaTitle = "Resource manifest schema URI"

dsc_lib/src/configure/mod.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::discovery::discovery_trait::DiscoveryFilter;
77
use crate::dscerror::DscError;
88
use crate::dscresources::invoke_result::ExportResult;
99
use crate::dscresources::{
10-
{dscresource::{Capability, Invoke, get_diff},
10+
{dscresource::{Capability, Invoke, get_diff, validate_properties},
1111
invoke_result::{GetResult, SetResult, TestResult, ResourceSetResponse}},
1212
resource_manifest::Kind,
1313
};
@@ -171,10 +171,15 @@ fn escape_property_values(properties: &Map<String, Value>) -> Result<Option<Map<
171171
Ok(Some(result))
172172
}
173173

174-
fn add_metadata(kind: &Kind, mut properties: Option<Map<String, Value>> ) -> Result<String, DscError> {
175-
if *kind == Kind::Adapter {
174+
fn add_metadata(dsc_resource: &DscResource, mut properties: Option<Map<String, Value>>, resource_metadata: Option<Metadata> ) -> Result<String, DscError> {
175+
if dsc_resource.kind == Kind::Adapter {
176176
// add metadata to the properties so the adapter knows this is a config
177-
let mut metadata = Map::new();
177+
let mut metadata: Map<String, Value> = Map::new();
178+
if let Some(resource_metadata) = resource_metadata {
179+
if !resource_metadata.other.is_empty() {
180+
metadata.extend(resource_metadata.other);
181+
}
182+
}
178183
let mut dsc_value = Map::new();
179184
dsc_value.insert("context".to_string(), Value::String("configuration".to_string()));
180185
metadata.insert("Microsoft.DSC".to_string(), Value::Object(dsc_value));
@@ -186,6 +191,22 @@ fn add_metadata(kind: &Kind, mut properties: Option<Map<String, Value>> ) -> Res
186191
return Ok(serde_json::to_string(&properties)?);
187192
}
188193

194+
if let Some(resource_metadata) = resource_metadata {
195+
let other_metadata = resource_metadata.other;
196+
let mut props = if let Some(props) = properties {
197+
props
198+
} else {
199+
Map::new()
200+
};
201+
props.insert("_metadata".to_string(), Value::Object(other_metadata));
202+
let modified_props = Value::from(props.clone());
203+
if let Ok(()) = validate_properties(dsc_resource, &modified_props) {} else {
204+
warn!("{}", t!("configure.mod.schemaExcludesMetadata"));
205+
props.remove("_metadata");
206+
}
207+
return Ok(serde_json::to_string(&props)?);
208+
}
209+
189210
match properties {
190211
Some(properties) => {
191212
Ok(serde_json::to_string(&properties)?)
@@ -330,7 +351,7 @@ impl Configurator {
330351
return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string()));
331352
};
332353
let properties = self.get_properties(&resource, &dsc_resource.kind)?;
333-
let filter = add_metadata(&dsc_resource.kind, properties)?;
354+
let filter = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
334355
let start_datetime = chrono::Local::now();
335356
let mut get_result = match dsc_resource.get(&filter) {
336357
Ok(result) => result,
@@ -424,7 +445,7 @@ impl Configurator {
424445
}
425446
};
426447

427-
let desired = add_metadata(&dsc_resource.kind, properties)?;
448+
let desired = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
428449
trace!("{}", t!("configure.mod.desired", state = desired));
429450

430451
let start_datetime;
@@ -561,7 +582,7 @@ impl Configurator {
561582
};
562583
let properties = self.get_properties(&resource, &dsc_resource.kind)?;
563584
debug!("resource_type {}", &resource.resource_type);
564-
let expected = add_metadata(&dsc_resource.kind, properties)?;
585+
let expected = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
565586
trace!("{}", t!("configure.mod.expectedState", state = expected));
566587
let start_datetime = chrono::Local::now();
567588
let mut test_result = match dsc_resource.test(&expected) {
@@ -637,7 +658,7 @@ impl Configurator {
637658
return Err(DscError::ResourceNotFound(resource.resource_type.clone(), resource.api_version.as_deref().unwrap_or("").to_string()));
638659
};
639660
let properties = self.get_properties(resource, &dsc_resource.kind)?;
640-
let input = add_metadata(&dsc_resource.kind, properties)?;
661+
let input = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
641662
trace!("{}", t!("configure.mod.exportInput", input = input));
642663
let export_result = match add_resource_export_results_to_configuration(dsc_resource, &mut conf, input.as_str()) {
643664
Ok(result) => result,

0 commit comments

Comments
 (0)