Skip to content

Commit 2096c4f

Browse files
authored
Semver lastedge error (#1169)
* when the last edge in a bundle set is +Y the existing code does not clear the skips list and crosses Y thresholds counter to design intent missing proper last-max-z detection for replaces working at long last * re-add cumulative skips over y changes in an x stream so folks can always skip directly to channel head Signed-off-by: Jordan Keister <[email protected]> --------- Signed-off-by: Jordan Keister <[email protected]>
1 parent a21f962 commit 2096c4f

File tree

3 files changed

+233
-119
lines changed

3 files changed

+233
-119
lines changed

alpha/template/semver/semver.go

Lines changed: 64 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import (
66
"io"
77
"sort"
88

9-
"github.com/blang/semver/v4"
10-
"k8s.io/apimachinery/pkg/util/errors"
11-
"k8s.io/apimachinery/pkg/util/sets"
12-
"sigs.k8s.io/yaml"
13-
149
"github.com/operator-framework/operator-registry/alpha/action"
1510
"github.com/operator-framework/operator-registry/alpha/declcfg"
1611
"github.com/operator-framework/operator-registry/alpha/property"
12+
13+
"github.com/blang/semver/v4"
14+
"k8s.io/apimachinery/pkg/util/errors"
15+
"sigs.k8s.io/yaml"
1716
)
1817

1918
func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) {
@@ -224,7 +223,6 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
224223
hwc := highwaterChannel{archetype: archetypesByPriority[0], version: semver.Version{Major: 0, Minor: 0}}
225224

226225
unlinkedChannels := make(map[string]*declcfg.Channel)
227-
unassociatedEdges := []entryTuple{}
228226

229227
for _, archetype := range archetypesByPriority {
230228
bundles := (*semverChannels)[archetype]
@@ -272,88 +270,84 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
272270
}
273271
}
274272
ch.Entries = append(ch.Entries, declcfg.ChannelEntry{Name: bundleName})
275-
unassociatedEdges = append(unassociatedEdges, entryTuple{arch: archetype, kind: cKey, parent: cName, name: bundleName, version: bundles[bundleName], index: len(ch.Entries) - 1})
276273
}
277274
}
278275
}
279276

280277
// save off the name of the high-water-mark channel for the default for this package
281278
sv.defaultChannel = hwc.name
282279

283-
outChannels = append(outChannels, sv.linkChannels(unlinkedChannels, unassociatedEdges)...)
280+
outChannels = append(outChannels, sv.linkChannels(unlinkedChannels, semverChannels)...)
284281

285282
return outChannels
286283
}
287284

288-
func (sv *semverTemplate) linkChannels(unlinkedChannels map[string]*declcfg.Channel, entries []entryTuple) []declcfg.Channel {
285+
func (sv *semverTemplate) linkChannels(unlinkedChannels map[string]*declcfg.Channel, harvestedVersions *bundleVersions) []declcfg.Channel {
289286
channels := []declcfg.Channel{}
290287

291-
// sort to force partitioning by archetype --> kind --> semver
292-
sort.Slice(entries, func(i, j int) bool {
293-
if channelPriorities[entries[i].arch] != channelPriorities[entries[j].arch] {
294-
return channelPriorities[entries[i].arch] < channelPriorities[entries[j].arch]
295-
}
296-
if streamTypePriorities[entries[i].kind] != streamTypePriorities[entries[j].kind] {
297-
return streamTypePriorities[entries[i].kind] < streamTypePriorities[entries[j].kind]
298-
}
299-
return entries[i].version.LT(entries[j].version)
300-
})
301-
302-
prevZMax := ""
303-
var curSkips sets.String = sets.NewString()
304-
305-
for index := 1; index < len(entries); index++ {
306-
prevTuple := entries[index-1]
307-
curTuple := entries[index]
308-
prevX := getMajorVersion(prevTuple.version)
309-
prevY := getMinorVersion(prevTuple.version)
310-
curX := getMajorVersion(curTuple.version)
311-
curY := getMinorVersion(curTuple.version)
312-
313-
archChange := curTuple.arch != prevTuple.arch
314-
kindChange := curTuple.kind != prevTuple.kind
315-
xChange := !prevX.EQ(curX)
316-
yChange := !prevY.EQ(curY)
317-
318-
if archChange || kindChange || xChange || yChange {
319-
// if we passed any kind of change besides Z, then we need to set skips/replaces for previous max-Z
320-
prevChannel := unlinkedChannels[prevTuple.parent]
321-
finalEntry := &prevChannel.Entries[prevTuple.index]
322-
finalEntry.Replaces = prevZMax
323-
// don't include replaces in skips list, but they are accumulated in discrete cycles (and maybe useful for later channels) so remove here
324-
if curSkips.Has(finalEntry.Replaces) {
325-
finalEntry.Skips = curSkips.Difference(sets.NewString(finalEntry.Replaces)).List()
326-
} else {
327-
finalEntry.Skips = curSkips.List()
328-
}
329-
}
330-
331-
if archChange || kindChange || xChange {
332-
// we don't maintain skips/replaces over these transitions
333-
curSkips = sets.NewString()
334-
prevZMax = ""
335-
} else {
336-
if yChange {
337-
prevZMax = prevTuple.name
288+
// bundle --> version lookup
289+
bundleVersions := make(map[string]semver.Version)
290+
for _, vs := range *harvestedVersions {
291+
for b, v := range vs {
292+
if _, ok := bundleVersions[b]; !ok {
293+
bundleVersions[b] = v
338294
}
339-
curSkips.Insert(prevTuple.name)
340295
}
341296
}
342297

343-
// last entry accumulation
344-
lastTuple := entries[len(entries)-1]
345-
prevChannel := unlinkedChannels[lastTuple.parent]
346-
finalEntry := &prevChannel.Entries[lastTuple.index]
347-
finalEntry.Replaces = prevZMax
348-
// don't include replaces in skips list, but they are accumulated in discrete cycles (and maybe useful for later channels) so remove here
349-
if curSkips.Has(finalEntry.Replaces) {
350-
finalEntry.Skips = curSkips.Difference(sets.NewString(finalEntry.Replaces)).List()
351-
} else {
352-
finalEntry.Skips = curSkips.List()
353-
}
298+
for _, channel := range unlinkedChannels {
299+
entries := &channel.Entries
300+
sort.Slice(*entries, func(i, j int) bool {
301+
return bundleVersions[(*entries)[i].Name].LT(bundleVersions[(*entries)[j].Name])
302+
})
354303

355-
for _, ch := range unlinkedChannels {
356-
channels = append(channels, *ch)
304+
// "inchworm" through the sorted entries, iterating curEdge but extending yProbe to the next Y-transition
305+
// then catch up curEdge to yProbe as 'skips', and repeat until we reach the end of the entries
306+
// finally, because the inchworm will always fail to pick up the last Y-transition, we test for it and link it up as a 'replaces'
307+
curEdge, yProbe := 0, 0
308+
zmaxQueue := ""
309+
entryCount := len(*entries)
310+
311+
for curEdge < entryCount {
312+
for yProbe < entryCount {
313+
curVersion := bundleVersions[(*entries)[curEdge].Name]
314+
yProbeVersion := bundleVersions[(*entries)[yProbe].Name]
315+
if getMinorVersion(yProbeVersion).EQ(getMinorVersion(curVersion)) {
316+
yProbe += 1
317+
} else {
318+
break
319+
}
320+
}
321+
// if yProbe crossed a threshold, the previous entry is the last of the previous Y-stream
322+
preChangeIndex := yProbe - 1
323+
324+
if curEdge != yProbe {
325+
if zmaxQueue != "" {
326+
// add skips edge to allow skipping over y iterations within an x stream
327+
(*entries)[preChangeIndex].Skips = append((*entries)[preChangeIndex].Skips, zmaxQueue)
328+
(*entries)[preChangeIndex].Replaces = zmaxQueue
329+
}
330+
zmaxQueue = (*entries)[preChangeIndex].Name
331+
}
332+
for curEdge < preChangeIndex {
333+
// add skips edges to y-1 from z < y
334+
(*entries)[preChangeIndex].Skips = append((*entries)[preChangeIndex].Skips, (*entries)[curEdge].Name)
335+
curEdge += 1
336+
}
337+
curEdge += 1
338+
yProbe = curEdge + 1
339+
}
340+
// since probe will always fail to pick up a y-change in the last item, test for it
341+
if entryCount > 1 {
342+
penultimateEntry := &(*entries)[len(*entries)-2]
343+
ultimateEntry := &(*entries)[len(*entries)-1]
344+
penultimateVersion := bundleVersions[penultimateEntry.Name]
345+
ultimateVersion := bundleVersions[ultimateEntry.Name]
346+
if ultimateVersion.Minor != penultimateVersion.Minor {
347+
ultimateEntry.Replaces = penultimateEntry.Name
348+
}
349+
}
350+
channels = append(channels, *channel)
357351
}
358352

359353
return channels

0 commit comments

Comments
 (0)