Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: sequence cli.Ui operations #6865

Merged
merged 1 commit into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions command/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"bufio"
"bytes"
"fmt"
"io"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/kr/text"
"github.com/mitchellh/cli"
"github.com/posener/complete"

"github.com/ryanuber/columnize"
Expand Down Expand Up @@ -464,3 +466,44 @@ func sanitizeUUIDPrefix(prefix string) string {
func commandErrorText(cmd NamedCommand) string {
return fmt.Sprintf("For additional help try 'nomad %s -help'", cmd.Name())
}

// uiErrorWriter is a io.Writer that wraps underlying ui.ErrorWriter().
// ui.ErrorWriter expects full lines as inputs and it emits its own line breaks.
//
// uiErrorWriter scans input for individual lines to pass to ui.ErrorWriter. If data
// doesn't contain a new line, it buffers result until next new line or writer is closed.
type uiErrorWriter struct {
ui cli.Ui
buf bytes.Buffer
}

func (w *uiErrorWriter) Write(data []byte) (int, error) {
read := 0
for len(data) != 0 {
a, token, err := bufio.ScanLines(data, false)
if err != nil {
return read, err
}

if a == 0 {
r, err := w.buf.Write(data)
return read + r, err
}

w.ui.Error(w.buf.String() + string(token))
data = data[a:]
w.buf.Reset()
read += a
}

return read, nil
}

func (w *uiErrorWriter) Close() error {
// emit what's remaining
if w.buf.Len() != 0 {
w.ui.Error(w.buf.String())
w.buf.Reset()
}
return nil
}
50 changes: 50 additions & 0 deletions command/helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"bytes"
"fmt"
"io"
"io/ioutil"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/hashicorp/nomad/helper/flatmap"
"github.com/kr/pretty"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
)

func TestHelpers_FormatKV(t *testing.T) {
Expand Down Expand Up @@ -342,3 +344,51 @@ func TestPrettyTimeDiff(t *testing.T) {
}

}

// TestUiErrorWriter asserts that writer buffers and
func TestUiErrorWriter(t *testing.T) {
t.Parallel()

var outBuf, errBuf bytes.Buffer
ui := &cli.BasicUi{
Writer: &outBuf,
ErrorWriter: &errBuf,
}

w := &uiErrorWriter{ui: ui}

inputs := []string{
"some line\n",
"multiple\nlines\r\nhere",
" with followup\nand",
" more lines ",
" without new line ",
"until here\nand then",
"some more",
}

partialAcc := ""
for _, in := range inputs {
n, err := w.Write([]byte(in))
require.NoError(t, err)
require.Equal(t, len(in), n)

// assert that writer emits partial result until last new line
partialAcc += strings.ReplaceAll(in, "\r\n", "\n")
lastNL := strings.LastIndex(partialAcc, "\n")
require.Equal(t, partialAcc[:lastNL+1], errBuf.String())
}

require.Empty(t, outBuf.String())

// note that the \r\n got replaced by \n
expectedErr := "some line\nmultiple\nlines\nhere with followup\nand more lines without new line until here\n"
require.Equal(t, expectedErr, errBuf.String())

// close emits the final line
err := w.Close()
require.NoError(t, err)

expectedErr += "and thensome more\n"
require.Equal(t, expectedErr, errBuf.String())
}
15 changes: 1 addition & 14 deletions command/meta.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package command

import (
"bufio"
"flag"
"io"
"os"
"strings"

Expand Down Expand Up @@ -83,18 +81,7 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {

}

// Create an io.Writer that writes to our UI properly for errors.
// This is kind of a hack, but it does the job. Basically: create
// a pipe, use a scanner to break it into lines, and output each line
// to the UI. Do this forever.
errR, errW := io.Pipe()
errScanner := bufio.NewScanner(errR)
go func() {
for errScanner.Scan() {
m.Ui.Error(errScanner.Text())
}
}()
f.SetOutput(errW)
f.SetOutput(&uiErrorWriter{ui: m.Ui})

return f
}
Expand Down