From fe2eef97cf883e7cf651fe1ecfccf306a83f139f Mon Sep 17 00:00:00 2001 From: Ava Hahn Date: Tue, 18 Jun 2024 23:06:05 -0700 Subject: [PATCH] tools/unitctl: unitctl export * new subcommand for "export" in CLI * new cmd submodule for exporting config tarballs * logic to also output to stdout * README additions * limitations documented Signed-off-by: Ava Hahn --- tools/unitctl/Cargo.lock | 66 ++++++++++++++++++--------- tools/unitctl/README.md | 14 ++++++ tools/unitctl/unitctl/Cargo.toml | 1 + tools/unitctl/unitctl/src/cmd/mod.rs | 1 + tools/unitctl/unitctl/src/cmd/save.rs | 52 +++++++++++++++++++++ tools/unitctl/unitctl/src/main.rs | 8 +++- tools/unitctl/unitctl/src/unitctl.rs | 10 ++++ 7 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 tools/unitctl/unitctl/src/cmd/save.rs diff --git a/tools/unitctl/Cargo.lock b/tools/unitctl/Cargo.lock index 16241296f..202799638 100644 --- a/tools/unitctl/Cargo.lock +++ b/tools/unitctl/Cargo.lock @@ -485,23 +485,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -510,6 +499,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1009,9 +1010,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -1034,9 +1035,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -1488,15 +1489,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1788,6 +1789,17 @@ dependencies = [ "windows", ] +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -2061,6 +2073,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "tar", "tempfile", "tokio", "unit-client-rs", @@ -2439,6 +2452,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/tools/unitctl/README.md b/tools/unitctl/README.md index dca16e639..743660073 100644 --- a/tools/unitctl/README.md +++ b/tools/unitctl/README.md @@ -177,6 +177,20 @@ Imported /opt/unit/config/put.json -> /config Imported 3 files ``` +### Export configuration from a running Unit instance +``` +$ unitctl export -f config.tar +``` + +Addtionally, standard out can be used: +``` +$ unitctl export -f - +$ unitctl export -f - | tar xf - config.json +$ unitctl export -f - > config.tar +``` + +*Note:* The exported configuration omits certificates. + ### Wait for socket to become available ``` $ unitctl --wait-timeout-seconds=3 --wait-max-tries=4 import /opt/unit/config` diff --git a/tools/unitctl/unitctl/Cargo.toml b/tools/unitctl/unitctl/Cargo.toml index 98930fb3d..8d83b4243 100644 --- a/tools/unitctl/unitctl/Cargo.toml +++ b/tools/unitctl/unitctl/Cargo.toml @@ -32,6 +32,7 @@ hyperlocal = "0.8" hyper-tls = "0.5" tokio = { version = "1.35", features = ["macros"] } futures = "0.3" +tar = "0.4.41" [package.metadata.deb] copyright = "2022, F5" diff --git a/tools/unitctl/unitctl/src/cmd/mod.rs b/tools/unitctl/unitctl/src/cmd/mod.rs index 07c509123..f2a2c1205 100644 --- a/tools/unitctl/unitctl/src/cmd/mod.rs +++ b/tools/unitctl/unitctl/src/cmd/mod.rs @@ -5,3 +5,4 @@ pub(crate) mod import; pub(crate) mod instances; pub(crate) mod listeners; pub(crate) mod status; +pub(crate) mod save; diff --git a/tools/unitctl/unitctl/src/cmd/save.rs b/tools/unitctl/unitctl/src/cmd/save.rs new file mode 100644 index 000000000..bce8fdb97 --- /dev/null +++ b/tools/unitctl/unitctl/src/cmd/save.rs @@ -0,0 +1,52 @@ +use crate::unitctl::UnitCtl; +use crate::wait; +use crate::UnitctlError; +use crate::requests::send_empty_body_deserialize_response; +use unit_client_rs::unit_client::UnitClient; +use tar::{Builder, Header}; +use std::fs::File; +use std::io::stdout; + + +pub async fn cmd( + cli: &UnitCtl, + filename: &String +) -> Result<(), UnitctlError> { + if !filename.ends_with(".tar") { + eprintln!("Warning: writing uncompressed tarball to {}", filename); + } + + let control_socket = wait::wait_for_socket(cli).await?; + let client = UnitClient::new(control_socket); + + let config_res = serde_json::to_string_pretty( + &send_empty_body_deserialize_response(&client, "GET", "/config").await? + ); + if let Err(e) = config_res { + return Err(UnitctlError::DeserializationError{message: e.to_string()}) + } + + let current_config = config_res + .unwrap() + .into_bytes(); + + //let current_js_modules = send_empty_body_deserialize_response(&client, "GET", "/js_modules") + // .await?; + + let mut conf_header = Header::new_gnu(); + conf_header.set_size(current_config.len() as u64); + conf_header.set_mode(0o644); + conf_header.set_cksum(); + + // builder has a different type depending on output + if filename == "-" { + let mut ar = Builder::new(stdout()); + ar.append_data(&mut conf_header, "config.json", current_config.as_slice()).unwrap(); + } else { + let file = File::create(filename).unwrap(); + let mut ar = Builder::new(file); + ar.append_data(&mut conf_header, "config.json", current_config.as_slice()).unwrap(); + } + + Ok(()) +} diff --git a/tools/unitctl/unitctl/src/main.rs b/tools/unitctl/unitctl/src/main.rs index 6c9faaf70..8f33fc16d 100644 --- a/tools/unitctl/unitctl/src/main.rs +++ b/tools/unitctl/unitctl/src/main.rs @@ -8,7 +8,11 @@ extern crate unit_client_rs; use clap::Parser; -use crate::cmd::{applications, edit, execute as execute_cmd, import, instances, listeners, status}; +use crate::cmd::{ + applications, edit, execute as execute_cmd, + import, instances, listeners, status, + save +}; use crate::output_format::OutputFormat; use crate::unitctl::{Commands, UnitCtl}; use crate::unitctl_error::UnitctlError; @@ -46,6 +50,8 @@ async fn main() -> Result<(), UnitctlError> { Commands::Status { output_format } => status::cmd(&cli, output_format).await, Commands::Listeners { output_format } => listeners::cmd(&cli, output_format).await, + + Commands::Export { ref filename } => save::cmd(&cli, filename).await, } .map_err(|error| { eprint_error(&error); diff --git a/tools/unitctl/unitctl/src/unitctl.rs b/tools/unitctl/unitctl/src/unitctl.rs index 47f338202..e567116b9 100644 --- a/tools/unitctl/unitctl/src/unitctl.rs +++ b/tools/unitctl/unitctl/src/unitctl.rs @@ -116,6 +116,16 @@ pub(crate) enum Commands { }, #[command(about = "List all configured Unit applications")] App(ApplicationArgs), + + #[command(about = "Export the current configuration of UNIT")] + Export { + #[arg( + required = true, + short = 'f', + help = "tarball filename to save configuration to" + )] + filename: String + }, } #[derive(Debug, Args)]