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 }