sigs.k8s.io/cluster-api@v1.6.3/cmd/clusterctl/client/repository/repository_versions.go (about) 1 /* 2 Copyright 2021 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 repository 18 19 import ( 20 "context" 21 22 "github.com/pkg/errors" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/runtime/serializer" 25 "k8s.io/apimachinery/pkg/util/version" 26 27 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 28 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme" 29 ) 30 31 const ( 32 latestVersionTag = "latest" 33 ) 34 35 // latestContractRelease returns the latest patch release for a repository for the current API contract, according to 36 // semantic version order of the release tag name. 37 func latestContractRelease(ctx context.Context, repo Repository, contract string) (string, error) { 38 latest, err := latestRelease(ctx, repo) 39 if err != nil { 40 return latest, err 41 } 42 // Attempt to check if the latest release satisfies the API Contract 43 // This is a best-effort attempt to find the latest release for an older API contract if it's not the latest release. 44 file, err := repo.GetFile(ctx, latest, metadataFile) 45 // If an error occurs, we just return the latest release. 46 if err != nil { 47 if errors.Is(err, errNotFound) { 48 // If it was ErrNotFound, then there is no release yet for the resolved tag. 49 // Ref: https://github.com/kubernetes-sigs/cluster-api/issues/7889 50 return "", err 51 } 52 // if we can't get the metadata file from the release, we return latest. 53 return latest, nil 54 } 55 latestMetadata := &clusterctlv1.Metadata{} 56 codecFactory := serializer.NewCodecFactory(scheme.Scheme) 57 if err := runtime.DecodeInto(codecFactory.UniversalDecoder(), file, latestMetadata); err != nil { 58 return latest, nil //nolint:nilerr 59 } 60 61 releaseSeries := latestMetadata.GetReleaseSeriesForContract(contract) 62 if releaseSeries == nil { 63 return latest, nil 64 } 65 66 sv, err := version.ParseSemantic(latest) 67 if err != nil { 68 return latest, nil //nolint:nilerr 69 } 70 71 // If the Major or Minor version of the latest release doesn't match the release series for the current contract, 72 // return the latest patch release of the desired Major/Minor version. 73 if sv.Major() != releaseSeries.Major || sv.Minor() != releaseSeries.Minor { 74 return latestPatchRelease(ctx, repo, &releaseSeries.Major, &releaseSeries.Minor) 75 } 76 return latest, nil 77 } 78 79 // latestRelease returns the latest release for a repository, according to 80 // semantic version order of the release tag name. 81 func latestRelease(ctx context.Context, repo Repository) (string, error) { 82 return latestPatchRelease(ctx, repo, nil, nil) 83 } 84 85 // latestPatchRelease returns the latest patch release for a given Major and Minor version. 86 func latestPatchRelease(ctx context.Context, repo Repository, major, minor *uint) (string, error) { 87 versions, err := repo.GetVersions(ctx) 88 if err != nil { 89 return "", errors.Wrapf(err, "failed to get repository versions") 90 } 91 92 // Search for the latest release according to semantic version ordering. 93 // Releases with tag name that are not in semver format are ignored. 94 var latestTag string 95 var latestPrereleaseTag string 96 97 var latestReleaseVersion *version.Version 98 var latestPrereleaseVersion *version.Version 99 100 for _, v := range versions { 101 sv, err := version.ParseSemantic(v) 102 if err != nil { 103 // discard releases with tags that are not a valid semantic versions (the user can point explicitly to such releases) 104 continue 105 } 106 107 if (major != nil && sv.Major() != *major) || (minor != nil && sv.Minor() != *minor) { 108 // skip versions that don't match the desired Major.Minor version. 109 continue 110 } 111 112 // track prereleases separately 113 if sv.PreRelease() != "" { 114 if latestPrereleaseVersion == nil || latestPrereleaseVersion.LessThan(sv) { 115 latestPrereleaseTag = v 116 latestPrereleaseVersion = sv 117 } 118 continue 119 } 120 121 if latestReleaseVersion == nil || latestReleaseVersion.LessThan(sv) { 122 latestTag = v 123 latestReleaseVersion = sv 124 } 125 } 126 127 // Fall back to returning latest prereleases if no release has been cut or bail if it's also empty 128 if latestTag == "" { 129 if latestPrereleaseTag == "" { 130 return "", errors.New("failed to find releases tagged with a valid semantic version number") 131 } 132 133 return latestPrereleaseTag, nil 134 } 135 return latestTag, nil 136 }