feat: lazysecrets list prints everything lazysecrets can find, for fzf and editor pickers #70
No files matched your search
@@ -37,10 +37,15 @@ func value(ctx context.Context, providers []provider.Provider, st *store.Store,
|
|||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// readKey is value, also returning the version read.
|
// readKey is value, also returning the version read. The account asked
|
||||||
|
// for (--account) goes first, else the one that last read it.
|
||||||
func readKey(ctx context.Context, providers []provider.Provider, st *store.Store, r ref.Ref) (provider.SecretValue, string, error) {
|
func readKey(ctx context.Context, providers []provider.Provider, st *store.Store, r ref.Ref) (provider.SecretValue, string, error) {
|
||||||
key := r
|
key := r
|
||||||
key.Version = ""
|
key.Version = ""
|
||||||
node := st.Node(key.String())
|
node := st.Node(key.String())
|
||||||
return ref.Read(ctx, providers, r, node.Account, node.Source)
|
account, source := node.Account, node.Source
|
||||||
|
if r.Account != "" && r.Account != account {
|
||||||
|
account, source = r.Account, ""
|
||||||
|
}
|
||||||
|
return ref.Read(ctx, providers, r, account, source)
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,11 @@ import (
|
|||||||
// listCmd prints every source, vault and secret lazysecrets can find, one
|
// listCmd prints every source, vault and secret lazysecrets can find, one
|
||||||
// per line, for fzf, editor pickers and scripts:
|
// per line, for fzf, editor pickers and scripts:
|
||||||
//
|
//
|
||||||
// lazysecrets list | fzf --ansi --with-nth='2..-2' --accept-nth=-1
|
// lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1
|
||||||
//
|
//
|
||||||
// Each line is the kind, the name and the link, tab-separated (the kind
|
// Each line is the kind, the name, the account and the link,
|
||||||
// a word, the link without spaces, so even fields split on spaces keep
|
// tab-separated (all but the name one word each, so even fields split on
|
||||||
// them first and last): the name coloured from the theme,
|
// spaces keep them first and last): the name 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,
|
||||||
// ui.icons); the link never is.
|
// ui.icons); the link never is.
|
||||||
//
|
//
|
||||||
@@ -42,7 +42,7 @@ func listCmd(providers []provider.Provider, cachePath string, timeout func(provi
|
|||||||
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, name and link, tab-separated; json: an object per line")
|
format := fs.String("format", "text", "text: kind, name, 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)")
|
||||||
kinds := fs.String("kind", "", "only these kinds, comma-separated: "+strings.Join(listing.Kinds, ", ")+" (vault: also scopes and repo files)")
|
kinds := fs.String("kind", "", "only these kinds, comma-separated: "+strings.Join(listing.Kinds, ", ")+" (vault: also scopes and repo files)")
|
||||||
@@ -75,7 +75,7 @@ func listCmd(providers []provider.Provider, cachePath string, timeout func(provi
|
|||||||
case "text":
|
case "text":
|
||||||
write = func(e listing.Entry) error {
|
write = func(e listing.Entry) error {
|
||||||
// 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\n", e.Kind, oneLine.Replace(nameOf(e, theme, iconSet, *color == "always")), e.Link)
|
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\n", e.Kind, oneLine.Replace(nameOf(e, theme, iconSet, *color == "always")), e.Account, e.Link)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
@@ -150,8 +150,8 @@ func nameOf(e listing.Entry, theme styles.Styles, iconSet icons.Set, color bool)
|
|||||||
}
|
}
|
||||||
name += e.Path[n-1]
|
name += e.Path[n-1]
|
||||||
}
|
}
|
||||||
if e.Account != "" {
|
if e.AccountLabel != "" {
|
||||||
name += render(theme.Muted, " · "+e.Account)
|
name += render(theme.Muted, " · "+e.AccountLabel)
|
||||||
}
|
}
|
||||||
if e.Error != "" {
|
if e.Error != "" {
|
||||||
name += " " + render(theme.Error, "✗ "+e.Error)
|
name += " " + render(theme.Error, "✗ "+e.Error)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestListNames(t *testing.T) {
|
func TestListNames(t *testing.T) {
|
||||||
e := listing.Entry{Provider: "azure", Path: []string{"platform-prod", "kv-app-prod", "db-password"}, Account: "alice"}
|
e := listing.Entry{Provider: "azure", Path: []string{"platform-prod", "kv-app-prod", "db-password"}, AccountLabel: "alice"}
|
||||||
if got := nameOf(e, styles.Default(), icons.None, false); got != "platform-prod / kv-app-prod / db-password · alice" {
|
if got := nameOf(e, styles.Default(), icons.None, false); got != "platform-prod / kv-app-prod / db-password · alice" {
|
||||||
t.Errorf("plain: %q", got)
|
t.Errorf("plain: %q", got)
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-6
@@ -89,6 +89,22 @@ func cmpOr(a, b string) string {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refArg parses get's and open's arguments: a reference, and optionally
|
||||||
|
// the account to use when several could (lazysecrets list's column).
|
||||||
|
func refArg(cmd string, args []string) (ref.Ref, error) {
|
||||||
|
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
|
||||||
|
account := fs.String("account", "", "the account to use when several could, as lazysecrets list prints it")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return ref.Ref{}, err
|
||||||
|
}
|
||||||
|
if fs.NArg() != 1 {
|
||||||
|
return ref.Ref{}, fmt.Errorf("usage: lazysecrets %s [--account <account>] <ref>", cmd)
|
||||||
|
}
|
||||||
|
r, err := ref.Parse(fs.Arg(0))
|
||||||
|
r.Account = *account
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
// defaultConfigPath is $LAZYSECRETS_CONFIG_FILE, else config.yaml in the
|
// defaultConfigPath is $LAZYSECRETS_CONFIG_FILE, else config.yaml in the
|
||||||
// config directory.
|
// config directory.
|
||||||
func defaultConfigPath() string {
|
func defaultConfigPath() string {
|
||||||
@@ -115,11 +131,12 @@ func run() error {
|
|||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), `Usage:
|
fmt.Fprintf(flag.CommandLine.Output(), `Usage:
|
||||||
lazysecrets [flags] browse secrets
|
lazysecrets [flags] browse secrets
|
||||||
lazysecrets [flags] [open] <ref>
|
lazysecrets [flags] [open [--account <account>]] <ref>
|
||||||
open a lazysecrets:// link (also a share link),
|
open a lazysecrets:// link (also a share link),
|
||||||
Key Vault URL, @Microsoft.KeyVault(...) or
|
Key Vault URL, @Microsoft.KeyVault(...) or
|
||||||
{{secrets/scope/key}}
|
{{secrets/scope/key}}
|
||||||
lazysecrets [flags] get <ref> print the value a lazysecrets:// link points at,
|
lazysecrets [flags] get [--account <account>] <ref>
|
||||||
|
print the value a lazysecrets:// link points at,
|
||||||
for scripts: VAR=$(lazysecrets get <ref>)
|
for scripts: VAR=$(lazysecrets get <ref>)
|
||||||
lazysecrets [flags] list [--kind secret,…] [--cached] [--max-age 2m] [--format text|json]
|
lazysecrets [flags] list [--kind secret,…] [--cached] [--max-age 2m] [--format text|json]
|
||||||
[--color always|never] [--icons=false] [--errors=false]
|
[--color always|never] [--icons=false] [--errors=false]
|
||||||
@@ -163,8 +180,8 @@ Flags:
|
|||||||
listArgs = append([]string{}, args[1:]...) // not nil: listing
|
listArgs = append([]string{}, args[1:]...) // not nil: listing
|
||||||
case len(args) == 1 && args[0] == "keybindings":
|
case len(args) == 1 && args[0] == "keybindings":
|
||||||
printKeys = true
|
printKeys = true
|
||||||
case len(args) == 2 && args[0] == "get":
|
case len(args) > 0 && args[0] == "get":
|
||||||
r, err := ref.Parse(args[1])
|
r, err := refArg("get", args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -176,8 +193,8 @@ Flags:
|
|||||||
case len(args) > 0 && args[0] == "update":
|
case len(args) > 0 && args[0] == "update":
|
||||||
return updateCmd(*configPath, args[1:])
|
return updateCmd(*configPath, args[1:])
|
||||||
case len(args) == 0:
|
case len(args) == 0:
|
||||||
case args[0] == "open" && len(args) == 2:
|
case args[0] == "open" && len(args) >= 2:
|
||||||
r, err := ref.Parse(args[1])
|
r, err := refArg("open", args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-21
@@ -138,26 +138,39 @@ a newline only on a terminal.
|
|||||||
|
|
||||||
### Listing, for fzf and pickers
|
### Listing, for fzf and pickers
|
||||||
|
|
||||||
`lazysecrets list` prints every source, vault and secret, one per line:
|
`lazysecrets list` prints every source, vault and secret, one per line,
|
||||||
its kind (`source`, `vault`, `secret`, `key` or `certificate`), its name
|
tab-separated:
|
||||||
(`platform-prod / kv-app-prod / db-password`) and its link,
|
|
||||||
tab-separated. It's what Go to searches, so fzf, or an editor's picker,
|
| Field | |
|
||||||
can search it instead, showing the names and acting on the links:
|
|---|---|
|
||||||
|
| kind | `source`, `vault`, `secret`, `key` or `certificate` |
|
||||||
|
| name | `platform-prod / kv-app-prod / db-password` |
|
||||||
|
| account | what it's listed through: an az account (`Azure:<user>/<tenant>`), `Databricks` or `Repos` |
|
||||||
|
| link | its `lazysecrets://` link |
|
||||||
|
|
||||||
|
It's what Go to searches, so fzf, or an editor's picker, can search it
|
||||||
|
instead, showing the names and acting on the links:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
lazysecrets list | fzf --ansi --with-nth='2..-2' --accept-nth=-1 \
|
lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1 \
|
||||||
--bind 'ctrl-o:become(lazysecrets open {-1})' \
|
--bind 'ctrl-o:become(lazysecrets open --account {-2} {-1})' \
|
||||||
--bind 'ctrl-y:execute-silent(lazysecrets get {-1} | pbcopy)'
|
--bind 'ctrl-y:execute-silent(lazysecrets get --account {-2} {-1} | pbcopy)'
|
||||||
```
|
```
|
||||||
|
|
||||||
`--with-nth='2..-2'` shows and searches the names, leaving out the kind,
|
`--with-nth='2..-3'` shows and searches only the names; `{-1}` and
|
||||||
the first field, and the link, the last; `{-1}` and `--accept-nth=-1` are
|
`--accept-nth=-1` are the link, `{-2}` the account. All but the name are
|
||||||
the link. The kind is one word and the link has no spaces, so this works
|
one word, so this works without `--delimiter`. To filter by kind
|
||||||
without `--delimiter`. To filter by kind yourself: `awk -F'\t' '$1 == "secret"'`. `enter` prints
|
yourself: `awk -F'\t' '$1 == "secret"'`.
|
||||||
it (`lazysecrets get $(…)`), `ctrl-o` opens it in lazysecrets and `ctrl-y`
|
|
||||||
copies the value (`pbcopy` on macOS; `wl-copy` or `xclip` on Linux). A
|
`enter` prints the link (`lazysecrets get $(…)`), `ctrl-o` opens it in
|
||||||
value copied this way isn't cleared from the clipboard after
|
lazysecrets and `ctrl-y` copies the value (`pbcopy` on macOS; `wl-copy` or
|
||||||
`values.clipboardClear`, as one copied in lazysecrets is.
|
`xclip` on Linux). A value copied this way isn't cleared from the
|
||||||
|
clipboard after `values.clipboardClear`, as one copied in lazysecrets is.
|
||||||
|
|
||||||
|
`--account` makes `open` and `get` use the account it was listed through,
|
||||||
|
when several of yours can see it: without, `open` asks which, and `get`
|
||||||
|
tries the one that last read it first. The link stays as it is, to copy
|
||||||
|
and share; the account isn't part of it.
|
||||||
|
|
||||||
What's [cached](config.md#cache) prints at once. Then, like Go to, it loads
|
What's [cached](config.md#cache) prints at once. Then, like Go to, it loads
|
||||||
what's missing or older than `--max-age` (2m), and what's new in it follows
|
what's missing or older than `--max-age` (2m), and what's new in it follows
|
||||||
@@ -183,15 +196,14 @@ shows the colours with `--ansi`. `--color never` leaves them out, for
|
|||||||
scripts, and `--icons=false` the icons. The link is never coloured.
|
scripts, and `--icons=false` the icons. The link is never coloured.
|
||||||
|
|
||||||
`--format json` prints an object per line instead, for editor plugins,
|
`--format json` prints an object per line instead, for editor plugins,
|
||||||
without colours or icons, with each entry's kind, and `error` on a list
|
without colours or icons, and with `error` on a list it couldn't read:
|
||||||
it couldn't read:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"alice"}
|
{"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"Azure:alice@example.com/<tenant>","accountLabel":"alice"}
|
||||||
```
|
```
|
||||||
|
|
||||||
`account` is there when several az accounts are signed in. Values are never
|
`accountLabel`, the account's short name, is there when several az
|
||||||
listed.
|
accounts are signed in. Values are never listed.
|
||||||
|
|
||||||
## From the browser and chat
|
## From the browser and chat
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,12 @@ type Entry struct {
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
// Path names it: its source's, vault's and own names.
|
// Path names it: its source's, vault's and own names.
|
||||||
Path []string `json:"path"`
|
Path []string `json:"path"`
|
||||||
// Account is the az account it's seen through, when there are several.
|
// Account is the provider it's listed through: an az account
|
||||||
Account string `json:"account,omitempty"`
|
// (Azure:<user>/<tenant>), Databricks or Repos. lazysecrets open and
|
||||||
|
// get take it (--account) to use that account when several could.
|
||||||
|
Account string `json:"account"`
|
||||||
|
// AccountLabel names the az account, when there are several.
|
||||||
|
AccountLabel string `json:"accountLabel,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.
|
||||||
@@ -186,12 +190,12 @@ func (l *lister) objects(p provider.Provider, c provider.Container, objs []provi
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
l.put(p, Entry{Link: r.String(), Kind: strings.ToLower(string(o.Type)), Provider: kindOf(c.Source.Kind),
|
l.put(p, Entry{Link: r.String(), Kind: strings.ToLower(string(o.Type)), Provider: kindOf(c.Source.Kind),
|
||||||
Path: []string{c.Source.Name, c.Name, o.Name}, Account: accountOf(c.Source)})
|
Path: []string{c.Source.Name, c.Name, o.Name}, AccountLabel: accountOf(c.Source)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceEntry(src provider.Source) Entry {
|
func sourceEntry(src provider.Source) Entry {
|
||||||
e := Entry{Kind: "source", Provider: kindOf(src.Kind), Path: []string{src.Name}, Account: accountOf(src)}
|
e := Entry{Kind: "source", Provider: kindOf(src.Kind), Path: []string{src.Name}, AccountLabel: accountOf(src)}
|
||||||
if r, err := ref.ForSource(src); err == nil {
|
if r, err := ref.ForSource(src); err == nil {
|
||||||
e.Link = r.String()
|
e.Link = r.String()
|
||||||
}
|
}
|
||||||
@@ -199,7 +203,7 @@ func sourceEntry(src provider.Source) Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func containerEntry(c provider.Container) Entry {
|
func containerEntry(c provider.Container) Entry {
|
||||||
e := Entry{Kind: "vault", Provider: kindOf(c.Source.Kind), Path: []string{c.Source.Name, c.Name}, Account: accountOf(c.Source)}
|
e := Entry{Kind: "vault", Provider: kindOf(c.Source.Kind), Path: []string{c.Source.Name, c.Name}, AccountLabel: accountOf(c.Source)}
|
||||||
if r, err := ref.ForContainer(c, provider.TypeSecret); err == nil {
|
if r, err := ref.ForContainer(c, provider.TypeSecret); err == nil {
|
||||||
e.Link = r.String()
|
e.Link = r.String()
|
||||||
}
|
}
|
||||||
@@ -212,6 +216,7 @@ func (l *lister) put(p provider.Provider, e Entry) {
|
|||||||
if (e.Link == "" || !l.wants(e.Kind)) && e.Error == "" {
|
if (e.Link == "" || !l.wants(e.Kind)) && e.Error == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
e.Account = p.ID()
|
||||||
id := p.ID() + "\x00" + e.Link + "\x00" + e.Error
|
id := p.ID() + "\x00" + e.Link + "\x00" + e.Error
|
||||||
if l.stopped != nil || l.seen[id] {
|
if l.stopped != nil || l.seen[id] {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ type Ref struct {
|
|||||||
// Share is a share link's sealed files, as in the link; the other
|
// Share is a share link's sealed files, as in the link; the other
|
||||||
// fields are empty. Package share reads it.
|
// fields are empty. Package share reads it.
|
||||||
Share string
|
Share string
|
||||||
|
// Account, when set, is the provider (its ID) to open or read it
|
||||||
|
// with when several could, from lazysecrets list. It isn't part of
|
||||||
|
// the link.
|
||||||
|
Account string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsShare reports whether r is a share link.
|
// IsShare reports whether r is a share link.
|
||||||
|
|||||||
@@ -218,6 +218,9 @@ func (m *Model) openSource(r ref.Ref) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if row, ok := preferredRow(r, choices); ok {
|
||||||
|
choices = []choice{{sourceRow: row}}
|
||||||
|
}
|
||||||
switch len(choices) {
|
switch len(choices) {
|
||||||
case 0:
|
case 0:
|
||||||
m.flash = flash{text: "open: " + describeRef(r) + " isn't in your sources", isErr: true}
|
m.flash = flash{text: "open: " + describeRef(r) + " isn't in your sources", isErr: true}
|
||||||
@@ -410,6 +413,10 @@ func (m *Model) handleResolved(msg resolvedMsg) {
|
|||||||
for _, row := range append([]string{msg.sourceRow}, msg.others...) {
|
for _, row := range append([]string{msg.sourceRow}, msg.others...) {
|
||||||
choices = append(choices, choice{label: "Open with " + m.sourceLabel(row), sourceRow: row})
|
choices = append(choices, choice{label: "Open with " + m.sourceLabel(row), sourceRow: row})
|
||||||
}
|
}
|
||||||
|
if row, ok := preferredRow(msg.ref, choices); ok {
|
||||||
|
m.startLinkNav(msg.ref, msg.act, row)
|
||||||
|
return
|
||||||
|
}
|
||||||
m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices, true)
|
m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices, true)
|
||||||
case msg.linked != nil:
|
case msg.linked != nil:
|
||||||
m.addLinked(*msg.linked)
|
m.addLinked(*msg.linked)
|
||||||
@@ -419,6 +426,17 @@ func (m *Model) handleResolved(msg resolvedMsg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preferredRow is the choice under the account the reference asks for
|
||||||
|
// (ref.Ref.Account), if it's one of them.
|
||||||
|
func preferredRow(r ref.Ref, choices []choice) (string, bool) {
|
||||||
|
for _, c := range choices {
|
||||||
|
if r.Account != "" && providerOf(c.sourceRow) == r.Account {
|
||||||
|
return c.sourceRow, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// startLinkNav opens a link: like startNav, ending on its object's
|
// startLinkNav opens a link: like startNav, ending on its object's
|
||||||
// details.
|
// details.
|
||||||
func (m *Model) startLinkNav(r ref.Ref, act linkAction, sourceRow string) {
|
func (m *Model) startLinkNav(r ref.Ref, act linkAction, sourceRow string) {
|
||||||
|
|||||||
@@ -305,6 +305,20 @@ func TestOpenAsksWhichAccount(t *testing.T) {
|
|||||||
t.Errorf("j enter picked %q", c.Source.Provider)
|
t.Errorf("j enter picked %q", c.Source.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lazysecrets open --account: no question, that account.
|
||||||
|
r, _ := ref.Parse("lazysecrets://azure/" + kv + "/secrets/api-key")
|
||||||
|
r.Account = "Azure:bob@example.com/t1"
|
||||||
|
d.send(OpenLink(r))
|
||||||
|
if d.m.prompt != nil && d.m.prompt.title == "Open link" {
|
||||||
|
d.keys("o")
|
||||||
|
}
|
||||||
|
if strings.Contains(d.screen(), "Several accounts can see") {
|
||||||
|
t.Fatalf("asked, with an account given:\n%s", d.screen())
|
||||||
|
}
|
||||||
|
if c, _ := d.m.containers.Selected(); c.Source.Provider != r.Account {
|
||||||
|
t.Errorf("--account opened via %q", c.Source.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
// Opening into a folded account unfolds it.
|
// Opening into a folded account unfolds it.
|
||||||
d.keys("1", "g") // alice's header
|
d.keys("1", "g") // alice's header
|
||||||
d.keys("enter") // fold it
|
d.keys("enter") // fold it
|
||||||
|
|||||||
Reference in new issue
Block a user