use crate::commands::Cli;
use crate::config::Config;
use crate::error::Result;
use clap::{Args, Subcommand, ValueEnum};
use strum::{Display, EnumString, VariantNames};
mod add;
mod list;
mod remove;
mod test;
pub use add::AddCommand;
pub use list::ListCommand;
pub use remove::RemoveCommand;
pub use test::TestCommand;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Display, EnumString, VariantNames)]
#[strum(serialize_all = "kebab-case")]
pub enum ProviderType {
#[value(name = "1password")]
#[strum(serialize = "1password")]
OnePassword,
#[value(name = "age")]
Age,
#[value(name = "aws")]
Aws,
#[value(name = "aws-kms")]
#[strum(serialize = "aws-kms")]
AwsKms,
#[value(name = "aws-ps")]
#[strum(serialize = "aws-ps")]
AwsParameterStore,
#[value(name = "azure-kms")]
#[strum(serialize = "azure-kms")]
AzureKms,
#[value(name = "azure-sm")]
#[strum(serialize = "azure-sm")]
AzureSecretsManager,
#[value(name = "gcp")]
Gcp,
#[value(name = "gcp-kms")]
#[strum(serialize = "gcp-kms")]
GcpKms,
#[cfg(not(target_env = "musl"))]
#[value(name = "fido2")]
Fido2,
#[value(name = "bitwarden")]
Bitwarden,
#[value(name = "doppler")]
Doppler,
#[value(name = "foks")]
Foks,
#[value(name = "bitwarden-sm")]
#[strum(serialize = "bitwarden-sm")]
BitwardenSecretsManager,
#[value(name = "infisical")]
Infisical,
#[value(name = "keepass")]
#[strum(serialize = "keepass")]
KeePass,
#[value(name = "keychain")]
Keychain,
#[value(name = "password-store")]
#[strum(serialize = "password-store")]
PasswordStore,
#[value(name = "passwordstate")]
Passwordstate,
#[value(name = "plain")]
Plain,
#[value(name = "proton-pass")]
#[strum(serialize = "proton-pass")]
ProtonPass,
#[value(name = "vault")]
Vault,
#[value(name = "yubikey")]
Yubikey,
}
#[derive(Debug, Args)]
pub struct ProviderCommand {
#[command(subcommand)]
pub action: Option<ProviderAction>,
}
#[derive(Debug, Subcommand)]
pub enum ProviderAction {
Add(AddCommand),
List(ListCommand),
Remove(RemoveCommand),
Test(TestCommand),
}
impl ProviderCommand {
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
match &self.action {
None => ListCommand { complete: false }.run(cli, config).await,
Some(ProviderAction::List(cmd)) => cmd.run(cli, config).await,
Some(ProviderAction::Add(cmd)) => cmd.run(cli).await,
Some(ProviderAction::Remove(cmd)) => cmd.run(cli).await,
Some(ProviderAction::Test(cmd)) => cmd.run(cli, config).await,
}
}
}
#[cfg(test)]
mod tests {
use super::ProviderType;
use clap::ValueEnum;
use std::collections::BTreeSet;
fn normalize_provider_type_for_add(provider_type: &str) -> String {
match provider_type {
"aws-sm" => "aws".to_string(),
"gcp-sm" => "gcp".to_string(),
_ => provider_type.to_string(),
}
}
#[test]
fn provider_add_types_match_provider_definitions() {
let providers_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("crates")
.join("fnox-core")
.join("providers");
let defined_types: BTreeSet<String> = std::fs::read_dir(&providers_dir)
.expect("providers directory should exist")
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|path| path.extension().is_some_and(|ext| ext == "toml"))
.filter_map(|path| {
path.file_stem()
.map(|stem| stem.to_string_lossy().into_owned())
})
.filter(|name| !(cfg!(target_env = "musl") && name == "fido2"))
.map(|provider_type| normalize_provider_type_for_add(&provider_type))
.collect();
let cli_types: BTreeSet<String> = ProviderType::value_variants()
.iter()
.filter_map(|variant| {
variant
.to_possible_value()
.map(|value| value.get_name().to_string())
})
.collect();
assert_eq!(
cli_types, defined_types,
"provider add choices drifted from providers/*.toml definitions"
);
}
}