sigs.k8s.io/cluster-api-provider-aws@v1.5.5/cmd/clusterawsadm/cloudformation/service/service.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package cloudformation provides the API operation methods for making requests to 18 // AWS CloudFormation. 19 package cloudformation 20 21 import ( 22 "fmt" 23 "os" 24 "text/tabwriter" 25 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" 29 go_cfn "github.com/awslabs/goformation/v4/cloudformation" 30 "github.com/pkg/errors" 31 "k8s.io/klog/v2" 32 "k8s.io/utils/pointer" 33 34 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 35 ) 36 37 // Service holds a collection of interfaces. 38 // The interfaces are broken down like this to group functions together. 39 // One alternative is to have a large list of functions from the ec2 client. 40 type Service struct { 41 CFN cloudformationiface.CloudFormationAPI 42 } 43 44 // NewService returns a new service given the CloudFormation api client. 45 func NewService(i cloudformationiface.CloudFormationAPI) *Service { 46 return &Service{ 47 CFN: i, 48 } 49 } 50 51 // ReconcileBootstrapStack creates or updates bootstrap CloudFormation. 52 func (s *Service) ReconcileBootstrapStack(stackName string, t go_cfn.Template, tags map[string]string) error { 53 yaml, err := t.YAML() 54 processedYaml := string(yaml) 55 if err != nil { 56 return errors.Wrap(err, "failed to generate AWS CloudFormation YAML") 57 } 58 59 stackTags := []*cfn.Tag{} 60 for k, v := range tags { 61 stackTags = append(stackTags, &cfn.Tag{ 62 Key: pointer.StringPtr(k), 63 Value: pointer.StringPtr(v), 64 }) 65 } 66 if err := s.createStack(stackName, processedYaml, stackTags); err != nil { // nolint:nestif 67 if code, _ := awserrors.Code(errors.Cause(err)); code == "AlreadyExistsException" { 68 klog.Infof("AWS Cloudformation stack %q already exists, updating", stackName) 69 updateErr := s.updateStack(stackName, processedYaml, stackTags) 70 if updateErr != nil { 71 code, ok := awserrors.Code(errors.Cause(updateErr)) 72 message := awserrors.Message(errors.Cause(updateErr)) 73 if !ok || code != "ValidationError" || message != "No updates are to be performed." { 74 return updateErr 75 } 76 } 77 return nil 78 } 79 return err 80 } 81 return nil 82 } 83 84 func (s *Service) createStack(stackName, yaml string, tags []*cfn.Tag) error { 85 input := &cfn.CreateStackInput{ 86 Capabilities: aws.StringSlice([]string{cfn.CapabilityCapabilityIam, cfn.CapabilityCapabilityNamedIam}), 87 TemplateBody: aws.String(yaml), 88 StackName: aws.String(stackName), 89 Tags: tags, 90 } 91 klog.V(2).Infof("creating AWS CloudFormation stack %q", stackName) 92 if _, err := s.CFN.CreateStack(input); err != nil { 93 return errors.Wrap(err, "failed to create AWS CloudFormation stack") 94 } 95 96 desInput := &cfn.DescribeStacksInput{StackName: aws.String(stackName)} 97 klog.V(2).Infof("waiting for stack %q to create", stackName) 98 if err := s.CFN.WaitUntilStackCreateComplete(desInput); err != nil { 99 return errors.Wrap(err, "failed to create AWS CloudFormation stack") 100 } 101 102 klog.V(2).Infof("stack %q created", stackName) 103 return nil 104 } 105 106 func (s *Service) updateStack(stackName, yaml string, tags []*cfn.Tag) error { 107 input := &cfn.UpdateStackInput{ 108 Capabilities: aws.StringSlice([]string{cfn.CapabilityCapabilityIam, cfn.CapabilityCapabilityNamedIam}), 109 TemplateBody: aws.String(yaml), 110 StackName: aws.String(stackName), 111 Tags: tags, 112 } 113 klog.V(2).Infof("updating AWS CloudFormation stack %q", stackName) 114 if _, err := s.CFN.UpdateStack(input); err != nil { 115 return errors.Wrap(err, "failed to update AWS CloudFormation stack") 116 } 117 desInput := &cfn.DescribeStacksInput{StackName: aws.String(stackName)} 118 klog.V(2).Infof("waiting for stack %q to update", stackName) 119 if err := s.CFN.WaitUntilStackUpdateComplete(desInput); err != nil { 120 return errors.Wrap(err, "failed to update AWS CloudFormation stack") 121 } 122 123 klog.V(2).Infof("stack %q updated", stackName) 124 return nil 125 } 126 127 // DeleteStack deletes a cloudformation stack. 128 func (s *Service) DeleteStack(stackName string, retainResources []*string) error { 129 klog.V(2).Infof("deleting AWS CloudFormation stack %q", stackName) 130 var err error 131 if retainResources == nil { 132 _, err = s.CFN.DeleteStack(&cfn.DeleteStackInput{StackName: aws.String(stackName)}) 133 } else { 134 _, err = s.CFN.DeleteStack(&cfn.DeleteStackInput{StackName: aws.String(stackName), RetainResources: retainResources}) 135 } 136 if err != nil { 137 return errors.Wrap(err, "failed to delete AWS CloudFormation stack") 138 } 139 140 klog.V(2).Infof("waiting for stack %q to delete", stackName) 141 if err := s.CFN.WaitUntilStackDeleteComplete(&cfn.DescribeStacksInput{StackName: aws.String(stackName)}); err != nil { 142 return errors.Wrap(err, "failed to delete AWS CloudFormation stack") 143 } 144 145 klog.V(2).Infof("stack %q deleted", stackName) 146 return nil 147 } 148 149 // ShowStackResources prints out in tabular format the resources in the 150 // stack. 151 func (s *Service) ShowStackResources(stackName string) error { 152 input := &cfn.DescribeStackResourcesInput{ 153 StackName: aws.String(stackName), 154 } 155 out, err := s.CFN.DescribeStackResources(input) 156 if err != nil { 157 return errors.Wrap(err, "unable to describe stack resources") 158 } 159 160 fmt.Print("\nFollowing resources are in the stack: \n\n") 161 162 w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug) 163 164 fmt.Fprintln(w, "Resource\tType\tStatus") 165 166 for _, r := range out.StackResources { 167 fmt.Fprintf(w, "%s\t%s\t%s\n", 168 aws.StringValue(r.ResourceType), 169 aws.StringValue(r.PhysicalResourceId), 170 aws.StringValue(r.ResourceStatus)) 171 172 switch aws.StringValue(r.ResourceStatus) { 173 case cfn.ResourceStatusCreateComplete, cfn.ResourceStatusUpdateComplete: 174 continue 175 default: 176 fmt.Println(aws.StringValue(r.ResourceStatusReason)) 177 } 178 } 179 180 w.Flush() 181 182 fmt.Print("\n\n") 183 184 return nil 185 }