Detect task failures

Signed-off-by: aiordache <anca.iordache@docker.com>
This commit is contained in:
aiordache 2020-09-30 14:52:53 +02:00
parent 144c403e96
commit d3effd2ead
4 changed files with 126 additions and 17 deletions

View File

@ -77,4 +77,5 @@ type Stack struct {
ID string ID string
Name string Name string
Status string Status string
Reason string
} }

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -60,7 +61,8 @@ func runList(ctx context.Context, opts composeOptions) error {
view := viewFromStackList(stackList) view := viewFromStackList(stackList)
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) { return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
for _, stack := range view { for _, stack := range view {
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) _, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, strings.TrimSpace(
fmt.Sprintf("%s %s", stack.Status, stack.Reason))
} }
}, "NAME", "STATUS") }, "NAME", "STATUS")
} }

View File

@ -304,9 +304,18 @@ func (s sdk) ListStacks(ctx context.Context, name string) ([]compose.Stack, erro
} }
stacks := []compose.Stack{} stacks := []compose.Stack{}
for _, stack := range cfStacks.Stacks { for _, stack := range cfStacks.Stacks {
skip := true
for _, t := range stack.Tags { for _, t := range stack.Tags {
if *t.Key == compose.ProjectTag { if *t.Key == compose.ProjectTag {
skip = false
break
}
}
if skip {
continue
}
status := compose.RUNNING status := compose.RUNNING
reason := ""
switch aws.StringValue(stack.StackStatus) { switch aws.StringValue(stack.StackStatus) {
case "CREATE_IN_PROGRESS": case "CREATE_IN_PROGRESS":
status = compose.STARTING status = compose.STARTING
@ -315,18 +324,93 @@ func (s sdk) ListStacks(ctx context.Context, name string) ([]compose.Stack, erro
case "UPDATE_IN_PROGRESS": case "UPDATE_IN_PROGRESS":
status = compose.UPDATING status = compose.UPDATING
} }
if status == compose.STARTING {
if err := s.CheckStackState(ctx, aws.StringValue(stack.StackName)); err != nil {
status = compose.FAILED
reason = err.Error()
}
}
stacks = append(stacks, compose.Stack{ stacks = append(stacks, compose.Stack{
ID: aws.StringValue(stack.StackId), ID: aws.StringValue(stack.StackId),
Name: aws.StringValue(stack.StackName), Name: aws.StringValue(stack.StackName),
Status: status, Status: status,
Reason: reason,
}) })
break
}
}
} }
return stacks, nil return stacks, nil
} }
func (s sdk) CheckStackState(ctx context.Context, name string) error {
resources, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
StackName: aws.String(name),
})
if err != nil {
return err
}
services := []*string{}
serviceNames := []string{}
var cluster *string
for _, r := range resources.StackResourceSummaries {
if aws.StringValue(r.ResourceType) == "AWS::ECS::Cluster" {
cluster = r.PhysicalResourceId
continue
}
if aws.StringValue(r.ResourceType) == "AWS::ECS::Service" {
if r.PhysicalResourceId == nil {
continue
}
services = append(services, r.PhysicalResourceId)
serviceNames = append(serviceNames, *r.LogicalResourceId)
}
}
for i, service := range services {
err := s.CheckTaskState(ctx, aws.StringValue(cluster), aws.StringValue(service))
if err != nil {
return fmt.Errorf("%s error: %s", serviceNames[i], err.Error())
}
}
return nil
}
func (s sdk) CheckTaskState(ctx context.Context, cluster string, serviceName string) error {
tasks, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
Cluster: aws.String(cluster),
ServiceName: aws.String(serviceName),
})
if err != nil {
return err
}
if len(tasks.TaskArns) > 0 {
return nil
}
tasks, err = s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
Cluster: aws.String(cluster),
ServiceName: aws.String(serviceName),
DesiredStatus: aws.String("STOPPED"),
})
if err != nil {
return err
}
if len(tasks.TaskArns) > 0 {
taskDescriptions, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
Cluster: aws.String(cluster),
Tasks: tasks.TaskArns,
})
if err != nil {
return err
}
if len(taskDescriptions.Tasks) > 0 {
recentTask := taskDescriptions.Tasks[0]
switch aws.StringValue(recentTask.StopCode) {
case "TaskFailedToStart":
return fmt.Errorf(aws.StringValue(recentTask.StoppedReason))
}
}
}
return nil
}
func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) { func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) {
// Fixme implement Paginator on Events and return as a chan(events) // Fixme implement Paginator on Events and return as a chan(events)
events := []*cloudformation.StackEvent{} events := []*cloudformation.StackEvent{}

View File

@ -52,6 +52,7 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
var completed bool var completed bool
var stackErr error var stackErr error
for !completed { for !completed {
select { select {
case <-done: case <-done:
@ -77,8 +78,8 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
reason := aws.StringValue(event.ResourceStatusReason) reason := aws.StringValue(event.ResourceStatusReason)
status := aws.StringValue(event.ResourceStatus) status := aws.StringValue(event.ResourceStatus)
progressStatus := progress.Working progressStatus := progress.Working
switch status { switch status {
case "CREATE_COMPLETE": case "CREATE_COMPLETE":
if operation == stackCreate { if operation == stackCreate {
progressStatus = progress.Done progressStatus = progress.Done
@ -100,12 +101,33 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
} }
} }
} }
w.Event(progress.Event{ w.Event(progress.Event{
ID: resource, ID: resource,
Status: progressStatus, Status: progressStatus,
StatusText: status, StatusText: reason,
}) })
} }
if operation != stackCreate || stackErr != nil {
continue
}
if err := b.SDK.CheckStackState(ctx, name); err != nil {
stackErr = err
b.SDK.DeleteStack(ctx, name)
operation = stackDelete
reason := err.Error()
if len(reason) > 30 {
reason = reason[:30] + "..."
}
w.Event(progress.Event{
ID: name,
Status: progress.Error,
StatusText: reason,
})
}
} }
return stackErr return stackErr