feat: lazysecrets list prints every source, vault and secret, for fzf and editor pickers

This commit is contained in:
keb committed 2026-10-14 21:24:01 +02:00
1 parent dbb0eb2bbd
commit b1455451e6
13 files changed
+886 -91

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"
"time"
"git.kebcloud.no/keb/lazysecrets/internal/cachefile"
"git.kebcloud.no/keb/lazysecrets/internal/listing"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
)
// listCmd prints every source, vault and secret lazysecrets can find, one
// per line, for fzf, editor pickers and scripts:
//
// lazysecrets list | fzf --delimiter='\t' --with-nth=3 | cut -f1
//
// What's cached prints at once; what's missing or older than --max-age
// follows as it loads. Lists that can't be read are reported on stderr.
func listCmd(providers []provider.Provider, cachePath string, timeout func(provider.Kind) time.Duration, args []string, out, errOut io.Writer) error {
fs := flag.NewFlagSet("list", flag.ContinueOnError)
cached := fs.Bool("cached", false, "only what's cached, and repos: load nothing from Azure or Databricks")
maxAge := fs.Duration("max-age", 2*time.Minute, "load cached lists older than this again")
format := fs.String("format", "text", "text: link, kind and name, tab-separated; json: an object per line")
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() > 0 {
return fmt.Errorf("usage: lazysecrets list [--cached] [--max-age 2m] [--format text|json]")
}
var write func(listing.Entry) error
switch *format {
case "text":
write = func(e listing.Entry) error {
name := strings.Join(e.Path, " / ")
if e.Account != "" {
name += " · " + e.Account
}
_, err := fmt.Fprintf(out, "%s\t%s\t%s\n", e.Link, e.Kind, name)
return err
}
case "json":
enc := json.NewEncoder(out)
enc.SetEscapeHTML(false)
write = func(e listing.Entry) error { return enc.Encode(e) }
default:
return fmt.Errorf("--format: %q is not text or json", *format)
}
// When the reader goes away (a pick in fzf), writing fails instead of
// killing the process, so what loaded is still saved.
signal.Notify(make(chan os.Signal, 1), syscall.SIGPIPE)
var gone error
emit := func(e listing.Entry) error {
if err := write(e); err != nil {
gone = err
return err
}
return nil
}
f := cachefile.New()
if cachePath != "" {
f = cachefile.Read(cachePath)
}
f, errs := listing.Run(context.Background(), listing.Options{
Providers: providers, Cache: f, MaxAge: *maxAge, Cached: *cached, Timeout: timeout,
}, emit)
if cachePath != "" && !*cached {
byID := map[string]provider.Provider{}
for _, p := range providers {
byID[p.ID()] = p
}
keep := func(id string) bool { p, ok := byID[id]; return ok && p.Kind() != provider.KindRepo }
if err := cachefile.Save(cachePath, f, keep, time.Now()); err != nil {
errs = append(errs, err)
}
}
if gone != nil {
if errors.Is(gone, syscall.EPIPE) {
return nil // the reader has what it wanted
}
return gone
}
for _, err := range errs {
fmt.Fprintln(errOut, "lazysecrets: list:", err)
}
return nil
}