S → f on a repo, an env file or a key in one copies a lazysecrets://share link holding the env files, encrypted (age) with a secret's value: whoever can read that secret can open it. The key is share.key in the repo's .lazysecrets.yaml, else in the config, else picked when sharing; on Azure the link pins its version. Opening one says what it holds, decrypts when asked, and saves the files in the clone of the repo (or a folder chosen), replacing existing ones only when asked. lazysecrets share prints a link from the command line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
package ref
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.kebcloud.no/keb/lazysecrets/internal/provider"
|
|
)
|
|
|
|
// Read reads what r points at without listing its vault: the vault and
|
|
// name are enough for Azure, tried with each account (account, the one
|
|
// that last read it, first, in source); for Databricks and repos the
|
|
// profile or repo is found by its host. It also returns the version read,
|
|
// when the provider tells (provider.Versioned).
|
|
func Read(ctx context.Context, providers []provider.Provider, r Ref, account, source string) (provider.SecretValue, string, error) {
|
|
if r.IsShare() || r.IsSource() || r.IsContainer() {
|
|
what := "vault or scope"
|
|
switch {
|
|
case r.IsShare():
|
|
what = "share link"
|
|
case r.IsSource():
|
|
what = "subscription or workspace"
|
|
}
|
|
return provider.SecretValue{}, "", fmt.Errorf("%s points at a %s, not a secret", r, what)
|
|
}
|
|
if r.Kind != provider.KindAzure && r.Host == "" {
|
|
return provider.SecretValue{}, "", errors.New("{{secrets/…}} names no workspace: use its lazysecrets:// link (s in lazysecrets)")
|
|
}
|
|
|
|
type try struct {
|
|
p provider.Provider
|
|
o provider.Object
|
|
}
|
|
var tries []try
|
|
for _, p := range providers {
|
|
if p.Kind() != r.Kind {
|
|
continue
|
|
}
|
|
o := provider.Object{Name: r.Name, Type: cmp.Or(r.Type, provider.TypeSecret), Version: r.Version}
|
|
if r.Kind != provider.KindAzure { // by host: workspaces, repos
|
|
loc, ok := p.(provider.Locator)
|
|
if !ok {
|
|
continue
|
|
}
|
|
profile, found, err := loc.Locate(ctx, r.Host)
|
|
if err != nil || !found {
|
|
continue
|
|
}
|
|
src := provider.Source{Kind: r.Kind, Provider: p.ID(), ID: profile}
|
|
o.Container = provider.Container{Source: src, Name: r.Scope}
|
|
// Listing the scopes tells a Key Vault-backed one, whose value
|
|
// can then be read from the vault if Databricks refuses.
|
|
if cs, err := p.Containers(ctx, src); err == nil {
|
|
for _, c := range cs {
|
|
if c.Name == r.Scope {
|
|
c.Source = src
|
|
o.Container = c
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
src := provider.Source{Kind: r.Kind, Provider: p.ID(), ID: source}
|
|
o.Container = provider.Container{Source: src, Name: r.ContainerName(), Detail: r.VaultURL()}
|
|
}
|
|
t := try{p, o}
|
|
if p.ID() == account {
|
|
tries = append([]try{t}, tries...)
|
|
} else {
|
|
tries = append(tries, t)
|
|
}
|
|
}
|
|
if len(tries) == 0 {
|
|
switch r.Kind {
|
|
case provider.KindDatabricks:
|
|
return provider.SecretValue{}, "", fmt.Errorf("no Databricks profile for workspace %s", r.Host)
|
|
case provider.KindRepo:
|
|
return provider.SecretValue{}, "", fmt.Errorf("repo %s isn't here: run it inside the repo, or list it under providers.repos.paths", r.Host)
|
|
}
|
|
return provider.SecretValue{}, "", fmt.Errorf("no %s account to read %s with", r.Kind, r)
|
|
}
|
|
var errs []error
|
|
for _, t := range tries {
|
|
v, version, err := value(ctx, t.p, t.o)
|
|
if err == nil {
|
|
return v, version, nil
|
|
}
|
|
if len(tries) == 1 || ctx.Err() != nil {
|
|
return provider.SecretValue{}, "", err
|
|
}
|
|
errs = append(errs, fmt.Errorf("%s: %w", t.p.Label(), err))
|
|
}
|
|
return provider.SecretValue{}, "", errors.Join(errs...)
|
|
}
|
|
|
|
func value(ctx context.Context, p provider.Provider, o provider.Object) (provider.SecretValue, string, error) {
|
|
if vp, ok := p.(provider.Versioned); ok {
|
|
return vp.VersionedValue(ctx, o)
|
|
}
|
|
v, err := p.Value(ctx, o)
|
|
return v, o.Version, err
|
|
}
|