@@ -16,6 +16,7 @@ import (
16
16
sdkcloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
17
17
"github.com/aws/copilot-cli/internal/pkg/aws/cloudformation"
18
18
"github.com/aws/copilot-cli/internal/pkg/aws/cloudformation/stackset"
19
+ "github.com/aws/copilot-cli/internal/pkg/aws/ecs"
19
20
"github.com/aws/copilot-cli/internal/pkg/stream"
20
21
"github.com/aws/copilot-cli/internal/pkg/term/log"
21
22
"github.com/aws/copilot-cli/internal/pkg/term/progress"
@@ -27,6 +28,9 @@ import (
27
28
const (
28
29
// waitForStackTimeout is how long we're willing to wait for a stack to go from in progress to a complete state.
29
30
waitForStackTimeout = 1 * time .Hour + 30 * time .Minute
31
+
32
+ // CloudFormation resource types.
33
+ ecsServiceResourceType = "AWS::ECS::Service"
30
34
)
31
35
32
36
// StackConfiguration represents the set of methods needed to deploy a cloudformation stack.
@@ -37,12 +41,16 @@ type StackConfiguration interface {
37
41
Tags () []* sdkcloudformation.Tag
38
42
}
39
43
44
+ type ecsClient interface {
45
+ stream.ECSServiceDescriber
46
+ }
47
+
40
48
type cfnClient interface {
41
49
// Methods augmented by the aws wrapper struct.
42
50
Create (* cloudformation.Stack ) (string , error )
43
51
CreateAndWait (* cloudformation.Stack ) error
44
52
WaitForCreate (ctx context.Context , stackName string ) error
45
- Update (* cloudformation.Stack ) error
53
+ Update (* cloudformation.Stack ) ( string , error )
46
54
UpdateAndWait (* cloudformation.Stack ) error
47
55
WaitForUpdate (ctx context.Context , stackName string ) error
48
56
Delete (stackName string ) error
@@ -72,15 +80,17 @@ type stackSetClient interface {
72
80
// CloudFormation wraps the CloudFormationAPI interface
73
81
type CloudFormation struct {
74
82
cfnClient cfnClient
83
+ ecsClient ecsClient
75
84
regionalClient func (region string ) cfnClient
76
85
appStackSet stackSetClient
77
86
box packd.Box
78
87
}
79
88
80
89
// New returns a configured CloudFormation client.
81
90
func New (sess * session.Session ) CloudFormation {
82
- return CloudFormation {
91
+ client := CloudFormation {
83
92
cfnClient : cloudformation .New (sess ),
93
+ ecsClient : ecs .New (sess ),
84
94
regionalClient : func (region string ) cfnClient {
85
95
return cloudformation .New (sess .Copy (& aws.Config {
86
96
Region : aws .String (region ),
@@ -89,11 +99,12 @@ func New(sess *session.Session) CloudFormation {
89
99
appStackSet : stackset .New (sess ),
90
100
box : templates .Box (),
91
101
}
102
+ return client
92
103
}
93
104
94
105
// errorEvents returns the list of status reasons of failed resource events
95
- func (cf CloudFormation ) errorEvents (conf StackConfiguration ) ([]string , error ) {
96
- events , err := cf .cfnClient .ErrorEvents (conf . StackName () )
106
+ func (cf CloudFormation ) errorEvents (stackName string ) ([]string , error ) {
107
+ events , err := cf .cfnClient .ErrorEvents (stackName )
97
108
if err != nil {
98
109
return nil , err
99
110
}
@@ -112,7 +123,44 @@ type renderStackChangesInput struct {
112
123
createChangeSet func () (string , error )
113
124
}
114
125
115
- func (cf CloudFormation ) renderStackChanges (in renderStackChangesInput ) error {
126
+ func (cf CloudFormation ) newRenderWorkloadInput (w progress.FileWriter , stack * cloudformation.Stack ) * renderStackChangesInput {
127
+ in := & renderStackChangesInput {
128
+ w : w ,
129
+ stackName : stack .Name ,
130
+ stackDescription : fmt .Sprintf ("Creating the infrastructure for stack %s" , stack .Name ),
131
+ }
132
+ in .createChangeSet = func () (changeSetID string , err error ) {
133
+ spinner := progress .NewSpinner (w )
134
+ label := fmt .Sprintf ("Proposing infrastructure changes for stack %s" , stack .Name )
135
+ spinner .Start (label )
136
+ changeSetID , err = cf .cfnClient .Create (stack )
137
+ if err == nil {
138
+ // Successfully created the change set to create the stack.
139
+ spinner .Stop (log .Ssuccessf ("%s\n " , label ))
140
+ return changeSetID , nil
141
+ }
142
+
143
+ var errAlreadyExists * cloudformation.ErrStackAlreadyExists
144
+ if ! errors .As (err , & errAlreadyExists ) {
145
+ // Unexpected error trying to create a stack.
146
+ spinner .Stop (log .Serrorf ("%s\n " , label ))
147
+ return "" , cf .handleStackError (stack .Name , err )
148
+ }
149
+
150
+ // We have to create an update stack change set instead.
151
+ in .stackDescription = fmt .Sprintf ("Updating the infrastructure for stack %s" , stack .Name )
152
+ changeSetID , err = cf .cfnClient .Update (stack )
153
+ if err != nil {
154
+ spinner .Stop (log .Serrorf ("%s\n " , label ))
155
+ return "" , cf .handleStackError (stack .Name , err )
156
+ }
157
+ spinner .Stop (log .Ssuccessf ("%s\n " , label ))
158
+ return changeSetID , nil
159
+ }
160
+ return in
161
+ }
162
+
163
+ func (cf CloudFormation ) renderStackChanges (in * renderStackChangesInput ) error {
116
164
changeSetID , err := in .createChangeSet ()
117
165
if err != nil {
118
166
return err
@@ -190,6 +238,12 @@ func (cf CloudFormation) changeRenderers(in changeRenderersInput) ([]progress.Re
190
238
}
191
239
var renderer progress.Renderer
192
240
switch {
241
+ case aws .StringValue (change .ResourceChange .ResourceType ) == ecsServiceResourceType :
242
+ renderer = progress .ListeningECSServiceResourceRenderer (in .stackStreamer , cf .ecsClient , logicalID , description , progress.ECSServiceRendererOpts {
243
+ Group : in .g ,
244
+ Ctx : in .ctx ,
245
+ RenderOpts : in .opts ,
246
+ })
193
247
case change .ResourceChange .ChangeSetId != nil :
194
248
// The resource change is a nested stack.
195
249
changeSetID := aws .StringValue (change .ResourceChange .ChangeSetId )
0 commit comments