sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/upgrade.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 client
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
    30  )
    31  
    32  const upgradeItemProviderNameError = "invalid provider name %q. Provider name should be in the form namespace/provider:version or provider:version"
    33  
    34  // PlanUpgradeOptions carries the options supported by upgrade plan.
    35  type PlanUpgradeOptions struct {
    36  	// Kubeconfig defines the kubeconfig to use for accessing the management cluster. If empty, default discovery rules apply.
    37  	Kubeconfig Kubeconfig
    38  }
    39  
    40  func (c *clusterctlClient) PlanCertManagerUpgrade(ctx context.Context, options PlanUpgradeOptions) (CertManagerUpgradePlan, error) {
    41  	// Get the client for interacting with the management cluster.
    42  	cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
    43  	if err != nil {
    44  		return CertManagerUpgradePlan{}, err
    45  	}
    46  
    47  	certManager := cluster.CertManager()
    48  	plan, err := certManager.PlanUpgrade(ctx)
    49  	return CertManagerUpgradePlan(plan), err
    50  }
    51  
    52  func (c *clusterctlClient) PlanUpgrade(ctx context.Context, options PlanUpgradeOptions) ([]UpgradePlan, error) {
    53  	// Get the client for interacting with the management cluster.
    54  	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	// Ensure this command only runs against management clusters with the current Cluster API contract.
    60  	if err := clusterClient.ProviderInventory().CheckCAPIContract(ctx); err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	// Ensures the custom resource definitions required by clusterctl are in place.
    65  	if err := clusterClient.ProviderInventory().EnsureCustomResourceDefinitions(ctx); err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	upgradePlans, err := clusterClient.ProviderUpgrader().Plan(ctx)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// UpgradePlan is an alias for cluster.UpgradePlan; this makes the conversion
    75  	aliasUpgradePlan := make([]UpgradePlan, len(upgradePlans))
    76  	for i, plan := range upgradePlans {
    77  		aliasUpgradePlan[i] = UpgradePlan{
    78  			Contract:  plan.Contract,
    79  			Providers: plan.Providers,
    80  		}
    81  	}
    82  
    83  	return aliasUpgradePlan, nil
    84  }
    85  
    86  // ApplyUpgradeOptions carries the options supported by upgrade apply.
    87  type ApplyUpgradeOptions struct {
    88  	// Kubeconfig to use for accessing the management cluster. If empty, default discovery rules apply.
    89  	Kubeconfig Kubeconfig
    90  
    91  	// Contract defines the API Version of Cluster API (contract e.g. v1alpha4) the management cluster should upgrade to.
    92  	// When upgrading by contract, the latest versions available will be used for all the providers; if you want
    93  	// a more granular control on upgrade, use CoreProvider, BootstrapProviders, ControlPlaneProviders, InfrastructureProviders.
    94  	Contract string
    95  
    96  	// CoreProvider instance and version (e.g. [capi-system/]cluster-api:v1.1.5) to upgrade to. This field can be used as alternative to Contract.
    97  	// Specifying a namespace is now optional and in the future it will be deprecated.
    98  	CoreProvider string
    99  
   100  	// BootstrapProviders instance and versions (e.g. [capi-kubeadm-bootstrap-system/]kubeadm:v1.1.5) to upgrade to. This field can be used as alternative to Contract.
   101  	// Specifying a namespace is now optional and in the future it will be deprecated.
   102  	BootstrapProviders []string
   103  
   104  	// ControlPlaneProviders instance and versions (e.g. [capi-kubeadm-control-plane-system/]kubeadm:v1.1.5) to upgrade to. This field can be used as alternative to Contract.
   105  	// Specifying a namespace is now optional and in the future it will be deprecated.
   106  	ControlPlaneProviders []string
   107  
   108  	// InfrastructureProviders instance and versions (e.g. [capa-system/]aws:v0.5.0) to upgrade to. This field can be used as alternative to Contract.
   109  	// Specifying a namespace is now optional and in the future it will be deprecated.
   110  	InfrastructureProviders []string
   111  
   112  	// IPAMProviders instance and versions (e.g. ipam-system/infoblox:v0.0.1) to upgrade to. This field can be used as alternative to Contract.
   113  	IPAMProviders []string
   114  
   115  	// RuntimeExtensionProviders instance and versions (e.g. runtime-extension-system/test:v0.0.1) to upgrade to. This field can be used as alternative to Contract.
   116  	RuntimeExtensionProviders []string
   117  
   118  	// AddonProviders instance and versions (e.g. caaph-system/helm:v0.1.0) to upgrade to. This field can be used as alternative to Contract.
   119  	AddonProviders []string
   120  
   121  	// WaitProviders instructs the upgrade apply command to wait till the providers are successfully upgraded.
   122  	WaitProviders bool
   123  
   124  	// WaitProviderTimeout sets the timeout per provider upgrade.
   125  	WaitProviderTimeout time.Duration
   126  }
   127  
   128  func (c *clusterctlClient) ApplyUpgrade(ctx context.Context, options ApplyUpgradeOptions) error {
   129  	if options.Contract != "" && options.Contract != clusterv1.GroupVersion.Version {
   130  		return errors.Errorf("current version of clusterctl could only upgrade to %s contract, requested %s", clusterv1.GroupVersion.Version, options.Contract)
   131  	}
   132  
   133  	// Default WaitProviderTimeout as we cannot rely on defaulting in the CLI
   134  	// when clusterctl is used as a library.
   135  	if options.WaitProviderTimeout.Nanoseconds() == 0 {
   136  		options.WaitProviderTimeout = time.Duration(5*60) * time.Second
   137  	}
   138  
   139  	// Get the client for interacting with the management cluster.
   140  	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	// Ensure this command only runs against management clusters with the current Cluster API contract.
   146  	if err := clusterClient.ProviderInventory().CheckCAPIContract(ctx); err != nil {
   147  		return err
   148  	}
   149  
   150  	// Ensures the custom resource definitions required by clusterctl are in place.
   151  	if err := clusterClient.ProviderInventory().EnsureCustomResourceDefinitions(ctx); err != nil {
   152  		return err
   153  	}
   154  
   155  	// Ensures the latest version of cert-manager.
   156  	// NOTE: it is safe to upgrade to latest version of cert-manager given that it provides
   157  	// conversion web-hooks around Issuer/Certificate kinds, so installing an older versions of providers
   158  	// should continue to work with the latest cert-manager.
   159  	certManager := clusterClient.CertManager()
   160  	if err := certManager.EnsureLatestVersion(ctx); err != nil {
   161  		return err
   162  	}
   163  
   164  	// Check if the user want a custom upgrade
   165  	isCustomUpgrade := options.CoreProvider != "" ||
   166  		len(options.BootstrapProviders) > 0 ||
   167  		len(options.ControlPlaneProviders) > 0 ||
   168  		len(options.InfrastructureProviders) > 0 ||
   169  		len(options.IPAMProviders) > 0 ||
   170  		len(options.RuntimeExtensionProviders) > 0 ||
   171  		len(options.AddonProviders) > 0
   172  
   173  	opts := cluster.UpgradeOptions{
   174  		WaitProviders:       options.WaitProviders,
   175  		WaitProviderTimeout: options.WaitProviderTimeout,
   176  	}
   177  
   178  	// If we are upgrading a specific set of providers only, process the providers and call ApplyCustomPlan.
   179  	if isCustomUpgrade {
   180  		// Converts upgrade references back into an UpgradeItem.
   181  		upgradeItems := []cluster.UpgradeItem{}
   182  
   183  		if options.CoreProvider != "" {
   184  			upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.CoreProviderType, options.CoreProvider)
   185  			if err != nil {
   186  				return err
   187  			}
   188  		}
   189  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.BootstrapProviderType, options.BootstrapProviders...)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.ControlPlaneProviderType, options.ControlPlaneProviders...)
   194  		if err != nil {
   195  			return err
   196  		}
   197  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.InfrastructureProviderType, options.InfrastructureProviders...)
   198  		if err != nil {
   199  			return err
   200  		}
   201  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.IPAMProviderType, options.IPAMProviders...)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.RuntimeExtensionProviderType, options.RuntimeExtensionProviders...)
   206  		if err != nil {
   207  			return err
   208  		}
   209  		upgradeItems, err = addUpgradeItems(ctx, clusterClient, upgradeItems, clusterctlv1.AddonProviderType, options.AddonProviders...)
   210  		if err != nil {
   211  			return err
   212  		}
   213  
   214  		// Execute the upgrade using the custom upgrade items
   215  		return clusterClient.ProviderUpgrader().ApplyCustomPlan(ctx, opts, upgradeItems...)
   216  	}
   217  
   218  	// Otherwise we are upgrading a whole management cluster according to a clusterctl generated upgrade plan.
   219  	return clusterClient.ProviderUpgrader().ApplyPlan(ctx, opts, options.Contract)
   220  }
   221  
   222  func addUpgradeItems(ctx context.Context, clusterClient cluster.Client, upgradeItems []cluster.UpgradeItem, providerType clusterctlv1.ProviderType, providers ...string) ([]cluster.UpgradeItem, error) {
   223  	for _, upgradeReference := range providers {
   224  		providerUpgradeItem, err := parseUpgradeItem(ctx, clusterClient, upgradeReference, providerType)
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  		if providerUpgradeItem.NextVersion == "" {
   229  			return nil, errors.Errorf("invalid provider name %q. Provider name should be in the form namespace/name:version and version cannot be empty", upgradeReference)
   230  		}
   231  		upgradeItems = append(upgradeItems, *providerUpgradeItem)
   232  	}
   233  	return upgradeItems, nil
   234  }
   235  
   236  func parseUpgradeItem(ctx context.Context, clusterClient cluster.Client, ref string, providerType clusterctlv1.ProviderType) (*cluster.UpgradeItem, error) {
   237  	// TODO(oscr) Remove when explicit namespaces for providers is removed
   238  	// ref format is old format: namespace/provider:version
   239  	if strings.Contains(ref, "/") {
   240  		return parseUpgradeItemWithNamespace(ref, providerType)
   241  	}
   242  
   243  	// ref format is: provider:version
   244  	return parseUpgradeItemWithoutNamespace(ctx, clusterClient, ref, providerType)
   245  }
   246  
   247  func parseUpgradeItemWithNamespace(ref string, providerType clusterctlv1.ProviderType) (*cluster.UpgradeItem, error) {
   248  	refSplit := strings.Split(strings.ToLower(ref), "/")
   249  
   250  	if len(refSplit) != 2 {
   251  		return nil, errors.Errorf(upgradeItemProviderNameError, ref)
   252  	}
   253  
   254  	if refSplit[0] == "" {
   255  		return nil, errors.Errorf(upgradeItemProviderNameError, ref)
   256  	}
   257  	namespace := refSplit[0]
   258  
   259  	name, version, err := parseProviderName(refSplit[1])
   260  	if err != nil {
   261  		return nil, errors.Wrapf(err, upgradeItemProviderNameError, ref)
   262  	}
   263  
   264  	return &cluster.UpgradeItem{
   265  		Provider: clusterctlv1.Provider{
   266  			ObjectMeta: metav1.ObjectMeta{
   267  				Namespace: namespace,
   268  				Name:      clusterctlv1.ManifestLabel(name, providerType),
   269  			},
   270  			ProviderName: name,
   271  			Type:         string(providerType),
   272  			// The value for the following fields will be retrieved while
   273  			// creating the custom upgrade plan.
   274  			WatchedNamespace: "",
   275  		},
   276  		NextVersion: version,
   277  	}, nil
   278  }
   279  
   280  func parseUpgradeItemWithoutNamespace(ctx context.Context, clusterClient cluster.Client, ref string, providerType clusterctlv1.ProviderType) (*cluster.UpgradeItem, error) {
   281  	if !strings.Contains(ref, ":") {
   282  		return nil, errors.Errorf(upgradeItemProviderNameError, ref)
   283  	}
   284  
   285  	name, version, err := parseProviderName(ref)
   286  	if err != nil {
   287  		return nil, errors.Wrapf(err, upgradeItemProviderNameError, ref)
   288  	}
   289  
   290  	namespace, err := clusterClient.ProviderInventory().GetProviderNamespace(ctx, name, providerType)
   291  	if err != nil {
   292  		return nil, errors.Errorf("unable to find default namespace for provider %q", ref)
   293  	}
   294  
   295  	return &cluster.UpgradeItem{
   296  		Provider: clusterctlv1.Provider{
   297  			ObjectMeta: metav1.ObjectMeta{
   298  				Namespace: namespace,
   299  				Name:      clusterctlv1.ManifestLabel(name, providerType),
   300  			},
   301  			ProviderName: name,
   302  			Type:         string(providerType),
   303  			// The value for the following fields will be retrieved while
   304  			// creating the custom upgrade plan.
   305  			WatchedNamespace: "",
   306  		},
   307  		NextVersion: version,
   308  	}, nil
   309  }