Module: Pangea::Secrets
- Defined in:
- lib/pangea/secrets.rb
Overview
Unified secret resolution for Pangea templates.
Single interface for all secret access — templates never know (or care) which backend provides the value. Resolution chain is tried in order until one succeeds.
Resolution chain (first match wins):
1. Environment variable (CI, manual override)
2. sops-nix pre-decrypted file (darwin-rebuild / nixos-rebuild)
3. SOPS CLI extraction (fallback — requires sops + age key)
Usage:
Pangea::Secrets.configure(
sops_file: '/path/to/secrets.yaml',
sops_nix_dir: '~/.config/sops-nix/secrets',
)
api_key = Pangea::Secrets.resolve('porkbun/api-key')
# Tries: ENV['PORKBUN_API_KEY'] → ~/.config/sops-nix/secrets/porkbun/api-key → sops -d
# Or with explicit env var name:
api_key = Pangea::Secrets.resolve('porkbun/api-key', env: 'MY_CUSTOM_VAR')
Defined Under Namespace
Classes: ResolutionError
Class Method Summary collapse
-
.configure(sops_file: nil, sops_nix_dir: nil) ⇒ Object
Configure default paths for secret resolution.
-
.exists?(path) ⇒ Boolean
Check if a secret exists without retrieving its value.
-
.reset! ⇒ Object
Reset configuration (for testing).
-
.resolve(path, env: nil, required: true) ⇒ String
Resolve a secret by path.
-
.resolve_optional(path, env: nil) ⇒ String?
Resolve a secret, returning nil instead of raising.
Class Method Details
.configure(sops_file: nil, sops_nix_dir: nil) ⇒ Object
Configure default paths for secret resolution.
35 36 37 38 |
# File 'lib/pangea/secrets.rb', line 35 def configure(sops_file: nil, sops_nix_dir: nil) @sops_file = sops_file || default_sops_file @sops_nix_dir = sops_nix_dir || default_sops_nix_dir end |
.exists?(path) ⇒ Boolean
Check if a secret exists without retrieving its value.
90 91 92 |
# File 'lib/pangea/secrets.rb', line 90 def exists?(path) !resolve_optional(path).nil? end |
.reset! ⇒ Object
Reset configuration (for testing).
95 96 97 98 |
# File 'lib/pangea/secrets.rb', line 95 def reset! @sops_file = nil @sops_nix_dir = nil end |
.resolve(path, env: nil, required: true) ⇒ String
Resolve a secret by path.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/pangea/secrets.rb', line 47 def resolve(path, env: nil, required: true) env_var = env || path_to_env_var(path) sops_extract = path_to_sops_extract(path) nix_file = File.join(sops_nix_dir, path) # 1. Environment variable val = ENV[env_var] return val if val && !val.empty? # 2. sops-nix pre-decrypted file if File.exist?(nix_file) val = File.read(nix_file).strip return val unless val.empty? end # 3. SOPS CLI extraction if File.exist?(sops_file) result = `sops --decrypt --extract '#{sops_extract}' #{sops_file} 2>/dev/null` return result.strip if $?.success? && !result.strip.empty? end # Not found if required raise ResolutionError, "secret '#{path}' not found in: ENV[#{env_var}], #{nix_file}, SOPS[#{sops_extract}]" end nil end |
.resolve_optional(path, env: nil) ⇒ String?
Resolve a secret, returning nil instead of raising.
82 83 84 |
# File 'lib/pangea/secrets.rb', line 82 def resolve_optional(path, env: nil) resolve(path, env: env, required: false) end |