@@ -19,55 +19,80 @@ limitations under the License.
1919package cloudformation
2020
2121import (
22+ "context"
2223 "fmt"
2324 "os"
2425 "text/tabwriter"
26+ "time"
2527
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 "
2931 go_cfn "github.com/awslabs/goformation/v4/cloudformation"
3032 "github.com/pkg/errors"
3133 "k8s.io/klog/v2"
32- "k8s.io/utils/ptr"
3334
3435 "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/awserrors"
3536)
3637
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+
3762// Service holds a collection of interfaces.
3863// The interfaces are broken down like this to group functions together.
3964// One alternative is to have a large list of functions from the ec2 client.
4065type Service struct {
41- CFN cloudformationiface. CloudFormationAPI
66+ CFN CFNAPI
4267}
4368
4469// NewService returns a new service given the CloudFormation api client.
45- func NewService (i cloudformationiface. CloudFormationAPI ) * Service {
70+ func NewService (i CFNAPI ) * Service {
4671 return & Service {
4772 CFN : i ,
4873 }
4974}
5075
5176// 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 {
5378 yaml , err := t .YAML ()
5479 processedYaml := string (yaml )
5580 if err != nil {
5681 return errors .Wrap (err , "failed to generate AWS CloudFormation YAML" )
5782 }
5883
59- stackTags := []* cfn .Tag {}
84+ stackTags := []cfntypes .Tag {}
6085 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 ),
6489 })
6590 }
6691 //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 () {
6994 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 )
7196 if updateErr != nil {
7297 code , ok := awserrors .Code (errors .Cause (updateErr ))
7398 message := awserrors .Message (errors .Cause (updateErr ))
@@ -83,25 +108,25 @@ func (s *Service) ReconcileBootstrapStack(stackName string, t go_cfn.Template, t
83108}
84109
85110// 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 {
87112 yaml , err := t .YAML ()
88113 processedYaml := string (yaml )
89114 if err != nil {
90115 return errors .Wrap (err , "failed to generate AWS CloudFormation YAML" )
91116 }
92117
93- stackTags := []* cfn .Tag {}
118+ stackTags := []cfntypes .Tag {}
94119 for k , v := range tags {
95- stackTags = append (stackTags , & cfn .Tag {
120+ stackTags = append (stackTags , cfntypes .Tag {
96121 Key : aws .String (k ),
97122 Value : aws .String (v ),
98123 })
99124 }
100125 //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 () {
103128 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 {
105130 return errors .Wrap (err , "failed to wait for AWS CloudFormation stack to be CreateComplete" )
106131 }
107132 return nil
@@ -111,42 +136,42 @@ func (s *Service) ReconcileBootstrapNoUpdate(stackName string, t go_cfn.Template
111136 return nil
112137}
113138
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 {
115140 input := & cfn.CreateStackInput {
116- Capabilities : aws . StringSlice ([] string { cfn .CapabilityCapabilityIam , cfn .CapabilityCapabilityNamedIam }) ,
141+ Capabilities : []cfntypes. Capability { cfntypes .CapabilityCapabilityIam , cfntypes .CapabilityCapabilityNamedIam },
117142 TemplateBody : aws .String (yaml ),
118143 StackName : aws .String (stackName ),
119144 Tags : tags ,
120145 }
121146 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 {
123148 return errors .Wrap (err , "failed to create AWS CloudFormation stack" )
124149 }
125150
126151 desInput := & cfn.DescribeStacksInput {StackName : aws .String (stackName )}
127152 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 {
129154 return errors .Wrap (err , "failed to wait for AWS CloudFormation stack to be CreateComplete" )
130155 }
131156
132157 klog .V (2 ).Infof ("stack %q created" , stackName )
133158 return nil
134159}
135160
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 {
137162 input := & cfn.UpdateStackInput {
138- Capabilities : aws . StringSlice ([] string { cfn .CapabilityCapabilityIam , cfn .CapabilityCapabilityNamedIam }) ,
163+ Capabilities : []cfntypes. Capability { cfntypes .CapabilityCapabilityIam , cfntypes .CapabilityCapabilityNamedIam },
139164 TemplateBody : aws .String (yaml ),
140165 StackName : aws .String (stackName ),
141166 Tags : tags ,
142167 }
143168 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 {
145170 return errors .Wrap (err , "failed to update AWS CloudFormation stack" )
146171 }
147172 desInput := & cfn.DescribeStacksInput {StackName : aws .String (stackName )}
148173 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 {
150175 return errors .Wrap (err , "failed to update AWS CloudFormation stack" )
151176 }
152177
@@ -155,20 +180,20 @@ func (s *Service) updateStack(stackName, yaml string, tags []*cfn.Tag) error {
155180}
156181
157182// 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 {
159184 klog .V (2 ).Infof ("deleting AWS CloudFormation stack %q" , stackName )
160185 var err error
161186 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 )})
163188 } 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 })
165190 }
166191 if err != nil {
167192 return errors .Wrap (err , "failed to delete AWS CloudFormation stack" )
168193 }
169194
170195 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 {
172197 return errors .Wrap (err , "failed to delete AWS CloudFormation stack" )
173198 }
174199
@@ -178,11 +203,11 @@ func (s *Service) DeleteStack(stackName string, retainResources []*string) error
178203
179204// ShowStackResources prints out in tabular format the resources in the
180205// stack.
181- func (s * Service ) ShowStackResources (stackName string ) error {
206+ func (s * Service ) ShowStackResources (ctx context. Context , stackName string ) error {
182207 input := & cfn.DescribeStackResourcesInput {
183208 StackName : aws .String (stackName ),
184209 }
185- out , err := s .CFN .DescribeStackResources (input )
210+ out , err := s .CFN .DescribeStackResources (ctx , input )
186211 if err != nil {
187212 return errors .Wrap (err , "unable to describe stack resources" )
188213 }
@@ -195,15 +220,15 @@ func (s *Service) ShowStackResources(stackName string) error {
195220
196221 for _ , r := range out .StackResources {
197222 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 )
201226
202- switch aws . StringValue ( r .ResourceStatus ) {
203- case cfn .ResourceStatusCreateComplete , cfn .ResourceStatusUpdateComplete :
227+ switch r .ResourceStatus {
228+ case cfntypes .ResourceStatusCreateComplete , cfntypes .ResourceStatusUpdateComplete :
204229 continue
205230 default :
206- fmt .Println (aws .StringValue (r .ResourceStatusReason ))
231+ fmt .Println (aws .ToString (r .ResourceStatusReason ))
207232 }
208233 }
209234
@@ -213,3 +238,30 @@ func (s *Service) ShowStackResources(stackName string) error {
213238
214239 return nil
215240}
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