feat: lazysecrets list prints everything lazysecrets can find, for fzf and editor pickers #70
No files matched your search
@@ -31,7 +31,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Each line is the kind, a mark (✗ or ⚠, ★, ◷ and the provider's icon),
|
// Each line is the kind, a mark (✗ or ⚠, ★, ◷ and the provider's icon),
|
||||||
// the name, a status (why it couldn't be read, or that it has expired or
|
// the name, a status (why it couldn't be read, or that it has expired or
|
||||||
// expires soon; else empty), the account and the link, tab-separated. The mark has a field of its own so a search can
|
// expires soon; else empty), its note (muted, or empty), the account and
|
||||||
|
// the link, tab-separated. The mark has a field of its own so a search can
|
||||||
// leave it out (fzf --nth), and ^prod finds names starting with prod. The
|
// leave it out (fzf --nth), and ^prod finds names starting with prod. The
|
||||||
// name is coloured from the theme,
|
// name is coloured from the theme,
|
||||||
// with the provider's icon (as in the TUI: --icons, LAZYSECRETS_ICONS,
|
// with the provider's icon (as in the TUI: --icons, LAZYSECRETS_ICONS,
|
||||||
@@ -45,7 +46,7 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
|||||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||||
cached := fs.Bool("cached", false, "only what's cached, and repos: load nothing from Azure or Databricks")
|
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")
|
maxAge := fs.Duration("max-age", 2*time.Minute, "load cached lists older than this again")
|
||||||
format := fs.String("format", "text", "text: kind, mark, name, status, account and link, tab-separated; json: an object per line")
|
format := fs.String("format", "text", "text: kind, mark, name, status, note, account and link, tab-separated; json: an object per line")
|
||||||
color := fs.String("color", "always", "colour the names (text): always or never; fzf needs --ansi")
|
color := fs.String("color", "always", "colour the names (text): always or never; fzf needs --ansi")
|
||||||
iconsOn := fs.Bool("icons", withIcons, "put the provider's Nerd Font icon before names (text)")
|
iconsOn := fs.Bool("icons", withIcons, "put the provider's Nerd Font icon before names (text)")
|
||||||
stars := fs.Bool("stars", true, "put ★ before starred names, in the theme's favouriteColor (text)")
|
stars := fs.Bool("stars", true, "put ★ before starred names, in the theme's favouriteColor (text)")
|
||||||
@@ -91,8 +92,10 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
|||||||
if status == "" && *expiry {
|
if status == "" && *expiry {
|
||||||
status = expiryOf(e, theme, expiryWarn, now, colored)
|
status = expiryOf(e, theme, expiryWarn, now, colored)
|
||||||
}
|
}
|
||||||
|
note := renderer(colored)(theme.Muted, e.Note)
|
||||||
// A tab or line break in a name would break the line up.
|
// A tab or line break in a name would break the line up.
|
||||||
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\n", e.Kind, mark, oneLine.Replace(name), oneLine.Replace(status), e.Account, e.Link)
|
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", e.Kind, mark, oneLine.Replace(name), oneLine.Replace(status),
|
||||||
|
oneLine.Replace(note), e.Account, e.Link)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
@@ -124,9 +127,9 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
|||||||
}
|
}
|
||||||
f = listing.Run(context.Background(), listing.Options{
|
f = listing.Run(context.Background(), listing.Options{
|
||||||
Providers: providers, Cache: f, MaxAge: *maxAge, Cached: *cached, Kinds: only, Timeout: timeout,
|
Providers: providers, Cache: f, MaxAge: *maxAge, Cached: *cached, Kinds: only, Timeout: timeout,
|
||||||
Mark: func(link, account string) (bool, time.Time, int) {
|
Mark: func(link, account string) (bool, time.Time, int, string) {
|
||||||
n := st.Node(link)
|
n := st.Node(link)
|
||||||
return n.StarredFor(account), n.LastUsed, n.Uses
|
return n.StarredFor(account), n.LastUsed, n.Uses, n.Note
|
||||||
},
|
},
|
||||||
}, emit)
|
}, emit)
|
||||||
if cachePath != "" && !*cached {
|
if cachePath != "" && !*cached {
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ type Entry struct {
|
|||||||
// Enabled whether it's enabled, where the provider says (Azure).
|
// Enabled whether it's enabled, where the provider says (Azure).
|
||||||
Expires time.Time `json:"expires,omitzero"`
|
Expires time.Time `json:"expires,omitzero"`
|
||||||
Enabled *bool `json:"enabled,omitempty"`
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
// Note is the user's note on it, if any.
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
// Error says why a source's vaults or a vault's secrets couldn't be
|
// Error says why a source's vaults or a vault's secrets couldn't be
|
||||||
// listed. The entry is then that source or vault, again, or with an
|
// listed. The entry is then that source or vault, again, or with an
|
||||||
// account's sources unreadable, the account without a link.
|
// account's sources unreadable, the account without a link.
|
||||||
@@ -68,9 +70,9 @@ type Options struct {
|
|||||||
// alone don't load any secrets. Lists that fail are always listed.
|
// alone don't load any secrets. Lists that fail are always listed.
|
||||||
Kinds []string
|
Kinds []string
|
||||||
// Mark tells whether the entry with link is starred for account,
|
// Mark tells whether the entry with link is starred for account,
|
||||||
// and when it was last used and how often (zero: not lately); nil
|
// when it was last used and how often (zero: not lately), and its
|
||||||
// marks nothing.
|
// note; nil marks nothing.
|
||||||
Mark func(link, account string) (starred bool, lastUsed time.Time, uses int)
|
Mark func(link, account string) (starred bool, lastUsed time.Time, uses int, note string)
|
||||||
Timeout func(provider.Kind) time.Duration
|
Timeout func(provider.Kind) time.Duration
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
}
|
}
|
||||||
@@ -233,7 +235,7 @@ func (l *lister) put(p provider.Provider, e Entry) {
|
|||||||
}
|
}
|
||||||
e.Account = p.ID()
|
e.Account = p.ID()
|
||||||
if l.o.Mark != nil && e.Link != "" {
|
if l.o.Mark != nil && e.Link != "" {
|
||||||
e.Starred, e.LastUsed, e.Uses = l.o.Mark(e.Link, e.Account)
|
e.Starred, e.LastUsed, e.Uses, e.Note = l.o.Mark(e.Link, e.Account)
|
||||||
e.Recent = !e.LastUsed.IsZero()
|
e.Recent = !e.LastUsed.IsZero()
|
||||||
}
|
}
|
||||||
id := p.ID() + "\x00" + e.Link + "\x00" + e.Error
|
id := p.ID() + "\x00" + e.Link + "\x00" + e.Error
|
||||||
|
|||||||
@@ -229,14 +229,14 @@ func TestListMarks(t *testing.T) {
|
|||||||
az := fake.Azure(0)
|
az := fake.Azure(0)
|
||||||
var starredLink string
|
var starredLink string
|
||||||
got, _, _ := run(t, Options{Providers: []provider.Provider{az}, MaxAge: time.Minute, Kinds: []string{"secret"},
|
got, _, _ := run(t, Options{Providers: []provider.Provider{az}, MaxAge: time.Minute, Kinds: []string{"secret"},
|
||||||
Mark: func(link, account string) (bool, time.Time, int) {
|
Mark: func(link, account string) (bool, time.Time, int, string) {
|
||||||
if starredLink == "" && strings.HasSuffix(link, "/db-password") {
|
if starredLink == "" && strings.HasSuffix(link, "/db-password") {
|
||||||
starredLink = link
|
starredLink = link
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(link, "/api-key") {
|
if strings.HasSuffix(link, "/api-key") {
|
||||||
return false, time.Now(), 3
|
return false, time.Now(), 3, "rotate in May"
|
||||||
}
|
}
|
||||||
return link == starredLink && account == az.ID(), time.Time{}, 0
|
return link == starredLink && account == az.ID(), time.Time{}, 0, ""
|
||||||
}})
|
}})
|
||||||
var starred, recent int
|
var starred, recent int
|
||||||
for _, e := range got {
|
for _, e := range got {
|
||||||
@@ -246,6 +246,9 @@ func TestListMarks(t *testing.T) {
|
|||||||
t.Errorf("%s starred", e.Link)
|
t.Errorf("%s starred", e.Link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (e.Note != "") != strings.HasSuffix(e.Link, "/api-key") {
|
||||||
|
t.Errorf("%s: note %q", e.Link, e.Note)
|
||||||
|
}
|
||||||
if e.Recent {
|
if e.Recent {
|
||||||
recent++
|
recent++
|
||||||
if e.Uses != 3 || e.LastUsed.IsZero() {
|
if e.Uses != 3 || e.LastUsed.IsZero() {
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import (
|
|||||||
"git.kebcloud.no/keb/lazysecrets/internal/store"
|
"git.kebcloud.no/keb/lazysecrets/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Seed stars and uses a few of the demo's secrets, so --demo shows
|
// Seed stars, notes and uses a few of the demo's secrets, so --demo
|
||||||
// Favourites (F), Recent (R) and stars in lazysecrets list. alice and
|
// shows Favourites (F), Recent (R), notes, and stars in lazysecrets list. alice and
|
||||||
// admin are the two Azure accounts' provider IDs.
|
// admin are the two Azure accounts' provider IDs.
|
||||||
func Seed(st *store.Store, alice, admin string) {
|
func Seed(st *store.Store, alice, admin string) {
|
||||||
type item struct {
|
type item struct {
|
||||||
link, account, source string
|
link, account, source string
|
||||||
name string // else the link's last part
|
name string // else the link's last part
|
||||||
|
note string
|
||||||
path []string
|
path []string
|
||||||
star bool
|
star bool
|
||||||
uses int
|
uses int
|
||||||
@@ -23,11 +24,12 @@ func Seed(st *store.Store, alice, admin string) {
|
|||||||
const azure, dbx = "lazysecrets://azure/", "lazysecrets://databricks/adb-1111111111111111.11.azuredatabricks.net/"
|
const azure, dbx = "lazysecrets://azure/", "lazysecrets://databricks/adb-1111111111111111.11.azuredatabricks.net/"
|
||||||
for _, it := range []item{
|
for _, it := range []item{
|
||||||
{link: azure + "kv-app-prod.vault.azure.net/secrets/db-password", account: alice, source: "sub-prod",
|
{link: azure + "kv-app-prod.vault.azure.net/secrets/db-password", account: alice, source: "sub-prod",
|
||||||
path: []string{"platform-prod", "kv-app-prod"}, star: true, uses: 12, ago: 20 * time.Minute},
|
path: []string{"platform-prod", "kv-app-prod"}, star: true, uses: 12, ago: 20 * time.Minute,
|
||||||
|
note: "rotated by the data team each quarter"},
|
||||||
{link: azure + "kv-app-prod.vault.azure.net/secrets/api-key", account: alice, source: "sub-prod",
|
{link: azure + "kv-app-prod.vault.azure.net/secrets/api-key", account: alice, source: "sub-prod",
|
||||||
path: []string{"platform-prod", "kv-app-prod"}, uses: 4, ago: 2 * time.Hour},
|
path: []string{"platform-prod", "kv-app-prod"}, uses: 4, ago: 2 * time.Hour},
|
||||||
{link: azure + "kv-breakglass.vault.azure.net/secrets/emergency-admin", account: admin, source: "sub-prod",
|
{link: azure + "kv-breakglass.vault.azure.net/secrets/emergency-admin", account: admin, source: "sub-prod",
|
||||||
path: []string{"platform-prod", "kv-breakglass"}, star: true},
|
path: []string{"platform-prod", "kv-breakglass"}, star: true, note: "incidents only: tell #ops when used"},
|
||||||
{link: azure + "kv-shared-prod.vault.azure.net/secrets/smtp-password", account: alice, source: "sub-prod",
|
{link: azure + "kv-shared-prod.vault.azure.net/secrets/smtp-password", account: alice, source: "sub-prod",
|
||||||
path: []string{"platform-prod", "kv-shared-prod"}, uses: 2, ago: 26 * time.Hour},
|
path: []string{"platform-prod", "kv-shared-prod"}, uses: 2, ago: 26 * time.Hour},
|
||||||
{link: dbx + "app/storage-key", account: "Databricks", source: "dbx-prod",
|
{link: dbx + "app/storage-key", account: "Databricks", source: "dbx-prod",
|
||||||
@@ -43,10 +45,13 @@ func Seed(st *store.Store, alice, admin string) {
|
|||||||
n.Name = cmp.Or(it.name, it.link[strings.LastIndex(it.link, "/")+1:])
|
n.Name = cmp.Or(it.name, it.link[strings.LastIndex(it.link, "/")+1:])
|
||||||
n.Path, n.Source, n.Account = it.path, it.source, it.account
|
n.Path, n.Source, n.Account = it.path, it.source, it.account
|
||||||
}
|
}
|
||||||
if it.star {
|
if it.star || it.note != "" {
|
||||||
_ = st.Update(it.link, func(n *store.Node) {
|
_ = st.Update(it.link, func(n *store.Node) {
|
||||||
context(n)
|
context(n)
|
||||||
n.ToggleStar(it.account)
|
if it.star {
|
||||||
|
n.ToggleStar(it.account)
|
||||||
|
}
|
||||||
|
n.Note = it.note
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for range it.uses {
|
for range it.uses {
|
||||||
|
|||||||
Reference in new issue
Block a user