From 4bb61b815334a3b4d386073bee85539460a502de Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 28 Sep 2023 14:03:47 -0700 Subject: [PATCH 1/9] initial draft --- dsc/Cargo.toml | 3 ++- dsc/src/main.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index cd2f2326..910854e1 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -14,7 +14,7 @@ lto = true atty = { version = "0.2" } clap = { version = "4.1", features = ["derive"] } clap_complete = { version = "4.4" } -crossterm = { version = "0.26.1" } +crossterm = { version = "0.27" } ctrlc = { version = "3.4.0" } dsc_lib = { path = "../dsc_lib" } jsonschema = "0.17" @@ -23,6 +23,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = { version = "0.9" } syntect = { version = "5.0", features = ["default-fancy"], default-features = false } +sysinfo = { version = "0.29" } thiserror = "1.0" tracing = "0.1.37" tracing-subscriber = "0.3.17" diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 1ba75ab0..ea38ff60 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -7,6 +7,7 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use std::io::{self, Read}; use std::process::exit; +use sysinfo::{Pid, Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; use tracing::{error, info}; #[cfg(debug_assertions)] @@ -79,9 +80,26 @@ fn main() { fn ctrlc_handler() { error!("Ctrl-C received"); + + // get process tree for current process and terminate all processes + let mut sys = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + let Ok(current_pid) = get_current_pid() else { + eprintln!("Could not get current process id"); + exit(util::EXIT_CTRL_C); + }; + let Some(current_process) = sys.process(current_pid) else { + eprintln!("Could not get current process"); + exit(util::EXIT_CTRL_C); + }; + + terminate_subprocesses(sys, current_process); exit(util::EXIT_CTRL_C); } +fn terminate_subprocesses(sys: &System, process: &Process) { + for subprocess in process. +} + #[cfg(debug_assertions)] fn check_debug() { if env::var("DEBUG_DSC").is_ok() { From 381ffbea6b2ba3c244e3f6f0b1a44d8c2d46bf40 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 29 Sep 2023 13:12:10 -0700 Subject: [PATCH 2/9] add support to terminate a subprocesses on ctrl+c --- dsc/Cargo.toml | 2 +- dsc/src/main.rs | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index 910854e1..5e9498c2 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = { version = "0.9" } syntect = { version = "5.0", features = ["default-fancy"], default-features = false } -sysinfo = { version = "0.29" } +sysinfo = { version = "0.29.10" } thiserror = "1.0" tracing = "0.1.37" tracing-subscriber = "0.3.17" diff --git a/dsc/src/main.rs b/dsc/src/main.rs index ea38ff60..8a4a4b05 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -92,12 +92,27 @@ fn ctrlc_handler() { exit(util::EXIT_CTRL_C); }; - terminate_subprocesses(sys, current_process); + terminate_subprocesses(&sys, ¤t_process); exit(util::EXIT_CTRL_C); } fn terminate_subprocesses(sys: &System, process: &Process) { - for subprocess in process. + for subprocess in sys.processes().values().filter_map(|p| + if let Some(parent_pid) = p.parent() { + if parent_pid == process.pid() { + Some(p) + } else { + None + } + } else { + None + } + ) + + terminate_subprocesses(sys, subprocess); + } + + process.kill(); } #[cfg(debug_assertions)] From 1a7d315ac18a8a8a9ce5df22ef33882784c39805 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 29 Sep 2023 13:22:49 -0700 Subject: [PATCH 3/9] fix clippy --- dsc/src/main.rs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 8a4a4b05..721acf5c 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -7,8 +7,8 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use std::io::{self, Read}; use std::process::exit; -use sysinfo::{Pid, Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; use tracing::{error, info}; +use sysinfo::{Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; #[cfg(debug_assertions)] use crossterm::event; @@ -82,7 +82,7 @@ fn ctrlc_handler() { error!("Ctrl-C received"); // get process tree for current process and terminate all processes - let mut sys = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + let sys = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); let Ok(current_pid) = get_current_pid() else { eprintln!("Could not get current process id"); exit(util::EXIT_CTRL_C); @@ -92,24 +92,13 @@ fn ctrlc_handler() { exit(util::EXIT_CTRL_C); }; - terminate_subprocesses(&sys, ¤t_process); + terminate_subprocesses(&sys, current_process); exit(util::EXIT_CTRL_C); } fn terminate_subprocesses(sys: &System, process: &Process) { - for subprocess in sys.processes().values().filter_map(|p| - if let Some(parent_pid) = p.parent() { - if parent_pid == process.pid() { - Some(p) - } else { - None - } - } else { - None - } - ) - - terminate_subprocesses(sys, subprocess); + for subprocess in sys.processes().values().filter(|p| p.parent().map_or(false, |parent| parent == process.pid())) { + terminate_subprocesses(sys, subprocess); } process.kill(); From bdb42dcee47298977f916545c084b4b079df7112 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 29 Sep 2023 14:46:13 -0700 Subject: [PATCH 4/9] add tracing --- dsc/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 721acf5c..31e525d8 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -9,6 +9,7 @@ use std::io::{self, Read}; use std::process::exit; use tracing::{error, info}; use sysinfo::{Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; +use tracing::{error, debug}; #[cfg(debug_assertions)] use crossterm::event; @@ -27,7 +28,9 @@ fn main() { // create subscriber that writes all events to stderr let subscriber = tracing_subscriber::fmt().pretty().with_writer(std::io::stderr).finish(); - let _ = tracing::subscriber::set_global_default(subscriber).map_err(|_err| eprintln!("Unable to set global default subscriber")); + if tracing::subscriber::set_global_default(subscriber).is_err() { + eprintln!("Unable to set global default subscriber"); + } if ctrlc::set_handler(ctrlc_handler).is_err() { error!("Error: Failed to set Ctrl-C handler"); @@ -101,7 +104,10 @@ fn terminate_subprocesses(sys: &System, process: &Process) { terminate_subprocesses(sys, subprocess); } - process.kill(); + debug!("Terminating process {} {}", process.name(), process.pid()); + if !process.kill() { + debug!("Failed to terminate process {} {}", process.name(), process.pid()); + } } #[cfg(debug_assertions)] From 4e16ed70b90d1c61dfc21714f5cd11705cadb5fc Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Sat, 30 Sep 2023 14:19:01 -0700 Subject: [PATCH 5/9] add dsctest resource --- .vscode/settings.json | 3 +- build.ps1 | 29 +++--- dsc/Cargo.toml | 1 + dsc/src/main.rs | 18 ++-- dsc_lib/src/dscresources/command_resource.rs | 91 +++++++++++++++---- dsc_lib/src/dscresources/resource_manifest.rs | 3 + tools/dsctest/Cargo.toml | 12 +++ tools/dsctest/README.md | 14 +++ tools/dsctest/dscsleep.dsc.resource.json | 48 ++++++++++ tools/dsctest/src/args.rs | 32 +++++++ tools/dsctest/src/main.rs | 38 ++++++++ tools/dsctest/src/sleep.rs | 13 +++ .../test_group_resource}/Cargo.toml | 2 +- .../test_group_resource}/src/args.rs | 0 .../test_group_resource}/src/main.rs | 0 .../testGroup.dsc.resource.json | 0 .../tests/provider.tests.ps1 | 0 17 files changed, 265 insertions(+), 39 deletions(-) create mode 100644 tools/dsctest/Cargo.toml create mode 100644 tools/dsctest/README.md create mode 100644 tools/dsctest/dscsleep.dsc.resource.json create mode 100644 tools/dsctest/src/args.rs create mode 100644 tools/dsctest/src/main.rs create mode 100644 tools/dsctest/src/sleep.rs rename {test_group_resource => tools/test_group_resource}/Cargo.toml (88%) rename {test_group_resource => tools/test_group_resource}/src/args.rs (100%) rename {test_group_resource => tools/test_group_resource}/src/main.rs (100%) rename {test_group_resource => tools/test_group_resource}/testGroup.dsc.resource.json (100%) rename {test_group_resource => tools/test_group_resource}/tests/provider.tests.ps1 (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 36b80c4d..195513ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,8 @@ "./osinfo/Cargo.toml", "./pal/Cargo.toml", "./registry/Cargo.toml", - "./test_group_resource/Cargo.toml", + "./tools/test_group_resource/Cargo.toml", + "./tools/dsctest/Cargo.toml", "./y2j/Cargo.toml" ], "rust-analyzer.showUnlinkedFileNotification": true, diff --git a/build.ps1 b/build.ps1 index ec49a162..a01f8558 100644 --- a/build.ps1 +++ b/build.ps1 @@ -90,8 +90,9 @@ if (Test-Path $target) { } New-Item -ItemType Directory $target > $null +# make sure dependencies are built first so clippy runs correctly $windows_projects = @("pal", "ntreg", "ntstatuserror", "ntuserinfo", "registry") -$projects = @("dsc_lib", "dsc", "osinfo", "process", "test_group_resource", "y2j", "powershellgroup") +$projects = @("dsc_lib", "dsc", "osinfo", "process", "tools/test_group_resource", "y2j", "powershellgroup", "tools/dsctest") $pedantic_unclean_projects = @("ntreg") if ($IsWindows) { @@ -126,11 +127,13 @@ foreach ($project in $projects) { $failed = $true } + $binary = Split-Path $project -Leaf + if ($IsWindows) { - Copy-Item "$path/$project.exe" $target -ErrorAction Ignore + Copy-Item "$path/$binary.exe" $target -ErrorAction Ignore } else { - Copy-Item "$path/$project" $target -ErrorAction Ignore + Copy-Item "$path/$binary" $target -ErrorAction Ignore } Copy-Item "*.dsc.resource.json" $target -Force -ErrorAction Ignore @@ -152,6 +155,16 @@ Copy-Item $PSScriptRoot/tools/add-path.ps1 $target -Force -ErrorAction Ignore $relative = Resolve-Path $target -Relative Write-Host -ForegroundColor Green "`nEXE's are copied to $target ($relative)" +# remove the other target in case switching between them +$dirSeparator = [System.IO.Path]::DirectorySeparatorChar +if ($Release) { + $oldTarget = $target.Replace($dirSeparator + 'release', $dirSeparator + 'debug') +} +else { + $oldTarget = $target.Replace($dirSeparator + 'debug', $dirSeparator + 'release') +} +$env:PATH = $env:PATH.Replace($oldTarget, '') + $paths = $env:PATH.Split([System.IO.Path]::PathSeparator) $found = $false foreach ($path in $paths) { @@ -161,14 +174,8 @@ foreach ($path in $paths) { } } -# remove the other target in case switching between them -if ($Release) { - $oldTarget = $target.Replace('\release', '\debug') -} -else { - $oldTarget = $target.Replace('\debug', '\release') -} -$env:PATH = $env:PATH.Replace(';' + $oldTarget, '') +# remove empty entries from path +$env:PATH = [string]::Join([System.IO.Path]::PathSeparator, $env:PATH.Split([System.IO.Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)) if (!$found) { Write-Host -ForegroundCOlor Yellow "Adding $target to `$env:PATH" diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index 5e9498c2..4c24e226 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -26,4 +26,5 @@ syntect = { version = "5.0", features = ["default-fancy"], default-features = fa sysinfo = { version = "0.29.10" } thiserror = "1.0" tracing = "0.1.37" +tracing-appender = "0.2.2" tracing-subscriber = "0.3.17" diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 31e525d8..538c3bd0 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -9,7 +9,7 @@ use std::io::{self, Read}; use std::process::exit; use tracing::{error, info}; use sysinfo::{Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; -use tracing::{error, debug}; +use tracing::{error, info, warn}; #[cfg(debug_assertions)] use crossterm::event; @@ -27,7 +27,8 @@ fn main() { check_debug(); // create subscriber that writes all events to stderr - let subscriber = tracing_subscriber::fmt().pretty().with_writer(std::io::stderr).finish(); + let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stderr()); + let subscriber = tracing_subscriber::fmt().pretty().with_writer(non_blocking).finish(); if tracing::subscriber::set_global_default(subscriber).is_err() { eprintln!("Unable to set global default subscriber"); } @@ -82,16 +83,18 @@ fn main() { } fn ctrlc_handler() { - error!("Ctrl-C received"); + warn!("Ctrl-C received"); // get process tree for current process and terminate all processes let sys = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + info!("Found {} processes", sys.processes().len()); let Ok(current_pid) = get_current_pid() else { - eprintln!("Could not get current process id"); + error!("Could not get current process id"); exit(util::EXIT_CTRL_C); }; + info!("Current process id: {}", current_pid); let Some(current_process) = sys.process(current_pid) else { - eprintln!("Could not get current process"); + error!("Could not get current process"); exit(util::EXIT_CTRL_C); }; @@ -100,13 +103,14 @@ fn ctrlc_handler() { } fn terminate_subprocesses(sys: &System, process: &Process) { + info!("Terminating subprocesses of process {} {}", process.name(), process.pid()); for subprocess in sys.processes().values().filter(|p| p.parent().map_or(false, |parent| parent == process.pid())) { terminate_subprocesses(sys, subprocess); } - debug!("Terminating process {} {}", process.name(), process.pid()); + info!("Terminating process {} {}", process.name(), process.pid()); if !process.kill() { - debug!("Failed to terminate process {} {}", process.name(), process.pid()); + info!("Failed to terminate process {} {}", process.name(), process.pid()); } } diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 2b759128..542095be 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -6,7 +6,7 @@ use serde_json::Value; use std::{collections::HashMap, process::Command, io::{Write, Read}, process::Stdio}; use crate::dscerror::DscError; use super::{dscresource::get_diff,resource_manifest::{ResourceManifest, InputKind, ReturnKind, SchemaKind}, invoke_result::{GetResult, SetResult, TestResult, ValidateResult, ExportResult}}; -use tracing::debug; +use tracing::{debug, info}; pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; @@ -30,18 +30,25 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul let mut env: Option> = None; let mut input_filter: Option<&str> = None; + let mut get_args = resource.get.args.clone(); if !filter.is_empty() { verify_json(resource, cwd, filter)?; - if input_kind == InputKind::Env { - env = Some(json_to_hashmap(filter)?); - } - else { - input_filter = Some(filter); + match input_kind { + InputKind::Env => { + env = Some(json_to_hashmap(filter)?); + }, + InputKind::Stdin => { + input_filter = Some(filter); + }, + InputKind::Arg(arg_name) => { + replace_token(&mut get_args, &arg_name, filter)?; + }, } } - let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, resource.get.args.clone(), input_filter, Some(cwd), env)?; + info!("Invoking get {} using {}", &resource.resource_type, &resource.get.executable); + let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, input_filter, Some(cwd), env)?; if exit_code != 0 { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } @@ -69,6 +76,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul /// # Errors /// /// Error returned if the resource does not successfully set the desired state +#[allow(clippy::too_many_lines)] pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool) -> Result { let Some(set) = resource.set.as_ref() else { return Err(DscError::NotImplemented("set".to_string())); @@ -77,15 +85,22 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te let mut env: Option> = None; let mut input_desired: Option<&str> = None; - if set.input == InputKind::Env { - env = Some(json_to_hashmap(desired)?); - } - else { - input_desired = Some(desired); + let mut args = set.args.clone(); + match &set.input { + InputKind::Env => { + env = Some(json_to_hashmap(desired)?); + }, + InputKind::Stdin => { + input_desired = Some(desired); + }, + InputKind::Arg(arg_token) => { + replace_token(&mut args, arg_token, desired)?; + }, } // if resource doesn't implement a pre-test, we execute test first to see if a set is needed if !skip_test && !set.pre_test.unwrap_or_default() { + info!("No pretest, invoking test {}", &resource.resource_type); let test_result = invoke_test(resource, cwd, desired)?; if test_result.in_desired_state { return Ok(SetResult { @@ -98,6 +113,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te let mut get_env: Option> = None; let mut get_input: Option<&str> = None; + let mut get_args = resource.get.args.clone(); match &resource.get.input { Some(InputKind::Env) => { get_env = Some(json_to_hashmap(desired)?); @@ -105,12 +121,16 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te Some(InputKind::Stdin) => { get_input = Some(desired); }, + Some(InputKind::Arg(arg_token)) => { + replace_token(&mut get_args, arg_token, desired)?; + }, None => { // leave input as none }, } - let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, resource.get.args.clone(), get_input, Some(cwd), get_env)?; + info!("Getting current state for set by invoking get {} using {}", &resource.resource_type, &resource.get.executable); + let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, get_args, get_input, Some(cwd), get_env)?; if exit_code != 0 { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } @@ -122,6 +142,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); }; + info!("Invoking set {} using {}", &resource.resource_type, &set.executable); let (exit_code, stdout, stderr) = invoke_command(&set.executable, set.args.clone(), input_desired, Some(cwd), env)?; if exit_code != 0 { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); @@ -195,14 +216,21 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re let mut env: Option> = None; let mut input_expected: Option<&str> = None; - if test.input == InputKind::Env { - env = Some(json_to_hashmap(expected)?); - } - else { - input_expected = Some(expected); + let mut args = test.args.clone(); + match &test.input { + InputKind::Env => { + env = Some(json_to_hashmap(expected)?); + }, + InputKind::Stdin => { + input_expected = Some(expected); + }, + InputKind::Arg(arg_token) => { + replace_token(&mut args, arg_token, expected)?; + }, } - let (exit_code, stdout, stderr) = invoke_command(&test.executable, test.args.clone(), input_expected, Some(cwd), env)?; + info!("Invoking test {} using {}", &resource.resource_type, &test.executable); + let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, input_expected, Some(cwd), env)?; if exit_code != 0 { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } @@ -381,6 +409,7 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str) -> Result>, input: Option<&str>, cwd: Option<&str>, env: Option>) -> Result<(i32, String, String), DscError> { + debug!("Invoking command {} with args {:?}", executable, args); let mut command = Command::new(executable); if input.is_some() { command.stdin(Stdio::piped()); @@ -422,12 +451,36 @@ pub fn invoke_command(executable: &str, args: Option>, input: Option let exit_status = child.wait()?; + // sleep for 5 seconds + info!("Sleeping for 5 seconds"); + std::thread::sleep(std::time::Duration::from_secs(5)); + let exit_code = exit_status.code().unwrap_or(EXIT_PROCESS_TERMINATED); let stdout = String::from_utf8_lossy(&stdout_buf).to_string(); let stderr = String::from_utf8_lossy(&stderr_buf).to_string(); Ok((exit_code, stdout, stderr)) } +fn replace_token(args: &mut Option>, token: &str, value: &str) -> Result<(), DscError> { + let Some(arg_values) = args else { + return Err(DscError::Operation("No args to replace".to_string())); + }; + + let mut found = false; + for arg in arg_values { + if arg == token { + found = true; + *arg = value.to_string(); + } + } + + if !found { + return Err(DscError::Operation(format!("Token {token} not found in args"))); + } + + Ok(()) +} + fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), DscError> { debug!("resource_type - {}", resource.resource_type); diff --git a/dsc_lib/src/dscresources/resource_manifest.rs b/dsc_lib/src/dscresources/resource_manifest.rs index 568bc89b..760cd35a 100644 --- a/dsc_lib/src/dscresources/resource_manifest.rs +++ b/dsc_lib/src/dscresources/resource_manifest.rs @@ -50,6 +50,9 @@ pub struct ResourceManifest { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] pub enum InputKind { + /// The input replaces arguments with this token in the command. + #[serde(rename = "arg")] + Arg(String), /// The input is accepted as environmental variables. #[serde(rename = "env")] Env, diff --git a/tools/dsctest/Cargo.toml b/tools/dsctest/Cargo.toml new file mode 100644 index 00000000..3f2d5f50 --- /dev/null +++ b/tools/dsctest/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dsctest" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.1", features = ["derive"] } +schemars = { version = "0.8" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/tools/dsctest/README.md b/tools/dsctest/README.md new file mode 100644 index 00000000..c03a1a7e --- /dev/null +++ b/tools/dsctest/README.md @@ -0,0 +1,14 @@ +# DSCTest Resource + +## Sleep + +Example config: + +```yaml +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: +- name: Sleep1 + type: Test/Sleep + properties: + seconds: 30 +``` diff --git a/tools/dsctest/dscsleep.dsc.resource.json b/tools/dsctest/dscsleep.dsc.resource.json new file mode 100644 index 00000000..6d8ce7fd --- /dev/null +++ b/tools/dsctest/dscsleep.dsc.resource.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json", + "type": "Test/Sleep", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "args": [ + "sleep", + "--input", + "{json}" + ], + "input": { + "arg": "{json}" + } + }, + "set": { + "executable": "dsctest", + "args": [ + "sleep", + "--input", + "{json}" + ], + "input": { + "arg": "{json}" + } + }, + "test": { + "executable": "dsctest", + "args": [ + "sleep", + "--input", + "{json}" + ], + "input": { + "arg": "{json}" + } + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "sleep" + ] + } + } +} diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs new file mode 100644 index 00000000..310561bf --- /dev/null +++ b/tools/dsctest/src/args.rs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use clap::{Parser, Subcommand, ValueEnum}; + +#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] +pub enum Schemas { + Sleep, +} + +#[derive(Debug, Parser)] +#[clap(name = "dscrtest", version = "0.1.0", about = "Test resource", long_about = None)] +pub struct Args { + /// The subcommand to run + #[clap(subcommand)] + pub subcommand: SubCommand, +} + +#[derive(Debug, PartialEq, Eq, Subcommand)] +pub enum SubCommand { + #[clap(name = "schema", about = "Get the JSON schema for a subcommand")] + Schema { + #[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")] + subcommand: Schemas, + }, + + #[clap(name = "sleep", about = "Sleep for a specified number of seconds")] + Sleep { + #[clap(name = "input", short, long, help = "The input to the sleep command")] + input: String, + }, +} diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs new file mode 100644 index 00000000..6c4eb32d --- /dev/null +++ b/tools/dsctest/src/main.rs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod args; +mod sleep; + +use args::{Args, Schemas, SubCommand}; +use clap::Parser; +use schemars::schema_for; +use crate::sleep::Sleep; +use std::{thread, time::Duration}; + +fn main() { + let args = Args::parse(); + let json = match args.subcommand { + SubCommand::Schema { subcommand } => { + match subcommand { + Schemas::Sleep => { + let schema = schema_for!(Sleep); + serde_json::to_string(&schema).unwrap() + }, + } + }, + SubCommand::Sleep { input } => { + let sleep = match serde_json::from_str::(&input) { + Ok(sleep) => sleep, + Err(err) => { + eprintln!("Error JSON does not match schema: {err}"); + std::process::exit(1); + } + }; + thread::sleep(Duration::from_secs(sleep.seconds)); + serde_json::to_string(&sleep).unwrap() + }, + }; + + println!("{json}"); +} diff --git a/tools/dsctest/src/sleep.rs b/tools/dsctest/src/sleep.rs new file mode 100644 index 00000000..5b5a853a --- /dev/null +++ b/tools/dsctest/src/sleep.rs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Returns information about the operating system. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct Sleep { + /// Number of seconds to sleep + pub seconds: u64, +} diff --git a/test_group_resource/Cargo.toml b/tools/test_group_resource/Cargo.toml similarity index 88% rename from test_group_resource/Cargo.toml rename to tools/test_group_resource/Cargo.toml index 60553f31..47661a1d 100644 --- a/test_group_resource/Cargo.toml +++ b/tools/test_group_resource/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] clap = { version = "4.1", features = ["derive"] } -dsc_lib = { path = "../dsc_lib" } +dsc_lib = { path = "../../dsc_lib" } schemars = { version = "0.8.12" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/test_group_resource/src/args.rs b/tools/test_group_resource/src/args.rs similarity index 100% rename from test_group_resource/src/args.rs rename to tools/test_group_resource/src/args.rs diff --git a/test_group_resource/src/main.rs b/tools/test_group_resource/src/main.rs similarity index 100% rename from test_group_resource/src/main.rs rename to tools/test_group_resource/src/main.rs diff --git a/test_group_resource/testGroup.dsc.resource.json b/tools/test_group_resource/testGroup.dsc.resource.json similarity index 100% rename from test_group_resource/testGroup.dsc.resource.json rename to tools/test_group_resource/testGroup.dsc.resource.json diff --git a/test_group_resource/tests/provider.tests.ps1 b/tools/test_group_resource/tests/provider.tests.ps1 similarity index 100% rename from test_group_resource/tests/provider.tests.ps1 rename to tools/test_group_resource/tests/provider.tests.ps1 From 77267f9bf9cd01c1eff1a8622718f74890ad761b Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Sat, 30 Sep 2023 14:24:45 -0700 Subject: [PATCH 6/9] remove sleep used for testing --- dsc_lib/src/dscresources/command_resource.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 542095be..f5e7fec3 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -450,11 +450,6 @@ pub fn invoke_command(executable: &str, args: Option>, input: Option child_stderr.read_to_end(&mut stderr_buf)?; let exit_status = child.wait()?; - - // sleep for 5 seconds - info!("Sleeping for 5 seconds"); - std::thread::sleep(std::time::Duration::from_secs(5)); - let exit_code = exit_status.code().unwrap_or(EXIT_PROCESS_TERMINATED); let stdout = String::from_utf8_lossy(&stdout_buf).to_string(); let stderr = String::from_utf8_lossy(&stderr_buf).to_string(); From bd2cf0a99391c9e52f57cefc56a2c6d75784cd2a Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Sat, 30 Sep 2023 18:02:31 -0700 Subject: [PATCH 7/9] remove use of tracing-appender --- dsc/Cargo.toml | 1 - dsc/src/main.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index 4c24e226..5e9498c2 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -26,5 +26,4 @@ syntect = { version = "5.0", features = ["default-fancy"], default-features = fa sysinfo = { version = "0.29.10" } thiserror = "1.0" tracing = "0.1.37" -tracing-appender = "0.2.2" tracing-subscriber = "0.3.17" diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 538c3bd0..27884fb4 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -27,8 +27,7 @@ fn main() { check_debug(); // create subscriber that writes all events to stderr - let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stderr()); - let subscriber = tracing_subscriber::fmt().pretty().with_writer(non_blocking).finish(); + let subscriber = tracing_subscriber::fmt().pretty().with_writer(std::io::stderr).finish(); if tracing::subscriber::set_global_default(subscriber).is_err() { eprintln!("Unable to set global default subscriber"); } From d5502be9cee279c83bcc088517c37843d106b681 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 3 Oct 2023 17:30:29 -0700 Subject: [PATCH 8/9] address Andrew's feedback --- dsc/src/main.rs | 2 +- tools/dsctest/src/sleep.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 27884fb4..b45b6e24 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -109,7 +109,7 @@ fn terminate_subprocesses(sys: &System, process: &Process) { info!("Terminating process {} {}", process.name(), process.pid()); if !process.kill() { - info!("Failed to terminate process {} {}", process.name(), process.pid()); + error!("Failed to terminate process {} {}", process.name(), process.pid()); } } diff --git a/tools/dsctest/src/sleep.rs b/tools/dsctest/src/sleep.rs index 5b5a853a..83e14277 100644 --- a/tools/dsctest/src/sleep.rs +++ b/tools/dsctest/src/sleep.rs @@ -4,7 +4,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Returns information about the operating system. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct Sleep { From f43f57d25cc1e6c4aad445f745ca2b02a9def838 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 4 Oct 2023 08:55:22 -0700 Subject: [PATCH 9/9] fix clippy --- dsc/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index b45b6e24..a3301beb 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -7,7 +7,6 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use std::io::{self, Read}; use std::process::exit; -use tracing::{error, info}; use sysinfo::{Process, ProcessExt, RefreshKind, System, SystemExt, get_current_pid, ProcessRefreshKind}; use tracing::{error, info, warn};