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 }