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
50 changes: 50 additions & 0 deletions dsc/tests/dsc_config_get.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,54 @@ Describe 'dsc config get tests' {
$result.results[0].result.actualState.family | Should -BeIn @('Windows', 'Linux', 'macOS')
$LASTEXITCODE | Should -Be 0
}

It 'embedded schema should only be retrieved once for multiple instances of the same resource' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Echo 1
type: Microsoft.DSC.Debug/Echo
properties:
output: hello
- name: Echo 2
type: Microsoft.DSC.Debug/Echo
properties:
output: world
"@
$result = dsc -l debug config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
$errorLog = Get-Content $TestDrive/error.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $errorLog
$result.hadErrors | Should -BeFalse
$result.results.Count | Should -Be 2
$errorLog | Should -BeLike "*Retrieved schema for resource 'Microsoft.DSC.Debug/Echo' with version '1.0.0' from cache*"
}

It 'retrieves command resource schema from cache for multiple instances of the same resource' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: one
type: Test/Version
requireVersion: =1.1.0
properties:
version: 1.1.0
- name: two
type: Test/Version
requireVersion: =1.1.0
properties:
version: 1.1.0
- name: three
type: Test/Version
requireVersion: =2.0.0
properties:
version: 2.0.0
"@
$result = dsc -l debug config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
$errorLog = Get-Content $TestDrive/error.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $errorLog
$result.hadErrors | Should -BeFalse
$result.results.Count | Should -Be 3
$errorLog | Should -BeLike "*Retrieved schema for resource 'Test/Version' with version '1.1.0' from cache*"
$errorLog | Should -BeLike "*Retrieved schema for resource 'Test/Version' with version '2.0.0' from cache*"
}
}
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_2",
"latestPatch": "V3_2_3",
"all": [
"V3",
"V3_2",
"V3_2_3",
"V3_2_2",
"V3_2_1",
"V3_2_0",
Expand Down
2 changes: 2 additions & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ securityContextRequired = "Operation '%{operation}' for resource '%{resource}' r
noAdaptedContent = "No adapted content available for resource '%{resource}'"
invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}"
exportFilteringNotSupported = "Resource '%{resource}' does not support export filtering"
retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache"

[dscresources.dscresource]
invokeGet = "Invoking get for '%{resource}'"
Expand Down Expand Up @@ -240,6 +241,7 @@ adapterManifestNotFound = "Adapter manifest for '%{adapter}' not found"
adapterDoesNotSupportDelete = "Adapter '%{adapter}' does not support delete operation"
validatingAgainstSchema = "Validating against resource schema"
deprecationMessage = "Resource '%{resource}' is deprecated: %{message}"
retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache"

[dscresources.resource_manifest]
resourceManifestSchemaTitle = "Resource manifest schema URI"
Expand Down
2 changes: 2 additions & 0 deletions lib/dsc-lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ use serde_json::{Map, Value};
use std::path::PathBuf;
use std::collections::HashMap;
use tracing::{debug, info, trace, warn};

pub mod context;
pub mod config_doc;
pub mod config_result;
pub mod constraints;
pub mod depends_on;
pub mod parameters;
pub(crate) mod schema_cache;

pub struct Configurator {
json: String,
Expand Down
19 changes: 19 additions & 0 deletions lib/dsc-lib/src/configure/schema_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::{collections::{BTreeMap, HashMap}, sync::RwLock, sync::LazyLock};
use serde_json::Value;

use crate::types::{FullyQualifiedTypeName, ResourceVersion};

pub(crate) type SchemaCache = BTreeMap<FullyQualifiedTypeName, HashMap<ResourceVersion, Value>>;

pub(crate) static RESOURCE_SCHEMAS: LazyLock<RwLock<SchemaCache>> = LazyLock::new(|| RwLock::new(SchemaCache::new()));

pub(crate) fn get_resource_schema(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option<Value> {
let cache = RESOURCE_SCHEMAS.read().unwrap();
if let Some(schemas) = cache.get(type_name) && let Some(schema) = schemas.get(version) {
return Some(schema.clone());
}
None
}
23 changes: 17 additions & 6 deletions lib/dsc-lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ 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::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::{ExitCodesMap}, util::canonicalize_which};
use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schema, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which};
use crate::dscerror::DscError;
use crate::locked_insert;
use super::{
dscresource::{get_diff, redact, DscResource},
invoke_result::{
Expand Down Expand Up @@ -556,6 +557,12 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op
///
/// Error if schema is not available or if there is an error getting the schema
pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result<String, DscError> {
let cached_resource = target_resource.unwrap_or(resource);
if let Some(schema) = get_resource_schema(&cached_resource.type_name, &cached_resource.version) {
debug!("{}", t!("dscresources.commandResource.retrievedSchemaFromCache", resource = &cached_resource.type_name, version = &cached_resource.version));
return Ok(serde_json::to_string(&schema)?);
}

let Some(manifest) = &resource.manifest else {
return Err(DscError::MissingManifest(resource.type_name.to_string()));
};
Expand All @@ -573,17 +580,21 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>)
return Err(DscError::SchemaNotAvailable(target_resource.type_name.to_string()));
};

match schema_kind {
let (schema, schema_value) = match schema_kind {
SchemaKind::Command(command) => {
let args = process_schema_args(command.args.as_ref(), target_resource);
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?;
Ok(stdout)
let schema_value: Value = serde_json::from_str(&stdout)?;
Comment thread
SteveL-MSFT marked this conversation as resolved.
(stdout, schema_value)
},
Comment thread
SteveL-MSFT marked this conversation as resolved.
SchemaKind::Embedded(schema) => {
let json = serde_json::to_string(&schema)?;
Ok(json)
(serde_json::to_string(&schema)?, schema.clone())
},
}
};

locked_insert!(RESOURCE_SCHEMAS, cached_resource.type_name.clone(), cached_resource.version.clone(), schema_value);

Ok(schema)
}

fn verify_with_export_schema(input: &str, resource: &DscResource, target_resource: Option<&DscResource>) -> Result<(), DscError> {
Expand Down
8 changes: 7 additions & 1 deletion lib/dsc-lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}};
use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schema}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}};
use crate::discovery::discovery_trait::DiscoveryFilter;
use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse};
use crate::schemas::transforms::idiomaticize_string_enum;
Expand Down Expand Up @@ -529,6 +529,12 @@ impl Invoke for DscResource {
}

fn schema(&self) -> Result<String, DscError> {
let target_resource = self.target_resource.as_deref().unwrap_or(self);
if let Some(schema) = get_resource_schema(&target_resource.type_name, &target_resource.version) {
debug!("{}", t!("dscresources.dscresource.retrievedSchemaFromCache", resource = target_resource.type_name, version = target_resource.version));
return Ok(serde_json::to_string(&schema)?);
}

debug!("{}", t!("dscresources.dscresource.invokeSchema", resource = self.type_name));
if let Some(deprecation_message) = self.deprecation_message.as_ref() {
warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message));
Expand Down
8 changes: 8 additions & 0 deletions lib/dsc-lib/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,11 @@ macro_rules! locked_get {
}
}};
}

#[macro_export]
macro_rules! locked_insert {
($lockable:expr, $key:expr, $subkey:expr, $value:expr) => {{
let mut btree = $lockable.write().unwrap();
btree.entry($key).or_default().insert($subkey, $value);
}};
}
Loading