`lazysecrets list` prints every source, vault and secret lazysecrets can find, one per line, for fzf, editor pickers and scripts: what Go to searches, without the TUI. What's cached prints at once; what's missing or older than `--max-age` loads in the background and streams in. It stops when the reader goes (a pick in fzf), saving what it loaded. - Text: kind, mark (✗/⚠, ★, ◷ and the provider icon), name, status (why it couldn't be read, or expired / expires in 12d), note, account and link, tab-separated; kind first and account and link last, so new fields go between. Coloured from the theme; `--color never`, `--icons=false`, `--stars=false`, `--recent`, `--notes=false`, `--expiry=false`. - `--format json`: an object per line, with starred, recent, lastUsed and uses, expires, enabled, note, reference and url too. - `--kind` lists only some kinds, and doesn't load lists only other kinds need; `--cached` loads nothing. - `lazysecrets open --action go|copy|show` skips the "what to do" menu and ends in the list, like Go to; `open` and `get` take `--account`, the account list printed, rather than asking which. - The cache file moved to `internal/cachefile`: saves merge with the file under a lock, so list and the TUI don't undo each other. - `ui.recentIcon` marks items used lately with ◷ in the TUI's lists too; Recent now means the last 90 days. - --demo has stars, notes, recent use, and expired, expiring and disabled secrets. - docs/integration.md documents get, list and an fzf picker; docs/links.md is docs/sharing.md. Reviewed-on: #70 Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
|
|
"git.kebcloud.no/keb/lazysecrets/internal/cachefile"
|
|
"git.kebcloud.no/keb/lazysecrets/internal/provider"
|
|
)
|
|
|
|
// containerKey identifies a container in the UI and caches. The same vault
|
|
// seen through two accounts must not share cached lists, since each
|
|
// account may see different things.
|
|
func containerKey(c provider.Container) string { return cachefile.ContainerKey(c) }
|
|
|
|
// providerFor finds the provider instance a source came from. Sources
|
|
// remembered from an earlier run (favourites) may name an account that
|
|
// isn't signed in any more, or predate accounts; they fall back to the
|
|
// first provider of the same kind.
|
|
func (m Model) providerFor(src provider.Source) provider.Provider {
|
|
if p, ok := m.byID[src.Provider]; ok {
|
|
return p
|
|
}
|
|
for _, p := range m.providers {
|
|
if p.Kind() == src.Kind {
|
|
return p
|
|
}
|
|
}
|
|
return missingProvider{src}
|
|
}
|
|
|
|
// candidates lists the providers that may serve src, in configured order:
|
|
// its own provider when that is known, otherwise every provider of its kind
|
|
// that lists the source (all of that kind if their sources haven't loaded).
|
|
// Favourites saved before accounts existed, or by an account that is no
|
|
// longer signed in, take the second path.
|
|
func (m Model) candidates(src provider.Source) []provider.Provider {
|
|
if p, ok := m.byID[src.Provider]; ok {
|
|
return []provider.Provider{p}
|
|
}
|
|
var seeing, ofKind []provider.Provider
|
|
for _, p := range m.providers {
|
|
if p.Kind() != src.Kind {
|
|
continue
|
|
}
|
|
ofKind = append(ofKind, p)
|
|
r, loaded := m.sourceResults[p.ID()]
|
|
if !loaded || slices.ContainsFunc(r.sources, func(s provider.Source) bool { return s.ID == src.ID }) {
|
|
seeing = append(seeing, p)
|
|
}
|
|
}
|
|
switch {
|
|
case len(seeing) > 0:
|
|
return seeing
|
|
case len(ofKind) > 0:
|
|
return ofKind
|
|
}
|
|
return []provider.Provider{missingProvider{src}}
|
|
}
|
|
|
|
// boundTo returns src as served by p.
|
|
func boundTo(src provider.Source, p provider.Provider) provider.Source {
|
|
src.Provider, src.ProviderLabel = p.ID(), p.Label()
|
|
return src
|
|
}
|
|
|
|
// missingProvider answers for a provider that isn't configured, e.g. a
|
|
// favourite from a provider disabled in the config.
|
|
type missingProvider struct{ src provider.Source }
|
|
|
|
func (p missingProvider) err() error {
|
|
return fmt.Errorf("%s isn't available (disabled in the config or not signed in)", p.src.Kind)
|
|
}
|
|
|
|
func (p missingProvider) Kind() provider.Kind { return p.src.Kind }
|
|
func (p missingProvider) ID() string { return p.src.Provider }
|
|
func (p missingProvider) Label() string { return string(p.src.Kind) }
|
|
func (p missingProvider) Sources(context.Context) ([]provider.Source, error) {
|
|
return nil, p.err()
|
|
}
|
|
func (p missingProvider) Containers(context.Context, provider.Source) ([]provider.Container, error) {
|
|
return nil, p.err()
|
|
}
|
|
func (p missingProvider) Objects(context.Context, provider.Container, provider.ObjectType) ([]provider.Object, error) {
|
|
return nil, p.err()
|
|
}
|
|
func (p missingProvider) Describe(_ context.Context, o provider.Object) (provider.Object, error) {
|
|
return o, p.err()
|
|
}
|
|
func (p missingProvider) Versions(context.Context, provider.Object) ([]provider.Object, error) {
|
|
return nil, p.err()
|
|
}
|
|
func (p missingProvider) Value(context.Context, provider.Object) (provider.SecretValue, error) {
|
|
return provider.SecretValue{}, p.err()
|
|
}
|