@@ -10,6 +10,7 @@ import (
1010
1111 "github.com/blang/semver/v4"
1212 "k8s.io/apimachinery/pkg/util/errors"
13+ "k8s.io/apimachinery/pkg/util/sets"
1314 "sigs.k8s.io/yaml"
1415
1516 "github.com/operator-framework/operator-registry/alpha/declcfg"
@@ -225,6 +226,7 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
225226 hwc := highwaterChannel {archetype : archetypesByPriority [0 ], version : semver.Version {Major : 0 , Minor : 0 }}
226227
227228 unlinkedChannels := make (map [string ]* declcfg.Channel )
229+ unassociatedEdges := []entryTuple {}
228230
229231 for _ , archetype := range archetypesByPriority {
230232 bundles := (* semverChannels )[archetype ]
@@ -272,83 +274,103 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
272274 }
273275 }
274276 ch .Entries = append (ch .Entries , declcfg.ChannelEntry {Name : bundleName })
277+ unassociatedEdges = append (unassociatedEdges , entryTuple {arch : archetype , kind : cKey , parent : cName , name : bundleName , version : bundles [bundleName ], index : len (ch .Entries ) - 1 })
275278 }
276279 }
277280 }
278281
279282 // save off the name of the high-water-mark channel for the default for this package
280283 sv .defaultChannel = hwc .name
281284
282- outChannels = append (outChannels , sv .linkChannels (unlinkedChannels , semverChannels )... )
285+ outChannels = append (outChannels , sv .linkChannels (unlinkedChannels , unassociatedEdges )... )
283286
284287 return outChannels
285288}
286289
287- func (sv * semverTemplate ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , harvestedVersions * bundleVersions ) []declcfg.Channel {
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
294- }
295- }
296- }
290+ func (sv * semverTemplate ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , entries []entryTuple ) []declcfg.Channel {
291+ channels := []declcfg.Channel {}
297292
298- channels := make ([]declcfg.Channel , 0 , len (unlinkedChannels ))
299- for _ , channel := range unlinkedChannels {
300- entries := & channel .Entries
301- sort .Slice (* entries , func (i , j int ) bool {
302- return bundleVersions [(* entries )[i ].Name ].LT (bundleVersions [(* entries )[j ].Name ])
303- })
293+ // sort to force partitioning by archetype --> kind --> semver
294+ sort .Slice (entries , func (i , j int ) bool {
295+ if channelPriorities [entries [i ].arch ] != channelPriorities [entries [j ].arch ] {
296+ return channelPriorities [entries [i ].arch ] < channelPriorities [entries [j ].arch ]
297+ }
298+ if streamTypePriorities [entries [i ].kind ] != streamTypePriorities [entries [j ].kind ] {
299+ return streamTypePriorities [entries [i ].kind ] < streamTypePriorities [entries [j ].kind ]
300+ }
301+ return entries [i ].version .LT (entries [j ].version )
302+ })
304303
305- // "inchworm" through the sorted entries, iterating curEdge but extending yProbe to the next Y-transition
306- // then catch up curEdge to yProbe as 'skips', and repeat until we reach the end of the entries
307- // 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'
308- curEdge , yProbe := 0 , 0
309- zmaxQueue := ""
310- entryCount := len (* entries )
311-
312- for curEdge < entryCount {
313- for yProbe < entryCount {
314- curVersion := bundleVersions [(* entries )[curEdge ].Name ]
315- yProbeVersion := bundleVersions [(* entries )[yProbe ].Name ]
316- if getMinorVersion (yProbeVersion ).EQ (getMinorVersion (curVersion )) {
317- yProbe += 1
318- } else {
319- break
320- }
304+ prevZMax := ""
305+ var curSkips = sets.Set [string ]{}
306+
307+ // iterate over the entries, starting from the second
308+ // write any skips/replaces for the previous entry to the current entry
309+ // then accumulate the skips/replaces for the current entry to be used in subsequent iterations
310+ for index := 1 ; index < len (entries ); index ++ {
311+ prevTuple := entries [index - 1 ]
312+ curTuple := entries [index ]
313+ prevX := getMajorVersion (prevTuple .version )
314+ prevY := getMinorVersion (prevTuple .version )
315+ curX := getMajorVersion (curTuple .version )
316+ curY := getMinorVersion (curTuple .version )
317+
318+ archChange := curTuple .arch != prevTuple .arch
319+ kindChange := curTuple .kind != prevTuple .kind
320+ xChange := ! prevX .EQ (curX )
321+ yChange := ! prevY .EQ (curY )
322+
323+ if archChange || kindChange || xChange || yChange {
324+ // if we passed any kind of change besides Z, then we need to set skips/replaces for previous max-Z
325+ prevChannel := unlinkedChannels [prevTuple .parent ]
326+ finalEntry := & prevChannel .Entries [prevTuple .index ]
327+ finalEntry .Replaces = prevZMax
328+ skips := sets .List (curSkips .Difference (sets .New (finalEntry .Replaces )))
329+ if len (skips ) > 0 {
330+ finalEntry .Skips = skips
321331 }
322- // if yProbe crossed a threshold, the previous entry is the last of the previous Y-stream
323- preChangeIndex := yProbe - 1
332+ }
324333
325- if curEdge != yProbe {
326- if zmaxQueue != "" {
327- (* entries )[preChangeIndex ].Replaces = zmaxQueue
328- }
329- zmaxQueue = (* entries )[preChangeIndex ].Name
330- }
331- for curEdge < preChangeIndex {
332- // add skips edges to y-1 from z < y
333- if (* entries )[preChangeIndex ].Replaces != (* entries )[curEdge ].Name {
334- (* entries )[preChangeIndex ].Skips = append ((* entries )[preChangeIndex ].Skips , (* entries )[curEdge ].Name )
335- }
336- curEdge += 1
334+ if archChange || kindChange || xChange {
335+ // we don't maintain skips/replaces over these transitions
336+ curSkips = sets.Set [string ]{}
337+ prevZMax = ""
338+ } else {
339+ if yChange {
340+ prevZMax = prevTuple .name
337341 }
338- curEdge += 1
339- yProbe = curEdge + 1
342+ curSkips .Insert (prevTuple .name )
340343 }
341- // since probe will always fail to pick up a y-change in the last item, test for it
342- if entryCount > 1 {
343- penultimateEntry := & (* entries )[len (* entries )- 2 ]
344- ultimateEntry := & (* entries )[len (* entries )- 1 ]
345- penultimateVersion := bundleVersions [penultimateEntry .Name ]
346- ultimateVersion := bundleVersions [ultimateEntry .Name ]
347- if ultimateVersion .Minor != penultimateVersion .Minor {
348- ultimateEntry .Replaces = penultimateEntry .Name
344+ }
345+
346+ if len (entries ) > 1 {
347+ // add edges for the last entry
348+ // note: this is substantially similar to the main iteration, but there are some subtle differences since the main loop mode
349+ // design is to write the edges and then accumulate new info for subsequent edges (and this is the last edge):
350+ // - we only need to watch for arch/kind/x change
351+ // - we don't need to accumulate skips/replaces, since we're not writing edges for subsequent entries
352+ lastTuple := entries [len (entries )- 1 ]
353+ penultimateTuple := entries [len (entries )- 2 ]
354+ prevX := getMajorVersion (penultimateTuple .version )
355+ curX := getMajorVersion (lastTuple .version )
356+
357+ archChange := penultimateTuple .arch != lastTuple .arch
358+ kindChange := penultimateTuple .kind != lastTuple .kind
359+ xChange := ! prevX .EQ (curX )
360+ // for arch / kind / x changes, we don't maintain skips/replaces
361+ if ! archChange && ! kindChange && ! xChange {
362+ prevChannel := unlinkedChannels [lastTuple .parent ]
363+ finalEntry := & prevChannel .Entries [lastTuple .index ]
364+ finalEntry .Replaces = prevZMax
365+ skips := sets .List (curSkips .Difference (sets .New (finalEntry .Replaces )))
366+ if len (skips ) > 0 {
367+ finalEntry .Skips = skips
349368 }
350369 }
351- channels = append (channels , * channel )
370+ }
371+
372+ for _ , ch := range unlinkedChannels {
373+ channels = append (channels , * ch )
352374 }
353375
354376 slices .SortFunc (channels , func (a , b declcfg.Channel ) int {
0 commit comments