Skip to content

Please make it possible to use typed secret names (macro-generated, not strings), without loading all secrets at startup #29

@emchristiansen

Description

@emchristiansen

I'm using this pattern to load SS in Rust (note there's a macro bug workaround in there, too):

use std::sync::OnceLock;

use secretspec::Resolved;

// Generate typed structs from secretspec.toml
secretspec_derive::declare_secrets!("../secretspec.toml");

// Constants for default provider and profile
pub const PROVIDER: &str = "onepassword://SecretSpecMetacortex";
pub const PROFILE: &str = "default";

// Global SecretSpec instance
static SECRETSPEC: OnceLock<Resolved<SecretSpec>> = OnceLock::new();

/// Get a reference to the global SecretSpec instance
/// Lazily initializes on first access
///
/// NOTE: This uses a workaround for a bug in secretspec where the builder pattern
/// strips vault information from onepassword URIs. The builder extracts only the
/// provider name (e.g., "onepassword") from URIs like "onepassword://VaultName",
/// losing the vault specification.
///
/// TODO: Switch to builder pattern when the bug is fixed:
/// ```
/// SecretSpec::builder()
///   .with_provider(PROVIDER)
///   .with_profile(Profile::Default)
///   .load()
/// ```
///
/// Current workaround uses side-effect based API where `secretspec::Secrets`
/// is a global singleton that holds configuration. When we call set_provider()
/// and set_profile(), we're modifying global state that SecretSpec::load()
/// then reads from.
///
/// PERFORMANCE WARNING: Initial load is VERY slow (~40s for 39 secrets) because
/// SecretSpec appears to make individual `op` CLI calls for each secret instead
/// of batching them. The OnceLock ensures this only happens once per process.
pub fn secretspec() -> &'static Resolved<SecretSpec>
{
  SECRETSPEC.get_or_init(|| {
    // Load mutable Secrets and set provider/profile directly to preserve vault info
    let mut spec =
      secretspec::Secrets::load().expect("Failed to load Secrets");
    spec.set_provider(PROVIDER);
    spec.set_profile(PROFILE);

    // Now load SecretSpec using the configured spec
    // This will use the provider/profile we just set
    SecretSpec::load(None::<String>, None::<Profile>)
      .expect("Failed to load SecretSpec")
  })
}

I want to use SecretSpec for the type safety (Secret expects string args), but it loads all my secrets at startup.
With OnePassword this is very slow!

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions