@@ -19,55 +19,80 @@ limitations under the License.
19
19
package cloudformation
20
20
21
21
import (
22
+ "context"
22
23
"fmt"
23
24
"os"
24
25
"text/tabwriter"
26
+ "time"
25
27
26
- "github.com/aws/aws-sdk-go/aws"
27
- cfn "github.com/aws/aws-sdk-go/service/cloudformation"
28
- "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface "
28
+ "github.com/aws/aws-sdk-go-v2 /aws"
29
+ cfn "github.com/aws/aws-sdk-go-v2 /service/cloudformation"
30
+ cfntypes "github.com/aws/aws-sdk-go-v2 /service/cloudformation/types "
29
31
go_cfn "github.com/awslabs/goformation/v4/cloudformation"
30
32
"github.com/pkg/errors"
31
33
"k8s.io/klog/v2"
32
- "k8s.io/utils/ptr"
33
34
34
35
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/awserrors"
35
36
)
36
37
38
+ const (
39
+ // MaxWaitCreateUpdateDelete is the default maximum amount of time to wait for a cfn stack to complete.
40
+ MaxWaitCreateUpdateDelete = 30 * time .Minute
41
+ )
42
+
43
+ // CFNAPI defines the CFN API interface.
44
+ type CFNAPI interface {
45
+ CreateStack (ctx context.Context , params * cfn.CreateStackInput , optFns ... func (* cfn.Options )) (* cfn.CreateStackOutput , error )
46
+ DeleteStack (ctx context.Context , params * cfn.DeleteStackInput , optFns ... func (* cfn.Options )) (* cfn.DeleteStackOutput , error )
47
+ DescribeStacks (ctx context.Context , params * cfn.DescribeStacksInput , optFns ... func (* cfn.Options )) (* cfn.DescribeStacksOutput , error )
48
+ DescribeStackResources (ctx context.Context , params * cfn.DescribeStackResourcesInput , optFns ... func (* cfn.Options )) (* cfn.DescribeStackResourcesOutput , error )
49
+ UpdateStack (ctx context.Context , params * cfn.UpdateStackInput , optFns ... func (* cfn.Options )) (* cfn.UpdateStackOutput , error )
50
+
51
+ // Waiters for CFN stacks
52
+ WaitUntilStackCreateComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error
53
+ WaitUntilStackUpdateComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error
54
+ WaitUntilStackDeleteComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error
55
+ }
56
+
57
+ // CFNClient is a wrapper over cfn.Client for implementing custom methods of CFNAPI.
58
+ type CFNClient struct {
59
+ * cfn.Client
60
+ }
61
+
37
62
// Service holds a collection of interfaces.
38
63
// The interfaces are broken down like this to group functions together.
39
64
// One alternative is to have a large list of functions from the ec2 client.
40
65
type Service struct {
41
- CFN cloudformationiface. CloudFormationAPI
66
+ CFN CFNAPI
42
67
}
43
68
44
69
// NewService returns a new service given the CloudFormation api client.
45
- func NewService (i cloudformationiface. CloudFormationAPI ) * Service {
70
+ func NewService (i CFNAPI ) * Service {
46
71
return & Service {
47
72
CFN : i ,
48
73
}
49
74
}
50
75
51
76
// ReconcileBootstrapStack creates or updates bootstrap CloudFormation.
52
- func (s * Service ) ReconcileBootstrapStack (stackName string , t go_cfn.Template , tags map [string ]string ) error {
77
+ func (s * Service ) ReconcileBootstrapStack (ctx context. Context , stackName string , t go_cfn.Template , tags map [string ]string ) error {
53
78
yaml , err := t .YAML ()
54
79
processedYaml := string (yaml )
55
80
if err != nil {
56
81
return errors .Wrap (err , "failed to generate AWS CloudFormation YAML" )
57
82
}
58
83
59
- stackTags := []* cfn .Tag {}
84
+ stackTags := []cfntypes .Tag {}
60
85
for k , v := range tags {
61
- stackTags = append (stackTags , & cfn .Tag {
62
- Key : ptr. To [ string ] (k ),
63
- Value : ptr. To [ string ] (v ),
86
+ stackTags = append (stackTags , cfntypes .Tag {
87
+ Key : aws . String (k ),
88
+ Value : aws . String (v ),
64
89
})
65
90
}
66
91
//nolint:nestif
67
- if err := s .createStack (stackName , processedYaml , stackTags ); err != nil {
68
- if code , _ := awserrors .Code (errors .Cause (err )); code == " AlreadyExistsException" {
92
+ if err := s .createStack (ctx , stackName , processedYaml , stackTags ); err != nil {
93
+ if code , _ := awserrors .Code (errors .Cause (err )); code == ( & cfntypes. AlreadyExistsException {}). ErrorCode () {
69
94
klog .Infof ("AWS Cloudformation stack %q already exists, updating" , klog .KRef ("" , stackName ))
70
- updateErr := s .updateStack (stackName , processedYaml , stackTags )
95
+ updateErr := s .updateStack (ctx , stackName , processedYaml , stackTags )
71
96
if updateErr != nil {
72
97
code , ok := awserrors .Code (errors .Cause (updateErr ))
73
98
message := awserrors .Message (errors .Cause (updateErr ))
@@ -83,25 +108,25 @@ func (s *Service) ReconcileBootstrapStack(stackName string, t go_cfn.Template, t
83
108
}
84
109
85
110
// ReconcileBootstrapNoUpdate creates or updates bootstrap CloudFormation without updating the stack.
86
- func (s * Service ) ReconcileBootstrapNoUpdate (stackName string , t go_cfn.Template , tags map [string ]string ) error {
111
+ func (s * Service ) ReconcileBootstrapNoUpdate (ctx context. Context , stackName string , t go_cfn.Template , tags map [string ]string ) error {
87
112
yaml , err := t .YAML ()
88
113
processedYaml := string (yaml )
89
114
if err != nil {
90
115
return errors .Wrap (err , "failed to generate AWS CloudFormation YAML" )
91
116
}
92
117
93
- stackTags := []* cfn .Tag {}
118
+ stackTags := []cfntypes .Tag {}
94
119
for k , v := range tags {
95
- stackTags = append (stackTags , & cfn .Tag {
120
+ stackTags = append (stackTags , cfntypes .Tag {
96
121
Key : aws .String (k ),
97
122
Value : aws .String (v ),
98
123
})
99
124
}
100
125
//nolint:nestif
101
- if err := s .createStack (stackName , processedYaml , stackTags ); err != nil {
102
- if code , _ := awserrors .Code (errors .Cause (err )); code == " AlreadyExistsException" {
126
+ if err := s .createStack (ctx , stackName , processedYaml , stackTags ); err != nil {
127
+ if code , _ := awserrors .Code (errors .Cause (err )); code == ( & cfntypes. AlreadyExistsException {}). ErrorCode () {
103
128
desInput := & cfn.DescribeStacksInput {StackName : aws .String (stackName )}
104
- if err := s .CFN .WaitUntilStackCreateComplete (desInput ); err != nil {
129
+ if err := s .CFN .WaitUntilStackCreateComplete (ctx , desInput , MaxWaitCreateUpdateDelete ); err != nil {
105
130
return errors .Wrap (err , "failed to wait for AWS CloudFormation stack to be CreateComplete" )
106
131
}
107
132
return nil
@@ -111,42 +136,42 @@ func (s *Service) ReconcileBootstrapNoUpdate(stackName string, t go_cfn.Template
111
136
return nil
112
137
}
113
138
114
- func (s * Service ) createStack (stackName , yaml string , tags []* cfn .Tag ) error {
139
+ func (s * Service ) createStack (ctx context. Context , stackName , yaml string , tags []cfntypes .Tag ) error {
115
140
input := & cfn.CreateStackInput {
116
- Capabilities : aws . StringSlice ([] string { cfn .CapabilityCapabilityIam , cfn .CapabilityCapabilityNamedIam }) ,
141
+ Capabilities : []cfntypes. Capability { cfntypes .CapabilityCapabilityIam , cfntypes .CapabilityCapabilityNamedIam },
117
142
TemplateBody : aws .String (yaml ),
118
143
StackName : aws .String (stackName ),
119
144
Tags : tags ,
120
145
}
121
146
klog .V (2 ).Infof ("creating AWS CloudFormation stack %q" , stackName )
122
- if _ , err := s .CFN .CreateStack (input ); err != nil {
147
+ if _ , err := s .CFN .CreateStack (ctx , input ); err != nil {
123
148
return errors .Wrap (err , "failed to create AWS CloudFormation stack" )
124
149
}
125
150
126
151
desInput := & cfn.DescribeStacksInput {StackName : aws .String (stackName )}
127
152
klog .V (2 ).Infof ("waiting for stack %q to create" , stackName )
128
- if err := s .CFN .WaitUntilStackCreateComplete (desInput ); err != nil {
153
+ if err := s .CFN .WaitUntilStackCreateComplete (ctx , desInput , MaxWaitCreateUpdateDelete ); err != nil {
129
154
return errors .Wrap (err , "failed to wait for AWS CloudFormation stack to be CreateComplete" )
130
155
}
131
156
132
157
klog .V (2 ).Infof ("stack %q created" , stackName )
133
158
return nil
134
159
}
135
160
136
- func (s * Service ) updateStack (stackName , yaml string , tags []* cfn .Tag ) error {
161
+ func (s * Service ) updateStack (ctx context. Context , stackName , yaml string , tags []cfntypes .Tag ) error {
137
162
input := & cfn.UpdateStackInput {
138
- Capabilities : aws . StringSlice ([] string { cfn .CapabilityCapabilityIam , cfn .CapabilityCapabilityNamedIam }) ,
163
+ Capabilities : []cfntypes. Capability { cfntypes .CapabilityCapabilityIam , cfntypes .CapabilityCapabilityNamedIam },
139
164
TemplateBody : aws .String (yaml ),
140
165
StackName : aws .String (stackName ),
141
166
Tags : tags ,
142
167
}
143
168
klog .V (2 ).Infof ("updating AWS CloudFormation stack %q" , stackName )
144
- if _ , err := s .CFN .UpdateStack (input ); err != nil {
169
+ if _ , err := s .CFN .UpdateStack (ctx , input ); err != nil {
145
170
return errors .Wrap (err , "failed to update AWS CloudFormation stack" )
146
171
}
147
172
desInput := & cfn.DescribeStacksInput {StackName : aws .String (stackName )}
148
173
klog .V (2 ).Infof ("waiting for stack %q to update" , stackName )
149
- if err := s .CFN .WaitUntilStackUpdateComplete (desInput ); err != nil {
174
+ if err := s .CFN .WaitUntilStackUpdateComplete (ctx , desInput , MaxWaitCreateUpdateDelete ); err != nil {
150
175
return errors .Wrap (err , "failed to update AWS CloudFormation stack" )
151
176
}
152
177
@@ -155,20 +180,20 @@ func (s *Service) updateStack(stackName, yaml string, tags []*cfn.Tag) error {
155
180
}
156
181
157
182
// DeleteStack deletes a cloudformation stack.
158
- func (s * Service ) DeleteStack (stackName string , retainResources []* string ) error {
183
+ func (s * Service ) DeleteStack (ctx context. Context , stackName string , retainResources []string ) error {
159
184
klog .V (2 ).Infof ("deleting AWS CloudFormation stack %q" , stackName )
160
185
var err error
161
186
if retainResources == nil {
162
- _ , err = s .CFN .DeleteStack (& cfn.DeleteStackInput {StackName : aws .String (stackName )})
187
+ _ , err = s .CFN .DeleteStack (ctx , & cfn.DeleteStackInput {StackName : aws .String (stackName )})
163
188
} else {
164
- _ , err = s .CFN .DeleteStack (& cfn.DeleteStackInput {StackName : aws .String (stackName ), RetainResources : retainResources })
189
+ _ , err = s .CFN .DeleteStack (ctx , & cfn.DeleteStackInput {StackName : aws .String (stackName ), RetainResources : retainResources })
165
190
}
166
191
if err != nil {
167
192
return errors .Wrap (err , "failed to delete AWS CloudFormation stack" )
168
193
}
169
194
170
195
klog .V (2 ).Infof ("waiting for stack %q to delete" , stackName )
171
- if err := s .CFN .WaitUntilStackDeleteComplete (& cfn.DescribeStacksInput {StackName : aws .String (stackName )}); err != nil {
196
+ if err := s .CFN .WaitUntilStackDeleteComplete (ctx , & cfn.DescribeStacksInput {StackName : aws .String (stackName )}, MaxWaitCreateUpdateDelete ); err != nil {
172
197
return errors .Wrap (err , "failed to delete AWS CloudFormation stack" )
173
198
}
174
199
@@ -178,11 +203,11 @@ func (s *Service) DeleteStack(stackName string, retainResources []*string) error
178
203
179
204
// ShowStackResources prints out in tabular format the resources in the
180
205
// stack.
181
- func (s * Service ) ShowStackResources (stackName string ) error {
206
+ func (s * Service ) ShowStackResources (ctx context. Context , stackName string ) error {
182
207
input := & cfn.DescribeStackResourcesInput {
183
208
StackName : aws .String (stackName ),
184
209
}
185
- out , err := s .CFN .DescribeStackResources (input )
210
+ out , err := s .CFN .DescribeStackResources (ctx , input )
186
211
if err != nil {
187
212
return errors .Wrap (err , "unable to describe stack resources" )
188
213
}
@@ -195,15 +220,15 @@ func (s *Service) ShowStackResources(stackName string) error {
195
220
196
221
for _ , r := range out .StackResources {
197
222
fmt .Fprintf (w , "%s\t %s\t %s\n " ,
198
- aws .StringValue (r .ResourceType ),
199
- aws .StringValue (r .PhysicalResourceId ),
200
- aws . StringValue ( r .ResourceStatus ) )
223
+ aws .ToString (r .ResourceType ),
224
+ aws .ToString (r .PhysicalResourceId ),
225
+ r .ResourceStatus )
201
226
202
- switch aws . StringValue ( r .ResourceStatus ) {
203
- case cfn .ResourceStatusCreateComplete , cfn .ResourceStatusUpdateComplete :
227
+ switch r .ResourceStatus {
228
+ case cfntypes .ResourceStatusCreateComplete , cfntypes .ResourceStatusUpdateComplete :
204
229
continue
205
230
default :
206
- fmt .Println (aws .StringValue (r .ResourceStatusReason ))
231
+ fmt .Println (aws .ToString (r .ResourceStatusReason ))
207
232
}
208
233
}
209
234
@@ -213,3 +238,30 @@ func (s *Service) ShowStackResources(stackName string) error {
213
238
214
239
return nil
215
240
}
241
+
242
+ // WaitUntilStackCreateComplete is blocking function to wait until CFN Stack is successfully created.
243
+ func (c * CFNClient ) WaitUntilStackCreateComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error {
244
+ waiter := cfn .NewStackCreateCompleteWaiter (c , func (o * cfn.StackCreateCompleteWaiterOptions ) {
245
+ o .LogWaitAttempts = true
246
+ })
247
+
248
+ return waiter .Wait (ctx , input , maxWait )
249
+ }
250
+
251
+ // WaitUntilStackUpdateComplete is blocking function to wait until CFN Stack is successfully updated.
252
+ func (c * CFNClient ) WaitUntilStackUpdateComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error {
253
+ waiter := cfn .NewStackUpdateCompleteWaiter (c , func (o * cfn.StackUpdateCompleteWaiterOptions ) {
254
+ o .LogWaitAttempts = true
255
+ })
256
+
257
+ return waiter .Wait (ctx , input , maxWait )
258
+ }
259
+
260
+ // WaitUntilStackDeleteComplete is blocking function to wait until CFN Stack is successfully deleted.
261
+ func (c * CFNClient ) WaitUntilStackDeleteComplete (ctx context.Context , input * cfn.DescribeStacksInput , maxWait time.Duration ) error {
262
+ waiter := cfn .NewStackDeleteCompleteWaiter (c , func (o * cfn.StackDeleteCompleteWaiterOptions ) {
263
+ o .LogWaitAttempts = true
264
+ })
265
+
266
+ return waiter .Wait (ctx , input , maxWait )
267
+ }
0 commit comments