-
Notifications
You must be signed in to change notification settings - Fork 4
/
tag_list.go
356 lines (299 loc) · 10.9 KB
/
tag_list.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package versioneer
import (
"errors"
"fmt"
"regexp"
"sort"
"strings"
"golang.org/x/mod/semver"
)
type TagList struct {
Tags []string
Artifact *Artifact
RegistryAndOrg string
}
// implements sort.Interface for TagList
func (tl TagList) Len() int { return len(tl.Tags) }
func (tl TagList) Swap(i, j int) { tl.Tags[i], tl.Tags[j] = tl.Tags[j], tl.Tags[i] }
func (tl TagList) Less(i, j int) bool {
iVersions := extractVersions(tl.Tags[i], *tl.Artifact)
jVersions := extractVersions(tl.Tags[j], *tl.Artifact)
iLen := len(iVersions)
jLen := len(jVersions)
// Drop to alphabetical order if no versions are found
if iLen == 0 || jLen == 0 {
return tl.Tags[i] < tl.Tags[j]
}
versionResult := semver.Compare(iVersions[0], jVersions[0])
// Versions are not equal. No need to check software version.
if versionResult != 0 {
return versionResult == -1 // sort lower version first
}
// Versions are equal.
// If there are software versions compare, otherwise return the one with
// no software version as lower.
if iLen == 2 && jLen == 2 {
return semver.Compare(iVersions[1], jVersions[1]) == -1
}
// The one with no software version is lower
return iLen < jLen
}
// Images returns only tags that represent images, skipping tags representing:
// - sbom
// - att
// - sig
// - -img
func (tl TagList) Images() TagList {
pattern := `.*-(core|standard)-(amd64|arm64)-.*-v.*`
regexpObject := regexp.MustCompile(pattern)
newTags := []string{}
for _, t := range tl.Tags {
// We have to filter "-img" tags outside the regexp because golang regexp doesn't support negative lookaheads.
if regexpObject.MatchString(t) && !strings.HasSuffix(t, "-img") {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
// OtherVersions returns tags that match all fields of the given Artifact,
// except the Version. Should be used to return other possible versions for the same
// Kairos image (e.g. that one could upgrade to).
// This method returns all versions, not only newer ones. Use NewerVersions to
// fetch only versions, newer than the one of the Artifact.
func (tl TagList) OtherVersions() TagList {
sVersionForTag := tl.Artifact.SoftwareVersionForTag()
newTags := []string{}
for _, t := range tl.Images().Tags {
versions := extractVersions(t, *tl.Artifact)
if len(versions) > 0 && versions[0] != tl.Artifact.Version {
if len(versions) > 1 && versions[1] != sVersionForTag {
continue
}
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
// NewerVersions returns OtherVersions filtered to only include tags with
// Version higher than the given artifact's.
func (tl TagList) NewerVersions() TagList {
tags := tl.OtherVersions()
return tags.newerVersions()
}
// OtherSoftwareVersions returns tags that match all fields of the given Artifact,
// except the SoftwareVersion. Should be used to return other possible software versions
// for the same Kairos image (e.g. that one could upgrade to).
// This method returns all versions, not only newer ones. Use NewerSofwareVersions to
// fetch only versions, newer than the one of the Artifact.
func (tl TagList) OtherSoftwareVersions() TagList {
versionForTag := tl.Artifact.VersionForTag()
softwareVersionForTag := tl.Artifact.SoftwareVersionForTag()
newTags := []string{}
for _, t := range tl.Images().Tags {
versions := extractVersions(t, *tl.Artifact)
if len(versions) > 1 && versions[1] != softwareVersionForTag && versions[0] == versionForTag {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
// NewerSofwareVersions returns OtherSoftwareVersions filtered to only include tags with
// SoftwareVersion higher than the given artifact's.
func (tl TagList) NewerSofwareVersions() TagList {
tags := tl.OtherSoftwareVersions()
return tags.newerSoftwareVersions()
}
// OtherAnyVersion returns tags that match all fields of the given Artifact,
// except the SoftwareVersion and/or Version.
// Should be used to return tags with newer versions (Kairos or "software")
// that one could upgrade to.
// This method returns all versions, not only newer ones. Use NewerAnyVersion to
// fetch only versions, newer than the one of the Artifact.
func (tl TagList) OtherAnyVersion() TagList {
versionForTag := tl.Artifact.VersionForTag()
sVersionForTag := tl.Artifact.SoftwareVersionForTag()
newTags := []string{}
for _, t := range tl.Images().Tags {
versions := extractVersions(t, *tl.Artifact)
versionDiffers := len(versions) > 0 && versions[0] != versionForTag
sVersionDiffers := len(versions) > 1 && versions[1] != sVersionForTag
if versionDiffers || sVersionDiffers {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
// NewerAnyVersion returns tags with:
// - a kairos version newer than the given artifact's
// - a kairos version same as the given artifacts but a software version higher
// than the current artifact's
//
// Splitting the 2 versions is done using the artifact's SoftwareVersionPrefix
// (first encountered, because our tags have a "k3s1" in the end too)
func (tl TagList) NewerAnyVersion() TagList {
if tl.Artifact.SoftwareVersion != "" {
return tl.Images().newerSomeVersions()
} else {
return tl.Images().newerVersions()
}
}
func (tl TagList) Print() {
for _, t := range tl.Tags {
fmt.Println(t)
}
}
// FullImages returns a slice of strings which has the tags converts to full
// image URLs (not just tags).
func (tl TagList) FullImages() ([]string, error) {
result := []string{}
if tl.Artifact == nil {
return result, errors.New("no artifact defined")
}
repo := tl.Artifact.Repository(tl.RegistryAndOrg)
for _, t := range tl.Tags {
result = append(result, fmt.Sprintf("%s:%s", repo, t))
}
return result, nil
}
func (tl TagList) PrintImages() {
fullImages, err := tl.FullImages()
if err != nil {
fmt.Printf("warn: %s\n", err.Error())
}
for _, t := range fullImages {
fmt.Println(t)
}
}
// Sorted returns the TagList sorted by semver.
// This means lower versions come first.
func (tl TagList) Sorted() TagList {
newTagList := newTagListWithTags(tl, tl.Tags)
sort.Sort(newTagList)
return newTagList
}
// RSorted returns the TagList in the reverse order of Sorted
// This means higher versions come first.
func (tl TagList) RSorted() TagList {
newTagList := newTagListWithTags(tl, tl.Tags)
sort.Sort(sort.Reverse(newTagList))
return newTagList
}
func (tl TagList) newerVersions() TagList {
newTags := []string{}
for _, t := range tl.Tags {
versions := extractVersions(t, *tl.Artifact)
if len(versions) > 0 && semver.Compare(versions[0], tl.Artifact.VersionForTag()) == +1 {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
func (tl TagList) newerSoftwareVersions() TagList {
newTags := []string{}
for _, t := range tl.Tags {
versions := extractVersions(t, *tl.Artifact)
if len(versions) > 1 && semver.Compare(versions[1], tl.Artifact.SoftwareVersionForTag()) == +1 {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
func (tl TagList) newerSomeVersions() TagList {
newTags := []string{}
for _, t := range tl.Tags {
versions := extractVersions(t, *tl.Artifact)
if len(versions) < 1 {
continue
}
versionResult := semver.Compare(versions[0], tl.Artifact.VersionForTag())
sVersionResult := semver.Compare(versions[1], tl.Artifact.SoftwareVersionForTag())
// If kairos version is higher add it (no matter what the sversion is)
if versionResult > 0 {
newTags = append(newTags, t)
}
// if kairos version is the same, require the sversion to be higher
if versionResult == 0 && sVersionResult > 0 {
newTags = append(newTags, t)
}
}
return newTagListWithTags(tl, newTags)
}
// NoPrereleases returns only tags in which Version is not a pre-release (as defined by semver).
// NOTE: We only filter out Kairos prereleases because the k3s version is not
// semver anyway. The upstream version is something like: v1.28.3+k3s2
// The first part is semver and it's the Kubernetes version and the "+k3s2"
// part is the k3s version which has changes over "k3s1" (it's not just a new build
// of the same thing)(https://github.com/k3s-io/k3s/releases/tag/v1.28.3%2Bk3s2)
// To make things more complicated, when we create a container image tag, we
// convert "+" to "-" because tags don't allow "+" symbols. This makes every
// k3s version look like a pre-release according to semver.
func (tl TagList) NoPrereleases() TagList {
newTags := []string{}
for _, t := range tl.Tags {
versions := extractVersions(t, *tl.Artifact)
noVersionsFound := len(versions) == 0
lessVersionsFound := tl.Artifact.SoftwareVersion != "" && len(versions) < 2
if noVersionsFound || lessVersionsFound {
continue
}
versionIsPrerelease := semver.IsValid(versions[0]) && semver.Prerelease(versions[0]) != ""
if versionIsPrerelease {
continue
}
newTags = append(newTags, t)
}
return newTagListWithTags(tl, newTags)
}
// extractVersions extracts extractVersions from a given tag, based on the given Artifact
// E.g. for an artifact like:
// leap-15.5-core-amd64-generic-v2.4.2-rc1-k3sv1.28.3-k3s1
// given a tagToCheck like:
// leap-15.5-core-amd64-generic-v2.4.3-k3sv1.28.6-k3s1
// it should return:
// []string{"v2.4.3", "v1.28.6-k3s1"}
//
// Or, for an artifact like:
// leap-15.5-core-amd64-generic-v2.4.2
// given a tagToCheck like:
// leap-15.5-core-amd64-generic-v2.4.3
// it should return:
// []string{"v2.4.3"}
//
// - check if there are 2 extractVersions in the tag and return both
// - if there is only one, return that (Version)
// - otherwise return no version
func extractVersions(tagToCheck string, artifact Artifact) []string {
tag, err := artifact.Tag()
if err != nil {
panic(fmt.Errorf("invalid artifact passed: %w", err))
}
// Remove all version information
cleanupPattern := fmt.Sprintf("-%s.*", artifact.Version)
re := regexp.MustCompile(cleanupPattern)
strippedTag := re.ReplaceAllString(tag, "")
if artifact.SoftwareVersionPrefix != "" { // If we know how to split the versions
// Construct a regexp for both versions and check if there is match
pattern := fmt.Sprintf("%s-(.+?)-%s(.+)", regexp.QuoteMeta(strippedTag), artifact.SoftwareVersionPrefix)
regexpObj := regexp.MustCompile(pattern)
matches := regexpObj.FindStringSubmatch(tagToCheck)
if len(matches) == 3 {
return matches[1:]
}
}
// Construct a regexp for one version and check if there is a match
pattern := fmt.Sprintf("%s-(.+)", regexp.QuoteMeta(strippedTag))
regexpObj := regexp.MustCompile(pattern)
matches := regexpObj.FindStringSubmatch(tagToCheck)
if len(matches) == 2 {
subSlice := make([]string, 1)
copy(subSlice, matches[1:])
return subSlice
}
// No version found
return []string{}
}
// newTagListWithTags returns a copy of the given TagList with same Artifact
// and RegistryAndOrg fields but with the given tags as Tags.
func newTagListWithTags(tl TagList, tags []string) TagList {
return TagList{Artifact: tl.Artifact, RegistryAndOrg: tl.RegistryAndOrg, Tags: tags}
}