Skip to content

Commit 0e0498d

Browse files
authored
chore(task): show task stop reason if tasks fail to run (#1894)
<!-- Provide summary of changes --> fixes #1893 <!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" --> By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 12eeead commit 0e0498d

File tree

3 files changed

+116
-48
lines changed

3 files changed

+116
-48
lines changed

internal/pkg/aws/ecs/ecs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,11 @@ func (e *ECS) RunTask(input RunTaskInput) ([]*Task, error) {
239239
waitErr := e.client.WaitUntilTasksRunning(&ecs.DescribeTasksInput{
240240
Cluster: aws.String(input.Cluster),
241241
Tasks: aws.StringSlice(taskARNs),
242+
Include: aws.StringSlice([]string{ecs.TaskFieldTags}),
242243
})
243244

244245
if waitErr != nil && !isRequestTimeoutErr(waitErr) {
245-
return nil, fmt.Errorf("wait for tasks to be running: %w", err)
246+
return nil, fmt.Errorf("wait for tasks to be running: %w", waitErr)
246247
}
247248

248249
tasks, describeErr := e.DescribeTasks(input.Cluster, taskARNs)

internal/pkg/aws/ecs/ecs_test.go

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"testing"
1010

1111
"github.com/aws/aws-sdk-go/aws"
12+
"github.com/aws/aws-sdk-go/aws/awserr"
13+
"github.com/aws/aws-sdk-go/aws/request"
1214
"github.com/aws/aws-sdk-go/service/ecs"
1315
"github.com/aws/copilot-cli/internal/pkg/aws/ecs/mocks"
1416
"github.com/golang/mock/gomock"
@@ -540,7 +542,22 @@ func TestECS_RunTask(t *testing.T) {
540542
taskFamilyName: "my-task",
541543
startedBy: "task",
542544
}
543-
545+
ecsTasks := []*ecs.Task{
546+
{
547+
TaskArn: aws.String("task-1"),
548+
},
549+
{
550+
TaskArn: aws.String("task-2"),
551+
},
552+
{
553+
TaskArn: aws.String("task-3"),
554+
},
555+
}
556+
describeTasksInput := ecs.DescribeTasksInput{
557+
Cluster: aws.String("my-cluster"),
558+
Tasks: aws.StringSlice([]string{"task-1", "task-2", "task-3"}),
559+
Include: aws.StringSlice([]string{ecs.TaskFieldTags}),
560+
}
544561
testCases := map[string]struct {
545562
input
546563

@@ -566,43 +583,14 @@ func TestECS_RunTask(t *testing.T) {
566583
},
567584
},
568585
PropagateTags: aws.String(ecs.PropagateTagsTaskDefinition),
569-
}).
570-
Return(&ecs.RunTaskOutput{
571-
Tasks: []*ecs.Task{
572-
{
573-
TaskArn: aws.String("task-1"),
574-
},
575-
{
576-
TaskArn: aws.String("task-2"),
577-
},
578-
{
579-
TaskArn: aws.String("task-3"),
580-
},
581-
},
582-
}, nil)
583-
m.EXPECT().WaitUntilTasksRunning(&ecs.DescribeTasksInput{
584-
Cluster: aws.String("my-cluster"),
585-
Tasks: aws.StringSlice([]string{"task-1", "task-2", "task-3"}),
586-
}).Times(1)
587-
m.EXPECT().DescribeTasks(&ecs.DescribeTasksInput{
588-
Cluster: aws.String("my-cluster"),
589-
Tasks: aws.StringSlice([]string{"task-1", "task-2", "task-3"}),
590-
Include: aws.StringSlice([]string{ecs.TaskFieldTags}),
591-
}).Return(&ecs.DescribeTasksOutput{
592-
Tasks: []*ecs.Task{
593-
{
594-
TaskArn: aws.String("task-1"),
595-
},
596-
{
597-
TaskArn: aws.String("task-2"),
598-
},
599-
{
600-
TaskArn: aws.String("task-3"),
601-
},
602-
},
603-
}, nil).Times(1)
586+
}).Return(&ecs.RunTaskOutput{
587+
Tasks: ecsTasks,
588+
}, nil)
589+
m.EXPECT().WaitUntilTasksRunning(&describeTasksInput).Times(1)
590+
m.EXPECT().DescribeTasks(&describeTasksInput).Return(&ecs.DescribeTasksOutput{
591+
Tasks: ecsTasks,
592+
}, nil)
604593
},
605-
606594
wantedTasks: []*Task{
607595
{
608596
TaskArn: aws.String("task-1"),
@@ -635,10 +623,82 @@ func TestECS_RunTask(t *testing.T) {
635623
PropagateTags: aws.String(ecs.PropagateTagsTaskDefinition),
636624
}).
637625
Return(&ecs.RunTaskOutput{}, errors.New("error"))
638-
m.EXPECT().WaitUntilTasksRunning(gomock.Any()).Times(0)
639626
},
640627
wantedError: errors.New("run task(s) my-task: error"),
641628
},
629+
"failed to call WaitUntilTasksRunning": {
630+
input: runTaskInput,
631+
632+
mockECSClient: func(m *mocks.Mockapi) {
633+
m.EXPECT().RunTask(&ecs.RunTaskInput{
634+
Cluster: aws.String("my-cluster"),
635+
Count: aws.Int64(3),
636+
LaunchType: aws.String(ecs.LaunchTypeFargate),
637+
StartedBy: aws.String("task"),
638+
TaskDefinition: aws.String("my-task"),
639+
NetworkConfiguration: &ecs.NetworkConfiguration{
640+
AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
641+
AssignPublicIp: aws.String(ecs.AssignPublicIpEnabled),
642+
Subnets: aws.StringSlice([]string{"subnet-1", "subnet-2"}),
643+
SecurityGroups: aws.StringSlice([]string{"sg-1", "sg-2"}),
644+
},
645+
},
646+
PropagateTags: aws.String(ecs.PropagateTagsTaskDefinition),
647+
}).
648+
Return(&ecs.RunTaskOutput{
649+
Tasks: ecsTasks,
650+
}, nil)
651+
m.EXPECT().WaitUntilTasksRunning(&describeTasksInput).Return(errors.New("some error"))
652+
},
653+
wantedError: errors.New("wait for tasks to be running: some error"),
654+
},
655+
"task failed to start": {
656+
input: runTaskInput,
657+
658+
mockECSClient: func(m *mocks.Mockapi) {
659+
m.EXPECT().RunTask(&ecs.RunTaskInput{
660+
Cluster: aws.String("my-cluster"),
661+
Count: aws.Int64(3),
662+
LaunchType: aws.String(ecs.LaunchTypeFargate),
663+
StartedBy: aws.String("task"),
664+
TaskDefinition: aws.String("my-task"),
665+
NetworkConfiguration: &ecs.NetworkConfiguration{
666+
AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
667+
AssignPublicIp: aws.String(ecs.AssignPublicIpEnabled),
668+
Subnets: aws.StringSlice([]string{"subnet-1", "subnet-2"}),
669+
SecurityGroups: aws.StringSlice([]string{"sg-1", "sg-2"}),
670+
},
671+
},
672+
PropagateTags: aws.String(ecs.PropagateTagsTaskDefinition),
673+
}).
674+
Return(&ecs.RunTaskOutput{
675+
Tasks: ecsTasks}, nil)
676+
m.EXPECT().WaitUntilTasksRunning(&describeTasksInput).
677+
Return(awserr.New(request.WaiterResourceNotReadyErrorCode, "some error", errors.New("some error")))
678+
m.EXPECT().DescribeTasks(&describeTasksInput).Return(&ecs.DescribeTasksOutput{
679+
Tasks: []*ecs.Task{
680+
{
681+
TaskArn: aws.String("task-1"),
682+
},
683+
{
684+
TaskArn: aws.String("arn:aws:ecs:us-west-2:123456789:task/4082490ee6c245e09d2145010aa1ba8d"),
685+
StoppedReason: aws.String("Task failed to start"),
686+
LastStatus: aws.String("STOPPED"),
687+
Containers: []*ecs.Container{
688+
{
689+
Reason: aws.String("CannotPullContainerError: inspect image has been retried 1 time(s)"),
690+
LastStatus: aws.String("STOPPED"),
691+
},
692+
},
693+
},
694+
{
695+
TaskArn: aws.String("task-3"),
696+
},
697+
},
698+
}, nil)
699+
},
700+
wantedError: errors.New("task 4082490e: Task failed to start: CannotPullContainerError: inspect image has been retried 1 time(s)"),
701+
},
642702
}
643703

644704
for name, tc := range testCases {

internal/pkg/aws/ecs/errors.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515
// DesiredStatusStopped represents the desired status "STOPPED" for a task.
1616
DesiredStatusStopped = ecs.DesiredStatusStopped
1717

18-
fmtErrContainerStopped = "task %s: %s"
18+
fmtErrTaskStopped = "task %s: %s"
1919
)
2020

2121
// ErrNoDefaultCluster occurs when the default cluster is not found.
@@ -32,18 +32,25 @@ func (e *ErrWaiterResourceNotReadyForTasks) Error() string {
3232
if aws.StringValue(task.LastStatus) != DesiredStatusStopped {
3333
continue
3434
}
35-
35+
taskID, err := TaskID(aws.StringValue(task.TaskArn))
36+
if err != nil {
37+
return err.Error()
38+
}
39+
// Combine both task stop reason and container stop reason.
40+
var errMsg string
41+
if task.StoppedReason != nil {
42+
errMsg = aws.StringValue(task.StoppedReason)
43+
}
3644
// TODO: generalize this to be any essential container.
3745
container := task.Containers[0] // NOTE: right now we only support one container per task
38-
if aws.StringValue(container.LastStatus) != DesiredStatusStopped {
39-
continue
46+
if aws.StringValue(container.LastStatus) == DesiredStatusStopped {
47+
if container.Reason != nil {
48+
errMsg = fmt.Sprintf("%s: %s", errMsg, aws.StringValue(container.Reason))
49+
}
4050
}
41-
42-
taskID, err := TaskID(aws.StringValue(task.TaskArn))
43-
if err != nil {
44-
continue
51+
if errMsg != "" {
52+
return fmt.Sprintf(fmtErrTaskStopped, taskID[:shortTaskIDLength], errMsg)
4553
}
46-
return fmt.Sprintf(fmtErrContainerStopped, taskID[:shortTaskIDLength], aws.StringValue(container.Reason))
4754
}
4855
return e.awsErrResourceNotReady.Error()
4956
}

0 commit comments

Comments
 (0)