feat: use interactive_parse lib to query for secrets attributes values #219

Merged
stremblay merged 5 commits from feat/json-attributes-prompt into master 2026-01-26 15:54:05 +00:00
21 changed files with 135 additions and 57 deletions

111
Cargo.lock generated
View File

@@ -686,6 +686,7 @@ dependencies = [
"regex", "regex",
"russh", "russh",
"russh-keys", "russh-keys",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
] ]
@@ -875,6 +876,22 @@ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cert_manager"
version = "0.1.0"
dependencies = [
"assert_cmd",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_types",
"log",
"tokio",
"url",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.3" version = "1.0.3"
@@ -1205,6 +1222,22 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio 0.8.11",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.28.1" version = "0.28.1"
@@ -1767,6 +1800,7 @@ dependencies = [
"harmony_tui", "harmony_tui",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",
@@ -1781,7 +1815,7 @@ dependencies = [
"harmony", "harmony",
"harmony_macros", "harmony_macros",
"http 1.3.1", "http 1.3.1",
"inquire", "inquire 0.7.5",
"k8s-openapi", "k8s-openapi",
"kube", "kube",
"log", "log",
@@ -1857,6 +1891,7 @@ dependencies = [
"harmony_tui", "harmony_tui",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",
@@ -1920,6 +1955,7 @@ dependencies = [
"harmony_secret_derive", "harmony_secret_derive",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",
@@ -1965,6 +2001,7 @@ dependencies = [
"harmony_secret", "harmony_secret",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",
@@ -2033,6 +2070,7 @@ dependencies = [
"harmony_secret_derive", "harmony_secret_derive",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",
@@ -2548,7 +2586,7 @@ dependencies = [
"helm-wrapper-rs", "helm-wrapper-rs",
"hex", "hex",
"http 1.3.1", "http 1.3.1",
"inquire", "inquire 0.7.5",
"k3d-rs", "k3d-rs",
"k8s-openapi", "k8s-openapi",
"kube", "kube",
@@ -2599,7 +2637,7 @@ dependencies = [
"harmony_tui", "harmony_tui",
"indicatif", "indicatif",
"indicatif-log-bridge", "indicatif-log-bridge",
"inquire", "inquire 0.7.5",
"lazy_static", "lazy_static",
"log", "log",
"tokio", "tokio",
@@ -2680,10 +2718,12 @@ dependencies = [
"harmony_secret_derive", "harmony_secret_derive",
"http 1.3.1", "http 1.3.1",
"infisical", "infisical",
"inquire", "inquire 0.7.5",
"interactive-parse",
"lazy_static", "lazy_static",
"log", "log",
"pretty_assertions", "pretty_assertions",
"schemars 0.8.22",
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "tempfile",
@@ -3348,6 +3388,22 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "inquire"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b"
dependencies = [
"bitflags 1.3.2",
"crossterm 0.25.0",
"dyn-clone",
"lazy_static",
"newline-converter 0.2.2",
"thiserror 1.0.69",
"unicode-segmentation",
"unicode-width 0.1.14",
]
[[package]] [[package]]
name = "inquire" name = "inquire"
version = "0.7.5" version = "0.7.5"
@@ -3359,7 +3415,7 @@ dependencies = [
"dyn-clone", "dyn-clone",
"fuzzy-matcher", "fuzzy-matcher",
"fxhash", "fxhash",
"newline-converter", "newline-converter 0.3.0",
"once_cell", "once_cell",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.1.14", "unicode-width 0.1.14",
@@ -3378,6 +3434,22 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "interactive-parse"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c6684d66c9fd6b51cafbf2a9105583d5046dd4c6363f31745686f503a285e8"
dependencies = [
"crossterm 0.26.1",
"inquire 0.6.2",
"lazy_static",
"log",
"schemars 0.8.22",
"serde",
"serde_json",
"thiserror 1.0.69",
]
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.10" version = "0.7.10"
@@ -3482,25 +3554,6 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "json-prompt"
version = "0.1.0"
dependencies = [
"brocade",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_secret",
"harmony_secret_derive",
"harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]] [[package]]
name = "jsonpath-rust" name = "jsonpath-rust"
version = "0.7.5" version = "0.7.5"
@@ -3956,6 +4009,15 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "newline-converter"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "newline-converter" name = "newline-converter"
version = "0.3.0" version = "0.3.0"
@@ -6113,6 +6175,7 @@ dependencies = [
"harmony_secret_derive", "harmony_secret_derive",
"harmony_types", "harmony_types",
"log", "log",
"schemars 0.8.22",
"serde", "serde",
"tokio", "tokio",
"url", "url",

View File

@@ -16,3 +16,4 @@ env_logger.workspace = true
regex = "1.11.3" regex = "1.11.3"
harmony_secret = { path = "../harmony_secret" } harmony_secret = { path = "../harmony_secret" }
serde.workspace = true serde.workspace = true
schemars = "0.8"

View File

@@ -3,9 +3,10 @@ use std::net::{IpAddr, Ipv4Addr};
use brocade::{BrocadeOptions, ssh}; use brocade::{BrocadeOptions, ssh};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use harmony_types::switch::PortLocation; use harmony_types::switch::PortLocation;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Secret, Clone, Debug, Serialize, Deserialize)] #[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
struct BrocadeSwitchAuth { struct BrocadeSwitchAuth {
username: String, username: String,
password: String, password: String,

View File

@@ -19,3 +19,4 @@ url = { workspace = true }
harmony_secret = { path = "../../harmony_secret" } harmony_secret = { path = "../../harmony_secret" }
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
serde = { workspace = true } serde = { workspace = true }
schemars = "0.8"

View File

@@ -21,6 +21,7 @@ use harmony::{
use harmony_macros::{ip, mac_address}; use harmony_macros::{ip, mac_address};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use harmony_types::net::Url; use harmony_types::net::Url;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[tokio::main] #[tokio::main]
@@ -136,7 +137,7 @@ async fn main() {
.unwrap(); .unwrap();
} }
#[derive(Secret, Serialize, Deserialize, Debug)] #[derive(Secret, Serialize, JsonSchema, Deserialize, Debug)]
pub struct BrocadeSwitchAuth { pub struct BrocadeSwitchAuth {
pub username: String, pub username: String,
pub password: String, pub password: String,

View File

@@ -19,3 +19,4 @@ env_logger = { workspace = true }
url = { workspace = true } url = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
schemars = "0.8"

View File

@@ -25,6 +25,7 @@ use harmony::{
use harmony_macros::{ip, mac_address}; use harmony_macros::{ip, mac_address};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use harmony_types::net::Url; use harmony_types::net::Url;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[tokio::main] #[tokio::main]
@@ -192,7 +193,7 @@ async fn main() {
.unwrap(); .unwrap();
} }
#[derive(Secret, Serialize, Deserialize, Debug)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug)]
pub struct BrocadeSwitchAuth { pub struct BrocadeSwitchAuth {
pub username: String, pub username: String,
pub password: String, pub password: String,

View File

@@ -20,3 +20,4 @@ env_logger = { workspace = true }
url = { workspace = true } url = { workspace = true }
serde.workspace = true serde.workspace = true
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
schemars = "0.8"

View File

@@ -8,13 +8,14 @@ use harmony::{
}; };
use harmony_macros::{ip, ipv4}; use harmony_macros::{ip, ipv4};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
net::IpAddr, net::IpAddr,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
struct OPNSenseFirewallConfig { struct OPNSenseFirewallConfig {
username: String, username: String,
password: String, password: String,
@@ -103,7 +104,7 @@ pub fn get_inventory() -> Inventory {
} }
} }
#[derive(Secret, Serialize, Deserialize, Debug)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug)]
pub struct BrocadeSwitchAuth { pub struct BrocadeSwitchAuth {
pub username: String, pub username: String,
pub password: String, pub password: String,

View File

@@ -20,3 +20,4 @@ env_logger = { workspace = true }
url = { workspace = true } url = { workspace = true }
serde.workspace = true serde.workspace = true
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
schemars = "0.8"

View File

@@ -9,6 +9,7 @@ use harmony::{
}; };
use harmony_macros::{ip, ipv4}; use harmony_macros::{ip, ipv4};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
net::IpAddr, net::IpAddr,
@@ -98,7 +99,7 @@ pub fn get_inventory() -> Inventory {
} }
} }
#[derive(Secret, Serialize, Deserialize, Debug)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug)]
pub struct BrocadeSwitchAuth { pub struct BrocadeSwitchAuth {
pub username: String, pub username: String,
pub password: String, pub password: String,

View File

@@ -19,3 +19,4 @@ url = { workspace = true }
harmony_secret = { path = "../../harmony_secret" } harmony_secret = { path = "../../harmony_secret" }
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
serde = { workspace = true } serde = { workspace = true }
schemars = "0.8"

View File

@@ -7,6 +7,7 @@ use harmony::{
}; };
use harmony_macros::{ip, ipv4}; use harmony_macros::{ip, ipv4};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[tokio::main] #[tokio::main]
@@ -70,7 +71,7 @@ async fn main() {
.unwrap(); .unwrap();
} }
#[derive(Secret, Serialize, Deserialize, Debug)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug)]
pub struct BrocadeSwitchAuth { pub struct BrocadeSwitchAuth {
pub username: String, pub username: String,
pub password: String, pub password: String,

View File

@@ -20,3 +20,4 @@ env_logger = { workspace = true }
url = { workspace = true } url = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
brocade = { path = "../../brocade" } brocade = { path = "../../brocade" }
schemars = "0.8"

View File

@@ -7,13 +7,14 @@ use harmony::{
}; };
use harmony_macros::{ip, ipv4}; use harmony_macros::{ip, ipv4};
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
net::IpAddr, net::IpAddr,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
struct OPNSenseFirewallConfig { struct OPNSenseFirewallConfig {
username: String, username: String,
password: String, password: String,

View File

@@ -1,20 +1,21 @@
use harmony_secret_derive::Secret; use harmony_secret_derive::Secret;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
pub struct OPNSenseFirewallCredentials { pub struct OPNSenseFirewallCredentials {
pub username: String, pub username: String,
pub password: String, pub password: String,
} }
// TODO we need a better way to handle multiple "instances" of the same secret structure. // TODO we need a better way to handle multiple "instances" of the same secret structure.
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
pub struct SshKeyPair { pub struct SshKeyPair {
pub private: String, pub private: String,
pub public: String, pub public: String,
} }
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
pub struct RedhatSecret { pub struct RedhatSecret {
pub pull_secret: String, pub pull_secret: String,
} }

View File

@@ -28,6 +28,7 @@ use harmony_secret_derive::Secret;
use harmony_types::net::Url; use harmony_types::net::Url;
use kube::api::ObjectMeta; use kube::api::ObjectMeta;
use log::{debug, info}; use log::{debug, info};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
@@ -132,7 +133,7 @@ impl<
} }
} }
#[derive(Secret, Serialize, Deserialize, Clone, Debug)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Clone, Debug)]
struct NtfyAuth { struct NtfyAuth {
username: String, username: String,
password: String, password: String,

View File

@@ -4,6 +4,7 @@ use async_trait::async_trait;
use brocade::BrocadeOptions; use brocade::BrocadeOptions;
use harmony_secret::{Secret, SecretManager}; use harmony_secret::{Secret, SecretManager};
use harmony_types::id::Id; use harmony_types::id::Id;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@@ -37,13 +38,13 @@ pub struct BrocadeEnableSnmpInterpret {
score: BrocadeEnableSnmpScore, score: BrocadeEnableSnmpScore,
} }
#[derive(Secret, Clone, Debug, Serialize, Deserialize)] #[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
struct BrocadeSwitchAuth { struct BrocadeSwitchAuth {
username: String, username: String,
password: String, password: String,
} }
#[derive(Secret, Clone, Debug, Serialize, Deserialize)] #[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
struct BrocadeSnmpAuth { struct BrocadeSnmpAuth {
username: String, username: String,
auth_password: String, auth_password: String,

View File

@@ -45,7 +45,7 @@ impl OKDRoleProperties for WorkerRole {
} }
fn required_hosts(&self) -> i16 { fn required_hosts(&self) -> i16 {
2 1
} }
fn logical_hosts<'a>(&self, t: &'a HAClusterTopology) -> &'a Vec<LogicalHost> { fn logical_hosts<'a>(&self, t: &'a HAClusterTopology) -> &'a Vec<LogicalHost> {

View File

@@ -19,6 +19,8 @@ tokio.workspace = true
async-trait.workspace = true async-trait.workspace = true
http.workspace = true http.workspace = true
inquire.workspace = true inquire.workspace = true
interactive-parse = "0.1.5"
schemars = "0.8"
[dev-dependencies] [dev-dependencies]
pretty_assertions.workspace = true pretty_assertions.workspace = true

View File

@@ -9,7 +9,10 @@ use config::INFISICAL_ENVIRONMENT;
use config::INFISICAL_PROJECT_ID; use config::INFISICAL_PROJECT_ID;
use config::INFISICAL_URL; use config::INFISICAL_URL;
use config::SECRET_STORE; use config::SECRET_STORE;
use interactive_parse::InteractiveParseObj;
use log::debug; use log::debug;
use log::info;
use schemars::JsonSchema;
use serde::{Serialize, de::DeserializeOwned}; use serde::{Serialize, de::DeserializeOwned};
use std::fmt; use std::fmt;
use store::InfisicalSecretStore; use store::InfisicalSecretStore;
@@ -20,7 +23,8 @@ use tokio::sync::OnceCell;
pub use harmony_secret_derive::Secret; pub use harmony_secret_derive::Secret;
// The Secret trait remains the same. // The Secret trait remains the same.
pub trait Secret: Serialize + DeserializeOwned + Sized { // pub trait Secret: Serialize + DeserializeOwned + Sized {
pub trait Secret: Serialize + DeserializeOwned + JsonSchema + InteractiveParseObj + Sized {
const KEY: &'static str; const KEY: &'static str;
} }
@@ -120,23 +124,15 @@ impl SecretManager {
Review

cleanup

cleanup
let ns = &manager.namespace; let ns = &manager.namespace;
let key = T::KEY; let key = T::KEY;
let secret_json = inquire::Text::new(&format!(
"Secret not found for {} {}, paste the JSON here :",
ns, key
))
.prompt()
.map_err(|e| {
SecretStoreError::Store(format!("Failed to prompt secret {ns} {key} : {e}").into())
})?;
let secret: T = serde_json::from_str(&secret_json).map_err(|e| { debug!("Prompting interactively for secret {ns} {key}");
SecretStoreError::Deserialization { info!("Secret not found for {} {}, fill the fields :", ns, key);
key: T::KEY.to_string(),
source: e,
}
})?;
Ok(secret) T::parse_to_obj().map_err(|e| {
SecretStoreError::Store(
format!("Failed to interactively parse secret {ns} {key}: {e}").into(),
)
})
})?; })?;
if prompted { if prompted {
@@ -168,12 +164,12 @@ mod test {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
struct TestUserMeta { struct TestUserMeta {
labels: Vec<String>, labels: Vec<String>,
} }
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] #[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
struct TestSecret { struct TestSecret {
user: String, user: String,
password: String, password: String,