From 9f03b3a46f43fb5a1572e8ac45f37e8142c921d1 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 30 Mar 2022 12:03:23 -0400 Subject: [PATCH 1/3] Miscellaneous thing-flinger changes --- package/src/bin/deployment-example.toml | 41 +++++++------------ package/src/bin/thing-flinger.rs | 41 ++++++++++--------- .../src/bin/sled-agent-overlay-files.rs | 2 +- 3 files changed, 37 insertions(+), 47 deletions(-) diff --git a/package/src/bin/deployment-example.toml b/package/src/bin/deployment-example.toml index 62296fa4d0f..7121911dd3a 100644 --- a/package/src/bin/deployment-example.toml +++ b/package/src/bin/deployment-example.toml @@ -3,23 +3,20 @@ # # It is ingested by the `thing-flinger` tool. -# This must be an absolute path -local_source = "/Users/ajs/oxide/omicron" +# This must be an absolute path. It refers to the path to Omicron on the +# machine where thing-flinger is being executed. +omicron_path = "/local/path/to/omicron" [builder] # `server` must refer to one of the `servers` in the servers table -server = "atrium" - -# This must be an absolute path -omicron_path = "/home/andrew/oxide/omicron" - -# Git branch, sha, etc... -git_treeish = "thing-flinger2" +server = "foo" +# This must be an absolute path. It refers to the path to Omicron on the +# builder server. +omicron_path = "/remote/path/to/omicron" [deployment] -servers = ["sock", "buskin"] +servers = ["foo", "bar"] rack_secret_threshold = 2 - # Location where files to install will be placed before running # `omicron-package install` # @@ -27,20 +24,10 @@ rack_secret_threshold = 2 # We specifically allow for $HOME in validating the absolute path staging_dir = "$HOME/omicron_staging" -[servers.tarz] -username = "ajs" -addr = "tarz.local" - -[servers.atrium] -username = "andrew" -addr = "atrium.eng.oxide.computer" - -[servers.sock] -username = "andrew" -addr = "sock.eng.oxide.computer" - -[servers.buskin] -username = "andrew" -addr = "buskin.eng.oxide.computer" - +[servers.foo] +username = "me" +addr = "foo" +[servers.bar] +username = "me" +addr = "bar" diff --git a/package/src/bin/thing-flinger.rs b/package/src/bin/thing-flinger.rs index f3d8d3b981e..2b8a6c01f3b 100644 --- a/package/src/bin/thing-flinger.rs +++ b/package/src/bin/thing-flinger.rs @@ -21,7 +21,6 @@ use thiserror::Error; struct Builder { pub server: String, pub omicron_path: PathBuf, - pub git_treeish: String, } // A server on which an omicron package is deployed @@ -40,7 +39,7 @@ struct Deployment { #[derive(Debug, Deserialize)] struct Config { - pub local_source: PathBuf, + pub omicron_path: PathBuf, pub builder: Builder, pub servers: BTreeMap, pub deployment: Deployment, @@ -74,8 +73,8 @@ enum SubCommand { /// /// Package always builds everything, but it can be set in release mode, and /// we expect the existing tools to run from 'target/debug'. Additionally, - // you can't run `Package` until you have actually built `omicron-package`, - // which `BuildMinimal` does. + /// you can't run `Package` until you have actually built `omicron-package`, + /// which `BuildMinimal` does. BuildMinimal, /// Use all subcommands from omicron-package @@ -153,7 +152,7 @@ fn do_sync(config: &Config) -> Result<()> { // For rsync to copy from the source appropriately we must guarantee a // trailing slash. let src = - format!("{}/", config.local_source.canonicalize()?.to_string_lossy()); + format!("{}/", config.omicron_path.canonicalize()?.to_string_lossy()); let dst = format!( "{}@{}:{}", server.username, @@ -176,6 +175,8 @@ fn do_sync(config: &Config) -> Result<()> { .arg("clickhouse/") .arg("--exclude") .arg("*.swp") + .arg("--exclude") + .arg(".git/") .arg("--out-format") .arg("File changed: %o %t %f") .arg(&src) @@ -195,9 +196,8 @@ fn do_sync(config: &Config) -> Result<()> { fn do_build_minimal(config: &Config) -> Result<()> { let server = &config.servers[&config.builder.server]; let cmd = format!( - "cd {} && git checkout {} && cargo build -p {} -p {}", + "cd {} && cargo build -p {} -p {}", config.builder.omicron_path.to_string_lossy(), - config.builder.git_treeish, "omicron-package", "omicron-sled-agent" ); @@ -219,9 +219,8 @@ fn do_package(config: &Config, artifact_dir: PathBuf) -> Result<()> { // See https://github.com/oxidecomputer/omicron/blob/8757ec542ea4ffbadd6f26094ed4ba357715d70d/rpaths/src/lib.rs write!( &mut cmd, - "bash -lc 'cd {} && git checkout {} && {} package --out {}'", + "bash -lc 'cd {} && {} package --out {}'", config.builder.omicron_path.to_string_lossy(), - config.builder.git_treeish, cmd_path, &artifact_dir, )?; @@ -236,9 +235,8 @@ fn do_check(config: &Config) -> Result<()> { write!( &mut cmd, - "bash -lc 'cd {} && git checkout {} && {} check'", + "bash -lc 'cd {} && {} check'", config.builder.omicron_path.to_string_lossy(), - config.builder.git_treeish, cmd_path, )?; @@ -324,8 +322,8 @@ fn do_overlay(config: &Config) -> Result<()> { // TODO: This needs to match the artifact_dir in `package` root_path.push("out/overlay"); let server_dirs = dir_per_deploy_server(config, &root_path); - let server = &config.servers[&config.builder.server]; - overlay_sled_agent(&server, config, &server_dirs) + let builder = &config.servers[&config.builder.server]; + overlay_sled_agent(&builder, config, &server_dirs) } fn overlay_sled_agent( @@ -344,12 +342,11 @@ fn overlay_sled_agent( // Create directories on builder let dirs = dir_string(&sled_agent_dirs); - let cmd = format!("sh -c 'for dir in {}; do mkdir -p $dir; done'", dirs); - let cmd = format!( - "{} && cd {} && ./target/debug/sled-agent-overlay-files \ - --threshold {} --directories {}", - cmd, + "sh -c 'for dir in {}; do mkdir -p $dir; done' && \ + cd {} && \ + cargo run --bin sled-agent-overlay-files -- --threshold {} --directories {}", + dirs, config.builder.omicron_path.to_string_lossy(), config.deployment.rack_secret_threshold, dirs @@ -506,6 +503,12 @@ fn dir_string(dirs: &[PathBuf]) -> String { dirs.iter().map(|dir| dir.to_string_lossy().to_string() + " ").collect() } +// For each server to be deployed, append the server name to `root`. +// +// Example (for servers "foo", "bar", "baz"): +// +// dir_per_deploy_server(&config, "/my/path") -> +// vec!["/my/path/foo", "/my/path/bar", "/my/path/baz"] fn dir_per_deploy_server(config: &Config, root: &Path) -> Vec { config .deployment @@ -570,7 +573,7 @@ fn validate_absolute_path( } fn validate(config: &Config) -> Result<(), FlingError> { - validate_absolute_path(&config.local_source, "local_source")?; + validate_absolute_path(&config.omicron_path, "omicron_path")?; validate_absolute_path( &config.builder.omicron_path, "builder.omicron_path", diff --git a/sled-agent/src/bin/sled-agent-overlay-files.rs b/sled-agent/src/bin/sled-agent-overlay-files.rs index a784adb2914..ad95e36329c 100644 --- a/sled-agent/src/bin/sled-agent-overlay-files.rs +++ b/sled-agent/src/bin/sled-agent-overlay-files.rs @@ -21,7 +21,7 @@ use structopt::StructOpt; about = "Generate server unique files for deployment" )] struct Args { - //// The rack secret threshold + /// The rack secret threshold #[structopt(short, long)] threshold: usize, From 836b8914470c24fd37cc87c77388b48f266827da Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 30 Mar 2022 12:53:06 -0400 Subject: [PATCH 2/3] Move thing-flinger, overlay-files into 'deploy' crate --- Cargo.lock | 16 +- Cargo.toml | 2 + deploy/.gitignore | 1 + deploy/Cargo.toml | 26 ++++ deploy/README.adoc | 142 ++++++++++++++++++ .../src/bin/deployment-example.toml | 0 .../src/bin/sled-agent-overlay-files.rs | 0 {package => deploy}/src/bin/thing-flinger.rs | 6 +- package/Cargo.toml | 7 +- sled-agent/Cargo.toml | 5 +- 10 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 deploy/.gitignore create mode 100644 deploy/Cargo.toml create mode 100644 deploy/README.adoc rename {package => deploy}/src/bin/deployment-example.toml (100%) rename {sled-agent => deploy}/src/bin/sled-agent-overlay-files.rs (100%) rename {package => deploy}/src/bin/thing-flinger.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 7bd3525933b..a3bf373caef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2347,6 +2347,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "omicron-deploy" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossbeam", + "omicron-package", + "omicron-sled-agent", + "serde", + "serde_derive", + "structopt", + "thiserror", +] + [[package]] name = "omicron-gateway" version = "0.1.0" @@ -2455,8 +2469,6 @@ name = "omicron-package" version = "0.1.0" dependencies = [ "anyhow", - "crossbeam", - "flate2", "futures", "hex", "indicatif", diff --git a/Cargo.toml b/Cargo.toml index 9e8055be407..e2d0bd52ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "common", + "deploy", "gateway", "gateway-client", "gateway-messages", @@ -29,6 +30,7 @@ members = [ default-members = [ "common", + "deploy", "gateway", "gateway-client", "gateway-messages", diff --git a/deploy/.gitignore b/deploy/.gitignore new file mode 100644 index 00000000000..ea8c4bf7f35 --- /dev/null +++ b/deploy/.gitignore @@ -0,0 +1 @@ +/target diff --git a/deploy/Cargo.toml b/deploy/Cargo.toml new file mode 100644 index 00000000000..370375e1293 --- /dev/null +++ b/deploy/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "omicron-deploy" +description = "Tools for deploying Omicron software to target machines" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +anyhow = "1.0" +crossbeam = "0.8" +omicron-sled-agent = { path = "../sled-agent" } +omicron-package = { path = "../package" } +serde = { version = "1.0", features = [ "derive" ] } +serde_derive = "1.0" +structopt = "0.3" +thiserror = "1.0" + +# Disable doc builds by default for our binaries to work around issue +# rust-lang/cargo#8373. These docs would not be very useful anyway. +[[bin]] +name = "sled-agent-overlay-files" +doc = false + +[[bin]] +name = "thing-flinger" +doc = false diff --git a/deploy/README.adoc b/deploy/README.adoc new file mode 100644 index 00000000000..eaf17ca782a --- /dev/null +++ b/deploy/README.adoc @@ -0,0 +1,142 @@ +Omicron is a complex piece of software consisting of many build and install-time dependencies. It's +intended to run primarily on illumos based systems, and as such is built to use runtime facilities +of illumos, such as https://illumos.org/man/5/smf[SMF]. Furthermore, Omicron is fundamentally a +distributed system, with its components intended to run on multiple servers communicating over the +network. In order to secure the system, certain cryptographic primitives, such as asymmetric key +pairs and shared secrets are required. Due to the nature of these cryptographic primitives, there is +a requirement for the distribution or creation of files unique to a specific server, such that no +other server has access to those files. Examples of this are private keys, and threshold key +shares, although other non-cryptographic unique files may also become necessary over time. + +In order to satisfy the above requirements of building and deploying a complex distributed system +consisting of unique, private files, two CLI tools have been created: + + . link:src/bin/omicron-package.rs[omicron-package] - build, package, install on local machine + . link:src/bin/thing-flinger.rs[thing-flinger] - build, package, deploy to remote machines + + +If a user is working on their local illumos based machine, and only wants to run +omicron in single node mode, they should follow the install instruction in +the link:../README.adoc[Omicron README] and use `omicron-package`. If the user +wishes for a more complete workflow, where they can code on their local laptop, +use a remote build machine, and install to multiple machines for a more realistic +deployment, they should use `thing-flinger`. + +The remainder of this document will describe a typical workflow for using +thing-flinger, pointing out room for improvement. + +== Environment and Configuration + + + +------------------+ +------------------+ + | | | | + | | | | + | Client |----------------> Builder | + | | | | + | | | | + +------------------+ +------------------+ + | + | + | + | + +---------------------------+--------------------------+ + | | | + | | | + | | | + +--------v---------+ +---------v--------+ +---------v--------+ + | | | | | | + | | | | | | + | Deployed Server | | Deployed Server | | Deployed Server | + | | | | | | + | | | | | | + +------------------+ +------------------+ +------------------+ + + +`thing-flinger` defines three types of nodes: + + * Client - Where a user typically edits their code and runs thing-flinger. This can run any OS. + * Builder - A Helios box where Omicron is built and packaged + * Deployed Server - Helios machines where Omicron will be installed and run + +It's not at all necessary for these to be separate nodes. For example, a client and builder can be +the same machine, as long as it's a Helios box. Same goes for Builder and a deployment server. The +benefit of this separation though, is that it allows editing on something like a laptop, without +having to worry about setting up a development environment on an illumos based host. + +Machine topology is configured in a `TOML` file that is passed on the command line. All illumos +machines are listed under `servers`, and just the names are used for configuring a builder and +deployment servers. An link:src/bin/deployment-example.toml[example] is provided. + +Thing flinger works over SSH, and so the user must have the public key of their client configured +for their account on all servers. SSH agent forwarding is used to prevent the need for the keys of +the builder to also be on the other servers, thus minimizing needed server configuration. + +== Typical Workflow + +=== Prerequisites + +Ensure you have an account on all illumos boxes, with the client public key in +`~/.ssh/authorized_keys`. + +.The build machine must have Rust and cargo installed, as well as +all the dependencies for Omicron installed. Following the *prerequisites* in the +https://github.com/oxidecomputer/omicron/#build-and-run[Build and run] section of the main Omicron +README is probably a good idea. + +=== Command Based Workflow + +==== Build thing-flinger on client +`thing-flinger` is part of the `omicron-package` crate. + +`cargo build -p omicron-package` + +==== sync +Copy your source code to the builder. Note that this copies over your `.git` subdirectory on purpose so +that a branch can be configured for building with the `git_treeish` field in the toml `builder` +table. + +`./target/debug/thing-flinger -c sync` + +==== build-minimal +Build necessary parts of omicron on the builder, required for future use by thing-flinger. + +`./target/debug/thing-flinger -c build-minimal` + +==== package +Build and package omicron using `omicron-package` on the builder. + +`./target/debug/thing-flinger -c package` + +==== overlay +Create files that are unique to each deployment server. + +`./target/debug/thing-flinger -c overlay` + +==== install +Install omicron to all machines, in parallel. This consists of copying the packaged omicron tarballs +along with overlay files, and omicron-package and its manifest to a `staging` directory on each +deployment server, and then running omicron-package, installing overlay files, and restarting +services. + +`./target/debug/thing-flinger -c install` + +=== Current Limitations + +`thing-flinger` is an early prototype. It has served so far to demonstrate that unique files, +specifically secret shares, can be created and distributed over ssh, and that omicron can be +installed remotely using `omicron-package`. It is not currently complete enough to fully test a +distributed omicron setup, as the underlying dependencies are not configured yet. Specifically, +`CockroachDB` and perhaps `Clickhouse`, need to be configured to run in multiple server mode. It's +anticipated that the `overlay` feature of `thing-flinger` can be used to generate and distribute +configs for this. + +=== Design rationale + +`thing-flinger` is a command line program written in rust. It was written this way to build upon +`omicron-package`, which is also in rust, as that is our default language of choice at Oxide. +`thing-flinger` is based around SSH, as that is the minimal viable requirement for a test tool such +as this. Additionally, it provides for the most straightforward implementation, and takes the least +effort to use securely. This particular implementation wraps the openssh ssh client via +`std::process::Command`, rather than using the `ssh2` crate, because ssh2, as a wrapper around +`libssh`, does not support agent-forwarding. + diff --git a/package/src/bin/deployment-example.toml b/deploy/src/bin/deployment-example.toml similarity index 100% rename from package/src/bin/deployment-example.toml rename to deploy/src/bin/deployment-example.toml diff --git a/sled-agent/src/bin/sled-agent-overlay-files.rs b/deploy/src/bin/sled-agent-overlay-files.rs similarity index 100% rename from sled-agent/src/bin/sled-agent-overlay-files.rs rename to deploy/src/bin/sled-agent-overlay-files.rs diff --git a/package/src/bin/thing-flinger.rs b/deploy/src/bin/thing-flinger.rs similarity index 99% rename from package/src/bin/thing-flinger.rs rename to deploy/src/bin/thing-flinger.rs index 2b8a6c01f3b..081eb5c672e 100644 --- a/package/src/bin/thing-flinger.rs +++ b/deploy/src/bin/thing-flinger.rs @@ -190,16 +190,16 @@ fn do_sync(config: &Config) -> Result<()> { Ok(()) } -// Build omicron-package and omicron-sled-agent on the builder +// Build omicron-package and omicron-deploy on the builder // -// We need to build omicron-sled-agent for overlay file generation +// We need to build omicron-deploy for overlay file generation fn do_build_minimal(config: &Config) -> Result<()> { let server = &config.servers[&config.builder.server]; let cmd = format!( "cd {} && cargo build -p {} -p {}", config.builder.omicron_path.to_string_lossy(), "omicron-package", - "omicron-sled-agent" + "omicron-deploy" ); ssh_exec(&server, &cmd, false) } diff --git a/package/Cargo.toml b/package/Cargo.toml index f968b3c39b6..d7907f37ee6 100644 --- a/package/Cargo.toml +++ b/package/Cargo.toml @@ -1,13 +1,12 @@ [package] name = "omicron-package" +description = "Tools for building and installing Omicron packages" version = "0.1.0" edition = "2021" license = "MPL-2.0" [dependencies] anyhow = "1.0" -crossbeam = "0.8" -flate2 = "1.0.22" futures = "0.3.21" hex = "0.4.3" indicatif = { version = "0.16.2", features = ["rayon"] } @@ -32,7 +31,3 @@ walkdir = "2.3" [[bin]] name = "omicron-package" doc = false - -[[bin]] -name = "thing-flinger" -doc = false diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index b5439a10e41..6cc2069fbeb 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "omicron-sled-agent" +description = "Services for managing sled-local resources" version = "0.1.0" edition = "2021" license = "MPL-2.0" @@ -67,7 +68,3 @@ doc = false [[bin]] name = "sled-agent" doc = false - -[[bin]] -name = "sled-agent-overlay-files" -doc = false From 79800a827d7f9162819180cdeef7542cbe4b199d Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 30 Mar 2022 12:55:02 -0400 Subject: [PATCH 3/3] remove readme from package --- package/README.adoc | 142 -------------------------------------------- 1 file changed, 142 deletions(-) delete mode 100644 package/README.adoc diff --git a/package/README.adoc b/package/README.adoc deleted file mode 100644 index eaf17ca782a..00000000000 --- a/package/README.adoc +++ /dev/null @@ -1,142 +0,0 @@ -Omicron is a complex piece of software consisting of many build and install-time dependencies. It's -intended to run primarily on illumos based systems, and as such is built to use runtime facilities -of illumos, such as https://illumos.org/man/5/smf[SMF]. Furthermore, Omicron is fundamentally a -distributed system, with its components intended to run on multiple servers communicating over the -network. In order to secure the system, certain cryptographic primitives, such as asymmetric key -pairs and shared secrets are required. Due to the nature of these cryptographic primitives, there is -a requirement for the distribution or creation of files unique to a specific server, such that no -other server has access to those files. Examples of this are private keys, and threshold key -shares, although other non-cryptographic unique files may also become necessary over time. - -In order to satisfy the above requirements of building and deploying a complex distributed system -consisting of unique, private files, two CLI tools have been created: - - . link:src/bin/omicron-package.rs[omicron-package] - build, package, install on local machine - . link:src/bin/thing-flinger.rs[thing-flinger] - build, package, deploy to remote machines - - -If a user is working on their local illumos based machine, and only wants to run -omicron in single node mode, they should follow the install instruction in -the link:../README.adoc[Omicron README] and use `omicron-package`. If the user -wishes for a more complete workflow, where they can code on their local laptop, -use a remote build machine, and install to multiple machines for a more realistic -deployment, they should use `thing-flinger`. - -The remainder of this document will describe a typical workflow for using -thing-flinger, pointing out room for improvement. - -== Environment and Configuration - - - +------------------+ +------------------+ - | | | | - | | | | - | Client |----------------> Builder | - | | | | - | | | | - +------------------+ +------------------+ - | - | - | - | - +---------------------------+--------------------------+ - | | | - | | | - | | | - +--------v---------+ +---------v--------+ +---------v--------+ - | | | | | | - | | | | | | - | Deployed Server | | Deployed Server | | Deployed Server | - | | | | | | - | | | | | | - +------------------+ +------------------+ +------------------+ - - -`thing-flinger` defines three types of nodes: - - * Client - Where a user typically edits their code and runs thing-flinger. This can run any OS. - * Builder - A Helios box where Omicron is built and packaged - * Deployed Server - Helios machines where Omicron will be installed and run - -It's not at all necessary for these to be separate nodes. For example, a client and builder can be -the same machine, as long as it's a Helios box. Same goes for Builder and a deployment server. The -benefit of this separation though, is that it allows editing on something like a laptop, without -having to worry about setting up a development environment on an illumos based host. - -Machine topology is configured in a `TOML` file that is passed on the command line. All illumos -machines are listed under `servers`, and just the names are used for configuring a builder and -deployment servers. An link:src/bin/deployment-example.toml[example] is provided. - -Thing flinger works over SSH, and so the user must have the public key of their client configured -for their account on all servers. SSH agent forwarding is used to prevent the need for the keys of -the builder to also be on the other servers, thus minimizing needed server configuration. - -== Typical Workflow - -=== Prerequisites - -Ensure you have an account on all illumos boxes, with the client public key in -`~/.ssh/authorized_keys`. - -.The build machine must have Rust and cargo installed, as well as -all the dependencies for Omicron installed. Following the *prerequisites* in the -https://github.com/oxidecomputer/omicron/#build-and-run[Build and run] section of the main Omicron -README is probably a good idea. - -=== Command Based Workflow - -==== Build thing-flinger on client -`thing-flinger` is part of the `omicron-package` crate. - -`cargo build -p omicron-package` - -==== sync -Copy your source code to the builder. Note that this copies over your `.git` subdirectory on purpose so -that a branch can be configured for building with the `git_treeish` field in the toml `builder` -table. - -`./target/debug/thing-flinger -c sync` - -==== build-minimal -Build necessary parts of omicron on the builder, required for future use by thing-flinger. - -`./target/debug/thing-flinger -c build-minimal` - -==== package -Build and package omicron using `omicron-package` on the builder. - -`./target/debug/thing-flinger -c package` - -==== overlay -Create files that are unique to each deployment server. - -`./target/debug/thing-flinger -c overlay` - -==== install -Install omicron to all machines, in parallel. This consists of copying the packaged omicron tarballs -along with overlay files, and omicron-package and its manifest to a `staging` directory on each -deployment server, and then running omicron-package, installing overlay files, and restarting -services. - -`./target/debug/thing-flinger -c install` - -=== Current Limitations - -`thing-flinger` is an early prototype. It has served so far to demonstrate that unique files, -specifically secret shares, can be created and distributed over ssh, and that omicron can be -installed remotely using `omicron-package`. It is not currently complete enough to fully test a -distributed omicron setup, as the underlying dependencies are not configured yet. Specifically, -`CockroachDB` and perhaps `Clickhouse`, need to be configured to run in multiple server mode. It's -anticipated that the `overlay` feature of `thing-flinger` can be used to generate and distribute -configs for this. - -=== Design rationale - -`thing-flinger` is a command line program written in rust. It was written this way to build upon -`omicron-package`, which is also in rust, as that is our default language of choice at Oxide. -`thing-flinger` is based around SSH, as that is the minimal viable requirement for a test tool such -as this. Additionally, it provides for the most straightforward implementation, and takes the least -effort to use securely. This particular implementation wraps the openssh ssh client via -`std::process::Command`, rather than using the `ssh2` crate, because ssh2, as a wrapper around -`libssh`, does not support agent-forwarding. -