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  }