mirror of https://github.com/docker/compose.git
Format stack events as a set of resources with progress status
This mimic how docker-compose report containers creation Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
678f4018f0
commit
5d61fc119a
|
@ -3,11 +3,13 @@ package amazon
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
cf "github.com/aws/aws-sdk-go/service/cloudformation"
|
||||
|
||||
"github.com/awslabs/goformation/v4/cloudformation"
|
||||
"github.com/docker/ecs-plugin/pkg/compose"
|
||||
"github.com/docker/ecs-plugin/pkg/console"
|
||||
)
|
||||
|
||||
func (c *client) ComposeUp(ctx context.Context, project *compose.Project) error {
|
||||
|
@ -36,23 +38,26 @@ func (c *client) ComposeUp(ctx context.Context, project *compose.Project) error
|
|||
return err
|
||||
}
|
||||
|
||||
w := console.NewProgressWriter()
|
||||
known := map[string]struct{}{}
|
||||
err = c.api.WaitStackComplete(ctx, project.Name, func() error {
|
||||
events, err := c.api.DescribeStackEvents(ctx, project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Slice(events, func(i, j int) bool {
|
||||
return events[i].Timestamp.Before(*events[j].Timestamp)
|
||||
})
|
||||
|
||||
for _, event := range events {
|
||||
if _, ok := known[*event.EventId]; ok {
|
||||
continue
|
||||
}
|
||||
known[*event.EventId] = struct{}{}
|
||||
|
||||
description := "-"
|
||||
if event.ResourceStatusReason != nil {
|
||||
description = *event.ResourceStatusReason
|
||||
}
|
||||
fmt.Printf("%s %q %s %s\n", *event.ResourceType, *event.LogicalResourceId, *event.ResourceStatus, description)
|
||||
resource := fmt.Sprintf("%s %q", aws.StringValue(event.ResourceType), aws.StringValue(event.LogicalResourceId))
|
||||
w.ResourceEvent(resource, aws.StringValue(event.ResourceStatus), aws.StringValue(event.ResourceStatusReason))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type resource struct {
|
||||
name string
|
||||
status string
|
||||
details string
|
||||
}
|
||||
|
||||
type progress struct {
|
||||
console console
|
||||
resources []*resource
|
||||
}
|
||||
|
||||
func NewProgressWriter() *progress {
|
||||
return &progress{
|
||||
console: ansiConsole{os.Stdout},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
cyan = "36;1"
|
||||
red = "31;1"
|
||||
green = "32;1"
|
||||
)
|
||||
|
||||
func (p *progress) ResourceEvent(name string, status string, details string) {
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
logrus.Debugf("> %s : %s %s\n", name, status, details)
|
||||
return
|
||||
}
|
||||
p.console.MoveUp(len(p.resources))
|
||||
|
||||
newResource := true
|
||||
for _, r := range p.resources {
|
||||
if r.name == name {
|
||||
newResource = false
|
||||
r.status = status
|
||||
r.details = details
|
||||
break
|
||||
}
|
||||
}
|
||||
if newResource {
|
||||
p.resources = append(p.resources, &resource{name, status, details})
|
||||
}
|
||||
|
||||
var width int
|
||||
for _, r := range p.resources {
|
||||
l := len(r.name)
|
||||
if width < l {
|
||||
width = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range p.resources {
|
||||
s := r.status
|
||||
if strings.HasSuffix(s, "_IN_PROGRESS") {
|
||||
s = p.console.WiP(s)
|
||||
} else if strings.HasSuffix(s, "_COMPLETE") {
|
||||
s = p.console.OK(s)
|
||||
} else if strings.HasSuffix(s, "_FAILED") {
|
||||
s = p.console.KO(s)
|
||||
}
|
||||
p.console.ClearLine()
|
||||
p.console.Printf("%-"+strconv.Itoa(width)+"s ... %s %s", r.name, s, r.details) // nolint:errcheck
|
||||
p.console.MoveDown(1)
|
||||
}
|
||||
}
|
||||
|
||||
type console interface {
|
||||
Printf(format string, a ...interface{})
|
||||
MoveUp(int)
|
||||
MoveDown(int)
|
||||
ClearLine()
|
||||
OK(string) string
|
||||
KO(string) string
|
||||
WiP(string) string
|
||||
}
|
||||
|
||||
type ansiConsole struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (c ansiConsole) Printf(format string, a ...interface{}) {
|
||||
fmt.Fprintf(c.out, format, a...) // nolint:errcheck
|
||||
fmt.Fprintf(c.out, "\r")
|
||||
}
|
||||
|
||||
func (c ansiConsole) MoveUp(i int) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(c.out, "\033[%dA", i) // nolint:errcheck
|
||||
}
|
||||
|
||||
func (c ansiConsole) MoveDown(i int) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(c.out, "\033[%dB", i) // nolint:errcheck
|
||||
}
|
||||
|
||||
func (c ansiConsole) ClearLine() {
|
||||
fmt.Fprint(c.out, "\033[2K\r") // nolint:errcheck
|
||||
}
|
||||
|
||||
func (c ansiConsole) OK(s string) string {
|
||||
return ansiColor(green, s)
|
||||
}
|
||||
|
||||
func (c ansiConsole) KO(s string) string {
|
||||
return ansiColor(red, s)
|
||||
}
|
||||
|
||||
func (c ansiConsole) WiP(s string) string {
|
||||
return ansiColor(cyan, s)
|
||||
}
|
||||
|
||||
func ansiColor(code, s string) string {
|
||||
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
|
||||
}
|
||||
|
||||
func ansi(code string) string {
|
||||
return fmt.Sprintf("\033[%sm", code)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestProgressWriter(t *testing.T) {
|
||||
c := &bufferConsole{}
|
||||
p := progress{
|
||||
console: c,
|
||||
}
|
||||
p.ResourceEvent("resource1", "CREATE_IN_PROGRESS", "")
|
||||
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
|
||||
|
||||
p.ResourceEvent("resource2_long_name", "CREATE_IN_PROGRESS", "ok")
|
||||
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
|
||||
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_IN_PROGRESS ok")
|
||||
|
||||
p.ResourceEvent("resource2_long_name", "CREATE_COMPLETE", "done")
|
||||
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
|
||||
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_COMPLETE done")
|
||||
|
||||
p.ResourceEvent("resource1", "CREATE_FAILED", "oups")
|
||||
assert.Equal(t, c.lines[0], "resource1 ... CREATE_FAILED oups")
|
||||
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_COMPLETE done")
|
||||
}
|
||||
|
||||
type bufferConsole struct {
|
||||
pos int
|
||||
lines []string
|
||||
}
|
||||
|
||||
func (b *bufferConsole) Printf(format string, a ...interface{}) {
|
||||
b.lines[b.pos] = fmt.Sprintf(format, a...)
|
||||
}
|
||||
|
||||
func (b *bufferConsole) MoveUp(i int) {
|
||||
b.pos -= i
|
||||
}
|
||||
|
||||
func (b *bufferConsole) MoveDown(i int) {
|
||||
b.pos += i
|
||||
}
|
||||
|
||||
func (b *bufferConsole) ClearLine() {
|
||||
if len(b.lines) <= b.pos {
|
||||
b.lines = append(b.lines, "")
|
||||
}
|
||||
b.lines[b.pos] = ""
|
||||
}
|
||||
|
||||
func (b *bufferConsole) OK(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *bufferConsole) KO(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *bufferConsole) WiP(s string) string {
|
||||
return s
|
||||
}
|
Loading…
Reference in New Issue