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

DSC meta configuration #574

Merged
merged 27 commits into from
Nov 8, 2024
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
10 changes: 8 additions & 2 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ $filesForWindowsPackage = @(
'wmi.resource.ps1',
'windows_baseline.dsc.yaml',
'windows_inventory.dsc.yaml'
'dsc_default.settings.json',
'dsc.settings.json'
)

$filesForLinuxPackage = @(
Expand All @@ -62,7 +64,9 @@ $filesForLinuxPackage = @(
'powershell.dsc.resource.json',
'psDscAdapter/',
'RunCommandOnSet.dsc.resource.json',
'runcommandonset'
'runcommandonset',
'dsc_default.settings.json',
'dsc.settings.json'
)

$filesForMacPackage = @(
Expand All @@ -77,7 +81,9 @@ $filesForMacPackage = @(
'powershell.dsc.resource.json',
'psDscAdapter/',
'RunCommandOnSet.dsc.resource.json',
'runcommandonset'
'runcommandonset',
'dsc_default.settings.json',
'dsc.settings.json'
)

# the list of files other than the binaries which need to be executable
Expand Down
2 changes: 2 additions & 0 deletions dsc/copy_files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dsc.settings.json
dsc_default.settings.json
12 changes: 12 additions & 0 deletions dsc/dsc.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"resourcePath": {
"allowEnvOverride": true,
"appendEnvPath": true,
"directories": []
},
"tracing": {
"level": "WARN",
"format": "Default",
"allowOverride": true
}
}
14 changes: 14 additions & 0 deletions dsc/dsc_default.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"1": {
"resourcePath": {
"allowEnvOverride": true,
"appendEnvPath": true,
"directories": []
},
"tracing": {
"level": "WARN",
"format": "Default",
"allowOverride": true
}
}
}
7 changes: 4 additions & 3 deletions dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use dsc_lib::dscresources::command_resource::TraceLevel;
use serde::Deserialize;

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
Expand All @@ -12,7 +13,7 @@ pub enum OutputFormat {
Yaml,
}

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum, Deserialize)]
pub enum TraceFormat {
Default,
Plaintext,
Expand All @@ -29,8 +30,8 @@ pub struct Args {
pub subcommand: SubCommand,
#[clap(short = 'l', long, help = "Trace level to use", value_enum)]
pub trace_level: Option<TraceLevel>,
#[clap(short = 'f', long, help = "Trace format to use", value_enum, default_value = "default")]
pub trace_format: TraceFormat,
#[clap(short = 'f', long, help = "Trace format to use", value_enum)]
pub trace_format: Option<TraceFormat>,
}

#[derive(Debug, PartialEq, Eq, Subcommand)]
Expand Down
117 changes: 94 additions & 23 deletions dsc/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ use dsc_lib::{
}, resource_manifest::ResourceManifest
},
util::parse_input_to_json,
util::get_setting,
};
use jsonschema::Validator;
use path_absolutize::Absolutize;
use schemars::{schema_for, schema::RootSchema};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::env;
Expand All @@ -39,7 +41,7 @@ use syntect::{
parsing::SyntaxSet,
util::{as_24_bit_terminal_escaped, LinesWithEndings}
};
use tracing::{Level, debug, error, warn, trace};
use tracing::{Level, debug, error, info, warn, trace};
use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, Layer};
use tracing_indicatif::IndicatifLayer;

Expand All @@ -55,6 +57,27 @@ pub const EXIT_DSC_RESOURCE_NOT_FOUND: i32 = 7;
pub const DSC_CONFIG_ROOT: &str = "DSC_CONFIG_ROOT";
pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL";

#[derive(Deserialize)]
pub struct TracingSetting {
/// Trace level to use - see pub enum `TraceLevel` in `dsc_lib\src\dscresources\command_resource.rs`
level: TraceLevel,
anmenaga marked this conversation as resolved.
Show resolved Hide resolved
/// Trace format to use - see pub enum `TraceFormat` in `dsc\src\args.rs`
format: TraceFormat,
/// Whether the 'level' can be overrridden by `DSC_TRACE_LEVEL` environment variable
#[serde(rename = "allowOverride")]
allow_override: bool
}

impl Default for TracingSetting {
fn default() -> TracingSetting {
TracingSetting {
level: TraceLevel::Warn,
format: TraceFormat::Default,
allow_override: true,
}
}
}

/// Get string representation of JSON value.
///
/// # Arguments
Expand Down Expand Up @@ -268,46 +291,92 @@ pub fn write_output(json: &str, format: &Option<OutputFormat>) {
}
}

pub fn enable_tracing(trace_level: &Option<TraceLevel>, trace_format: &TraceFormat) {
let tracing_level = match trace_level {
Some(level) => level,
None => {
// use DSC_TRACE_LEVEL env var if set
match env::var(DSC_TRACE_LEVEL) {
Ok(level) => {
match level.to_ascii_uppercase().as_str() {
"ERROR" => &TraceLevel::Error,
"WARN" => &TraceLevel::Warn,
"INFO" => &TraceLevel::Info,
"DEBUG" => &TraceLevel::Debug,
"TRACE" => &TraceLevel::Trace,
_ => {
warn!("Invalid DSC_TRACE_LEVEL value '{level}', defaulting to 'warn'");
&TraceLevel::Warn
},
}
#[allow(clippy::too_many_lines)]
pub fn enable_tracing(trace_level_arg: &Option<TraceLevel>, trace_format_arg: &Option<TraceFormat>) {

let mut policy_is_used = false;
let mut tracing_setting = TracingSetting::default();

let default_filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("warn"))
.unwrap_or_default()
.add_directive(Level::WARN.into());
let default_indicatif_layer = IndicatifLayer::new();
let default_layer = tracing_subscriber::fmt::Layer::default().with_writer(default_indicatif_layer.get_stderr_writer());
let default_fmt = default_layer
.with_ansi(true)
.with_level(true)
.boxed();
let default_subscriber = tracing_subscriber::Registry::default().with(default_fmt).with(default_filter).with(default_indicatif_layer);
let default_guard = tracing::subscriber::set_default(default_subscriber);

// read setting/policy from files
if let Ok(v) = get_setting("tracing") {
if v.policy != serde_json::Value::Null {
match serde_json::from_value::<TracingSetting>(v.policy) {
Ok(v) => {
tracing_setting = v;
policy_is_used = true;
},
Err(e) => { error!("{e}"); }
}
} else if v.setting != serde_json::Value::Null {
match serde_json::from_value::<TracingSetting>(v.setting) {
Ok(v) => {
tracing_setting = v;
},
Err(_) => &TraceLevel::Warn,
Err(e) => { error!("{e}"); }
}
}
} else {
error!("Could not read 'tracing' setting");
}

// override with DSC_TRACE_LEVEL env var if permitted
if tracing_setting.allow_override {
if let Ok(level) = env::var(DSC_TRACE_LEVEL) {
tracing_setting.level = match level.to_ascii_uppercase().as_str() {
"ERROR" => TraceLevel::Error,
"WARN" => TraceLevel::Warn,
"INFO" => TraceLevel::Info,
"DEBUG" => TraceLevel::Debug,
"TRACE" => TraceLevel::Trace,
_ => {
warn!("Invalid DSC_TRACE_LEVEL value '{level}', defaulting to 'warn'");
TraceLevel::Warn
}
}
}
}

// command-line args override setting value, but not policy
if !policy_is_used {
if let Some(v) = trace_level_arg {
tracing_setting.level = v.clone();
};
if let Some(v) = trace_format_arg {
tracing_setting.format = v.clone();
};
};

let tracing_level = match tracing_level {
// convert to 'tracing' crate type
let tracing_level = match tracing_setting.level {
TraceLevel::Error => Level::ERROR,
TraceLevel::Warn => Level::WARN,
TraceLevel::Info => Level::INFO,
TraceLevel::Debug => Level::DEBUG,
TraceLevel::Trace => Level::TRACE,
};

// enable tracing
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("warning"))
.or_else(|_| EnvFilter::try_new("warn"))
.unwrap_or_default()
.add_directive(tracing_level.into());
let indicatif_layer = IndicatifLayer::new();
let layer = tracing_subscriber::fmt::Layer::default().with_writer(indicatif_layer.get_stderr_writer());
let with_source = tracing_level == Level::DEBUG || tracing_level == Level::TRACE;
let fmt = match trace_format {
let fmt = match tracing_setting.format {
TraceFormat::Default => {
layer
.with_ansi(true)
Expand Down Expand Up @@ -337,12 +406,14 @@ pub fn enable_tracing(trace_level: &Option<TraceLevel>, trace_format: &TraceForm

let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter).with(indicatif_layer);

drop(default_guard);
if tracing::subscriber::set_global_default(subscriber).is_err() {
eprintln!("Unable to set global default tracing subscriber. Tracing is diabled.");
}

// set DSC_TRACE_LEVEL for child processes
env::set_var(DSC_TRACE_LEVEL, tracing_level.to_string().to_ascii_lowercase());
info!("Trace-level is {:?}", tracing_setting.level);
}

/// Validate the JSON against the schema.
Expand Down
109 changes: 109 additions & 0 deletions dsc/tests/dsc_settings.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'tests for dsc settings' {
BeforeAll {

$script:policyFilePath = if ($IsWindows) {
Join-Path $env:ProgramData "dsc" "dsc.settings.json"
} else {
"/etc/dsc/dsc.settings.json"
}

$script:dscHome = (Get-Command dsc).Path | Split-Path
$script:dscSettingsFilePath = Join-Path $script:dscHome "dsc.settings.json"
$script:dscDefaultSettingsFilePath = Join-Path $script:dscHome "dsc_default.settings.json"

if ($IsWindows) { #"Setting policy on Linux requires sudo"
$script:policyDirPath = $script:policyFilePath | Split-Path
New-Item -ItemType Directory -Path $script:policyDirPath | Out-Null
}

#create backups of settings files
$script:dscSettingsFilePath_backup = Join-Path $script:dscHome "dsc.settings.json.backup"
$script:dscDefaultSettingsFilePath_backup = Join-Path $script:dscHome "dsc_default.settings.json.backup"
Copy-Item -Force -Path $script:dscSettingsFilePath -Destination $script:dscSettingsFilePath_backup
Copy-Item -Force -Path $script:dscDefaultSettingsFilePath -Destination $script:dscDefaultSettingsFilePath_backup
}

AfterAll {
Remove-Item -Force -Path $script:dscSettingsFilePath_backup
Remove-Item -Force -Path $script:dscDefaultSettingsFilePath_backup
if ($IsWindows) { #"Setting policy on Linux requires sudo"
Remove-Item -Recurse -Force -Path $script:policyDirPath
}
}

BeforeEach {
$script:dscDefaultSettings = Get-Content -Raw -Path $script:dscDefaultSettingsFilePath_backup | ConvertFrom-Json
$script:dscDefaultv1Settings = (Get-Content -Raw -Path $script:dscDefaultSettingsFilePath_backup | ConvertFrom-Json)."1"
}

AfterEach {
Copy-Item -Force -Path $script:dscSettingsFilePath_backup -Destination $script:dscSettingsFilePath
Copy-Item -Force -Path $script:dscDefaultSettingsFilePath_backup -Destination $script:dscDefaultSettingsFilePath
if ($IsWindows) { #"Setting policy on Linux requires sudo"
Remove-Item -Path $script:policyFilePath -ErrorAction SilentlyContinue
}
}

It 'ensure a new tracing value in settings has effect' {

$script:dscDefaultv1Settings."tracing"."level" = "TRACE"
$script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscSettingsFilePath

dsc resource list 2> $TestDrive/tracing.txt
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace"
}

It 'ensure a new resource_path value in settings has effect' {

$script:dscDefaultv1Settings."resourcePath"."directories" = @("TestDir1","TestDir2")
$script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscSettingsFilePath
dsc -l debug resource list 2> $TestDrive/tracing.txt
$expectedString = 'Using Resource Path: "TestDir1'+[System.IO.Path]::PathSeparator+'TestDir2'
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly $expectedString
}

It 'Confirm settings override priorities' {

if (! $IsWindows) {
Set-ItResult -Skip -Because "Setting policy requires sudo"
return
}

$script:dscDefaultv1Settings."tracing"."level" = "TRACE"
$script:dscDefaultv1Settings."resourcePath"."directories" = @("PolicyDir")
$script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:policyFilePath

$script:dscDefaultv1Settings."tracing"."level" = "TRACE"
$script:dscDefaultv1Settings."resourcePath"."directories" = @("SettingsDir")
$script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscSettingsFilePath

$script:dscDefaultSettings."1"."tracing"."level" = "TRACE"
$script:dscDefaultSettings."1"."resourcePath"."directories" = @("Defaultv1SettingsDir")
$script:dscDefaultSettings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscDefaultSettingsFilePath

# ensure policy overrides everything
dsc -l debug resource list 2> $TestDrive/tracing.txt
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace"
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "PolicyDir'

# without policy, command-line args have priority
Remove-Item -Path $script:policyFilePath
dsc -l debug resource list 2> $TestDrive/tracing.txt
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Debug"
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "SettingsDir'

# without policy and command-line args, settings file is used
dsc resource list 2> $TestDrive/tracing.txt
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace"
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "SettingsDir'

# without policy and command-line args and settings file, the default settings file is used
Remove-Item -Path $script:dscSettingsFilePath
dsc resource list 2> $TestDrive/tracing.txt
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace"
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "Defaultv1SettingsDir'
}
}
Loading
Loading