sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/eks/fargate.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 eks
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/aws/aws-sdk-go/aws"
    24  	"github.com/aws/aws-sdk-go/aws/awserr"
    25  	"github.com/aws/aws-sdk-go/service/eks"
    26  	"github.com/aws/aws-sdk-go/service/iam"
    27  	"github.com/pkg/errors"
    28  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    29  
    30  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    31  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api-provider-aws/pkg/record"
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	capierrors "sigs.k8s.io/cluster-api/errors"
    35  	"sigs.k8s.io/cluster-api/util/conditions"
    36  )
    37  
    38  func requeueProfileUpdating() reconcile.Result {
    39  	return reconcile.Result{RequeueAfter: 10 * time.Second}
    40  }
    41  
    42  func requeueRoleUpdating() reconcile.Result {
    43  	return reconcile.Result{RequeueAfter: 10 * time.Second}
    44  }
    45  
    46  // Reconcile is the entrypoint for FargateProfile reconciliation.
    47  func (s *FargateService) Reconcile() (reconcile.Result, error) {
    48  	s.scope.V(2).Info("Reconciling EKS fargate profile")
    49  
    50  	requeue, err := s.reconcileFargateIAMRole()
    51  	if err != nil {
    52  		conditions.MarkFalse(
    53  			s.scope.FargateProfile,
    54  			expinfrav1.IAMFargateRolesReadyCondition,
    55  			expinfrav1.IAMFargateRolesReconciliationFailedReason,
    56  			clusterv1.ConditionSeverityError,
    57  			err.Error(),
    58  		)
    59  		return reconcile.Result{}, err
    60  	}
    61  	// When the role is updated, we requeue to let e.g. trust relationship
    62  	// propagate
    63  	if requeue {
    64  		return requeueRoleUpdating(), nil
    65  	}
    66  
    67  	conditions.MarkTrue(s.scope.FargateProfile, expinfrav1.IAMFargateRolesReadyCondition)
    68  
    69  	requeue, err = s.reconcileFargateProfile()
    70  	if err != nil {
    71  		conditions.MarkFalse(
    72  			s.scope.FargateProfile,
    73  			clusterv1.ReadyCondition,
    74  			expinfrav1.EKSFargateReconciliationFailedReason,
    75  			clusterv1.ConditionSeverityError,
    76  			err.Error(),
    77  		)
    78  		return reconcile.Result{}, err
    79  	}
    80  	if requeue {
    81  		return requeueProfileUpdating(), nil
    82  	}
    83  
    84  	return reconcile.Result{}, nil
    85  }
    86  
    87  func (s *FargateService) reconcileFargateProfile() (requeue bool, err error) {
    88  	profileName := s.scope.FargateProfile.Spec.ProfileName
    89  
    90  	profile, err := s.describeFargateProfile()
    91  	if err != nil {
    92  		return false, errors.Wrap(err, "failed to describe profile")
    93  	}
    94  
    95  	if eksClusterName := s.scope.KubernetesClusterName(); profile == nil {
    96  		profile, err = s.createFargateProfile()
    97  		if err != nil {
    98  			return false, errors.Wrap(err, "failed to create profile")
    99  		}
   100  		// Force status to creating
   101  		profile.Status = aws.String(eks.FargateProfileStatusCreating)
   102  		s.scope.Info("Created EKS fargate profile", "cluster-name", eksClusterName, "profile-name", profileName)
   103  	} else {
   104  		tagKey := infrav1.ClusterAWSCloudProviderTagKey(s.scope.ClusterName())
   105  		ownedTag := profile.Tags[tagKey]
   106  		if ownedTag == nil {
   107  			return false, errors.New("owned tag not found for this cluster")
   108  		}
   109  		s.scope.V(2).Info("Found owned EKS fargate profile", "cluster-name", eksClusterName, "profile-name", profileName)
   110  	}
   111  
   112  	if err := s.reconcileTags(profile); err != nil {
   113  		return false, errors.Wrapf(err, "failed to reconcile profile tags")
   114  	}
   115  
   116  	return s.handleStatus(profile), nil
   117  }
   118  
   119  func (s *FargateService) handleStatus(profile *eks.FargateProfile) (requeue bool) {
   120  	s.V(2).Info("fargate profile", "status", *profile.Status)
   121  	switch *profile.Status {
   122  	case eks.FargateProfileStatusCreating:
   123  		s.scope.FargateProfile.Status.Ready = false
   124  		if conditions.IsTrue(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition) {
   125  			conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition, expinfrav1.EKSFargateCreatingReason, clusterv1.ConditionSeverityInfo, "")
   126  		}
   127  		if !conditions.IsTrue(s.scope.FargateProfile, expinfrav1.EKSFargateCreatingCondition) {
   128  			record.Eventf(s.scope.FargateProfile, "InitiatedCreateEKSFargateProfile", "Started creating EKS fargate profile %s", s.scope.FargateProfile.Spec.ProfileName)
   129  			conditions.MarkTrue(s.scope.FargateProfile, expinfrav1.EKSFargateCreatingCondition)
   130  		}
   131  		conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateProfileReadyCondition, expinfrav1.EKSFargateCreatingReason, clusterv1.ConditionSeverityInfo, "")
   132  	case eks.FargateProfileStatusCreateFailed, eks.FargateProfileStatusDeleteFailed:
   133  		s.scope.FargateProfile.Status.Ready = false
   134  		s.scope.FargateProfile.Status.FailureMessage = aws.String(fmt.Sprintf("unexpected profile status: %s", *profile.Status))
   135  		reason := capierrors.MachineStatusError(expinfrav1.EKSFargateFailedReason)
   136  		s.scope.FargateProfile.Status.FailureReason = &reason
   137  		conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateProfileReadyCondition, expinfrav1.EKSFargateFailedReason, clusterv1.ConditionSeverityError, "")
   138  	case eks.FargateProfileStatusActive:
   139  		s.scope.FargateProfile.Status.Ready = true
   140  		if conditions.IsTrue(s.scope.FargateProfile, expinfrav1.EKSFargateCreatingCondition) {
   141  			record.Eventf(s.scope.FargateProfile, "SuccessfulCreateEKSFargateProfile", "Created new EKS fargate profile %s", s.scope.FargateProfile.Spec.ProfileName)
   142  			conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateCreatingCondition, expinfrav1.EKSFargateCreatedReason, clusterv1.ConditionSeverityInfo, "")
   143  		}
   144  		conditions.MarkTrue(s.scope.FargateProfile, expinfrav1.EKSFargateProfileReadyCondition)
   145  	case eks.FargateProfileStatusDeleting:
   146  		s.scope.FargateProfile.Status.Ready = false
   147  		if !conditions.IsTrue(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition) {
   148  			record.Eventf(s.scope.FargateProfile, "InitiatedDeleteEKSFargateProfile", "Started deleting EKS fargate profile %s", s.scope.FargateProfile.Spec.ProfileName)
   149  			conditions.MarkTrue(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition)
   150  		}
   151  		conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateProfileReadyCondition, expinfrav1.EKSFargateDeletingReason, clusterv1.ConditionSeverityInfo, "")
   152  	}
   153  	switch *profile.Status {
   154  	case eks.FargateProfileStatusCreating, eks.FargateProfileStatusDeleting:
   155  		return true
   156  	default:
   157  		return false
   158  	}
   159  }
   160  
   161  // ReconcileDelete is the entrypoint for FargateProfile reconciliation.
   162  func (s *FargateService) ReconcileDelete() (reconcile.Result, error) {
   163  	s.scope.V(2).Info("Reconciling EKS fargate profile deletion")
   164  
   165  	requeue, err := s.deleteFargateProfile()
   166  	if err != nil {
   167  		conditions.MarkFalse(
   168  			s.scope.FargateProfile,
   169  			clusterv1.ReadyCondition,
   170  			expinfrav1.EKSFargateReconciliationFailedReason,
   171  			clusterv1.ConditionSeverityError,
   172  			err.Error(),
   173  		)
   174  		return reconcile.Result{}, err
   175  	}
   176  
   177  	if requeue {
   178  		return requeueProfileUpdating(), nil
   179  	}
   180  
   181  	err = s.deleteFargateIAMRole()
   182  	if err != nil {
   183  		conditions.MarkFalse(
   184  			s.scope.FargateProfile,
   185  			expinfrav1.IAMFargateRolesReadyCondition,
   186  			expinfrav1.IAMFargateRolesReconciliationFailedReason,
   187  			clusterv1.ConditionSeverityError,
   188  			err.Error(),
   189  		)
   190  	}
   191  	return reconcile.Result{}, err
   192  }
   193  
   194  func (s *FargateService) describeFargateProfile() (*eks.FargateProfile, error) {
   195  	eksClusterName := s.scope.KubernetesClusterName()
   196  	profileName := s.scope.FargateProfile.Spec.ProfileName
   197  	input := &eks.DescribeFargateProfileInput{
   198  		ClusterName:        aws.String(eksClusterName),
   199  		FargateProfileName: aws.String(profileName),
   200  	}
   201  
   202  	out, err := s.EKSClient.DescribeFargateProfile(input)
   203  	if err != nil {
   204  		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == eks.ErrCodeResourceNotFoundException {
   205  			return nil, nil
   206  		}
   207  		return nil, errors.Wrap(err, "failed to describe fargate profile")
   208  	}
   209  
   210  	return out.FargateProfile, nil
   211  }
   212  
   213  func (s *FargateService) createFargateProfile() (*eks.FargateProfile, error) {
   214  	eksClusterName := s.scope.KubernetesClusterName()
   215  	profileName := s.scope.FargateProfile.Spec.ProfileName
   216  
   217  	additionalTags := s.scope.AdditionalTags()
   218  
   219  	roleArn, err := s.roleArn()
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	tags := ngTags(s.scope.ClusterName(), additionalTags)
   225  
   226  	subnets := s.scope.FargateProfile.Spec.SubnetIDs
   227  	if len(subnets) == 0 {
   228  		subnets = []string{}
   229  		for _, s := range s.scope.ControlPlane.Spec.NetworkSpec.Subnets.FilterPrivate() {
   230  			subnets = append(subnets, s.ID)
   231  		}
   232  	}
   233  
   234  	selectors := []*eks.FargateProfileSelector{}
   235  	for _, s := range s.scope.FargateProfile.Spec.Selectors {
   236  		selectors = append(selectors, &eks.FargateProfileSelector{
   237  			Labels:    aws.StringMap(s.Labels),
   238  			Namespace: aws.String(s.Namespace),
   239  		})
   240  	}
   241  
   242  	input := &eks.CreateFargateProfileInput{
   243  		ClusterName:         aws.String(eksClusterName),
   244  		FargateProfileName:  aws.String(profileName),
   245  		PodExecutionRoleArn: roleArn,
   246  		Subnets:             aws.StringSlice(subnets),
   247  		Tags:                aws.StringMap(tags),
   248  		Selectors:           selectors,
   249  	}
   250  	if err := input.Validate(); err != nil {
   251  		return nil, errors.Wrap(err, "created invalid CreateFargateProfileInput")
   252  	}
   253  
   254  	out, err := s.EKSClient.CreateFargateProfile(input)
   255  	if err != nil {
   256  		return nil, errors.Wrap(err, "failed to create fargate profile")
   257  	}
   258  
   259  	return out.FargateProfile, nil
   260  }
   261  
   262  func (s *FargateService) deleteFargateProfile() (requeue bool, err error) {
   263  	eksClusterName := s.scope.KubernetesClusterName()
   264  	profileName := s.scope.FargateProfile.Spec.ProfileName
   265  
   266  	profile, err := s.describeFargateProfile()
   267  	if err != nil {
   268  		return false, errors.Wrap(err, "failed to describe profile")
   269  	}
   270  	if profile == nil {
   271  		if conditions.IsTrue(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition) {
   272  			record.Eventf(s.scope.FargateProfile, "SuccessfulDeleteEKSFargateProfile", "Deleted EKS fargate profile %s", s.scope.FargateProfile.Spec.ProfileName)
   273  			conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateDeletingCondition, expinfrav1.EKSFargateDeletedReason, clusterv1.ConditionSeverityInfo, "")
   274  		}
   275  		conditions.MarkFalse(s.scope.FargateProfile, expinfrav1.EKSFargateProfileReadyCondition, expinfrav1.EKSFargateDeletedReason, clusterv1.ConditionSeverityInfo, "")
   276  		return false, nil
   277  	}
   278  
   279  	switch aws.StringValue(profile.Status) {
   280  	case eks.FargateProfileStatusCreating, eks.FargateProfileStatusDeleting, eks.FargateProfileStatusDeleteFailed:
   281  		return s.handleStatus(profile), nil
   282  	case eks.FargateProfileStatusActive, eks.FargateProfileStatusCreateFailed:
   283  	}
   284  
   285  	input := &eks.DeleteFargateProfileInput{
   286  		ClusterName:        aws.String(eksClusterName),
   287  		FargateProfileName: aws.String(profileName),
   288  	}
   289  	if err := input.Validate(); err != nil {
   290  		return false, errors.Wrap(err, "created invalid DeleteFargateProfileInput")
   291  	}
   292  
   293  	out, err := s.EKSClient.DeleteFargateProfile(input)
   294  	if err != nil {
   295  		return false, errors.Wrap(err, "failed to delete fargate profile")
   296  	}
   297  
   298  	profile = out.FargateProfile
   299  	profile.Status = aws.String(eks.FargateProfileStatusDeleting)
   300  
   301  	return s.handleStatus(profile), nil
   302  }
   303  
   304  func (s *FargateService) roleArn() (*string, error) {
   305  	var role *iam.Role
   306  	if s.scope.RoleName() != "" {
   307  		var err error
   308  		role, err = s.GetIAMRole(s.scope.RoleName())
   309  		if err != nil {
   310  			return nil, errors.Wrapf(err, "error getting fargate profile IAM role: %s", s.scope.RoleName())
   311  		}
   312  	}
   313  	return role.Arn, nil
   314  }