sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/eks/addons.go (about)

     1  /*
     2  Copyright 2021 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  
    23  	"github.com/aws/aws-sdk-go/aws"
    24  	"github.com/aws/aws-sdk-go/service/eks"
    25  
    26  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    27  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    28  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/converters"
    29  	eksaddons "sigs.k8s.io/cluster-api-provider-aws/pkg/eks/addons"
    30  	"sigs.k8s.io/cluster-api-provider-aws/pkg/record"
    31  )
    32  
    33  func (s *Service) reconcileAddons(ctx context.Context) error {
    34  	s.scope.Info("Reconciling EKS addons")
    35  
    36  	eksClusterName := s.scope.KubernetesClusterName()
    37  
    38  	// Get available addon names for the cluster
    39  	addonNames, err := s.listAddons(eksClusterName)
    40  	if err != nil {
    41  		s.Error(err, "failed listing addons")
    42  		return fmt.Errorf("listing eks addons: %w", err)
    43  	}
    44  
    45  	// Get installed addons for the cluster
    46  	s.scope.V(2).Info("getting installed eks addons", "cluster", eksClusterName)
    47  	installed, err := s.getClusterAddonsInstalled(eksClusterName, addonNames)
    48  	if err != nil {
    49  		return fmt.Errorf("getting installed eks addons: %w", err)
    50  	}
    51  
    52  	// Get the addons from the spec we want for the cluster
    53  	desiredAddons := s.translateAPIToAddon(s.scope.Addons())
    54  
    55  	// If there are no addons desired or installed then do nothing
    56  	if len(installed) == 0 && len(desiredAddons) == 0 {
    57  		s.scope.Info("no addons installed and no addons to install, no action needed")
    58  		return nil
    59  	}
    60  
    61  	//  Compute operations to move installed to desired
    62  	s.scope.V(2).Info("creating eks addons plan", "cluster", eksClusterName, "numdesired", len(desiredAddons), "numinstalled", len(installed))
    63  	addonsPlan := eksaddons.NewPlan(eksClusterName, desiredAddons, installed, s.EKSClient)
    64  	procedures, err := addonsPlan.Create(ctx)
    65  	if err != nil {
    66  		s.scope.Error(err, "failed creating eks addons plane")
    67  		return fmt.Errorf("creating eks addons plan: %w", err)
    68  	}
    69  	s.scope.V(2).Info("computed EKS addons plan", "numprocs", len(procedures))
    70  
    71  	// Perform required operations
    72  	for _, procedure := range procedures {
    73  		s.scope.V(2).Info("Executing addon procedure", "name", procedure.Name())
    74  		if err := procedure.Do(ctx); err != nil {
    75  			s.scope.Error(err, "failed executing addon procedure", "name", procedure.Name())
    76  			return fmt.Errorf("%s: %w", procedure.Name(), err)
    77  		}
    78  	}
    79  
    80  	// Update status with addons installed details
    81  	// Note: we are not relying on the computed state from the operations as we still want
    82  	// to update the state even if there are no operations to capture things like status changes
    83  	s.scope.V(2).Info("getting installed eks addons to update status", "cluster", eksClusterName)
    84  	addonState, err := s.getInstalledState(eksClusterName, addonNames)
    85  	if err != nil {
    86  		return fmt.Errorf("getting installed state of eks addons: %w", err)
    87  	}
    88  	s.scope.ControlPlane.Status.Addons = addonState
    89  
    90  	// Persist status and record event
    91  	if err := s.scope.PatchObject(); err != nil {
    92  		return fmt.Errorf("failed to update control plane: %w", err)
    93  	}
    94  	record.Eventf(s.scope.ControlPlane, "SuccessfulReconcileEKSClusterAddons", "Reconciled addons for EKS Cluster %s", s.scope.KubernetesClusterName())
    95  	s.scope.V(2).Info("Reconcile EKS addons completed successfully")
    96  
    97  	return nil
    98  }
    99  
   100  func (s *Service) getClusterAddonsInstalled(eksClusterName string, addonNames []*string) ([]*eksaddons.EKSAddon, error) {
   101  	s.V(2).Info("getting eks addons installed")
   102  
   103  	addonsInstalled := []*eksaddons.EKSAddon{}
   104  	if len(addonNames) == 0 {
   105  		s.scope.Info("no eks addons installed in cluster", "cluster", eksClusterName)
   106  		return addonsInstalled, nil
   107  	}
   108  
   109  	for _, addon := range addonNames {
   110  		describeInput := &eks.DescribeAddonInput{
   111  			AddonName:   addon,
   112  			ClusterName: &eksClusterName,
   113  		}
   114  		describeOutput, err := s.EKSClient.DescribeAddon(describeInput)
   115  		if err != nil {
   116  			return addonsInstalled, fmt.Errorf("describing eks addon %s: %w", *addon, err)
   117  		}
   118  
   119  		if describeOutput.Addon == nil {
   120  			continue
   121  		}
   122  		s.scope.V(2).Info("describe output", "output", describeOutput.Addon)
   123  
   124  		installedAddon := &eksaddons.EKSAddon{
   125  			Name:                  describeOutput.Addon.AddonName,
   126  			Version:               describeOutput.Addon.AddonVersion,
   127  			ARN:                   describeOutput.Addon.AddonArn,
   128  			Tags:                  infrav1.Tags{},
   129  			Status:                describeOutput.Addon.Status,
   130  			ServiceAccountRoleARN: describeOutput.Addon.ServiceAccountRoleArn,
   131  		}
   132  		for k, v := range describeOutput.Addon.Tags {
   133  			installedAddon.Tags[k] = *v
   134  		}
   135  
   136  		addonsInstalled = append(addonsInstalled, installedAddon)
   137  	}
   138  
   139  	return addonsInstalled, nil
   140  }
   141  
   142  func (s *Service) getInstalledState(eksClusterName string, addonNames []*string) ([]ekscontrolplanev1.AddonState, error) {
   143  	s.V(2).Info("getting eks addons installed to create state")
   144  
   145  	addonState := []ekscontrolplanev1.AddonState{}
   146  	if len(addonNames) == 0 {
   147  		s.scope.Info("no eks addons installed in cluster", "cluster", eksClusterName)
   148  		return addonState, nil
   149  	}
   150  
   151  	for _, addonName := range addonNames {
   152  		describeInput := &eks.DescribeAddonInput{
   153  			AddonName:   addonName,
   154  			ClusterName: &eksClusterName,
   155  		}
   156  		describeOutput, err := s.EKSClient.DescribeAddon(describeInput)
   157  		if err != nil {
   158  			return addonState, fmt.Errorf("describing eks addon %s: %w", *addonName, err)
   159  		}
   160  
   161  		if describeOutput.Addon == nil {
   162  			continue
   163  		}
   164  		s.scope.V(2).Info("describe output", "output", describeOutput.Addon)
   165  
   166  		installedAddonState := converters.AddonSDKToAddonState(describeOutput.Addon)
   167  		addonState = append(addonState, *installedAddonState)
   168  	}
   169  
   170  	return addonState, nil
   171  }
   172  
   173  func (s *Service) listAddons(eksClusterName string) ([]*string, error) {
   174  	s.V(2).Info("getting list of eks addons")
   175  
   176  	input := &eks.ListAddonsInput{
   177  		ClusterName: &eksClusterName,
   178  	}
   179  
   180  	addons := []*string{}
   181  	output, err := s.EKSClient.ListAddons(input)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("listing eks addons: %w", err)
   184  	}
   185  
   186  	addons = append(addons, output.Addons...)
   187  
   188  	return addons, nil
   189  }
   190  
   191  func (s *Service) translateAPIToAddon(addons []ekscontrolplanev1.Addon) []*eksaddons.EKSAddon {
   192  	converted := []*eksaddons.EKSAddon{}
   193  
   194  	for i := range addons {
   195  		addon := addons[i]
   196  		convertedAddon := &eksaddons.EKSAddon{
   197  			Name:                  &addon.Name,
   198  			Version:               &addon.Version,
   199  			Tags:                  ngTags(s.scope.Cluster.Name, s.scope.AdditionalTags()),
   200  			ResolveConflict:       convertConflictResolution(*addon.ConflictResolution),
   201  			ServiceAccountRoleARN: addon.ServiceAccountRoleArn,
   202  		}
   203  
   204  		converted = append(converted, convertedAddon)
   205  	}
   206  
   207  	return converted
   208  }
   209  
   210  func convertConflictResolution(conflict ekscontrolplanev1.AddonResolution) *string {
   211  	switch conflict {
   212  	case ekscontrolplanev1.AddonResolutionNone:
   213  		return aws.String(eks.ResolveConflictsNone)
   214  	case ekscontrolplanev1.AddonResolutionOverwrite:
   215  		return aws.String(eks.ResolveConflictsOverwrite)
   216  	default:
   217  		return nil
   218  	}
   219  }