use crate::{SecretStore, SecretStoreError}; use async_trait::async_trait; use infisical::{ AuthMethod, InfisicalError, client::Client, secrets::{CreateSecretRequest, GetSecretRequest, UpdateSecretRequest}, }; use log::{info, warn}; #[derive(Debug)] pub struct InfisicalSecretStore { client: Client, project_id: String, environment: String, } impl InfisicalSecretStore { /// Creates a new, authenticated Infisical client. pub async fn new( base_url: String, project_id: String, environment: String, client_id: String, client_secret: String, ) -> Result { info!("INFISICAL_STORE: Initializing client for URL: {base_url}"); // The builder and login logic remains the same. let mut client = Client::builder().base_url(base_url).build().await?; let auth_method = AuthMethod::new_universal_auth(client_id, client_secret); client.login(auth_method).await?; info!("INFISICAL_STORE: Client authenticated successfully."); Ok(Self { client, project_id, environment, }) } } #[async_trait] impl SecretStore for InfisicalSecretStore { async fn get_raw(&self, _environment: &str, key: &str) -> Result, SecretStoreError> { let environment = &self.environment; info!("INFISICAL_STORE: Getting key '{key}' from environment '{environment}'"); let request = GetSecretRequest::builder(key, &self.project_id, environment).build(); match self.client.secrets().get(request).await { Ok(secret) => Ok(secret.secret_value.into_bytes()), Err(e) => { // Correctly match against the actual InfisicalError enum. match e { // The specific case for a 404 Not Found error. InfisicalError::HttpError { status, .. } if status == http::StatusCode::NOT_FOUND => { Err(SecretStoreError::NotFound { namespace: environment.to_string(), key: key.to_string(), }) } // For all other errors, wrap them in our generic Store error. _ => Err(SecretStoreError::Store(Box::new(e))), } } } } async fn set_raw( &self, _environment: &str, key: &str, val: &[u8], ) -> Result<(), SecretStoreError> { info!( "INFISICAL_STORE: Setting key '{key}' in environment '{}'", self.environment ); let value_str = String::from_utf8(val.to_vec()).map_err(|e| SecretStoreError::Store(Box::new(e)))?; // --- Upsert Logic --- // First, attempt to update the secret. let update_req = UpdateSecretRequest::builder(key, &self.project_id, &self.environment) .secret_value(&value_str) .build(); match self.client.secrets().update(update_req).await { Ok(_) => { info!("INFISICAL_STORE: Successfully updated secret '{key}'."); Ok(()) } Err(e) => { // If the update failed, check if it was because the secret doesn't exist. match e { InfisicalError::HttpError { status, .. } if status == http::StatusCode::NOT_FOUND => { // The secret was not found, so we create it instead. warn!( "INFISICAL_STORE: Secret '{key}' not found for update, attempting to create it." ); let create_req = CreateSecretRequest::builder( key, &value_str, &self.project_id, &self.environment, ) .build(); // Handle potential errors during creation. self.client .secrets() .create(create_req) .await .map_err(|create_err| SecretStoreError::Store(Box::new(create_err)))?; info!("INFISICAL_STORE: Successfully created secret '{key}'."); Ok(()) } // Any other error during update is a genuine failure. _ => Err(SecretStoreError::Store(Box::new(e))), } } } } }