Skip to content
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
13 changes: 13 additions & 0 deletions apps/cinc/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"bufio"
"errors"
"fmt"
"io"
"net/url"
Expand Down Expand Up @@ -386,12 +387,24 @@ func splitChefServerURL(raw string) (host, org string) {
return u.Host, ""
}

// errStdinExhausted is returned by promptNoDefault when there is no input
// left to read. The prompts that have no sensible default re-ask until they
// get an answer, so without this the flow would spin forever against a
// closed stdin (`cinc config create < /dev/null`, or a CI run).
var errStdinExhausted = errors.New("we ran out of input while waiting for an answer. `cinc config create` needs an interactive terminal; to configure without prompts, pass --client-name, --client-key, and --server-url")

// promptNoDefault asks for an answer that has no default. An empty line is a
// valid (if usually rejected) answer, so it is reported as one; only a reader
// with nothing left to give yields errStdinExhausted.
func promptNoDefault(reader *bufio.Reader, out io.Writer, label string) (string, error) {
fmt.Fprintf(out, "%s: ", label)
answer, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
return "", err
}
if err == io.EOF && answer == "" {
return "", errStdinExhausted
}
return strings.TrimSpace(answer), nil
}

Expand Down
43 changes: 43 additions & 0 deletions apps/cinc/cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,53 @@ import (
"path/filepath"
"strings"
"testing"
"time"

"github.com/cinc-project/cinc-cli/cli/config"
)

// TestConfigureStopsWhenStdinIsExhausted covers `cinc config create` run
// without an interactive terminal (for example `cinc config create <
// /dev/null`, or from CI) against a credentials file that already holds
// profiles. The action prompt defaults to "Add a new profile", which then
// asks for a name it will not accept as empty. With no more input to read
// the command has to give up with a clear error rather than re-asking forever.
func TestConfigureStopsWhenStdinIsExhausted(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "credentials")
if err := config.WriteProfile(cfgPath, "default", config.Profile{
ServerURL: "https://old.example.test",
Org: "old",
ClientName: "old",
KeyPath: "/keys/old.pem",
}); err != nil {
t.Fatal(err)
}

root := newRootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(new(bytes.Buffer))
root.SetIn(strings.NewReader(""))
root.SetArgs([]string{"config", "create", "--config", cfgPath})

done := make(chan error, 1)
go func() { done <- root.Execute() }()

select {
case err := <-done:
if err == nil {
t.Fatal("expected an error when stdin is exhausted, got nil")
}
if !strings.Contains(err.Error(), "ran out of input") {
t.Fatalf("error = %v, want it to explain that input ran out", err)
}
case <-time.After(10 * time.Second):
t.Fatalf("config create never returned; it asked for a profile name %d times",
strings.Count(out.String(), "New profile name"))
}
}

func TestConfigureCommandWritesTOMLCredentialsProfile(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "credentials")
Expand Down