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  }