-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathrepository_versions.go
136 lines (114 loc) · 4.56 KB
/
repository_versions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package repository
import (
"context"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/version"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
)
const (
latestVersionTag = "latest"
)
// latestContractRelease returns the latest patch release for a repository for the current API contract, according to
// semantic version order of the release tag name.
func latestContractRelease(ctx context.Context, repo Repository, contract string) (string, error) {
latest, err := latestRelease(ctx, repo)
if err != nil {
return latest, err
}
// Attempt to check if the latest release satisfies the API Contract
// This is a best-effort attempt to find the latest release for an older API contract if it's not the latest release.
file, err := repo.GetFile(ctx, latest, metadataFile)
// If an error occurs, we just return the latest release.
if err != nil {
if errors.Is(err, errNotFound) {
// If it was ErrNotFound, then there is no release yet for the resolved tag.
// Ref: https://github.com/kubernetes-sigs/cluster-api/issues/7889
return "", err
}
// if we can't get the metadata file from the release, we return latest.
return latest, nil
}
latestMetadata := &clusterctlv1.Metadata{}
codecFactory := serializer.NewCodecFactory(scheme.Scheme)
if err := runtime.DecodeInto(codecFactory.UniversalDecoder(), file, latestMetadata); err != nil {
return latest, nil //nolint:nilerr
}
releaseSeries := latestMetadata.GetReleaseSeriesForContract(contract)
if releaseSeries == nil {
return latest, nil
}
sv, err := version.ParseSemantic(latest)
if err != nil {
return latest, nil //nolint:nilerr
}
// If the Major or Minor version of the latest release doesn't match the release series for the current contract,
// return the latest patch release of the desired Major/Minor version.
if sv.Major() != releaseSeries.Major || sv.Minor() != releaseSeries.Minor {
return latestPatchRelease(ctx, repo, &releaseSeries.Major, &releaseSeries.Minor)
}
return latest, nil
}
// latestRelease returns the latest release for a repository, according to
// semantic version order of the release tag name.
func latestRelease(ctx context.Context, repo Repository) (string, error) {
return latestPatchRelease(ctx, repo, nil, nil)
}
// latestPatchRelease returns the latest patch release for a given Major and Minor version.
func latestPatchRelease(ctx context.Context, repo Repository, major, minor *uint) (string, error) {
versions, err := repo.GetVersions(ctx)
if err != nil {
return "", errors.Wrapf(err, "failed to get repository versions")
}
// Search for the latest release according to semantic version ordering.
// Releases with tag name that are not in semver format are ignored.
var latestTag string
var latestPrereleaseTag string
var latestReleaseVersion *version.Version
var latestPrereleaseVersion *version.Version
for _, v := range versions {
sv, err := version.ParseSemantic(v)
if err != nil {
// discard releases with tags that are not a valid semantic versions (the user can point explicitly to such releases)
continue
}
if (major != nil && sv.Major() != *major) || (minor != nil && sv.Minor() != *minor) {
// skip versions that don't match the desired Major.Minor version.
continue
}
// track prereleases separately
if sv.PreRelease() != "" {
if latestPrereleaseVersion == nil || latestPrereleaseVersion.LessThan(sv) {
latestPrereleaseTag = v
latestPrereleaseVersion = sv
}
continue
}
if latestReleaseVersion == nil || latestReleaseVersion.LessThan(sv) {
latestTag = v
latestReleaseVersion = sv
}
}
// Fall back to returning latest prereleases if no release has been cut or bail if it's also empty
if latestTag == "" {
if latestPrereleaseTag == "" {
return "", errors.New("failed to find releases tagged with a valid semantic version number")
}
return latestPrereleaseTag, nil
}
return latestTag, nil
}