@@ -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
1918func (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