-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #321 from tgauth/add-run-command-resource
Add run command resource
- Loading branch information
Showing
9 changed files
with
525 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"$schema": "https://raw.githubusercontent.com/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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.