From ffcb1693adfb1fe98868191e36dda3b885c95522 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 20 Mar 2020 20:13:14 +0100 Subject: [PATCH 001/119] Pipe audit command output to --- internals/secrethub/audit.go | 95 +++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index c913bcc8..e254ca7a 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -2,13 +2,17 @@ package secrethub import ( "fmt" + "io" + "os" + "os/exec" + "strconv" "strings" - "text/tabwriter" + + "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/pkg/secrethub" - "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" "github.com/secrethub/secrethub-go/internals/api" ) @@ -63,11 +67,16 @@ func (cmd *AuditCommand) run() error { return err } - tabWriter := tabwriter.NewWriter(cmd.io.Stdout(), 0, 4, 4, ' ', 0) + paginatedWriter, done, err := paginateWriter(os.Stdout) + if err != nil { + return err + } + header := strings.Join(auditTable.header(), "\t") + "\n" - fmt.Fprint(tabWriter, header) + fmt.Fprint(paginatedWriter, header) i := 0 + paginatorClosed := false for { i++ event, err := iter.Next() @@ -82,29 +91,18 @@ func (cmd *AuditCommand) run() error { return err } - fmt.Fprint(tabWriter, strings.Join(row, "\t")+"\n") - - if i == cmd.perPage { - err = tabWriter.Flush() - if err != nil { - return err - } - i = 0 - - // wait for to continue. - _, err := ui.Ask(cmd.io, "Press to show more results. Press to exit.") - if err != nil { - return err - } - fmt.Fprint(tabWriter, header) + select { + case <-done: + paginatorClosed = true + default: + fmt.Fprint(paginatedWriter, strings.Join(row, "\t")+"\n") + } + if paginatorClosed { + break } } - err = tabWriter.Flush() - if err != nil { - return err - } - + fmt.Fprint(os.Stdout, strconv.Itoa(i)+" rows fetched") return nil } @@ -150,6 +148,55 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } +func paginateWriter(outputWriter io.Writer) (io.WriteCloser, <-chan struct{}, error) { + pager, err := pagerCommand() + if err != nil { + return nil, nil, err + } + + cmd := exec.Command(pager) + + writer, err := cmd.StdinPipe() + if err != nil { + return nil, nil, err + } + + cmd.Stdout = outputWriter + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return nil, nil, err + } + done := make(chan struct{}, 1) + go func() { + cmd.Wait() + done <- struct{}{} + }() + return writer, done, nil +} + +func pagerCommand() (string, error) { + var pager string + var err error + + pager = os.ExpandEnv("$PAGER") + if pager != "" { + return pager, nil + } + + pager, err = exec.LookPath("less") + if err == nil { + return pager, nil + } + + pager, err = exec.LookPath("more") + if err != nil { + return "", err + } + return pager, nil +} + type auditTable interface { header() []string row(event api.Audit) ([]string, error) From 4855d97f4138fd409bb608d59b5a57d21de26c8b Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 20 Mar 2020 20:19:17 +0100 Subject: [PATCH 002/119] Remove redundant print --- internals/secrethub/audit.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index e254ca7a..3e4434d5 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -5,7 +5,6 @@ import ( "io" "os" "os/exec" - "strconv" "strings" "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" @@ -101,8 +100,6 @@ func (cmd *AuditCommand) run() error { break } } - - fmt.Fprint(os.Stdout, strconv.Itoa(i)+" rows fetched") return nil } From 35075bdd0ee039d762be2a61f6d0023a66401344 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 20 Mar 2020 22:15:50 +0100 Subject: [PATCH 003/119] Restructure paginatedWriter & implement proper cleanup --- internals/secrethub/audit.go | 58 +++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 3e4434d5..5b9b2ba7 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -66,16 +66,16 @@ func (cmd *AuditCommand) run() error { return err } - paginatedWriter, done, err := paginateWriter(os.Stdout) + paginatedWriter, err := newPaginatedWriter(os.Stdout) if err != nil { return err } + defer paginatedWriter.Close() header := strings.Join(auditTable.header(), "\t") + "\n" fmt.Fprint(paginatedWriter, header) i := 0 - paginatorClosed := false for { i++ event, err := iter.Next() @@ -90,13 +90,8 @@ func (cmd *AuditCommand) run() error { return err } - select { - case <-done: - paginatorClosed = true - default: - fmt.Fprint(paginatedWriter, strings.Join(row, "\t")+"\n") - } - if paginatorClosed { + fmt.Fprint(paginatedWriter, strings.Join(row, "\t")+"\n") + if paginatedWriter.IsClosed() { break } } @@ -145,17 +140,17 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } -func paginateWriter(outputWriter io.Writer) (io.WriteCloser, <-chan struct{}, error) { +func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { pager, err := pagerCommand() if err != nil { - return nil, nil, err + return nil, err } cmd := exec.Command(pager) writer, err := cmd.StdinPipe() if err != nil { - return nil, nil, err + return nil, err } cmd.Stdout = outputWriter @@ -163,14 +158,49 @@ func paginateWriter(outputWriter io.Writer) (io.WriteCloser, <-chan struct{}, er err = cmd.Start() if err != nil { - return nil, nil, err + return nil, err } done := make(chan struct{}, 1) go func() { cmd.Wait() done <- struct{}{} }() - return writer, done, nil + return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil +} + +type paginatedWriter struct { + writer io.WriteCloser + cmd *exec.Cmd + done <-chan struct{} + closed bool +} + +func (p *paginatedWriter) Write(data []byte) (n int, err error) { + return p.writer.Write(data) +} + +func (p *paginatedWriter) Close() error { + err := p.writer.Close() + if err != nil { + return err + } + if !p.closed { + <-p.done + } + return nil +} + +func (p *paginatedWriter) IsClosed() bool { + if p.closed { + return true + } + select { + case <-p.done: + p.closed = true + return true + default: + return false + } } func pagerCommand() (string, error) { From 530d94c119d283d7abff32438e8775b479b5605c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 20 Mar 2020 22:35:53 +0100 Subject: [PATCH 004/119] Add comments to paginated Writer --- internals/secrethub/audit.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 5b9b2ba7..c133a456 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -140,6 +140,14 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } +type paginatedWriter struct { + writer io.WriteCloser + cmd *exec.Cmd + done <-chan struct{} + closed bool +} + +// newPaginatedWriter runs the default terminal pager and returns a writer to its standard input. func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { pager, err := pagerCommand() if err != nil { @@ -168,17 +176,11 @@ func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil } -type paginatedWriter struct { - writer io.WriteCloser - cmd *exec.Cmd - done <-chan struct{} - closed bool -} - func (p *paginatedWriter) Write(data []byte) (n int, err error) { return p.writer.Write(data) } +// Close closes the writer to the terminal pager and waits for the terminal pager to close. func (p *paginatedWriter) Close() error { err := p.writer.Close() if err != nil { @@ -190,6 +192,7 @@ func (p *paginatedWriter) Close() error { return nil } +// IsClosed checks if the terminal pager process has been stopped. func (p *paginatedWriter) IsClosed() bool { if p.closed { return true @@ -203,6 +206,7 @@ func (p *paginatedWriter) IsClosed() bool { } } +// pagerCommand returns the name of an available paging program. func pagerCommand() (string, error) { var pager string var err error From f853dcdf96d3bda0caa78f2f283218f66f2d9bbd Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 20 Mar 2020 22:40:16 +0100 Subject: [PATCH 005/119] Make PAGER envvar name a constant --- internals/secrethub/audit.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index c133a456..2e1f9216 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -16,6 +16,10 @@ import ( "github.com/secrethub/secrethub-go/internals/api" ) +const ( + pagerEnvvar = "$PAGER" +) + // AuditCommand is a command to audit a repo or a secret. type AuditCommand struct { io ui.IO @@ -211,7 +215,7 @@ func pagerCommand() (string, error) { var pager string var err error - pager = os.ExpandEnv("$PAGER") + pager = os.ExpandEnv(pagerEnvvar) if pager != "" { return pager, nil } From efda400253437a929ecb130d79b02aada1dc475e Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sun, 22 Mar 2020 16:56:21 +0100 Subject: [PATCH 006/119] Add paginator not found error --- internals/secrethub/audit.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 2e1f9216..7c3bc48d 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -1,6 +1,7 @@ package secrethub import ( + "errors" "fmt" "io" "os" @@ -16,6 +17,10 @@ import ( "github.com/secrethub/secrethub-go/internals/api" ) +var ( + errPaginatorNotFound = errors.New("no paginator available") +) + const ( pagerEnvvar = "$PAGER" ) @@ -226,10 +231,11 @@ func pagerCommand() (string, error) { } pager, err = exec.LookPath("more") - if err != nil { - return "", err + if err == nil { + return pager, nil } - return pager, nil + + return "", errPaginatorNotFound } type auditTable interface { From 45fdb135a71f7e18fff07bb3bee6a9b0746a2da4 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 14:58:10 +0100 Subject: [PATCH 007/119] Format audit log as a table --- internals/secrethub/audit.go | 51 ++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 7c3bc48d..d8ad9f81 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -8,6 +8,8 @@ import ( "os/exec" "strings" + "golang.org/x/crypto/ssh/terminal" + "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" "github.com/secrethub/secrethub-cli/internals/cli/ui" @@ -70,6 +72,11 @@ func (cmd *AuditCommand) run() error { return fmt.Errorf("per-page should be positive, got %d", cmd.perPage) } + terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + terminalWidth = 80 + } + iter, auditTable, err := cmd.iterAndAuditTable() if err != nil { return err @@ -81,7 +88,7 @@ func (cmd *AuditCommand) run() error { } defer paginatedWriter.Close() - header := strings.Join(auditTable.header(), "\t") + "\n" + header := formatTableRow(auditTable.header(), terminalWidth) fmt.Fprint(paginatedWriter, header) i := 0 @@ -99,7 +106,7 @@ func (cmd *AuditCommand) run() error { return err } - fmt.Fprint(paginatedWriter, strings.Join(row, "\t")+"\n") + fmt.Fprint(paginatedWriter, formatTableRow(row, terminalWidth)) if paginatedWriter.IsClosed() { break } @@ -107,6 +114,46 @@ func (cmd *AuditCommand) run() error { return nil } +// formatTableRow formats the given table row to fit the specified width +// by giving each cell an equal width and wrapping the text in cells that exceed it +func formatTableRow(row []string, tableWidth int) string { + maxLinesPerCell := 1 + colWidth := (tableWidth - 2*len(row)) / len(row) + for _, cell := range row { + lines := len(cell) / colWidth + if len(cell)%colWidth != 0 { + lines++ + } + if lines > maxLinesPerCell { + maxLinesPerCell = lines + } + } + + splitCells := make([][]string, maxLinesPerCell) + for i := 0; i < maxLinesPerCell; i++ { + splitCells[i] = make([]string, len(row)) + } + + for i, cell := range row { + j := 0 + for ; len(cell) > colWidth; j++ { + splitCells[j][i] = cell[:colWidth] + cell = cell[colWidth:] + } + splitCells[j][i] = cell + strings.Repeat(" ", colWidth-len(cell)) + j++ + for ; j < maxLinesPerCell; j++ { + splitCells[j][i] = strings.Repeat(" ", colWidth) + } + } + + strRes := strings.Builder{} + for j := 0; j < maxLinesPerCell; j++ { + strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") + } + return strRes.String() +} + func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, auditTable, error) { repoPath, err := cmd.path.ToRepoPath() if err == nil { From 38d619eafca8254fd2cdf903b3983d7986e00f09 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 16:21:13 +0100 Subject: [PATCH 008/119] Update checking available paginator to not error on wrong configuration --- internals/secrethub/audit.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index d8ad9f81..b3f77beb 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -205,7 +205,7 @@ type paginatedWriter struct { // newPaginatedWriter runs the default terminal pager and returns a writer to its standard input. func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { - pager, err := pagerCommand() + pager, err := paginatorCommand() if err != nil { return nil, err } @@ -262,13 +262,13 @@ func (p *paginatedWriter) IsClosed() bool { } } -// pagerCommand returns the name of an available paging program. -func pagerCommand() (string, error) { +// paginatorCommand returns the name of an available paging program. +func paginatorCommand() (string, error) { var pager string var err error - pager = os.ExpandEnv(pagerEnvvar) - if pager != "" { + pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)) + if err == nil { return pager, nil } From 05a283d2e0f0ce6d9dfc91eda8ae25e255dbacb4 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 16:22:36 +0100 Subject: [PATCH 009/119] Reorder functions --- internals/secrethub/audit.go | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index b3f77beb..ae975df1 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -203,35 +203,6 @@ type paginatedWriter struct { closed bool } -// newPaginatedWriter runs the default terminal pager and returns a writer to its standard input. -func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { - pager, err := paginatorCommand() - if err != nil { - return nil, err - } - - cmd := exec.Command(pager) - - writer, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - cmd.Stdout = outputWriter - cmd.Stderr = os.Stderr - - err = cmd.Start() - if err != nil { - return nil, err - } - done := make(chan struct{}, 1) - go func() { - cmd.Wait() - done <- struct{}{} - }() - return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil -} - func (p *paginatedWriter) Write(data []byte) (n int, err error) { return p.writer.Write(data) } @@ -262,6 +233,35 @@ func (p *paginatedWriter) IsClosed() bool { } } +// newPaginatedWriter runs the default terminal pager and returns a writer to its standard input. +func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { + pager, err := paginatorCommand() + if err != nil { + return nil, err + } + + cmd := exec.Command(pager) + + writer, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + + cmd.Stdout = outputWriter + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return nil, err + } + done := make(chan struct{}, 1) + go func() { + cmd.Wait() + done <- struct{}{} + }() + return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil +} + // paginatorCommand returns the name of an available paging program. func paginatorCommand() (string, error) { var pager string From 2458e7b35ed39ad2ce5f08c6239cc608898df000 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 16:49:11 +0100 Subject: [PATCH 010/119] Factor out default terminal width as a constant --- internals/secrethub/audit.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index ae975df1..bd5df7c4 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -24,7 +24,8 @@ var ( ) const ( - pagerEnvvar = "$PAGER" + pagerEnvvar = "$PAGER" + defaultTerminalWidth = 80 ) // AuditCommand is a command to audit a repo or a secret. @@ -74,7 +75,7 @@ func (cmd *AuditCommand) run() error { terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd())) if err != nil { - terminalWidth = 80 + terminalWidth = defaultTerminalWidth } iter, auditTable, err := cmd.iterAndAuditTable() From 0021d44ad485d7d59eb26d5439285fc79ef3df68 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 16:55:32 +0100 Subject: [PATCH 011/119] Update terminal paginator comments --- internals/secrethub/audit.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index bd5df7c4..4e5391f8 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -20,7 +20,7 @@ import ( ) var ( - errPaginatorNotFound = errors.New("no paginator available") + errPagerNotFound = errors.New("no terminal pager available") ) const ( @@ -234,9 +234,10 @@ func (p *paginatedWriter) IsClosed() bool { } } -// newPaginatedWriter runs the default terminal pager and returns a writer to its standard input. +// newPaginatedWriter runs the terminal pager configured in the OS environment +// and returns a writer to its standard input. func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { - pager, err := paginatorCommand() + pager, err := pagerCommand() if err != nil { return nil, err } @@ -263,8 +264,9 @@ func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil } -// paginatorCommand returns the name of an available paging program. -func paginatorCommand() (string, error) { +// pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). +// If no pager is configured less or more is returned depending on which is available. +func pagerCommand() (string, error) { var pager string var err error @@ -283,7 +285,7 @@ func paginatorCommand() (string, error) { return pager, nil } - return "", errPaginatorNotFound + return "", errPagerNotFound } type auditTable interface { From e7f1728f7e1e09a0a00aa61282c0fbe407494f55 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 17:03:15 +0100 Subject: [PATCH 012/119] Hide --per-page flag as it is no longer meaningful --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 4e5391f8..ab2e18dd 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -50,7 +50,7 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { func (cmd *AuditCommand) Register(r command.Registerer) { clause := r.Command("audit", "Show the audit log.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) - clause.Flag("per-page", "number of audit events shown per page").Default("20").IntVar(&cmd.perPage) + clause.Flag("per-page", "number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) From 93297e6547d3c0910b1055dae583a5d5f99b51b2 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 17:32:56 +0100 Subject: [PATCH 013/119] Explicitly ignore pager command exit code error --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index ab2e18dd..b108a83b 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -258,7 +258,7 @@ func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { } done := make(chan struct{}, 1) go func() { - cmd.Wait() + _ = cmd.Wait() done <- struct{}{} }() return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil From 262c188c596f5403e0a2a7ad916aef1bf40a494a Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 24 Mar 2020 17:36:52 +0100 Subject: [PATCH 014/119] Remove unnecessary index --- internals/secrethub/audit.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index b108a83b..9f32dbb9 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -92,9 +92,7 @@ func (cmd *AuditCommand) run() error { header := formatTableRow(auditTable.header(), terminalWidth) fmt.Fprint(paginatedWriter, header) - i := 0 for { - i++ event, err := iter.Next() if err == iterator.Done { break From 613900424a9f0b5c771c1b8872cf1f62e1f7b9ad Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 11:57:34 +0100 Subject: [PATCH 015/119] Add json format and factor out table row formatting --- internals/secrethub/audit.go | 93 +++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 9f32dbb9..3cce90ac 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -1,6 +1,7 @@ package secrethub import ( + "encoding/json" "errors" "fmt" "io" @@ -36,6 +37,7 @@ type AuditCommand struct { timeFormatter TimeFormatter newClient newClientFunc perPage int + json bool } // NewAuditCommand creates a new audit command. @@ -51,6 +53,7 @@ func (cmd *AuditCommand) Register(r command.Registerer) { clause := r.Command("audit", "Show the audit log.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) + clause.Flag("json", "output the audit log in json format").BoolVar(&cmd.json) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) @@ -73,24 +76,35 @@ func (cmd *AuditCommand) run() error { return fmt.Errorf("per-page should be positive, got %d", cmd.perPage) } - terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd())) - if err != nil { - terminalWidth = defaultTerminalWidth - } - iter, auditTable, err := cmd.iterAndAuditTable() if err != nil { return err } + var formatter rowFormatter + if cmd.json { + formatter = newJSONFormatter(auditTable.header()) + } else { + terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + terminalWidth = defaultTerminalWidth + } + formatter = newColumnFormatter(terminalWidth) + } + paginatedWriter, err := newPaginatedWriter(os.Stdout) if err != nil { return err } defer paginatedWriter.Close() - header := formatTableRow(auditTable.header(), terminalWidth) - fmt.Fprint(paginatedWriter, header) + if formatter.printHeader() { + header, err := formatter.formatRow(auditTable.header()) + if err != nil { + return err + } + fmt.Fprint(paginatedWriter, header) + } for { event, err := iter.Next() @@ -105,7 +119,12 @@ func (cmd *AuditCommand) run() error { return err } - fmt.Fprint(paginatedWriter, formatTableRow(row, terminalWidth)) + formattedRow, err := formatter.formatRow(row) + if err != nil { + return err + } + + fmt.Fprint(paginatedWriter, formattedRow) if paginatedWriter.IsClosed() { break } @@ -113,11 +132,59 @@ func (cmd *AuditCommand) run() error { return nil } -// formatTableRow formats the given table row to fit the specified width -// by giving each cell an equal width and wrapping the text in cells that exceed it -func formatTableRow(row []string, tableWidth int) string { +type rowFormatter interface { + printHeader() bool + formatRow(row []string) (string, error) +} + +func newJSONFormatter(fieldNames []string) *jsonFormatter { + return &jsonFormatter{fields: fieldNames} +} + +type jsonFormatter struct { + fields []string +} + +func (f *jsonFormatter) printHeader() bool { + return false +} + +// formatRow returns the json representation of the given row +// with the configured field names as keys and the provided values +func (f *jsonFormatter) formatRow(row []string) (string, error) { + if len(f.fields) != len(row) { + return "", fmt.Errorf("unexpected number of json fields") + } + + jsonMap := make(map[string]string) + for i, element := range row { + jsonMap[f.fields[i]] = element + } + + jsonData, err := json.Marshal(jsonMap) + if err != nil { + return "", err + } + return string(jsonData) + "\n", nil +} + +func newColumnFormatter(tableWidth int) *columnFormatter { + return &columnFormatter{tableWidth: tableWidth} +} + +type columnFormatter struct { + tableWidth int +} + +func (f *columnFormatter) printHeader() bool { + return true +} + +// formatRow formats the given table row to fit the configured width by +// giving each cell an equal width and wrapping the text in cells that exceed it +func (f *columnFormatter) formatRow(row []string) (string, error) { maxLinesPerCell := 1 - colWidth := (tableWidth - 2*len(row)) / len(row) + colWidth := (f.tableWidth - 2*len(row)) / len(row) for _, cell := range row { lines := len(cell) / colWidth if len(cell)%colWidth != 0 { @@ -150,7 +217,7 @@ func formatTableRow(row []string, tableWidth int) string { for j := 0; j < maxLinesPerCell; j++ { strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") } - return strRes.String() + return strRes.String(), nil } func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, auditTable, error) { From c9a39f413dcab23659c692d52460df377a8dca55 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 14:26:23 +0100 Subject: [PATCH 016/119] Add optional maximum width attribute to audit table columns and scale table accordingly --- internals/secrethub/audit.go | 114 +++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 24 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 3cce90ac..bb07ffff 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -81,7 +81,7 @@ func (cmd *AuditCommand) run() error { return err } - var formatter rowFormatter + var formatter tableFormatter if cmd.json { formatter = newJSONFormatter(auditTable.header()) } else { @@ -89,7 +89,7 @@ func (cmd *AuditCommand) run() error { if err != nil { terminalWidth = defaultTerminalWidth } - formatter = newColumnFormatter(terminalWidth) + formatter = newColumnFormatter(terminalWidth, auditTable.columns()) } paginatedWriter, err := newPaginatedWriter(os.Stdout) @@ -132,11 +132,12 @@ func (cmd *AuditCommand) run() error { return nil } -type rowFormatter interface { +type tableFormatter interface { printHeader() bool formatRow(row []string) (string, error) } +// newJSONFormatter returns a table formatter that formats the given table rows as json. func newJSONFormatter(fieldNames []string) *jsonFormatter { return &jsonFormatter{fields: fieldNames} } @@ -168,12 +169,15 @@ func (f *jsonFormatter) formatRow(row []string) (string, error) { return string(jsonData) + "\n", nil } -func newColumnFormatter(tableWidth int) *columnFormatter { - return &columnFormatter{tableWidth: tableWidth} +// newColumnFormatter returns a table formatter that aligns the columns of the table. +func newColumnFormatter(tableWidth int, columns []auditTableColumn) *columnFormatter { + return &columnFormatter{tableWidth: tableWidth, columns: columns} } type columnFormatter struct { - tableWidth int + tableWidth int + computedColumnWidths []int + columns []auditTableColumn } func (f *columnFormatter) printHeader() bool { @@ -184,10 +188,10 @@ func (f *columnFormatter) printHeader() bool { // giving each cell an equal width and wrapping the text in cells that exceed it func (f *columnFormatter) formatRow(row []string) (string, error) { maxLinesPerCell := 1 - colWidth := (f.tableWidth - 2*len(row)) / len(row) - for _, cell := range row { - lines := len(cell) / colWidth - if len(cell)%colWidth != 0 { + columnWidths := f.columnWidths() + for i, cell := range row { + lines := len(cell) / columnWidths[i] + if len(cell)%columnWidths[i] != 0 { lines++ } if lines > maxLinesPerCell { @@ -202,14 +206,14 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { for i, cell := range row { j := 0 - for ; len(cell) > colWidth; j++ { - splitCells[j][i] = cell[:colWidth] - cell = cell[colWidth:] + for ; len(cell) > columnWidths[i]; j++ { + splitCells[j][i] = cell[:columnWidths[i]] + cell = cell[columnWidths[i]:] } - splitCells[j][i] = cell + strings.Repeat(" ", colWidth-len(cell)) + splitCells[j][i] = cell + strings.Repeat(" ", columnWidths[i]-len(cell)) j++ for ; j < maxLinesPerCell; j++ { - splitCells[j][i] = strings.Repeat(" ", colWidth) + splitCells[j][i] = strings.Repeat(" ", columnWidths[i]) } } @@ -220,6 +224,48 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { return strRes.String(), nil } +// columnWidths returns the width of each column based on their maximum widths +// and the table width. +func (f *columnFormatter) columnWidths() []int { + if f.computedColumnWidths != nil { + return f.computedColumnWidths + } + + res := make([]int, len(f.columns)) + widthPerColumn := (f.tableWidth - 2*(len(f.columns)-1)) / len(f.columns) + + adjusted := true + for adjusted { + adjusted = false + for i, col := range f.columns { + if res[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { + res[i] = col.maxWidth + adjusted = true + } + } + if !adjusted { + break + } + count := len(f.columns) + widthLeft := f.tableWidth - 2*(len(f.columns)-1) + for _, width := range res { + if width != 0 { + count-- + widthLeft -= width + } + } + widthPerColumn = widthLeft / count + } + + for i := range res { + if res[i] == 0 { + res[i] = widthPerColumn + } + } + f.computedColumnWidths = res + return res +} + func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, auditTable, error) { repoPath, err := cmd.path.ToRepoPath() if err == nil { @@ -353,24 +399,44 @@ func pagerCommand() (string, error) { return "", errPagerNotFound } +type auditTableColumn struct { + name string + maxWidth int +} + type auditTable interface { header() []string row(event api.Audit) ([]string, error) + columns() []auditTableColumn } -func newBaseAuditTable(timeFormatter TimeFormatter) baseAuditTable { +func newBaseAuditTable(timeFormatter TimeFormatter, midColumns ...auditTableColumn) baseAuditTable { + columns := append([]auditTableColumn{ + {name: "AUTHOR", maxWidth: 32}, + {name: "EVENT", maxWidth: 22}, + }, midColumns...) + columns = append(columns, []auditTableColumn{ + {name: "IP ADDRESS", maxWidth: 45}, + {name: "DATE", maxWidth: 22}, + }...) + return baseAuditTable{ + tableColumns: columns, timeFormatter: timeFormatter, } } type baseAuditTable struct { + tableColumns []auditTableColumn timeFormatter TimeFormatter } -func (table baseAuditTable) header(content ...string) []string { - res := append([]string{"AUTHOR", "EVENT"}, content...) - return append(res, "IP ADDRESS", "DATE") +func (table baseAuditTable) header() []string { + res := make([]string, len(table.tableColumns)) + for i, col := range table.tableColumns { + res[i] = col.name + } + return res } func (table baseAuditTable) row(event api.Audit, content ...string) ([]string, error) { @@ -383,6 +449,10 @@ func (table baseAuditTable) row(event api.Audit, content ...string) ([]string, e return append(res, event.IPAddress, table.timeFormatter.Format(event.LoggedAt)), nil } +func (table baseAuditTable) columns() []auditTableColumn { + return table.tableColumns +} + func newSecretAuditTable(timeFormatter TimeFormatter) secretAuditTable { return secretAuditTable{ baseAuditTable: newBaseAuditTable(timeFormatter), @@ -403,7 +473,7 @@ func (table secretAuditTable) row(event api.Audit) ([]string, error) { func newRepoAuditTable(tree *api.Tree, timeFormatter TimeFormatter) repoAuditTable { return repoAuditTable{ - baseAuditTable: newBaseAuditTable(timeFormatter), + baseAuditTable: newBaseAuditTable(timeFormatter, auditTableColumn{name: "EVENT SUBJECT"}), tree: tree, } } @@ -413,10 +483,6 @@ type repoAuditTable struct { tree *api.Tree } -func (table repoAuditTable) header() []string { - return table.baseAuditTable.header("EVENT SUBJECT") -} - func (table repoAuditTable) row(event api.Audit) ([]string, error) { subject, err := getAuditSubject(event, table.tree) if err != nil { From 3f04f4dd07f993f305a073fc8917c50de492615b Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 14:27:42 +0100 Subject: [PATCH 017/119] Fix typo --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index bb07ffff..24cb01cc 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -185,7 +185,7 @@ func (f *columnFormatter) printHeader() bool { } // formatRow formats the given table row to fit the configured width by -// giving each cell an equal width and wrapping the text in cells that exceed it +// giving each cell an equal width and wrapping the text in cells that exceed it. func (f *columnFormatter) formatRow(row []string) (string, error) { maxLinesPerCell := 1 columnWidths := f.columnWidths() From cddc98b86773f0e7006669e9355682d4837aa3d7 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 15:00:31 +0100 Subject: [PATCH 018/119] Add distributing leftover table width to each column --- internals/secrethub/audit.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 24cb01cc..796fe73a 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -254,6 +254,12 @@ func (f *columnFormatter) columnWidths() []int { widthLeft -= width } } + if count == 0 { + for i := range res { + res[i] += widthLeft / len(res) + } + break + } widthPerColumn = widthLeft / count } From 5f9b46f30f8d57a80642511ef76b4270a39dbb8c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 15:00:56 +0100 Subject: [PATCH 019/119] Add tests for column width computation --- internals/secrethub/audit_test.go | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 internals/secrethub/audit_test.go diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go new file mode 100644 index 00000000..4b9dd1dd --- /dev/null +++ b/internals/secrethub/audit_test.go @@ -0,0 +1,64 @@ +package secrethub + +import ( + "testing" + + "github.com/secrethub/secrethub-go/internals/assert" +) + +func Test_columnFormatter_columnWidths(t *testing.T) { + cases := map[string]struct { + formatter columnFormatter + expected []int + }{ + "all columns fit": { + formatter: columnFormatter{ + tableWidth: 102, + columns: []auditTableColumn{ + {maxWidth: 10}, + {maxWidth: 10}, + }, + }, + expected: []int{50, 50}, + }, + "no columns fit": { + formatter: columnFormatter{ + tableWidth: 12, + columns: []auditTableColumn{ + {maxWidth: 10}, + {maxWidth: 10}, + }, + }, + expected: []int{5, 5}, + }, + "one column fits": { + formatter: columnFormatter{ + tableWidth: 27, + columns: []auditTableColumn{ + {maxWidth: 10}, + {maxWidth: 20}, + }, + }, + expected: []int{10, 15}, + }, + "multiple adjustments": { + formatter: columnFormatter{ + tableWidth: 106, + columns: []auditTableColumn{ + {maxWidth: 27}, + {maxWidth: 26}, + {maxWidth: 25}, + {maxWidth: 20}, + }, + }, + expected: []int{27, 26, 25, 20}, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + result := tc.formatter.columnWidths() + assert.Equal(t, result, tc.expected) + }) + } +} From 26b18f3211b5a60ea7d0885bf1515dc9793cb4df Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 16:20:01 +0100 Subject: [PATCH 020/119] Refactor aligning columns and printing '\n' --- internals/secrethub/audit.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 796fe73a..509f070c 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -103,7 +103,7 @@ func (cmd *AuditCommand) run() error { if err != nil { return err } - fmt.Fprint(paginatedWriter, header) + fmt.Fprintln(paginatedWriter, header) } for { @@ -124,7 +124,7 @@ func (cmd *AuditCommand) run() error { return err } - fmt.Fprint(paginatedWriter, formattedRow) + fmt.Fprintln(paginatedWriter, formattedRow) if paginatedWriter.IsClosed() { break } @@ -166,7 +166,7 @@ func (f *jsonFormatter) formatRow(row []string) (string, error) { if err != nil { return "", err } - return string(jsonData) + "\n", nil + return string(jsonData), nil } // newColumnFormatter returns a table formatter that aligns the columns of the table. @@ -221,7 +221,7 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { for j := 0; j < maxLinesPerCell; j++ { strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") } - return strRes.String(), nil + return strings.TrimSuffix(strRes.String(), "\n"), nil } // columnWidths returns the width of each column based on their maximum widths @@ -230,37 +230,29 @@ func (f *columnFormatter) columnWidths() []int { if f.computedColumnWidths != nil { return f.computedColumnWidths } - res := make([]int, len(f.columns)) - widthPerColumn := (f.tableWidth - 2*(len(f.columns)-1)) / len(f.columns) adjusted := true + columnsLeft := len(f.columns) + widthLeft := f.tableWidth - 2*(len(f.columns)-1) + widthPerColumn := widthLeft / columnsLeft for adjusted { adjusted = false for i, col := range f.columns { if res[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { res[i] = col.maxWidth + widthLeft -= col.maxWidth + columnsLeft-- adjusted = true } } - if !adjusted { - break - } - count := len(f.columns) - widthLeft := f.tableWidth - 2*(len(f.columns)-1) - for _, width := range res { - if width != 0 { - count-- - widthLeft -= width - } - } - if count == 0 { + if columnsLeft == 0 { for i := range res { res[i] += widthLeft / len(res) } break } - widthPerColumn = widthLeft / count + widthPerColumn = widthLeft / columnsLeft } for i := range res { From a55e8961bf9aead676dcac344907fcab2bd5d312 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 16:48:26 +0100 Subject: [PATCH 021/119] Refactor line wrapping in table cells --- internals/secrethub/audit.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 509f070c..8d7a70b2 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -187,8 +187,9 @@ func (f *columnFormatter) printHeader() bool { // formatRow formats the given table row to fit the configured width by // giving each cell an equal width and wrapping the text in cells that exceed it. func (f *columnFormatter) formatRow(row []string) (string, error) { - maxLinesPerCell := 1 columnWidths := f.columnWidths() + + maxLinesPerCell := 1 for i, cell := range row { lines := len(cell) / columnWidths[i] if len(cell)%columnWidths[i] != 0 { @@ -205,15 +206,23 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { } for i, cell := range row { - j := 0 - for ; len(cell) > columnWidths[i]; j++ { - splitCells[j][i] = cell[:columnWidths[i]] - cell = cell[columnWidths[i]:] + columnWidth := columnWidths[i] + lineCount := len(cell) / columnWidth + for j := 0; j < lineCount; j++ { + begin := j * columnWidth + end := (j + 1) * columnWidth + splitCells[j][i] = cell[begin:end] } - splitCells[j][i] = cell + strings.Repeat(" ", columnWidths[i]-len(cell)) - j++ - for ; j < maxLinesPerCell; j++ { - splitCells[j][i] = strings.Repeat(" ", columnWidths[i]) + + charactersLeft := len(cell) % columnWidth + if charactersLeft != 0 { + splitCells[lineCount][i] = cell[len(cell)-charactersLeft:] + strings.Repeat(" ", columnWidth-charactersLeft) + } else if lineCount < maxLinesPerCell { + splitCells[lineCount][i] = strings.Repeat(" ", columnWidth) + } + + for j := lineCount + 1; j < maxLinesPerCell; j++ { + splitCells[j][i] = strings.Repeat(" ", columnWidth) } } From 9f69116bd5e5cd3184edfc70786e2a4121c06570 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 16:48:56 +0100 Subject: [PATCH 022/119] Add more audit table formatting tests --- internals/secrethub/audit_test.go | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go index 4b9dd1dd..2ebf0e22 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/audit_test.go @@ -1,6 +1,7 @@ package secrethub import ( + "strings" "testing" "github.com/secrethub/secrethub-go/internals/assert" @@ -53,6 +54,28 @@ func Test_columnFormatter_columnWidths(t *testing.T) { }, expected: []int{27, 26, 25, 20}, }, + "no max width for some all fit": { + formatter: columnFormatter{ + tableWidth: 64, + columns: []auditTableColumn{ + {maxWidth: 15}, + {}, + {maxWidth: 15}, + }, + }, + expected: []int{15, 30, 15}, + }, + "no max width for some not all fit": { + formatter: columnFormatter{ + tableWidth: 64, + columns: []auditTableColumn{ + {maxWidth: 50}, + {}, + {maxWidth: 10}, + }, + }, + expected: []int{25, 25, 10}, + }, } for name, tc := range cases { @@ -62,3 +85,53 @@ func Test_columnFormatter_columnWidths(t *testing.T) { }) } } + +func Test_columnFormatter_formatRow(t *testing.T) { + cases := map[string]struct { + formatter columnFormatter + row []string + expected string + expectedErr error + }{ + "all cells fit": { + formatter: columnFormatter{ + tableWidth: 102, + computedColumnWidths: []int{50, 50}, + columns: []auditTableColumn{{}, {}}, + }, + row: []string{"foo", "bar"}, + expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47), + expectedErr: nil, + }, + "wrapping": { + formatter: columnFormatter{ + tableWidth: 6, + computedColumnWidths: []int{2, 2}, + columns: []auditTableColumn{{}, {}}, + }, + row: []string{"foo", "bar"}, + expected: "fo ba\no r ", + expectedErr: nil, + }, + "fits exactly": { + formatter: columnFormatter{ + tableWidth: 8, + computedColumnWidths: []int{3, 3}, + columns: []auditTableColumn{{}, {}}, + }, + row: []string{"foo", "bar"}, + expected: "foo bar", + expectedErr: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + result, err := tc.formatter.formatRow(tc.row) + assert.Equal(t, err, tc.expectedErr) + if err == nil { + assert.Equal(t, result, tc.expected) + } + }) + } +} From b58e884dd045c8dad5ebf7cdb2c234c50f40867c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 18:03:27 +0100 Subject: [PATCH 023/119] Refactor AuditCommand to allow mocking --- internals/secrethub/audit.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 8d7a70b2..8895e66a 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -31,20 +31,27 @@ const ( // AuditCommand is a command to audit a repo or a secret. type AuditCommand struct { - io ui.IO - path api.Path - useTimestamps bool - timeFormatter TimeFormatter - newClient newClientFunc - perPage int - json bool + io ui.IO + newPaginatedWriter func(io.Writer) (pager, error) + path api.Path + useTimestamps bool + timeFormatter TimeFormatter + newClient newClientFunc + terminalWidth func(int) (int, error) + perPage int + json bool } // NewAuditCommand creates a new audit command. func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { return &AuditCommand{ - io: io, - newClient: newClient, + io: io, + newPaginatedWriter: newPaginatedWriter, + newClient: newClient, + terminalWidth: func(fd int) (int, error) { + w, _, err := terminal.GetSize(fd) + return w, err + }, } } @@ -85,14 +92,14 @@ func (cmd *AuditCommand) run() error { if cmd.json { formatter = newJSONFormatter(auditTable.header()) } else { - terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd())) + terminalWidth, err := cmd.terminalWidth(int(os.Stdout.Fd())) if err != nil { terminalWidth = defaultTerminalWidth } formatter = newColumnFormatter(terminalWidth, auditTable.columns()) } - paginatedWriter, err := newPaginatedWriter(os.Stdout) + paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) if err != nil { return err } @@ -315,6 +322,11 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } +type pager interface { + io.WriteCloser + IsClosed() bool +} + type paginatedWriter struct { writer io.WriteCloser cmd *exec.Cmd @@ -354,7 +366,7 @@ func (p *paginatedWriter) IsClosed() bool { // newPaginatedWriter runs the terminal pager configured in the OS environment // and returns a writer to its standard input. -func newPaginatedWriter(outputWriter io.Writer) (*paginatedWriter, error) { +func newPaginatedWriter(outputWriter io.Writer) (pager, error) { pager, err := pagerCommand() if err != nil { return nil, err From 62377e2efb4d9c95fe188c0c7f67afe4fca13851 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 18:06:21 +0100 Subject: [PATCH 024/119] Update audit tests & add fake pager --- internals/secrethub/audit_repo_test.go | 38 ++++++++++++++++++------ internals/secrethub/audit_secret_test.go | 35 ++++++++++++++++------ internals/secrethub/fakes/pager.go | 19 ++++++++++++ 3 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 internals/secrethub/fakes/pager.go diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index b69d335b..7ec46d3e 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -1,11 +1,12 @@ package secrethub import ( + "bytes" "errors" + "io" "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -34,9 +35,12 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + terminalWidth: func(int) (int, error) { + return 83, nil + }, perPage: 20, }, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE\n", + out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", }, "create repo event": { cmd: AuditCommand{ @@ -70,12 +74,16 @@ func TestAuditRepoCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(_ int) (int, error) { + return 83, nil + }, timeFormatter: &fakes.TimeFormatter{ Response: "2018-01-01T01:01:01+01:00", }, }, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE\n" + - "developer create.repo repo 127.0.0.1 2018-01-01T01:01:01+01:00\n", + out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n" + + "developer create.repo repo 127.0.0.1 2018-01-01T01:0\n" + + " 1:01+01:00 \n", }, "client creation error": { cmd: AuditCommand{ @@ -103,8 +111,12 @@ func TestAuditRepoCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(int) (int, error) { + return 83, nil + }, }, err: testError, + out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", }, "get dir error": { cmd: AuditCommand{ @@ -148,9 +160,12 @@ func TestAuditRepoCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(int) (int, error) { + return 83, nil + }, }, err: ErrInvalidAuditActor, - out: "", + out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", }, "invalid audit subject": { cmd: AuditCommand{ @@ -175,24 +190,29 @@ func TestAuditRepoCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(int) (int, error) { + return 83, nil + }, }, err: ErrInvalidAuditSubject, - out: "", + out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() - tc.cmd.io = io + buffer := bytes.Buffer{} + tc.cmd.newPaginatedWriter = func(_ io.Writer) (pager, error) { + return &fakes.Pager{Buffer: &buffer}, nil + } // Act err := tc.cmd.run() // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, buffer.String(), tc.out) }) } } diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 6d644ead..0c072c58 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -1,11 +1,12 @@ package secrethub import ( + "bytes" "errors" + "io" "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -55,12 +56,17 @@ func TestAuditSecretCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(_ int) (int, error) { + return 46, nil + }, timeFormatter: &fakes.TimeFormatter{ Response: "2018-01-01T01:01:01+01:00", }, }, - out: "AUTHOR EVENT IP ADDRESS DATE\n" + - "developer create.secret 127.0.0.1 2018-01-01T01:01:01+01:00\n", + out: "AUTHOR EVENT IP ADDRESS DATE \n" + + "developer create.sec 127.0.0.1 2018-01-01\n" + + " ret T01:01:01+\n" + + " 01:00 \n", }, "0 events": { cmd: AuditCommand{ @@ -80,8 +86,11 @@ func TestAuditSecretCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(_ int) (int, error) { + return 46, nil + }, }, - out: "AUTHOR EVENT IP ADDRESS DATE\n", + out: "AUTHOR EVENT IP ADDRESS DATE \n", }, "error secret version": { cmd: AuditCommand{ @@ -139,8 +148,12 @@ func TestAuditSecretCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(_ int) (int, error) { + return 46, nil + }, }, err: testError, + out: "AUTHOR EVENT IP ADDRESS DATE \n", }, "invalid audit actor": { cmd: AuditCommand{ @@ -162,24 +175,28 @@ func TestAuditSecretCommand_run(t *testing.T) { }, nil }, perPage: 20, + terminalWidth: func(int) (int, error) { + return 83, nil + }, }, err: ErrInvalidAuditActor, - out: "", + out: "AUTHOR EVENT IP ADDRESS DATE \n", }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() - tc.cmd.io = io - + buffer := bytes.Buffer{} + tc.cmd.newPaginatedWriter = func(_ io.Writer) (pager, error) { + return &fakes.Pager{Buffer: &buffer}, nil + } // Act err := tc.cmd.run() // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, buffer.String(), tc.out) }) } } diff --git a/internals/secrethub/fakes/pager.go b/internals/secrethub/fakes/pager.go new file mode 100644 index 00000000..0fcd70ea --- /dev/null +++ b/internals/secrethub/fakes/pager.go @@ -0,0 +1,19 @@ +package fakes + +import "bytes" + +type Pager struct { + Buffer *bytes.Buffer +} + +func (f *Pager) Write(p []byte) (n int, err error) { + return f.Buffer.Write(p) +} + +func (f *Pager) Close() error { + return nil +} + +func (f *Pager) IsClosed() bool { + return false +} From e4965e51b24075261242c3af265632c23389e83e Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 18:09:41 +0100 Subject: [PATCH 025/119] Reorder audit functions --- internals/secrethub/audit.go | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 8895e66a..92152a5a 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -327,6 +327,36 @@ type pager interface { IsClosed() bool } +// newPaginatedWriter runs the terminal pager configured in the OS environment +// and returns a writer to its standard input. +func newPaginatedWriter(outputWriter io.Writer) (pager, error) { + pager, err := pagerCommand() + if err != nil { + return nil, err + } + + cmd := exec.Command(pager) + + writer, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + + cmd.Stdout = outputWriter + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return nil, err + } + done := make(chan struct{}, 1) + go func() { + _ = cmd.Wait() + done <- struct{}{} + }() + return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil +} + type paginatedWriter struct { writer io.WriteCloser cmd *exec.Cmd @@ -364,36 +394,6 @@ func (p *paginatedWriter) IsClosed() bool { } } -// newPaginatedWriter runs the terminal pager configured in the OS environment -// and returns a writer to its standard input. -func newPaginatedWriter(outputWriter io.Writer) (pager, error) { - pager, err := pagerCommand() - if err != nil { - return nil, err - } - - cmd := exec.Command(pager) - - writer, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - cmd.Stdout = outputWriter - cmd.Stderr = os.Stderr - - err = cmd.Start() - if err != nil { - return nil, err - } - done := make(chan struct{}, 1) - go func() { - _ = cmd.Wait() - done <- struct{}{} - }() - return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil -} - // pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). // If no pager is configured less or more is returned depending on which is available. func pagerCommand() (string, error) { From ecb8658beeb4cbc77a3bb310f368c7328143fe71 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 19:02:52 +0100 Subject: [PATCH 026/119] Add fallback pager that is used when no terminal pager is found --- internals/secrethub/audit.go | 56 ++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 92152a5a..95b8124e 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -1,6 +1,7 @@ package secrethub import ( + "bytes" "encoding/json" "errors" "fmt" @@ -21,12 +22,13 @@ import ( ) var ( - errPagerNotFound = errors.New("no terminal pager available") + errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install less or more") ) const ( - pagerEnvvar = "$PAGER" - defaultTerminalWidth = 80 + pagerEnvvar = "$PAGER" + defaultTerminalWidth = 80 + fallbackPagerLineCount = 100 ) // AuditCommand is a command to audit a repo or a secret. @@ -100,7 +102,9 @@ func (cmd *AuditCommand) run() error { } paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) - if err != nil { + if err == errPagerNotFound { + paginatedWriter = newFallbackPager(os.Stdout) + } else if err != nil { return err } defer paginatedWriter.Close() @@ -131,10 +135,13 @@ func (cmd *AuditCommand) run() error { return err } - fmt.Fprintln(paginatedWriter, formattedRow) if paginatedWriter.IsClosed() { break } + _, err = fmt.Fprintln(paginatedWriter, formattedRow) + if err != nil { + return err + } } return nil } @@ -418,6 +425,45 @@ func pagerCommand() (string, error) { return "", errPagerNotFound } +// newFallbackPager returns a pager that outputs a fixed number of lines without pagination +// and returns errPagerNotFound on the last (or any subsequent) write. +func newFallbackPager(w io.WriteCloser) pager { + return &fallbackPager{ + linesLeft: fallbackPagerLineCount, + writer: w, + } +} + +type fallbackPager struct { + writer io.WriteCloser + linesLeft int +} + +func (p *fallbackPager) Write(data []byte) (int, error) { + if p.linesLeft == 0 { + return 0, errPagerNotFound + } + + lines := bytes.Count(data, []byte{'\n'}) + if lines > p.linesLeft { + data = bytes.Join(bytes.Split(data, []byte{'\n'})[:p.linesLeft], []byte{'\n'}) + } + p.linesLeft -= bytes.Count(data, []byte{'\n'}) + n, err := p.writer.Write(data) + if p.linesLeft == 0 { + err = errPagerNotFound + } + return n, err +} + +func (p *fallbackPager) Close() error { + return p.writer.Close() +} + +func (p *fallbackPager) IsClosed() bool { + return p.linesLeft == 0 +} + type auditTableColumn struct { name string maxWidth int From bb161b7d969f054b6e4933d0852e313d0eb24fe6 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 19:25:11 +0100 Subject: [PATCH 027/119] Add tests for fallback pager --- internals/secrethub/audit_test.go | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go index 2ebf0e22..d3317881 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/audit_test.go @@ -1,9 +1,12 @@ package secrethub import ( + "bytes" "strings" "testing" + "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" + "github.com/secrethub/secrethub-go/internals/assert" ) @@ -135,3 +138,47 @@ func Test_columnFormatter_formatRow(t *testing.T) { }) } } + +func TestFallbackPager_Write(t *testing.T) { + cases := map[string]struct { + pager *fallbackPager + param string + expected string + expectedErr error + }{ + "no lines left": { + pager: &fallbackPager{linesLeft: 0}, + expectedErr: errPagerNotFound, + param: "test\n", + expected: "", + }, + "last line": { + pager: &fallbackPager{linesLeft: 1}, + expectedErr: errPagerNotFound, + param: "test\n", + expected: "test\n", + }, + "print more": { + pager: &fallbackPager{linesLeft: 2}, + param: "test1\ntest2\ntest3\ntest4", + expected: "test1\ntest2\n", + expectedErr: errPagerNotFound, + }, + "more lines left": { + pager: &fallbackPager{linesLeft: 3}, + expectedErr: nil, + param: "test\n", + expected: "test\n", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + buffer := bytes.Buffer{} + tc.pager.writer = &fakes.Pager{Buffer: &buffer} + _, err := tc.pager.Write([]byte(tc.param)) + assert.Equal(t, err, tc.expectedErr) + assert.Equal(t, buffer.String(), tc.expected) + }) + } +} From 2085ca796463f71b5f2c7434db50a8668bef54c3 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 19:25:41 +0100 Subject: [PATCH 028/119] Fix bug in fallback pager --- internals/secrethub/audit.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 95b8124e..b1d0dda6 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -447,6 +447,7 @@ func (p *fallbackPager) Write(data []byte) (int, error) { lines := bytes.Count(data, []byte{'\n'}) if lines > p.linesLeft { data = bytes.Join(bytes.Split(data, []byte{'\n'})[:p.linesLeft], []byte{'\n'}) + data = append(data, '\n') } p.linesLeft -= bytes.Count(data, []byte{'\n'}) n, err := p.writer.Write(data) From 782832d1e5d054bb069a10c36550be13bbd1939b Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 26 Mar 2020 19:26:12 +0100 Subject: [PATCH 029/119] Add comment to fake pager --- internals/secrethub/fakes/pager.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internals/secrethub/fakes/pager.go b/internals/secrethub/fakes/pager.go index 0fcd70ea..f00f9611 100644 --- a/internals/secrethub/fakes/pager.go +++ b/internals/secrethub/fakes/pager.go @@ -2,6 +2,8 @@ package fakes import "bytes" +// Pager is a mock pager that remembers what is written to it. +// It can also be used as a fake io.WriteCloser. type Pager struct { Buffer *bytes.Buffer } From d7e5c36efbbbf6ae09195c9cf2915f63ef91b5e3 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 27 Mar 2020 10:51:18 +0100 Subject: [PATCH 030/119] Update description of audit command --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index b1d0dda6..74252711 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -59,7 +59,7 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AuditCommand) Register(r command.Registerer) { - clause := r.Command("audit", "Show the audit log.") + clause := r.Command("audit", "Show the audit log.\nIf the output of the command is parsed by a script the --json flag must be used.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) clause.Flag("json", "output the audit log in json format").BoolVar(&cmd.json) From 0960fef1127481c0609576fcb4247ee68f80fbf6 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 17:36:57 +0200 Subject: [PATCH 031/119] Improve comments --- internals/secrethub/audit.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 74252711..5296d89d 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -22,7 +22,7 @@ import ( ) var ( - errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install less or more") + errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install \"less\" or \"more\"") ) const ( @@ -59,7 +59,7 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AuditCommand) Register(r command.Registerer) { - clause := r.Command("audit", "Show the audit log.\nIf the output of the command is parsed by a script the --json flag must be used.") + clause := r.Command("audit", "Show the audit log.\n\nIf the output of the command is parsed by a script an alternative of the default table format must be used.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) clause.Flag("json", "output the audit log in json format").BoolVar(&cmd.json) @@ -203,6 +203,7 @@ func (f *columnFormatter) printHeader() bool { func (f *columnFormatter) formatRow(row []string) (string, error) { columnWidths := f.columnWidths() + // calculate the maximum number of lines a cell value will be broken into maxLinesPerCell := 1 for i, cell := range row { lines := len(cell) / columnWidths[i] @@ -214,6 +215,7 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { } } + // split the cell values into a grid according to how they will be printed splitCells := make([][]string, maxLinesPerCell) for i := 0; i < maxLinesPerCell; i++ { splitCells[i] = make([]string, len(row)) @@ -240,6 +242,7 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { } } + // convert the grid to a string strRes := strings.Builder{} for j := 0; j < maxLinesPerCell; j++ { strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") @@ -255,6 +258,9 @@ func (f *columnFormatter) columnWidths() []int { } res := make([]int, len(f.columns)) + // Distribute the maximum width equally between all columns and repeatedly + // check if any of them have a smaller maximum width and can be shrunk. + // Stop when no columns can be further adjusted. adjusted := true columnsLeft := len(f.columns) widthLeft := f.tableWidth - 2*(len(f.columns)-1) @@ -278,6 +284,7 @@ func (f *columnFormatter) columnWidths() []int { widthPerColumn = widthLeft / columnsLeft } + // distribute the remaining width equally between columns with no maximum width. for i := range res { if res[i] == 0 { res[i] = widthPerColumn @@ -335,7 +342,7 @@ type pager interface { } // newPaginatedWriter runs the terminal pager configured in the OS environment -// and returns a writer to its standard input. +// and returns a writer that is piped to the standard input of the pager command. func newPaginatedWriter(outputWriter io.Writer) (pager, error) { pager, err := pagerCommand() if err != nil { @@ -364,6 +371,7 @@ func newPaginatedWriter(outputWriter io.Writer) (pager, error) { return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil } +// paginatedWriter is a writer that is piped to a terminal pager command. type paginatedWriter struct { writer io.WriteCloser cmd *exec.Cmd @@ -402,7 +410,7 @@ func (p *paginatedWriter) IsClosed() bool { } // pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). -// If no pager is configured less or more is returned depending on which is available. +// If no pager is configured it falls back to "less" than "more", returning an error if neither are available. func pagerCommand() (string, error) { var pager string var err error From a1fc9db78c6787ce507f50a2f863d63f82c6e28a Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 17:51:52 +0200 Subject: [PATCH 032/119] Change --json flag to --output-format --- internals/secrethub/audit.go | 15 ++++++++++----- internals/secrethub/audit_repo_test.go | 7 +++++++ internals/secrethub/audit_secret_test.go | 7 +++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 5296d89d..8f41d968 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -23,12 +23,15 @@ import ( var ( errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install \"less\" or \"more\"") + errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format) } ) const ( pagerEnvvar = "$PAGER" defaultTerminalWidth = 80 fallbackPagerLineCount = 100 + formatTable = "table" + formatJSON = "json" ) // AuditCommand is a command to audit a repo or a secret. @@ -41,7 +44,7 @@ type AuditCommand struct { newClient newClientFunc terminalWidth func(int) (int, error) perPage int - json bool + format string } // NewAuditCommand creates a new audit command. @@ -61,8 +64,8 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { func (cmd *AuditCommand) Register(r command.Registerer) { clause := r.Command("audit", "Show the audit log.\n\nIf the output of the command is parsed by a script an alternative of the default table format must be used.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) - clause.Flag("per-page", "number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) - clause.Flag("json", "output the audit log in json format").BoolVar(&cmd.json) + clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) + clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. Defaults to table").HintOptions("json").Default("table").StringVar(&cmd.format) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) @@ -91,14 +94,16 @@ func (cmd *AuditCommand) run() error { } var formatter tableFormatter - if cmd.json { + if cmd.format == formatJSON { formatter = newJSONFormatter(auditTable.header()) - } else { + } else if cmd.format == formatTable { terminalWidth, err := cmd.terminalWidth(int(os.Stdout.Fd())) if err != nil { terminalWidth = defaultTerminalWidth } formatter = newColumnFormatter(terminalWidth, auditTable.columns()) + } else { + return errNoSuchFormat(cmd.format) } paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index 7ec46d3e..e7587906 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -38,6 +38,7 @@ func TestAuditRepoCommand_run(t *testing.T) { terminalWidth: func(int) (int, error) { return 83, nil }, + format: formatTable, perPage: 20, }, out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", @@ -73,6 +74,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(_ int) (int, error) { return 83, nil @@ -91,6 +93,7 @@ func TestAuditRepoCommand_run(t *testing.T) { newClient: func() (secrethub.ClientInterface, error) { return nil, ErrCannotFindHomeDir() }, + format: formatTable, perPage: 20, }, err: ErrCannotFindHomeDir(), @@ -110,6 +113,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(int) (int, error) { return 83, nil @@ -133,6 +137,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, }, err: testError, @@ -159,6 +164,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(int) (int, error) { return 83, nil @@ -189,6 +195,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(int) (int, error) { return 83, nil diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 0c072c58..4d6f55f1 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -55,6 +55,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(_ int) (int, error) { return 46, nil @@ -85,6 +86,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(_ int) (int, error) { return 46, nil @@ -95,6 +97,7 @@ func TestAuditSecretCommand_run(t *testing.T) { "error secret version": { cmd: AuditCommand{ path: "namespace/repo/secret:1", + format: formatTable, perPage: 20, }, err: ErrCannotAuditSecretVersion, @@ -105,6 +108,7 @@ func TestAuditSecretCommand_run(t *testing.T) { newClient: func() (secrethub.ClientInterface, error) { return nil, ErrCannotFindHomeDir() }, + format: formatTable, perPage: 20, }, err: ErrCannotFindHomeDir(), @@ -126,6 +130,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, }, err: ErrCannotAuditDir, @@ -147,6 +152,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(_ int) (int, error) { return 46, nil @@ -174,6 +180,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, + format: formatTable, perPage: 20, terminalWidth: func(int) (int, error) { return 83, nil From 74608dc51a345dfbef9c7713392a0d8a4dc2bee7 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 17:52:55 +0200 Subject: [PATCH 033/119] Fix punctuation --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 8f41d968..3a283272 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -65,7 +65,7 @@ func (cmd *AuditCommand) Register(r command.Registerer) { clause := r.Command("audit", "Show the audit log.\n\nIf the output of the command is parsed by a script an alternative of the default table format must be used.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) - clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. Defaults to table").HintOptions("json").Default("table").StringVar(&cmd.format) + clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. Defaults to table.").HintOptions("json").Default("table").StringVar(&cmd.format) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) From eefac6685f22d0f8a4571d320019b0c1122d7677 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 17:58:03 +0200 Subject: [PATCH 034/119] Improve naming of fallbackPager and printHeader --- internals/secrethub/audit.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 3a283272..898dc7c5 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -108,13 +108,13 @@ func (cmd *AuditCommand) run() error { paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) if err == errPagerNotFound { - paginatedWriter = newFallbackPager(os.Stdout) + paginatedWriter = newFallbackPaginatedWriter(os.Stdout) } else if err != nil { return err } defer paginatedWriter.Close() - if formatter.printHeader() { + if formatter.shouldPrintHeader() { header, err := formatter.formatRow(auditTable.header()) if err != nil { return err @@ -152,7 +152,7 @@ func (cmd *AuditCommand) run() error { } type tableFormatter interface { - printHeader() bool + shouldPrintHeader() bool formatRow(row []string) (string, error) } @@ -165,7 +165,7 @@ type jsonFormatter struct { fields []string } -func (f *jsonFormatter) printHeader() bool { +func (f *jsonFormatter) shouldPrintHeader() bool { return false } @@ -199,7 +199,7 @@ type columnFormatter struct { columns []auditTableColumn } -func (f *columnFormatter) printHeader() bool { +func (f *columnFormatter) shouldPrintHeader() bool { return true } @@ -438,9 +438,9 @@ func pagerCommand() (string, error) { return "", errPagerNotFound } -// newFallbackPager returns a pager that outputs a fixed number of lines without pagination +// newFallbackPaginatedWriter returns a pager that closes after outputting a fixed number of lines without pagination // and returns errPagerNotFound on the last (or any subsequent) write. -func newFallbackPager(w io.WriteCloser) pager { +func newFallbackPaginatedWriter(w io.WriteCloser) pager { return &fallbackPager{ linesLeft: fallbackPagerLineCount, writer: w, From 9d114fd24e17bc1a18b064940290f9c44d099ada Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 20:32:05 +0200 Subject: [PATCH 035/119] Update pager to return error when pager command is closed instead of requiring to check pager.IsClosed() and update tableFormatter to write directly to a writer --- internals/secrethub/audit.go | 135 +++++++++++++---------- internals/secrethub/audit_repo_test.go | 8 +- internals/secrethub/audit_secret_test.go | 6 +- internals/secrethub/audit_test.go | 26 ++--- internals/secrethub/fakes/pager.go | 4 - 5 files changed, 94 insertions(+), 85 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 898dc7c5..04b6014e 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "strings" + "syscall" "golang.org/x/crypto/ssh/terminal" @@ -23,6 +24,7 @@ import ( var ( errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install \"less\" or \"more\"") + errPagerClosed = errors.New("cannot write to closed terminal pager") errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format) } ) @@ -93,19 +95,6 @@ func (cmd *AuditCommand) run() error { return err } - var formatter tableFormatter - if cmd.format == formatJSON { - formatter = newJSONFormatter(auditTable.header()) - } else if cmd.format == formatTable { - terminalWidth, err := cmd.terminalWidth(int(os.Stdout.Fd())) - if err != nil { - terminalWidth = defaultTerminalWidth - } - formatter = newColumnFormatter(terminalWidth, auditTable.columns()) - } else { - return errNoSuchFormat(cmd.format) - } - paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) if err == errPagerNotFound { paginatedWriter = newFallbackPaginatedWriter(os.Stdout) @@ -114,12 +103,17 @@ func (cmd *AuditCommand) run() error { } defer paginatedWriter.Close() - if formatter.shouldPrintHeader() { - header, err := formatter.formatRow(auditTable.header()) + var formatter tableFormatter + if cmd.format == formatJSON { + formatter = newJSONFormatter(paginatedWriter, auditTable.header()) + } else if cmd.format == formatTable { + terminalWidth, err := cmd.terminalWidth(int(os.Stdout.Fd())) if err != nil { - return err + terminalWidth = defaultTerminalWidth } - fmt.Fprintln(paginatedWriter, header) + formatter = newColumnFormatter(paginatedWriter, terminalWidth, auditTable.columns()) + } else { + return errNoSuchFormat(cmd.format) } for { @@ -135,16 +129,10 @@ func (cmd *AuditCommand) run() error { return err } - formattedRow, err := formatter.formatRow(row) - if err != nil { - return err - } - - if paginatedWriter.IsClosed() { + err = formatter.WriteRow(row) + if err == errPagerClosed { break - } - _, err = fmt.Fprintln(paginatedWriter, formattedRow) - if err != nil { + } else if err != nil { return err } } @@ -152,28 +140,24 @@ func (cmd *AuditCommand) run() error { } type tableFormatter interface { - shouldPrintHeader() bool - formatRow(row []string) (string, error) + WriteRow([]string) error } // newJSONFormatter returns a table formatter that formats the given table rows as json. -func newJSONFormatter(fieldNames []string) *jsonFormatter { - return &jsonFormatter{fields: fieldNames} +func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { + return &jsonFormatter{encoder: json.NewEncoder(writer), fields: fieldNames} } type jsonFormatter struct { - fields []string -} - -func (f *jsonFormatter) shouldPrintHeader() bool { - return false + encoder *json.Encoder + fields []string } // formatRow returns the json representation of the given row // with the configured field names as keys and the provided values -func (f *jsonFormatter) formatRow(row []string) (string, error) { +func (f *jsonFormatter) WriteRow(row []string) error { if len(f.fields) != len(row) { - return "", fmt.Errorf("unexpected number of json fields") + return fmt.Errorf("unexpected number of json fields") } jsonMap := make(map[string]string) @@ -181,31 +165,53 @@ func (f *jsonFormatter) formatRow(row []string) (string, error) { jsonMap[f.fields[i]] = element } - jsonData, err := json.Marshal(jsonMap) - if err != nil { - return "", err - } - return string(jsonData), nil + return f.encoder.Encode(jsonMap) } // newColumnFormatter returns a table formatter that aligns the columns of the table. -func newColumnFormatter(tableWidth int, columns []auditTableColumn) *columnFormatter { - return &columnFormatter{tableWidth: tableWidth, columns: columns} +func newColumnFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *columnFormatter { + return &columnFormatter{writer: writer, tableWidth: tableWidth, columns: columns} } type columnFormatter struct { tableWidth int + writer io.Writer computedColumnWidths []int - columns []auditTableColumn + columns []tableColumn + didPrintHeader bool } func (f *columnFormatter) shouldPrintHeader() bool { return true } +func (f *columnFormatter) WriteRow(row []string) error { + if !f.didPrintHeader { + header := make([]string, len(f.columns)) + for i, col := range f.columns { + header[i] = col.name + } + formattedHeader, err := f.formatRow(header) + if err != nil { + return err + } + _, err = f.writer.Write(formattedHeader) + if err != nil { + return err + } + f.didPrintHeader = true + } + formattedRow, err := f.formatRow(row) + if err != nil { + return err + } + _, err = f.writer.Write(formattedRow) + return err +} + // formatRow formats the given table row to fit the configured width by // giving each cell an equal width and wrapping the text in cells that exceed it. -func (f *columnFormatter) formatRow(row []string) (string, error) { +func (f *columnFormatter) formatRow(row []string) ([]byte, error) { columnWidths := f.columnWidths() // calculate the maximum number of lines a cell value will be broken into @@ -252,7 +258,7 @@ func (f *columnFormatter) formatRow(row []string) (string, error) { for j := 0; j < maxLinesPerCell; j++ { strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") } - return strings.TrimSuffix(strRes.String(), "\n"), nil + return []byte(strRes.String()), nil } // columnWidths returns the width of each column based on their maximum widths @@ -341,10 +347,8 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } -type pager interface { - io.WriteCloser - IsClosed() bool -} +// pager is an io.WriteCloser that returns errPagerClosed if it has been closed. +type pager io.WriteCloser // newPaginatedWriter runs the terminal pager configured in the OS environment // and returns a writer that is piped to the standard input of the pager command. @@ -384,7 +388,12 @@ type paginatedWriter struct { closed bool } +// Write pipes the data to the terminal pager. +// It returns errPagerClosed if the terminal pager has been closed. func (p *paginatedWriter) Write(data []byte) (n int, err error) { + if p.isClosed() { + return 0, errPagerClosed + } return p.writer.Write(data) } @@ -395,13 +404,17 @@ func (p *paginatedWriter) Close() error { return err } if !p.closed { + err = p.cmd.Process.Signal(syscall.SIGINT) + if err != nil { + p.cmd.Process.Kill() + } <-p.done } return nil } -// IsClosed checks if the terminal pager process has been stopped. -func (p *paginatedWriter) IsClosed() bool { +// isClosed checks if the terminal pager process has been stopped. +func (p *paginatedWriter) isClosed() bool { if p.closed { return true } @@ -478,7 +491,7 @@ func (p *fallbackPager) IsClosed() bool { return p.linesLeft == 0 } -type auditTableColumn struct { +type tableColumn struct { name string maxWidth int } @@ -486,15 +499,15 @@ type auditTableColumn struct { type auditTable interface { header() []string row(event api.Audit) ([]string, error) - columns() []auditTableColumn + columns() []tableColumn } -func newBaseAuditTable(timeFormatter TimeFormatter, midColumns ...auditTableColumn) baseAuditTable { - columns := append([]auditTableColumn{ +func newBaseAuditTable(timeFormatter TimeFormatter, midColumns ...tableColumn) baseAuditTable { + columns := append([]tableColumn{ {name: "AUTHOR", maxWidth: 32}, {name: "EVENT", maxWidth: 22}, }, midColumns...) - columns = append(columns, []auditTableColumn{ + columns = append(columns, []tableColumn{ {name: "IP ADDRESS", maxWidth: 45}, {name: "DATE", maxWidth: 22}, }...) @@ -506,7 +519,7 @@ func newBaseAuditTable(timeFormatter TimeFormatter, midColumns ...auditTableColu } type baseAuditTable struct { - tableColumns []auditTableColumn + tableColumns []tableColumn timeFormatter TimeFormatter } @@ -528,7 +541,7 @@ func (table baseAuditTable) row(event api.Audit, content ...string) ([]string, e return append(res, event.IPAddress, table.timeFormatter.Format(event.LoggedAt)), nil } -func (table baseAuditTable) columns() []auditTableColumn { +func (table baseAuditTable) columns() []tableColumn { return table.tableColumns } @@ -552,7 +565,7 @@ func (table secretAuditTable) row(event api.Audit) ([]string, error) { func newRepoAuditTable(tree *api.Tree, timeFormatter TimeFormatter) repoAuditTable { return repoAuditTable{ - baseAuditTable: newBaseAuditTable(timeFormatter, auditTableColumn{name: "EVENT SUBJECT"}), + baseAuditTable: newBaseAuditTable(timeFormatter, tableColumn{name: "EVENT SUBJECT"}), tree: tree, } } diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index e7587906..64f8f436 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -41,7 +41,7 @@ func TestAuditRepoCommand_run(t *testing.T) { format: formatTable, perPage: 20, }, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", + out: "", }, "create repo event": { cmd: AuditCommand{ @@ -120,7 +120,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, err: testError, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", + out: "", }, "get dir error": { cmd: AuditCommand{ @@ -171,7 +171,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, err: ErrInvalidAuditActor, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", + out: "", }, "invalid audit subject": { cmd: AuditCommand{ @@ -202,7 +202,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, err: ErrInvalidAuditSubject, - out: "AUTHOR EVENT EVENT SUBJECT IP ADDRESS DATE \n", + out: "", }, } diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 4d6f55f1..aaf07741 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -92,7 +92,7 @@ func TestAuditSecretCommand_run(t *testing.T) { return 46, nil }, }, - out: "AUTHOR EVENT IP ADDRESS DATE \n", + out: "", }, "error secret version": { cmd: AuditCommand{ @@ -159,7 +159,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, err: testError, - out: "AUTHOR EVENT IP ADDRESS DATE \n", + out: "", }, "invalid audit actor": { cmd: AuditCommand{ @@ -187,7 +187,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, err: ErrInvalidAuditActor, - out: "AUTHOR EVENT IP ADDRESS DATE \n", + out: "", }, } diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go index d3317881..45a7b439 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/audit_test.go @@ -18,7 +18,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "all columns fit": { formatter: columnFormatter{ tableWidth: 102, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 10}, {maxWidth: 10}, }, @@ -28,7 +28,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "no columns fit": { formatter: columnFormatter{ tableWidth: 12, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 10}, {maxWidth: 10}, }, @@ -38,7 +38,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "one column fits": { formatter: columnFormatter{ tableWidth: 27, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 10}, {maxWidth: 20}, }, @@ -48,7 +48,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "multiple adjustments": { formatter: columnFormatter{ tableWidth: 106, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 27}, {maxWidth: 26}, {maxWidth: 25}, @@ -60,7 +60,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "no max width for some all fit": { formatter: columnFormatter{ tableWidth: 64, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 15}, {}, {maxWidth: 15}, @@ -71,7 +71,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { "no max width for some not all fit": { formatter: columnFormatter{ tableWidth: 64, - columns: []auditTableColumn{ + columns: []tableColumn{ {maxWidth: 50}, {}, {maxWidth: 10}, @@ -100,30 +100,30 @@ func Test_columnFormatter_formatRow(t *testing.T) { formatter: columnFormatter{ tableWidth: 102, computedColumnWidths: []int{50, 50}, - columns: []auditTableColumn{{}, {}}, + columns: []tableColumn{{}, {}}, }, row: []string{"foo", "bar"}, - expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47), + expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47) + "\n", expectedErr: nil, }, "wrapping": { formatter: columnFormatter{ tableWidth: 6, computedColumnWidths: []int{2, 2}, - columns: []auditTableColumn{{}, {}}, + columns: []tableColumn{{}, {}}, }, row: []string{"foo", "bar"}, - expected: "fo ba\no r ", + expected: "fo ba\no r \n", expectedErr: nil, }, "fits exactly": { formatter: columnFormatter{ tableWidth: 8, computedColumnWidths: []int{3, 3}, - columns: []auditTableColumn{{}, {}}, + columns: []tableColumn{{}, {}}, }, row: []string{"foo", "bar"}, - expected: "foo bar", + expected: "foo bar\n", expectedErr: nil, }, } @@ -133,7 +133,7 @@ func Test_columnFormatter_formatRow(t *testing.T) { result, err := tc.formatter.formatRow(tc.row) assert.Equal(t, err, tc.expectedErr) if err == nil { - assert.Equal(t, result, tc.expected) + assert.Equal(t, string(result), tc.expected) } }) } diff --git a/internals/secrethub/fakes/pager.go b/internals/secrethub/fakes/pager.go index f00f9611..9d3b3f5a 100644 --- a/internals/secrethub/fakes/pager.go +++ b/internals/secrethub/fakes/pager.go @@ -15,7 +15,3 @@ func (f *Pager) Write(p []byte) (n int, err error) { func (f *Pager) Close() error { return nil } - -func (f *Pager) IsClosed() bool { - return false -} From 1d2f3c35ca3ce85cb84e318acb39d32b1888a9c5 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 20:40:00 +0200 Subject: [PATCH 036/119] Fix lint errors --- internals/secrethub/audit.go | 27 ++++++++++----------------- internals/secrethub/audit_test.go | 29 +++++++++++------------------ 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 04b6014e..92ca2b10 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -181,37 +181,27 @@ type columnFormatter struct { didPrintHeader bool } -func (f *columnFormatter) shouldPrintHeader() bool { - return true -} - func (f *columnFormatter) WriteRow(row []string) error { if !f.didPrintHeader { header := make([]string, len(f.columns)) for i, col := range f.columns { header[i] = col.name } - formattedHeader, err := f.formatRow(header) - if err != nil { - return err - } - _, err = f.writer.Write(formattedHeader) + formattedHeader := f.formatRow(header) + _, err := f.writer.Write(formattedHeader) if err != nil { return err } f.didPrintHeader = true } - formattedRow, err := f.formatRow(row) - if err != nil { - return err - } - _, err = f.writer.Write(formattedRow) + formattedRow := f.formatRow(row) + _, err := f.writer.Write(formattedRow) return err } // formatRow formats the given table row to fit the configured width by // giving each cell an equal width and wrapping the text in cells that exceed it. -func (f *columnFormatter) formatRow(row []string) ([]byte, error) { +func (f *columnFormatter) formatRow(row []string) []byte { columnWidths := f.columnWidths() // calculate the maximum number of lines a cell value will be broken into @@ -258,7 +248,7 @@ func (f *columnFormatter) formatRow(row []string) ([]byte, error) { for j := 0; j < maxLinesPerCell; j++ { strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") } - return []byte(strRes.String()), nil + return []byte(strRes.String()) } // columnWidths returns the width of each column based on their maximum widths @@ -406,7 +396,10 @@ func (p *paginatedWriter) Close() error { if !p.closed { err = p.cmd.Process.Signal(syscall.SIGINT) if err != nil { - p.cmd.Process.Kill() + err = p.cmd.Process.Kill() + if err != nil { + return err + } } <-p.done } diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go index 45a7b439..34dd0dc6 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/audit_test.go @@ -91,10 +91,9 @@ func Test_columnFormatter_columnWidths(t *testing.T) { func Test_columnFormatter_formatRow(t *testing.T) { cases := map[string]struct { - formatter columnFormatter - row []string - expected string - expectedErr error + formatter columnFormatter + row []string + expected string }{ "all cells fit": { formatter: columnFormatter{ @@ -102,9 +101,8 @@ func Test_columnFormatter_formatRow(t *testing.T) { computedColumnWidths: []int{50, 50}, columns: []tableColumn{{}, {}}, }, - row: []string{"foo", "bar"}, - expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47) + "\n", - expectedErr: nil, + row: []string{"foo", "bar"}, + expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47) + "\n", }, "wrapping": { formatter: columnFormatter{ @@ -112,9 +110,8 @@ func Test_columnFormatter_formatRow(t *testing.T) { computedColumnWidths: []int{2, 2}, columns: []tableColumn{{}, {}}, }, - row: []string{"foo", "bar"}, - expected: "fo ba\no r \n", - expectedErr: nil, + row: []string{"foo", "bar"}, + expected: "fo ba\no r \n", }, "fits exactly": { formatter: columnFormatter{ @@ -122,19 +119,15 @@ func Test_columnFormatter_formatRow(t *testing.T) { computedColumnWidths: []int{3, 3}, columns: []tableColumn{{}, {}}, }, - row: []string{"foo", "bar"}, - expected: "foo bar\n", - expectedErr: nil, + row: []string{"foo", "bar"}, + expected: "foo bar\n", }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { - result, err := tc.formatter.formatRow(tc.row) - assert.Equal(t, err, tc.expectedErr) - if err == nil { - assert.Equal(t, string(result), tc.expected) - } + result := tc.formatter.formatRow(tc.row) + assert.Equal(t, string(result), tc.expected) }) } } From 078b0bb029cf42da7fb6a3931c9a62ff2bdddf1b Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 20:44:04 +0200 Subject: [PATCH 037/119] Refactor paginatedWriter.Close --- internals/secrethub/audit.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 92ca2b10..69f01b13 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -393,16 +393,17 @@ func (p *paginatedWriter) Close() error { if err != nil { return err } - if !p.closed { - err = p.cmd.Process.Signal(syscall.SIGINT) + if p.closed { + return nil + } + err = p.cmd.Process.Signal(syscall.SIGINT) + if err != nil { + err = p.cmd.Process.Kill() if err != nil { - err = p.cmd.Process.Kill() - if err != nil { - return err - } + return err } - <-p.done } + <-p.done return nil } From 0260d71f0cb101b769f5839c5566b41228deb98c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 1 Apr 2020 20:45:08 +0200 Subject: [PATCH 038/119] Remove unused method --- internals/secrethub/audit.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 69f01b13..8d85ef22 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -481,10 +481,6 @@ func (p *fallbackPager) Close() error { return p.writer.Close() } -func (p *fallbackPager) IsClosed() bool { - return p.linesLeft == 0 -} - type tableColumn struct { name string maxWidth int From e179303c4e2e864a98300714b5bbca335ec533fd Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 10:46:54 +0200 Subject: [PATCH 039/119] Rename audit log entry formatters --- internals/secrethub/audit.go | 35 +++++++++++++++++-------------- internals/secrethub/audit_test.go | 22 +++++++++---------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 8d85ef22..f4cdb6ee 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -103,7 +103,7 @@ func (cmd *AuditCommand) run() error { } defer paginatedWriter.Close() - var formatter tableFormatter + var formatter listFormatter if cmd.format == formatJSON { formatter = newJSONFormatter(paginatedWriter, auditTable.header()) } else if cmd.format == formatTable { @@ -111,7 +111,7 @@ func (cmd *AuditCommand) run() error { if err != nil { terminalWidth = defaultTerminalWidth } - formatter = newColumnFormatter(paginatedWriter, terminalWidth, auditTable.columns()) + formatter = newTableFormatter(paginatedWriter, terminalWidth, auditTable.columns()) } else { return errNoSuchFormat(cmd.format) } @@ -129,7 +129,7 @@ func (cmd *AuditCommand) run() error { return err } - err = formatter.WriteRow(row) + err = formatter.Write(row) if err == errPagerClosed { break } else if err != nil { @@ -139,8 +139,8 @@ func (cmd *AuditCommand) run() error { return nil } -type tableFormatter interface { - WriteRow([]string) error +type listFormatter interface { + Write([]string) error } // newJSONFormatter returns a table formatter that formats the given table rows as json. @@ -155,25 +155,25 @@ type jsonFormatter struct { // formatRow returns the json representation of the given row // with the configured field names as keys and the provided values -func (f *jsonFormatter) WriteRow(row []string) error { - if len(f.fields) != len(row) { +func (f *jsonFormatter) Write(values []string) error { + if len(f.fields) != len(values) { return fmt.Errorf("unexpected number of json fields") } jsonMap := make(map[string]string) - for i, element := range row { + for i, element := range values { jsonMap[f.fields[i]] = element } return f.encoder.Encode(jsonMap) } -// newColumnFormatter returns a table formatter that aligns the columns of the table. -func newColumnFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *columnFormatter { - return &columnFormatter{writer: writer, tableWidth: tableWidth, columns: columns} +// newTableFormatter returns a list formatter that formats entries in a table. +func newTableFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *tableFormatter { + return &tableFormatter{writer: writer, tableWidth: tableWidth, columns: columns} } -type columnFormatter struct { +type tableFormatter struct { tableWidth int writer io.Writer computedColumnWidths []int @@ -181,7 +181,9 @@ type columnFormatter struct { didPrintHeader bool } -func (f *columnFormatter) WriteRow(row []string) error { +// Write writes the given values formatted in a table with the columns of +// +func (f *tableFormatter) Write(values []string) error { if !f.didPrintHeader { header := make([]string, len(f.columns)) for i, col := range f.columns { @@ -194,14 +196,15 @@ func (f *columnFormatter) WriteRow(row []string) error { } f.didPrintHeader = true } - formattedRow := f.formatRow(row) + + formattedRow := f.formatRow(values) _, err := f.writer.Write(formattedRow) return err } // formatRow formats the given table row to fit the configured width by // giving each cell an equal width and wrapping the text in cells that exceed it. -func (f *columnFormatter) formatRow(row []string) []byte { +func (f *tableFormatter) formatRow(row []string) []byte { columnWidths := f.columnWidths() // calculate the maximum number of lines a cell value will be broken into @@ -253,7 +256,7 @@ func (f *columnFormatter) formatRow(row []string) []byte { // columnWidths returns the width of each column based on their maximum widths // and the table width. -func (f *columnFormatter) columnWidths() []int { +func (f *tableFormatter) columnWidths() []int { if f.computedColumnWidths != nil { return f.computedColumnWidths } diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/audit_test.go index 34dd0dc6..932f2b12 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/audit_test.go @@ -12,11 +12,11 @@ import ( func Test_columnFormatter_columnWidths(t *testing.T) { cases := map[string]struct { - formatter columnFormatter + formatter tableFormatter expected []int }{ "all columns fit": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 102, columns: []tableColumn{ {maxWidth: 10}, @@ -26,7 +26,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { expected: []int{50, 50}, }, "no columns fit": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 12, columns: []tableColumn{ {maxWidth: 10}, @@ -36,7 +36,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { expected: []int{5, 5}, }, "one column fits": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 27, columns: []tableColumn{ {maxWidth: 10}, @@ -46,7 +46,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { expected: []int{10, 15}, }, "multiple adjustments": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 106, columns: []tableColumn{ {maxWidth: 27}, @@ -58,7 +58,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { expected: []int{27, 26, 25, 20}, }, "no max width for some all fit": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 64, columns: []tableColumn{ {maxWidth: 15}, @@ -69,7 +69,7 @@ func Test_columnFormatter_columnWidths(t *testing.T) { expected: []int{15, 30, 15}, }, "no max width for some not all fit": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 64, columns: []tableColumn{ {maxWidth: 50}, @@ -91,12 +91,12 @@ func Test_columnFormatter_columnWidths(t *testing.T) { func Test_columnFormatter_formatRow(t *testing.T) { cases := map[string]struct { - formatter columnFormatter + formatter tableFormatter row []string expected string }{ "all cells fit": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 102, computedColumnWidths: []int{50, 50}, columns: []tableColumn{{}, {}}, @@ -105,7 +105,7 @@ func Test_columnFormatter_formatRow(t *testing.T) { expected: "foo" + strings.Repeat(" ", 47) + " " + "bar" + strings.Repeat(" ", 47) + "\n", }, "wrapping": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 6, computedColumnWidths: []int{2, 2}, columns: []tableColumn{{}, {}}, @@ -114,7 +114,7 @@ func Test_columnFormatter_formatRow(t *testing.T) { expected: "fo ba\no r \n", }, "fits exactly": { - formatter: columnFormatter{ + formatter: tableFormatter{ tableWidth: 8, computedColumnWidths: []int{3, 3}, columns: []tableColumn{{}, {}}, From 6280db33a87dabf2d647d0cbe191774f1c931e39 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 14:48:26 +0200 Subject: [PATCH 040/119] Update audit command description of scriptable output formats --- internals/secrethub/audit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index f4cdb6ee..2a8537e9 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -64,10 +64,10 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AuditCommand) Register(r command.Registerer) { - clause := r.Command("audit", "Show the audit log.\n\nIf the output of the command is parsed by a script an alternative of the default table format must be used.") + clause := r.Command("audit", "Show the audit log.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) - clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. Defaults to table.").HintOptions("json").Default("table").StringVar(&cmd.format) + clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.").HintOptions("table", "json").Default("table").StringVar(&cmd.format) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) From e15b11c1d68245154f7bee6fd33da7931b83803c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 14:55:00 +0200 Subject: [PATCH 041/119] Reformat struct initialization Co-Authored-By: Joris Coenen --- internals/secrethub/audit.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 8d85ef22..835c3d87 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -145,7 +145,10 @@ type tableFormatter interface { // newJSONFormatter returns a table formatter that formats the given table rows as json. func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { - return &jsonFormatter{encoder: json.NewEncoder(writer), fields: fieldNames} + return &jsonFormatter{ + encoder: json.NewEncoder(writer), + fields: fieldNames, + } } type jsonFormatter struct { From 9496a84fa411c754869e2704c6b677ff0970464f Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 14:55:16 +0200 Subject: [PATCH 042/119] Reformat struct initialization Co-Authored-By: Joris Coenen --- internals/secrethub/audit.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 835c3d87..8a3978cb 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -173,7 +173,11 @@ func (f *jsonFormatter) WriteRow(row []string) error { // newColumnFormatter returns a table formatter that aligns the columns of the table. func newColumnFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *columnFormatter { - return &columnFormatter{writer: writer, tableWidth: tableWidth, columns: columns} + return &columnFormatter{ + writer: writer, + tableWidth: tableWidth, + columns: columns, + } } type columnFormatter struct { From 4e9ade623407959ef06f9a5df28cfedffcb174bc Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 14:56:30 +0200 Subject: [PATCH 043/119] Rename didPrintHeader to headerPrinted --- internals/secrethub/audit.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 2a8537e9..b36528a6 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -178,13 +178,13 @@ type tableFormatter struct { writer io.Writer computedColumnWidths []int columns []tableColumn - didPrintHeader bool + headerPrinted bool } // Write writes the given values formatted in a table with the columns of // func (f *tableFormatter) Write(values []string) error { - if !f.didPrintHeader { + if !f.headerPrinted { header := make([]string, len(f.columns)) for i, col := range f.columns { header[i] = col.name @@ -194,7 +194,7 @@ func (f *tableFormatter) Write(values []string) error { if err != nil { return err } - f.didPrintHeader = true + f.headerPrinted = true } formattedRow := f.formatRow(values) From 6d351c7561a4d232af7ea6a8b9ad95ee30260592 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 16:12:55 +0200 Subject: [PATCH 044/119] Refactor audit log table formatting into multiple smaller methods --- internals/secrethub/audit.go | 59 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 3b089cf9..9a0bb146 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -188,8 +188,8 @@ type tableFormatter struct { headerPrinted bool } -// Write writes the given values formatted in a table with the columns of -// +// Write writes the given values formatted in a table with the configured column widths and names. +// The header of the table is printed on the first call, before any other value. func (f *tableFormatter) Write(values []string) error { if !f.headerPrinted { header := make([]string, len(f.columns)) @@ -213,23 +213,23 @@ func (f *tableFormatter) Write(values []string) error { // giving each cell an equal width and wrapping the text in cells that exceed it. func (f *tableFormatter) formatRow(row []string) []byte { columnWidths := f.columnWidths() + grid := f.rowToGrid(row, columnWidths) - // calculate the maximum number of lines a cell value will be broken into - maxLinesPerCell := 1 - for i, cell := range row { - lines := len(cell) / columnWidths[i] - if len(cell)%columnWidths[i] != 0 { - lines++ - } - if lines > maxLinesPerCell { - maxLinesPerCell = lines - } + strRes := strings.Builder{} + for _, row := range grid { + strRes.WriteString(strings.Join(row, " ") + "\n") } + return []byte(strRes.String()) +} + +// rowToGrid returns a the given row split over a matrix in which all columns have equal length. +// Longer values are split over multiple cells and shorter (or empty) ones are padded with " ". +func (f *tableFormatter) rowToGrid(row []string, columnWidths []int) [][]string { + maxLinesPerCell := f.lineCount(row, columnWidths) - // split the cell values into a grid according to how they will be printed - splitCells := make([][]string, maxLinesPerCell) + grid := make([][]string, maxLinesPerCell) for i := 0; i < maxLinesPerCell; i++ { - splitCells[i] = make([]string, len(row)) + grid[i] = make([]string, len(row)) } for i, cell := range row { @@ -238,27 +238,38 @@ func (f *tableFormatter) formatRow(row []string) []byte { for j := 0; j < lineCount; j++ { begin := j * columnWidth end := (j + 1) * columnWidth - splitCells[j][i] = cell[begin:end] + grid[j][i] = cell[begin:end] } charactersLeft := len(cell) % columnWidth if charactersLeft != 0 { - splitCells[lineCount][i] = cell[len(cell)-charactersLeft:] + strings.Repeat(" ", columnWidth-charactersLeft) + grid[lineCount][i] = cell[len(cell)-charactersLeft:] + strings.Repeat(" ", columnWidth-charactersLeft) } else if lineCount < maxLinesPerCell { - splitCells[lineCount][i] = strings.Repeat(" ", columnWidth) + grid[lineCount][i] = strings.Repeat(" ", columnWidth) } for j := lineCount + 1; j < maxLinesPerCell; j++ { - splitCells[j][i] = strings.Repeat(" ", columnWidth) + grid[j][i] = strings.Repeat(" ", columnWidth) } } - // convert the grid to a string - strRes := strings.Builder{} - for j := 0; j < maxLinesPerCell; j++ { - strRes.WriteString(strings.Join(splitCells[j], " ") + "\n") + return grid +} + +// lineCount returns the number of lines the given table row will occupy after splitting the +// cell values that exceed their column width. +func (f *tableFormatter) lineCount(row []string, widths []int) int { + maxLinesPerCell := 1 + for i, value := range row { + lines := len(value) / widths[i] + if len(value)%widths[i] != 0 { + lines++ + } + if lines > maxLinesPerCell { + maxLinesPerCell = lines + } } - return []byte(strRes.String()) + return maxLinesPerCell } // columnWidths returns the width of each column based on their maximum widths From cef2d1e26e768b2483c667ac378df80622459ffd Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 3 Apr 2020 16:28:01 +0200 Subject: [PATCH 045/119] Convert json formatted audit log entry keys to PascalCase --- internals/secrethub/audit.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 9a0bb146..11d2428a 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -145,12 +145,19 @@ type listFormatter interface { // newJSONFormatter returns a table formatter that formats the given table rows as json. func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { + for i := range fieldNames { + fieldNames[i] = toPascalCase(fieldNames[i]) + } return &jsonFormatter{ encoder: json.NewEncoder(writer), fields: fieldNames, } } +func toPascalCase(s string) string { + return strings.ReplaceAll(strings.Title(s), " ", "") +} + type jsonFormatter struct { encoder *json.Encoder fields []string @@ -194,7 +201,7 @@ func (f *tableFormatter) Write(values []string) error { if !f.headerPrinted { header := make([]string, len(f.columns)) for i, col := range f.columns { - header[i] = col.name + header[i] = strings.ToUpper(col.name) } formattedHeader := f.formatRow(header) _, err := f.writer.Write(formattedHeader) @@ -515,12 +522,12 @@ type auditTable interface { func newBaseAuditTable(timeFormatter TimeFormatter, midColumns ...tableColumn) baseAuditTable { columns := append([]tableColumn{ - {name: "AUTHOR", maxWidth: 32}, - {name: "EVENT", maxWidth: 22}, + {name: "author", maxWidth: 32}, + {name: "event", maxWidth: 22}, }, midColumns...) columns = append(columns, []tableColumn{ - {name: "IP ADDRESS", maxWidth: 45}, - {name: "DATE", maxWidth: 22}, + {name: "IP address", maxWidth: 45}, + {name: "date", maxWidth: 22}, }...) return baseAuditTable{ From 12b75b57e81fd7b4fc8fd6f30de6d99452300e7c Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 11:34:06 +0200 Subject: [PATCH 046/119] Restructure pagination and formatting code. Moved the code for handling pagination to a separate package and the code for handling formatting to a separate file. Fixed inconsistency in pager struct names. --- internals/secrethub/audit.go | 357 +----------------- internals/secrethub/audit_repo_test.go | 2 +- internals/secrethub/audit_secret_test.go | 2 +- internals/secrethub/formatter.go | 192 ++++++++++ .../{audit_test.go => formatter_test.go} | 47 --- internals/secrethub/pager/pager.go | 159 ++++++++ internals/secrethub/pager/pager_test.go | 53 +++ 7 files changed, 417 insertions(+), 395 deletions(-) create mode 100644 internals/secrethub/formatter.go rename internals/secrethub/{audit_test.go => formatter_test.go} (70%) create mode 100644 internals/secrethub/pager/pager.go create mode 100644 internals/secrethub/pager/pager_test.go diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 11d2428a..28677c3b 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -1,15 +1,12 @@ package secrethub import ( - "bytes" - "encoding/json" "errors" "fmt" "io" "os" - "os/exec" - "strings" - "syscall" + + "github.com/secrethub/secrethub-cli/internals/secrethub/pager" "golang.org/x/crypto/ssh/terminal" @@ -23,23 +20,19 @@ import ( ) var ( - errPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install \"less\" or \"more\"") - errPagerClosed = errors.New("cannot write to closed terminal pager") - errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format) } + errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format) } ) const ( - pagerEnvvar = "$PAGER" - defaultTerminalWidth = 80 - fallbackPagerLineCount = 100 - formatTable = "table" - formatJSON = "json" + defaultTerminalWidth = 80 + formatTable = "table" + formatJSON = "json" ) // AuditCommand is a command to audit a repo or a secret. type AuditCommand struct { io ui.IO - newPaginatedWriter func(io.Writer) (pager, error) + newPaginatedWriter func(io.Writer) (io.WriteCloser, error) path api.Path useTimestamps bool timeFormatter TimeFormatter @@ -53,7 +46,7 @@ type AuditCommand struct { func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { return &AuditCommand{ io: io, - newPaginatedWriter: newPaginatedWriter, + newPaginatedWriter: pager.New, newClient: newClient, terminalWidth: func(fd int) (int, error) { w, _, err := terminal.GetSize(fd) @@ -96,8 +89,8 @@ func (cmd *AuditCommand) run() error { } paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) - if err == errPagerNotFound { - paginatedWriter = newFallbackPaginatedWriter(os.Stdout) + if err == pager.ErrPagerNotFound { + paginatedWriter = pager.NewFallbackPager(os.Stdout) } else if err != nil { return err } @@ -130,7 +123,7 @@ func (cmd *AuditCommand) run() error { } err = formatter.Write(row) - if err == errPagerClosed { + if err == pager.ErrPagerClosed { break } else if err != nil { return err @@ -139,190 +132,6 @@ func (cmd *AuditCommand) run() error { return nil } -type listFormatter interface { - Write([]string) error -} - -// newJSONFormatter returns a table formatter that formats the given table rows as json. -func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { - for i := range fieldNames { - fieldNames[i] = toPascalCase(fieldNames[i]) - } - return &jsonFormatter{ - encoder: json.NewEncoder(writer), - fields: fieldNames, - } -} - -func toPascalCase(s string) string { - return strings.ReplaceAll(strings.Title(s), " ", "") -} - -type jsonFormatter struct { - encoder *json.Encoder - fields []string -} - -// formatRow returns the json representation of the given row -// with the configured field names as keys and the provided values -func (f *jsonFormatter) Write(values []string) error { - if len(f.fields) != len(values) { - return fmt.Errorf("unexpected number of json fields") - } - - jsonMap := make(map[string]string) - for i, element := range values { - jsonMap[f.fields[i]] = element - } - - return f.encoder.Encode(jsonMap) -} - -// newTableFormatter returns a list formatter that formats entries in a table. -func newTableFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *tableFormatter { - return &tableFormatter{ - writer: writer, - tableWidth: tableWidth, - columns: columns, - } -} - -type tableFormatter struct { - tableWidth int - writer io.Writer - computedColumnWidths []int - columns []tableColumn - headerPrinted bool -} - -// Write writes the given values formatted in a table with the configured column widths and names. -// The header of the table is printed on the first call, before any other value. -func (f *tableFormatter) Write(values []string) error { - if !f.headerPrinted { - header := make([]string, len(f.columns)) - for i, col := range f.columns { - header[i] = strings.ToUpper(col.name) - } - formattedHeader := f.formatRow(header) - _, err := f.writer.Write(formattedHeader) - if err != nil { - return err - } - f.headerPrinted = true - } - - formattedRow := f.formatRow(values) - _, err := f.writer.Write(formattedRow) - return err -} - -// formatRow formats the given table row to fit the configured width by -// giving each cell an equal width and wrapping the text in cells that exceed it. -func (f *tableFormatter) formatRow(row []string) []byte { - columnWidths := f.columnWidths() - grid := f.rowToGrid(row, columnWidths) - - strRes := strings.Builder{} - for _, row := range grid { - strRes.WriteString(strings.Join(row, " ") + "\n") - } - return []byte(strRes.String()) -} - -// rowToGrid returns a the given row split over a matrix in which all columns have equal length. -// Longer values are split over multiple cells and shorter (or empty) ones are padded with " ". -func (f *tableFormatter) rowToGrid(row []string, columnWidths []int) [][]string { - maxLinesPerCell := f.lineCount(row, columnWidths) - - grid := make([][]string, maxLinesPerCell) - for i := 0; i < maxLinesPerCell; i++ { - grid[i] = make([]string, len(row)) - } - - for i, cell := range row { - columnWidth := columnWidths[i] - lineCount := len(cell) / columnWidth - for j := 0; j < lineCount; j++ { - begin := j * columnWidth - end := (j + 1) * columnWidth - grid[j][i] = cell[begin:end] - } - - charactersLeft := len(cell) % columnWidth - if charactersLeft != 0 { - grid[lineCount][i] = cell[len(cell)-charactersLeft:] + strings.Repeat(" ", columnWidth-charactersLeft) - } else if lineCount < maxLinesPerCell { - grid[lineCount][i] = strings.Repeat(" ", columnWidth) - } - - for j := lineCount + 1; j < maxLinesPerCell; j++ { - grid[j][i] = strings.Repeat(" ", columnWidth) - } - } - - return grid -} - -// lineCount returns the number of lines the given table row will occupy after splitting the -// cell values that exceed their column width. -func (f *tableFormatter) lineCount(row []string, widths []int) int { - maxLinesPerCell := 1 - for i, value := range row { - lines := len(value) / widths[i] - if len(value)%widths[i] != 0 { - lines++ - } - if lines > maxLinesPerCell { - maxLinesPerCell = lines - } - } - return maxLinesPerCell -} - -// columnWidths returns the width of each column based on their maximum widths -// and the table width. -func (f *tableFormatter) columnWidths() []int { - if f.computedColumnWidths != nil { - return f.computedColumnWidths - } - res := make([]int, len(f.columns)) - - // Distribute the maximum width equally between all columns and repeatedly - // check if any of them have a smaller maximum width and can be shrunk. - // Stop when no columns can be further adjusted. - adjusted := true - columnsLeft := len(f.columns) - widthLeft := f.tableWidth - 2*(len(f.columns)-1) - widthPerColumn := widthLeft / columnsLeft - for adjusted { - adjusted = false - for i, col := range f.columns { - if res[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { - res[i] = col.maxWidth - widthLeft -= col.maxWidth - columnsLeft-- - adjusted = true - } - } - if columnsLeft == 0 { - for i := range res { - res[i] += widthLeft / len(res) - } - break - } - widthPerColumn = widthLeft / columnsLeft - } - - // distribute the remaining width equally between columns with no maximum width. - for i := range res { - if res[i] == 0 { - res[i] = widthPerColumn - } - } - f.computedColumnWidths = res - return res -} - func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, auditTable, error) { repoPath, err := cmd.path.ToRepoPath() if err == nil { @@ -365,150 +174,6 @@ func (cmd *AuditCommand) iterAndAuditTable() (secrethub.AuditEventIterator, audi return nil, nil, ErrNoValidRepoOrSecretPath } -// pager is an io.WriteCloser that returns errPagerClosed if it has been closed. -type pager io.WriteCloser - -// newPaginatedWriter runs the terminal pager configured in the OS environment -// and returns a writer that is piped to the standard input of the pager command. -func newPaginatedWriter(outputWriter io.Writer) (pager, error) { - pager, err := pagerCommand() - if err != nil { - return nil, err - } - - cmd := exec.Command(pager) - - writer, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - cmd.Stdout = outputWriter - cmd.Stderr = os.Stderr - - err = cmd.Start() - if err != nil { - return nil, err - } - done := make(chan struct{}, 1) - go func() { - _ = cmd.Wait() - done <- struct{}{} - }() - return &paginatedWriter{writer: writer, cmd: cmd, done: done}, nil -} - -// paginatedWriter is a writer that is piped to a terminal pager command. -type paginatedWriter struct { - writer io.WriteCloser - cmd *exec.Cmd - done <-chan struct{} - closed bool -} - -// Write pipes the data to the terminal pager. -// It returns errPagerClosed if the terminal pager has been closed. -func (p *paginatedWriter) Write(data []byte) (n int, err error) { - if p.isClosed() { - return 0, errPagerClosed - } - return p.writer.Write(data) -} - -// Close closes the writer to the terminal pager and waits for the terminal pager to close. -func (p *paginatedWriter) Close() error { - err := p.writer.Close() - if err != nil { - return err - } - if p.closed { - return nil - } - err = p.cmd.Process.Signal(syscall.SIGINT) - if err != nil { - err = p.cmd.Process.Kill() - if err != nil { - return err - } - } - <-p.done - return nil -} - -// isClosed checks if the terminal pager process has been stopped. -func (p *paginatedWriter) isClosed() bool { - if p.closed { - return true - } - select { - case <-p.done: - p.closed = true - return true - default: - return false - } -} - -// pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). -// If no pager is configured it falls back to "less" than "more", returning an error if neither are available. -func pagerCommand() (string, error) { - var pager string - var err error - - pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)) - if err == nil { - return pager, nil - } - - pager, err = exec.LookPath("less") - if err == nil { - return pager, nil - } - - pager, err = exec.LookPath("more") - if err == nil { - return pager, nil - } - - return "", errPagerNotFound -} - -// newFallbackPaginatedWriter returns a pager that closes after outputting a fixed number of lines without pagination -// and returns errPagerNotFound on the last (or any subsequent) write. -func newFallbackPaginatedWriter(w io.WriteCloser) pager { - return &fallbackPager{ - linesLeft: fallbackPagerLineCount, - writer: w, - } -} - -type fallbackPager struct { - writer io.WriteCloser - linesLeft int -} - -func (p *fallbackPager) Write(data []byte) (int, error) { - if p.linesLeft == 0 { - return 0, errPagerNotFound - } - - lines := bytes.Count(data, []byte{'\n'}) - if lines > p.linesLeft { - data = bytes.Join(bytes.Split(data, []byte{'\n'})[:p.linesLeft], []byte{'\n'}) - data = append(data, '\n') - } - p.linesLeft -= bytes.Count(data, []byte{'\n'}) - n, err := p.writer.Write(data) - if p.linesLeft == 0 { - err = errPagerNotFound - } - return n, err -} - -func (p *fallbackPager) Close() error { - return p.writer.Close() -} - type tableColumn struct { name string maxWidth int diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index 64f8f436..6783a23e 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -210,7 +210,7 @@ func TestAuditRepoCommand_run(t *testing.T) { t.Run(name, func(t *testing.T) { // Setup buffer := bytes.Buffer{} - tc.cmd.newPaginatedWriter = func(_ io.Writer) (pager, error) { + tc.cmd.newPaginatedWriter = func(_ io.Writer) (io.WriteCloser, error) { return &fakes.Pager{Buffer: &buffer}, nil } diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index aaf07741..d4c28954 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -195,7 +195,7 @@ func TestAuditSecretCommand_run(t *testing.T) { t.Run(name, func(t *testing.T) { // Setup buffer := bytes.Buffer{} - tc.cmd.newPaginatedWriter = func(_ io.Writer) (pager, error) { + tc.cmd.newPaginatedWriter = func(_ io.Writer) (io.WriteCloser, error) { return &fakes.Pager{Buffer: &buffer}, nil } // Act diff --git a/internals/secrethub/formatter.go b/internals/secrethub/formatter.go new file mode 100644 index 00000000..ec54eba1 --- /dev/null +++ b/internals/secrethub/formatter.go @@ -0,0 +1,192 @@ +package secrethub + +import ( + "encoding/json" + "fmt" + "io" + "strings" +) + +type listFormatter interface { + Write([]string) error +} + +// newJSONFormatter returns a table formatter that formats the given table rows as json. +func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { + for i := range fieldNames { + fieldNames[i] = toPascalCase(fieldNames[i]) + } + return &jsonFormatter{ + encoder: json.NewEncoder(writer), + fields: fieldNames, + } +} + +func toPascalCase(s string) string { + return strings.ReplaceAll(strings.Title(s), " ", "") +} + +type jsonFormatter struct { + encoder *json.Encoder + fields []string +} + +// formatRow returns the json representation of the given row +// with the configured field names as keys and the provided values +func (f *jsonFormatter) Write(values []string) error { + if len(f.fields) != len(values) { + return fmt.Errorf("unexpected number of json fields") + } + + jsonMap := make(map[string]string) + for i, element := range values { + jsonMap[f.fields[i]] = element + } + + return f.encoder.Encode(jsonMap) +} + +// newTableFormatter returns a list formatter that formats entries in a table. +func newTableFormatter(writer io.Writer, tableWidth int, columns []tableColumn) *tableFormatter { + return &tableFormatter{ + writer: writer, + tableWidth: tableWidth, + columns: columns, + } +} + +type tableFormatter struct { + tableWidth int + writer io.Writer + computedColumnWidths []int + columns []tableColumn + headerPrinted bool +} + +// Write writes the given values formatted in a table with the configured column widths and names. +// The header of the table is printed on the first call, before any other value. +func (f *tableFormatter) Write(values []string) error { + if !f.headerPrinted { + header := make([]string, len(f.columns)) + for i, col := range f.columns { + header[i] = strings.ToUpper(col.name) + } + formattedHeader := f.formatRow(header) + _, err := f.writer.Write(formattedHeader) + if err != nil { + return err + } + f.headerPrinted = true + } + + formattedRow := f.formatRow(values) + _, err := f.writer.Write(formattedRow) + return err +} + +// formatRow formats the given table row to fit the configured width by +// giving each cell an equal width and wrapping the text in cells that exceed it. +func (f *tableFormatter) formatRow(row []string) []byte { + columnWidths := f.columnWidths() + grid := f.rowToGrid(row, columnWidths) + + strRes := strings.Builder{} + for _, row := range grid { + strRes.WriteString(strings.Join(row, " ") + "\n") + } + return []byte(strRes.String()) +} + +// rowToGrid returns a the given row split over a matrix in which all columns have equal length. +// Longer values are split over multiple cells and shorter (or empty) ones are padded with " ". +func (f *tableFormatter) rowToGrid(row []string, columnWidths []int) [][]string { + maxLinesPerCell := f.lineCount(row, columnWidths) + + grid := make([][]string, maxLinesPerCell) + for i := 0; i < maxLinesPerCell; i++ { + grid[i] = make([]string, len(row)) + } + + for i, cell := range row { + columnWidth := columnWidths[i] + lineCount := len(cell) / columnWidth + for j := 0; j < lineCount; j++ { + begin := j * columnWidth + end := (j + 1) * columnWidth + grid[j][i] = cell[begin:end] + } + + charactersLeft := len(cell) % columnWidth + if charactersLeft != 0 { + grid[lineCount][i] = cell[len(cell)-charactersLeft:] + strings.Repeat(" ", columnWidth-charactersLeft) + } else if lineCount < maxLinesPerCell { + grid[lineCount][i] = strings.Repeat(" ", columnWidth) + } + + for j := lineCount + 1; j < maxLinesPerCell; j++ { + grid[j][i] = strings.Repeat(" ", columnWidth) + } + } + + return grid +} + +// lineCount returns the number of lines the given table row will occupy after splitting the +// cell values that exceed their column width. +func (f *tableFormatter) lineCount(row []string, widths []int) int { + maxLinesPerCell := 1 + for i, value := range row { + lines := len(value) / widths[i] + if len(value)%widths[i] != 0 { + lines++ + } + if lines > maxLinesPerCell { + maxLinesPerCell = lines + } + } + return maxLinesPerCell +} + +// columnWidths returns the width of each column based on their maximum widths +// and the table width. +func (f *tableFormatter) columnWidths() []int { + if f.computedColumnWidths != nil { + return f.computedColumnWidths + } + res := make([]int, len(f.columns)) + + // Distribute the maximum width equally between all columns and repeatedly + // check if any of them have a smaller maximum width and can be shrunk. + // Stop when no columns can be further adjusted. + adjusted := true + columnsLeft := len(f.columns) + widthLeft := f.tableWidth - 2*(len(f.columns)-1) + widthPerColumn := widthLeft / columnsLeft + for adjusted { + adjusted = false + for i, col := range f.columns { + if res[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { + res[i] = col.maxWidth + widthLeft -= col.maxWidth + columnsLeft-- + adjusted = true + } + } + if columnsLeft == 0 { + for i := range res { + res[i] += widthLeft / len(res) + } + break + } + widthPerColumn = widthLeft / columnsLeft + } + + // distribute the remaining width equally between columns with no maximum width. + for i := range res { + if res[i] == 0 { + res[i] = widthPerColumn + } + } + f.computedColumnWidths = res + return res +} diff --git a/internals/secrethub/audit_test.go b/internals/secrethub/formatter_test.go similarity index 70% rename from internals/secrethub/audit_test.go rename to internals/secrethub/formatter_test.go index 932f2b12..c07945ff 100644 --- a/internals/secrethub/audit_test.go +++ b/internals/secrethub/formatter_test.go @@ -1,12 +1,9 @@ package secrethub import ( - "bytes" "strings" "testing" - "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" - "github.com/secrethub/secrethub-go/internals/assert" ) @@ -131,47 +128,3 @@ func Test_columnFormatter_formatRow(t *testing.T) { }) } } - -func TestFallbackPager_Write(t *testing.T) { - cases := map[string]struct { - pager *fallbackPager - param string - expected string - expectedErr error - }{ - "no lines left": { - pager: &fallbackPager{linesLeft: 0}, - expectedErr: errPagerNotFound, - param: "test\n", - expected: "", - }, - "last line": { - pager: &fallbackPager{linesLeft: 1}, - expectedErr: errPagerNotFound, - param: "test\n", - expected: "test\n", - }, - "print more": { - pager: &fallbackPager{linesLeft: 2}, - param: "test1\ntest2\ntest3\ntest4", - expected: "test1\ntest2\n", - expectedErr: errPagerNotFound, - }, - "more lines left": { - pager: &fallbackPager{linesLeft: 3}, - expectedErr: nil, - param: "test\n", - expected: "test\n", - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - buffer := bytes.Buffer{} - tc.pager.writer = &fakes.Pager{Buffer: &buffer} - _, err := tc.pager.Write([]byte(tc.param)) - assert.Equal(t, err, tc.expectedErr) - assert.Equal(t, buffer.String(), tc.expected) - }) - } -} diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go new file mode 100644 index 00000000..fda7efea --- /dev/null +++ b/internals/secrethub/pager/pager.go @@ -0,0 +1,159 @@ +package pager + +import ( + "bytes" + "errors" + "io" + "os" + "os/exec" + "syscall" +) + +const ( + pagerEnvvar = "$PAGER" + fallbackPagerLineCount = 100 +) + +var ErrPagerClosed = errors.New("cannot write to closed terminal pager") +var ErrPagerNotFound = errors.New("no terminal pager available. Please configure a terminal pager by setting the $PAGER environment variable or install \"less\" or \"more\"") + +// pager is a writer that is piped to a terminal pager command. +type pager struct { + writer io.WriteCloser + cmd *exec.Cmd + done <-chan struct{} + closed bool +} + +// New runs the terminal pager configured in the OS environment +// and returns a writer that is piped to the standard input of the pager command. +func New(outputWriter io.Writer) (io.WriteCloser, error) { + pagerCommand, err := pagerCommand() + if err != nil { + return nil, err + } + + cmd := exec.Command(pagerCommand) + + writer, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + + cmd.Stdout = outputWriter + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return nil, err + } + done := make(chan struct{}, 1) + go func() { + _ = cmd.Wait() + done <- struct{}{} + }() + return &pager{writer: writer, cmd: cmd, done: done}, nil +} + +// Write pipes the data to the terminal pager. +// It returns errPagerClosed if the terminal pager has been closed. +func (p *pager) Write(data []byte) (n int, err error) { + if p.isClosed() { + return 0, ErrPagerClosed + } + return p.writer.Write(data) +} + +// Close closes the writer to the terminal pager and waits for the terminal pager to close. +func (p *pager) Close() error { + err := p.writer.Close() + if err != nil { + return err + } + if p.closed { + return nil + } + err = p.cmd.Process.Signal(syscall.SIGINT) + if err != nil { + err = p.cmd.Process.Kill() + if err != nil { + return err + } + } + <-p.done + return nil +} + +// isClosed checks if the terminal pager process has been stopped. +func (p *pager) isClosed() bool { + if p.closed { + return true + } + select { + case <-p.done: + p.closed = true + return true + default: + return false + } +} + +// pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). +// If no pager is configured it falls back to "less" than "more", returning an error if neither are available. +func pagerCommand() (string, error) { + var pager string + var err error + + pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)) + if err == nil { + return pager, nil + } + + pager, err = exec.LookPath("less") + if err == nil { + return pager, nil + } + + pager, err = exec.LookPath("more") + if err == nil { + return pager, nil + } + + return "", ErrPagerNotFound +} + +// newFallbackPaginatedWriter returns a pager that closes after outputting a fixed number of lines without pagination +// and returns errPagerNotFound on the last (or any subsequent) write. +func NewFallbackPager(w io.WriteCloser) io.WriteCloser { + return &fallbackPager{ + linesLeft: fallbackPagerLineCount, + writer: w, + } +} + +type fallbackPager struct { + writer io.WriteCloser + linesLeft int +} + +func (p *fallbackPager) Write(data []byte) (int, error) { + if p.linesLeft == 0 { + return 0, ErrPagerNotFound + } + + lines := bytes.Count(data, []byte{'\n'}) + if lines > p.linesLeft { + data = bytes.Join(bytes.Split(data, []byte{'\n'})[:p.linesLeft], []byte{'\n'}) + data = append(data, '\n') + } + p.linesLeft -= bytes.Count(data, []byte{'\n'}) + n, err := p.writer.Write(data) + if p.linesLeft == 0 { + err = ErrPagerNotFound + } + return n, err +} + +func (p *fallbackPager) Close() error { + return p.writer.Close() +} diff --git a/internals/secrethub/pager/pager_test.go b/internals/secrethub/pager/pager_test.go new file mode 100644 index 00000000..ca14a185 --- /dev/null +++ b/internals/secrethub/pager/pager_test.go @@ -0,0 +1,53 @@ +package pager + +import ( + "bytes" + "testing" + + "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" + "gotest.tools/assert" +) + +func TestFallbackPager_Write(t *testing.T) { + cases := map[string]struct { + pager *fallbackPager + param string + expected string + expectedErr error + }{ + "no lines left": { + pager: &fallbackPager{linesLeft: 0}, + expectedErr: ErrPagerNotFound, + param: "test\n", + expected: "", + }, + "last line": { + pager: &fallbackPager{linesLeft: 1}, + expectedErr: ErrPagerNotFound, + param: "test\n", + expected: "test\n", + }, + "print more": { + pager: &fallbackPager{linesLeft: 2}, + param: "test1\ntest2\ntest3\ntest4", + expected: "test1\ntest2\n", + expectedErr: ErrPagerNotFound, + }, + "more lines left": { + pager: &fallbackPager{linesLeft: 3}, + expectedErr: nil, + param: "test\n", + expected: "test\n", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + buffer := bytes.Buffer{} + tc.pager.writer = &fakes.Pager{Buffer: &buffer} + _, err := tc.pager.Write([]byte(tc.param)) + assert.Equal(t, err, tc.expectedErr) + assert.Equal(t, buffer.String(), tc.expected) + }) + } +} From 5e9ba999e4bc9ca44e6f15ae6d5d60be8fadefba Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 12:02:36 +0200 Subject: [PATCH 047/119] Improve commenting of table column width calculation --- internals/secrethub/formatter.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/internals/secrethub/formatter.go b/internals/secrethub/formatter.go index ec54eba1..d15b64c5 100644 --- a/internals/secrethub/formatter.go +++ b/internals/secrethub/formatter.go @@ -153,40 +153,41 @@ func (f *tableFormatter) columnWidths() []int { if f.computedColumnWidths != nil { return f.computedColumnWidths } - res := make([]int, len(f.columns)) + fixedWidths := make([]int, len(f.columns)) - // Distribute the maximum width equally between all columns and repeatedly - // check if any of them have a smaller maximum width and can be shrunk. - // Stop when no columns can be further adjusted. - adjusted := true + // Distribute the table width equally between all columns and leave a margin of 2 characters between them. columnsLeft := len(f.columns) widthLeft := f.tableWidth - 2*(len(f.columns)-1) widthPerColumn := widthLeft / columnsLeft + adjusted := true for adjusted { adjusted = false for i, col := range f.columns { - if res[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { - res[i] = col.maxWidth + // fix columns that have a smaller maximum width than the current width/column and have not been fixed yet. + if fixedWidths[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { + fixedWidths[i] = col.maxWidth widthLeft -= col.maxWidth columnsLeft-- adjusted = true } } + // If all columns are fixed to their max width, distribute the remaining width equally between all of them. if columnsLeft == 0 { - for i := range res { - res[i] += widthLeft / len(res) + for i := range fixedWidths { + fixedWidths[i] += widthLeft / len(fixedWidths) } break } + // Recalculate the width/column for the remaining unadjusted columns. widthPerColumn = widthLeft / columnsLeft } // distribute the remaining width equally between columns with no maximum width. - for i := range res { - if res[i] == 0 { - res[i] = widthPerColumn + for i := range fixedWidths { + if fixedWidths[i] == 0 { + fixedWidths[i] = widthPerColumn } } - f.computedColumnWidths = res - return res + f.computedColumnWidths = fixedWidths + return fixedWidths } From 4e00062ae0c04ef8f8b5b42df498b8b3ea2b6d0a Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 12:06:27 +0200 Subject: [PATCH 048/119] Refactor pager program checks --- internals/secrethub/pager/pager.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go index fda7efea..b790c7bc 100644 --- a/internals/secrethub/pager/pager.go +++ b/internals/secrethub/pager/pager.go @@ -104,18 +104,15 @@ func pagerCommand() (string, error) { var pager string var err error - pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)) - if err == nil { + if pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)); err == nil { return pager, nil } - pager, err = exec.LookPath("less") - if err == nil { + if pager, err = exec.LookPath("less"); err == nil { return pager, nil } - pager, err = exec.LookPath("more") - if err == nil { + if pager, err = exec.LookPath("more"); err == nil { return pager, nil } From c9d4d0bfdbc674f3c1c368cf3a7c22cb73247cac Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 12:15:08 +0200 Subject: [PATCH 049/119] Add error code to displayed error --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 28677c3b..4049d36b 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -20,7 +20,7 @@ import ( ) var ( - errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format) } + errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format + " (audit.invalid_format)") } ) const ( From ee5135c478bbd069618f933af882001951c961f0 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 15:18:55 +0200 Subject: [PATCH 050/119] Refactor terminal pager command logic --- internals/secrethub/pager/pager.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go index b790c7bc..4f657925 100644 --- a/internals/secrethub/pager/pager.go +++ b/internals/secrethub/pager/pager.go @@ -101,18 +101,15 @@ func (p *pager) isClosed() bool { // pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). // If no pager is configured it falls back to "less" than "more", returning an error if neither are available. func pagerCommand() (string, error) { - var pager string - var err error - - if pager, err = exec.LookPath(os.ExpandEnv(pagerEnvvar)); err == nil { + if pager, err := exec.LookPath(os.ExpandEnv(pagerEnvvar)); err == nil { return pager, nil } - if pager, err = exec.LookPath("less"); err == nil { + if pager, err := exec.LookPath("less"); err == nil { return pager, nil } - if pager, err = exec.LookPath("more"); err == nil { + if pager, err := exec.LookPath("more"); err == nil { return pager, nil } From c9d6d03c25424d482bf1da70e1ed2384f1071197 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 15:29:16 +0200 Subject: [PATCH 051/119] Use errio for error formatting --- internals/secrethub/audit.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 4049d36b..057a1a9f 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -1,11 +1,12 @@ package secrethub import ( - "errors" "fmt" "io" "os" + "github.com/secrethub/secrethub-go/internals/errio" + "github.com/secrethub/secrethub-cli/internals/secrethub/pager" "golang.org/x/crypto/ssh/terminal" @@ -20,7 +21,8 @@ import ( ) var ( - errNoSuchFormat = func(format string) error { return errors.New("invalid format: " + format + " (audit.invalid_format)") } + errAudit = errio.Namespace("audit") + errNoSuchFormat = errAudit.Code("invalid_format").ErrorPref("invalid format: %s") ) const ( From 024dcfa7523a641184ec46c0dc4f3952e9ee4a55 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 15:30:20 +0200 Subject: [PATCH 052/119] Rename fixedWidths to adjustedWidths --- internals/secrethub/formatter.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internals/secrethub/formatter.go b/internals/secrethub/formatter.go index d15b64c5..f11d1377 100644 --- a/internals/secrethub/formatter.go +++ b/internals/secrethub/formatter.go @@ -153,7 +153,7 @@ func (f *tableFormatter) columnWidths() []int { if f.computedColumnWidths != nil { return f.computedColumnWidths } - fixedWidths := make([]int, len(f.columns)) + adjustedWidths := make([]int, len(f.columns)) // Distribute the table width equally between all columns and leave a margin of 2 characters between them. columnsLeft := len(f.columns) @@ -164,8 +164,8 @@ func (f *tableFormatter) columnWidths() []int { adjusted = false for i, col := range f.columns { // fix columns that have a smaller maximum width than the current width/column and have not been fixed yet. - if fixedWidths[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { - fixedWidths[i] = col.maxWidth + if adjustedWidths[i] == 0 && col.maxWidth != 0 && col.maxWidth < widthPerColumn { + adjustedWidths[i] = col.maxWidth widthLeft -= col.maxWidth columnsLeft-- adjusted = true @@ -173,8 +173,8 @@ func (f *tableFormatter) columnWidths() []int { } // If all columns are fixed to their max width, distribute the remaining width equally between all of them. if columnsLeft == 0 { - for i := range fixedWidths { - fixedWidths[i] += widthLeft / len(fixedWidths) + for i := range adjustedWidths { + adjustedWidths[i] += widthLeft / len(adjustedWidths) } break } @@ -183,11 +183,11 @@ func (f *tableFormatter) columnWidths() []int { } // distribute the remaining width equally between columns with no maximum width. - for i := range fixedWidths { - if fixedWidths[i] == 0 { - fixedWidths[i] = widthPerColumn + for i := range adjustedWidths { + if adjustedWidths[i] == 0 { + adjustedWidths[i] = widthPerColumn } } - f.computedColumnWidths = fixedWidths - return fixedWidths + f.computedColumnWidths = adjustedWidths + return adjustedWidths } From 3ab60f96e88eedb5ef8496bf0f5b386d03fdf3b1 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 15:31:48 +0200 Subject: [PATCH 053/119] Rename formatter.go to list_formatters.go --- internals/secrethub/{formatter.go => list_formatters.go} | 0 .../secrethub/{formatter_test.go => list_formatters_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename internals/secrethub/{formatter.go => list_formatters.go} (100%) rename internals/secrethub/{formatter_test.go => list_formatters_test.go} (100%) diff --git a/internals/secrethub/formatter.go b/internals/secrethub/list_formatters.go similarity index 100% rename from internals/secrethub/formatter.go rename to internals/secrethub/list_formatters.go diff --git a/internals/secrethub/formatter_test.go b/internals/secrethub/list_formatters_test.go similarity index 100% rename from internals/secrethub/formatter_test.go rename to internals/secrethub/list_formatters_test.go From 9377a5f88f7f6dad853be3d12fb01eb92faf8f02 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 6 Apr 2020 15:41:07 +0200 Subject: [PATCH 054/119] Rename rowToGrid to fitToColumns --- internals/secrethub/list_formatters.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internals/secrethub/list_formatters.go b/internals/secrethub/list_formatters.go index f11d1377..13fd3098 100644 --- a/internals/secrethub/list_formatters.go +++ b/internals/secrethub/list_formatters.go @@ -88,7 +88,7 @@ func (f *tableFormatter) Write(values []string) error { // giving each cell an equal width and wrapping the text in cells that exceed it. func (f *tableFormatter) formatRow(row []string) []byte { columnWidths := f.columnWidths() - grid := f.rowToGrid(row, columnWidths) + grid := f.fitToColumns(row, columnWidths) strRes := strings.Builder{} for _, row := range grid { @@ -97,17 +97,17 @@ func (f *tableFormatter) formatRow(row []string) []byte { return []byte(strRes.String()) } -// rowToGrid returns a the given row split over a matrix in which all columns have equal length. +// fitToColumns returns a the given row split over a matrix in which all columns have equal length. // Longer values are split over multiple cells and shorter (or empty) ones are padded with " ". -func (f *tableFormatter) rowToGrid(row []string, columnWidths []int) [][]string { - maxLinesPerCell := f.lineCount(row, columnWidths) +func (f *tableFormatter) fitToColumns(cells []string, columnWidths []int) [][]string { + maxLinesPerCell := f.lineCount(cells, columnWidths) grid := make([][]string, maxLinesPerCell) for i := 0; i < maxLinesPerCell; i++ { - grid[i] = make([]string, len(row)) + grid[i] = make([]string, len(cells)) } - for i, cell := range row { + for i, cell := range cells { columnWidth := columnWidths[i] lineCount := len(cell) / columnWidth for j := 0; j < lineCount; j++ { From 5645fc435055dc466a657cbf44bf755ec888a495 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 7 Apr 2020 13:00:30 +0200 Subject: [PATCH 055/119] Move fallback pager creation to pager package --- internals/secrethub/audit.go | 6 ++---- internals/secrethub/pager/pager.go | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 057a1a9f..2056d3fe 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -48,7 +48,7 @@ type AuditCommand struct { func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { return &AuditCommand{ io: io, - newPaginatedWriter: pager.New, + newPaginatedWriter: pager.NewWithFallback, newClient: newClient, terminalWidth: func(fd int) (int, error) { w, _, err := terminal.GetSize(fd) @@ -91,9 +91,7 @@ func (cmd *AuditCommand) run() error { } paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) - if err == pager.ErrPagerNotFound { - paginatedWriter = pager.NewFallbackPager(os.Stdout) - } else if err != nil { + if err != nil { return err } defer paginatedWriter.Close() diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go index 4f657925..a4972786 100644 --- a/internals/secrethub/pager/pager.go +++ b/internals/secrethub/pager/pager.go @@ -25,6 +25,16 @@ type pager struct { closed bool } +func NewWithFallback(outputWriter io.Writer) (io.WriteCloser, error) { + pager, err := New(outputWriter) + if err == ErrPagerNotFound { + return NewFallbackPager(outputWriter), nil + } else if err != nil { + return nil, err + } + return pager, nil +} + // New runs the terminal pager configured in the OS environment // and returns a writer that is piped to the standard input of the pager command. func New(outputWriter io.Writer) (io.WriteCloser, error) { @@ -118,7 +128,7 @@ func pagerCommand() (string, error) { // newFallbackPaginatedWriter returns a pager that closes after outputting a fixed number of lines without pagination // and returns errPagerNotFound on the last (or any subsequent) write. -func NewFallbackPager(w io.WriteCloser) io.WriteCloser { +func NewFallbackPager(w io.Writer) io.WriteCloser { return &fallbackPager{ linesLeft: fallbackPagerLineCount, writer: w, @@ -126,7 +136,7 @@ func NewFallbackPager(w io.WriteCloser) io.WriteCloser { } type fallbackPager struct { - writer io.WriteCloser + writer io.Writer linesLeft int } @@ -149,5 +159,5 @@ func (p *fallbackPager) Write(data []byte) (int, error) { } func (p *fallbackPager) Close() error { - return p.writer.Close() + return nil } From 443977fa160339940b9d7a3550e9a4e4d12177d7 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 7 Apr 2020 13:01:35 +0200 Subject: [PATCH 056/119] Fix typo --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 2056d3fe..cd77c8db 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -248,7 +248,7 @@ func (table secretAuditTable) row(event api.Audit) ([]string, error) { func newRepoAuditTable(tree *api.Tree, timeFormatter TimeFormatter) repoAuditTable { return repoAuditTable{ - baseAuditTable: newBaseAuditTable(timeFormatter, tableColumn{name: "EVENT SUBJECT"}), + baseAuditTable: newBaseAuditTable(timeFormatter, tableColumn{name: "event subject"}), tree: tree, } } From c03f5d1eecf2e39349a0f571dd5d0ef87a1c6856 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 7 Apr 2020 13:11:41 +0200 Subject: [PATCH 057/119] Implement morea overriding less --- internals/secrethub/pager/pager.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go index a4972786..bc035796 100644 --- a/internals/secrethub/pager/pager.go +++ b/internals/secrethub/pager/pager.go @@ -10,6 +10,7 @@ import ( ) const ( + secrethubPagerEnvvar = "$SECRETHUB_PAGER" pagerEnvvar = "$PAGER" fallbackPagerLineCount = 100 ) @@ -108,9 +109,14 @@ func (p *pager) isClosed() bool { } } -// pagerCommand returns the name of the terminal pager configured in the OS environment ($PAGER). +// pagerCommand returns the name of the terminal pager configured in OS environment. +// It first checks the $SECRETHUB_PAGER environment variable and if it is not set to a valid pager it checks $PAGER. // If no pager is configured it falls back to "less" than "more", returning an error if neither are available. func pagerCommand() (string, error) { + if pager, err := exec.LookPath(os.ExpandEnv(secrethubPagerEnvvar)); err == nil { + return pager, nil + } + if pager, err := exec.LookPath(os.ExpandEnv(pagerEnvvar)); err == nil { return pager, nil } From 6a3dec93d87b966a94f961bef9073a993ee78583 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 15 Apr 2020 13:10:51 +0200 Subject: [PATCH 058/119] Use ui.io.Stdout() as pager stdout --- internals/secrethub/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index cd77c8db..4eb3605f 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -90,7 +90,7 @@ func (cmd *AuditCommand) run() error { return err } - paginatedWriter, err := cmd.newPaginatedWriter(os.Stdout) + paginatedWriter, err := cmd.newPaginatedWriter(cmd.io.Stdout()) if err != nil { return err } From 634d4a099b19b376ef6e69e73c631e5c6abcdac9 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 15 Apr 2020 13:23:14 +0200 Subject: [PATCH 059/119] Fix tests --- internals/secrethub/audit_repo_test.go | 3 +++ internals/secrethub/audit_secret_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index be223594..eefdd15f 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -233,6 +235,7 @@ func TestAuditRepoCommand_run(t *testing.T) { tc.cmd.newPaginatedWriter = func(_ io.Writer) (io.WriteCloser, error) { return &fakes.Pager{Buffer: &buffer}, nil } + tc.cmd.io = ui.NewFakeIO() // Act err := tc.cmd.run() diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index d4c28954..91e20439 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -198,6 +200,8 @@ func TestAuditSecretCommand_run(t *testing.T) { tc.cmd.newPaginatedWriter = func(_ io.Writer) (io.WriteCloser, error) { return &fakes.Pager{Buffer: &buffer}, nil } + tc.cmd.io = ui.NewFakeIO() + // Act err := tc.cmd.run() From 0cca7343db5e64158b369db0be487eb30ff98a1e Mon Sep 17 00:00:00 2001 From: Marc Mackenbach Date: Fri, 24 Apr 2020 14:12:21 +0200 Subject: [PATCH 060/119] Remove unused file struct from io_windows.go It was leftover from the ui package refactor. --- internals/cli/ui/io_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 7006b01e..545c49cf 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -14,7 +14,7 @@ func NewUserIO() UserIO { // Ensure colors are printed correctly on Windows. if !color.NoColor { return UserIO{ - Input: file{os.Stdin}, + Input: os.Stdin, Output: colorStdout{colorable.NewColorableStdout()}, } } From 3653627cf09944dac4ac916faa256872ceb26a7c Mon Sep 17 00:00:00 2001 From: Marc Mackenbach Date: Fri, 24 Apr 2020 14:14:02 +0200 Subject: [PATCH 061/119] Make isPiped work on windows and non-windows --- internals/cli/ui/io.go | 11 ----------- internals/cli/ui/io_unix.go | 11 +++++++++++ internals/cli/ui/io_windows.go | 12 ++++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 5cbd110b..f4a8b479 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -104,17 +104,6 @@ func Readln(r io.Reader) (string, error) { return s.Text(), nil } -// isPiped checks whether the file is a pipe. -// If the file does not exist, it returns false. -func isPiped(file *os.File) bool { - stat, err := file.Stat() - if err != nil { - return false - } - - return (stat.Mode() & os.ModeCharDevice) == 0 -} - // EOFKey returns the key that should be pressed to enter an EOF. // This can be used to end multiline input. func EOFKey() string { diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index acbcdacf..72fd3f6e 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -19,6 +19,17 @@ func NewUserIO() UserIO { return NewStdUserIO() } +// isPiped checks whether the file is a pipe. +// If the file does not exist, it returns false. +func isPiped(file *os.File) bool { + stat, err := file.Stat() + if err != nil { + return false + } + + return (stat.Mode() & os.ModeCharDevice) == 0 +} + func eofKey() string { return "CTRL-D" } diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 545c49cf..2afe3c3a 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -34,3 +34,15 @@ func (c colorStdout) IsPiped() bool { func eofKey() string { return "CTRL-Z + ENTER" } + +// isPiped checks whether the file is a pipe. +// If the file does not exist, it returns false. +func isPiped(file *os.File) bool { + stat, err := file.Stat() + if err != nil { + return false + } + + return os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(file.Fd()) && !isatty.IsCygwinTerminal(file.Fd())) +} From e4641210ce3f4734ccebfd3038921a56513662fc Mon Sep 17 00:00:00 2001 From: Marc Mackenbach Date: Fri, 24 Apr 2020 14:49:57 +0200 Subject: [PATCH 062/119] Remove windows specific colorable stdout wrapper It's no longer needed now that the isPiped logic is moved to a helper function. --- internals/cli/ui/io_windows.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 2afe3c3a..99644710 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -1,7 +1,6 @@ package ui import ( - "io" "os" "github.com/fatih/color" @@ -15,22 +14,13 @@ func NewUserIO() UserIO { if !color.NoColor { return UserIO{ Input: os.Stdin, - Output: colorStdout{colorable.NewColorableStdout()}, + Output: colorable.NewColorable(os.Stdout), } } return NewStdUserIO() } -type colorStdout struct { - io.Writer -} - -func (c colorStdout) IsPiped() bool { - return os.Getenv("TERM") == "dumb" || - (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) -} - func eofKey() string { return "CTRL-Z + ENTER" } From 1b5e4e2f161966a84072f4eb77d15b0a4d50b08b Mon Sep 17 00:00:00 2001 From: Marc Mackenbach Date: Fri, 24 Apr 2020 14:51:35 +0200 Subject: [PATCH 063/119] Add godoc --- internals/cli/ui/io_unix.go | 1 + internals/cli/ui/io_windows.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 72fd3f6e..4a346089 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -30,6 +30,7 @@ func isPiped(file *os.File) bool { return (stat.Mode() & os.ModeCharDevice) == 0 } +// eofKey returns the key(s) that should be pressed to enter an EOF. func eofKey() string { return "CTRL-D" } diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 99644710..4a5137fd 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -21,6 +21,7 @@ func NewUserIO() UserIO { return NewStdUserIO() } +// eofKey returns the key(s) that should be pressed to enter an EOF. func eofKey() string { return "CTRL-Z + ENTER" } From 5af427ec1d628a9c083ab3aabf443b644281c81b Mon Sep 17 00:00:00 2001 From: Marc Mackenbach Date: Fri, 24 Apr 2020 15:01:45 +0200 Subject: [PATCH 064/119] Make Input and Output private fields They aren't used outside the package and have accessor functions so there's no need for them to be public. --- internals/cli/ui/io.go | 18 +++++++++--------- internals/cli/ui/io_unix.go | 4 ++-- internals/cli/ui/io_windows.go | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index f4a8b479..4653df4a 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -28,8 +28,8 @@ type IO interface { // UserIO is a middleware between input and output to the CLI program. // It implements userIO.Prompter and can be passed to libraries. type UserIO struct { - Input *os.File - Output *os.File + input *os.File + output *os.File tty *os.File ttyAvailable bool } @@ -37,19 +37,19 @@ type UserIO struct { // NewStdUserIO creates a new UserIO middleware only from os.Stdin and os.Stdout. func NewStdUserIO() UserIO { return UserIO{ - Input: os.Stdin, - Output: os.Stdout, + input: os.Stdin, + output: os.Stdout, } } // Stdin returns the UserIO's Input. func (o UserIO) Stdin() io.Reader { - return o.Input + return o.input } // Stdout returns the UserIO's Output. func (o UserIO) Stdout() io.Writer { - return o.Output + return o.output } // Prompts simply returns Stdin and Stdout, when both input and output are @@ -64,15 +64,15 @@ func (o UserIO) Prompts() (io.Reader, io.Writer, error) { } return nil, nil, ErrCannotAsk } - return o.Input, o.Output, nil + return o.input, o.output, nil } func (o UserIO) IsStdinPiped() bool { - return isPiped(o.Input) + return isPiped(o.input) } func (o UserIO) IsStdoutPiped() bool { - return isPiped(o.Output) + return isPiped(o.output) } // readPassword reads one line of input from the terminal without echoing the user input. diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 4a346089..bc896563 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -9,8 +9,8 @@ func NewUserIO() UserIO { tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err == nil { return UserIO{ - Input: os.Stdin, - Output: os.Stdout, + input: os.Stdin, + output: os.Stdout, tty: tty, ttyAvailable: true, } diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 4a5137fd..65d1242e 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -13,8 +13,8 @@ func NewUserIO() UserIO { // Ensure colors are printed correctly on Windows. if !color.NoColor { return UserIO{ - Input: os.Stdin, - Output: colorable.NewColorable(os.Stdout), + input: os.Stdin, + output: colorable.NewColorable(os.Stdout), } } From b8bbfe714b11fe4610667a4d24739c9384435dbe Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:00:20 +0200 Subject: [PATCH 065/119] Move differences in implementation to separate structs It is weird to reuse the same struct for different implementations of an interface, while it is also possible to give them all a separate struct. This is way cleaner. --- internals/cli/ui/io.go | 35 +++++++++---------- internals/cli/ui/io_unix.go | 61 +++++++++++++++++++++++++++------- internals/cli/ui/io_windows.go | 28 ++++++++++------ 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 4653df4a..8ef1b9b1 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -25,30 +25,28 @@ type IO interface { IsStdoutPiped() bool } -// UserIO is a middleware between input and output to the CLI program. -// It implements userIO.Prompter and can be passed to libraries. -type UserIO struct { - input *os.File - output *os.File - tty *os.File - ttyAvailable bool +// standardIO is a middleware between input and output to the CLI program. +// It implements standardIO.Prompter and can be passed to libraries. +type standardIO struct { + input *os.File + output *os.File } -// NewStdUserIO creates a new UserIO middleware only from os.Stdin and os.Stdout. -func NewStdUserIO() UserIO { - return UserIO{ +// newStdUserIO creates a new standardIO middleware only from os.Stdin and os.Stdout. +func newStdUserIO() standardIO { + return standardIO{ input: os.Stdin, output: os.Stdout, } } -// Stdin returns the UserIO's Input. -func (o UserIO) Stdin() io.Reader { +// Stdin returns the standardIO's Input. +func (o standardIO) Stdin() io.Reader { return o.input } -// Stdout returns the UserIO's Output. -func (o UserIO) Stdout() io.Writer { +// Stdout returns the standardIO's Output. +func (o standardIO) Stdout() io.Writer { return o.output } @@ -57,21 +55,18 @@ func (o UserIO) Stdout() io.Writer { // bypass stdin and stdout by connecting to /dev/tty on Unix systems when // available. On systems where tty is not available and when either input // or output is piped, prompting is not possible so an error is returned. -func (o UserIO) Prompts() (io.Reader, io.Writer, error) { +func (o standardIO) Prompts() (io.Reader, io.Writer, error) { if o.IsStdoutPiped() || o.IsStdinPiped() { - if o.ttyAvailable { - return o.tty, o.tty, nil - } return nil, nil, ErrCannotAsk } return o.input, o.output, nil } -func (o UserIO) IsStdinPiped() bool { +func (o standardIO) IsStdinPiped() bool { return isPiped(o.input) } -func (o UserIO) IsStdoutPiped() bool { +func (o standardIO) IsStdoutPiped() bool { return isPiped(o.output) } diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index bc896563..0206057a 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -2,21 +2,60 @@ package ui -import "os" +import ( + "io" + "os" +) -// NewUserIO creates a new UserIO middleware from os.Stdin and os.Stdout and adds tty if it is available. -func NewUserIO() UserIO { +// ttyIO is the implementation of the IO interface that can use a TTY. +type ttyIO struct { + input *os.File + output *os.File + tty *os.File +} + +// NewUserIO creates a new ttyIO if a TTY is available, otherwise it returns a standardIO. +func NewUserIO() IO { tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err == nil { - return UserIO{ - input: os.Stdin, - output: os.Stdout, - tty: tty, - ttyAvailable: true, + return ttyIO{ + input: os.Stdin, + output: os.Stdout, + tty: tty, } } - return NewStdUserIO() + return newStdUserIO() +} + +// Prompts simply returns Stdin and Stdout, when both input and output are +// not piped. When either input or output is piped, Prompts attempts to +// bypass stdin and stdout by connecting to /dev/tty on Unix systems when +// available. On systems where tty is not available and when either input +// or output is piped, prompting is not possible so an error is returned. +func (o ttyIO) Prompts() (io.Reader, io.Writer, error) { + if o.IsStdoutPiped() || o.IsStdinPiped() { + return o.tty, o.tty, nil + } + return o.input, o.output, nil +} + +func (o ttyIO) IsStdinPiped() bool { + return isPiped(o.input) +} + +func (o ttyIO) IsStdoutPiped() bool { + return isPiped(o.output) +} + +// Stdin returns the standardIO's Input. +func (o ttyIO) Stdin() io.Reader { + return o.input +} + +// Stdout returns the standardIO's Output. +func (o ttyIO) Stdout() io.Writer { + return o.output } // isPiped checks whether the file is a pipe. @@ -28,9 +67,7 @@ func isPiped(file *os.File) bool { } return (stat.Mode() & os.ModeCharDevice) == 0 -} - -// eofKey returns the key(s) that should be pressed to enter an EOF. +} // eofKey returns the key(s) that should be pressed to enter an EOF. func eofKey() string { return "CTRL-D" } diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index 65d1242e..b7e86b85 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -1,6 +1,7 @@ package ui import ( + "io" "os" "github.com/fatih/color" @@ -8,17 +9,24 @@ import ( isatty "github.com/mattn/go-isatty" ) -// NewUserIO creates a new UserIO middleware from os.Stdin and os.Stdout and adds tty if it is available. -func NewUserIO() UserIO { - // Ensure colors are printed correctly on Windows. - if !color.NoColor { - return UserIO{ - input: os.Stdin, - output: colorable.NewColorable(os.Stdout), - } +// windowsIO is the Windows-specific implementation of the IO interface. +type windowsIO struct { + standardIO +} + +// NewUserIO creates a new windowsIO. +func NewUserIO() IO { + return windowsIO{ + standardIO: newStdUserIO(), } +} - return NewStdUserIO() +// Stdout returns the standardIO's Output. +func (o windowsIO) Stdout() io.Writer { + if !color.NoColor { + return colorable.NewColorable(os.Stdout) + } + return o.output } // eofKey returns the key(s) that should be pressed to enter an EOF. @@ -29,7 +37,7 @@ func eofKey() string { // isPiped checks whether the file is a pipe. // If the file does not exist, it returns false. func isPiped(file *os.File) bool { - stat, err := file.Stat() + _, err := file.Stat() if err != nil { return false } From f06aad7e361f2d7a0982eccee3220b2cd6f915eb Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:12:23 +0200 Subject: [PATCH 066/119] Rename Stdin and Stdout to Input and Output These names better describe the abstraction. Stdin and Stdout describe the implementation, not the interface. --- internals/cli/ui/io.go | 8 ++--- internals/cli/ui/io_unix.go | 4 +-- internals/cli/ui/testing.go | 4 +-- internals/secrethub/account_email_verify.go | 6 ++-- internals/secrethub/account_init.go | 34 +++++++++---------- internals/secrethub/account_inspect.go | 2 +- internals/secrethub/acl_check.go | 6 ++-- internals/secrethub/acl_list.go | 2 +- internals/secrethub/acl_rm.go | 6 ++-- internals/secrethub/acl_set.go | 6 ++-- internals/secrethub/audit.go | 2 +- internals/secrethub/clear.go | 4 +-- .../secrethub/config_update_passphrase.go | 4 +-- internals/secrethub/credential_backup.go | 6 ++-- internals/secrethub/credential_disable.go | 6 ++-- internals/secrethub/credential_list.go | 2 +- internals/secrethub/env_ls.go | 2 +- internals/secrethub/env_read.go | 2 +- internals/secrethub/generate.go | 4 +-- internals/secrethub/init.go | 8 ++--- internals/secrethub/inject.go | 10 +++--- internals/secrethub/inspect_secret.go | 2 +- internals/secrethub/inspect_secret_version.go | 2 +- internals/secrethub/list.go | 6 ++-- internals/secrethub/mkdir.go | 2 +- internals/secrethub/org_init.go | 8 ++--- internals/secrethub/org_inspect.go | 2 +- internals/secrethub/org_invite.go | 6 ++-- internals/secrethub/org_list_users.go | 2 +- internals/secrethub/org_ls.go | 4 +-- internals/secrethub/org_purchase.go | 4 +-- internals/secrethub/org_revoke.go | 20 +++++------ internals/secrethub/org_rm.go | 6 ++-- internals/secrethub/org_set_role.go | 4 +-- internals/secrethub/printenv.go | 2 +- internals/secrethub/read.go | 4 +-- internals/secrethub/repo_export.go | 2 +- internals/secrethub/repo_init.go | 4 +-- internals/secrethub/repo_inspect.go | 2 +- internals/secrethub/repo_invite.go | 6 ++-- internals/secrethub/repo_ls.go | 4 +-- internals/secrethub/repo_revoke.go | 12 +++---- internals/secrethub/repo_rm.go | 6 ++-- internals/secrethub/rm.go | 8 ++--- internals/secrethub/run.go | 4 +-- internals/secrethub/service_aws_init.go | 12 +++---- internals/secrethub/service_deploy_winrm.go | 10 +++--- internals/secrethub/service_init.go | 6 ++-- internals/secrethub/service_ls.go | 4 +-- internals/secrethub/set.go | 6 ++-- internals/secrethub/signup.go | 24 ++++++------- internals/secrethub/tree.go | 2 +- internals/secrethub/write.go | 6 ++-- 53 files changed, 160 insertions(+), 160 deletions(-) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 8ef1b9b1..4a1c46a0 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -18,8 +18,8 @@ var ( // IO is an interface to work with input/output. type IO interface { - Stdin() io.Reader - Stdout() io.Writer + Input() io.Reader + Output() io.Writer Prompts() (io.Reader, io.Writer, error) IsStdinPiped() bool IsStdoutPiped() bool @@ -41,12 +41,12 @@ func newStdUserIO() standardIO { } // Stdin returns the standardIO's Input. -func (o standardIO) Stdin() io.Reader { +func (o standardIO) Input() io.Reader { return o.input } // Stdout returns the standardIO's Output. -func (o standardIO) Stdout() io.Writer { +func (o standardIO) Output() io.Writer { return o.output } diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 0206057a..9572e68d 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -49,12 +49,12 @@ func (o ttyIO) IsStdoutPiped() bool { } // Stdin returns the standardIO's Input. -func (o ttyIO) Stdin() io.Reader { +func (o ttyIO) Input() io.Reader { return o.input } // Stdout returns the standardIO's Output. -func (o ttyIO) Stdout() io.Writer { +func (o ttyIO) Output() io.Writer { return o.output } diff --git a/internals/cli/ui/testing.go b/internals/cli/ui/testing.go index 0c16f8e8..fd44e7ac 100644 --- a/internals/cli/ui/testing.go +++ b/internals/cli/ui/testing.go @@ -36,12 +36,12 @@ func NewFakeIO() *FakeIO { } // Stdin returns the mocked StdIn. -func (f *FakeIO) Stdin() io.Reader { +func (f *FakeIO) Input() io.Reader { return f.StdIn } // Stdout returns the mocked StdOut. -func (f *FakeIO) Stdout() io.Writer { +func (f *FakeIO) Output() io.Writer { return f.StdOut } diff --git a/internals/secrethub/account_email_verify.go b/internals/secrethub/account_email_verify.go index 8dc6fa70..0181274c 100644 --- a/internals/secrethub/account_email_verify.go +++ b/internals/secrethub/account_email_verify.go @@ -44,7 +44,7 @@ func (cmd *AccountEmailVerifyCommand) Run() error { } if user.EmailVerified { - fmt.Fprintln(cmd.io.Stdout(), "Your email address is already verified.") + fmt.Fprintln(cmd.io.Output(), "Your email address is already verified.") return nil } @@ -53,9 +53,9 @@ func (cmd *AccountEmailVerifyCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "An email has been sent to %s with an email verification link. Please check your mail and click the link.\n\n", user.Email) + fmt.Fprintf(cmd.io.Output(), "An email has been sent to %s with an email verification link. Please check your mail and click the link.\n\n", user.Email) - fmt.Fprintf(cmd.io.Stdout(), "Please contact support@secrethub.io if the problem persists.\n\n") + fmt.Fprintf(cmd.io.Output(), "Please contact support@secrethub.io if the problem persists.\n\n") return nil } diff --git a/internals/secrethub/account_init.go b/internals/secrethub/account_init.go index c39f086a..7b187b12 100644 --- a/internals/secrethub/account_init.go +++ b/internals/secrethub/account_init.go @@ -52,7 +52,7 @@ func NewAccountInitCommand(io ui.IO, newClient newClientFunc, credentialStore Cr io: io, credentialStore: credentialStore, clipper: clip.NewClipboard(), - progressPrinter: progress.NewPrinter(io.Stdout(), 500*time.Millisecond), + progressPrinter: progress.NewPrinter(io.Output(), 500*time.Millisecond), newClient: newClient, } } @@ -105,7 +105,7 @@ func (cmd *AccountInitCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -138,14 +138,14 @@ func (cmd *AccountInitCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } } fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "An account credential will be generated and stored at %s. "+ "Losing this credential means you lose the ability to decrypt your secrets. "+ "So keep it safe.\n", @@ -166,13 +166,13 @@ func (cmd *AccountInitCommand) Run() error { } } - fmt.Fprint(cmd.io.Stdout(), "Generating credential...") + fmt.Fprint(cmd.io.Output(), "Generating credential...") err := credential.Create() if err != nil { return err } - fmt.Fprintln(cmd.io.Stdout(), " Done") + fmt.Fprintln(cmd.io.Output(), " Done") exportKey := credential.Key if passphrase != "" { @@ -216,11 +216,11 @@ func (cmd *AccountInitCommand) Run() error { if err != nil { return err } - fmt.Fprintln(cmd.io.Stdout(), "The credential's public component has been copied to the clipboard. To add the credential to your account, paste the clipboard contents in https://dashboard.secrethub.io/account-init") + fmt.Fprintln(cmd.io.Output(), "The credential's public component has been copied to the clipboard. To add the credential to your account, paste the clipboard contents in https://dashboard.secrethub.io/account-init") } else { - fmt.Fprintln(cmd.io.Stdout(), "To add the credential to your account, paste the public component shown below in https://dashboard.secrethub.io/account-init") + fmt.Fprintln(cmd.io.Output(), "To add the credential to your account, paste the public component shown below in https://dashboard.secrethub.io/account-init") - fmt.Fprintf(cmd.io.Stdout(), "\n%s\n", out) + fmt.Fprintf(cmd.io.Output(), "\n%s\n", out) } } else { if !cmd.credentialStore.ConfigDir().Credential().Exists() { @@ -249,21 +249,21 @@ func (cmd *AccountInitCommand) createAccountKey() error { if !isAuthenticated { if cmd.noWait { - fmt.Fprintln(cmd.io.Stdout(), "Not waiting for credential to be added. To continue initializing your account after you have added the credential, run again with --continue.") + fmt.Fprintln(cmd.io.Output(), "Not waiting for credential to be added. To continue initializing your account after you have added the credential, run again with --continue.") return nil } - fmt.Fprint(cmd.io.Stdout(), "Waiting for credential to be added...") + fmt.Fprint(cmd.io.Output(), "Waiting for credential to be added...") authenticatedC, errC := cmd.waitForCredentialToBeAdded(client) select { case <-authenticatedC: - fmt.Fprintln(cmd.io.Stdout(), " Done") + fmt.Fprintln(cmd.io.Output(), " Done") case err := <-errC: - fmt.Fprintln(cmd.io.Stdout(), " Failed") + fmt.Fprintln(cmd.io.Output(), " Failed") return err case <-time.After(WaitTimeout): - fmt.Fprintln(cmd.io.Stdout(), " Failed") + fmt.Fprintln(cmd.io.Output(), " Failed") return ErrAddCredentialTimeout } } @@ -286,7 +286,7 @@ func (cmd *AccountInitCommand) createAccountKey() error { return ErrAccountAlreadyInitialized } - fmt.Fprint(cmd.io.Stdout(), "Finishing setup of your account...") + fmt.Fprint(cmd.io.Output(), "Finishing setup of your account...") key, err := cmd.credentialStore.Import() if err != nil { @@ -295,7 +295,7 @@ func (cmd *AccountInitCommand) createAccountKey() error { _, err = client.Accounts().Keys().Create(key.Verifier(), key.Encrypter()) if err != nil { - fmt.Fprintln(cmd.io.Stdout(), " Failed") + fmt.Fprintln(cmd.io.Output(), " Failed") return err } @@ -313,7 +313,7 @@ func (cmd *AccountInitCommand) createAccountKey() error { return err } - fmt.Fprintln(cmd.io.Stdout(), " Done") + fmt.Fprintln(cmd.io.Output(), " Done") return nil } diff --git a/internals/secrethub/account_inspect.go b/internals/secrethub/account_inspect.go index 27fc7114..bb81543c 100644 --- a/internals/secrethub/account_inspect.go +++ b/internals/secrethub/account_inspect.go @@ -50,7 +50,7 @@ func (cmd *AccountInspectCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), output) + fmt.Fprintln(cmd.io.Output(), output) return nil } diff --git a/internals/secrethub/acl_check.go b/internals/secrethub/acl_check.go index 27459ac2..0e9df30a 100644 --- a/internals/secrethub/acl_check.go +++ b/internals/secrethub/acl_check.go @@ -48,18 +48,18 @@ func (cmd *ACLCheckCommand) Run() error { if cmd.accountName != "" { for _, level := range levels { if level.Account.Name == cmd.accountName { - fmt.Fprintf(cmd.io.Stdout(), "%s\n", level.Permission.String()) + fmt.Fprintf(cmd.io.Output(), "%s\n", level.Permission.String()) return nil } } - fmt.Fprintln(cmd.io.Stdout(), api.PermissionNone.String()) + fmt.Fprintln(cmd.io.Output(), api.PermissionNone.String()) return nil } sort.Sort(api.SortAccessLevels(levels)) - tabWriter := tabwriter.NewWriter(cmd.io.Stdout(), 0, 4, 4, ' ', 0) + tabWriter := tabwriter.NewWriter(cmd.io.Output(), 0, 4, 4, ' ', 0) fmt.Fprintf(tabWriter, "%s\t%s\n", "PERMISSIONS", "ACCOUNT") for _, level := range levels { diff --git a/internals/secrethub/acl_list.go b/internals/secrethub/acl_list.go index 5fa96b24..76f8ee8a 100644 --- a/internals/secrethub/acl_list.go +++ b/internals/secrethub/acl_list.go @@ -103,7 +103,7 @@ func (cmd *ACLListCommand) run() error { sort.Sort(api.SortDirPaths(paths)) - tabWriter := tabwriter.NewWriter(cmd.io.Stdout(), 0, 4, 4, ' ', 0) + tabWriter := tabwriter.NewWriter(cmd.io.Output(), 0, 4, 4, ' ', 0) fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\n", "PATH", "PERMISSIONS", "LAST EDITED", "ACCOUNT") for _, p := range paths { diff --git a/internals/secrethub/acl_rm.go b/internals/secrethub/acl_rm.go index 2fefd241..675eec7a 100644 --- a/internals/secrethub/acl_rm.go +++ b/internals/secrethub/acl_rm.go @@ -54,7 +54,7 @@ func (cmd *ACLRmCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -64,14 +64,14 @@ func (cmd *ACLRmCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Removing access rule...") + fmt.Fprintln(cmd.io.Output(), "Removing access rule...") err = client.AccessRules().Delete(cmd.path.Value(), cmd.accountName.Value()) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Removal complete! The access rule for %s on %s has been removed.\n", cmd.accountName, cmd.path) + fmt.Fprintf(cmd.io.Output(), "Removal complete! The access rule for %s on %s has been removed.\n", cmd.accountName, cmd.path) return nil } diff --git a/internals/secrethub/acl_set.go b/internals/secrethub/acl_set.go index ea401dd4..7d68866e 100644 --- a/internals/secrethub/acl_set.go +++ b/internals/secrethub/acl_set.go @@ -58,12 +58,12 @@ func (cmd *ACLSetCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } - fmt.Fprintf(cmd.io.Stdout(), "Setting access rule for %s at %s with %s\n", cmd.accountName, cmd.path, cmd.permission) + fmt.Fprintf(cmd.io.Output(), "Setting access rule for %s at %s with %s\n", cmd.accountName, cmd.path, cmd.permission) client, err := cmd.newClient() if err != nil { @@ -75,7 +75,7 @@ func (cmd *ACLSetCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Access rule set!") + fmt.Fprintln(cmd.io.Output(), "Access rule set!") return nil diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index c913bcc8..424c5c4b 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -63,7 +63,7 @@ func (cmd *AuditCommand) run() error { return err } - tabWriter := tabwriter.NewWriter(cmd.io.Stdout(), 0, 4, 4, ' ', 0) + tabWriter := tabwriter.NewWriter(cmd.io.Output(), 0, 4, 4, ' ', 0) header := strings.Join(auditTable.header(), "\t") + "\n" fmt.Fprint(tabWriter, header) diff --git a/internals/secrethub/clear.go b/internals/secrethub/clear.go index 30565103..83b1c33a 100644 --- a/internals/secrethub/clear.go +++ b/internals/secrethub/clear.go @@ -53,14 +53,14 @@ func (cmd *ClearCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Clearing secrets...") + fmt.Fprintln(cmd.io.Output(), "Clearing secrets...") err = presenter.Clear() if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Clear complete! The secrets are no longer available on the system.\n") + fmt.Fprintf(cmd.io.Output(), "Clear complete! The secrets are no longer available on the system.\n") return nil } diff --git a/internals/secrethub/config_update_passphrase.go b/internals/secrethub/config_update_passphrase.go index a4c8be9f..c890b715 100644 --- a/internals/secrethub/config_update_passphrase.go +++ b/internals/secrethub/config_update_passphrase.go @@ -49,7 +49,7 @@ func (cmd *ConfigUpdatePassphraseCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } @@ -75,7 +75,7 @@ func (cmd *ConfigUpdatePassphraseCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Successfully updated passphrase!") + fmt.Fprintln(cmd.io.Output(), "Successfully updated passphrase!") return nil } diff --git a/internals/secrethub/credential_backup.go b/internals/secrethub/credential_backup.go index 4d09eb7a..d4673edd 100644 --- a/internals/secrethub/credential_backup.go +++ b/internals/secrethub/credential_backup.go @@ -50,7 +50,7 @@ func (cmd *CredentialBackupCommand) Run() error { return err } if !ok { - fmt.Fprintln(cmd.io.Stdout(), "Aborting") + fmt.Fprintln(cmd.io.Output(), "Aborting") return nil } @@ -66,8 +66,8 @@ func (cmd *CredentialBackupCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "This is your backup code: \n%s\n", code) - fmt.Fprintln(cmd.io.Stdout(), "Write it down and store it in a safe location! "+ + fmt.Fprintf(cmd.io.Output(), "This is your backup code: \n%s\n", code) + fmt.Fprintln(cmd.io.Output(), "Write it down and store it in a safe location! "+ "You can restore your account by running `secrethub init`.") return nil diff --git a/internals/secrethub/credential_disable.go b/internals/secrethub/credential_disable.go index 0bfeba0f..dfc6b247 100644 --- a/internals/secrethub/credential_disable.go +++ b/internals/secrethub/credential_disable.go @@ -60,7 +60,7 @@ func (cmd *CredentialDisableCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), + fmt.Fprintln(cmd.io.Output(), "A disabled credential can no longer be used to access SecretHub. "+ "This process can currently not be reversed.") @@ -70,7 +70,7 @@ func (cmd *CredentialDisableCommand) Run() error { return err } if !ok { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -80,7 +80,7 @@ func (cmd *CredentialDisableCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Credential disabled.") + fmt.Fprintln(cmd.io.Output(), "Credential disabled.") return nil } diff --git a/internals/secrethub/credential_list.go b/internals/secrethub/credential_list.go index 8d97cef8..31801f11 100644 --- a/internals/secrethub/credential_list.go +++ b/internals/secrethub/credential_list.go @@ -46,7 +46,7 @@ func (cmd *CredentialListCommand) Run() error { timeFormatter := NewTimeFormatter(cmd.useTimestamps) - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) fmt.Fprintln(w, "FINGERPRINT\t"+ "TYPE\t"+ diff --git a/internals/secrethub/env_ls.go b/internals/secrethub/env_ls.go index 2f4908be..e4835701 100644 --- a/internals/secrethub/env_ls.go +++ b/internals/secrethub/env_ls.go @@ -43,7 +43,7 @@ func (cmd *EnvListCommand) Run() error { // For now only environment variables in which a secret is loaded are printed. // TODO: Make this behavior configurable. if value.containsSecret() { - fmt.Fprintln(cmd.io.Stdout(), key) + fmt.Fprintln(cmd.io.Output(), key) } } diff --git a/internals/secrethub/env_read.go b/internals/secrethub/env_read.go index 3f1059bb..028b2f69 100644 --- a/internals/secrethub/env_read.go +++ b/internals/secrethub/env_read.go @@ -54,7 +54,7 @@ func (cmd *EnvReadCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), res) + fmt.Fprintln(cmd.io.Output(), res) return nil } diff --git a/internals/secrethub/generate.go b/internals/secrethub/generate.go index c5ce7452..67fa949e 100644 --- a/internals/secrethub/generate.go +++ b/internals/secrethub/generate.go @@ -133,7 +133,7 @@ func (cmd *GenerateSecretCommand) run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "A randomly generated secret has been written to %s:%d.\n", path, version.Version) + fmt.Fprintf(cmd.io.Output(), "A randomly generated secret has been written to %s:%d.\n", path, version.Version) if cmd.copyToClipboard { err = WriteClipboardAutoClear(data, cmd.clearClipboardAfter, cmd.clipper) @@ -142,7 +142,7 @@ func (cmd *GenerateSecretCommand) run() error { } fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "The generated value has been copied to the clipboard. It will be cleared after %s.\n", units.HumanDuration(cmd.clearClipboardAfter), ) diff --git a/internals/secrethub/init.go b/internals/secrethub/init.go index 51c77f3c..dce0c9ac 100644 --- a/internals/secrethub/init.go +++ b/internals/secrethub/init.go @@ -32,7 +32,7 @@ func NewInitCommand(io ui.IO, newClient newClientFunc, newClientWithoutCredentia newClient: newClient, newClientWithoutCredentials: newClientWithoutCredentials, credentialStore: credentialStore, - progressPrinter: progress.NewPrinter(io.Stdout(), 500*time.Millisecond), + progressPrinter: progress.NewPrinter(io.Output(), 500*time.Millisecond), } } @@ -70,7 +70,7 @@ func (cmd *InitCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -136,13 +136,13 @@ func (cmd *InitCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "This backup code can be used to recover the account `%s`\n", me.Username) + fmt.Fprintf(cmd.io.Output(), "This backup code can be used to recover the account `%s`\n", me.Username) ok, err := ui.AskYesNo(cmd.io, "Do you want to continue?", ui.DefaultYes) if err != nil { return err } if !ok { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } diff --git a/internals/secrethub/inject.go b/internals/secrethub/inject.go index df275fd0..23f18af3 100644 --- a/internals/secrethub/inject.go +++ b/internals/secrethub/inject.go @@ -95,7 +95,7 @@ func (cmd *InjectCommand) Run() error { return ErrNoDataOnStdin } - raw, err = ioutil.ReadAll(cmd.io.Stdin()) + raw, err = ioutil.ReadAll(cmd.io.Input()) if err != nil { return err } @@ -135,7 +135,7 @@ func (cmd *InjectCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), fmt.Sprintf("Copied injected template to clipboard. It will be cleared after %s.", units.HumanDuration(cmd.clearClipboardAfter))) + fmt.Fprintln(cmd.io.Output(), fmt.Sprintf("Copied injected template to clipboard. It will be cleared after %s.", units.HumanDuration(cmd.clearClipboardAfter))) } else if cmd.outFile != "" { _, err := os.Stat(cmd.outFile) if err == nil && !cmd.force { @@ -156,7 +156,7 @@ func (cmd *InjectCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -171,9 +171,9 @@ func (cmd *InjectCommand) Run() error { return ErrCannotWrite(err) } - fmt.Fprintf(cmd.io.Stdout(), "%s\n", absPath) + fmt.Fprintf(cmd.io.Output(), "%s\n", absPath) } else { - fmt.Fprintf(cmd.io.Stdout(), "%s", posix.AddNewLine(out)) + fmt.Fprintf(cmd.io.Output(), "%s", posix.AddNewLine(out)) } return nil diff --git a/internals/secrethub/inspect_secret.go b/internals/secrethub/inspect_secret.go index eb6f212d..fc1ea195 100644 --- a/internals/secrethub/inspect_secret.go +++ b/internals/secrethub/inspect_secret.go @@ -49,7 +49,7 @@ func (cmd *InspectSecretCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), output) + fmt.Fprintln(cmd.io.Output(), output) return nil } diff --git a/internals/secrethub/inspect_secret_version.go b/internals/secrethub/inspect_secret_version.go index 3e9f5c1a..a372e3f2 100644 --- a/internals/secrethub/inspect_secret_version.go +++ b/internals/secrethub/inspect_secret_version.go @@ -44,7 +44,7 @@ func (cmd *InspectSecretVersionCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), output) + fmt.Fprintln(cmd.io.Output(), output) return nil } diff --git a/internals/secrethub/list.go b/internals/secrethub/list.go index c99d0a63..8a51f99e 100644 --- a/internals/secrethub/list.go +++ b/internals/secrethub/list.go @@ -71,7 +71,7 @@ func (cmd *LsCommand) Run() error { return err } - err = printVersions(cmd.io.Stdout(), cmd.quiet, timeFormatter, version) + err = printVersions(cmd.io.Output(), cmd.quiet, timeFormatter, version) if err != nil { return err } @@ -88,7 +88,7 @@ func (cmd *LsCommand) Run() error { } else if err != nil && !api.IsErrNotFound(err) { return err } else if err == nil { - err = printDir(cmd.io.Stdout(), cmd.quiet, dirFS.RootDir, timeFormatter) + err = printDir(cmd.io.Output(), cmd.quiet, dirFS.RootDir, timeFormatter) if err != nil { return err } @@ -106,7 +106,7 @@ func (cmd *LsCommand) Run() error { return err } - err = printVersions(cmd.io.Stdout(), cmd.quiet, timeFormatter, versions...) + err = printVersions(cmd.io.Output(), cmd.quiet, timeFormatter, versions...) if err != nil { return err } diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 226c76b0..7625c986 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -62,7 +62,7 @@ func (cmd *MkDirCommand) Run() error { } } - fmt.Fprintf(cmd.io.Stdout(), "Created a new directory at %s\n", cmd.path) + fmt.Fprintf(cmd.io.Output(), "Created a new directory at %s\n", cmd.path) return nil } diff --git a/internals/secrethub/org_init.go b/internals/secrethub/org_init.go index ce39c8fc..b936b906 100644 --- a/internals/secrethub/org_init.go +++ b/internals/secrethub/org_init.go @@ -47,7 +47,7 @@ func (cmd *OrgInitCommand) Run() error { return ErrMissingFlags } else if !cmd.force && incompleteInput { fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "Before initializing a new organization, we need to know a few things about your organization. "+ "Please answer the questions below, followed by an [ENTER]\n\n", ) @@ -68,7 +68,7 @@ func (cmd *OrgInitCommand) Run() error { } // Print a whitespace line here for readability. - fmt.Fprintln(cmd.io.Stdout(), "") + fmt.Fprintln(cmd.io.Output(), "") } client, err := cmd.newClient() @@ -76,14 +76,14 @@ func (cmd *OrgInitCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "Creating organization...\n") + fmt.Fprintf(cmd.io.Output(), "Creating organization...\n") resp, err := client.Orgs().Create(cmd.name.Value(), cmd.description) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Creation complete! The organization %s is now ready to use.\n", resp.Name) + fmt.Fprintf(cmd.io.Output(), "Creation complete! The organization %s is now ready to use.\n", resp.Name) return nil } diff --git a/internals/secrethub/org_inspect.go b/internals/secrethub/org_inspect.go index bdd58fad..123d2df6 100644 --- a/internals/secrethub/org_inspect.go +++ b/internals/secrethub/org_inspect.go @@ -62,7 +62,7 @@ func (cmd *OrgInspectCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), output) + fmt.Fprintln(cmd.io.Output(), output) return nil } diff --git a/internals/secrethub/org_invite.go b/internals/secrethub/org_invite.go index bf7fd172..459193ce 100644 --- a/internals/secrethub/org_invite.go +++ b/internals/secrethub/org_invite.go @@ -51,7 +51,7 @@ func (cmd *OrgInviteCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -61,14 +61,14 @@ func (cmd *OrgInviteCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Inviting user...") + fmt.Fprintln(cmd.io.Output(), "Inviting user...") resp, err := client.Orgs().Members().Invite(cmd.orgName.Value(), cmd.username, cmd.role) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Invite complete! The user %s is now %s of the %s organization.\n", resp.User.Username, resp.Role, cmd.orgName) + fmt.Fprintf(cmd.io.Output(), "Invite complete! The user %s is now %s of the %s organization.\n", resp.User.Username, resp.Role, cmd.orgName) return nil } diff --git a/internals/secrethub/org_list_users.go b/internals/secrethub/org_list_users.go index bdc7b743..7bfbedc7 100644 --- a/internals/secrethub/org_list_users.go +++ b/internals/secrethub/org_list_users.go @@ -63,7 +63,7 @@ func (cmd *OrgListUsersCommand) run() error { sort.Sort(api.SortOrgMemberByUsername(resp)) - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) fmt.Fprintf(w, "%s\t%s\t%s\n", "USER", "ROLE", "LAST CHANGED") for _, member := range resp { diff --git a/internals/secrethub/org_ls.go b/internals/secrethub/org_ls.go index 1fe27927..fccc24b4 100644 --- a/internals/secrethub/org_ls.go +++ b/internals/secrethub/org_ls.go @@ -65,10 +65,10 @@ func (cmd *OrgLsCommand) run() error { if cmd.quiet { for _, org := range resp { - fmt.Fprintf(cmd.io.Stdout(), "%s\n", org.Name) + fmt.Fprintf(cmd.io.Output(), "%s\n", org.Name) } } else { - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "NAME", "REPOS", "USERS", "CREATED") diff --git a/internals/secrethub/org_purchase.go b/internals/secrethub/org_purchase.go index e40a4ae0..a7da9083 100644 --- a/internals/secrethub/org_purchase.go +++ b/internals/secrethub/org_purchase.go @@ -28,8 +28,8 @@ func (cmd *OrgPurchaseCommand) Register(r command.Registerer) { // Run prints instructions on purchasing a SecretHub subscription. func (cmd OrgPurchaseCommand) Run() error { - fmt.Fprintf(cmd.io.Stdout(), "An organization subscription for SecretHub can be purchased through the billing dashboard.\n\n") - fmt.Fprintf(cmd.io.Stdout(), "For more information, check out:\nhttps://secrethub.io/docs/organizations/upgrade/\n\n") + fmt.Fprintf(cmd.io.Output(), "An organization subscription for SecretHub can be purchased through the billing dashboard.\n\n") + fmt.Fprintf(cmd.io.Output(), "For more information, check out:\nhttps://secrethub.io/docs/organizations/upgrade/\n\n") return nil } diff --git a/internals/secrethub/org_revoke.go b/internals/secrethub/org_revoke.go index 7de7e360..dccfc0bf 100644 --- a/internals/secrethub/org_revoke.go +++ b/internals/secrethub/org_revoke.go @@ -53,7 +53,7 @@ func (cmd *OrgRevokeCommand) Run() error { if len(planned.Repos) > 0 { fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "[WARNING] Revoking %s from the %s organization will revoke the user from %d repositories, "+ "automatically flagging secrets for rotation.\n\n"+ "A revocation plan has been generated and is shown below. "+ @@ -65,7 +65,7 @@ func (cmd *OrgRevokeCommand) Run() error { len(planned.Repos), ) - err = writeOrgRevokeRepoList(cmd.io.Stdout(), planned.Repos...) + err = writeOrgRevokeRepoList(cmd.io.Output(), planned.Repos...) if err != nil { return err } @@ -74,10 +74,10 @@ func (cmd *OrgRevokeCommand) Run() error { failed := planned.StatusCounts[api.StatusFailed] unaffected := planned.StatusCounts[api.StatusOK] - fmt.Fprintf(cmd.io.Stdout(), "Revocation plan: %d to flag, %d to fail, %d OK.\n\n", flagged, failed, unaffected) + fmt.Fprintf(cmd.io.Output(), "Revocation plan: %d to flag, %d to fail, %d OK.\n\n", flagged, failed, unaffected) } else { fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "The user %s has no memberships to any of %s's repos and can be safely removed.\n\n", cmd.username, cmd.orgName, @@ -94,11 +94,11 @@ func (cmd *OrgRevokeCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Name does not match. Aborting.") + fmt.Fprintln(cmd.io.Output(), "Name does not match. Aborting.") return nil } - fmt.Fprintf(cmd.io.Stdout(), "\nRevoking user...\n") + fmt.Fprintf(cmd.io.Output(), "\nRevoking user...\n") revoked, err := client.Orgs().Members().Revoke(cmd.orgName.Value(), cmd.username, nil) if err != nil { @@ -106,8 +106,8 @@ func (cmd *OrgRevokeCommand) Run() error { } if len(revoked.Repos) > 0 { - fmt.Fprintln(cmd.io.Stdout(), "") - err = writeOrgRevokeRepoList(cmd.io.Stdout(), revoked.Repos...) + fmt.Fprintln(cmd.io.Output(), "") + err = writeOrgRevokeRepoList(cmd.io.Output(), revoked.Repos...) if err != nil { return err } @@ -117,14 +117,14 @@ func (cmd *OrgRevokeCommand) Run() error { unaffected := revoked.StatusCounts[api.StatusOK] fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "Revoke complete! Repositories: %d flagged, %d failed, %d OK.\n", flagged, failed, unaffected, ) } else { - fmt.Fprintln(cmd.io.Stdout(), "Revoke complete!") + fmt.Fprintln(cmd.io.Output(), "Revoke complete!") } return nil diff --git a/internals/secrethub/org_rm.go b/internals/secrethub/org_rm.go index 7f855ca5..12a651b0 100644 --- a/internals/secrethub/org_rm.go +++ b/internals/secrethub/org_rm.go @@ -51,7 +51,7 @@ func (cmd *OrgRmCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Name does not match. Aborting.") + fmt.Fprintln(cmd.io.Output(), "Name does not match. Aborting.") return nil } @@ -60,14 +60,14 @@ func (cmd *OrgRmCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Deleting organization...") + fmt.Fprintln(cmd.io.Output(), "Deleting organization...") err = client.Orgs().Delete(cmd.name.Value()) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Delete complete! The organization %s has been permanently deleted.\n", cmd.name) + fmt.Fprintf(cmd.io.Output(), "Delete complete! The organization %s has been permanently deleted.\n", cmd.name) return nil } diff --git a/internals/secrethub/org_set_role.go b/internals/secrethub/org_set_role.go index c2444794..65286a31 100644 --- a/internals/secrethub/org_set_role.go +++ b/internals/secrethub/org_set_role.go @@ -43,14 +43,14 @@ func (cmd *OrgSetRoleCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "Setting role...\n") + fmt.Fprintf(cmd.io.Output(), "Setting role...\n") resp, err := client.Orgs().Members().Update(cmd.orgName.Value(), cmd.username, cmd.role) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Set complete! The user %s is %s of the %s organization.\n", resp.User.Username, resp.Role, cmd.orgName) + fmt.Fprintf(cmd.io.Output(), "Set complete! The user %s is %s of the %s organization.\n", resp.User.Username, resp.Role, cmd.orgName) return nil } diff --git a/internals/secrethub/printenv.go b/internals/secrethub/printenv.go index ece91a11..44f597d9 100644 --- a/internals/secrethub/printenv.go +++ b/internals/secrethub/printenv.go @@ -27,7 +27,7 @@ func NewPrintEnvCommand(app *cli.App, io ui.IO) *PrintEnvCommand { // Run prints out debug statements about all environment variables. func (cmd *PrintEnvCommand) Run() error { - err := cmd.app.PrintEnv(cmd.io.Stdout(), cmd.verbose, cmd.osEnv) + err := cmd.app.PrintEnv(cmd.io.Output(), cmd.verbose, cmd.osEnv) if err != nil { return err } diff --git a/internals/secrethub/read.go b/internals/secrethub/read.go index e74b7d3b..295a4ea1 100644 --- a/internals/secrethub/read.go +++ b/internals/secrethub/read.go @@ -76,7 +76,7 @@ func (cmd *ReadCommand) Run() error { } fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "Copied %s to clipboard. It will be cleared after %s.\n", cmd.path, units.HumanDuration(cmd.clearClipboardAfter), @@ -96,7 +96,7 @@ func (cmd *ReadCommand) Run() error { } if cmd.outFile == "" && !cmd.useClipboard { - fmt.Fprintf(cmd.io.Stdout(), "%s", string(secretData)) + fmt.Fprintf(cmd.io.Output(), "%s", string(secretData)) } return nil diff --git a/internals/secrethub/repo_export.go b/internals/secrethub/repo_export.go index 5a85fdd7..23ffe444 100644 --- a/internals/secrethub/repo_export.go +++ b/internals/secrethub/repo_export.go @@ -71,7 +71,7 @@ func (cmd *RepoExportCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Name does not match. Aborting.") + fmt.Fprintln(cmd.io.Output(), "Name does not match. Aborting.") return nil } diff --git a/internals/secrethub/repo_init.go b/internals/secrethub/repo_init.go index 4e78756b..df801140 100644 --- a/internals/secrethub/repo_init.go +++ b/internals/secrethub/repo_init.go @@ -39,14 +39,14 @@ func (cmd *RepoInitCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), "Creating repository...") + fmt.Fprintln(cmd.io.Output(), "Creating repository...") _, err = client.Repos().Create(cmd.path.Value()) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Create complete! The repository %s is now ready to use.\n", cmd.path.String()) + fmt.Fprintf(cmd.io.Output(), "Create complete! The repository %s is now ready to use.\n", cmd.path.String()) return nil } diff --git a/internals/secrethub/repo_inspect.go b/internals/secrethub/repo_inspect.go index 9e2e7f80..66165648 100644 --- a/internals/secrethub/repo_inspect.go +++ b/internals/secrethub/repo_inspect.go @@ -62,7 +62,7 @@ func (cmd *RepoInspectCommand) Run() error { return err } - fmt.Fprintln(cmd.io.Stdout(), output) + fmt.Fprintln(cmd.io.Output(), output) return nil } diff --git a/internals/secrethub/repo_invite.go b/internals/secrethub/repo_invite.go index b39488dd..317a86d3 100644 --- a/internals/secrethub/repo_invite.go +++ b/internals/secrethub/repo_invite.go @@ -59,18 +59,18 @@ func (cmd *RepoInviteCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } - fmt.Fprintln(cmd.io.Stdout(), "Inviting user...") + fmt.Fprintln(cmd.io.Output(), "Inviting user...") _, err = client.Repos().Users().Invite(cmd.path.Value(), cmd.username) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Invite complete! The user %s is now a member of the %s repository.\n", cmd.username, cmd.path) + fmt.Fprintf(cmd.io.Output(), "Invite complete! The user %s is now a member of the %s repository.\n", cmd.username, cmd.path) return nil } diff --git a/internals/secrethub/repo_ls.go b/internals/secrethub/repo_ls.go index f5ffeb95..f13ae0b2 100644 --- a/internals/secrethub/repo_ls.go +++ b/internals/secrethub/repo_ls.go @@ -75,10 +75,10 @@ func (cmd *RepoLSCommand) run() error { if cmd.quiet { for _, repo := range list { - fmt.Fprintf(cmd.io.Stdout(), "%s\n", repo.Path()) + fmt.Fprintf(cmd.io.Output(), "%s\n", repo.Path()) } } else { - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) fmt.Fprintf(w, "%s\t%s\t%s\n", "NAME", "STATUS", "CREATED") for _, repo := range list { fmt.Fprintf(w, "%s\t%s\t%s\n", repo.Path(), repo.Status, cmd.timeFormatter.Format(repo.CreatedAt.Local())) diff --git a/internals/secrethub/repo_revoke.go b/internals/secrethub/repo_revoke.go index 8de8c22a..02b2f5cf 100644 --- a/internals/secrethub/repo_revoke.go +++ b/internals/secrethub/repo_revoke.go @@ -70,12 +70,12 @@ func (cmd *RepoRevokeCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } - fmt.Fprint(cmd.io.Stdout(), "Revoking account...\n\n") + fmt.Fprint(cmd.io.Output(), "Revoking account...\n\n") var revoked *api.RevokeRepoResponse if cmd.accountName.IsService() { @@ -88,7 +88,7 @@ func (cmd *RepoRevokeCommand) Run() error { } if revoked.Status == api.StatusFailed { - fmt.Fprintf(cmd.io.Stdout(), + fmt.Fprintf(cmd.io.Output(), "\nRevoke failed! The account %s is the only admin on the repo %s."+ "You need to make sure another account has admin rights on the repository or you can remove the repo.", prettyName, @@ -101,7 +101,7 @@ func (cmd *RepoRevokeCommand) Run() error { return err } - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) countUnaffected, countFlagged := printFlaggedSecrets(w, rootDir.RootDir, cmd.path.GetNamespace()) @@ -111,9 +111,9 @@ func (cmd *RepoRevokeCommand) Run() error { } if countFlagged > 0 { - fmt.Fprintln(cmd.io.Stdout()) + fmt.Fprintln(cmd.io.Output()) } - fmt.Fprintf(cmd.io.Stdout(), + fmt.Fprintf(cmd.io.Output(), "Revoke complete! The account %s can no longer access the %s repository. "+ "Make sure you overwrite or delete all flagged secrets. "+ "Secrets: %d unaffected, %d flagged\n", diff --git a/internals/secrethub/repo_rm.go b/internals/secrethub/repo_rm.go index 28e37a57..9c33eef8 100644 --- a/internals/secrethub/repo_rm.go +++ b/internals/secrethub/repo_rm.go @@ -60,18 +60,18 @@ func (cmd *RepoRmCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Name does not match. Aborting.") + fmt.Fprintln(cmd.io.Output(), "Name does not match. Aborting.") return nil } - fmt.Fprintln(cmd.io.Stdout(), "Removing repository...") + fmt.Fprintln(cmd.io.Output(), "Removing repository...") err = client.Repos().Delete(cmd.path.Value()) if err != nil { return err } - fmt.Fprintf(cmd.io.Stdout(), "Removal complete! The repository %s has been permanently removed.\n", cmd.path) + fmt.Fprintf(cmd.io.Output(), "Removal complete! The repository %s has been permanently removed.\n", cmd.path) return nil } diff --git a/internals/secrethub/rm.go b/internals/secrethub/rm.go index 7815d724..21b0c1d6 100644 --- a/internals/secrethub/rm.go +++ b/internals/secrethub/rm.go @@ -121,7 +121,7 @@ func rmSecretVersion(client secrethub.ClientInterface, secretPath api.SecretPath } fmt.Fprintf( - io.Stdout(), + io.Output(), "Removal complete! The secret version %s has been permanently removed.\n", secretPath, ) @@ -151,7 +151,7 @@ func rmSecret(client secrethub.ClientInterface, secretPath api.SecretPath, force } fmt.Fprintf( - io.Stdout(), + io.Output(), "Removal complete! The secret %s has been permanently removed.\n", secretPath, ) @@ -181,7 +181,7 @@ func rmDir(client secrethub.ClientInterface, dirPath api.DirPath, force bool, io } fmt.Fprintf( - io.Stdout(), + io.Output(), "Removal complete! The directory %s has been permanently removed.\n", dirPath, ) @@ -210,7 +210,7 @@ func askRmConfirmation(io ui.IO, confirmationText string, force bool, expected . } if !confirmed { - fmt.Fprintln(io.Stdout(), "Name does not match. Aborting.") + fmt.Fprintln(io.Output(), "Name does not match. Aborting.") return false, nil } return true, nil diff --git a/internals/secrethub/run.go b/internals/secrethub/run.go index 0826e148..705e743a 100644 --- a/internals/secrethub/run.go +++ b/internals/secrethub/run.go @@ -112,10 +112,10 @@ func (cmd *RunCommand) Run() error { command.Env = environment command.Stdin = os.Stdin if cmd.noMasking { - command.Stdout = cmd.io.Stdout() + command.Stdout = cmd.io.Output() command.Stderr = os.Stderr } else { - command.Stdout = m.AddStream(cmd.io.Stdout()) + command.Stdout = m.AddStream(cmd.io.Output()) command.Stderr = m.AddStream(os.Stderr) go m.Start() diff --git a/internals/secrethub/service_aws_init.go b/internals/secrethub/service_aws_init.go index 16bf7d91..71bcf4ef 100644 --- a/internals/secrethub/service_aws_init.go +++ b/internals/secrethub/service_aws_init.go @@ -59,7 +59,7 @@ func (cmd *ServiceAWSInitCommand) Run() error { } if cmd.role == "" && cmd.kmsKeyID == "" { - fmt.Fprintln(cmd.io.Stdout(), "This command creates a new service account for use on AWS. For help on this, run `secrethub service aws init --help`.") + fmt.Fprintln(cmd.io.Output(), "This command creates a new service account for use on AWS. For help on this, run `secrethub service aws init --help`.") } cfg := aws.NewConfig() @@ -85,7 +85,7 @@ func (cmd *ServiceAWSInitCommand) Run() error { } accountID := aws.StringValue(identity.Account) - fmt.Fprintf(cmd.io.Stdout(), "Detected access to AWS account %s.", accountID) + fmt.Fprintf(cmd.io.Output(), "Detected access to AWS account %s.", accountID) if cfg.Region == nil && cmd.kmsKeyID != "" { // When the region is not configured in the AWS configuration and not supplied using the flag, use @@ -97,9 +97,9 @@ func (cmd *ServiceAWSInitCommand) Run() error { } if cfg.Region != nil { - fmt.Fprintf(cmd.io.Stdout(), "Using region %s.", *cfg.Region) + fmt.Fprintf(cmd.io.Output(), "Using region %s.", *cfg.Region) } - fmt.Fprintln(cmd.io.Stdout()) + fmt.Fprintln(cmd.io.Output()) if cfg.Region == nil { region, err := ui.ChooseDynamicOptions(cmd.io, "Which region do you want to use for KMS?", getAWSRegionOptions, true, "region") @@ -149,8 +149,8 @@ func (cmd *ServiceAWSInitCommand) Run() error { } } - fmt.Fprintln(cmd.io.Stdout(), "Successfully created a new service account with ID: "+service.ServiceID) - fmt.Fprintf(cmd.io.Stdout(), "Any host that assumes the IAM role %s can now automatically authenticate to SecretHub and fetch the secrets the service has been given access to.\n", roleNameFromRole(cmd.role)) + fmt.Fprintln(cmd.io.Output(), "Successfully created a new service account with ID: "+service.ServiceID) + fmt.Fprintf(cmd.io.Output(), "Any host that assumes the IAM role %s can now automatically authenticate to SecretHub and fetch the secrets the service has been given access to.\n", roleNameFromRole(cmd.role)) return nil } diff --git a/internals/secrethub/service_deploy_winrm.go b/internals/secrethub/service_deploy_winrm.go index 2a8355fe..0b2abd6e 100644 --- a/internals/secrethub/service_deploy_winrm.go +++ b/internals/secrethub/service_deploy_winrm.go @@ -183,19 +183,19 @@ func (cmd *ServiceDeployWinRmCommand) Run() error { return ErrNoDataOnStdin } - credential, err := ioutil.ReadAll(cmd.io.Stdin()) + credential, err := ioutil.ReadAll(cmd.io.Input()) if err != nil { return err } // Copy the config to the host. - fmt.Fprintln(cmd.io.Stdout(), "Deploying configuration...") + fmt.Fprintln(cmd.io.Output(), "Deploying configuration...") err = deployer.configure(credential) if err != nil { return err } - fmt.Fprintln(cmd.io.Stdout(), "Deploy complete! The service account can now be used to connect to SecretHub from the host.") + fmt.Fprintln(cmd.io.Output(), "Deploy complete! The service account can now be used to connect to SecretHub from the host.") return nil } @@ -203,7 +203,7 @@ func (cmd *ServiceDeployWinRmCommand) Run() error { // checkWinRMTLS checks if the given schema corresponds to the given CLI flags. func (cmd *ServiceDeployWinRmCommand) checkWinRMTLS() (bool, error) { if cmd.resourceURI.Scheme == "http" { - fmt.Fprintln(cmd.io.Stdout(), "WARNING: insecure no tls flag is set! We recommend to always use TLS.") + fmt.Fprintln(cmd.io.Output(), "WARNING: insecure no tls flag is set! We recommend to always use TLS.") return false, nil } @@ -221,7 +221,7 @@ func (cmd *ServiceDeployWinRmCommand) checkWinRMTLS() (bool, error) { // checkWinRMVerifyCert checks if the given schema corresponds to the given CLI flags. func (cmd *ServiceDeployWinRmCommand) checkWinRMVerifyCert() bool { if cmd.noVerify { - fmt.Fprintln(cmd.io.Stdout(), "WARNING: insecure no verify cert flag is set! We recommend to always verify the certificate.") + fmt.Fprintln(cmd.io.Output(), "WARNING: insecure no verify cert flag is set! We recommend to always verify the certificate.") return true } diff --git a/internals/secrethub/service_init.go b/internals/secrethub/service_init.go index a23fc0d6..5a7698b7 100644 --- a/internals/secrethub/service_init.go +++ b/internals/secrethub/service_init.go @@ -88,7 +88,7 @@ func (cmd *ServiceInitCommand) Run() error { return err } - fmt.Fprintf(cmd.io.Stdout(), "Copied account configuration for %s to clipboard. It will be cleared after 45 seconds.\n", service.ServiceID) + fmt.Fprintf(cmd.io.Output(), "Copied account configuration for %s to clipboard. It will be cleared after 45 seconds.\n", service.ServiceID) } else if cmd.file != "" { err = ioutil.WriteFile(cmd.file, posix.AddNewLine(out), cmd.fileMode.FileMode()) if err != nil { @@ -96,13 +96,13 @@ func (cmd *ServiceInitCommand) Run() error { } fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "Written account configuration for %s to %s. Be sure to remove it when you're done.\n", service.ServiceID, cmd.file, ) } else { - fmt.Fprintf(cmd.io.Stdout(), "%s", posix.AddNewLine(out)) + fmt.Fprintf(cmd.io.Output(), "%s", posix.AddNewLine(out)) } return nil diff --git a/internals/secrethub/service_ls.go b/internals/secrethub/service_ls.go index aaa6d0fe..d0d0686f 100644 --- a/internals/secrethub/service_ls.go +++ b/internals/secrethub/service_ls.go @@ -83,10 +83,10 @@ outer: if cmd.quiet { for _, service := range included { - fmt.Fprintf(cmd.io.Stdout(), "%s\n", service.ServiceID) + fmt.Fprintf(cmd.io.Output(), "%s\n", service.ServiceID) } } else { - w := tabwriter.NewWriter(cmd.io.Stdout(), 0, 2, 2, ' ', 0) + w := tabwriter.NewWriter(cmd.io.Output(), 0, 2, 2, ' ', 0) serviceTable := cmd.newServiceTable(NewTimeFormatter(cmd.useTimestamps)) fmt.Fprintln(w, strings.Join(serviceTable.header(), "\t")) diff --git a/internals/secrethub/set.go b/internals/secrethub/set.go index 1f420058..73059e41 100644 --- a/internals/secrethub/set.go +++ b/internals/secrethub/set.go @@ -76,7 +76,7 @@ func (cmd *SetCommand) Run() error { } for _, c := range presenter.EmptyConsumables() { - fmt.Fprintf(cmd.io.Stdout(), "Warning: %s contains no secret declarations.\n", c) + fmt.Fprintf(cmd.io.Output(), "Warning: %s contains no secret declarations.\n", c) } secrets := make(map[string]api.SecretVersion) @@ -88,14 +88,14 @@ func (cmd *SetCommand) Run() error { secrets[path] = *secret } - fmt.Fprintln(cmd.io.Stdout(), "Setting secrets...") + fmt.Fprintln(cmd.io.Output(), "Setting secrets...") err = presenter.Set(secrets) if err != nil { return err } - fmt.Fprintln(cmd.io.Stdout(), "Set complete! The secrets are now available on your system.") + fmt.Fprintln(cmd.io.Output(), "Set complete! The secrets are now available on your system.") return nil } diff --git a/internals/secrethub/signup.go b/internals/secrethub/signup.go index 459afd9a..2d8f3eca 100644 --- a/internals/secrethub/signup.go +++ b/internals/secrethub/signup.go @@ -38,7 +38,7 @@ func NewSignUpCommand(io ui.IO, newClient newClientFunc, credentialStore Credent io: io, newClient: newClient, credentialStore: credentialStore, - progressPrinter: progress.NewPrinter(io.Stdout(), 500*time.Millisecond), + progressPrinter: progress.NewPrinter(io.Output(), 500*time.Millisecond), } } @@ -78,7 +78,7 @@ func (cmd *SignUpCommand) Run() error { } if !confirmed { - fmt.Fprintln(cmd.io.Stdout(), "Aborting.") + fmt.Fprintln(cmd.io.Output(), "Aborting.") return nil } } @@ -112,12 +112,12 @@ func (cmd *SignUpCommand) Run() error { return err } } - fmt.Fprintln(cmd.io.Stdout()) + fmt.Fprintln(cmd.io.Output()) } } fmt.Fprintf( - cmd.io.Stdout(), + cmd.io.Output(), "An account credential will be generated and stored at %s. "+ "Losing this credential means you lose the ability to decrypt your secrets. "+ "So keep it safe.\n", @@ -141,7 +141,7 @@ func (cmd *SignUpCommand) Run() error { return err } - fmt.Fprint(cmd.io.Stdout(), "Setting up your account...") + fmt.Fprint(cmd.io.Output(), "Setting up your account...") cmd.progressPrinter.Start() credential := credentials.CreateKey() _, err = client.Users().Create(cmd.username, cmd.email, cmd.fullName, credential) @@ -186,7 +186,7 @@ func (cmd *SignUpCommand) Run() error { } cmd.progressPrinter.Stop() - fmt.Fprint(cmd.io.Stdout(), "Created your account.\n\n") + fmt.Fprint(cmd.io.Output(), "Created your account.\n\n") createWorkspace := cmd.org != "" if !createWorkspace { @@ -194,9 +194,9 @@ func (cmd *SignUpCommand) Run() error { if err != nil { return err } - fmt.Fprintln(cmd.io.Stdout()) + fmt.Fprintln(cmd.io.Output()) if !createWorkspace { - fmt.Fprint(cmd.io.Stdout(), "You can create a shared workspace later using `secrethub org init`.\n\n") + fmt.Fprint(cmd.io.Output(), "You can create a shared workspace later using `secrethub org init`.\n\n") } } if createWorkspace { @@ -212,20 +212,20 @@ func (cmd *SignUpCommand) Run() error { return err } } - fmt.Fprint(cmd.io.Stdout(), "Creating your shared workspace...") + fmt.Fprint(cmd.io.Output(), "Creating your shared workspace...") cmd.progressPrinter.Start() _, err := client.Orgs().Create(cmd.org, cmd.orgDescription) cmd.progressPrinter.Stop() if err == api.ErrOrgAlreadyExists { - fmt.Fprintf(cmd.io.Stdout(), "The workspace %s already exists. If it is your organization, ask a colleague to invite you to the workspace. You can also create a new one using `secrethub org init`.\n", cmd.org) + fmt.Fprintf(cmd.io.Output(), "The workspace %s already exists. If it is your organization, ask a colleague to invite you to the workspace. You can also create a new one using `secrethub org init`.\n", cmd.org) } else if err != nil { return err } else { - fmt.Fprint(cmd.io.Stdout(), "Created your shared workspace.\n\n") + fmt.Fprint(cmd.io.Output(), "Created your shared workspace.\n\n") } } - fmt.Fprintf(cmd.io.Stdout(), "Setup complete. To read your first secret, run:\n\n secrethub read %s\n\n", secretPath) + fmt.Fprintf(cmd.io.Output(), "Setup complete. To read your first secret, run:\n\n secrethub read %s\n\n", secretPath) return nil } diff --git a/internals/secrethub/tree.go b/internals/secrethub/tree.go index 1934292a..3fd756c3 100644 --- a/internals/secrethub/tree.go +++ b/internals/secrethub/tree.go @@ -38,7 +38,7 @@ func (cmd *TreeCommand) Run() error { return err } - printTree(t, cmd.io.Stdout()) + printTree(t, cmd.io.Output()) return nil } diff --git a/internals/secrethub/write.go b/internals/secrethub/write.go index 5c366cf1..adfbd7a5 100644 --- a/internals/secrethub/write.go +++ b/internals/secrethub/write.go @@ -85,7 +85,7 @@ func (cmd *WriteCommand) Run() error { return ErrReadFile(cmd.inFile, err) } } else if cmd.io.IsStdinPiped() { - data, err = ioutil.ReadAll(cmd.io.Stdin()) + data, err = ioutil.ReadAll(cmd.io.Input()) if err != nil { return ui.ErrReadInput(err) } @@ -112,7 +112,7 @@ func (cmd *WriteCommand) Run() error { return errEmptySecret } - _, err = fmt.Fprint(cmd.io.Stdout(), "Writing secret value...\n") + _, err = fmt.Fprint(cmd.io.Output(), "Writing secret value...\n") if err != nil { return err } @@ -127,7 +127,7 @@ func (cmd *WriteCommand) Run() error { return err } - _, err = fmt.Fprintf(cmd.io.Stdout(), "Write complete! The given value has been written to %s:%d\n", cmd.path, version.Version) + _, err = fmt.Fprintf(cmd.io.Output(), "Write complete! The given value has been written to %s:%d\n", cmd.path, version.Version) if err != nil { return err } From 4ceb9a1af104ea41b43a0a42f8a36ae3b7fa6be2 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:15:57 +0200 Subject: [PATCH 067/119] Add Stdin() and Stdout() to IO interface This allows direct interfacing with stdin and stdout for some specific use-cases. --- internals/cli/ui/io.go | 10 ++++++++++ internals/cli/ui/io_unix.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 4a1c46a0..bc05a7f3 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -20,6 +20,8 @@ var ( type IO interface { Input() io.Reader Output() io.Writer + Stdin() *os.File + Stdout() *os.File Prompts() (io.Reader, io.Writer, error) IsStdinPiped() bool IsStdoutPiped() bool @@ -40,6 +42,14 @@ func newStdUserIO() standardIO { } } +func (o standardIO) Stdin() *os.File { + return o.input +} + +func (o standardIO) Stdout() *os.File { + return o.output +} + // Stdin returns the standardIO's Input. func (o standardIO) Input() io.Reader { return o.input diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 9572e68d..5e60ca7a 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -48,6 +48,14 @@ func (o ttyIO) IsStdoutPiped() bool { return isPiped(o.output) } +func (o ttyIO) Stdin() *os.File { + return o.input +} + +func (o ttyIO) Stdout() *os.File { + return o.output +} + // Stdin returns the standardIO's Input. func (o ttyIO) Input() io.Reader { return o.input From 6800b5104c1afc8b93dce1361c82d2d7fc4499a6 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:16:58 +0200 Subject: [PATCH 068/119] Fix Windows interface implementation --- internals/cli/ui/io_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index b7e86b85..b6ad113f 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -22,7 +22,7 @@ func NewUserIO() IO { } // Stdout returns the standardIO's Output. -func (o windowsIO) Stdout() io.Writer { +func (o windowsIO) Output() io.Writer { if !color.NoColor { return colorable.NewColorable(os.Stdout) } From ca647e03da8bcc40fa00824d683c491e3d90bb7a Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:27:06 +0200 Subject: [PATCH 069/119] Move ui fake to separate package --- internals/cli/ui/ask_test.go | 12 ++++--- internals/cli/ui/{ => fakeui}/testing.go | 35 ++++++++++++------- internals/secrethub/account_inspect_test.go | 6 ++-- internals/secrethub/acl_check_test.go | 6 ++-- internals/secrethub/acl_list_test.go | 6 ++-- internals/secrethub/acl_rm_test.go | 6 ++-- internals/secrethub/acl_set_test.go | 5 +-- internals/secrethub/audit_repo_test.go | 9 ++--- internals/secrethub/audit_secret_test.go | 6 ++-- internals/secrethub/generate_test.go | 6 ++-- internals/secrethub/inspect_secret_test.go | 6 ++-- .../secrethub/inspect_secret_version_test.go | 6 ++-- internals/secrethub/mkdir_test.go | 6 ++-- internals/secrethub/org_init_test.go | 6 ++-- internals/secrethub/org_inspect_test.go | 6 ++-- internals/secrethub/org_invite_test.go | 6 ++-- internals/secrethub/org_list_users_test.go | 6 ++-- internals/secrethub/org_ls_test.go | 6 ++-- internals/secrethub/org_revoke_test.go | 6 ++-- internals/secrethub/org_rm_test.go | 6 ++-- internals/secrethub/org_set_role_test.go | 6 ++-- internals/secrethub/repo_init_test.go | 6 ++-- internals/secrethub/repo_inspect_test.go | 6 ++-- internals/secrethub/repo_invite_test.go | 6 ++-- internals/secrethub/repo_ls_test.go | 6 ++-- internals/secrethub/repo_revoke_test.go | 6 ++-- internals/secrethub/repo_rm_test.go | 5 +-- internals/secrethub/run_test.go | 16 ++++----- internals/secrethub/service_ls_test.go | 6 ++-- internals/secrethub/signup_test.go | 6 ++-- internals/secrethub/variable_reader_test.go | 7 ++-- internals/secrethub/write_test.go | 11 +++--- 32 files changed, 131 insertions(+), 113 deletions(-) rename internals/cli/ui/{ => fakeui}/testing.go (76%) diff --git a/internals/cli/ui/ask_test.go b/internals/cli/ui/ask_test.go index 0dd3c44b..ebbbe961 100644 --- a/internals/cli/ui/ask_test.go +++ b/internals/cli/ui/ask_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/secrethub/secrethub-go/internals/assert" + + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" ) func TestAskWithDefault(t *testing.T) { @@ -33,7 +35,7 @@ func TestAskWithDefault(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Reads = tc.in // Run @@ -94,7 +96,7 @@ func TestConfirmCaseInsensitive(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) // Run @@ -239,7 +241,7 @@ func TestAskYesNo(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Reads = tc.in // Run @@ -319,7 +321,7 @@ func TestChoose(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Reads = tc.in // Run @@ -415,7 +417,7 @@ func TestChooseDynamicOptions(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Reads = tc.in if tc.getOptions == nil { diff --git a/internals/cli/ui/testing.go b/internals/cli/ui/fakeui/testing.go similarity index 76% rename from internals/cli/ui/testing.go rename to internals/cli/ui/fakeui/testing.go index fd44e7ac..5cec7841 100644 --- a/internals/cli/ui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -1,29 +1,32 @@ // +build !production -package ui +package fakeui import ( "bytes" "errors" "io" + "os" ) // FakeIO is a helper type for testing that implements the ui.IO interface type FakeIO struct { - StdIn *FakeReader - StdOut *FakeWriter + In *FakeReader + Out *FakeWriter + StdIn *os.File + StdOut *os.File PromptIn *FakeReader PromptOut *FakeWriter PromptErr error } -// NewFakeIO creates a new FakeIO with empty buffers. -func NewFakeIO() *FakeIO { +// NewIO creates a new FakeIO with empty buffers. +func NewIO() *FakeIO { return &FakeIO{ - StdIn: &FakeReader{ + In: &FakeReader{ Buffer: &bytes.Buffer{}, }, - StdOut: &FakeWriter{ + Out: &FakeWriter{ Buffer: &bytes.Buffer{}, }, PromptIn: &FakeReader{ @@ -35,13 +38,21 @@ func NewFakeIO() *FakeIO { } } -// Stdin returns the mocked StdIn. +// Stdin returns the mocked In. func (f *FakeIO) Input() io.Reader { - return f.StdIn + return f.In } -// Stdout returns the mocked StdOut. +// Stdout returns the mocked Out. func (f *FakeIO) Output() io.Writer { + return f.Out +} + +func (f *FakeIO) Stdin() *os.File { + return f.StdIn +} + +func (f *FakeIO) Stdout() *os.File { return f.StdOut } @@ -51,11 +62,11 @@ func (f *FakeIO) Prompts() (io.Reader, io.Writer, error) { } func (f *FakeIO) IsStdinPiped() bool { - return f.StdIn.Piped + return f.In.Piped } func (f *FakeIO) IsStdoutPiped() bool { - return f.StdOut.Piped + return f.Out.Piped } // FakeReader implements the Reader interface. diff --git a/internals/secrethub/account_inspect_test.go b/internals/secrethub/account_inspect_test.go index 63f226c7..c16b719b 100644 --- a/internals/secrethub/account_inspect_test.go +++ b/internals/secrethub/account_inspect_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -85,7 +85,7 @@ func TestAccountInspect(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -93,7 +93,7 @@ func TestAccountInspect(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/acl_check_test.go b/internals/secrethub/acl_check_test.go index 2a60d09a..34bd45d4 100644 --- a/internals/secrethub/acl_check_test.go +++ b/internals/secrethub/acl_check_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -105,7 +105,7 @@ func TestACLCheckCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io lister := tc.lister @@ -126,7 +126,7 @@ func TestACLCheckCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, argPath, tc.listerArgPath) }) } diff --git a/internals/secrethub/acl_list_test.go b/internals/secrethub/acl_list_test.go index 1b993330..12179055 100644 --- a/internals/secrethub/acl_list_test.go +++ b/internals/secrethub/acl_list_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" faketimeformatter "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -149,7 +149,7 @@ func TestACLListCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io tc.cmd.newClient = func() (secrethub.ClientInterface, error) { @@ -164,7 +164,7 @@ func TestACLListCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/acl_rm_test.go b/internals/secrethub/acl_rm_test.go index f75dbe84..acba56d4 100644 --- a/internals/secrethub/acl_rm_test.go +++ b/internals/secrethub/acl_rm_test.go @@ -5,7 +5,7 @@ import ( "errors" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -86,7 +86,7 @@ func TestACLRmCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.in) io.PromptErr = tc.promptErr tc.cmd.io = io @@ -110,7 +110,7 @@ func TestACLRmCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, io.PromptOut.String(), tc.promptOut) assert.Equal(t, argPath, tc.argPath) assert.Equal(t, argAccountName, tc.argAccountName) diff --git a/internals/secrethub/acl_set_test.go b/internals/secrethub/acl_set_test.go index 9033c10f..4b5ef5b1 100644 --- a/internals/secrethub/acl_set_test.go +++ b/internals/secrethub/acl_set_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -106,7 +107,7 @@ func TestACLSetCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.in) io.PromptErr = tc.askErr tc.cmd.io = io @@ -116,7 +117,7 @@ func TestACLSetCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.stdout) + assert.Equal(t, io.Out.String(), tc.stdout) assert.Equal(t, io.PromptOut.String(), tc.promptOut) }) diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index f874d3f1..ca1baeb6 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -5,12 +5,13 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" - "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/pkg/secrethub/fakeclient" + + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" + "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" ) func TestAuditRepoCommand_run(t *testing.T) { @@ -204,7 +205,7 @@ func TestAuditRepoCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -212,7 +213,7 @@ func TestAuditRepoCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 6d644ead..c9bd4f7e 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -171,7 +171,7 @@ func TestAuditSecretCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -179,7 +179,7 @@ func TestAuditSecretCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/generate_test.go b/internals/secrethub/generate_test.go index 27aa4d92..87335c31 100644 --- a/internals/secrethub/generate_test.go +++ b/internals/secrethub/generate_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -184,7 +184,7 @@ func TestGenerateSecretCommand_run(t *testing.T) { }, tc.clientCreationErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -194,7 +194,7 @@ func TestGenerateSecretCommand_run(t *testing.T) { assert.Equal(t, err, tc.err) assert.Equal(t, argPath, tc.path) assert.Equal(t, argData, tc.data) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/inspect_secret_test.go b/internals/secrethub/inspect_secret_test.go index be699c74..9880a351 100644 --- a/internals/secrethub/inspect_secret_test.go +++ b/internals/secrethub/inspect_secret_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -104,7 +104,7 @@ func TestInspectSecret_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -112,7 +112,7 @@ func TestInspectSecret_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } diff --git a/internals/secrethub/inspect_secret_version_test.go b/internals/secrethub/inspect_secret_version_test.go index c453926d..69ad1911 100644 --- a/internals/secrethub/inspect_secret_version_test.go +++ b/internals/secrethub/inspect_secret_version_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -79,7 +79,7 @@ func TestInspectSecretVersion_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -87,7 +87,7 @@ func TestInspectSecretVersion_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index acfd2b56..54a2eafe 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/api/uuid" @@ -73,7 +73,7 @@ func TestMkDirCommand(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := ui.NewFakeIO() + io := fakeui.NewIO() cmd := MkDirCommand{ io: io, path: api.DirPath(tc.path), @@ -83,7 +83,7 @@ func TestMkDirCommand(t *testing.T) { err := cmd.Run() assert.Equal(t, err, tc.err) - assert.Equal(t, tc.stdout, io.StdOut.String()) + assert.Equal(t, tc.stdout, io.Out.String()) }) } } diff --git a/internals/secrethub/org_init_test.go b/internals/secrethub/org_init_test.go index 8e90c9df..bbbefe28 100644 --- a/internals/secrethub/org_init_test.go +++ b/internals/secrethub/org_init_test.go @@ -3,7 +3,7 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -73,7 +73,7 @@ func TestOrgInitCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -81,7 +81,7 @@ func TestOrgInitCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/org_inspect_test.go b/internals/secrethub/org_inspect_test.go index 10085d83..4b67ec1e 100644 --- a/internals/secrethub/org_inspect_test.go +++ b/internals/secrethub/org_inspect_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -156,7 +156,7 @@ func TestOrgInspectCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -164,7 +164,7 @@ func TestOrgInspectCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/org_invite_test.go b/internals/secrethub/org_invite_test.go index 52322b74..605812bd 100644 --- a/internals/secrethub/org_invite_test.go +++ b/internals/secrethub/org_invite_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -111,7 +111,7 @@ func TestOrgInviteCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.in) tc.cmd.io = io @@ -120,7 +120,7 @@ func TestOrgInviteCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, io.PromptOut.String(), tc.promptOut) }) } diff --git a/internals/secrethub/org_list_users_test.go b/internals/secrethub/org_list_users_test.go index 687947a0..c381b987 100644 --- a/internals/secrethub/org_list_users_test.go +++ b/internals/secrethub/org_list_users_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -84,7 +84,7 @@ func TestOrgListUsersCommand_run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -92,7 +92,7 @@ func TestOrgListUsersCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, argOrg, tc.ArgListOrgMember) }) } diff --git a/internals/secrethub/org_ls_test.go b/internals/secrethub/org_ls_test.go index 6417566c..7eef1d95 100644 --- a/internals/secrethub/org_ls_test.go +++ b/internals/secrethub/org_ls_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -144,7 +144,7 @@ func TestOrgLsCommand_run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -152,7 +152,7 @@ func TestOrgLsCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/org_revoke_test.go b/internals/secrethub/org_revoke_test.go index 8cb18b89..ffae5ee1 100644 --- a/internals/secrethub/org_revoke_test.go +++ b/internals/secrethub/org_revoke_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -130,7 +130,7 @@ func TestOrgRevokeCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) tc.cmd.io = io @@ -139,7 +139,7 @@ func TestOrgRevokeCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/org_rm_test.go b/internals/secrethub/org_rm_test.go index 1091e80a..7b73967c 100644 --- a/internals/secrethub/org_rm_test.go +++ b/internals/secrethub/org_rm_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/assert" "github.com/secrethub/secrethub-go/internals/errio" @@ -95,7 +95,7 @@ func TestOrgRmCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptErr = tc.promptErr tc.cmd.io = io @@ -107,7 +107,7 @@ func TestOrgRmCommand_Run(t *testing.T) { assert.Equal(t, err, tc.err) assert.Equal(t, io.PromptOut.String(), tc.promptOut) assert.Equal(t, argName, tc.argName) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/org_set_role_test.go b/internals/secrethub/org_set_role_test.go index f0079e8c..6138f342 100644 --- a/internals/secrethub/org_set_role_test.go +++ b/internals/secrethub/org_set_role_test.go @@ -3,7 +3,7 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -80,7 +80,7 @@ func TestOrgSetRoleCommand_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -88,7 +88,7 @@ func TestOrgSetRoleCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, argOrgName, tc.ArgOrgName) assert.Equal(t, argUsername, tc.ArgUsername) assert.Equal(t, argRole, tc.ArgRole) diff --git a/internals/secrethub/repo_init_test.go b/internals/secrethub/repo_init_test.go index 8ab85e26..135eb525 100644 --- a/internals/secrethub/repo_init_test.go +++ b/internals/secrethub/repo_init_test.go @@ -3,7 +3,7 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -72,7 +72,7 @@ func TestRepoInitCommand_Run(t *testing.T) { } } - io := ui.NewFakeIO() + io := fakeui.NewIO() cmd.io = io // Run @@ -80,7 +80,7 @@ func TestRepoInitCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, argPath, tc.argPath) }) } diff --git a/internals/secrethub/repo_inspect_test.go b/internals/secrethub/repo_inspect_test.go index 7002a5c6..e894adf5 100644 --- a/internals/secrethub/repo_inspect_test.go +++ b/internals/secrethub/repo_inspect_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -115,7 +115,7 @@ func TestInspectRepo_Run(t *testing.T) { }, tc.newClientErr } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Act @@ -123,7 +123,7 @@ func TestInspectRepo_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/repo_invite_test.go b/internals/secrethub/repo_invite_test.go index 4b67c143..58557183 100644 --- a/internals/secrethub/repo_invite_test.go +++ b/internals/secrethub/repo_invite_test.go @@ -3,7 +3,7 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -99,7 +99,7 @@ func TestRepoInviteCommand_Run(t *testing.T) { } } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -107,7 +107,7 @@ func TestRepoInviteCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, argGetUsername, tc.getArgUsername) assert.Equal(t, argInviteUsername, tc.inviteArgUsername) assert.Equal(t, argPath, tc.inviteArgPath) diff --git a/internals/secrethub/repo_ls_test.go b/internals/secrethub/repo_ls_test.go index 040bd3f5..af7decc5 100644 --- a/internals/secrethub/repo_ls_test.go +++ b/internals/secrethub/repo_ls_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/fakes" "github.com/secrethub/secrethub-go/internals/api" @@ -120,7 +120,7 @@ func TestRepoLSCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io if tc.newClientErr != nil { @@ -140,7 +140,7 @@ func TestRepoLSCommand_run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/repo_revoke_test.go b/internals/secrethub/repo_revoke_test.go index 14f523c7..1098982a 100644 --- a/internals/secrethub/repo_revoke_test.go +++ b/internals/secrethub/repo_revoke_test.go @@ -3,7 +3,7 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/api/uuid" @@ -341,7 +341,7 @@ func TestRepoRevokeCommand_Run(t *testing.T) { } } - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io // Run @@ -349,7 +349,7 @@ func TestRepoRevokeCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/repo_rm_test.go b/internals/secrethub/repo_rm_test.go index 16c1a00a..77809888 100644 --- a/internals/secrethub/repo_rm_test.go +++ b/internals/secrethub/repo_rm_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -133,7 +134,7 @@ func TestRepoRmCommand_Run(t *testing.T) { } } - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptIn.ReadErr = tc.promptReadErr io.PromptErr = tc.promptErr @@ -144,7 +145,7 @@ func TestRepoRmCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) assert.Equal(t, io.PromptOut.String(), tc.promptOut) }) } diff --git a/internals/secrethub/run_test.go b/internals/secrethub/run_test.go index b074a23a..fe27dee6 100644 --- a/internals/secrethub/run_test.go +++ b/internals/secrethub/run_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" "github.com/secrethub/secrethub-cli/internals/secrethub/tpl/fakes" @@ -477,7 +477,7 @@ func TestRunCommand_Run(t *testing.T) { }{ "success, no secrets": { command: RunCommand{ - io: ui.NewFakeIO(), + io: fakeui.NewIO(), environment: &environment{ osStat: osStatNotExist, }, @@ -517,7 +517,7 @@ func TestRunCommand_Run(t *testing.T) { "missing": "path/to/unexisting/secret", }, }, - io: ui.NewFakeIO(), + io: fakeui.NewIO(), newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ SecretService: &fakeclient.SecretService{ @@ -542,7 +542,7 @@ func TestRunCommand_Run(t *testing.T) { }, osStat: osStatNotExist, }, - io: ui.NewFakeIO(), + io: fakeui.NewIO(), newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ SecretService: &fakeclient.SecretService{ @@ -587,7 +587,7 @@ func TestRunCommand_Run(t *testing.T) { "os env secret not found": { command: RunCommand{ command: []string{"echo", "test"}, - io: ui.NewFakeIO(), + io: fakeui.NewIO(), environment: &environment{ osEnv: []string{"TEST=secrethub://nonexistent/secret/path"}, osStat: osStatNotExist, @@ -610,7 +610,7 @@ func TestRunCommand_Run(t *testing.T) { command: RunCommand{ ignoreMissingSecrets: true, command: []string{"echo", "test"}, - io: ui.NewFakeIO(), + io: fakeui.NewIO(), environment: &environment{ osEnv: []string{"TEST=secrethub://nonexistent/secret/path"}, osStat: osStatNotExist, @@ -1081,12 +1081,12 @@ func TestRunCommand_RunWithFile(t *testing.T) { defer os.Remove(scriptFile) } - fakeIO := ui.NewFakeIO() + fakeIO := fakeui.NewIO() tc.command.io = fakeIO err := tc.command.Run() assert.Equal(t, err, tc.err) - assert.Equal(t, fakeIO.StdOut.String(), tc.expectedStdOut) + assert.Equal(t, fakeIO.Out.String(), tc.expectedStdOut) }) } } diff --git a/internals/secrethub/service_ls_test.go b/internals/secrethub/service_ls_test.go index 795d803b..acb70aba 100644 --- a/internals/secrethub/service_ls_test.go +++ b/internals/secrethub/service_ls_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -145,7 +145,7 @@ func TestServiceLsCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io if tc.newClientErr != nil { @@ -165,7 +165,7 @@ func TestServiceLsCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/signup_test.go b/internals/secrethub/signup_test.go index 85aaa954..5602839e 100644 --- a/internals/secrethub/signup_test.go +++ b/internals/secrethub/signup_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/secrethub/secrethub-cli/internals/cli/progress/fakeprogress" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/assert" "github.com/secrethub/secrethub-go/pkg/secrethub" @@ -26,7 +26,7 @@ func TestSignUpCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := ui.NewFakeIO() + io := fakeui.NewIO() tc.cmd.io = io io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) @@ -44,7 +44,7 @@ func TestSignUpCommand_Run(t *testing.T) { // Assert assert.Equal(t, err, tc.err) assert.Equal(t, io.PromptOut.String(), tc.promptOut) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } diff --git a/internals/secrethub/variable_reader_test.go b/internals/secrethub/variable_reader_test.go index 875bf672..bd063923 100644 --- a/internals/secrethub/variable_reader_test.go +++ b/internals/secrethub/variable_reader_test.go @@ -3,10 +3,11 @@ package secrethub import ( "testing" - "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" - "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" "github.com/secrethub/secrethub-go/internals/assert" + + "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" ) func TestVariableReader(t *testing.T) { @@ -155,7 +156,7 @@ func TestPromptVariableReader(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := ui.NewFakeIO() + io := fakeui.NewIO() io.PromptIn.Reads = tc.promptIn reader := newPromptMissingVariableReader(reader, io) diff --git a/internals/secrethub/write_test.go b/internals/secrethub/write_test.go index 1eb9e34f..f2ec28c4 100644 --- a/internals/secrethub/write_test.go +++ b/internals/secrethub/write_test.go @@ -8,6 +8,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/clip" "github.com/secrethub/secrethub-cli/internals/cli/clip/fakeclip" "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/assert" @@ -216,12 +217,12 @@ func TestWriteCommand_Run(t *testing.T) { }, nil } - io := ui.NewFakeIO() - io.StdIn.ReadErr = tc.readErr + io := fakeui.NewIO() + io.In.ReadErr = tc.readErr io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptErr = tc.promptErr - io.StdIn.Piped = tc.piped - io.StdIn.Buffer = bytes.NewBufferString(tc.in) + io.In.Piped = tc.piped + io.In.Buffer = bytes.NewBufferString(tc.in) tc.cmd.io = io @@ -233,7 +234,7 @@ func TestWriteCommand_Run(t *testing.T) { assert.Equal(t, argPath, tc.path) assert.Equal(t, argData, tc.data) assert.Equal(t, io.PromptOut.String(), tc.promptOut) - assert.Equal(t, io.StdOut.String(), tc.out) + assert.Equal(t, io.Out.String(), tc.out) }) } } From 4ba99d7900b071e483d7015c5ee4f1e02db18e4d Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 24 Apr 2020 17:27:54 +0200 Subject: [PATCH 070/119] Remove obsolete package This has not been used for a long time and it has some compatibility issues with the ui update. So we can just as well delete it. --- internals/cli/configuration/configuration.go | 193 ------------------- internals/cli/configuration/migrater.go | 56 ------ internals/cli/configuration/migrater_test.go | 104 ---------- 3 files changed, 353 deletions(-) delete mode 100644 internals/cli/configuration/configuration.go delete mode 100644 internals/cli/configuration/migrater.go delete mode 100644 internals/cli/configuration/migrater_test.go diff --git a/internals/cli/configuration/configuration.go b/internals/cli/configuration/configuration.go deleted file mode 100644 index c4a1ff04..00000000 --- a/internals/cli/configuration/configuration.go +++ /dev/null @@ -1,193 +0,0 @@ -package configuration - -import ( - "encoding/json" - "io/ioutil" - "os" - "reflect" - "strings" - "time" - - "github.com/secrethub/secrethub-cli/internals/cli/posix" - - "github.com/secrethub/secrethub-go/internals/api/uuid" - "github.com/secrethub/secrethub-go/internals/errio" - - "github.com/mitchellh/mapstructure" - yaml "gopkg.in/yaml.v2" -) - -var ( - errConfig = errio.Namespace("configuration") - - // ErrDecodeFailed is given when the config cannot be decoded. - ErrDecodeFailed = errConfig.Code("decode_fail").Error("failed to decode config") - // ErrEncodeFailed is given when the config cannot be encoded. - ErrEncodeFailed = errConfig.Code("encode_fail").Error("failed to encode config") - // ErrFileNotFound is given when the config file cannot be found. - ErrFileNotFound = errConfig.Code("not_found").Error("config file not found") - - // ErrTypeNotSet is given when a config has no type specified - ErrTypeNotSet = errConfig.Code("type_not_set").Error("field `type` of config is not set") -) - -// ReadFromFile attempts to read a config as a struct from a file. -// It will unmarshal yaml and json into the destination. -func ReadFromFile(path string, destination interface{}) error { - data, err := readFile(path) - if err != nil { - return err - } - - return Read(data, destination) -} - -// Read attempts to read a config in a destination interface. -func Read(data []byte, destination interface{}) error { - err := yaml.Unmarshal(data, destination) - return err -} - -// ReadConfigurationDataFromFile retrieves the data and attempts to read the config as a ConfigMap -// from a file. This simplifies the process of determining to parse the config as a ConfigMap or to -// directly unmarshal into a struct. -func ReadConfigurationDataFromFile(path string) (ConfigMap, []byte, error) { - data, err := readFile(path) - if err != nil { - return nil, nil, err - } - - configMap, err := ReadMap(data) - return configMap, data, err -} - -// ReadMap attempts to unmarshal a []byte it into the dest map. -// Both json and yaml are supported -func ReadMap(data []byte) (ConfigMap, error) { - var dest ConfigMap - - // Supports both json and yaml - if err := yaml.Unmarshal(data, &dest); err != nil { - return nil, ErrDecodeFailed - } - - return dest, nil -} - -// ParseMap uses mapstructure to convert a ConfigMap into a struct -// -// For example, the following JSON: -// { -// "ssh_key": "/home/joris/.ssh/secrethub" -// } -// -// Can be loaded in a struct of type: -// type UserConfig struct { -// SSHKeyPath string `json:"ssh_key,omitempty"` -// } -// -// decodeHook is used to convert non-standard types into the correct format -func ParseMap(src *ConfigMap, dst interface{}) error { - - c := mapstructure.DecoderConfig{ - TagName: "json", - Result: dst, - WeaklyTypedInput: true, - DecodeHook: decodeHook} - decoder, err := mapstructure.NewDecoder(&c) - - if err != nil { - return err - } - - return decoder.Decode(src) -} - -// decodeHook adds extra decoding functionality to parsing the map. -func decodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if t == reflect.TypeOf(uuid.UUID{}) && f == reflect.TypeOf(string("")) { - return uuid.FromString(data.(string)) - } - - if t == reflect.TypeOf(time.Duration(0)) && f == reflect.TypeOf(string("")) { - return time.ParseDuration(data.(string)) - } - - return data, nil -} - -// WriteToFile attempts to marshal the src arg and write to a file at the given path. -// If the file is of an unsupported extension we cannot determine what type -// of encoding to use, so it defaults to writing encoded json to that file. -// JSON is written in indented 'pretty' format to allow for easy user editing. -func WriteToFile(src interface{}, path string, fileMode os.FileMode) error { - path = strings.ToLower(path) - - var data []byte - var err error - switch { - case strings.HasSuffix(path, ".yml") || strings.HasSuffix(path, ".yaml"): - data, err = yaml.Marshal(src) - if err != nil { - return err - } - case strings.HasSuffix(path, ".json"): - data, err = json.MarshalIndent(src, "", " ") - if err != nil { - return err - } - default: - data, err = json.MarshalIndent(src, "", " ") - if err != nil { - return ErrEncodeFailed - } - } - - return ioutil.WriteFile(path, posix.AddNewLine(data), fileMode) -} - -func readFile(path string) ([]byte, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return nil, ErrFileNotFound - } - - return ioutil.ReadFile(path) -} - -// ConfigMap is the type used for configurations that are still in a map format -// Map format is to make changes in structure with migrations possible -type ConfigMap map[string]interface{} - -// GetVersion returns the version of the configuration file. -// If it is not set, it is assumed that is configuration version 1. -func (c ConfigMap) GetVersion() (int, error) { - version, ok := c["version"] - if !ok { - // Version not set - return 1, nil - } - - ret, ok := version.(int) - - if !ok { - return 0, errConfig.Code("version_wrong_type").Errorf("config value `version` has wrong type %T (actual) != int (expected)", version) - } - - return ret, nil -} - -// GetType returns the type of the configuration file -// If it is not set, it is assumed to have no type. -func (c ConfigMap) GetType() (string, error) { - t, ok := c["type"] - if !ok { - // Config type not set - return "", ErrTypeNotSet - } - - ret, ok := t.(string) - if !ok { - return "", errConfig.Code("type_wrong_type").Errorf("config value `type` has wrong type %T (actual) != string (expected)", t) - } - return ret, nil -} diff --git a/internals/cli/configuration/migrater.go b/internals/cli/configuration/migrater.go deleted file mode 100644 index c3b973d2..00000000 --- a/internals/cli/configuration/migrater.go +++ /dev/null @@ -1,56 +0,0 @@ -package configuration - -import ( - "github.com/secrethub/secrethub-cli/internals/cli/ui" -) - -var ( - // ErrVersionNotReachable is given when the migrater cannot reach the desired version - ErrVersionNotReachable = errConfig.Code("not_reachable").Error("desired config version not reachable") -) - -// Migration defines a function that is used to convert a configuration in ConfigMap format from VersionFrom to VersionTo -type Migration struct { - VersionFrom int - VersionTo int - UpdateFunc func(ui.IO, ConfigMap) (ConfigMap, error) -} - -// MigrateConfigTo attempts to convert a config in ConfigMap format from versionFrom to versionTo -// A list of Migrations has to be passed that form at least a path from versionFrom to versionTo -// Function may fail if the migration path contains dead ends that do not lead to versionTo -// Setting checkOnly to true will not perform the actual migration and can be used to check whether there is a valid -// migration path from one version to another -func MigrateConfigTo(io ui.IO, config ConfigMap, versionFrom int, versionTo int, migrations []Migration, checkOnly bool) (ConfigMap, error) { - var err error - version := versionFrom - for version != versionTo { - migrated := false - for _, m := range migrations { - if m.VersionFrom == version { - if !checkOnly { - config, err = m.migrate(io, config) - } - version = m.VersionTo - migrated = true - break - } - } - - if err != nil { - return config, err - } - if !migrated { - return config, ErrVersionNotReachable - } - - } - - config["version"] = versionTo - - return config, nil -} - -func (m *Migration) migrate(io ui.IO, src ConfigMap) (ConfigMap, error) { - return m.UpdateFunc(io, src) -} diff --git a/internals/cli/configuration/migrater_test.go b/internals/cli/configuration/migrater_test.go deleted file mode 100644 index c577c30e..00000000 --- a/internals/cli/configuration/migrater_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package configuration - -import ( - "testing" - - "github.com/secrethub/secrethub-cli/internals/cli/ui" -) - -var testMigrations = []Migration{ - {VersionFrom: 1, VersionTo: 2, UpdateFunc: testMigration1}, - {VersionFrom: 2, VersionTo: 3, UpdateFunc: testMigration2}, - {VersionFrom: 3, VersionTo: 4, UpdateFunc: testMigration3}, - {VersionFrom: 4, VersionTo: 5, UpdateFunc: testMigration3}, -} - -func TestConfigMigrater(t *testing.T) { - - config, err := getTestConfig() - if err != nil { - t.Fatal(err) - } - version, err := config.GetVersion() - if err != nil { - t.Error(err) - } - - goalVersion := 4 - - config, err = MigrateConfigTo(ui.NewFakeIO(), config, version, goalVersion, testMigrations, false) - - if err != nil { - t.Error(err) - } - - if !(config["applied_migration_1"] == true && config["applied_migration_2"] == true && config["applied_migration_3"] == true && config["migration_count"] == 3) { - t.Errorf("Not all migrations were applied correctly. This was the result: %s", config) - } - - if config["change"] == "not_changed" { - t.Errorf("Migration did not correctly change `change`") - } - - if config["not_change"] != "not_changed" { - t.Errorf("Migration changed `not_change`") - } - -} - -func TestConfigMigrater_NotReachable(t *testing.T) { - config, err := getTestConfig() - if err != nil { - t.Fatal(err) - } - version, err := config.GetVersion() - if err != nil { - t.Error(err) - } - - goalVersion := 6 - - _, err = MigrateConfigTo(ui.NewFakeIO(), config, version, goalVersion, testMigrations, false) - - if err != ErrVersionNotReachable { - t.Error("Did not throw a ErrVersionNotReachable for an unreachable version") - } -} - -func getTestConfig() (ConfigMap, error) { - - data := []byte(`{` + - `"applied_migration_1": false,` + - `"applied_migration_2": false,` + - `"not_change": "not_changed",` + - `"change": "not_changed",` + - `"migration_count": 0` + - `}`) - - return ReadMap(data) -} - -func testMigration1(_ ui.IO, src ConfigMap) (ConfigMap, error) { - - src["applied_migration_1"] = true - src["change"] = "changed" - src["migration_count"] = src["migration_count"].(int) + 1 - - return src, nil -} - -func testMigration2(_ ui.IO, src ConfigMap) (ConfigMap, error) { - - src["applied_migration_2"] = true - src["migration_count"] = src["migration_count"].(int) + 1 - - return src, nil -} - -func testMigration3(_ ui.IO, src ConfigMap) (ConfigMap, error) { - - src["applied_migration_3"] = true - src["migration_count"] = src["migration_count"].(int) + 1 - - return src, nil -} From ea95a55cfda4cf0b5d3eeb873584f8110686ce1e Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:06:23 +0200 Subject: [PATCH 071/119] Only construct colored output for Windows once --- internals/cli/ui/io_windows.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internals/cli/ui/io_windows.go b/internals/cli/ui/io_windows.go index b6ad113f..b39d9f53 100644 --- a/internals/cli/ui/io_windows.go +++ b/internals/cli/ui/io_windows.go @@ -12,19 +12,21 @@ import ( // windowsIO is the Windows-specific implementation of the IO interface. type windowsIO struct { standardIO + coloredOutput io.Writer } // NewUserIO creates a new windowsIO. func NewUserIO() IO { return windowsIO{ - standardIO: newStdUserIO(), + standardIO: newStdUserIO(), + coloredOutput: colorable.NewColorable(os.Stdout), } } // Stdout returns the standardIO's Output. func (o windowsIO) Output() io.Writer { if !color.NoColor { - return colorable.NewColorable(os.Stdout) + return o.coloredOutput } return o.output } From 50edf44cea3439c5c585fd6f90f922f6c2e942dd Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:08:40 +0200 Subject: [PATCH 072/119] Pass Stdout instead of Output() in run The exec package checks if an os.File is passed. So we should explicitly set Stdout and not a wrapped writer. --- internals/secrethub/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/run.go b/internals/secrethub/run.go index 705e743a..0826e148 100644 --- a/internals/secrethub/run.go +++ b/internals/secrethub/run.go @@ -112,10 +112,10 @@ func (cmd *RunCommand) Run() error { command.Env = environment command.Stdin = os.Stdin if cmd.noMasking { - command.Stdout = cmd.io.Output() + command.Stdout = cmd.io.Stdout() command.Stderr = os.Stderr } else { - command.Stdout = m.AddStream(cmd.io.Output()) + command.Stdout = m.AddStream(cmd.io.Stdout()) command.Stderr = m.AddStream(os.Stderr) go m.Start() From 773182184d9405d46911c2eb13d07c4b5be65412 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:14:54 +0200 Subject: [PATCH 073/119] Rename IsStdinPiped and IsStdoutPiped to IsInputPiped/IsOutputPiped These names better describe the abstraction. Stdin and Stdout are implementations of input and output. The interface should not describe the implementation. --- internals/cli/ui/fakeui/testing.go | 4 ++-- internals/cli/ui/io.go | 10 +++++----- internals/cli/ui/io_unix.go | 6 +++--- internals/secrethub/inject.go | 4 ++-- internals/secrethub/service_deploy_winrm.go | 2 +- internals/secrethub/write.go | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 5cec7841..9d3ec220 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -61,11 +61,11 @@ func (f *FakeIO) Prompts() (io.Reader, io.Writer, error) { return f.PromptIn, f.PromptOut, f.PromptErr } -func (f *FakeIO) IsStdinPiped() bool { +func (f *FakeIO) IsInputPiped() bool { return f.In.Piped } -func (f *FakeIO) IsStdoutPiped() bool { +func (f *FakeIO) IsOutputPiped() bool { return f.Out.Piped } diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index bc05a7f3..60661dd3 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -23,8 +23,8 @@ type IO interface { Stdin() *os.File Stdout() *os.File Prompts() (io.Reader, io.Writer, error) - IsStdinPiped() bool - IsStdoutPiped() bool + IsInputPiped() bool + IsOutputPiped() bool } // standardIO is a middleware between input and output to the CLI program. @@ -66,17 +66,17 @@ func (o standardIO) Output() io.Writer { // available. On systems where tty is not available and when either input // or output is piped, prompting is not possible so an error is returned. func (o standardIO) Prompts() (io.Reader, io.Writer, error) { - if o.IsStdoutPiped() || o.IsStdinPiped() { + if o.IsOutputPiped() || o.IsInputPiped() { return nil, nil, ErrCannotAsk } return o.input, o.output, nil } -func (o standardIO) IsStdinPiped() bool { +func (o standardIO) IsInputPiped() bool { return isPiped(o.input) } -func (o standardIO) IsStdoutPiped() bool { +func (o standardIO) IsOutputPiped() bool { return isPiped(o.output) } diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 5e60ca7a..5d993818 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -34,17 +34,17 @@ func NewUserIO() IO { // available. On systems where tty is not available and when either input // or output is piped, prompting is not possible so an error is returned. func (o ttyIO) Prompts() (io.Reader, io.Writer, error) { - if o.IsStdoutPiped() || o.IsStdinPiped() { + if o.IsOutputPiped() || o.IsInputPiped() { return o.tty, o.tty, nil } return o.input, o.output, nil } -func (o ttyIO) IsStdinPiped() bool { +func (o ttyIO) IsInputPiped() bool { return isPiped(o.input) } -func (o ttyIO) IsStdoutPiped() bool { +func (o ttyIO) IsOutputPiped() bool { return isPiped(o.output) } diff --git a/internals/secrethub/inject.go b/internals/secrethub/inject.go index 23f18af3..e8718bd9 100644 --- a/internals/secrethub/inject.go +++ b/internals/secrethub/inject.go @@ -91,7 +91,7 @@ func (cmd *InjectCommand) Run() error { return ErrReadFile(cmd.inFile, err) } } else { - if !cmd.io.IsStdinPiped() { + if !cmd.io.IsInputPiped() { return ErrNoDataOnStdin } @@ -139,7 +139,7 @@ func (cmd *InjectCommand) Run() error { } else if cmd.outFile != "" { _, err := os.Stat(cmd.outFile) if err == nil && !cmd.force { - if cmd.io.IsStdoutPiped() { + if cmd.io.IsOutputPiped() { return ErrFileAlreadyExists } diff --git a/internals/secrethub/service_deploy_winrm.go b/internals/secrethub/service_deploy_winrm.go index 0b2abd6e..8c6c5d5c 100644 --- a/internals/secrethub/service_deploy_winrm.go +++ b/internals/secrethub/service_deploy_winrm.go @@ -179,7 +179,7 @@ func (cmd *ServiceDeployWinRmCommand) Run() error { deployer := newWindowsDeployer(client, destinationPath) - if !cmd.io.IsStdinPiped() { + if !cmd.io.IsInputPiped() { return ErrNoDataOnStdin } diff --git a/internals/secrethub/write.go b/internals/secrethub/write.go index adfbd7a5..9ccc813e 100644 --- a/internals/secrethub/write.go +++ b/internals/secrethub/write.go @@ -84,7 +84,7 @@ func (cmd *WriteCommand) Run() error { if err != nil { return ErrReadFile(cmd.inFile, err) } - } else if cmd.io.IsStdinPiped() { + } else if cmd.io.IsInputPiped() { data, err = ioutil.ReadAll(cmd.io.Input()) if err != nil { return ui.ErrReadInput(err) From 33a839c8d6e088c147bf101570db002901a260ef Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:15:28 +0200 Subject: [PATCH 074/119] Fix disappeared newlines --- internals/cli/ui/io_unix.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index 5d993818..f6633c56 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -75,7 +75,9 @@ func isPiped(file *os.File) bool { } return (stat.Mode() & os.ModeCharDevice) == 0 -} // eofKey returns the key(s) that should be pressed to enter an EOF. +} + +// eofKey returns the key(s) that should be pressed to enter an EOF. func eofKey() string { return "CTRL-D" } From 1cb0640015ec98b653d486044ace48e7b2eace6c Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:21:03 +0200 Subject: [PATCH 075/119] Fix comments for Prompts --- internals/cli/ui/io.go | 6 ++---- internals/cli/ui/io_unix.go | 5 +---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 60661dd3..eee9daf4 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -61,10 +61,8 @@ func (o standardIO) Output() io.Writer { } // Prompts simply returns Stdin and Stdout, when both input and output are -// not piped. When either input or output is piped, Prompts attempts to -// bypass stdin and stdout by connecting to /dev/tty on Unix systems when -// available. On systems where tty is not available and when either input -// or output is piped, prompting is not possible so an error is returned. +// not piped. When either input or output is piped, it returns an error because standardIO does not have +// access to a tty for prompting. func (o standardIO) Prompts() (io.Reader, io.Writer, error) { if o.IsOutputPiped() || o.IsInputPiped() { return nil, nil, ErrCannotAsk diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index f6633c56..b6d99946 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -29,10 +29,7 @@ func NewUserIO() IO { } // Prompts simply returns Stdin and Stdout, when both input and output are -// not piped. When either input or output is piped, Prompts attempts to -// bypass stdin and stdout by connecting to /dev/tty on Unix systems when -// available. On systems where tty is not available and when either input -// or output is piped, prompting is not possible so an error is returned. +// not piped. When either input or output is piped, Prompts bypasses stdin and stdout by returning the tty. func (o ttyIO) Prompts() (io.Reader, io.Writer, error) { if o.IsOutputPiped() || o.IsInputPiped() { return o.tty, o.tty, nil From d6c9960b467e541abe57b58f46580e82323eee25 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 12:43:57 +0200 Subject: [PATCH 076/119] Setup empty files for fakeui Stdout and Stdin We're not actually doing anyting with the contents, but the secrethub run tests expect there to be a file. --- internals/cli/ui/ask_test.go | 10 ++++----- internals/cli/ui/fakeui/testing.go | 22 ++++++++++++++++++- internals/secrethub/account_inspect_test.go | 2 +- internals/secrethub/acl_check_test.go | 2 +- internals/secrethub/acl_list_test.go | 2 +- internals/secrethub/acl_rm_test.go | 2 +- internals/secrethub/acl_set_test.go | 2 +- internals/secrethub/audit_repo_test.go | 2 +- internals/secrethub/audit_secret_test.go | 2 +- internals/secrethub/generate_test.go | 2 +- internals/secrethub/inspect_secret_test.go | 2 +- .../secrethub/inspect_secret_version_test.go | 2 +- internals/secrethub/mkdir_test.go | 2 +- internals/secrethub/org_init_test.go | 2 +- internals/secrethub/org_inspect_test.go | 2 +- internals/secrethub/org_invite_test.go | 2 +- internals/secrethub/org_list_users_test.go | 2 +- internals/secrethub/org_ls_test.go | 2 +- internals/secrethub/org_revoke_test.go | 2 +- internals/secrethub/org_rm_test.go | 2 +- internals/secrethub/org_set_role_test.go | 2 +- internals/secrethub/repo_init_test.go | 2 +- internals/secrethub/repo_inspect_test.go | 2 +- internals/secrethub/repo_invite_test.go | 2 +- internals/secrethub/repo_ls_test.go | 2 +- internals/secrethub/repo_revoke_test.go | 2 +- internals/secrethub/repo_rm_test.go | 2 +- internals/secrethub/run_test.go | 12 +++++----- internals/secrethub/service_ls_test.go | 2 +- internals/secrethub/signup_test.go | 2 +- internals/secrethub/variable_reader_test.go | 2 +- internals/secrethub/write_test.go | 2 +- 32 files changed, 61 insertions(+), 41 deletions(-) diff --git a/internals/cli/ui/ask_test.go b/internals/cli/ui/ask_test.go index ebbbe961..8d6c6cff 100644 --- a/internals/cli/ui/ask_test.go +++ b/internals/cli/ui/ask_test.go @@ -35,7 +35,7 @@ func TestAskWithDefault(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Reads = tc.in // Run @@ -96,7 +96,7 @@ func TestConfirmCaseInsensitive(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) // Run @@ -241,7 +241,7 @@ func TestAskYesNo(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Reads = tc.in // Run @@ -321,7 +321,7 @@ func TestChoose(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Reads = tc.in // Run @@ -417,7 +417,7 @@ func TestChooseDynamicOptions(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Reads = tc.in if tc.getOptions == nil { diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 9d3ec220..9f1d5de8 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -5,8 +5,13 @@ package fakeui import ( "bytes" "errors" + "fmt" "io" "os" + "path/filepath" + "testing" + + "github.com/secrethub/secrethub-go/internals/assert" ) // FakeIO is a helper type for testing that implements the ui.IO interface @@ -21,7 +26,20 @@ type FakeIO struct { } // NewIO creates a new FakeIO with empty buffers. -func NewIO() *FakeIO { +func NewIO(t *testing.T) *FakeIO { + tempDir := os.TempDir() + stdIn, err := os.Create(filepath.Join(tempDir, "in")) + assert.OK(t, err) + stdOut, err := os.Create(filepath.Join(tempDir, "out")) + assert.OK(t, err) + + t.Cleanup(func() { + err := os.RemoveAll(tempDir) + if err != nil { + fmt.Printf("could not remove temp dir: %s", err) + } + }) + return &FakeIO{ In: &FakeReader{ Buffer: &bytes.Buffer{}, @@ -29,6 +47,8 @@ func NewIO() *FakeIO { Out: &FakeWriter{ Buffer: &bytes.Buffer{}, }, + StdIn: stdIn, + StdOut: stdOut, PromptIn: &FakeReader{ Buffer: &bytes.Buffer{}, }, diff --git a/internals/secrethub/account_inspect_test.go b/internals/secrethub/account_inspect_test.go index c16b719b..c6d8f62a 100644 --- a/internals/secrethub/account_inspect_test.go +++ b/internals/secrethub/account_inspect_test.go @@ -85,7 +85,7 @@ func TestAccountInspect(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/acl_check_test.go b/internals/secrethub/acl_check_test.go index 34bd45d4..93868a26 100644 --- a/internals/secrethub/acl_check_test.go +++ b/internals/secrethub/acl_check_test.go @@ -105,7 +105,7 @@ func TestACLCheckCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io lister := tc.lister diff --git a/internals/secrethub/acl_list_test.go b/internals/secrethub/acl_list_test.go index 12179055..ef94ee7b 100644 --- a/internals/secrethub/acl_list_test.go +++ b/internals/secrethub/acl_list_test.go @@ -149,7 +149,7 @@ func TestACLListCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io tc.cmd.newClient = func() (secrethub.ClientInterface, error) { diff --git a/internals/secrethub/acl_rm_test.go b/internals/secrethub/acl_rm_test.go index acba56d4..aae8f9ee 100644 --- a/internals/secrethub/acl_rm_test.go +++ b/internals/secrethub/acl_rm_test.go @@ -86,7 +86,7 @@ func TestACLRmCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.in) io.PromptErr = tc.promptErr tc.cmd.io = io diff --git a/internals/secrethub/acl_set_test.go b/internals/secrethub/acl_set_test.go index 4b5ef5b1..5f801a7f 100644 --- a/internals/secrethub/acl_set_test.go +++ b/internals/secrethub/acl_set_test.go @@ -107,7 +107,7 @@ func TestACLSetCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.in) io.PromptErr = tc.askErr tc.cmd.io = io diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index ca1baeb6..231d6ffe 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -205,7 +205,7 @@ func TestAuditRepoCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index c9bd4f7e..55f38f88 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -171,7 +171,7 @@ func TestAuditSecretCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/generate_test.go b/internals/secrethub/generate_test.go index 87335c31..086d41d4 100644 --- a/internals/secrethub/generate_test.go +++ b/internals/secrethub/generate_test.go @@ -184,7 +184,7 @@ func TestGenerateSecretCommand_run(t *testing.T) { }, tc.clientCreationErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/inspect_secret_test.go b/internals/secrethub/inspect_secret_test.go index 9880a351..42edbaa8 100644 --- a/internals/secrethub/inspect_secret_test.go +++ b/internals/secrethub/inspect_secret_test.go @@ -104,7 +104,7 @@ func TestInspectSecret_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/inspect_secret_version_test.go b/internals/secrethub/inspect_secret_version_test.go index 69ad1911..dfd0653a 100644 --- a/internals/secrethub/inspect_secret_version_test.go +++ b/internals/secrethub/inspect_secret_version_test.go @@ -79,7 +79,7 @@ func TestInspectSecretVersion_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index 54a2eafe..8113a5a2 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -73,7 +73,7 @@ func TestMkDirCommand(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := fakeui.NewIO() + io := fakeui.NewIO(t) cmd := MkDirCommand{ io: io, path: api.DirPath(tc.path), diff --git a/internals/secrethub/org_init_test.go b/internals/secrethub/org_init_test.go index bbbefe28..c298d1d3 100644 --- a/internals/secrethub/org_init_test.go +++ b/internals/secrethub/org_init_test.go @@ -73,7 +73,7 @@ func TestOrgInitCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/org_inspect_test.go b/internals/secrethub/org_inspect_test.go index 4b67ec1e..063e76f0 100644 --- a/internals/secrethub/org_inspect_test.go +++ b/internals/secrethub/org_inspect_test.go @@ -156,7 +156,7 @@ func TestOrgInspectCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/org_invite_test.go b/internals/secrethub/org_invite_test.go index 605812bd..0aebe43c 100644 --- a/internals/secrethub/org_invite_test.go +++ b/internals/secrethub/org_invite_test.go @@ -111,7 +111,7 @@ func TestOrgInviteCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.in) tc.cmd.io = io diff --git a/internals/secrethub/org_list_users_test.go b/internals/secrethub/org_list_users_test.go index c381b987..4ef90049 100644 --- a/internals/secrethub/org_list_users_test.go +++ b/internals/secrethub/org_list_users_test.go @@ -84,7 +84,7 @@ func TestOrgListUsersCommand_run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/org_ls_test.go b/internals/secrethub/org_ls_test.go index 7eef1d95..7e987b85 100644 --- a/internals/secrethub/org_ls_test.go +++ b/internals/secrethub/org_ls_test.go @@ -144,7 +144,7 @@ func TestOrgLsCommand_run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/org_revoke_test.go b/internals/secrethub/org_revoke_test.go index ffae5ee1..2b84c7d2 100644 --- a/internals/secrethub/org_revoke_test.go +++ b/internals/secrethub/org_revoke_test.go @@ -130,7 +130,7 @@ func TestOrgRevokeCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) tc.cmd.io = io diff --git a/internals/secrethub/org_rm_test.go b/internals/secrethub/org_rm_test.go index 7b73967c..32caaf03 100644 --- a/internals/secrethub/org_rm_test.go +++ b/internals/secrethub/org_rm_test.go @@ -95,7 +95,7 @@ func TestOrgRmCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptErr = tc.promptErr tc.cmd.io = io diff --git a/internals/secrethub/org_set_role_test.go b/internals/secrethub/org_set_role_test.go index 6138f342..a403f40d 100644 --- a/internals/secrethub/org_set_role_test.go +++ b/internals/secrethub/org_set_role_test.go @@ -80,7 +80,7 @@ func TestOrgSetRoleCommand_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/repo_init_test.go b/internals/secrethub/repo_init_test.go index 135eb525..ac40fea8 100644 --- a/internals/secrethub/repo_init_test.go +++ b/internals/secrethub/repo_init_test.go @@ -72,7 +72,7 @@ func TestRepoInitCommand_Run(t *testing.T) { } } - io := fakeui.NewIO() + io := fakeui.NewIO(t) cmd.io = io // Run diff --git a/internals/secrethub/repo_inspect_test.go b/internals/secrethub/repo_inspect_test.go index e894adf5..f6349630 100644 --- a/internals/secrethub/repo_inspect_test.go +++ b/internals/secrethub/repo_inspect_test.go @@ -115,7 +115,7 @@ func TestInspectRepo_Run(t *testing.T) { }, tc.newClientErr } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Act diff --git a/internals/secrethub/repo_invite_test.go b/internals/secrethub/repo_invite_test.go index 58557183..07d20dbc 100644 --- a/internals/secrethub/repo_invite_test.go +++ b/internals/secrethub/repo_invite_test.go @@ -99,7 +99,7 @@ func TestRepoInviteCommand_Run(t *testing.T) { } } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/repo_ls_test.go b/internals/secrethub/repo_ls_test.go index af7decc5..514d1634 100644 --- a/internals/secrethub/repo_ls_test.go +++ b/internals/secrethub/repo_ls_test.go @@ -120,7 +120,7 @@ func TestRepoLSCommand_run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io if tc.newClientErr != nil { diff --git a/internals/secrethub/repo_revoke_test.go b/internals/secrethub/repo_revoke_test.go index 1098982a..6fc4e53a 100644 --- a/internals/secrethub/repo_revoke_test.go +++ b/internals/secrethub/repo_revoke_test.go @@ -341,7 +341,7 @@ func TestRepoRevokeCommand_Run(t *testing.T) { } } - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io // Run diff --git a/internals/secrethub/repo_rm_test.go b/internals/secrethub/repo_rm_test.go index 77809888..840328f9 100644 --- a/internals/secrethub/repo_rm_test.go +++ b/internals/secrethub/repo_rm_test.go @@ -134,7 +134,7 @@ func TestRepoRmCommand_Run(t *testing.T) { } } - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptIn.ReadErr = tc.promptReadErr io.PromptErr = tc.promptErr diff --git a/internals/secrethub/run_test.go b/internals/secrethub/run_test.go index fe27dee6..fd3b1694 100644 --- a/internals/secrethub/run_test.go +++ b/internals/secrethub/run_test.go @@ -477,7 +477,7 @@ func TestRunCommand_Run(t *testing.T) { }{ "success, no secrets": { command: RunCommand{ - io: fakeui.NewIO(), + io: fakeui.NewIO(t), environment: &environment{ osStat: osStatNotExist, }, @@ -517,7 +517,7 @@ func TestRunCommand_Run(t *testing.T) { "missing": "path/to/unexisting/secret", }, }, - io: fakeui.NewIO(), + io: fakeui.NewIO(t), newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ SecretService: &fakeclient.SecretService{ @@ -542,7 +542,7 @@ func TestRunCommand_Run(t *testing.T) { }, osStat: osStatNotExist, }, - io: fakeui.NewIO(), + io: fakeui.NewIO(t), newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ SecretService: &fakeclient.SecretService{ @@ -587,7 +587,7 @@ func TestRunCommand_Run(t *testing.T) { "os env secret not found": { command: RunCommand{ command: []string{"echo", "test"}, - io: fakeui.NewIO(), + io: fakeui.NewIO(t), environment: &environment{ osEnv: []string{"TEST=secrethub://nonexistent/secret/path"}, osStat: osStatNotExist, @@ -610,7 +610,7 @@ func TestRunCommand_Run(t *testing.T) { command: RunCommand{ ignoreMissingSecrets: true, command: []string{"echo", "test"}, - io: fakeui.NewIO(), + io: fakeui.NewIO(t), environment: &environment{ osEnv: []string{"TEST=secrethub://nonexistent/secret/path"}, osStat: osStatNotExist, @@ -1081,7 +1081,7 @@ func TestRunCommand_RunWithFile(t *testing.T) { defer os.Remove(scriptFile) } - fakeIO := fakeui.NewIO() + fakeIO := fakeui.NewIO(t) tc.command.io = fakeIO err := tc.command.Run() diff --git a/internals/secrethub/service_ls_test.go b/internals/secrethub/service_ls_test.go index acb70aba..d00152f3 100644 --- a/internals/secrethub/service_ls_test.go +++ b/internals/secrethub/service_ls_test.go @@ -145,7 +145,7 @@ func TestServiceLsCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io if tc.newClientErr != nil { diff --git a/internals/secrethub/signup_test.go b/internals/secrethub/signup_test.go index 5602839e..5a575da7 100644 --- a/internals/secrethub/signup_test.go +++ b/internals/secrethub/signup_test.go @@ -26,7 +26,7 @@ func TestSignUpCommand_Run(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // Setup - io := fakeui.NewIO() + io := fakeui.NewIO(t) tc.cmd.io = io io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) diff --git a/internals/secrethub/variable_reader_test.go b/internals/secrethub/variable_reader_test.go index bd063923..56123aee 100644 --- a/internals/secrethub/variable_reader_test.go +++ b/internals/secrethub/variable_reader_test.go @@ -156,7 +156,7 @@ func TestPromptVariableReader(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.PromptIn.Reads = tc.promptIn reader := newPromptMissingVariableReader(reader, io) diff --git a/internals/secrethub/write_test.go b/internals/secrethub/write_test.go index f2ec28c4..9ff71cfd 100644 --- a/internals/secrethub/write_test.go +++ b/internals/secrethub/write_test.go @@ -217,7 +217,7 @@ func TestWriteCommand_Run(t *testing.T) { }, nil } - io := fakeui.NewIO() + io := fakeui.NewIO(t) io.In.ReadErr = tc.readErr io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptErr = tc.promptErr From 043f61c9b109d4cada8d2963c3d253721b1c30f7 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 14:55:29 +0200 Subject: [PATCH 077/119] Update to go 1.14 This allows us to use t.Cleanup() --- .circleci/config.yml | 4 ++-- Dockerfile | 2 +- go.mod | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36786823..9fe24417 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: - run: golangci-lint run test: docker: - - image: circleci/golang:1.13 + - image: circleci/golang:1.14 steps: - checkout - restore_cache: @@ -22,7 +22,7 @@ jobs: - run: make test verify-goreleaser: docker: - - image: goreleaser/goreleaser:v0.127 + - image: goreleaser/goreleaser:v0.133 steps: - checkout - run: goreleaser check diff --git a/Dockerfile b/Dockerfile index f166bf67..29753135 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.12-alpine as build_base +FROM golang:1.14-alpine as build_base WORKDIR /build ENV GO111MODULE=on RUN apk add --update git diff --git a/go.mod b/go.mod index 57a0f433..f71d461e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/secrethub/secrethub-cli -go 1.13 +go 1.14 require ( bitbucket.org/zombiezen/cardcpx v0.0.0-20150417151802-902f68ff43ef From 794811f8d13ff33b624f9ef2d774f71aff35df63 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 15:13:23 +0200 Subject: [PATCH 078/119] Create files in subdirectory of tmpdir --- internals/cli/ui/fakeui/testing.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 9f1d5de8..5aae5624 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -7,8 +7,8 @@ import ( "errors" "fmt" "io" + "io/ioutil" "os" - "path/filepath" "testing" "github.com/secrethub/secrethub-go/internals/assert" @@ -27,10 +27,11 @@ type FakeIO struct { // NewIO creates a new FakeIO with empty buffers. func NewIO(t *testing.T) *FakeIO { - tempDir := os.TempDir() - stdIn, err := os.Create(filepath.Join(tempDir, "in")) + tempDir, err := ioutil.TempDir("", "") assert.OK(t, err) - stdOut, err := os.Create(filepath.Join(tempDir, "out")) + stdIn, err := ioutil.TempFile(tempDir, "in") + assert.OK(t, err) + stdOut, err := ioutil.TempFile(tempDir, "out") assert.OK(t, err) t.Cleanup(func() { From 004f46bd2525b09249ab9c4e6c96074c05d1a379 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 4 May 2020 15:14:52 +0200 Subject: [PATCH 079/119] Read stdout in test The command ran by secrethub run writes to Stdout() instead of Output(). The test should reflect that to succeed. --- internals/cli/ui/fakeui/testing.go | 4 ++++ internals/secrethub/run_test.go | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 5aae5624..16de046b 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -77,6 +77,10 @@ func (f *FakeIO) Stdout() *os.File { return f.StdOut } +func (f *FakeIO) ReadStdout() ([]byte, error) { + return ioutil.ReadFile(f.StdOut.Name()) +} + // Prompts returns the mocked prompts and error. func (f *FakeIO) Prompts() (io.Reader, io.Writer, error) { return f.PromptIn, f.PromptOut, f.PromptErr diff --git a/internals/secrethub/run_test.go b/internals/secrethub/run_test.go index fd3b1694..fb6d4295 100644 --- a/internals/secrethub/run_test.go +++ b/internals/secrethub/run_test.go @@ -1086,7 +1086,10 @@ func TestRunCommand_RunWithFile(t *testing.T) { err := tc.command.Run() assert.Equal(t, err, tc.err) - assert.Equal(t, fakeIO.Out.String(), tc.expectedStdOut) + + stdout, err := fakeIO.ReadStdout() + assert.OK(t, err) + assert.Equal(t, string(stdout), tc.expectedStdOut) }) } } From fc2c793059307b94a9ff4ddea06803f71b20c037 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 5 May 2020 12:13:55 +0200 Subject: [PATCH 080/119] Make NewFallbackPager unexported --- internals/secrethub/pager/pager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/pager/pager.go b/internals/secrethub/pager/pager.go index bc035796..f1780e7b 100644 --- a/internals/secrethub/pager/pager.go +++ b/internals/secrethub/pager/pager.go @@ -29,7 +29,7 @@ type pager struct { func NewWithFallback(outputWriter io.Writer) (io.WriteCloser, error) { pager, err := New(outputWriter) if err == ErrPagerNotFound { - return NewFallbackPager(outputWriter), nil + return newFallbackPager(outputWriter), nil } else if err != nil { return nil, err } @@ -134,7 +134,7 @@ func pagerCommand() (string, error) { // newFallbackPaginatedWriter returns a pager that closes after outputting a fixed number of lines without pagination // and returns errPagerNotFound on the last (or any subsequent) write. -func NewFallbackPager(w io.Writer) io.WriteCloser { +func newFallbackPager(w io.Writer) io.WriteCloser { return &fallbackPager{ linesLeft: fallbackPagerLineCount, writer: w, From aeaaf25f8ea23a2d499951a6f970c98c08156be3 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Tue, 5 May 2020 12:19:53 +0200 Subject: [PATCH 081/119] Use timestamps formatted to RFC3339 when outputting json --- internals/secrethub/audit.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 4eb3605f..4880dbb2 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -76,7 +76,11 @@ func (cmd *AuditCommand) Run() error { // beforeRun configures the command using the flag values. func (cmd *AuditCommand) beforeRun() { - cmd.timeFormatter = NewTimeFormatter(cmd.useTimestamps) + if cmd.format == formatJSON { + cmd.timeFormatter = NewTimeFormatter(true) + } else { + cmd.timeFormatter = NewTimeFormatter(cmd.useTimestamps) + } } // Run prints all audit events for the given repository or secret. From ee19601418b11add5bdc9e8294588723231dc6b8 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 8 May 2020 14:57:02 +0200 Subject: [PATCH 082/119] Reintroduce ReadPassword() in IO interface This makes it possible to mock this functionality in tests. --- internals/cli/ui/ask.go | 6 ++--- internals/cli/ui/fakeui/testing.go | 22 +++++++++++----- internals/cli/ui/io.go | 21 ++++++++------- internals/cli/ui/io_unix.go | 4 +++ internals/secrethub/write_test.go | 42 +++++++++++++++++++----------- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/internals/cli/ui/ask.go b/internals/cli/ui/ask.go index bbd88be3..9d3012ec 100644 --- a/internals/cli/ui/ask.go +++ b/internals/cli/ui/ask.go @@ -52,7 +52,7 @@ func AskWithDefault(io IO, question, defaultValue string) (string, error) { // AskSecret prints out the question and reads back the input, // without echoing it back. Useful for passwords and other sensitive inputs. func AskSecret(io IO, question string) (string, error) { - promptIn, promptOut, err := io.Prompts() + _, promptOut, err := io.Prompts() if err != nil { return "", err } @@ -62,14 +62,14 @@ func AskSecret(io IO, question string) (string, error) { return "", err } - raw, err := readPassword(promptIn) + raw, err := io.ReadPassword() if err != nil { return "", ErrReadInput(err) } fmt.Fprintln(promptOut, "") - return raw, nil + return string(raw), nil } // AskMultiline prints out the question and reads back the input until an EOF is reached. diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 16de046b..41de11a0 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -16,13 +16,14 @@ import ( // FakeIO is a helper type for testing that implements the ui.IO interface type FakeIO struct { - In *FakeReader - Out *FakeWriter - StdIn *os.File - StdOut *os.File - PromptIn *FakeReader - PromptOut *FakeWriter - PromptErr error + In *FakeReader + Out *FakeWriter + StdIn *os.File + StdOut *os.File + PromptIn *FakeReader + PromptOut *FakeWriter + PasswordReader *FakeReader + PromptErr error } // NewIO creates a new FakeIO with empty buffers. @@ -50,6 +51,9 @@ func NewIO(t *testing.T) *FakeIO { }, StdIn: stdIn, StdOut: stdOut, + PasswordReader: &FakeReader{ + Buffer: &bytes.Buffer{}, + }, PromptIn: &FakeReader{ Buffer: &bytes.Buffer{}, }, @@ -94,6 +98,10 @@ func (f *FakeIO) IsOutputPiped() bool { return f.Out.Piped } +func (f *FakeIO) ReadPassword() ([]byte, error) { + return ioutil.ReadAll(f.PasswordReader) +} + // FakeReader implements the Reader interface. type FakeReader struct { *bytes.Buffer diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index eee9daf4..5ade4986 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -23,6 +23,7 @@ type IO interface { Stdin() *os.File Stdout() *os.File Prompts() (io.Reader, io.Writer, error) + ReadPassword() ([]byte, error) IsInputPiped() bool IsOutputPiped() bool } @@ -78,22 +79,22 @@ func (o standardIO) IsOutputPiped() bool { return isPiped(o.output) } +func (o standardIO) ReadPassword() ([]byte, error) { + return readPassword(o.input) +} + // readPassword reads one line of input from the terminal without echoing the user input. -func readPassword(r io.Reader) (string, error) { - file, ok := r.(*os.File) - if !ok { - return "", ErrCannotAsk - } +func readPassword(f *os.File) ([]byte, error) { // this case happens among other things when input is piped and ReadPassword is called. - if !terminal.IsTerminal(int(file.Fd())) { - return "", ErrCannotAsk + if !terminal.IsTerminal(int(f.Fd())) { + return nil, ErrCannotAsk } - password, err := terminal.ReadPassword(int(file.Fd())) + password, err := terminal.ReadPassword(int(f.Fd())) if err != nil { - return "", err + return nil, err } - return string(password), nil + return password, nil } // Readln reads 1 line of input from a io.Reader. The newline character is not included in the response. diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index b6d99946..c167a723 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -63,6 +63,10 @@ func (o ttyIO) Output() io.Writer { return o.output } +func (o ttyIO) ReadPassword() ([]byte, error) { + return readPassword(o.tty) +} + // isPiped checks whether the file is a pipe. // If the file does not exist, it returns false. func isPiped(file *os.File) bool { diff --git a/internals/secrethub/write_test.go b/internals/secrethub/write_test.go index 75fbadd8..af00dc07 100644 --- a/internals/secrethub/write_test.go +++ b/internals/secrethub/write_test.go @@ -20,18 +20,20 @@ func TestWriteCommand_Run(t *testing.T) { testErr := errio.Namespace("test").Code("test").Error("test error") cases := map[string]struct { - cmd WriteCommand - writeFunc func(path string, data []byte) (*api.SecretVersion, error) - in string - piped bool - promptIn string - promptOut string - promptErr error - readErr error - err error - path api.SecretPath - data []byte - out string + cmd WriteCommand + writeFunc func(path string, data []byte) (*api.SecretVersion, error) + in string + piped bool + promptIn string + promptOut string + promptErr error + passwordIn string + passwordErr error + readErr error + err error + path api.SecretPath + data []byte + out string }{ "path with version": { cmd: WriteCommand{ @@ -124,8 +126,8 @@ func TestWriteCommand_Run(t *testing.T) { cmd: WriteCommand{ path: "namespace/repo/secret", }, - promptIn: "asked secret value", - promptOut: "Please type in the value of the secret, followed by an [ENTER]:\n", + passwordIn: "asked secret value", + promptOut: "Please type in the value of the secret, followed by an [ENTER]:\n", writeFunc: func(path string, data []byte) (*api.SecretVersion, error) { return &api.SecretVersion{ Version: 1, @@ -136,13 +138,21 @@ func TestWriteCommand_Run(t *testing.T) { data: []byte("asked secret value"), out: "Writing secret value...\nWrite complete! The given value has been written to namespace/repo/secret:1\n", }, - "ask secret error": { + "ask secret prompt error": { cmd: WriteCommand{ path: "namespace/repo/secret", }, promptErr: testErr, err: testErr, }, + "ask secret read password error": { + cmd: WriteCommand{ + path: "namespace/repo/secret", + }, + promptOut: "Please type in the value of the secret, followed by an [ENTER]:", + passwordErr: testErr, + err: ui.ErrReadInput(testErr), + }, "piped read error": { cmd: WriteCommand{ path: "namespace/repo/secret", @@ -205,6 +215,8 @@ func TestWriteCommand_Run(t *testing.T) { io.In.ReadErr = tc.readErr io.PromptIn.Buffer = bytes.NewBufferString(tc.promptIn) io.PromptErr = tc.promptErr + io.PasswordReader.Buffer = bytes.NewBufferString(tc.passwordIn) + io.PasswordReader.ReadErr = tc.passwordErr io.In.Piped = tc.piped io.In.Buffer = bytes.NewBufferString(tc.in) From 4a3c764d2d904acd8a198a3f2e6cabb687c636b8 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 8 May 2020 15:09:02 +0200 Subject: [PATCH 083/119] Rename ReadPassword to ReadSecret and add comments to IO interface The interface really needed some comments. --- internals/cli/ui/ask.go | 2 +- internals/cli/ui/fakeui/testing.go | 2 +- internals/cli/ui/io.go | 25 +++++++++++++++++++------ internals/cli/ui/io_unix.go | 4 ++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/internals/cli/ui/ask.go b/internals/cli/ui/ask.go index 9d3012ec..e5acc48c 100644 --- a/internals/cli/ui/ask.go +++ b/internals/cli/ui/ask.go @@ -62,7 +62,7 @@ func AskSecret(io IO, question string) (string, error) { return "", err } - raw, err := io.ReadPassword() + raw, err := io.ReadSecret() if err != nil { return "", ErrReadInput(err) } diff --git a/internals/cli/ui/fakeui/testing.go b/internals/cli/ui/fakeui/testing.go index 41de11a0..4f620fd1 100644 --- a/internals/cli/ui/fakeui/testing.go +++ b/internals/cli/ui/fakeui/testing.go @@ -98,7 +98,7 @@ func (f *FakeIO) IsOutputPiped() bool { return f.Out.Piped } -func (f *FakeIO) ReadPassword() ([]byte, error) { +func (f *FakeIO) ReadSecret() ([]byte, error) { return ioutil.ReadAll(f.PasswordReader) } diff --git a/internals/cli/ui/io.go b/internals/cli/ui/io.go index 5ade4986..380adb36 100644 --- a/internals/cli/ui/io.go +++ b/internals/cli/ui/io.go @@ -18,13 +18,26 @@ var ( // IO is an interface to work with input/output. type IO interface { + // Input returns an io,Reader that reads input for the current process. + // If the process's input is piped, this reads from the pipe otherwise it asks input from the user. Input() io.Reader + // Output returns an io.Writer that writes output for the current process. + // If the process's output is piped, this writes to the pipe otherwise it prints to the terminal. Output() io.Writer + // Stdin returns the *os.File of the current process's stdin stream. Stdin() *os.File + // Stdin returns the *os.File of the current process's stdout stream. Stdout() *os.File + // Prompts returns an io.Reader and io.Writer that read and write directly to/from the terminal, even if the + // input or output of the current process is piped. + // If this is not supported, an error is returned. Prompts() (io.Reader, io.Writer, error) - ReadPassword() ([]byte, error) + // ReadSecret reads a line of input from the terminal while hiding the entered characters. + // Returns an error if secret input is not supported. + ReadSecret() ([]byte, error) + // IsInputPiped returns whether the current process's input is piped from another process. IsInputPiped() bool + // IsOutputPiped returns whether the current process's output is piped to another process. IsOutputPiped() bool } @@ -79,13 +92,13 @@ func (o standardIO) IsOutputPiped() bool { return isPiped(o.output) } -func (o standardIO) ReadPassword() ([]byte, error) { - return readPassword(o.input) +func (o standardIO) ReadSecret() ([]byte, error) { + return readSecret(o.input) } -// readPassword reads one line of input from the terminal without echoing the user input. -func readPassword(f *os.File) ([]byte, error) { - // this case happens among other things when input is piped and ReadPassword is called. +// readSecret reads one line of input from the terminal without echoing the user input. +func readSecret(f *os.File) ([]byte, error) { + // this case happens among other things when input is piped and ReadSecret is called. if !terminal.IsTerminal(int(f.Fd())) { return nil, ErrCannotAsk } diff --git a/internals/cli/ui/io_unix.go b/internals/cli/ui/io_unix.go index c167a723..e97196ce 100644 --- a/internals/cli/ui/io_unix.go +++ b/internals/cli/ui/io_unix.go @@ -63,8 +63,8 @@ func (o ttyIO) Output() io.Writer { return o.output } -func (o ttyIO) ReadPassword() ([]byte, error) { - return readPassword(o.tty) +func (o ttyIO) ReadSecret() ([]byte, error) { + return readSecret(o.tty) } // isPiped checks whether the file is a pipe. From 04e8b68716555b318f999030e39b9bc58550d7c3 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 18 May 2020 11:48:49 +0200 Subject: [PATCH 084/119] Update all CircleCI jobs to Go 1.14 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e238df36..f8d72ab4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: arch: type: string docker: - - image: circleci/golang:1.13 + - image: circleci/golang:1.14 steps: - checkout - run: GOOS=<< parameters.os >> GOARCH=<< parameters.arch >> go build ./cmd/secrethub From 29f58b36a3ab13062f2e55eb67b48e63f429eec2 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 18:40:25 +0200 Subject: [PATCH 085/119] Implement printing unaligned table rows when output is piped --- internals/secrethub/audit.go | 2 ++ internals/secrethub/list_formatters.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index f7c1f725..6c01da9f 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -102,6 +102,8 @@ func (cmd *AuditCommand) run() error { var formatter listFormatter if cmd.format == formatJSON { formatter = newJSONFormatter(paginatedWriter, auditTable.header()) + } else if cmd.format == formatTable && cmd.io.IsOutputPiped() { + formatter = newLineFormatter(paginatedWriter) } else if cmd.format == formatTable { terminalWidth, err := cmd.terminalWidth(int(cmd.io.Stdout().Fd())) if err != nil { diff --git a/internals/secrethub/list_formatters.go b/internals/secrethub/list_formatters.go index 13fd3098..9d99459b 100644 --- a/internals/secrethub/list_formatters.go +++ b/internals/secrethub/list_formatters.go @@ -11,6 +11,21 @@ type listFormatter interface { Write([]string) error } +func newLineFormatter(writer io.Writer) lineFormatter { + return lineFormatter{writer: writer} +} + +// lineFormatter returns a formatter that formats the given table into lines of text with unaligned columns. +type lineFormatter struct { + writer io.Writer +} + +// Write writes a the given row entries separated by '\t' characters. +func (l lineFormatter) Write(line []string) error { + _, err := l.writer.Write([]byte(strings.Join(line, "\t") + "\n")) + return err +} + // newJSONFormatter returns a table formatter that formats the given table rows as json. func newJSONFormatter(writer io.Writer, fieldNames []string) *jsonFormatter { for i := range fieldNames { From dc029e7cd9d55700cee3f68d49bff85e5c42ecdc Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 18:51:01 +0200 Subject: [PATCH 086/119] Add 1000 line limit when audit output is piped --- internals/secrethub/audit.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 6c01da9f..c7d6070c 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -28,6 +28,7 @@ const ( defaultTerminalWidth = 80 formatTable = "table" formatJSON = "json" + pipedOutputLineLimit = 1000 ) // AuditCommand is a command to audit a repo or a secret. @@ -40,6 +41,7 @@ type AuditCommand struct { newClient newClientFunc terminalWidth func(int) (int, error) perPage int + limit int format string } @@ -88,6 +90,12 @@ func (cmd *AuditCommand) run() error { return fmt.Errorf("per-page should be positive, got %d", cmd.perPage) } + if cmd.io.IsOutputPiped() { + cmd.limit = pipedOutputLineLimit + } else { + cmd.limit = -1 + } + iter, auditTable, err := cmd.iterAndAuditTable() if err != nil { return err @@ -114,7 +122,7 @@ func (cmd *AuditCommand) run() error { return errNoSuchFormat(cmd.format) } - for { + for lineCount := 0; lineCount != cmd.limit; lineCount++ { event, err := iter.Next() if err == iterator.Done { break From f7701f48eef65f1a9f6627024d5b14756be446e8 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 19:01:01 +0200 Subject: [PATCH 087/119] Add limit flag --- internals/secrethub/audit.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index c7d6070c..a2a27888 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -64,6 +64,7 @@ func (cmd *AuditCommand) Register(r command.Registerer) { clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.").HintOptions("table", "json").Default("table").StringVar(&cmd.format) + clause.Flag("limit", "Specify the number of entries to list. If limit < 0 all entries are displayed. If the output of the command is piped, limit defaults to 1000.").Default("-1").IntVar(&cmd.limit) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) @@ -90,10 +91,8 @@ func (cmd *AuditCommand) run() error { return fmt.Errorf("per-page should be positive, got %d", cmd.perPage) } - if cmd.io.IsOutputPiped() { + if cmd.limit == -1 && cmd.io.IsOutputPiped() { cmd.limit = pipedOutputLineLimit - } else { - cmd.limit = -1 } iter, auditTable, err := cmd.iterAndAuditTable() From 7b8769d4550dece55e1e96dd71c57624071962c5 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 19:01:46 +0200 Subject: [PATCH 088/119] Fix typo in comment --- internals/secrethub/list_formatters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/list_formatters.go b/internals/secrethub/list_formatters.go index 9d99459b..7ebb425f 100644 --- a/internals/secrethub/list_formatters.go +++ b/internals/secrethub/list_formatters.go @@ -46,7 +46,7 @@ type jsonFormatter struct { fields []string } -// formatRow returns the json representation of the given row +// Write writes the json representation of the given row // with the configured field names as keys and the provided values func (f *jsonFormatter) Write(values []string) error { if len(f.fields) != len(values) { From 3528acb63e87cf537f55eca5cc35e59cd9ad4260 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 19:32:13 +0200 Subject: [PATCH 089/119] Update tests with limit flag value --- internals/secrethub/audit_repo_test.go | 5 +++++ internals/secrethub/audit_secret_test.go | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index ec6093e5..03b235f7 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -46,6 +46,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, }, out: "", }, @@ -86,6 +87,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(_ int) (int, error) { return 83, nil }, @@ -127,6 +129,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(int) (int, error) { return 83, nil }, @@ -184,6 +187,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(int) (int, error) { return 83, nil }, @@ -219,6 +223,7 @@ func TestAuditRepoCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(int) (int, error) { return 83, nil }, diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 0b6ff7b9..5ec07007 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -59,6 +59,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -90,6 +91,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -156,6 +158,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -184,6 +187,7 @@ func TestAuditSecretCommand_run(t *testing.T) { }, format: formatTable, perPage: 20, + limit: -1, terminalWidth: func(int) (int, error) { return 83, nil }, From a417a2d599786018de8d5aac0a210144bfdee94d Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 18 May 2020 19:41:57 +0200 Subject: [PATCH 090/119] Fix setting default value of limit flag --- internals/secrethub/audit.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index a2a27888..183ff10b 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -3,6 +3,7 @@ package secrethub import ( "fmt" "io" + "strconv" "github.com/secrethub/secrethub-go/internals/errio" @@ -60,11 +61,16 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AuditCommand) Register(r command.Registerer) { + defaultLimit := -1 + if cmd.io.IsOutputPiped() { + defaultLimit = pipedOutputLineLimit + } + clause := r.Command("audit", "Show the audit log.") clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.").HintOptions("table", "json").Default("table").StringVar(&cmd.format) - clause.Flag("limit", "Specify the number of entries to list. If limit < 0 all entries are displayed. If the output of the command is piped, limit defaults to 1000.").Default("-1").IntVar(&cmd.limit) + clause.Flag("limit", "Specify the number of entries to list. If limit < 0 all entries are displayed. If the output of the command is piped, limit defaults to 1000.").Default(strconv.Itoa(defaultLimit)).IntVar(&cmd.limit) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) @@ -91,10 +97,6 @@ func (cmd *AuditCommand) run() error { return fmt.Errorf("per-page should be positive, got %d", cmd.perPage) } - if cmd.limit == -1 && cmd.io.IsOutputPiped() { - cmd.limit = pipedOutputLineLimit - } - iter, auditTable, err := cmd.iterAndAuditTable() if err != nil { return err From d06f55422ef17d3b0620ae519197fa296f78a0c6 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 27 May 2020 11:34:30 +0200 Subject: [PATCH 091/119] Update secrethub-go to include GCP IdP --- go.mod | 11 +- go.sum | 300 +++++++++++++++++++++++++- internals/secrethub/client_factory.go | 4 +- 3 files changed, 308 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fb42264c..ed5b2ab4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( bitbucket.org/zombiezen/cardcpx v0.0.0-20150417151802-902f68ff43ef + cloud.google.com/go v0.57.0 // indirect github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 github.com/atotto/clipboard v0.1.2 github.com/aws/aws-sdk-go v1.25.49 @@ -16,10 +17,12 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 - github.com/secrethub/secrethub-go v0.28.0 + github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 - golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a - golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 - golang.org/x/text v0.3.0 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/sys v0.0.0-20200501052902-10377860bb8e + golang.org/x/text v0.3.2 + google.golang.org/api v0.25.0 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 977a9e18..58b2804a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,34 @@ bitbucket.org/zombiezen/cardcpx v0.0.0-20150417151802-902f68ff43ef h1:Y5Zf3CYdrdGE7GOuK/MNN98GS1V8mOfeiJlISrKUcEo= bitbucket.org/zombiezen/cardcpx v0.0.0-20150417151802-902f68ff43ef/go.mod h1:ZJR5FpaQx7Bt2bzIV3gBaCInI1+kG949WhNYYlRr8eA= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 h1:y8Gs8CzNfDF5AZvjr+5UyGQvQEBL7pwo+v+wX6q9JI8= github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= github.com/alecthomas/kingpin v0.0.0-20190930021037-0a108b7f5563 h1:YT8l7Flq7VNXnjqwtjCF9bzffTPGgedBC+xyj88lVe4= @@ -20,6 +47,12 @@ github.com/aws/aws-sdk-go v1.19.38 h1:WKjobgPO4Ua1ww2NJJl2/zQNreUZxvqmEzwMlRjjm9 github.com/aws/aws-sdk-go v1.19.38/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.49 h1:j5R2Ey+g8qaiy2NJ9iH+KWzDWS4SjXRCjhc22EeQVE4= github.com/aws/aws-sdk-go v1.25.49/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -30,18 +63,72 @@ github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA= github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -68,14 +155,16 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0C github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/secrethub/demo-app v0.1.0 h1:HwPPxuiSvx4TBE7Qppzu3A9eHqmsBrIz4Ko8u8pqMqw= github.com/secrethub/demo-app v0.1.0/go.mod h1:ymjm8+WXTSDTFqsGVBNVmHSnwtZMYi7KptHvpo/fLH4= github.com/secrethub/secrethub-cli v0.30.0/go.mod h1:dC0wd40v+iQdV83/0rUrOa01LYq+8Yj2AtJB1vzh2ao= github.com/secrethub/secrethub-go v0.21.0/go.mod h1:rc2IfKKBJ4L0wGec0u4XnF5/pe0FFPE4Q1MWfrFso7s= -github.com/secrethub/secrethub-go v0.28.0 h1:N46plUaOIqeE51X/qpNY9rCKTVL7TIlS7LJHoF3z1fA= -github.com/secrethub/secrethub-go v0.28.0/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= +github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5 h1:uR96YdjVf1tHpZsdYsXmR1b4CKNNVorSEPALQyfbIA0= +github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -84,24 +173,231 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 h1:U5I57s4ISLpeeLYl8b3MsainSSh9F+mRXauln37b50I= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc h1:TnonUr8u3himcMY0vSh23jFOXA+cnucl1gB6EQTReBI= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internals/secrethub/client_factory.go b/internals/secrethub/client_factory.go index 9e17ff83..e0b87bfb 100644 --- a/internals/secrethub/client_factory.go +++ b/internals/secrethub/client_factory.go @@ -40,7 +40,7 @@ type clientFactory struct { // Register the flags for configuration on a cli application. func (f *clientFactory) Register(r FlagRegisterer) { r.Flag("api-remote", "The SecretHub API address, don't set this unless you know what you're doing.").Hidden().URLVar(&f.ServerURL) - r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) + r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS), `gcp` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) } // NewClient returns a new client that is configured to use the remote that @@ -51,6 +51,8 @@ func (f *clientFactory) NewClient() (secrethub.ClientInterface, error) { switch strings.ToLower(f.identityProvider) { case "aws": credentialProvider = credentials.UseAWS() + case "gcp": + credentialProvider = credentials.UseGCPServiceAccount() case "key": credentialProvider = f.store.Provider() default: From c1699e8cbf6e65608ffd76c707a0ff14d9bfa538 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 27 May 2020 11:35:25 +0200 Subject: [PATCH 092/119] Add service commands for GCP service accounts --- internals/secrethub/service.go | 1 + internals/secrethub/service_gcp.go | 27 ++++++ internals/secrethub/service_gcp_init.go | 104 ++++++++++++++++++++++++ internals/secrethub/service_ls.go | 36 ++++++++ 4 files changed, 168 insertions(+) create mode 100644 internals/secrethub/service_gcp.go create mode 100644 internals/secrethub/service_gcp_init.go diff --git a/internals/secrethub/service.go b/internals/secrethub/service.go index fb636660..9a8d187a 100644 --- a/internals/secrethub/service.go +++ b/internals/secrethub/service.go @@ -23,6 +23,7 @@ func NewServiceCommand(io ui.IO, newClient newClientFunc) *ServiceCommand { func (cmd *ServiceCommand) Register(r command.Registerer) { clause := r.Command("service", "Manage service accounts.") NewServiceAWSCommand(cmd.io, cmd.newClient).Register(clause) + NewServiceGCPCommand(cmd.io, cmd.newClient).Register(clause) NewServiceDeployCommand(cmd.io).Register(clause) NewServiceInitCommand(cmd.io, cmd.newClient).Register(clause) NewServiceLsCommand(cmd.io, cmd.newClient).Register(clause) diff --git a/internals/secrethub/service_gcp.go b/internals/secrethub/service_gcp.go new file mode 100644 index 00000000..bd7bb867 --- /dev/null +++ b/internals/secrethub/service_gcp.go @@ -0,0 +1,27 @@ +package secrethub + +import ( + "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/secrethub/command" +) + +// ServiceGCPCommand handles GCP services. +type ServiceGCPCommand struct { + io ui.IO + newClient newClientFunc +} + +// NewServiceGCPCommand creates a new ServiceGCPCommand. +func NewServiceGCPCommand(io ui.IO, newClient newClientFunc) *ServiceGCPCommand { + return &ServiceGCPCommand{ + io: io, + newClient: newClient, + } +} + +// Register registers the command and its sub-commands on the provided Registerer. +func (cmd *ServiceGCPCommand) Register(r command.Registerer) { + clause := r.Command("gcp", "Manage GCP service accounts.") + NewServiceGCPInitCommand(cmd.io, cmd.newClient).Register(clause) + NewServiceGCPLsCommand(cmd.io, cmd.newClient).Register(clause) +} diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go new file mode 100644 index 00000000..4d186b81 --- /dev/null +++ b/internals/secrethub/service_gcp_init.go @@ -0,0 +1,104 @@ +package secrethub + +import ( + "fmt" + "strings" + + "github.com/secrethub/secrethub-cli/internals/cli/ui" + "github.com/secrethub/secrethub-cli/internals/secrethub/command" + + "github.com/secrethub/secrethub-go/internals/api" + "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" +) + +// ServiceGCPInitCommand initializes a service for GCP. +type ServiceGCPInitCommand struct { + description string + repo api.RepoPath + kmsKeyResourceID string + serviceAccountEmail string + permission string + io ui.IO + newClient newClientFunc +} + +// NewServiceGCPInitCommand creates a new ServiceGCPInitCommand. +func NewServiceGCPInitCommand(io ui.IO, newClient newClientFunc) *ServiceGCPInitCommand { + return &ServiceGCPInitCommand{ + io: io, + newClient: newClient, + } +} + +// Run initializes an GCP service. +func (cmd *ServiceGCPInitCommand) Run() error { + client, err := cmd.newClient() + if err != nil { + return err + } + + if cmd.serviceAccountEmail == "" && cmd.kmsKeyResourceID == "" { + fmt.Fprintln(cmd.io.Stdout(), "This command creates a new service account for use on GCP. For help on this, run `secrethub service gcp init --help`.") + } + + if cmd.serviceAccountEmail == "" { + serviceAccountEmail, err := ui.AskAndValidate(cmd.io, "What is the email of the GCP Service Account that should have access to the service?\n", 3, checkIsNotEmpty("service account email")) + if err != nil { + return err + } + cmd.serviceAccountEmail = strings.TrimSpace(serviceAccountEmail) + } + + if cmd.kmsKeyResourceID == "" { + kmsKey, err := ui.AskAndValidate(cmd.io, "What is the Resource ID of the KMS-key that should be used for encrypting the service's account key?\n", 3, checkIsNotEmpty("kms key")) + if err != nil { + return err + } + cmd.kmsKeyResourceID = strings.TrimSpace(kmsKey) + } + + if cmd.description == "" { + cmd.description = "GCP Service Account " + roleNameFromRole(cmd.serviceAccountEmail) + } + + service, err := client.Services().Create(cmd.repo.Value(), cmd.description, credentials.CreateGCPServiceAccount(cmd.serviceAccountEmail, cmd.kmsKeyResourceID)) + if err == api.ErrCredentialAlreadyExists { + return ErrRoleAlreadyTaken + } else if err != nil { + return err + } + + if cmd.permission != "" { + err = givePermission(service, cmd.repo, cmd.permission, client) + if err != nil { + return err + } + } + + fmt.Fprintln(cmd.io.Stdout(), "Successfully created a new service account with ID: "+service.ServiceID) + fmt.Fprintf(cmd.io.Stdout(), "Any host that assumes the Service Account %s can now automatically authenticate to SecretHub and fetch the secrets the service has been given access to.\n", cmd.serviceAccountEmail) + + return nil +} + +// Register registers the command, arguments and flags on the provided Registerer. +func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { + clause := r.Command("init", "Create a new service account that is tied to an GCP IAM role.") + clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) + clause.Flag("kms-key", "The Resource ID of the KMS-key to be used for encrypting the service's account key.").StringVar(&cmd.kmsKeyResourceID) + clause.Flag("service-account-email", "The email of the GCP Service Account that should have access to this service account.").StringVar(&cmd.serviceAccountEmail) + clause.Flag("description", "A description for the service so others will recognize it. Defaults to the name of the role that is attached to the service.").StringVar(&cmd.description) + clause.Flag("descr", "").Hidden().StringVar(&cmd.description) + clause.Flag("desc", "").Hidden().StringVar(&cmd.description) + clause.Flag("permission", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.").StringVar(&cmd.permission) + + clause.HelpLong("The native GCP identity provider uses a combination of GCP IAM and GCP KMS to provide access to SecretHub for any service running on GCP. For this to work, a GCP Service Account and a KMS key are needed.\n" + + "\n" + + " - The GCP Service Account should be the service account that is assumed by the service during execution.\n" + + " - The KMS key is a key that is used for encryption of the account. Decryption permission on this key must be granted to the previously described GCP Service Account.\n" + + "\n" + + "To create a new service that uses the GCP identity provider, the CLI must have encryption access to the KMS key that will be used by the service account. Therefore GCP credentials should be configured on this system. For details on how this can be done, see https://cloud.google.com/sdk/docs/quickstarts.\n", + ) + + command.BindAction(clause, cmd.Run) +} diff --git a/internals/secrethub/service_ls.go b/internals/secrethub/service_ls.go index aaa6d0fe..2b540692 100644 --- a/internals/secrethub/service_ls.go +++ b/internals/secrethub/service_ls.go @@ -46,6 +46,18 @@ func NewServiceAWSLsCommand(io ui.IO, newClient newClientFunc) *ServiceLsCommand } } +func NewServiceGCPLsCommand(io ui.IO, newClient newClientFunc) *ServiceLsCommand { + return &ServiceLsCommand{ + io: io, + newClient: newClient, + newServiceTable: newGCPServiceTable, + filters: []func(service *api.Service) bool{ + isGCPService, + }, + help: "List all GCP service accounts in a given repository.", + } +} + // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceLsCommand) Register(r command.Registerer) { clause := r.Command("ls", cmd.help) @@ -162,3 +174,27 @@ func isAWSService(service *api.Service) bool { return service.Credential.Type == api.CredentialTypeAWS } + +type gcpServiceTable struct { + baseServiceTable +} + +func newGCPServiceTable(timeFormatter TimeFormatter) serviceTable { + return gcpServiceTable{baseServiceTable{timeFormatter: timeFormatter}} +} + +func (sw gcpServiceTable) header() []string { + return sw.baseServiceTable.header("SERVICE-ACCOUNT-EMAIL", "KMS-KEY") +} + +func (sw gcpServiceTable) row(service *api.Service) []string { + return sw.baseServiceTable.row(service, service.Credential.Metadata[api.CredentialMetadataGCPServiceAccountEmail], service.Credential.Metadata[api.CredentialMetadataGCPKMSKeyResourceID]) +} + +func isGCPService(service *api.Service) bool { + if service == nil { + return false + } + + return service.Credential.Type == api.CredentialTypeGCPServiceAccount +} From 98b1d595d9a78b99e744fbc819a2c79cc3bc774e Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 28 May 2020 12:21:25 +0200 Subject: [PATCH 093/119] Directly validate email address on prompt --- go.mod | 1 + internals/secrethub/service_gcp_init.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ed5b2ab4..edaa1168 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( bitbucket.org/zombiezen/cardcpx v0.0.0-20150417151802-902f68ff43ef cloud.google.com/go v0.57.0 // indirect github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 + github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/atotto/clipboard v0.1.2 github.com/aws/aws-sdk-go v1.25.49 github.com/docker/go-units v0.3.3 diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index 4d186b81..d6e54880 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -1,9 +1,12 @@ package secrethub import ( + "errors" "fmt" "strings" + "github.com/asaskevich/govalidator" + "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" @@ -42,7 +45,7 @@ func (cmd *ServiceGCPInitCommand) Run() error { } if cmd.serviceAccountEmail == "" { - serviceAccountEmail, err := ui.AskAndValidate(cmd.io, "What is the email of the GCP Service Account that should have access to the service?\n", 3, checkIsNotEmpty("service account email")) + serviceAccountEmail, err := ui.AskAndValidate(cmd.io, "What is the email of the GCP Service Account that should have access to the service?\n", 3, checkValidEmail) if err != nil { return err } @@ -102,3 +105,10 @@ func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { command.BindAction(clause, cmd.Run) } + +func checkValidEmail(v string) error { + if !govalidator.IsEmail(v) { + return errors.New("invalid email") + } + return nil +} From 367a8c751d9f1f63a97f82697b11e06e3147d1af Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 17:27:38 +0200 Subject: [PATCH 094/119] Rename --limit flag to --max-results --- internals/secrethub/audit.go | 6 ++--- internals/secrethub/audit_repo_test.go | 30 ++++++++++++------------ internals/secrethub/audit_secret_test.go | 24 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 183ff10b..3871585d 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -42,7 +42,7 @@ type AuditCommand struct { newClient newClientFunc terminalWidth func(int) (int, error) perPage int - limit int + maxResults int format string } @@ -70,7 +70,7 @@ func (cmd *AuditCommand) Register(r command.Registerer) { clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.").HintOptions("table", "json").Default("table").StringVar(&cmd.format) - clause.Flag("limit", "Specify the number of entries to list. If limit < 0 all entries are displayed. If the output of the command is piped, limit defaults to 1000.").Default(strconv.Itoa(defaultLimit)).IntVar(&cmd.limit) + clause.Flag("max-results", "Specify the number of entries to list. If maxResults < 0 all entries are displayed. If the output of the command is piped, maxResults defaults to 1000.").Default(strconv.Itoa(defaultLimit)).IntVar(&cmd.maxResults) registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) command.BindAction(clause, cmd.Run) @@ -123,7 +123,7 @@ func (cmd *AuditCommand) run() error { return errNoSuchFormat(cmd.format) } - for lineCount := 0; lineCount != cmd.limit; lineCount++ { + for lineCount := 0; lineCount != cmd.maxResults; lineCount++ { event, err := iter.Next() if err == iterator.Done { break diff --git a/internals/secrethub/audit_repo_test.go b/internals/secrethub/audit_repo_test.go index 03b235f7..6d5bad37 100644 --- a/internals/secrethub/audit_repo_test.go +++ b/internals/secrethub/audit_repo_test.go @@ -44,9 +44,9 @@ func TestAuditRepoCommand_run(t *testing.T) { terminalWidth: func(int) (int, error) { return 83, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, }, out: "", }, @@ -85,9 +85,9 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(_ int) (int, error) { return 83, nil }, @@ -127,9 +127,9 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(int) (int, error) { return 83, nil }, @@ -185,9 +185,9 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(int) (int, error) { return 83, nil }, @@ -221,9 +221,9 @@ func TestAuditRepoCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(int) (int, error) { return 83, nil }, diff --git a/internals/secrethub/audit_secret_test.go b/internals/secrethub/audit_secret_test.go index 5ec07007..4bffd59d 100644 --- a/internals/secrethub/audit_secret_test.go +++ b/internals/secrethub/audit_secret_test.go @@ -57,9 +57,9 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -89,9 +89,9 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -156,9 +156,9 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(_ int) (int, error) { return 46, nil }, @@ -185,9 +185,9 @@ func TestAuditSecretCommand_run(t *testing.T) { }, }, nil }, - format: formatTable, - perPage: 20, - limit: -1, + format: formatTable, + perPage: 20, + maxResults: -1, terminalWidth: func(int) (int, error) { return 83, nil }, From c1229de6ba3a52ed81854a9351a5d6c77dd58d6d Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 18:28:04 +0200 Subject: [PATCH 095/119] Implement accepting multiple arguments for mkdir command --- internals/secrethub/mkdir.go | 50 +++++++++++++++++------- internals/secrethub/path_placeholders.go | 1 + 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 226c76b0..93dc40b4 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -17,7 +17,7 @@ var ( // MkDirCommand creates a new directory inside a repository. type MkDirCommand struct { io ui.IO - path api.DirPath + paths dirPathList parents bool newClient newClientFunc } @@ -33,7 +33,7 @@ func NewMkDirCommand(io ui.IO, newClient newClientFunc) *MkDirCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *MkDirCommand) Register(r command.Registerer) { clause := r.Command("mkdir", "Create a new directory.") - clause.Arg("dir-path", "The path to the directory").Required().PlaceHolder(dirPathPlaceHolder).SetValue(&cmd.path) + clause.Arg("dir-paths", "The paths to the directories").Required().PlaceHolder(dirPathsPlaceHolder).SetValue(&cmd.paths) clause.Flag("parents", "Create parent directories if needed. Does not error when directories already exist.").BoolVar(&cmd.parents) command.BindAction(clause, cmd.Run) @@ -41,8 +41,10 @@ func (cmd *MkDirCommand) Register(r command.Registerer) { // Run executes the command. func (cmd *MkDirCommand) Run() error { - if cmd.path.IsRepoPath() { - return ErrMkDirOnRootDir + for _, path := range cmd.paths { + if path.IsRepoPath() { + return ErrMkDirOnRootDir + } } client, err := cmd.newClient() @@ -50,19 +52,39 @@ func (cmd *MkDirCommand) Run() error { return err } - if cmd.parents { - err = client.Dirs().CreateAll(cmd.path.Value()) - if err != nil { - return err - } - } else { - _, err = client.Dirs().Create(cmd.path.Value()) - if err != nil { - return err + for _, path := range cmd.paths { + if cmd.parents { + err = client.Dirs().CreateAll(path.Value()) + if err != nil { + return err + } + } else { + _, err = client.Dirs().Create(path.Value()) + if err != nil { + return err + } } + + fmt.Fprintf(cmd.io.Stdout(), "Created a new directory at %s\n", path) } + return nil +} + +type dirPathList []api.DirPath - fmt.Fprintf(cmd.io.Stdout(), "Created a new directory at %s\n", cmd.path) +func (d *dirPathList) String() string { + return "" +} +func (d *dirPathList) Set(path string) error { + dirPath, err := api.NewDirPath(path) + if err != nil { + return err + } + *d = append(*d, dirPath) return nil } + +func (d *dirPathList) IsCumulative() bool { + return true +} diff --git a/internals/secrethub/path_placeholders.go b/internals/secrethub/path_placeholders.go index 42c3e9cd..1aead2d7 100644 --- a/internals/secrethub/path_placeholders.go +++ b/internals/secrethub/path_placeholders.go @@ -3,6 +3,7 @@ package secrethub const ( repoPathPlaceHolder = "/" dirPathPlaceHolder = repoPathPlaceHolder + "/[/ ...]" + dirPathsPlaceHolder = dirPathPlaceHolder + "..." optionalDirPathPlaceHolder = repoPathPlaceHolder + "[/ ...]" secretPathPlaceHolder = optionalDirPathPlaceHolder + "/" secretPathOptionalVersionPlaceHolder = secretPathPlaceHolder + "[:]" From 9782a75bba13a5e69e9765e6bc630df99e06d521 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 18:31:41 +0200 Subject: [PATCH 096/119] Fix tests --- internals/secrethub/mkdir_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index acfd2b56..8a4337f2 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -16,13 +16,13 @@ import ( func TestMkDirCommand(t *testing.T) { cases := map[string]struct { - path string + paths []string newClient func() (secrethub.ClientInterface, error) stdout string err error }{ "success": { - path: "namespace/repo/dir", + paths: []string{"namespace/repo/dir"}, newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ DirService: &fakeclient.DirService{ @@ -43,12 +43,12 @@ func TestMkDirCommand(t *testing.T) { err: nil, }, "on root dir": { - path: "namespace/repo", + paths: []string{"namespace/repo"}, stdout: "", err: ErrMkDirOnRootDir, }, "new client fails": { - path: "namespace/repo/dir", + paths: []string{"namespace/repo/dir"}, newClient: func() (secrethub.ClientInterface, error) { return nil, errio.Namespace("test").Code("foo").Error("bar") }, @@ -56,7 +56,7 @@ func TestMkDirCommand(t *testing.T) { err: errio.Namespace("test").Code("foo").Error("bar"), }, "create dir fails": { - path: "namespace/repo/dir", + paths: []string{"namespace/repo/dir"}, newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ DirService: &fakeclient.DirService{ @@ -74,9 +74,13 @@ func TestMkDirCommand(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { io := ui.NewFakeIO() + dirPaths := dirPathList{} + for _, path := range tc.paths { + _ = dirPaths.Set(path) + } cmd := MkDirCommand{ io: io, - path: api.DirPath(tc.path), + paths: dirPaths, newClient: tc.newClient, } From c1b921d2d7aa6e7c1e073ec8dfa4653e6aa14c70 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 18:36:01 +0200 Subject: [PATCH 097/119] Add tests for mkdir with multiple args --- internals/secrethub/mkdir_test.go | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index 8a4337f2..c5fee816 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -42,6 +42,27 @@ func TestMkDirCommand(t *testing.T) { stdout: "Created a new directory at namespace/repo/dir\n", err: nil, }, + "success multiple dirs": { + paths: []string{"namespace/repo/dir1", "namespace/repo/dir2"}, + newClient: func() (secrethub.ClientInterface, error) { + return fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + return &api.Dir{ + DirID: uuid.New(), + BlindName: "blindname", + Name: "dir", + Status: api.StatusOK, + CreatedAt: time.Now().UTC(), + LastModifiedAt: time.Now().UTC(), + }, nil + }, + }, + }, nil + }, + stdout: "Created a new directory at namespace/repo/dir1\nCreated a new directory at namespace/repo/dir2\n", + err: nil, + }, "on root dir": { paths: []string{"namespace/repo"}, stdout: "", @@ -69,6 +90,30 @@ func TestMkDirCommand(t *testing.T) { stdout: "", err: api.ErrDirAlreadyExists, }, + "create dir fails on second dir": { + paths: []string{"namespace/repo/dir1", "namespace/repo/dir2"}, + newClient: func() (secrethub.ClientInterface, error) { + return fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + if path == "namespace/repo/dir2" { + return nil, api.ErrDirAlreadyExists + } + return &api.Dir{ + DirID: uuid.New(), + BlindName: "blindname", + Name: "dir", + Status: api.StatusOK, + CreatedAt: time.Now().UTC(), + LastModifiedAt: time.Now().UTC(), + }, nil + }, + }, + }, nil + }, + stdout: "Created a new directory at namespace/repo/dir1\n", + err: api.ErrDirAlreadyExists, + }, } for name, tc := range cases { From dd39a7ec8d179d8a1987021600fcfb90ad1aba50 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 18:43:44 +0200 Subject: [PATCH 098/119] Add comments --- internals/secrethub/mkdir.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 93dc40b4..258bfc56 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -70,12 +70,14 @@ func (cmd *MkDirCommand) Run() error { return nil } +// dirPathList represents the value of a repeatable directory path argument. type dirPathList []api.DirPath func (d *dirPathList) String() string { return "" } +// Set adds a new directory path to the list. func (d *dirPathList) Set(path string) error { dirPath, err := api.NewDirPath(path) if err != nil { From ef890b129627e5d45ba782aa6e7842ff864c5745 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Sat, 30 May 2020 18:54:18 +0200 Subject: [PATCH 099/119] Move dir path validation to Set method --- internals/secrethub/mkdir.go | 11 ++++------ internals/secrethub/mkdir_test.go | 34 ++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 258bfc56..60b5fcb4 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -41,12 +41,6 @@ func (cmd *MkDirCommand) Register(r command.Registerer) { // Run executes the command. func (cmd *MkDirCommand) Run() error { - for _, path := range cmd.paths { - if path.IsRepoPath() { - return ErrMkDirOnRootDir - } - } - client, err := cmd.newClient() if err != nil { return err @@ -77,12 +71,15 @@ func (d *dirPathList) String() string { return "" } -// Set adds a new directory path to the list. +// Set validates and adds a new directory path to the list. func (d *dirPathList) Set(path string) error { dirPath, err := api.NewDirPath(path) if err != nil { return err } + if dirPath.IsRepoPath() { + return ErrMkDirOnRootDir + } *d = append(*d, dirPath) return nil } diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index c5fee816..8b1f1810 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -63,11 +63,6 @@ func TestMkDirCommand(t *testing.T) { stdout: "Created a new directory at namespace/repo/dir1\nCreated a new directory at namespace/repo/dir2\n", err: nil, }, - "on root dir": { - paths: []string{"namespace/repo"}, - stdout: "", - err: ErrMkDirOnRootDir, - }, "new client fails": { paths: []string{"namespace/repo/dir"}, newClient: func() (secrethub.ClientInterface, error) { @@ -136,3 +131,32 @@ func TestMkDirCommand(t *testing.T) { }) } } + +func TestDirPathList_Set(t *testing.T) { + cases := map[string]struct { + path string + expected dirPathList + err error + }{ + "success": { + path: "namespace/repo/dir", + expected: dirPathList{"namespace/repo/dir"}, + }, + "root dir": { + path: "namespace/repo", + err: ErrMkDirOnRootDir, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + list := dirPathList{} + err := list.Set(tc.path) + assert.Equal(t, err, tc.err) + assert.Equal(t, len(list), len(tc.expected)) + for i := range list { + assert.Equal(t, list[i], tc.expected[i]) + } + }) + } +} From c2b5f4418a6e9f2957aecabc17536eec6c227404 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 10:44:14 +0200 Subject: [PATCH 100/119] Hide GCP subcommand As long is this command is in private beta, it remains hidden. --- internals/secrethub/service_gcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/service_gcp.go b/internals/secrethub/service_gcp.go index bd7bb867..ab9ee0d8 100644 --- a/internals/secrethub/service_gcp.go +++ b/internals/secrethub/service_gcp.go @@ -21,7 +21,7 @@ func NewServiceGCPCommand(io ui.IO, newClient newClientFunc) *ServiceGCPCommand // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ServiceGCPCommand) Register(r command.Registerer) { - clause := r.Command("gcp", "Manage GCP service accounts.") + clause := r.Command("gcp", "Manage GCP service accounts.").Hidden() NewServiceGCPInitCommand(cmd.io, cmd.newClient).Register(clause) NewServiceGCPLsCommand(cmd.io, cmd.newClient).Register(clause) } From 7092a1253037028a96bb9978f860879ae8bdc821 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 10:46:05 +0200 Subject: [PATCH 101/119] Update to latest version of secrethub-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index edaa1168..52b23bbc 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 - github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5 + github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e diff --git a/go.sum b/go.sum index 58b2804a..39ee9c8f 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,8 @@ github.com/secrethub/demo-app v0.1.0 h1:HwPPxuiSvx4TBE7Qppzu3A9eHqmsBrIz4Ko8u8pq github.com/secrethub/demo-app v0.1.0/go.mod h1:ymjm8+WXTSDTFqsGVBNVmHSnwtZMYi7KptHvpo/fLH4= github.com/secrethub/secrethub-cli v0.30.0/go.mod h1:dC0wd40v+iQdV83/0rUrOa01LYq+8Yj2AtJB1vzh2ao= github.com/secrethub/secrethub-go v0.21.0/go.mod h1:rc2IfKKBJ4L0wGec0u4XnF5/pe0FFPE4Q1MWfrFso7s= -github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5 h1:uR96YdjVf1tHpZsdYsXmR1b4CKNNVorSEPALQyfbIA0= -github.com/secrethub/secrethub-go v0.27.1-0.20200527092505-0a02b49a0aa5/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= +github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81 h1:gGp4NCf/2N2epcNdVEqwF025gY0h6DOmS7xS7DciyZI= +github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= From 71c5818cc33b9913b2cdeb0274afb9e522e27e50 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 12:25:00 +0200 Subject: [PATCH 102/119] Support user in choosing options for creating GCP service account --- go.mod | 2 +- internals/cli/ui/ask.go | 37 ++++- internals/secrethub/service_gcp_init.go | 204 +++++++++++++++++++++++- 3 files changed, 227 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 52b23bbc..e44d0d1a 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 - github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81 + github.com/secrethub/secrethub-go v0.27.1-0.20200603102047-1a4e50eafb91 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e diff --git a/internals/cli/ui/ask.go b/internals/cli/ui/ask.go index ff314aa1..9b7bd0e5 100644 --- a/internals/cli/ui/ask.go +++ b/internals/cli/ui/ask.go @@ -272,6 +272,28 @@ func (o Option) String() string { return o.Display } +func ChooseDynamicOptionsValidate(io IO, question string, getOptions func() ([]Option, bool, error), optionName string, validateFunc func(string) error) (string, error) { + r, w, err := io.Prompts() + if err != nil { + return "", err + } + + if optionName == "" { + optionName = "option" + } + + s := selecter{ + r: r, + w: w, + getOptions: getOptions, + question: question, + addOwn: true, + validateFunc: validateFunc, + optionName: optionName, + } + return s.run() +} + func ChooseDynamicOptions(io IO, question string, getOptions func() ([]Option, bool, error), addOwn bool, optionName string) (string, error) { r, w, err := io.Prompts() if err != nil { @@ -294,12 +316,13 @@ func ChooseDynamicOptions(io IO, question string, getOptions func() ([]Option, b } type selecter struct { - r io.Reader - w io.Writer - getOptions func() ([]Option, bool, error) - question string - addOwn bool - optionName string + r io.Reader + w io.Writer + getOptions func() ([]Option, bool, error) + validateFunc func(string) error + question string + addOwn bool + optionName string done bool options []Option @@ -359,7 +382,7 @@ func (s *selecter) process() (string, error) { choice, err := strconv.Atoi(in) if err != nil || choice < 1 || choice > len(s.options) { if s.addOwn { - return in, nil + return in, s.validateFunc(in) } fmt.Fprintln(os.Stderr, fmt.Sprintf("%s is not a valid choice", in)) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index d6e54880..e4c8279f 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -1,14 +1,21 @@ package secrethub import ( - "errors" + "context" "fmt" "strings" + "sync" + "time" - "github.com/asaskevich/govalidator" + "google.golang.org/api/cloudkms/v1" + "google.golang.org/api/cloudresourcemanager/v1" + "google.golang.org/api/iam/v1" + "google.golang.org/api/option" + "google.golang.org/api/transport" "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/secrethub/secrethub-go/internals/gcp" "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" @@ -42,10 +49,47 @@ func (cmd *ServiceGCPInitCommand) Run() error { if cmd.serviceAccountEmail == "" && cmd.kmsKeyResourceID == "" { fmt.Fprintln(cmd.io.Stdout(), "This command creates a new service account for use on GCP. For help on this, run `secrethub service gcp init --help`.") + + var projectID string + creds, err := transport.Creds(context.Background()) + if err == nil && creds.ProjectID != "" { + projectID = creds.ProjectID + } else { + var projectLister gcpProjectOptionLister + chosenProjectID, err := ui.ChooseDynamicOptions(cmd.io, "What GCP project do you want to use?", projectLister.Options, true, "project") + if err != nil { + return err + } + + projectID = chosenProjectID + } + + serviceAccountLister := gcpServiceAccountOptionLister{ + ProjectID: projectID, + } + serviceAccountEmail, err := ui.ChooseDynamicOptionsValidate(cmd.io, "What is the email of the service account you want to use?", serviceAccountLister.Options, "service account", api.ValidateGCPServiceAccountEmail) + if err != nil { + return err + } + cmd.serviceAccountEmail = serviceAccountEmail + + kmsKeyLister, err := newGCPKeyOptionsLister(projectID) + if err != nil { + return err + } + keyring, err := ui.ChooseDynamicOptions(cmd.io, "In which keyring is the KMS key you want to use for encrypting the service account's key?", kmsKeyLister.KeyringOptions, true, "keyring") + if err != nil { + return err + } + kmsKey, err := ui.ChooseDynamicOptions(cmd.io, "What is the KMS key you want to use for encrypting the service account's key?", kmsKeyLister.KeyOptions(keyring), true, "kms key") + if err != nil { + return err + } + cmd.kmsKeyResourceID = kmsKey } if cmd.serviceAccountEmail == "" { - serviceAccountEmail, err := ui.AskAndValidate(cmd.io, "What is the email of the GCP Service Account that should have access to the service?\n", 3, checkValidEmail) + serviceAccountEmail, err := ui.AskAndValidate(cmd.io, "What is the email of the GCP Service Account that should have access to the service?\n", 3, api.ValidateGCPServiceAccountEmail) if err != nil { return err } @@ -53,7 +97,7 @@ func (cmd *ServiceGCPInitCommand) Run() error { } if cmd.kmsKeyResourceID == "" { - kmsKey, err := ui.AskAndValidate(cmd.io, "What is the Resource ID of the KMS-key that should be used for encrypting the service's account key?\n", 3, checkIsNotEmpty("kms key")) + kmsKey, err := ui.AskAndValidate(cmd.io, "What is the Resource ID of the KMS-key that should be used for encrypting the service's account key?\n", 3, api.ValidateGCPKMSKeyResourceID) if err != nil { return err } @@ -106,9 +150,153 @@ func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { command.BindAction(clause, cmd.Run) } -func checkValidEmail(v string) error { - if !govalidator.IsEmail(v) { - return errors.New("invalid email") +type gcpProjectOptionLister struct { + nextPage string +} + +func (l *gcpProjectOptionLister) Options() ([]ui.Option, bool, error) { + // Explicitly setting the credentials is needed to avoid a permission denied error from cloudresourcemanager. + creds, err := transport.Creds(context.Background()) + if err != nil { + return nil, false, gcp.HandleError(err) + } + + crm, err := cloudresourcemanager.NewService(context.Background(), option.WithTokenSource(creds.TokenSource)) + if err != nil { + return nil, false, gcp.HandleError(err) + } + + resp, err := crm.Projects.List().Filter("lifecycleState:ACTIVE").PageToken(l.nextPage).PageSize(10).Do() + if err != nil { + return nil, false, gcp.HandleError(err) + } + + options := make([]ui.Option, len(resp.Projects)) + for i, project := range resp.Projects { + options[i] = ui.Option{ + Value: project.ProjectId, + Display: fmt.Sprintf("%s (%s)", project.Name, project.ProjectId), + } + } + + l.nextPage = resp.NextPageToken + return options, resp.NextPageToken == "", nil +} + +type gcpServiceAccountOptionLister struct { + ProjectID string + nextPage string +} + +func (l *gcpServiceAccountOptionLister) Options() ([]ui.Option, bool, error) { + iamService, err := iam.NewService(context.Background()) + if err != nil { + return nil, false, gcp.HandleError(err) + } + + resp, err := iamService.Projects.ServiceAccounts.List("projects/" + l.ProjectID).PageToken(l.nextPage).PageSize(10).Do() + if err != nil { + return nil, false, gcp.HandleError(err) + } + + options := make([]ui.Option, len(resp.Accounts)) + for i, account := range resp.Accounts { + options[i] = ui.Option{ + Value: account.Email, + Display: fmt.Sprintf("%s (%s)", account.Email, account.Description), + } + } + + l.nextPage = resp.NextPageToken + return options, resp.NextPageToken == "", nil +} + +func newGCPKeyOptionsLister(projectID string) (*gcpKMSKeyOptionLister, error) { + kmsService, err := cloudkms.NewService(context.Background()) + if err != nil { + return nil, gcp.HandleError(err) + } + + return &gcpKMSKeyOptionLister{ + projectID: projectID, + kmsService: kmsService, + }, nil +} + +type gcpKMSKeyOptionLister struct { + projectID string + nextPage string + kmsService *cloudkms.Service +} + +func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { + var options []ui.Option + + errChan := make(chan error) + resChan := make(chan ui.Option, 16) + var wg sync.WaitGroup + + ctx, cancelTimeout := context.WithTimeout(context.Background(), 15*time.Second) + + err := l.kmsService.Projects.Locations.List("projects/"+l.projectID).Pages(ctx, func(resp *cloudkms.ListLocationsResponse) error { + for _, loc := range resp.Locations { + wg.Add(1) + go func(locationName string) { + err := l.kmsService.Projects.Locations.KeyRings.List(locationName).Pages(ctx, func(resp *cloudkms.ListKeyRingsResponse) error { + for _, keyring := range resp.KeyRings { + resChan <- ui.Option{ + Value: keyring.Name, + Display: keyring.Name, + } + } + return nil + }) + wg.Done() + if err != nil { + select { + case errChan <- err: + default: + } + } + }(loc.Name) + } + return nil + }) + if err != nil { + return nil, false, gcp.HandleError(err) + } + go func() { + wg.Wait() + cancelTimeout() + close(resChan) + }() + for res := range resChan { + options = append(options, res) + } + select { + case err := <-errChan: + return nil, false, err + default: + return options, false, nil + } +} + +func (l *gcpKMSKeyOptionLister) KeyOptions(keyring string) func() ([]ui.Option, bool, error) { + return func() ([]ui.Option, bool, error) { + resp, err := l.kmsService.Projects.Locations.KeyRings.CryptoKeys.List(keyring).PageSize(10).Filter("purpose:ENCRYPT_DECRYPT").PageToken(l.nextPage).Do() + if err != nil { + return nil, false, gcp.HandleError(err) + } + + options := make([]ui.Option, len(resp.CryptoKeys)) + for i, key := range resp.CryptoKeys { + options[i] = ui.Option{ + Value: key.Name, + Display: key.Name, + } + } + + l.nextPage = resp.NextPageToken + return options, resp.NextPageToken == "", nil } - return nil } From 64a110f02f6a36e196191316babf8fb33d73d330 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 13:37:14 +0200 Subject: [PATCH 103/119] Add missing handling of GCP errors --- internals/secrethub/service_gcp_init.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index e4c8279f..28657caa 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -232,7 +232,7 @@ type gcpKMSKeyOptionLister struct { func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { var options []ui.Option - errChan := make(chan error) + errChan := make(chan error, 1) resChan := make(chan ui.Option, 16) var wg sync.WaitGroup @@ -275,7 +275,7 @@ func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { } select { case err := <-errChan: - return nil, false, err + return nil, false, gcp.HandleError(err) default: return options, false, nil } From 8878531c0f0c955bd52d8f3db823b4c8eb77c04d Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 13:51:08 +0200 Subject: [PATCH 104/119] Improve CLI help text --- internals/secrethub/service_gcp_init.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index 28657caa..a7576543 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -123,14 +123,14 @@ func (cmd *ServiceGCPInitCommand) Run() error { } fmt.Fprintln(cmd.io.Stdout(), "Successfully created a new service account with ID: "+service.ServiceID) - fmt.Fprintf(cmd.io.Stdout(), "Any host that assumes the Service Account %s can now automatically authenticate to SecretHub and fetch the secrets the service has been given access to.\n", cmd.serviceAccountEmail) + fmt.Fprintf(cmd.io.Stdout(), "Any host using the Service Account %s can now automatically authenticate to SecretHub and fetch the secrets the service has been given access to.\n", cmd.serviceAccountEmail) return nil } // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Create a new service account that is tied to an GCP IAM role.") + clause := r.Command("init", "Create a new service account that is tied to an GCP Service Account.") clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) clause.Flag("kms-key", "The Resource ID of the KMS-key to be used for encrypting the service's account key.").StringVar(&cmd.kmsKeyResourceID) clause.Flag("service-account-email", "The email of the GCP Service Account that should have access to this service account.").StringVar(&cmd.serviceAccountEmail) @@ -144,7 +144,7 @@ func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { " - The GCP Service Account should be the service account that is assumed by the service during execution.\n" + " - The KMS key is a key that is used for encryption of the account. Decryption permission on this key must be granted to the previously described GCP Service Account.\n" + "\n" + - "To create a new service that uses the GCP identity provider, the CLI must have encryption access to the KMS key that will be used by the service account. Therefore GCP credentials should be configured on this system. For details on how this can be done, see https://cloud.google.com/sdk/docs/quickstarts.\n", + "To create a new service that uses the GCP identity provider, the CLI must have encryption access to the KMS key that will be used by the service account. Therefore GCP application default credentials should be configured on this system. To achieve this, first install the Google Cloud SDK (https://cloud.google.com/sdk/docs/quickstarts) and then run `gcloud auth application-default login`.", ) command.BindAction(clause, cmd.Run) From ce56a0a6ecf015cd71d3bb5d1adfb891d10a0aca Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 13:58:35 +0200 Subject: [PATCH 105/119] Defer cancel --- internals/secrethub/service_gcp_init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index a7576543..24e552d7 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -237,6 +237,7 @@ func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { var wg sync.WaitGroup ctx, cancelTimeout := context.WithTimeout(context.Background(), 15*time.Second) + defer cancelTimeout() err := l.kmsService.Projects.Locations.List("projects/"+l.projectID).Pages(ctx, func(resp *cloudkms.ListLocationsResponse) error { for _, loc := range resp.Locations { @@ -267,7 +268,6 @@ func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { } go func() { wg.Wait() - cancelTimeout() close(resChan) }() for res := range resChan { From 0827d5e47dcd2437e2f571044f4e458ff9d5a7ec Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 14:01:08 +0200 Subject: [PATCH 106/119] Initialize with non-zero capacity to prevent some allocations --- internals/secrethub/service_gcp_init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index 24e552d7..e05d941b 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -230,7 +230,7 @@ type gcpKMSKeyOptionLister struct { } func (l *gcpKMSKeyOptionLister) KeyringOptions() ([]ui.Option, bool, error) { - var options []ui.Option + options := make([]ui.Option, 0, 16) errChan := make(chan error, 1) resChan := make(chan ui.Option, 16) From c31fe2519fabf077c1b25c8de12ad24bdfe8084e Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 14:02:57 +0200 Subject: [PATCH 107/119] Only validate if validateFunc is set --- internals/cli/ui/ask.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internals/cli/ui/ask.go b/internals/cli/ui/ask.go index 9b7bd0e5..e1f2cd08 100644 --- a/internals/cli/ui/ask.go +++ b/internals/cli/ui/ask.go @@ -382,7 +382,10 @@ func (s *selecter) process() (string, error) { choice, err := strconv.Atoi(in) if err != nil || choice < 1 || choice > len(s.options) { if s.addOwn { - return in, s.validateFunc(in) + if s.validateFunc != nil { + return in, s.validateFunc(in) + } + return in, nil } fmt.Fprintln(os.Stderr, fmt.Sprintf("%s is not a valid choice", in)) From 863a6fe2796bb4f66db33acd0cab24837c824320 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 3 Jun 2020 14:18:18 +0200 Subject: [PATCH 108/119] Test service listing with GCP service accounts --- internals/secrethub/service_ls_test.go | 75 ++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/service_ls_test.go b/internals/secrethub/service_ls_test.go index 795d803b..78496feb 100644 --- a/internals/secrethub/service_ls_test.go +++ b/internals/secrethub/service_ls_test.go @@ -47,7 +47,9 @@ func TestServiceLsCommand_Run(t *testing.T) { }, nil }, }, - out: "ID DESCRIPTION TYPE CREATED\ntest foobar key About an hour ago\nsecond foobarbaz key 2 hours ago\n", + out: "" + + "ID DESCRIPTION TYPE CREATED\n" + + "test foobar key About an hour ago\nsecond foobarbaz key 2 hours ago\n", }, "success quiet": { cmd: ServiceLsCommand{ @@ -91,7 +93,9 @@ func TestServiceLsCommand_Run(t *testing.T) { }, nil }, }, - out: "ID DESCRIPTION ROLE KMS-KEY CREATED\ntest foobar arn:aws:iam::123456:role/path/to/role 12345678-1234-1234-1234-123456789012 About an hour ago\n", + out: "" + + "ID DESCRIPTION ROLE KMS-KEY CREATED\n" + + "test foobar arn:aws:iam::123456:role/path/to/role 12345678-1234-1234-1234-123456789012 About an hour ago\n", }, "success aws filter": { cmd: ServiceLsCommand{ @@ -126,7 +130,72 @@ func TestServiceLsCommand_Run(t *testing.T) { }, nil }, }, - out: "ID DESCRIPTION ROLE KMS-KEY CREATED\ntest foobar arn:aws:iam::123456:role/path/to/role arn:aws:kms:us-east-1:123456:key/12345678-1234-1234-1234-123456789012 About an hour ago\n", + out: "" + + "ID DESCRIPTION ROLE KMS-KEY CREATED\n" + + "test foobar arn:aws:iam::123456:role/path/to/role arn:aws:kms:us-east-1:123456:key/12345678-1234-1234-1234-123456789012 About an hour ago\n", + }, + "success gcp": { + cmd: ServiceLsCommand{ + newServiceTable: newGCPServiceTable, + }, + serviceService: fakeclient.ServiceService{ + ListFunc: func(path string) ([]*api.Service, error) { + return []*api.Service{ + { + ServiceID: "test", + Description: "foobar", + Credential: &api.Credential{ + Type: api.CredentialTypeGCPServiceAccount, + Metadata: map[string]string{ + api.CredentialMetadataGCPServiceAccountEmail: "service-account@secrethub-test-1234567890.iam.gserviceaccount.com", + api.CredentialMetadataGCPKMSKeyResourceID: "projects/secrethub-test-1234567890.iam/locations/global/keyRings/test/cryptoKeys/test", + }, + }, + CreatedAt: time.Now().Add(-1 * time.Hour), + }, + }, nil + }, + }, + out: "" + + "ID DESCRIPTION SERVICE-ACCOUNT-EMAIL KMS-KEY CREATED\n" + + "test foobar service-account@secrethub-test-1234567890.iam.gserviceaccount.com projects/secrethub-test-1234567890.iam/locations/global/keyRings/test/cryptoKeys/test About an hour ago\n", + }, + "success gcp filter": { + cmd: ServiceLsCommand{ + newServiceTable: newGCPServiceTable, + filters: []func(*api.Service) bool{ + isGCPService, + }, + }, + serviceService: fakeclient.ServiceService{ + ListFunc: func(path string) ([]*api.Service, error) { + return []*api.Service{ + { + ServiceID: "test", + Description: "foobar", + Credential: &api.Credential{ + Type: api.CredentialTypeGCPServiceAccount, + Metadata: map[string]string{ + api.CredentialMetadataGCPServiceAccountEmail: "service-account@secrethub-test-1234567890.iam.gserviceaccount.com", + api.CredentialMetadataGCPKMSKeyResourceID: "projects/secrethub-test-1234567890.iam/locations/global/keyRings/test/cryptoKeys/test", + }, + }, + CreatedAt: time.Now().Add(-1 * time.Hour), + }, + { + ServiceID: "test2", + Description: "foobarbaz", + Credential: &api.Credential{ + Type: api.CredentialTypeKey, + }, + CreatedAt: time.Now().Add(-1 * time.Hour), + }, + }, nil + }, + }, + out: "" + + "ID DESCRIPTION SERVICE-ACCOUNT-EMAIL KMS-KEY CREATED\n" + + "test foobar service-account@secrethub-test-1234567890.iam.gserviceaccount.com projects/secrethub-test-1234567890.iam/locations/global/keyRings/test/cryptoKeys/test About an hour ago\n", }, "new client error": { newClientErr: errors.New("error"), From 584e755ff5b1baae7f7c469047b8752ce2d1a829 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 3 Jun 2020 22:19:12 +0200 Subject: [PATCH 109/119] Make mkdir execute for all arguments and log errors --- internals/secrethub/mkdir.go | 49 +++++++++++++------------ internals/secrethub/mkdir_test.go | 60 +++++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 60b5fcb4..615f471e 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -2,11 +2,13 @@ package secrethub import ( "fmt" + "os" + + "github.com/secrethub/secrethub-go/internals/api" + "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" - - "github.com/secrethub/secrethub-go/internals/api" ) // Errors @@ -47,32 +49,18 @@ func (cmd *MkDirCommand) Run() error { } for _, path := range cmd.paths { - if cmd.parents { - err = client.Dirs().CreateAll(path.Value()) - if err != nil { - return err - } + err := cmd.createDirectory(client, path) + if err != nil { + fmt.Fprintf(os.Stderr, "Encountered an error: %s\n", err) } else { - _, err = client.Dirs().Create(path.Value()) - if err != nil { - return err - } + fmt.Fprintf(cmd.io.Stdout(), "Created a new directory at %s\n", path) } - - fmt.Fprintf(cmd.io.Stdout(), "Created a new directory at %s\n", path) } return nil } -// dirPathList represents the value of a repeatable directory path argument. -type dirPathList []api.DirPath - -func (d *dirPathList) String() string { - return "" -} - -// Set validates and adds a new directory path to the list. -func (d *dirPathList) Set(path string) error { +// createDirectory validates the given path and creates a directory on it. +func (cmd *MkDirCommand) createDirectory(client secrethub.ClientInterface, path string) error { dirPath, err := api.NewDirPath(path) if err != nil { return err @@ -80,7 +68,22 @@ func (d *dirPathList) Set(path string) error { if dirPath.IsRepoPath() { return ErrMkDirOnRootDir } - *d = append(*d, dirPath) + if cmd.parents { + return client.Dirs().CreateAll(dirPath.Value()) + } + _, err = client.Dirs().Create(dirPath.Value()) + return err +} + +// dirPathList represents the value of a repeatable directory path argument. +type dirPathList []string + +func (d *dirPathList) String() string { + return "" +} + +func (d *dirPathList) Set(path string) error { + *d = append(*d, path) return nil } diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index 8b1f1810..b2c920ff 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -83,7 +83,6 @@ func TestMkDirCommand(t *testing.T) { }, nil }, stdout: "", - err: api.ErrDirAlreadyExists, }, "create dir fails on second dir": { paths: []string{"namespace/repo/dir1", "namespace/repo/dir2"}, @@ -107,7 +106,6 @@ func TestMkDirCommand(t *testing.T) { }, nil }, stdout: "Created a new directory at namespace/repo/dir1\n", - err: api.ErrDirAlreadyExists, }, } @@ -132,31 +130,65 @@ func TestMkDirCommand(t *testing.T) { } } -func TestDirPathList_Set(t *testing.T) { +func TestCreateDirectory(t *testing.T) { cases := map[string]struct { - path string - expected dirPathList - err error + client secrethub.ClientInterface + path string + err error }{ "success": { - path: "namespace/repo/dir", - expected: dirPathList{"namespace/repo/dir"}, + client: fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + return &api.Dir{ + DirID: uuid.New(), + BlindName: "blindname", + Name: "dir", + Status: api.StatusOK, + CreatedAt: time.Now().UTC(), + LastModifiedAt: time.Now().UTC(), + }, nil + }, + }, + }, + path: "namespace/repo/dir", }, "root dir": { + client: fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + return &api.Dir{ + DirID: uuid.New(), + BlindName: "blindname", + Name: "dir", + Status: api.StatusOK, + CreatedAt: time.Now().UTC(), + LastModifiedAt: time.Now().UTC(), + }, nil + }, + }, + }, path: "namespace/repo", err: ErrMkDirOnRootDir, }, + "create dir fails": { + client: fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + return nil, api.ErrDirAlreadyExists + }, + }, + }, + path: "namespace/repo/dir", + err: api.ErrDirAlreadyExists, + }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { - list := dirPathList{} - err := list.Set(tc.path) + cmd := MkDirCommand{} + err := cmd.createDirectory(tc.client, tc.path) assert.Equal(t, err, tc.err) - assert.Equal(t, len(list), len(tc.expected)) - for i := range list { - assert.Equal(t, list[i], tc.expected[i]) - } }) } } From d33c6c4d95995722710acf810467e6ba7f44ab0a Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 3 Jun 2020 22:22:44 +0200 Subject: [PATCH 110/119] Add test for verifying that mkdir executes for all arguments --- internals/secrethub/mkdir_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/internals/secrethub/mkdir_test.go b/internals/secrethub/mkdir_test.go index b2c920ff..7517417f 100644 --- a/internals/secrethub/mkdir_test.go +++ b/internals/secrethub/mkdir_test.go @@ -107,6 +107,29 @@ func TestMkDirCommand(t *testing.T) { }, stdout: "Created a new directory at namespace/repo/dir1\n", }, + "create dir fails on first dir": { + paths: []string{"namespace/repo/dir1", "namespace/repo/dir2"}, + newClient: func() (secrethub.ClientInterface, error) { + return fakeclient.Client{ + DirService: &fakeclient.DirService{ + CreateFunc: func(path string) (*api.Dir, error) { + if path == "namespace/repo/dir1" { + return nil, api.ErrDirAlreadyExists + } + return &api.Dir{ + DirID: uuid.New(), + BlindName: "blindname", + Name: "dir", + Status: api.StatusOK, + CreatedAt: time.Now().UTC(), + LastModifiedAt: time.Now().UTC(), + }, nil + }, + }, + }, nil + }, + stdout: "Created a new directory at namespace/repo/dir2\n", + }, } for name, tc := range cases { From e269e26c36e394ab5e301bd5a454bcfcd109f4f4 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 4 Jun 2020 11:03:42 +0200 Subject: [PATCH 111/119] Make proxy for HTTP-client configurable through flag --- internals/secrethub/client_factory.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internals/secrethub/client_factory.go b/internals/secrethub/client_factory.go index 9e17ff83..87a58759 100644 --- a/internals/secrethub/client_factory.go +++ b/internals/secrethub/client_factory.go @@ -1,6 +1,7 @@ package secrethub import ( + "net/http" "net/url" "strings" @@ -34,6 +35,7 @@ type clientFactory struct { client *secrethub.Client ServerURL *url.URL identityProvider string + proxyAddress *url.URL store CredentialConfig } @@ -41,6 +43,7 @@ type clientFactory struct { func (f *clientFactory) Register(r FlagRegisterer) { r.Flag("api-remote", "The SecretHub API address, don't set this unless you know what you're doing.").Hidden().URLVar(&f.ServerURL) r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) + r.Flag("proxy-address", "Set to the address of a proxy to connect to the API through a proxy. The prepended scheme determines the proxy type (http, https and socks5 are supported). For example: `--proxy-address http://my-proxy:1234`").URLVar(&f.proxyAddress) } // NewClient returns a new client that is configured to use the remote that @@ -60,6 +63,14 @@ func (f *clientFactory) NewClient() (secrethub.ClientInterface, error) { options := f.baseClientOptions() options = append(options, secrethub.WithCredentials(credentialProvider)) + if f.proxyAddress != nil { + transport := http.DefaultTransport.(*http.Transport) + transport.Proxy = func(request *http.Request) (*url.URL, error) { + return f.proxyAddress, nil + } + options = append(options, secrethub.WithTransport(transport)) + } + client, err := secrethub.NewClient(options...) if err == configdir.ErrCredentialNotFound { return nil, ErrCredentialNotExist From ee49cd1169a17030634cf2d67d612ba830ff1c7e Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 4 Jun 2020 11:48:05 +0200 Subject: [PATCH 112/119] Set proxy for all clients --- internals/secrethub/client_factory.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internals/secrethub/client_factory.go b/internals/secrethub/client_factory.go index 87a58759..8465513e 100644 --- a/internals/secrethub/client_factory.go +++ b/internals/secrethub/client_factory.go @@ -63,14 +63,6 @@ func (f *clientFactory) NewClient() (secrethub.ClientInterface, error) { options := f.baseClientOptions() options = append(options, secrethub.WithCredentials(credentialProvider)) - if f.proxyAddress != nil { - transport := http.DefaultTransport.(*http.Transport) - transport.Proxy = func(request *http.Request) (*url.URL, error) { - return f.proxyAddress, nil - } - options = append(options, secrethub.WithTransport(transport)) - } - client, err := secrethub.NewClient(options...) if err == configdir.ErrCredentialNotFound { return nil, ErrCredentialNotExist @@ -114,6 +106,14 @@ func (f *clientFactory) baseClientOptions() []secrethub.ClientOption { }), } + if f.proxyAddress != nil { + transport := http.DefaultTransport.(*http.Transport) + transport.Proxy = func(request *http.Request) (*url.URL, error) { + return f.proxyAddress, nil + } + options = append(options, secrethub.WithTransport(transport)) + } + if f.ServerURL != nil { options = append(options, secrethub.WithServerURL(f.ServerURL.String())) } From a0b5ec9becf78047d77e33e70ffdad54095c5e52 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 4 Jun 2020 11:48:28 +0200 Subject: [PATCH 113/119] Add test for proxy configuration --- internals/secrethub/client_factory_test.go | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 internals/secrethub/client_factory_test.go diff --git a/internals/secrethub/client_factory_test.go b/internals/secrethub/client_factory_test.go new file mode 100644 index 00000000..6b820f04 --- /dev/null +++ b/internals/secrethub/client_factory_test.go @@ -0,0 +1,46 @@ +package secrethub + +import ( + "net/http" + "net/url" + "os" + "testing" + + "github.com/secrethub/secrethub-go/internals/assert" + "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" + + "github.com/secrethub/secrethub-cli/internals/cli/ui" +) + +func TestNewClientFactory_ProxyAddress(t *testing.T) { + proxyAddress, err := url.Parse("http://127.0.0.1:15555") + assert.OK(t, err) + + proxyReceivedRequest := false + go http.ListenAndServe(proxyAddress.Hostname()+":"+proxyAddress.Port(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxyReceivedRequest = true + })) + + // Check if the configuration option takes precedence over the global HTTP_PROXY environment variable + os.Setenv("HTTP_PROXY", "http://test.unknown") + + // Make sure the actual API is not reached if proxying fails + serverAddress, err := url.Parse("http://test.unknown") + assert.OK(t, err) + + io := ui.NewUserIO() + store := NewCredentialConfig(io) + factory := clientFactory{ + identityProvider: "key", + store: store, + ServerURL: serverAddress, + proxyAddress: proxyAddress, + } + + client, err := factory.NewUnauthenticatedClient() + assert.OK(t, err) + + _, _ = client.Users().Create("test", "test@test.test", "test", credentials.CreateKey()) + assert.OK(t, err) + assert.Equal(t, proxyReceivedRequest, true) +} From b681e5c9598fae998d45bb441bb90fca636918a2 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 4 Jun 2020 13:23:10 +0200 Subject: [PATCH 114/119] Add missing errcheck --- internals/secrethub/client_factory_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internals/secrethub/client_factory_test.go b/internals/secrethub/client_factory_test.go index 6b820f04..b4899d91 100644 --- a/internals/secrethub/client_factory_test.go +++ b/internals/secrethub/client_factory_test.go @@ -17,9 +17,14 @@ func TestNewClientFactory_ProxyAddress(t *testing.T) { assert.OK(t, err) proxyReceivedRequest := false - go http.ListenAndServe(proxyAddress.Hostname()+":"+proxyAddress.Port(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - proxyReceivedRequest = true - })) + go func() { + err := http.ListenAndServe(proxyAddress.Hostname()+":"+proxyAddress.Port(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxyReceivedRequest = true + })) + if err != http.ErrServerClosed && err != nil { + t.Errorf("http server error: %s", err) + } + }() // Check if the configuration option takes precedence over the global HTTP_PROXY environment variable os.Setenv("HTTP_PROXY", "http://test.unknown") From e51fb3c7a5acda8c09cf8b89397945fcdadbd7e4 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 5 Jun 2020 09:59:14 +0200 Subject: [PATCH 115/119] Hide gcp Identity Provider option It is still in private beta, listing it would be confusing. --- internals/secrethub/client_factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/client_factory.go b/internals/secrethub/client_factory.go index e0b87bfb..aa3649f9 100644 --- a/internals/secrethub/client_factory.go +++ b/internals/secrethub/client_factory.go @@ -40,7 +40,7 @@ type clientFactory struct { // Register the flags for configuration on a cli application. func (f *clientFactory) Register(r FlagRegisterer) { r.Flag("api-remote", "The SecretHub API address, don't set this unless you know what you're doing.").Hidden().URLVar(&f.ServerURL) - r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS), `gcp` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) + r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) } // NewClient returns a new client that is configured to use the remote that From bcec05ee4bf294b9885f6f1d2f2ed2c81bc713d0 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 8 Jun 2020 17:03:56 +0200 Subject: [PATCH 116/119] Update to secrethub-go v0.29.0 --- go.mod | 4 ++-- go.sum | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 295c53e6..a1c0f024 100644 --- a/go.mod +++ b/go.mod @@ -18,12 +18,12 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 - github.com/secrethub/secrethub-go v0.27.1-0.20200603102047-1a4e50eafb91 + github.com/secrethub/secrethub-go v0.29.0 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e golang.org/x/text v0.3.2 - google.golang.org/api v0.25.0 // indirect + google.golang.org/api v0.26.0 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 39ee9c8f..5db40c7d 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,9 @@ github.com/secrethub/secrethub-cli v0.30.0/go.mod h1:dC0wd40v+iQdV83/0rUrOa01LYq github.com/secrethub/secrethub-go v0.21.0/go.mod h1:rc2IfKKBJ4L0wGec0u4XnF5/pe0FFPE4Q1MWfrFso7s= github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81 h1:gGp4NCf/2N2epcNdVEqwF025gY0h6DOmS7xS7DciyZI= github.com/secrethub/secrethub-go v0.27.1-0.20200603082037-a48b9700bb81/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= +github.com/secrethub/secrethub-go v0.27.1-0.20200603102047-1a4e50eafb91/go.mod h1:Wr4gXWrk8OvBHiCttjLq7wFdKSm07rlEhq5OSYPemtI= +github.com/secrethub/secrethub-go v0.29.0 h1:BUM7lcxmjJENNF6pxq13dKPXf4sP6iQKWq7cbLbOM0g= +github.com/secrethub/secrethub-go v0.29.0/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -337,6 +340,8 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA= google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08= +google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From 378ab5edea6f9568e8b72793dcb5fec45c46d276 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Mon, 8 Jun 2020 17:34:31 +0200 Subject: [PATCH 117/119] Rephrase mkdir error message Change the error message to be more in line with the success message. Co-authored-by: Joris Coenen --- internals/secrethub/mkdir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index c71ff7eb..fe1d5c15 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -51,7 +51,7 @@ func (cmd *MkDirCommand) Run() error { for _, path := range cmd.paths { err := cmd.createDirectory(client, path) if err != nil { - fmt.Fprintf(os.Stderr, "Encountered an error: %s\n", err) +fmt.Fprintf(os.Stderr, "Could not create a new directory at %s: %s\n", path, err) } else { fmt.Fprintf(cmd.io.Output(), "Created a new directory at %s\n", path) } From 68d3efb78528662a24c418c4e993b34312358c4a Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 8 Jun 2020 17:58:50 +0200 Subject: [PATCH 118/119] Fix formatting --- internals/secrethub/mkdir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index fe1d5c15..37e94438 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -51,7 +51,7 @@ func (cmd *MkDirCommand) Run() error { for _, path := range cmd.paths { err := cmd.createDirectory(client, path) if err != nil { -fmt.Fprintf(os.Stderr, "Could not create a new directory at %s: %s\n", path, err) + fmt.Fprintf(os.Stderr, "Could not create a new directory at %s: %s\n", path, err) } else { fmt.Fprintf(cmd.io.Output(), "Created a new directory at %s\n", path) } From e9553d39e9e11ae68f3936b78591861c493c4cba Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Tue, 9 Jun 2020 13:59:58 +0200 Subject: [PATCH 119/119] Hide description if empty Otherwise an empty pair of brackets if shown, which looks weird. --- internals/secrethub/service_gcp_init.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index e05d941b..8e41558a 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -201,9 +201,13 @@ func (l *gcpServiceAccountOptionLister) Options() ([]ui.Option, bool, error) { options := make([]ui.Option, len(resp.Accounts)) for i, account := range resp.Accounts { + display := account.Email + if account.Description != "" { + display += " (" + account.Description + ")" + } options[i] = ui.Option{ Value: account.Email, - Display: fmt.Sprintf("%s (%s)", account.Email, account.Description), + Display: display, } }