sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/cert_manager.go (about) 1 /* 2 Copyright 2019 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 cluster 18 19 import ( 20 "context" 21 _ "embed" 22 "time" 23 24 "github.com/blang/semver/v4" 25 "github.com/pkg/errors" 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 32 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" 33 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" 34 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/util" 35 logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" 36 utilresource "sigs.k8s.io/cluster-api/util/resource" 37 "sigs.k8s.io/cluster-api/util/version" 38 utilyaml "sigs.k8s.io/cluster-api/util/yaml" 39 ) 40 41 const ( 42 waitCertManagerInterval = 1 * time.Second 43 44 certManagerNamespace = "cert-manager" 45 46 // This is maintained only for supporting upgrades from cluster created with clusterctl v1alpha3. 47 // 48 // Deprecated: Use clusterctlv1.CertManagerVersionAnnotation instead. 49 // TODO: Remove once upgrades from v1alpha3 are no longer supported. 50 certManagerVersionAnnotation = "certmanager.clusterctl.cluster.x-k8s.io/version" 51 ) 52 53 var ( 54 //go:embed assets/cert-manager-test-resources.yaml 55 certManagerTestManifest []byte 56 ) 57 58 // CertManagerUpgradePlan defines the upgrade plan if cert-manager needs to be 59 // upgraded to a different version. 60 type CertManagerUpgradePlan struct { 61 ExternallyManaged bool 62 From, To string 63 ShouldUpgrade bool 64 } 65 66 // CertManagerClient has methods to work with cert-manager components in the cluster. 67 type CertManagerClient interface { 68 // EnsureInstalled makes sure cert-manager is running and its API is available. 69 // This is required to install a new provider. 70 EnsureInstalled(ctx context.Context) error 71 72 // EnsureLatestVersion checks the cert-manager version currently installed, and if it is 73 // older than the version currently suggested by clusterctl, upgrades it. 74 EnsureLatestVersion(ctx context.Context) error 75 76 // PlanUpgrade retruns a CertManagerUpgradePlan with information regarding 77 // a cert-manager upgrade if necessary. 78 PlanUpgrade(ctx context.Context) (CertManagerUpgradePlan, error) 79 80 // Images return the list of images required for installing the cert-manager. 81 Images(ctx context.Context) ([]string, error) 82 } 83 84 // certManagerClient implements CertManagerClient . 85 type certManagerClient struct { 86 configClient config.Client 87 repositoryClientFactory RepositoryClientFactory 88 proxy Proxy 89 pollImmediateWaiter PollImmediateWaiter 90 } 91 92 // Ensure certManagerClient implements the CertManagerClient interface. 93 var _ CertManagerClient = &certManagerClient{} 94 95 // newCertManagerClient returns a certManagerClient. 96 func newCertManagerClient(configClient config.Client, repositoryClientFactory RepositoryClientFactory, proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *certManagerClient { 97 return &certManagerClient{ 98 configClient: configClient, 99 repositoryClientFactory: repositoryClientFactory, 100 proxy: proxy, 101 pollImmediateWaiter: pollImmediateWaiter, 102 } 103 } 104 105 // Images return the list of images required for installing the cert-manager. 106 func (cm *certManagerClient) Images(ctx context.Context) ([]string, error) { 107 // If cert manager already exists in the cluster, there is no need of additional images for cert-manager. 108 exists, err := cm.certManagerNamespaceExists(ctx) 109 if err != nil { 110 return nil, err 111 } 112 if exists { 113 return []string{}, nil 114 } 115 116 // Otherwise, retrieve the images from the cert-manager manifest. 117 config, err := cm.configClient.CertManager().Get() 118 if err != nil { 119 return nil, err 120 } 121 122 objs, err := cm.getManifestObjs(ctx, config) 123 if err != nil { 124 return nil, err 125 } 126 127 images, err := util.InspectImages(objs) 128 if err != nil { 129 return nil, err 130 } 131 return images, nil 132 } 133 134 func (cm *certManagerClient) certManagerNamespaceExists(ctx context.Context) (bool, error) { 135 ns := &corev1.Namespace{} 136 key := client.ObjectKey{Name: certManagerNamespace} 137 c, err := cm.proxy.NewClient(ctx) 138 if err != nil { 139 return false, err 140 } 141 142 if err := c.Get(ctx, key, ns); err != nil { 143 if apierrors.IsNotFound(err) { 144 return false, nil 145 } 146 return false, err 147 } 148 return true, nil 149 } 150 151 // EnsureInstalled makes sure cert-manager is running and its API is available. 152 // This is required to install a new provider. 153 func (cm *certManagerClient) EnsureInstalled(ctx context.Context) error { 154 log := logf.Log 155 156 // Checking if a version of cert manager supporting cert-manager-test-resources.yaml is already installed and properly working. 157 if err := cm.waitForAPIReady(ctx, false); err == nil { 158 log.Info("Skipping installing cert-manager as it is already installed") 159 return nil 160 } 161 162 // Otherwise install cert manager. 163 // NOTE: this instance of cert-manager will have clusterctl specific annotations that will be used to 164 // manage the lifecycle of all the components. 165 return cm.install(ctx) 166 } 167 168 func (cm *certManagerClient) install(ctx context.Context) error { 169 log := logf.Log 170 171 config, err := cm.configClient.CertManager().Get() 172 if err != nil { 173 return err 174 } 175 log.Info("Installing cert-manager", "Version", config.Version()) 176 177 // Gets the cert-manager components from the repository. 178 objs, err := cm.getManifestObjs(ctx, config) 179 if err != nil { 180 return err 181 } 182 183 // Install all cert-manager manifests 184 createCertManagerBackoff := newWriteBackoff() 185 objs = utilresource.SortForCreate(objs) 186 for i := range objs { 187 o := objs[i] 188 // Create the Kubernetes object. 189 // Nb. The operation is wrapped in a retry loop to make ensureCerts more resilient to unexpected conditions. 190 if err := retryWithExponentialBackoff(ctx, createCertManagerBackoff, func(ctx context.Context) error { 191 return cm.createObj(ctx, o) 192 }); err != nil { 193 return err 194 } 195 } 196 197 // Wait for the cert-manager API to be ready to accept requests 198 return cm.waitForAPIReady(ctx, true) 199 } 200 201 // PlanUpgrade returns a CertManagerUpgradePlan with information regarding 202 // a cert-manager upgrade if necessary. 203 func (cm *certManagerClient) PlanUpgrade(ctx context.Context) (CertManagerUpgradePlan, error) { 204 log := logf.Log 205 206 objs, err := cm.proxy.ListResources(ctx, map[string]string{clusterctlv1.ClusterctlCoreLabel: clusterctlv1.ClusterctlCoreLabelCertManagerValue}, certManagerNamespace) 207 if err != nil { 208 return CertManagerUpgradePlan{}, errors.Wrap(err, "failed get cert manager components") 209 } 210 211 // If there are no cert manager components with the clusterctl labels, it means that cert-manager is externally managed. 212 if len(objs) == 0 { 213 log.V(5).Info("Skipping cert-manager version check because externally managed") 214 return CertManagerUpgradePlan{ExternallyManaged: true}, nil 215 } 216 217 log.Info("Checking cert-manager version...") 218 currentVersion, targetVersion, shouldUpgrade, err := cm.shouldUpgrade(objs) 219 if err != nil { 220 return CertManagerUpgradePlan{}, err 221 } 222 223 return CertManagerUpgradePlan{ 224 From: currentVersion, 225 To: targetVersion, 226 ShouldUpgrade: shouldUpgrade, 227 }, nil 228 } 229 230 // EnsureLatestVersion checks the cert-manager version currently installed, and if it is 231 // older than the version currently suggested by clusterctl, upgrades it. 232 func (cm *certManagerClient) EnsureLatestVersion(ctx context.Context) error { 233 log := logf.Log 234 235 objs, err := cm.proxy.ListResources(ctx, map[string]string{clusterctlv1.ClusterctlCoreLabel: clusterctlv1.ClusterctlCoreLabelCertManagerValue}, certManagerNamespace) 236 if err != nil { 237 return errors.Wrap(err, "failed get cert manager components") 238 } 239 240 // If there are no cert manager components with the clusterctl labels, it means that cert-manager is externally managed. 241 if len(objs) == 0 { 242 log.V(5).Info("Skipping cert-manager upgrade because externally managed") 243 return nil 244 } 245 246 log.Info("Checking cert-manager version...") 247 currentVersion, _, shouldUpgrade, err := cm.shouldUpgrade(objs) 248 if err != nil { 249 return err 250 } 251 252 if !shouldUpgrade { 253 log.Info("Cert-manager is already up to date") 254 return nil 255 } 256 257 // Migrate CRs to latest CRD storage version, if necessary. 258 // Note: We have to do this before cert-manager is deleted so conversion webhooks still work. 259 if err := cm.migrateCRDs(ctx); err != nil { 260 return err 261 } 262 263 // delete the cert-manager version currently installed (because it should be upgraded); 264 // NOTE: CRDs, and namespace are preserved in order to avoid deletion of user objects; 265 // web-hooks are preserved to avoid a user attempting to CREATE a cert-manager resource while the upgrade is in progress. 266 log.Info("Deleting cert-manager", "Version", currentVersion) 267 if err := cm.deleteObjs(ctx, objs); err != nil { 268 return err 269 } 270 271 // Install cert-manager. 272 return cm.install(ctx) 273 } 274 275 func (cm *certManagerClient) migrateCRDs(ctx context.Context) error { 276 config, err := cm.configClient.CertManager().Get() 277 if err != nil { 278 return err 279 } 280 281 // Gets the new cert-manager components from the repository. 282 objs, err := cm.getManifestObjs(ctx, config) 283 if err != nil { 284 return err 285 } 286 287 c, err := cm.proxy.NewClient(ctx) 288 if err != nil { 289 return err 290 } 291 292 return NewCRDMigrator(c).Run(ctx, objs) 293 } 294 295 func (cm *certManagerClient) deleteObjs(ctx context.Context, objs []unstructured.Unstructured) error { 296 deleteCertManagerBackoff := newWriteBackoff() 297 for i := range objs { 298 obj := objs[i] 299 300 // CRDs, and namespace are preserved in order to avoid deletion of user objects; 301 // web-hooks are preserved to avoid a user attempting to CREATE a cert-manager resource while the upgrade is in progress. 302 if obj.GetKind() == "CustomResourceDefinition" || 303 obj.GetKind() == "Namespace" || 304 obj.GetKind() == "MutatingWebhookConfiguration" || 305 obj.GetKind() == "ValidatingWebhookConfiguration" { 306 continue 307 } 308 309 if err := retryWithExponentialBackoff(ctx, deleteCertManagerBackoff, func(ctx context.Context) error { 310 if err := cm.deleteObj(ctx, obj); err != nil { 311 // tolerate NotFound errors when deleting the test resources 312 if apierrors.IsNotFound(err) { 313 return nil 314 } 315 return err 316 } 317 return nil 318 }); err != nil { 319 return err 320 } 321 } 322 return nil 323 } 324 325 func (cm *certManagerClient) shouldUpgrade(objs []unstructured.Unstructured) (string, string, bool, error) { 326 config, err := cm.configClient.CertManager().Get() 327 if err != nil { 328 return "", "", false, err 329 } 330 331 desiredVersion := config.Version() 332 desiredSemVersion, err := semver.ParseTolerant(desiredVersion) 333 if err != nil { 334 return "", "", false, errors.Wrapf(err, "failed to parse config version [%s] for cert-manager component", desiredVersion) 335 } 336 337 needUpgrade := false 338 currentVersion := "" 339 for i := range objs { 340 obj := objs[i] 341 342 // Endpoints and EndpointSlices are generated by Kubernetes without the version annotation, so we are skipping them 343 if obj.GetKind() == "Endpoints" || obj.GetKind() == "EndpointSlice" { 344 continue 345 } 346 347 // if there is no version annotation, this means the obj is cert-manager v0.11.0 (installed with older version of clusterctl) 348 objVersion, ok := obj.GetAnnotations()[clusterctlv1.CertManagerVersionAnnotation] 349 if !ok { 350 // try the old annotation name 351 objVersion, ok = obj.GetAnnotations()[certManagerVersionAnnotation] 352 if !ok { 353 currentVersion = "v0.11.0" 354 needUpgrade = true 355 break 356 } 357 } 358 359 objSemVersion, err := semver.ParseTolerant(objVersion) 360 if err != nil { 361 return "", "", false, errors.Wrapf(err, "failed to parse version for cert-manager component %s/%s", obj.GetKind(), obj.GetName()) 362 } 363 364 c := version.Compare(objSemVersion, desiredSemVersion, version.WithBuildTags()) 365 switch { 366 case c < 0 || c == 2: 367 // if version < current or same version and different non-numeric build metadata, then upgrade 368 currentVersion = objVersion 369 needUpgrade = true 370 case c >= 0: 371 // the installed version is greater than or equal to one required by clusterctl, so we are ok 372 currentVersion = objVersion 373 } 374 375 if needUpgrade { 376 break 377 } 378 } 379 return currentVersion, desiredVersion, needUpgrade, nil 380 } 381 382 func (cm *certManagerClient) getWaitTimeout() time.Duration { 383 log := logf.Log 384 385 certManagerConfig, err := cm.configClient.CertManager().Get() 386 if err != nil { 387 return config.CertManagerDefaultTimeout 388 } 389 timeoutDuration, err := time.ParseDuration(certManagerConfig.Timeout()) 390 if err != nil { 391 log.Info("Invalid value set for cert-manager configuration", "timeout", certManagerConfig.Timeout()) 392 return config.CertManagerDefaultTimeout 393 } 394 return timeoutDuration 395 } 396 397 func (cm *certManagerClient) getManifestObjs(ctx context.Context, certManagerConfig config.CertManager) ([]unstructured.Unstructured, error) { 398 // Given that cert manager components yaml are stored in a repository like providers components yaml, 399 // we are using the same machinery to retrieve the file by using a fake provider object using 400 // the cert manager repository url. 401 certManagerFakeProvider := config.NewProvider("cert-manager", certManagerConfig.URL(), "") 402 certManagerRepository, err := cm.repositoryClientFactory(ctx, certManagerFakeProvider, cm.configClient) 403 if err != nil { 404 return nil, err 405 } 406 407 // Gets the cert-manager component yaml from the repository. 408 file, err := certManagerRepository.Components().Raw(ctx, repository.ComponentsOptions{ 409 Version: certManagerConfig.Version(), 410 }) 411 if err != nil { 412 return nil, err 413 } 414 415 // Converts the file to ustructured objects. 416 objs, err := utilyaml.ToUnstructured(file) 417 if err != nil { 418 return nil, errors.Wrap(err, "failed to parse yaml for cert-manager manifest") 419 } 420 421 // Apply image overrides. 422 objs, err = util.FixImages(objs, func(image string) (string, error) { 423 return cm.configClient.ImageMeta().AlterImage(config.CertManagerImageComponent, image) 424 }) 425 if err != nil { 426 return nil, errors.Wrap(err, "failed to apply image override to the cert-manager manifest") 427 } 428 429 // Add cert manager labels and annotations. 430 objs = addCerManagerLabel(objs) 431 objs = addCerManagerAnnotations(objs, certManagerConfig.Version()) 432 433 return objs, nil 434 } 435 436 func addCerManagerLabel(objs []unstructured.Unstructured) []unstructured.Unstructured { 437 for _, o := range objs { 438 labels := o.GetLabels() 439 if labels == nil { 440 labels = map[string]string{} 441 } 442 labels[clusterctlv1.ClusterctlLabel] = "" 443 labels[clusterctlv1.ClusterctlCoreLabel] = clusterctlv1.ClusterctlCoreLabelCertManagerValue 444 o.SetLabels(labels) 445 } 446 return objs 447 } 448 449 func addCerManagerAnnotations(objs []unstructured.Unstructured, version string) []unstructured.Unstructured { 450 for _, o := range objs { 451 annotations := o.GetAnnotations() 452 if annotations == nil { 453 annotations = map[string]string{} 454 } 455 annotations[clusterctlv1.CertManagerVersionAnnotation] = version 456 o.SetAnnotations(annotations) 457 } 458 return objs 459 } 460 461 // getTestResourcesManifestObjs gets the cert-manager test manifests, converted to unstructured objects. 462 // These are used to ensure the cert-manager API components are all ready and the API is available for use. 463 func getTestResourcesManifestObjs() ([]unstructured.Unstructured, error) { 464 objs, err := utilyaml.ToUnstructured(certManagerTestManifest) 465 if err != nil { 466 return nil, errors.Wrap(err, "failed to parse yaml for cert-manager test resources manifest") 467 } 468 return objs, nil 469 } 470 471 func (cm *certManagerClient) createObj(ctx context.Context, obj unstructured.Unstructured) error { 472 log := logf.Log 473 474 c, err := cm.proxy.NewClient(ctx) 475 if err != nil { 476 return err 477 } 478 479 // check if the component already exists, and eventually update it; otherwise create it 480 // NOTE: This is required because this func is used also for upgrading cert-manager and during upgrades 481 // some objects of the previous release are preserved in order to avoid to delete user data (e.g. CRDs). 482 currentR := &unstructured.Unstructured{} 483 currentR.SetGroupVersionKind(obj.GroupVersionKind()) 484 485 key := client.ObjectKey{ 486 Namespace: obj.GetNamespace(), 487 Name: obj.GetName(), 488 } 489 if err := c.Get(ctx, key, currentR); err != nil { 490 if !apierrors.IsNotFound(err) { 491 return errors.Wrapf(err, "failed to get cert-manager object %s, %s/%s", obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName()) 492 } 493 494 // if it does not exists, create the component 495 log.V(5).Info("Creating", logf.UnstructuredToValues(obj)...) 496 if err := c.Create(ctx, &obj); err != nil { 497 return errors.Wrapf(err, "failed to create cert-manager component %s, %s/%s", obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName()) 498 } 499 return nil 500 } 501 502 // otherwise update the component 503 log.V(5).Info("Updating", logf.UnstructuredToValues(obj)...) 504 obj.SetResourceVersion(currentR.GetResourceVersion()) 505 if err := c.Update(ctx, &obj); err != nil { 506 return errors.Wrapf(err, "failed to update cert-manager component %s, %s/%s", obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName()) 507 } 508 return nil 509 } 510 511 func (cm *certManagerClient) deleteObj(ctx context.Context, obj unstructured.Unstructured) error { 512 log := logf.Log 513 log.V(5).Info("Deleting", logf.UnstructuredToValues(obj)...) 514 515 cl, err := cm.proxy.NewClient(ctx) 516 if err != nil { 517 return err 518 } 519 520 return cl.Delete(ctx, &obj) 521 } 522 523 // waitForAPIReady will attempt to create the cert-manager 'test assets' (i.e. a basic 524 // Issuer and Certificate). 525 // This ensures that the Kubernetes apiserver is ready to serve resources within the 526 // cert-manager API group. 527 // If retry is true, the createObj call will be retried if it fails. Otherwise, the 528 // 'create' operations will only be attempted once. 529 func (cm *certManagerClient) waitForAPIReady(ctx context.Context, retry bool) error { 530 log := logf.Log 531 // Waits for the cert-manager to be available. 532 if retry { 533 log.Info("Waiting for cert-manager to be available...") 534 } 535 536 testObjs, err := getTestResourcesManifestObjs() 537 if err != nil { 538 return err 539 } 540 541 for i := range testObjs { 542 o := testObjs[i] 543 544 // Create the Kubernetes object. 545 // This is wrapped with a retry as the cert-manager API may not be available 546 // yet, so we need to keep retrying until it is. 547 if err := cm.pollImmediateWaiter(ctx, waitCertManagerInterval, cm.getWaitTimeout(), func(ctx context.Context) (bool, error) { 548 if err := cm.createObj(ctx, o); err != nil { 549 // If retrying is disabled, return the error here. 550 if !retry { 551 return false, err 552 } 553 return false, nil 554 } 555 return true, nil 556 }); err != nil { 557 return err 558 } 559 } 560 deleteCertManagerBackoff := newWriteBackoff() 561 for i := range testObjs { 562 obj := testObjs[i] 563 if err := retryWithExponentialBackoff(ctx, deleteCertManagerBackoff, func(ctx context.Context) error { 564 if err := cm.deleteObj(ctx, obj); err != nil { 565 // tolerate NotFound errors when deleting the test resources 566 if apierrors.IsNotFound(err) { 567 return nil 568 } 569 return err 570 } 571 return nil 572 }); err != nil { 573 return err 574 } 575 } 576 577 return nil 578 }