sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/network/vpc.go (about) 1 /* 2 Copyright 2018 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 network 18 19 import ( 20 "fmt" 21 22 "github.com/aws/aws-sdk-go/aws" 23 "github.com/aws/aws-sdk-go/service/ec2" 24 "github.com/pkg/errors" 25 kerrors "k8s.io/apimachinery/pkg/util/errors" 26 27 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 28 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 29 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/converters" 30 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/filter" 31 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services" 32 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait" 33 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/tags" 34 "sigs.k8s.io/cluster-api-provider-aws/pkg/record" 35 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 36 "sigs.k8s.io/cluster-api/util/conditions" 37 ) 38 39 const ( 40 defaultVPCCidr = "10.0.0.0/16" 41 ) 42 43 func (s *Service) reconcileVPC() error { 44 s.scope.V(2).Info("Reconciling VPC") 45 46 // If the ID is not nil, VPC is either managed or unmanaged but should exist in the AWS. 47 if s.scope.VPC().ID != "" { 48 vpc, err := s.describeVPCByID() 49 if err != nil { 50 return errors.Wrap(err, ".spec.vpc.id is set but VPC resource is missing in AWS; failed to describe VPC resources. (might be in creation process)") 51 } 52 53 s.scope.VPC().CidrBlock = vpc.CidrBlock 54 s.scope.VPC().Tags = vpc.Tags 55 56 // If VPC is unmanaged, return early. 57 if vpc.IsUnmanaged(s.scope.Name()) { 58 s.scope.V(2).Info("Working on unmanaged VPC", "vpc-id", vpc.ID) 59 if err := s.scope.PatchObject(); err != nil { 60 return errors.Wrap(err, "failed to patch unmanaged VPC fields") 61 } 62 record.Eventf(s.scope.InfraCluster(), "SuccessfulSetVPCAttributes", "Set managed VPC attributes for %q", vpc.ID) 63 return nil 64 } 65 66 // if the VPC is managed, make managed sure attributes are configured. 67 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 68 if err := s.ensureManagedVPCAttributes(vpc); err != nil { 69 return false, err 70 } 71 return true, nil 72 }, awserrors.VPCNotFound); err != nil { 73 return errors.Wrapf(err, "failed to to set vpc attributes for %q", vpc.ID) 74 } 75 76 return nil 77 } 78 79 // .spec.vpc.id is nil, Create a new managed vpc. 80 if !conditions.Has(s.scope.InfraCluster(), infrav1.VpcReadyCondition) { 81 conditions.MarkFalse(s.scope.InfraCluster(), infrav1.VpcReadyCondition, infrav1.VpcCreationStartedReason, clusterv1.ConditionSeverityInfo, "") 82 if err := s.scope.PatchObject(); err != nil { 83 return errors.Wrap(err, "failed to patch conditions") 84 } 85 } 86 vpc, err := s.createVPC() 87 if err != nil { 88 return errors.Wrap(err, "failed to create new vpc") 89 } 90 s.scope.Info("Created VPC", "vpc-id", vpc.ID) 91 92 s.scope.VPC().CidrBlock = vpc.CidrBlock 93 s.scope.VPC().Tags = vpc.Tags 94 s.scope.VPC().ID = vpc.ID 95 96 // Make sure attributes are configured 97 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 98 if err := s.ensureManagedVPCAttributes(vpc); err != nil { 99 return false, err 100 } 101 return true, nil 102 }, awserrors.VPCNotFound); err != nil { 103 return errors.Wrapf(err, "failed to to set vpc attributes for %q", vpc.ID) 104 } 105 106 return nil 107 } 108 109 func (s *Service) ensureManagedVPCAttributes(vpc *infrav1.VPCSpec) error { 110 var ( 111 errs []error 112 updated bool 113 ) 114 115 // Cannot get or set both attributes at the same time. 116 descAttrInput := &ec2.DescribeVpcAttributeInput{ 117 VpcId: aws.String(vpc.ID), 118 Attribute: aws.String("enableDnsHostnames"), 119 } 120 vpcAttr, err := s.EC2Client.DescribeVpcAttribute(descAttrInput) 121 if err != nil { 122 // If the returned error is a 'NotFound' error it should trigger retry 123 if code, ok := awserrors.Code(errors.Cause(err)); ok && code == awserrors.VPCNotFound { 124 return err 125 } 126 errs = append(errs, errors.Wrap(err, "failed to describe enableDnsHostnames vpc attribute")) 127 } else if !aws.BoolValue(vpcAttr.EnableDnsHostnames.Value) { 128 attrInput := &ec2.ModifyVpcAttributeInput{ 129 VpcId: aws.String(vpc.ID), 130 EnableDnsHostnames: &ec2.AttributeBooleanValue{Value: aws.Bool(true)}, 131 } 132 if _, err := s.EC2Client.ModifyVpcAttribute(attrInput); err != nil { 133 errs = append(errs, errors.Wrap(err, "failed to set enableDnsHostnames vpc attribute")) 134 } else { 135 updated = true 136 } 137 } 138 139 descAttrInput = &ec2.DescribeVpcAttributeInput{ 140 VpcId: aws.String(vpc.ID), 141 Attribute: aws.String("enableDnsSupport"), 142 } 143 vpcAttr, err = s.EC2Client.DescribeVpcAttribute(descAttrInput) 144 if err != nil { 145 // If the returned error is a 'NotFound' error it should trigger retry 146 if code, ok := awserrors.Code(errors.Cause(err)); ok && code == awserrors.VPCNotFound { 147 return err 148 } 149 errs = append(errs, errors.Wrap(err, "failed to describe enableDnsSupport vpc attribute")) 150 } else if !aws.BoolValue(vpcAttr.EnableDnsSupport.Value) { 151 attrInput := &ec2.ModifyVpcAttributeInput{ 152 VpcId: aws.String(vpc.ID), 153 EnableDnsSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(true)}, 154 } 155 if _, err := s.EC2Client.ModifyVpcAttribute(attrInput); err != nil { 156 errs = append(errs, errors.Wrap(err, "failed to set enableDnsSupport vpc attribute")) 157 } else { 158 updated = true 159 } 160 } 161 162 if len(errs) > 0 { 163 record.Warnf(s.scope.InfraCluster(), "FailedSetVPCAttributes", "Failed to set managed VPC attributes for %q: %v", vpc.ID, err) 164 return kerrors.NewAggregate(errs) 165 } 166 167 if updated { 168 record.Eventf(s.scope.InfraCluster(), "SuccessfulSetVPCAttributes", "Set managed VPC attributes for %q", vpc.ID) 169 } 170 171 return nil 172 } 173 174 func (s *Service) createVPC() (*infrav1.VPCSpec, error) { 175 if s.scope.VPC().CidrBlock == "" { 176 s.scope.VPC().CidrBlock = defaultVPCCidr 177 } 178 179 input := &ec2.CreateVpcInput{ 180 CidrBlock: aws.String(s.scope.VPC().CidrBlock), 181 TagSpecifications: []*ec2.TagSpecification{ 182 tags.BuildParamsToTagSpecification(ec2.ResourceTypeVpc, s.getVPCTagParams(services.TemporaryResourceID)), 183 }, 184 } 185 186 out, err := s.EC2Client.CreateVpc(input) 187 if err != nil { 188 record.Warnf(s.scope.InfraCluster(), "FailedCreateVPC", "Failed to create new managed VPC: %v", err) 189 return nil, errors.Wrap(err, "failed to create vpc") 190 } 191 192 record.Eventf(s.scope.InfraCluster(), "SuccessfulCreateVPC", "Created new managed VPC %q", *out.Vpc.VpcId) 193 s.scope.V(2).Info("Created new VPC with cidr", "vpc-id", *out.Vpc.VpcId, "cidr-block", *out.Vpc.CidrBlock) 194 195 return &infrav1.VPCSpec{ 196 ID: *out.Vpc.VpcId, 197 CidrBlock: *out.Vpc.CidrBlock, 198 Tags: converters.TagsToMap(out.Vpc.Tags), 199 }, nil 200 } 201 202 func (s *Service) deleteVPC() error { 203 vpc := s.scope.VPC() 204 205 if vpc.IsUnmanaged(s.scope.Name()) { 206 s.scope.V(4).Info("Skipping VPC deletion in unmanaged mode") 207 return nil 208 } 209 210 input := &ec2.DeleteVpcInput{ 211 VpcId: aws.String(vpc.ID), 212 } 213 214 if _, err := s.EC2Client.DeleteVpc(input); err != nil { 215 // Ignore if it's already deleted 216 if code, ok := awserrors.Code(err); ok && code == awserrors.VPCNotFound { 217 s.scope.V(4).Info("Skipping VPC deletion, VPC not found") 218 return nil 219 } 220 record.Warnf(s.scope.InfraCluster(), "FailedDeleteVPC", "Failed to delete managed VPC %q: %v", vpc.ID, err) 221 return errors.Wrapf(err, "failed to delete vpc %q", vpc.ID) 222 } 223 224 s.scope.Info("Deleted VPC", "vpc-id", vpc.ID) 225 record.Eventf(s.scope.InfraCluster(), "SuccessfulDeleteVPC", "Deleted managed VPC %q", vpc.ID) 226 return nil 227 } 228 229 func (s *Service) describeVPCByID() (*infrav1.VPCSpec, error) { 230 if s.scope.VPC().ID == "" { 231 return nil, errors.New("VPC ID is not set, failed to describe VPCs by ID") 232 } 233 234 input := &ec2.DescribeVpcsInput{ 235 Filters: []*ec2.Filter{ 236 filter.EC2.VPCStates(ec2.VpcStatePending, ec2.VpcStateAvailable), 237 }, 238 } 239 240 input.VpcIds = []*string{aws.String(s.scope.VPC().ID)} 241 242 out, err := s.EC2Client.DescribeVpcs(input) 243 if err != nil { 244 if awserrors.IsNotFound(err) { 245 return nil, err 246 } 247 248 return nil, errors.Wrap(err, "failed to query ec2 for VPCs") 249 } 250 251 if len(out.Vpcs) == 0 { 252 return nil, awserrors.NewNotFound(fmt.Sprintf("could not find vpc %q", s.scope.VPC().ID)) 253 } else if len(out.Vpcs) > 1 { 254 return nil, awserrors.NewConflict(fmt.Sprintf("found %v VPCs with matching tags for %v. Only one VPC per cluster name is supported. Ensure duplicate VPCs are deleted for this AWS account and there are no conflicting instances of Cluster API Provider AWS. filtered VPCs: %v", len(out.Vpcs), s.scope.Name(), out.GoString())) 255 } 256 257 switch *out.Vpcs[0].State { 258 case ec2.VpcStateAvailable, ec2.VpcStatePending: 259 default: 260 return nil, awserrors.NewNotFound("could not find available or pending vpc") 261 } 262 263 return &infrav1.VPCSpec{ 264 ID: *out.Vpcs[0].VpcId, 265 CidrBlock: *out.Vpcs[0].CidrBlock, 266 Tags: converters.TagsToMap(out.Vpcs[0].Tags), 267 }, nil 268 } 269 270 func (s *Service) getVPCTagParams(id string) infrav1.BuildParams { 271 name := fmt.Sprintf("%s-vpc", s.scope.Name()) 272 273 return infrav1.BuildParams{ 274 ClusterName: s.scope.Name(), 275 ResourceID: id, 276 Lifecycle: infrav1.ResourceLifecycleOwned, 277 Name: aws.String(name), 278 Role: aws.String(infrav1.CommonRoleTagValue), 279 Additional: s.scope.AdditionalTags(), 280 } 281 }