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 }