Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate expressions/functions with DSC #252

Merged
merged 15 commits into from
Nov 7, 2023
Merged
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
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
],
"cwd": "${workspaceFolder}"
},
{
"name": "(macOS) Attach",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}",
},
{
"name": "(Windows) Attach",
"type": "cppvsdbg",
Expand Down
6 changes: 3 additions & 3 deletions dsc/examples/osinfo_registry.dsc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
"name": "windows product name",
"type": "Microsoft.Windows/Registry",
"properties": {
"keyPath": "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion",
"valueName": "ProductName"
"keyPath": "[concat('HKLM\\','Software\\Microsoft\\Windows NT\\','CurrentVersion')]",
"valueName": "ProductName"
}
},
{
"name": "system root",
"type": "Microsoft.Windows/Registry",
"properties": {
"keyPath": "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion",
"valueName": "SystemRoot"
"valueName": "SystemRoot"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn terminate_subprocesses(sys: &System, process: &Process) {
#[cfg(debug_assertions)]
fn check_debug() {
if env::var("DEBUG_DSC").is_ok() {
error!("attach debugger to pid {} and press a key to continue", std::process::id());
eprintln!("attach debugger to pid {} and press a key to continue", std::process::id());
loop {
let event = event::read().unwrap();
if let event::Event::Key(key) = event {
Expand All @@ -147,7 +147,7 @@ fn check_debug() {
break;
}
} else {
error!("Unexpected event: {event:?}");
eprintln!("Unexpected event: {event:?}");
continue;
}
}
Expand Down
27 changes: 27 additions & 0 deletions dsc/tests/dsc_functions.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'tests for function expressions' {
It 'function works: <text>' -TestCases @(
@{ text = "[concat('a', 'b')]"; expected = 'ab' }
@{ text = "[concat('a', 'b', 'c')]"; expected = 'abc' }
@{ text = "[concat('a', 1, concat(2, 'b'))]"; expected = 'a12b' }
@{ text = "[base64('ab')]"; expected = 'YWI=' }
@{ text = "[base64(concat('a','b'))]"; expected = 'YWI=' }
@{ text = "[base64(base64(concat('a','b')))]"; expected = 'WVdJPQ==' }
) {
param($text, $expected)

$escapedText = $text -replace "'", "''"
$config_yaml = @"
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
resources:
- name: Echo
type: Test/Echo
properties:
text: '$escapedText'
"@
$out = $config_yaml | dsc config get | ConvertFrom-Json
$out.results[0].result.actualState.text | Should -Be $expected
}
}
6 changes: 2 additions & 4 deletions dsc_lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::{Map, Value};
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
Expand Down Expand Up @@ -66,16 +66,14 @@ pub struct Resource {
/// The fully qualified name of the resource type
#[serde(rename = "type")]
pub resource_type: String,
// TODO: `apiVersion` is required by ARM but doesn't make sense here

/// A friendly name for the resource instance
pub name: String, // friendly unique instance name
#[serde(rename = "dependsOn", skip_serializing_if = "Option::is_none")]
#[schemars(regex(pattern = r"^\[resourceId\(\s*'[a-zA-Z0-9\.]+/[a-zA-Z0-9]+'\s*,\s*'[a-zA-Z0-9 ]+'\s*\)]$"))]
pub depends_on: Option<Vec<String>>,
// `identity` can be used for run-as
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<HashMap<String, Value>>,
pub properties: Option<Map<String, Value>>,
}

// Defines the valid and recognized canonical URIs for the configuration schema
Expand Down
76 changes: 43 additions & 33 deletions dsc_lib/src/configure/depends_on.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use regex::Regex;

use crate::configure::config_doc::Resource;
use crate::configure::Configuration;
use crate::DscError;
use crate::parser::Statement;

/// Gets the invocation order of resources based on their dependencies
///
///
/// # Arguments
///
///
/// * `config` - The configuration to get the invocation order for
///
///
/// # Returns
///
///
/// * `Result<Vec<Resource>, DscError>` - The invocation order of resources
///
///
/// # Errors
///
///
/// * `DscError::Validation` - The configuration is invalid
pub fn get_resource_invocation_order(config: &Configuration) -> Result<Vec<Resource>, DscError> {
pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statement) -> Result<Vec<Resource>, DscError> {
let mut order: Vec<Resource> = Vec::new();
let depends_on_regex = Regex::new(r"^\[resourceId\(\s*'(?<type>[a-zA-Z0-9\.]+/[a-zA-Z0-9]+)'\s*,\s*'(?<name>[a-zA-Z0-9 ]+)'\s*\)]$")?;
for resource in &config.resources {
// validate that the resource isn't specified more than once in the config
if config.resources.iter().filter(|r| r.name == resource.name && r.resource_type == resource.resource_type).count() > 1 {
Expand All @@ -32,12 +30,9 @@ pub fn get_resource_invocation_order(config: &Configuration) -> Result<Vec<Resou
let mut dependency_already_in_order = true;
if let Some(depends_on) = resource.depends_on.clone() {
for dependency in depends_on {
// validate dependency exists
let Some(captures) = depends_on_regex.captures(&dependency) else {
return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect for resource name '{0}': {dependency}", resource.name)));
};
let resource_type = captures.name("type").ok_or(DscError::Validation("Resource type missing".to_string()))?.as_str();
let resource_name = captures.name("name").ok_or(DscError::Validation("Resource name missing".to_string()))?.as_str();
let statement = parser.parse_and_execute(&dependency)?;
let (resource_type, resource_name) = get_type_and_name(&statement)?;

// find the resource by name
let Some(dependency_resource) = config.resources.iter().find(|r| r.name.eq(resource_name)) else {
return Err(DscError::Validation(format!("'dependsOn' resource name '{resource_name}' does not exist for resource named '{0}'", resource.name)));
Expand Down Expand Up @@ -66,15 +61,12 @@ pub fn get_resource_invocation_order(config: &Configuration) -> Result<Vec<Resou
// check if the order has resource before its dependencies
let resource_index = order.iter().position(|r| r.name == resource.name && r.resource_type == resource.resource_type).ok_or(DscError::Validation("Resource not found in order".to_string()))?;
for dependency in depends_on {
let Some(captures) = depends_on_regex.captures(dependency) else {
return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect for resource name '{0}': {dependency}", resource.name)));
};
let resource_type = captures.name("type").ok_or(DscError::Validation("Resource type not found in dependency".to_string()))?.as_str();
let resource_name = captures.name("name").ok_or(DscError::Validation("Resource name not found in dependency".to_string()))?.as_str();
let dependency_index = order.iter().position(|r| r.name == resource_name && r.resource_type == resource_type).ok_or(DscError::Validation("Dependency not found in order".to_string()))?;
if resource_index < dependency_index {
return Err(DscError::Validation(format!("Circular dependency detected for resource named '{0}'", resource.name)));
}
let statement = parser.parse_and_execute(dependency)?;
let (resource_type, resource_name) = get_type_and_name(&statement)?;
let dependency_index = order.iter().position(|r| r.name == resource_name && r.resource_type == resource_type).ok_or(DscError::Validation("Dependency not found in order".to_string()))?;
if resource_index < dependency_index {
return Err(DscError::Validation(format!("Circular dependency detected for resource named '{0}'", resource.name)));
}
}
}

Expand All @@ -87,8 +79,18 @@ pub fn get_resource_invocation_order(config: &Configuration) -> Result<Vec<Resou
Ok(order)
}

fn get_type_and_name(statement: &str) -> Result<(&str, &str), DscError> {
let parts: Vec<&str> = statement.split(':').collect();
if parts.len() != 2 {
return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect: {statement}")));
}
Ok((parts[0], parts[1]))
}

#[cfg(test)]
mod tests {
use crate::parser;

use super::*;

#[test]
Expand All @@ -105,7 +107,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config).unwrap();
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser).unwrap();
assert_eq!(order[0].name, "First");
assert_eq!(order[1].name, "Second");
}
Expand All @@ -126,7 +129,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config);
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser);
assert!(order.is_err());
}

Expand All @@ -142,7 +146,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config);
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser);
assert!(order.is_err());
}

Expand All @@ -164,7 +169,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config).unwrap();
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser).unwrap();
assert_eq!(order[0].name, "First");
assert_eq!(order[1].name, "Second");
assert_eq!(order[2].name, "Third");
Expand All @@ -186,7 +192,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config);
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser);
assert!(order.is_err());
}

Expand All @@ -207,7 +214,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config).unwrap();
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser).unwrap();
assert_eq!(order[0].name, "First");
assert_eq!(order[1].name, "Second");
assert_eq!(order[2].name, "Third");
Expand All @@ -234,7 +242,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config);
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser);
assert!(order.is_err());
}

Expand All @@ -261,7 +270,8 @@ mod tests {
"#;

let config: Configuration = serde_yaml::from_str(config_yaml).unwrap();
let order = get_resource_invocation_order(&config).unwrap();
let mut parser = parser::Statement::new().unwrap();
let order = get_resource_invocation_order(&config, &mut parser).unwrap();
assert_eq!(order[0].name, "First");
assert_eq!(order[1].name, "Second");
assert_eq!(order[2].name, "Third");
Expand Down
Loading