feat: Added 1password support
Check / stable / fmt (push) Has been cancelled
Check / beta / clippy (push) Has been cancelled
Check / stable / clippy (push) Has been cancelled
Check / nightly / doc (push) Has been cancelled
Check / 1.89.0 / check (push) Has been cancelled
Test Suite / ubuntu / beta (push) Has been cancelled
Test Suite / ubuntu / stable (push) Has been cancelled
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
Test Suite / ubuntu / stable / coverage (push) Has been cancelled
Check / stable / fmt (push) Has been cancelled
Check / beta / clippy (push) Has been cancelled
Check / stable / clippy (push) Has been cancelled
Check / nightly / doc (push) Has been cancelled
Check / 1.89.0 / check (push) Has been cancelled
Test Suite / ubuntu / beta (push) Has been cancelled
Test Suite / ubuntu / stable (push) Has been cancelled
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
Test Suite / ubuntu / stable / coverage (push) Has been cancelled
This commit is contained in:
@@ -96,6 +96,7 @@ gman aws sts get-caller-identity
|
|||||||
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
||||||
- [Azure Key Vault](#provider-azure_key_vault)
|
- [Azure Key Vault](#provider-azure_key_vault)
|
||||||
- [Gopass](#provider-gopass)
|
- [Gopass](#provider-gopass)
|
||||||
|
- [1Password](#provider-one_password)
|
||||||
- [Run Configurations](#run-configurations)
|
- [Run Configurations](#run-configurations)
|
||||||
- [Specifying a Default Provider per Run Config](#specifying-a-default-provider-per-run-config)
|
- [Specifying a Default Provider per Run Config](#specifying-a-default-provider-per-run-config)
|
||||||
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
||||||
@@ -287,7 +288,7 @@ documented and added without breaking existing setups. The following table shows
|
|||||||
| [`azure_key_vault`](https://azure.microsoft.com/en-us/products/key-vault/) | ✅ | [Azure Key Vault](#provider-azure_key_vault) | |
|
| [`azure_key_vault`](https://azure.microsoft.com/en-us/products/key-vault/) | ✅ | [Azure Key Vault](#provider-azure_key_vault) | |
|
||||||
| [`gcp_secret_manager`](https://cloud.google.com/security/products/secret-manager?hl=en) | ✅ | [GCP Secret Manager](#provider-gcp_secret_manager) | |
|
| [`gcp_secret_manager`](https://cloud.google.com/security/products/secret-manager?hl=en) | ✅ | [GCP Secret Manager](#provider-gcp_secret_manager) | |
|
||||||
| [`gopass`](https://www.gopass.pw/) | ✅ | | |
|
| [`gopass`](https://www.gopass.pw/) | ✅ | | |
|
||||||
| [`1password`](https://1password.com/) | 🕒 | | |
|
| [`1password`](https://1password.com/) | ✅ | [1Password](#provider-one_password) | |
|
||||||
| [`bitwarden`](https://bitwarden.com/) | 🕒 | | |
|
| [`bitwarden`](https://bitwarden.com/) | 🕒 | | |
|
||||||
| [`dashlane`](https://www.dashlane.com/) | 🕒 | | Waiting for CLI support for adding secrets |
|
| [`dashlane`](https://www.dashlane.com/) | 🕒 | | Waiting for CLI support for adding secrets |
|
||||||
| [`lastpass`](https://www.lastpass.com/) | 🕒 | | |
|
| [`lastpass`](https://www.lastpass.com/) | 🕒 | | |
|
||||||
@@ -450,6 +451,42 @@ Important notes:
|
|||||||
- Secrets are managed using gopass's native commands; `gman` acts as a wrapper to interface with gopass.
|
- Secrets are managed using gopass's native commands; `gman` acts as a wrapper to interface with gopass.
|
||||||
- Updates overwrite existing secrets
|
- Updates overwrite existing secrets
|
||||||
- If no store is specified, the default gopass store is used and `gman sync` will sync with all configured stores.
|
- If no store is specified, the default gopass store is used and `gman sync` will sync with all configured stores.
|
||||||
|
|
||||||
|
### Provider: `one_password`
|
||||||
|
The `one_password` provider uses the [1Password CLI (`op`)](https://developer.1password.com/docs/cli/) as the backing
|
||||||
|
storage location for secrets.
|
||||||
|
|
||||||
|
- Optional: `vault` (string) to specify which 1Password vault to use. If omitted, the default vault is used.
|
||||||
|
- Optional: `account` (string) to specify which 1Password account to use. Useful if you have multiple accounts. If
|
||||||
|
omitted, the default signed-in account is used.
|
||||||
|
|
||||||
|
Configuration example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
default_provider: op
|
||||||
|
providers:
|
||||||
|
- name: op
|
||||||
|
type: one_password
|
||||||
|
vault: Production # Optional; if omitted, uses the default vault
|
||||||
|
account: my.1password.com # Optional; if omitted, uses the default account
|
||||||
|
```
|
||||||
|
|
||||||
|
Authentication:
|
||||||
|
- **Interactive**: Run `op signin` to sign in interactively.
|
||||||
|
- **Service Account**: Set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable for non-interactive/CI usage.
|
||||||
|
- **Desktop App Integration**: If the 1Password desktop app is installed and configured, the CLI can use biometric
|
||||||
|
authentication (Touch ID, Windows Hello, etc.).
|
||||||
|
|
||||||
|
Important notes:
|
||||||
|
- Ensure the 1Password CLI (`op`) is installed on your system. Install instructions are at
|
||||||
|
https://developer.1password.com/docs/cli/get-started/.
|
||||||
|
- Secrets are stored as 1Password Password items. The item title is the secret name and the `password` field holds the
|
||||||
|
secret value.
|
||||||
|
- **Deletions are permanent. Deleted items are not archived.**
|
||||||
|
- `add` creates a new Password item. If an item with the same title already exists in the vault, `op` will create a
|
||||||
|
duplicate. Use `update` to change an existing secret value.
|
||||||
|
- `list` returns the titles of all items in the configured vault.
|
||||||
|
|
||||||
## Run Configurations
|
## Run Configurations
|
||||||
|
|
||||||
Run configurations (or "profiles") tell `gman` how to inject secrets into a command. Three modes of secret injection are
|
Run configurations (or "profiles") tell `gman` how to inject secrets into a command. Three modes of secret injection are
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ impl ProviderConfig {
|
|||||||
debug!("Using Gopass provider");
|
debug!("Using Gopass provider");
|
||||||
provider_def
|
provider_def
|
||||||
}
|
}
|
||||||
|
SupportedProvider::OnePassword { provider_def } => {
|
||||||
|
debug!("Using 1Password provider");
|
||||||
|
provider_def
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ pub mod gcp_secret_manager;
|
|||||||
mod git_sync;
|
mod git_sync;
|
||||||
pub mod gopass;
|
pub mod gopass;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
pub mod one_password;
|
||||||
|
|
||||||
use crate::providers::gopass::GopassProvider;
|
use crate::providers::gopass::GopassProvider;
|
||||||
use crate::providers::local::LocalProvider;
|
use crate::providers::local::LocalProvider;
|
||||||
|
use crate::providers::one_password::OnePasswordProvider;
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use aws_secrets_manager::AwsSecretsManagerProvider;
|
use aws_secrets_manager::AwsSecretsManagerProvider;
|
||||||
use azure_key_vault::AzureKeyVaultProvider;
|
use azure_key_vault::AzureKeyVaultProvider;
|
||||||
@@ -76,6 +78,10 @@ pub enum SupportedProvider {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
provider_def: GopassProvider,
|
provider_def: GopassProvider,
|
||||||
},
|
},
|
||||||
|
OnePassword {
|
||||||
|
#[serde(flatten)]
|
||||||
|
provider_def: OnePasswordProvider,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Validate for SupportedProvider {
|
impl Validate for SupportedProvider {
|
||||||
@@ -86,6 +92,7 @@ impl Validate for SupportedProvider {
|
|||||||
SupportedProvider::GcpSecretManager { provider_def } => provider_def.validate(),
|
SupportedProvider::GcpSecretManager { provider_def } => provider_def.validate(),
|
||||||
SupportedProvider::AzureKeyVault { provider_def } => provider_def.validate(),
|
SupportedProvider::AzureKeyVault { provider_def } => provider_def.validate(),
|
||||||
SupportedProvider::Gopass { provider_def } => provider_def.validate(),
|
SupportedProvider::Gopass { provider_def } => provider_def.validate(),
|
||||||
|
SupportedProvider::OnePassword { provider_def } => provider_def.validate(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +113,7 @@ impl Display for SupportedProvider {
|
|||||||
SupportedProvider::GcpSecretManager { .. } => write!(f, "gcp_secret_manager"),
|
SupportedProvider::GcpSecretManager { .. } => write!(f, "gcp_secret_manager"),
|
||||||
SupportedProvider::AzureKeyVault { .. } => write!(f, "azure_key_vault"),
|
SupportedProvider::AzureKeyVault { .. } => write!(f, "azure_key_vault"),
|
||||||
SupportedProvider::Gopass { .. } => write!(f, "gopass"),
|
SupportedProvider::Gopass { .. } => write!(f, "gopass"),
|
||||||
|
SupportedProvider::OnePassword { .. } => write!(f, "one_password"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
use crate::providers::{ENV_PATH, SecretProvider};
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
/// 1Password-based secret provider.
|
||||||
|
/// See [1Password CLI](https://developer.1password.com/docs/cli/) for more
|
||||||
|
/// information.
|
||||||
|
///
|
||||||
|
/// You must already have the 1Password CLI (`op`) installed and configured
|
||||||
|
/// on your system.
|
||||||
|
///
|
||||||
|
/// This provider stores secrets as 1Password Password items. It requires
|
||||||
|
/// an optional vault name and an optional account identifier to be specified.
|
||||||
|
/// If no vault is specified, the user's default vault is used. If no account
|
||||||
|
/// is specified, the default signed-in account is used.
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
/// ```no_run
|
||||||
|
/// use gman::providers::one_password::OnePasswordProvider;
|
||||||
|
/// use gman::providers::{SecretProvider, SupportedProvider};
|
||||||
|
/// use gman::config::Config;
|
||||||
|
///
|
||||||
|
/// let provider = OnePasswordProvider::default();
|
||||||
|
/// let _ = provider.set_secret("MY_SECRET", "value");
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct OnePasswordProvider {
|
||||||
|
pub vault: Option<String>,
|
||||||
|
pub account: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnePasswordProvider {
|
||||||
|
fn base_command(&self) -> Command {
|
||||||
|
let mut cmd = Command::new("op");
|
||||||
|
cmd.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"));
|
||||||
|
if let Some(account) = &self.account {
|
||||||
|
cmd.args(["--account", account]);
|
||||||
|
}
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_args(&self) -> Vec<&str> {
|
||||||
|
match &self.vault {
|
||||||
|
Some(vault) => vec!["--vault", vault],
|
||||||
|
None => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl SecretProvider for OnePasswordProvider {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"OnePasswordProvider"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_secret(&self, key: &str) -> Result<String> {
|
||||||
|
ensure_op_installed()?;
|
||||||
|
|
||||||
|
let mut cmd = self.base_command();
|
||||||
|
cmd.args(["item", "get", key, "--fields", "password", "--reveal"]);
|
||||||
|
cmd.args(self.vault_args());
|
||||||
|
cmd.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
child
|
||||||
|
.stdout
|
||||||
|
.as_mut()
|
||||||
|
.expect("Failed to open op stdout")
|
||||||
|
.read_to_string(&mut output)
|
||||||
|
.context("Failed to read op output")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on op process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("op command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output.trim_end_matches(&['\r', '\n'][..]).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||||
|
ensure_op_installed()?;
|
||||||
|
|
||||||
|
let mut cmd = self.base_command();
|
||||||
|
cmd.args(["item", "create", "--category", "password", "--title", key]);
|
||||||
|
cmd.args(self.vault_args());
|
||||||
|
cmd.arg(format!("password={}", value));
|
||||||
|
cmd.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on op process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("op command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||||
|
ensure_op_installed()?;
|
||||||
|
|
||||||
|
let mut cmd = self.base_command();
|
||||||
|
cmd.args(["item", "edit", key]);
|
||||||
|
cmd.args(self.vault_args());
|
||||||
|
cmd.arg(format!("password={}", value));
|
||||||
|
cmd.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on op process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("op command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_secret(&self, key: &str) -> Result<()> {
|
||||||
|
ensure_op_installed()?;
|
||||||
|
|
||||||
|
let mut cmd = self.base_command();
|
||||||
|
cmd.args(["item", "delete", key]);
|
||||||
|
cmd.args(self.vault_args());
|
||||||
|
cmd.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on op process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("op command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_secrets(&self) -> Result<Vec<String>> {
|
||||||
|
ensure_op_installed()?;
|
||||||
|
|
||||||
|
let mut cmd = self.base_command();
|
||||||
|
cmd.args(["item", "list", "--format", "json"]);
|
||||||
|
cmd.args(self.vault_args());
|
||||||
|
cmd.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
child
|
||||||
|
.stdout
|
||||||
|
.as_mut()
|
||||||
|
.expect("Failed to open op stdout")
|
||||||
|
.read_to_string(&mut output)
|
||||||
|
.context("Failed to read op output")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on op process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("op command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let items: Vec<serde_json::Value> =
|
||||||
|
serde_json::from_str(&output).context("Failed to parse op item list JSON output")?;
|
||||||
|
|
||||||
|
let secrets: Vec<String> = items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| item.get("title").and_then(|t| t.as_str()))
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(secrets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_op_installed() -> Result<()> {
|
||||||
|
if which::which("op").is_err() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"1Password CLI (op) is not installed or not found in PATH. \
|
||||||
|
Please install it from https://developer.1password.com/docs/cli/get-started/"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@ mod azure_key_vault_tests;
|
|||||||
mod gcp_secret_manager_tests;
|
mod gcp_secret_manager_tests;
|
||||||
mod gopass_tests;
|
mod gopass_tests;
|
||||||
mod local_tests;
|
mod local_tests;
|
||||||
|
mod one_password_tests;
|
||||||
mod provider_tests;
|
mod provider_tests;
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
use gman::config::{Config, ProviderConfig};
|
||||||
|
use gman::providers::{SecretProvider, SupportedProvider};
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_supported_provider_display_and_validate_from_yaml() {
|
||||||
|
let yaml = r#"---
|
||||||
|
type: one_password
|
||||||
|
vault: Production
|
||||||
|
account: my.1password.com
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||||
|
assert!(sp.validate().is_ok());
|
||||||
|
assert_eq!(sp.to_string(), "one_password");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_supported_provider_minimal_yaml() {
|
||||||
|
let yaml = r#"---
|
||||||
|
type: one_password
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||||
|
assert!(sp.validate().is_ok());
|
||||||
|
assert_eq!(sp.to_string(), "one_password");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_supported_provider_vault_only() {
|
||||||
|
let yaml = r#"---
|
||||||
|
type: one_password
|
||||||
|
vault: Personal
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||||
|
assert!(sp.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_supported_provider_account_only() {
|
||||||
|
let yaml = r#"---
|
||||||
|
type: one_password
|
||||||
|
account: team.1password.com
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||||
|
assert!(sp.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_supported_provider_rejects_unknown_fields() {
|
||||||
|
let yaml = r#"---
|
||||||
|
type: one_password
|
||||||
|
vault: Production
|
||||||
|
unknown_field: bad
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result: Result<SupportedProvider, _> = serde_yaml::from_str(yaml);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_provider_config_with_one_password_deserialize_and_extract() {
|
||||||
|
let yaml = r#"---
|
||||||
|
name: op
|
||||||
|
type: one_password
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let pc: ProviderConfig = serde_yaml::from_str(yaml).expect("valid provider config yaml");
|
||||||
|
assert!(pc.validate().is_ok());
|
||||||
|
|
||||||
|
let mut pc_owned = pc.clone();
|
||||||
|
let provider: &mut dyn SecretProvider = pc_owned.extract_provider();
|
||||||
|
assert_str_eq!(provider.name(), "OnePasswordProvider");
|
||||||
|
|
||||||
|
let cfg_yaml = r#"---
|
||||||
|
default_provider: op
|
||||||
|
providers:
|
||||||
|
- name: op
|
||||||
|
type: one_password
|
||||||
|
vault: Production
|
||||||
|
account: my.1password.com
|
||||||
|
"#;
|
||||||
|
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
|
||||||
|
assert!(cfg.validate().is_ok());
|
||||||
|
|
||||||
|
let extracted = cfg
|
||||||
|
.extract_provider_config(None)
|
||||||
|
.expect("should find default provider");
|
||||||
|
assert_eq!(extracted.name.as_deref(), Some("op"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_password_config_with_multiple_providers() {
|
||||||
|
let cfg_yaml = r#"---
|
||||||
|
default_provider: local
|
||||||
|
providers:
|
||||||
|
- name: local
|
||||||
|
type: local
|
||||||
|
- name: op
|
||||||
|
type: one_password
|
||||||
|
vault: Production
|
||||||
|
"#;
|
||||||
|
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
|
||||||
|
assert!(cfg.validate().is_ok());
|
||||||
|
|
||||||
|
let extracted = cfg
|
||||||
|
.extract_provider_config(Some("op".into()))
|
||||||
|
.expect("should find op provider");
|
||||||
|
assert_eq!(extracted.name.as_deref(), Some("op"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user