sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/eks/cluster.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 "context" 21 "fmt" 22 "net" 23 "time" 24 25 "github.com/aws/aws-sdk-go/aws" 26 "github.com/aws/aws-sdk-go/aws/awserr" 27 "github.com/aws/aws-sdk-go/aws/request" 28 "github.com/aws/aws-sdk-go/service/eks" 29 "github.com/pkg/errors" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "k8s.io/apimachinery/pkg/util/version" 32 33 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 34 ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1" 35 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud" 36 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 37 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait" 38 "sigs.k8s.io/cluster-api-provider-aws/pkg/internal/cidr" 39 "sigs.k8s.io/cluster-api-provider-aws/pkg/internal/cmp" 40 "sigs.k8s.io/cluster-api-provider-aws/pkg/internal/tristate" 41 "sigs.k8s.io/cluster-api-provider-aws/pkg/record" 42 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 43 "sigs.k8s.io/cluster-api/util/conditions" 44 ) 45 46 func (s *Service) reconcileCluster(ctx context.Context) error { 47 s.scope.V(2).Info("Reconciling EKS cluster") 48 49 eksClusterName := s.scope.KubernetesClusterName() 50 51 cluster, err := s.describeEKSCluster(eksClusterName) 52 if err != nil { 53 return errors.Wrap(err, "failed to describe eks clusters") 54 } 55 56 if cluster == nil { 57 cluster, err = s.createCluster(eksClusterName) 58 if err != nil { 59 return errors.Wrap(err, "failed to create cluster") 60 } 61 } else { 62 tagKey := infrav1.ClusterAWSCloudProviderTagKey(s.scope.KubernetesClusterName()) 63 ownedTag := cluster.Tags[tagKey] 64 // Prior to https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/3573, 65 // Clusters were tagged using s.scope.Name() 66 // To support upgrading older clusters, check for both tags 67 oldTagKey := infrav1.ClusterAWSCloudProviderTagKey(s.scope.Name()) 68 oldOwnedTag := cluster.Tags[oldTagKey] 69 70 if ownedTag == nil && oldOwnedTag == nil { 71 return fmt.Errorf("EKS cluster resource %q must have a tag with key %q or %q", eksClusterName, oldTagKey, tagKey) 72 } 73 74 s.scope.V(2).Info("Found owned EKS cluster in AWS", "cluster-name", eksClusterName) 75 } 76 77 if err := s.setStatus(cluster); err != nil { 78 return errors.Wrap(err, "failed to set status") 79 } 80 81 // Wait for our cluster to be ready if necessary 82 switch *cluster.Status { 83 case eks.ClusterStatusUpdating, eks.ClusterStatusCreating: 84 cluster, err = s.waitForClusterActive() 85 default: 86 break 87 } 88 if err != nil { 89 return errors.Wrap(err, "failed to wait for cluster to be active") 90 } 91 92 if !s.scope.ControlPlane.Status.Ready { 93 return nil 94 } 95 96 s.scope.V(2).Info("EKS Control Plane active", "endpoint", *cluster.Endpoint) 97 98 s.scope.ControlPlane.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{ 99 Host: *cluster.Endpoint, 100 Port: 443, 101 } 102 103 if err := s.reconcileSecurityGroups(cluster); err != nil { 104 return errors.Wrap(err, "failed reconciling security groups") 105 } 106 107 if err := s.reconcileKubeconfig(ctx, cluster); err != nil { 108 return errors.Wrap(err, "failed reconciling kubeconfig") 109 } 110 111 if err := s.reconcileAdditionalKubeconfigs(ctx, cluster); err != nil { 112 return errors.Wrap(err, "failed reconciling additional kubeconfigs") 113 } 114 115 if err := s.reconcileClusterVersion(cluster); err != nil { 116 return errors.Wrap(err, "failed reconciling cluster version") 117 } 118 119 if err := s.reconcileClusterConfig(cluster); err != nil { 120 return errors.Wrap(err, "failed reconciling cluster config") 121 } 122 123 if err := s.reconcileEKSEncryptionConfig(cluster.EncryptionConfig); err != nil { 124 return errors.Wrap(err, "failed reconciling eks encryption config") 125 } 126 127 if err := s.reconcileTags(cluster); err != nil { 128 return errors.Wrap(err, "failed updating cluster tags") 129 } 130 131 if err := s.reconcileOIDCProvider(cluster); err != nil { 132 return errors.Wrap(err, "failed reconciling OIDC provider for cluster") 133 } 134 135 return nil 136 } 137 138 func (s *Service) setStatus(cluster *eks.Cluster) error { 139 switch *cluster.Status { 140 case eks.ClusterStatusDeleting: 141 s.scope.ControlPlane.Status.Ready = false 142 case eks.ClusterStatusFailed: 143 s.scope.ControlPlane.Status.Ready = false 144 // TODO FailureReason 145 failureMsg := fmt.Sprintf("EKS cluster in unexpected %s state", *cluster.Status) 146 s.scope.ControlPlane.Status.FailureMessage = &failureMsg 147 case eks.ClusterStatusActive: 148 s.scope.ControlPlane.Status.Ready = true 149 s.scope.ControlPlane.Status.FailureMessage = nil 150 if conditions.IsTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneCreatingCondition) { 151 record.Eventf(s.scope.ControlPlane, "SuccessfulCreateEKSControlPlane", "Created new EKS control plane %s", s.scope.KubernetesClusterName()) 152 conditions.MarkFalse(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneCreatingCondition, "created", clusterv1.ConditionSeverityInfo, "") 153 } 154 if conditions.IsTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition) { 155 conditions.MarkFalse(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition, "updated", clusterv1.ConditionSeverityInfo, "") 156 record.Eventf(s.scope.ControlPlane, "SuccessfulUpdateEKSControlPlane", "Updated EKS control plane %s", s.scope.KubernetesClusterName()) 157 } 158 // TODO FailureReason 159 case eks.ClusterStatusCreating: 160 s.scope.ControlPlane.Status.Ready = false 161 case eks.ClusterStatusUpdating: 162 s.scope.ControlPlane.Status.Ready = true 163 default: 164 return errors.Errorf("unexpected EKS cluster status %s", *cluster.Status) 165 } 166 if err := s.scope.PatchObject(); err != nil { 167 return errors.Wrap(err, "failed to update control plane") 168 } 169 return nil 170 } 171 172 // deleteCluster deletes an EKS cluster. 173 func (s *Service) deleteCluster() error { 174 eksClusterName := s.scope.KubernetesClusterName() 175 176 if eksClusterName == "" { 177 s.scope.V(2).Info("no EKS cluster name, skipping EKS cluster deletion") 178 return nil 179 } 180 181 cluster, err := s.describeEKSCluster(eksClusterName) 182 if err != nil { 183 if awserrors.IsNotFound(err) { 184 s.scope.V(4).Info("eks cluster does not exist") 185 return nil 186 } 187 return errors.Wrap(err, "unable to describe eks cluster") 188 } 189 if cluster == nil { 190 return nil 191 } 192 193 err = s.deleteClusterAndWait(cluster) 194 if err != nil { 195 record.Warnf(s.scope.ControlPlane, "FailedDeleteEKSCluster", "Failed to delete EKS cluster %s: %v", s.scope.KubernetesClusterName(), err) 196 return errors.Wrap(err, "unable to delete EKS cluster") 197 } 198 record.Eventf(s.scope.ControlPlane, "SuccessfulDeleteEKSCluster", "Deleted EKS Cluster %s", s.scope.KubernetesClusterName()) 199 200 return nil 201 } 202 203 func (s *Service) deleteClusterAndWait(cluster *eks.Cluster) error { 204 s.scope.Info("Deleting EKS cluster", "cluster-name", s.scope.KubernetesClusterName()) 205 206 input := &eks.DeleteClusterInput{ 207 Name: cluster.Name, 208 } 209 _, err := s.EKSClient.DeleteCluster(input) 210 if err != nil { 211 return errors.Wrapf(err, "failed to request delete of eks cluster %s", *cluster.Name) 212 } 213 214 waitInput := &eks.DescribeClusterInput{ 215 Name: cluster.Name, 216 } 217 218 err = s.EKSClient.WaitUntilClusterDeleted(waitInput) 219 if err != nil { 220 return errors.Wrapf(err, "failed waiting for eks cluster %s to delete", *cluster.Name) 221 } 222 223 return nil 224 } 225 226 func makeEksEncryptionConfigs(encryptionConfig *ekscontrolplanev1.EncryptionConfig) []*eks.EncryptionConfig { 227 cfg := []*eks.EncryptionConfig{} 228 229 if encryptionConfig == nil { 230 return cfg 231 } 232 // TODO: change EncryptionConfig so that provider and resources are required if encruptionConfig is specified 233 if encryptionConfig.Provider == nil || len(*encryptionConfig.Provider) == 0 { 234 return cfg 235 } 236 if len(encryptionConfig.Resources) == 0 { 237 return cfg 238 } 239 240 return append(cfg, &eks.EncryptionConfig{ 241 Provider: &eks.Provider{ 242 KeyArn: encryptionConfig.Provider, 243 }, 244 Resources: encryptionConfig.Resources, 245 }) 246 } 247 248 func makeKubernetesNetworkConfig(serviceCidrs *clusterv1.NetworkRanges) (*eks.KubernetesNetworkConfigRequest, error) { 249 if serviceCidrs == nil || len(serviceCidrs.CIDRBlocks) == 0 { 250 return nil, nil 251 } 252 253 ipv4cidrs, err := cidr.GetIPv4Cidrs(serviceCidrs.CIDRBlocks) 254 if err != nil { 255 return nil, fmt.Errorf("filtering service cidr blocks to IPv4: %w", err) 256 } 257 258 if len(ipv4cidrs) == 0 { 259 return nil, nil 260 } 261 262 return &eks.KubernetesNetworkConfigRequest{ 263 ServiceIpv4Cidr: &ipv4cidrs[0], 264 }, nil 265 } 266 267 func makeVpcConfig(subnets infrav1.Subnets, endpointAccess ekscontrolplanev1.EndpointAccess, securityGroups map[infrav1.SecurityGroupRole]infrav1.SecurityGroup) (*eks.VpcConfigRequest, error) { 268 // TODO: Do we need to just add the private subnets? 269 if len(subnets) < 2 { 270 return nil, awserrors.NewFailedDependency("at least 2 subnets is required") 271 } 272 273 if zones := subnets.GetUniqueZones(); len(zones) < 2 { 274 return nil, awserrors.NewFailedDependency("subnets in at least 2 different az's are required") 275 } 276 277 subnetIds := make([]*string, 0) 278 for i := range subnets { 279 subnet := subnets[i] 280 subnetIds = append(subnetIds, &subnet.ID) 281 } 282 283 cidrs := make([]*string, 0) 284 for _, cidr := range endpointAccess.PublicCIDRs { 285 _, ipNet, err := net.ParseCIDR(*cidr) 286 if err != nil { 287 return nil, errors.Wrap(err, "couldn't parse PublicCIDRs") 288 } 289 parsedCIDR := ipNet.String() 290 cidrs = append(cidrs, &parsedCIDR) 291 } 292 293 vpcConfig := &eks.VpcConfigRequest{ 294 EndpointPublicAccess: endpointAccess.Public, 295 EndpointPrivateAccess: endpointAccess.Private, 296 SubnetIds: subnetIds, 297 } 298 299 if len(cidrs) > 0 { 300 vpcConfig.PublicAccessCidrs = cidrs 301 } 302 sg, ok := securityGroups[infrav1.SecurityGroupEKSNodeAdditional] 303 if ok { 304 vpcConfig.SecurityGroupIds = append(vpcConfig.SecurityGroupIds, &sg.ID) 305 } 306 return vpcConfig, nil 307 } 308 309 func makeEksLogging(loggingSpec *ekscontrolplanev1.ControlPlaneLoggingSpec) *eks.Logging { 310 if loggingSpec == nil { 311 return nil 312 } 313 on := true 314 off := false 315 var enabledTypes []string 316 var disabledTypes []string 317 318 appendToTypes := func(logType string, field bool) { 319 if field { 320 enabledTypes = append(enabledTypes, logType) 321 } else { 322 disabledTypes = append(disabledTypes, logType) 323 } 324 } 325 326 appendToTypes(eks.LogTypeApi, loggingSpec.APIServer) 327 appendToTypes(eks.LogTypeAudit, loggingSpec.Audit) 328 appendToTypes(eks.LogTypeAuthenticator, loggingSpec.Authenticator) 329 appendToTypes(eks.LogTypeControllerManager, loggingSpec.ControllerManager) 330 appendToTypes(eks.LogTypeScheduler, loggingSpec.Scheduler) 331 332 var clusterLogging []*eks.LogSetup 333 if len(enabledTypes) > 0 { 334 enabled := eks.LogSetup{ 335 Enabled: &on, 336 Types: aws.StringSlice(enabledTypes), 337 } 338 clusterLogging = append(clusterLogging, &enabled) 339 } 340 if len(disabledTypes) > 0 { 341 disabled := eks.LogSetup{ 342 Enabled: &off, 343 Types: aws.StringSlice(disabledTypes), 344 } 345 clusterLogging = append(clusterLogging, &disabled) 346 } 347 if len(clusterLogging) > 0 { 348 return &eks.Logging{ 349 ClusterLogging: clusterLogging, 350 } 351 } 352 return nil 353 } 354 355 func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) { 356 logging := makeEksLogging(s.scope.ControlPlane.Spec.Logging) 357 encryptionConfigs := makeEksEncryptionConfigs(s.scope.ControlPlane.Spec.EncryptionConfig) 358 vpcConfig, err := makeVpcConfig(s.scope.Subnets(), s.scope.ControlPlane.Spec.EndpointAccess, s.scope.SecurityGroups()) 359 if err != nil { 360 return nil, errors.Wrap(err, "couldn't create vpc config for cluster") 361 } 362 netConfig, err := makeKubernetesNetworkConfig(s.scope.ServiceCidrs()) 363 if err != nil { 364 return nil, errors.Wrap(err, "couldn't create Kubernetes network config for cluster") 365 } 366 367 // Make sure to use the MachineScope here to get the merger of AWSCluster and AWSMachine tags 368 additionalTags := s.scope.AdditionalTags() 369 370 // Set the cloud provider tag 371 additionalTags[infrav1.ClusterAWSCloudProviderTagKey(s.scope.KubernetesClusterName())] = string(infrav1.ResourceLifecycleOwned) 372 tags := make(map[string]*string) 373 for k, v := range additionalTags { 374 tagValue := v 375 tags[k] = &tagValue 376 } 377 378 role, err := s.GetIAMRole(*s.scope.ControlPlane.Spec.RoleName) 379 if err != nil { 380 return nil, errors.Wrapf(err, "error getting control plane iam role: %s", *s.scope.ControlPlane.Spec.RoleName) 381 } 382 383 v := versionToEKS(parseEKSVersion(*s.scope.ControlPlane.Spec.Version)) 384 input := &eks.CreateClusterInput{ 385 Name: aws.String(eksClusterName), 386 Version: aws.String(v), 387 Logging: logging, 388 EncryptionConfig: encryptionConfigs, 389 ResourcesVpcConfig: vpcConfig, 390 RoleArn: role.Arn, 391 Tags: tags, 392 KubernetesNetworkConfig: netConfig, 393 } 394 395 var out *eks.CreateClusterOutput 396 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 397 if out, err = s.EKSClient.CreateCluster(input); err != nil { 398 if aerr, ok := err.(awserr.Error); ok { 399 return false, aerr 400 } 401 return false, err 402 } 403 conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneCreatingCondition) 404 record.Eventf(s.scope.ControlPlane, "InitiatedCreateEKSControlPlane", "Initiated creation of a new EKS control plane %s", s.scope.KubernetesClusterName()) 405 return true, nil 406 }, awserrors.ResourceNotFound); err != nil { // TODO: change the error that can be retried 407 record.Warnf(s.scope.ControlPlane, "FailedCreateEKSControlPlane", "Failed to initiate creation of a new EKS control plane: %v", err) 408 return nil, errors.Wrapf(err, "failed to create EKS cluster") 409 } 410 411 s.scope.Info("Created EKS cluster in AWS", "cluster-name", eksClusterName) 412 return out.Cluster, nil 413 } 414 415 func (s *Service) waitForClusterActive() (*eks.Cluster, error) { 416 eksClusterName := s.scope.KubernetesClusterName() 417 req := eks.DescribeClusterInput{ 418 Name: aws.String(eksClusterName), 419 } 420 if err := s.EKSClient.WaitUntilClusterActive(&req); err != nil { 421 return nil, errors.Wrapf(err, "failed to wait for eks control plane %q", *req.Name) 422 } 423 424 s.scope.Info("EKS control plane is now active", "cluster-name", eksClusterName) 425 426 cluster, err := s.describeEKSCluster(eksClusterName) 427 if err != nil { 428 return nil, errors.Wrap(err, "failed to describe eks clusters") 429 } 430 431 if err := s.setStatus(cluster); err != nil { 432 return nil, errors.Wrap(err, "failed to set status") 433 } 434 435 return cluster, nil 436 } 437 438 func (s *Service) reconcileClusterConfig(cluster *eks.Cluster) error { 439 var needsUpdate bool 440 input := eks.UpdateClusterConfigInput{Name: aws.String(s.scope.KubernetesClusterName())} 441 442 if updateLogging := s.reconcileLogging(cluster.Logging); updateLogging != nil { 443 needsUpdate = true 444 input.Logging = updateLogging 445 } 446 447 updateVpcConfig, err := s.reconcileVpcConfig(cluster.ResourcesVpcConfig) 448 if err != nil { 449 return errors.Wrap(err, "couldn't create vpc config for cluster") 450 } 451 if updateVpcConfig != nil { 452 needsUpdate = true 453 input.ResourcesVpcConfig = updateVpcConfig 454 } 455 456 if needsUpdate { 457 if err := input.Validate(); err != nil { 458 return errors.Wrap(err, "created invalid UpdateClusterConfigInput") 459 } 460 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 461 if _, err := s.EKSClient.UpdateClusterConfig(&input); err != nil { 462 if aerr, ok := err.(awserr.Error); ok { 463 return false, aerr 464 } 465 return false, err 466 } 467 conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition) 468 record.Eventf(s.scope.ControlPlane, "InitiatedUpdateEKSControlPlane", "Initiated update of a new EKS control plane %s", s.scope.KubernetesClusterName()) 469 return true, nil 470 }); err != nil { 471 record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "Failed to update the EKS control plane: %v", err) 472 return errors.Wrapf(err, "failed to update EKS cluster") 473 } 474 } 475 return nil 476 } 477 478 func (s *Service) reconcileLogging(logging *eks.Logging) *eks.Logging { 479 for _, logSetup := range logging.ClusterLogging { 480 for _, l := range logSetup.Types { 481 enabled := s.scope.ControlPlane.Spec.Logging.IsLogEnabled(*l) 482 if enabled != *logSetup.Enabled { 483 return makeEksLogging(s.scope.ControlPlane.Spec.Logging) 484 } 485 } 486 } 487 return nil 488 } 489 490 func publicAccessCIDRsEqual(as []*string, bs []*string) bool { 491 all := "0.0.0.0/0" 492 if len(as) == 0 { 493 as = []*string{&all} 494 } 495 if len(bs) == 0 { 496 bs = []*string{&all} 497 } 498 return sets.NewString(aws.StringValueSlice(as)...).Equal( 499 sets.NewString(aws.StringValueSlice(bs)...), 500 ) 501 } 502 503 func (s *Service) reconcileVpcConfig(vpcConfig *eks.VpcConfigResponse) (*eks.VpcConfigRequest, error) { 504 endpointAccess := s.scope.ControlPlane.Spec.EndpointAccess 505 updatedVpcConfig, err := makeVpcConfig(s.scope.Subnets(), endpointAccess, s.scope.SecurityGroups()) 506 if err != nil { 507 return nil, err 508 } 509 needsUpdate := !tristate.EqualWithDefault(false, vpcConfig.EndpointPrivateAccess, updatedVpcConfig.EndpointPrivateAccess) || 510 !tristate.EqualWithDefault(true, vpcConfig.EndpointPublicAccess, updatedVpcConfig.EndpointPublicAccess) || 511 !publicAccessCIDRsEqual(vpcConfig.PublicAccessCidrs, updatedVpcConfig.PublicAccessCidrs) 512 if needsUpdate { 513 return &eks.VpcConfigRequest{ 514 EndpointPublicAccess: updatedVpcConfig.EndpointPublicAccess, 515 EndpointPrivateAccess: updatedVpcConfig.EndpointPrivateAccess, 516 PublicAccessCidrs: updatedVpcConfig.PublicAccessCidrs, 517 }, nil 518 } 519 return nil, nil 520 } 521 522 func (s *Service) reconcileEKSEncryptionConfig(currentClusterConfig []*eks.EncryptionConfig) error { 523 s.Info("reconciling encryption configuration") 524 if currentClusterConfig == nil { 525 currentClusterConfig = []*eks.EncryptionConfig{} 526 } 527 528 encryptionConfigs := s.scope.ControlPlane.Spec.EncryptionConfig 529 updatedEncryptionConfigs := makeEksEncryptionConfigs(encryptionConfigs) 530 531 if compareEncryptionConfig(currentClusterConfig, updatedEncryptionConfigs) { 532 s.V(2).Info("encryption configuration unchanged, no action") 533 return nil 534 } 535 536 if len(currentClusterConfig) == 0 && len(updatedEncryptionConfigs) > 0 { 537 s.V(2).Info("enabling encryption for eks cluster", "cluster", s.scope.KubernetesClusterName()) 538 if err := s.updateEncryptionConfig(updatedEncryptionConfigs); err != nil { 539 record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "failed to update the EKS control plane encryption configuration: %v", err) 540 return errors.Wrapf(err, "failed to update EKS cluster") 541 } 542 543 return nil 544 } 545 546 record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "failed to update the EKS control plane: disabling EKS encryption is not allowed after it has been enabled") 547 return errors.Errorf("failed to update the EKS control plane: disabling EKS encryption is not allowed after it has been enabled") 548 } 549 550 func parseEKSVersion(raw string) *version.Version { 551 v := version.MustParseGeneric(raw) 552 return version.MustParseGeneric(fmt.Sprintf("%d.%d", v.Major(), v.Minor())) 553 } 554 555 func versionToEKS(v *version.Version) string { 556 return fmt.Sprintf("%d.%d", v.Major(), v.Minor()) 557 } 558 559 func (s *Service) reconcileClusterVersion(cluster *eks.Cluster) error { 560 specVersion := parseEKSVersion(*s.scope.ControlPlane.Spec.Version) 561 clusterVersion := version.MustParseGeneric(*cluster.Version) 562 563 if clusterVersion.LessThan(specVersion) { 564 // NOTE: you can only upgrade increments of minor versions. If you want to upgrade 1.14 to 1.16 we 565 // need to go 1.14-> 1.15 and then 1.15 -> 1.16. 566 nextVersionString := versionToEKS(clusterVersion.WithMinor(clusterVersion.Minor() + 1)) 567 568 input := &eks.UpdateClusterVersionInput{ 569 Name: aws.String(s.scope.KubernetesClusterName()), 570 Version: &nextVersionString, 571 } 572 573 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 574 if _, err := s.EKSClient.UpdateClusterVersion(input); err != nil { 575 if aerr, ok := err.(awserr.Error); ok { 576 return false, aerr 577 } 578 return false, err 579 } 580 581 // Wait until status transitions to UPDATING because there's a short 582 // window after UpdateClusterVersion returns where the cluster 583 // status is ACTIVE and the update would be tried again 584 if err := s.EKSClient.WaitUntilClusterUpdating( 585 &eks.DescribeClusterInput{Name: aws.String(s.scope.KubernetesClusterName())}, 586 request.WithWaiterLogger(&awslog{s}), 587 ); err != nil { 588 return false, err 589 } 590 591 conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition) 592 record.Eventf(s.scope.ControlPlane, "InitiatedUpdateEKSControlPlane", "Initiated update of EKS control plane %s to version %s", s.scope.KubernetesClusterName(), nextVersionString) 593 594 return true, nil 595 }); err != nil { 596 record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "failed to update the EKS control plane: %v", err) 597 return errors.Wrapf(err, "failed to update EKS cluster") 598 } 599 } 600 return nil 601 } 602 603 func (s *Service) describeEKSCluster(eksClusterName string) (*eks.Cluster, error) { 604 input := &eks.DescribeClusterInput{ 605 Name: aws.String(eksClusterName), 606 } 607 608 out, err := s.EKSClient.DescribeCluster(input) 609 if err != nil { 610 if aerr, ok := err.(awserr.Error); ok { 611 switch aerr.Code() { 612 case eks.ErrCodeResourceNotFoundException: 613 return nil, nil 614 default: 615 return nil, errors.Wrap(err, "failed to describe cluster") 616 } 617 } else { 618 return nil, errors.Wrap(err, "failed to describe cluster") 619 } 620 } 621 622 return out.Cluster, nil 623 } 624 625 func (s *Service) updateEncryptionConfig(updatedEncryptionConfigs []*eks.EncryptionConfig) error { 626 input := &eks.AssociateEncryptionConfigInput{ 627 ClusterName: aws.String(s.scope.KubernetesClusterName()), 628 EncryptionConfig: updatedEncryptionConfigs, 629 } 630 if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { 631 if _, err := s.EKSClient.AssociateEncryptionConfig(input); err != nil { 632 if aerr, ok := err.(awserr.Error); ok { 633 return false, aerr 634 } 635 return false, err 636 } 637 638 // Wait until status transitions to UPDATING because there's a short 639 // window after UpdateClusterVersion returns where the cluster 640 // status is ACTIVE and the update would be tried again 641 if err := s.EKSClient.WaitUntilClusterUpdating( 642 &eks.DescribeClusterInput{Name: aws.String(s.scope.KubernetesClusterName())}, 643 request.WithWaiterLogger(&awslog{s}), 644 ); err != nil { 645 return false, err 646 } 647 648 conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition) 649 record.Eventf(s.scope.ControlPlane, "InitiatedUpdateEncryptionConfig", "Initiated update of encryption config in EKS control plane %s", s.scope.KubernetesClusterName()) 650 651 return true, nil 652 }); err != nil { 653 return err 654 } 655 return nil 656 } 657 658 // An internal type to satisfy aws' log interface. 659 type awslog struct { 660 cloud.Logger 661 } 662 663 func (a *awslog) Log(args ...interface{}) { 664 a.WithName("aws").Info(fmt.Sprintln(args...)) 665 } 666 667 // WaitUntilClusterUpdating is adapted from aws-sdk-go/service/eks/waiters.go. 668 func (c EKSClient) WaitUntilClusterUpdating(input *eks.DescribeClusterInput, opts ...request.WaiterOption) error { 669 ctx := aws.BackgroundContext() 670 statusPath := "cluster.status" 671 w := request.Waiter{ 672 Name: "WaitUntilClusterUpdating", 673 MaxAttempts: 40, 674 Delay: request.ConstantWaiterDelay(30 * time.Second), 675 Acceptors: []request.WaiterAcceptor{ 676 { 677 State: request.FailureWaiterState, 678 Matcher: request.PathWaiterMatch, Argument: statusPath, 679 Expected: eks.ClusterStatusDeleting, 680 }, 681 { 682 State: request.FailureWaiterState, 683 Matcher: request.PathWaiterMatch, Argument: statusPath, 684 Expected: eks.ClusterStatusFailed, 685 }, 686 { 687 State: request.SuccessWaiterState, 688 Matcher: request.PathWaiterMatch, Argument: statusPath, 689 Expected: eks.ClusterStatusUpdating, 690 }, 691 }, 692 NewRequest: func(opts []request.Option) (*request.Request, error) { 693 var inCpy *eks.DescribeClusterInput 694 if input != nil { 695 tmp := *input 696 inCpy = &tmp 697 } 698 req, _ := c.DescribeClusterRequest(inCpy) 699 req.SetContext(ctx) 700 req.ApplyOptions(opts...) 701 return req, nil 702 }, 703 } 704 w.ApplyOptions(opts...) 705 706 return w.WaitWithContext(ctx) 707 } 708 709 func compareEncryptionConfig(updatedEncryptionConfig, existingEncryptionConfig []*eks.EncryptionConfig) bool { 710 if len(updatedEncryptionConfig) != len(existingEncryptionConfig) { 711 return false 712 } 713 for index := range updatedEncryptionConfig { 714 encryptionConfig := updatedEncryptionConfig[index] 715 716 if getKeyArn(encryptionConfig) != getKeyArn(existingEncryptionConfig[index]) { 717 return false 718 } 719 if !cmp.Equals(encryptionConfig.Resources, existingEncryptionConfig[index].Resources) { 720 return false 721 } 722 } 723 return true 724 } 725 726 func getKeyArn(encryptionConfig *eks.EncryptionConfig) string { 727 if encryptionConfig.Provider != nil { 728 return aws.StringValue(encryptionConfig.Provider.KeyArn) 729 } 730 return "" 731 }