diff --git a/src/go/rpk/pkg/cli/container/start.go b/src/go/rpk/pkg/cli/container/start.go index 919d81f9427a3..c78c71ae49261 100644 --- a/src/go/rpk/pkg/cli/container/start.go +++ b/src/go/rpk/pkg/cli/container/start.go @@ -10,7 +10,7 @@ package container import ( - "bufio" + "bytes" "context" "errors" "fmt" @@ -24,6 +24,8 @@ import ( "github.com/avast/retry-go" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/pkg/stdcopy" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/container/common" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" vnet "github.com/redpanda-data/redpanda/src/go/rpk/pkg/net" @@ -281,7 +283,15 @@ func startCluster( } err = waitForCluster(check(nodes), retries) if err != nil { - return false, err + state, sErr := common.GetState(c, nodes[0].id) + if sErr != nil { + return false, fmt.Errorf("%v\nunable to get Docker container logs: %v", err, sErr) + } + errStr, cErr := getContainerErr(state, c) + if cErr != nil { + return false, fmt.Errorf("%v\nunable to get Docker container logs: %v", err, cErr) + } + return false, fmt.Errorf("%v\n\nErrors reported from the Docker container:\n\n%v", err, errStr) } fmt.Println("Cluster started!") @@ -337,39 +347,11 @@ func restartCluster( } err = waitForCluster(check(nodes), retries) if err != nil { - // Attempt to fetch the latest stderr output from the first - // Redpanda node. It may reveal reasons for failing to start. - ctx, _ := common.DefaultCtx() - state := states[0] - - json, errInspect := c.ContainerInspect(ctx, state.ContainerID) - if errInspect != nil { - return nil, fmt.Errorf("%v\n%v", err, errInspect) - } - - reader, errLogs := c.ContainerLogs( - ctx, - state.ContainerID, - types.ContainerLogsOptions{ - ShowStdout: false, - ShowStderr: true, - Since: json.State.StartedAt, - }, - ) - if errLogs != nil { - return nil, fmt.Errorf("%v\nCould not get container logs: %v", err, errLogs) + errStr, cErr := getContainerErr(states[0], c) + if cErr != nil { + return nil, fmt.Errorf("%v\nunable to get Docker container logs: %v", err, cErr) } - - scanner := bufio.NewScanner(reader) - errStr := "" - for scanner.Scan() { - errStr += scanner.Text() + "\n" - } - return nil, fmt.Errorf( - "%v\nErrors reported from the Docker container:\n%v", - err, - errors.New(errStr), - ) + return nil, fmt.Errorf("%v\n\nErrors reported from the Docker container:\n%v", err, errStr) } return nodes, nil } @@ -510,3 +492,38 @@ func nodeAddr(port uint) string { port, ) } + +// getContainerErr attempts to fetch the latest stderr output from the first +// Redpanda node. It may reveal reasons for failing to start. +func getContainerErr(state *common.NodeState, c common.Client) (string, error) { + ctx, _ := common.DefaultCtx() + + json, err := c.ContainerInspect(ctx, state.ContainerID) + if err != nil { + return "", fmt.Errorf("could not inspect container: %v", err) + } + + reader, err := c.ContainerLogs( + ctx, + state.ContainerID, + container.LogsOptions{ + ShowStdout: false, + ShowStderr: true, + Since: json.State.StartedAt, + }, + ) + if err != nil { + return "", fmt.Errorf("could not get container logs: %v", err) + } + + // Docker logs over the wire are multiplexed using stdcopy package. To + // demux this stream we need to use stdcopy.StdCopy. See: + // https://github.com/moby/moby/issues/32794#issuecomment-297151440 + bErr := new(bytes.Buffer) + _, err = stdcopy.StdCopy(bErr, bErr, reader) + if err != nil { + return "", fmt.Errorf("unable to read docker logs: %v", err) + } + + return bErr.String(), nil +}