From 8ad1d0cbb66dfe1f10f60aa0c1f05be5c4dba086 Mon Sep 17 00:00:00 2001 From: Timon Borter Date: Sat, 21 Sep 2024 09:29:12 +0200 Subject: [PATCH] ci: refactor code for testibility --- Cargo.lock | 68 ++++++------ argo-cd | 2 +- src/cli.rs | 2 +- src/config.rs | 23 +++- src/database.rs | 102 ++++++++++++++++-- src/main.rs | 7 +- src/vault.rs | 41 ++----- src/workflow.rs | 45 ++------ tests/cli.rs | 87 +++++++++++++++ tests/init_vault.rs | 2 +- tests/resources/config/missing_postgresql.yml | 2 +- tests/resources/config/missing_vault.yml | 2 +- tests/resources/init_vault/invalid_url.yml | 2 +- tests/utilities/src/lib.rs | 7 +- 14 files changed, 266 insertions(+), 126 deletions(-) create mode 100644 tests/cli.rs diff --git a/Cargo.lock b/Cargo.lock index 0b395f4..29451a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234314bd569802ec87011d653d6815c6d7b9ffb969e9fee5b8b20ef860e8dce9" +checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" dependencies = [ "bindgen", "cc", @@ -383,15 +383,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.19" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -462,19 +462,19 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", - "clap_derive 4.5.13", + "clap_derive 4.5.18", ] [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", @@ -497,9 +497,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1530,9 +1530,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2033,9 +2033,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" @@ -2189,9 +2189,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -2200,9 +2200,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -2210,9 +2210,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", @@ -2223,9 +2223,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -2433,7 +2433,7 @@ name = "propeller" version = "0.0.0" dependencies = [ "assert_cmd", - "clap 4.5.17", + "clap 4.5.18", "cross", "env_logger", "lazy_static", @@ -2925,9 +2925,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3582,9 +3582,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap 2.5.0", "toml_datetime", @@ -3721,9 +3721,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -3736,9 +3736,9 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" diff --git a/argo-cd b/argo-cd index 6b9cd82..e7d8b31 160000 --- a/argo-cd +++ b/argo-cd @@ -1 +1 @@ -Subproject commit 6b9cd828c6e9807398869ad5ac44efd2c28422d6 +Subproject commit e7d8b31a7bf6612a579d62d68a58a3b6e28089c2 diff --git a/src/cli.rs b/src/cli.rs index 9b83042..7f76b2d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,7 +33,7 @@ pub(crate) enum Command { /// Base arguments for subcommands that share common parameters. #[derive(Parser, Debug)] pub(crate) struct BaseArgs { - /// Path to the configuration file (default: config.yml). + /// Path to the configuration file #[clap(short, long, default_value = "config.yml")] pub(crate) config_path: std::path::PathBuf, } diff --git a/src/config.rs b/src/config.rs index f376a7b..045fe72 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,8 +25,8 @@ pub(crate) struct ArgoConfig { impl Default for ArgoConfig { fn default() -> Self { ArgoConfig { - application: String::from(""), - base_url: String::from(""), + application: String::from("propeller"), + base_url: String::from("http://localhost:3100"), danger_accept_insecure: Option::from(false), sync_timeout_seconds: Option::from(60), } @@ -40,12 +40,31 @@ pub(crate) struct PostgresConfig { pub(crate) database: String, } +impl Default for PostgresConfig { + fn default() -> Self { + PostgresConfig { + host: String::from("localhost"), + port: 5432, + database: String::from("propeller"), + } + } +} + #[derive(Clone, Deserialize, Debug)] pub(crate) struct VaultConfig { pub(crate) base_url: String, pub(crate) path: String, } +impl Default for VaultConfig { + fn default() -> Self { + VaultConfig { + base_url: String::from("http://localhost:8200"), + path: String::from("propeller"), + } + } +} + pub(crate) fn read_config(config_path: PathBuf) -> Config { let path_string = config_path.clone().into_os_string().into_string().unwrap(); debug!("Reading config at: {path_string}"); diff --git a/src/database.rs b/src/database.rs index 2992e3d..f85c2d7 100644 --- a/src/database.rs +++ b/src/database.rs @@ -3,18 +3,33 @@ // This software is released under the MIT License. // https://opensource.org/licenses/MIT +use crate::config::{Config, PostgresConfig}; +use postgres::Error; use postgres::{Client, NoTls}; +use std::sync::Arc; -use crate::config::{Config, PostgresConfig}; +pub trait ClientFactory { + fn create_client(&self, connection_string: &str) -> Result; +} + +struct PropellerClientFactory; -pub(crate) struct PostgresClient { +impl ClientFactory for PropellerClientFactory { + fn create_client(&self, connection_string: &str) -> Result { + Client::connect(connection_string, NoTls) + } +} + +pub struct PostgresClient { postgres_config: PostgresConfig, + client_factory: Arc, } impl PostgresClient { pub(crate) fn init(config: &Config) -> PostgresClient { PostgresClient { postgres_config: config.postgres.clone(), + client_factory: Arc::new(PropellerClientFactory), } } @@ -23,13 +38,80 @@ impl PostgresClient { let port = self.postgres_config.port; let database = self.postgres_config.database.as_str(); - Client::connect( - format!( - "host={host} port={port} dbname={database} user={username} password={password}" - ) - .as_str(), - NoTls, - ) - .expect("Failed to build PostgreSQL connection") + let connection_string = format!( + "host={host} port={port} dbname={database} user={username} password={password}" + ); + + self.client_factory + .create_client(&connection_string) + .expect("Failed to build PostgreSQL connection") + } + + #[cfg(test)] + pub(crate) fn with_client_factory( + config: &Config, + client_factory: Arc, + ) -> PostgresClient { + PostgresClient { + postgres_config: config.postgres.clone(), + client_factory, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::{ArgoConfig, VaultConfig}; + + struct MockClientFactory; + + impl ClientFactory for MockClientFactory { + fn create_client(&self, connection_string: &str) -> Result { + assert!(connection_string.contains("host=testhost")); + assert!(connection_string.contains("port=2345")); + assert!(connection_string.contains("dbname=testdb")); + assert!(connection_string.contains("user=testuser")); + assert!(connection_string.contains("password=testpass")); + + Client::connect("", NoTls) + } + } + + #[test] + fn init() { + let config = create_config_with_testdb(); + + let fixture = PostgresClient::init(&config); + + assert_eq!(fixture.postgres_config.host, "testhost"); + assert_eq!(fixture.postgres_config.port, 2345); + assert_eq!(fixture.postgres_config.database, "testdb"); + } + + #[test] + #[should_panic(expected = "both host and hostaddr are missing")] + fn connect_for_user() { + let config = create_config_with_testdb(); + + let client = PostgresClient::with_client_factory( + &config, + Arc::new(MockClientFactory) as Arc, + ); + + // This should panic + client.connect_for_user("testuser".to_string(), "testpass".to_string()); + } + + fn create_config_with_testdb() -> Config { + Config { + argo_cd: ArgoConfig::default(), + postgres: PostgresConfig { + host: "testhost".to_string(), + port: 2345, + database: "testdb".to_string(), + }, + vault: VaultConfig::default(), + } } } diff --git a/src/main.rs b/src/main.rs index 01df379..2402440 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,8 +89,11 @@ mod tests { fn should_process_path(path: &Path) -> bool { let path_str = path.to_str().unwrap(); - if path_str.starts_with("tests/utilities") { - path_str == "tests/utilities/src" || path_str.starts_with("tests/utilities/src/") + if path_str.starts_with("tests/utilities") || path_str.starts_with("tests\\utilities") { + path_str == "tests/utilities/src" + || path_str.starts_with("tests/utilities/src/") + || path_str == "tests\\utilities\\src" + || path_str.starts_with("tests\\utilities\\src/") } else { true } diff --git a/src/vault.rs b/src/vault.rs index a303d9f..90a8f77 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -119,18 +119,7 @@ mod tests { #[test] fn successful_vault_connect() { - let config = Config { - argo_cd: ArgoConfig { - application: "sut".to_string(), - base_url: "http://localhost:3100".to_string(), - ..Default::default() - }, - postgres: mock_postgres_config(), - vault: VaultConfig { - base_url: "http://localhost:8200".to_string(), - path: "path/to/my/secret".to_string(), - }, - }; + let config = create_config(); env::set_var(VAULT_TOKEN, "test_token"); // Mock environment variable let vault = Vault::connect(&config); @@ -142,28 +131,20 @@ mod tests { #[test] #[should_panic(expected = "Missing VAULT_TOKEN environment variable")] fn vault_connect_missing_token() { - let config = Config { - argo_cd: ArgoConfig { - application: "sut".to_string(), - base_url: "http://localhost:3100".to_string(), - ..Default::default() - }, - postgres: mock_postgres_config(), - vault: VaultConfig { - base_url: "http://localhost:8200".to_string(), - path: "path/to/my/secret".to_string(), - }, - }; + let config = create_config(); env::remove_var(VAULT_TOKEN); // Ensure VAULT_TOKEN is not present - let _ = Vault::connect(&config); // This should panic + Vault::connect(&config); // This should panic } - fn mock_postgres_config() -> PostgresConfig { - PostgresConfig { - host: "".to_string(), - port: 1234, - database: "".to_string(), + fn create_config() -> Config { + Config { + argo_cd: ArgoConfig::default(), + postgres: PostgresConfig::default(), + vault: VaultConfig { + base_url: "http://localhost:8200".to_string(), + path: "path/to/my/secret".to_string(), + }, } } } diff --git a/src/workflow.rs b/src/workflow.rs index 58f2806..f4c51ce 100644 --- a/src/workflow.rs +++ b/src/workflow.rs @@ -106,10 +106,11 @@ fn update_passive_user_postgres_password( (secret.postgresql_user_1.clone(), original_password) }; - let mut conn = db.connect_for_user(passive_user.clone(), passive_user_password); + let mut client = db.connect_for_user(passive_user.clone(), passive_user_password); let query = format!("ALTER ROLE {passive_user} WITH PASSWORD '{new_password}'"); - conn.execute(query.as_str(), &[]) + client + .execute(query.as_str(), &[]) .unwrap_or_else(|e| panic!("Failed to update password of '{}': {}", passive_user, e)); trace!("Successfully rotated database password of passive user"); @@ -139,57 +140,25 @@ mod tests { assert_eq!(secret.postgresql_active_user_password, "password1"); } - // #[test] - // fn update_passive_user_password_user1_active() { - // let client = PropellerDBClient{}; - // - // let mut secret: VaultStructure = create_vault_structure_active_user_1(); - // - // let new_password = "new_password".to_string(); - // - // update_passive_user_postgres_password(client, & mut secret, new_password.clone()); - // - // assert_eq!(secret.postgresql_active_user, "user1"); - // assert_eq!(secret.postgresql_active_user_password, "password1"); - // assert_eq!(secret.postgresql_user_2_password, new_password); - // } - // - // #[test] - // fn update_passive_user_password_user2_active() { - // let client = PropellerDBClient{}; - // - // let mut secret: VaultStructure = create_vault_structure_active_user_2(); - // - // let new_password = "new_password".to_string(); - // - // update_passive_user_postgres_password(client,&mut secret, new_password.clone()); - // - // assert_eq!(secret.postgresql_active_user, "user2"); - // assert_eq!(secret.postgresql_active_user_password, "password2"); - // assert_eq!(secret.postgresql_user_1_password, new_password); - // } - fn create_vault_structure_active_user_1() -> VaultStructure { - let secret = VaultStructure { + VaultStructure { postgresql_active_user: "user1".to_string(), postgresql_active_user_password: "password1".to_string(), postgresql_user_1: "user1".to_string(), postgresql_user_1_password: "password1".to_string(), postgresql_user_2: "user2".to_string(), postgresql_user_2_password: "password2".to_string(), - }; - secret + } } fn create_vault_structure_active_user_2() -> VaultStructure { - let secret = VaultStructure { + VaultStructure { postgresql_active_user: "user2".to_string(), postgresql_active_user_password: "password2".to_string(), postgresql_user_1: "user1".to_string(), postgresql_user_1_password: "password1".to_string(), postgresql_user_2: "user2".to_string(), postgresql_user_2_password: "password2".to_string(), - }; - secret + } } } diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 0000000..86c475e --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,87 @@ +// Copyright (c) 2024 PostFinance AG +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +use assert_cmd::prelude::*; +use predicates::str::contains; +use std::process::{Command, Stdio}; + +#[test] +fn propeller_cli() { + Command::cargo_bin("propeller") + .unwrap() + .stdout(Stdio::piped()) + .assert() + .failure() + .stderr(contains("propeller - Automated database secret rotation")) + .stderr(contains("init-vault")) + .stderr(contains( + "Initialize a Vault path with the necessary structure for secret management", + )) + .stderr(contains("rotate")) + .stderr(contains("Rotate PostgreSQL database secrets")) + .stderr(contains("help")) + .stderr(contains( + "Print this message or the help of the given subcommand(s)", + )) + .stderr(contains("-h, --help")) + .stderr(contains("Print help")) + .stderr(contains("-V, --version")) + .stderr(contains("Print version")); +} + +#[test] +fn propeller_cli_init_vault_help() { + Command::cargo_bin("propeller") + .unwrap() + .arg("init-vault") + .arg("--help") + .stdout(Stdio::piped()) + .assert() + .success() + .stdout(contains( + "Initialize a Vault path with the necessary structure for secret management.", + )) + .stdout(contains( + "This command prepares the Vault backend for subsequent secret rotation operations.", + )) + .stdout(contains("init-vault [OPTIONS]")) + .stdout(contains("-c, --config-path ")) + .stdout(contains("Path to the configuration file")) + .stdout(contains("[default: config.yml]")) + .stdout(contains("-h, --help")) + .stdout(contains("Print help")) + .stdout(contains("-V, --version")) + .stdout(contains("Print version")); +} + +#[test] +fn propeller_cli_rotate_help() { + Command::cargo_bin("propeller") + .unwrap() + .arg("rotate") + .arg("--help") + .stdout(Stdio::piped()) + .assert() + .success() + .stdout(contains("Rotate PostgreSQL database secrets.")) + .stdout(contains("This command orchestrates the process of generating new secrets, updating the database, and storing the new secrets in Vault.")) + .stdout(contains("rotate [OPTIONS")) + .stdout(contains( + "-c, --config-path ", + )) + .stdout(contains("Path to the configuration file")) + .stdout(contains("[default: config.yml]")) + .stdout(contains("-p, --password-length ")) + .stdout(contains( + "The length of the randomly generated alphanumeric password", + )) + .stdout(contains( + "[default: 20]", + )) + .stdout(contains("-h, --help")) + .stdout(contains("Print help")) + .stdout(contains("-V, --version")) + .stdout(contains("Print version")); +} diff --git a/tests/init_vault.rs b/tests/init_vault.rs index 3b230e1..ea3b3eb 100644 --- a/tests/init_vault.rs +++ b/tests/init_vault.rs @@ -31,7 +31,7 @@ async fn init_vault_new_path() { // language=yaml " argo_cd: - application: 'sut' + application: 'propeller' base_url: 'http://localhost:3100' postgres: host: 'localhost' diff --git a/tests/resources/config/missing_postgresql.yml b/tests/resources/config/missing_postgresql.yml index 7fff065..a902ec2 100644 --- a/tests/resources/config/missing_postgresql.yml +++ b/tests/resources/config/missing_postgresql.yml @@ -1,5 +1,5 @@ argo_cd: - application: 'sut' + application: 'propeller' base_url: 'http://localhost:3100' vault: base_url: 'http://localhost:1234' diff --git a/tests/resources/config/missing_vault.yml b/tests/resources/config/missing_vault.yml index 93a4d42..0ea1966 100644 --- a/tests/resources/config/missing_vault.yml +++ b/tests/resources/config/missing_vault.yml @@ -1,5 +1,5 @@ argo_cd: - application: 'sut' + application: 'propeller' base_url: 'http://localhost:3100' postgres: host: 'localhost' diff --git a/tests/resources/init_vault/invalid_url.yml b/tests/resources/init_vault/invalid_url.yml index 83f28e1..9c4ca05 100644 --- a/tests/resources/init_vault/invalid_url.yml +++ b/tests/resources/init_vault/invalid_url.yml @@ -1,5 +1,5 @@ argo_cd: - application: 'sut' + application: 'propeller' base_url: 'http://localhost:3100' postgres: host: 'localhost' diff --git a/tests/utilities/src/lib.rs b/tests/utilities/src/lib.rs index 40cb3a8..59a9c2f 100644 --- a/tests/utilities/src/lib.rs +++ b/tests/utilities/src/lib.rs @@ -21,6 +21,7 @@ use serde_json::{json, Value}; use serde_yaml::from_value; use std::collections::BTreeMap; use std::env::temp_dir; +use std::error::Error; use std::fs::{read_to_string, File}; use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; @@ -226,9 +227,7 @@ fn create_singleton_btree(key: &str, value: &str) -> Option Result, Box> { +fn multidoc_deserialize(data: &str) -> Result, Box> { use serde::Deserialize; let mut docs = vec![]; for de in serde_yaml::Deserializer::from_str(data) { @@ -327,7 +326,7 @@ async fn forward_connection( pod_name: &str, port: u16, mut client_conn: impl AsyncRead + AsyncWrite + Unpin, -) -> Result<(), Box> { +) -> Result<(), Box> { let mut forwarder = pods.portforward(pod_name, &[port]).await?; let mut upstream_conn = forwarder .take_stream(port)