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 }