sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/upgrader_info.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 cluster
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/apimachinery/pkg/util/version"
    27  
    28  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    29  )
    30  
    31  // upgradeInfo holds all the information required for taking upgrade decisions for a provider.
    32  type upgradeInfo struct {
    33  	// metadata holds the information about releaseSeries and the link between release series and the API Version of Cluster API (contract).
    34  	// e.g. release series 0.5.x for the AWS provider --> v1alpha3
    35  	metadata *clusterctlv1.Metadata
    36  
    37  	// currentVersion of the provider
    38  	currentVersion *version.Version
    39  
    40  	// currentContract of the provider
    41  	currentContract string
    42  
    43  	// nextVersions return the list of versions available for upgrades, defined as the list of version available in the provider repository
    44  	// greater than the currentVersion.
    45  	nextVersions []version.Version
    46  }
    47  
    48  // getUpgradeInfo returns all the info required for taking upgrade decisions for a provider.
    49  // NOTE: This could contain also versions for the previous or next Cluster API contract (not supported in current clusterctl release, but upgrade plan should report this options).
    50  func (u *providerUpgrader) getUpgradeInfo(ctx context.Context, provider clusterctlv1.Provider) (*upgradeInfo, error) {
    51  	// Gets the list of versions available in the provider repository.
    52  	configRepository, err := u.configClient.Providers().Get(provider.ProviderName, provider.GetProviderType())
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	providerRepository, err := u.repositoryClientFactory(ctx, configRepository, u.configClient)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	repositoryVersions, err := providerRepository.GetVersions(ctx)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	if len(repositoryVersions) == 0 {
    68  		return nil, errors.Errorf("failed to get available versions for the %s provider", provider.InstanceName())
    69  	}
    70  
    71  	//  Pick the provider's latest version available in the repository and use it to get the most recent metadata for the provider.
    72  	var latestVersion *version.Version
    73  	for _, availableVersion := range repositoryVersions {
    74  		availableSemVersion, err := version.ParseSemantic(availableVersion)
    75  		if err != nil {
    76  			return nil, errors.Wrapf(err, "failed to parse available version for the %s provider", provider.InstanceName())
    77  		}
    78  
    79  		if latestVersion == nil || latestVersion.LessThan(availableSemVersion) {
    80  			latestVersion = availableSemVersion
    81  		}
    82  	}
    83  
    84  	latestMetadata, err := providerRepository.Metadata(versionTag(latestVersion)).Get(ctx)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	// Get current provider version and check if the releaseSeries defined in metadata includes it.
    90  	currentVersion, err := version.ParseSemantic(provider.Version)
    91  	if err != nil {
    92  		return nil, errors.Wrapf(err, "failed to parse current version for the %s provider", provider.InstanceName())
    93  	}
    94  
    95  	if latestMetadata.GetReleaseSeriesForVersion(currentVersion) == nil {
    96  		return nil, errors.Errorf("invalid provider metadata: version %s (the current version) for the provider %s does not match any release series", provider.Version, provider.InstanceName())
    97  	}
    98  
    99  	// Filters the versions to be considered for upgrading the provider (next
   100  	// versions) and checks if the releaseSeries defined in metadata includes
   101  	// all of them.
   102  	// NOTE: This could contain also versions for the previous or next Cluster API contract (not supported in current clusterctl release, but upgrade plan should report this options).
   103  	nextVersions := []version.Version{}
   104  	for _, repositoryVersion := range repositoryVersions {
   105  		// we are ignoring the conversion error here because a first check already passed above
   106  		repositorySemVersion, _ := version.ParseSemantic(repositoryVersion)
   107  
   108  		// Drop the nextVersion version if older or equal that the current version
   109  		// NB. Using !LessThan because version does not implement a GreaterThan method.
   110  		if !currentVersion.LessThan(repositorySemVersion) {
   111  			continue
   112  		}
   113  
   114  		if latestMetadata.GetReleaseSeriesForVersion(repositorySemVersion) == nil {
   115  			return nil, errors.Errorf("invalid provider metadata: version %s (one of the available versions) for the provider %s does not match any release series", repositoryVersion, provider.InstanceName())
   116  		}
   117  
   118  		nextVersions = append(nextVersions, *repositorySemVersion)
   119  	}
   120  
   121  	return newUpgradeInfo(latestMetadata, currentVersion, nextVersions), nil
   122  }
   123  
   124  func newUpgradeInfo(metadata *clusterctlv1.Metadata, currentVersion *version.Version, nextVersions []version.Version) *upgradeInfo {
   125  	// Sorts release series; this ensures also an implicit ordering of API Version of Cluster API (contract).
   126  	sort.Slice(metadata.ReleaseSeries, func(i, j int) bool {
   127  		return metadata.ReleaseSeries[i].Major < metadata.ReleaseSeries[j].Major ||
   128  			(metadata.ReleaseSeries[i].Major == metadata.ReleaseSeries[j].Major && metadata.ReleaseSeries[i].Minor < metadata.ReleaseSeries[j].Minor)
   129  	})
   130  
   131  	// Sorts nextVersions.
   132  	sort.Slice(nextVersions, func(i, j int) bool {
   133  		return nextVersions[i].LessThan(&nextVersions[j])
   134  	})
   135  
   136  	// Gets the current contract for the provider
   137  	// Please note this should never be empty, because getUpgradeInfo ensures the releaseSeries defined in metadata includes the current version.
   138  	currentContract := ""
   139  	if currentReleaseSeries := metadata.GetReleaseSeriesForVersion(currentVersion); currentReleaseSeries != nil {
   140  		currentContract = currentReleaseSeries.Contract
   141  	}
   142  
   143  	return &upgradeInfo{
   144  		metadata:        metadata,
   145  		currentVersion:  currentVersion,
   146  		currentContract: currentContract,
   147  		nextVersions:    nextVersions,
   148  	}
   149  }
   150  
   151  // getContractsForUpgrade return the list of API Version of Cluster API (contract) version available for a provider upgrade.
   152  func (i *upgradeInfo) getContractsForUpgrade() []string {
   153  	contractsForUpgrade := sets.Set[string]{}
   154  	for _, releaseSeries := range i.metadata.ReleaseSeries {
   155  		// Drop the release series if older than the current version, because not relevant for upgrade.
   156  		if i.currentVersion.Major() > releaseSeries.Major || (i.currentVersion.Major() == releaseSeries.Major && i.currentVersion.Minor() > releaseSeries.Minor) {
   157  			continue
   158  		}
   159  		contractsForUpgrade.Insert(releaseSeries.Contract)
   160  	}
   161  
   162  	return sets.List(contractsForUpgrade)
   163  }
   164  
   165  // getLatestNextVersion returns the next available version for a provider within the target API Version of Cluster API (contract).
   166  // the next available version is the latest version available in the for the target contract version.
   167  func (i *upgradeInfo) getLatestNextVersion(contract string) *version.Version {
   168  	var latestNextVersion *version.Version
   169  	for _, releaseSeries := range i.metadata.ReleaseSeries {
   170  		// Skip the release series if not linked with the target contract version
   171  		if releaseSeries.Contract != contract {
   172  			continue
   173  		}
   174  
   175  		for j := range i.nextVersions {
   176  			nextVersion := &i.nextVersions[j]
   177  
   178  			// Drop the nextVersion version if not linked with the current
   179  			// release series or if it is a pre-release.
   180  			if nextVersion.Major() != releaseSeries.Major ||
   181  				nextVersion.Minor() != releaseSeries.Minor ||
   182  				nextVersion.PreRelease() != "" {
   183  				continue
   184  			}
   185  
   186  			// Drop the nextVersion if older that the latestNextVersion selected so far
   187  			if latestNextVersion == nil || latestNextVersion.LessThan(nextVersion) {
   188  				latestNextVersion = nextVersion
   189  			}
   190  		}
   191  	}
   192  
   193  	return latestNextVersion
   194  }
   195  
   196  // versionTag converts a version to a RepositoryTag.
   197  func versionTag(version *version.Version) string {
   198  	if version == nil {
   199  		return ""
   200  	}
   201  
   202  	return fmt.Sprintf("v%s", version.String())
   203  }