@@ -15,12 +15,55 @@ import (
1515
1616 "github.com/operator-framework/operator-registry/alpha/declcfg"
1717 "github.com/operator-framework/operator-registry/alpha/property"
18+ "github.com/operator-framework/operator-registry/alpha/template"
1819)
1920
20- func (t Template ) Render (ctx context.Context ) (* declcfg.DeclarativeConfig , error ) {
21+ // IO structs -- BEGIN
22+ type semverTemplateBundleEntry struct {
23+ Image string `json:"image,omitempty"`
24+ }
25+
26+ type semverTemplateChannelBundles struct {
27+ Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"`
28+ }
29+
30+ type SemverTemplateData struct {
31+ Schema string `json:"schema"`
32+ GenerateMajorChannels bool `json:"generateMajorChannels,omitempty"`
33+ GenerateMinorChannels bool `json:"generateMinorChannels,omitempty"`
34+ DefaultChannelTypePreference streamType `json:"defaultChannelTypePreference,omitempty"`
35+ Candidate semverTemplateChannelBundles `json:"candidate,omitempty"`
36+ Fast semverTemplateChannelBundles `json:"fast,omitempty"`
37+ Stable semverTemplateChannelBundles `json:"stable,omitempty"`
38+
39+ pkg string `json:"-"` // the derived package name
40+ defaultChannel string `json:"-"` // detected "most stable" channel head
41+ }
42+
43+ // IO structs -- END
44+
45+ // SemverTemplate implements the common template interface
46+ type SemverTemplate struct {
47+ renderBundle template.BundleRenderer
48+ }
49+
50+ // NewTemplate creates a new semver template instance
51+ func NewTemplate (renderBundle template.BundleRenderer ) template.Template {
52+ return & SemverTemplate {
53+ renderBundle : renderBundle ,
54+ }
55+ }
56+
57+ // RenderBundle implements the template.Template interface
58+ func (t * SemverTemplate ) RenderBundle (ctx context.Context , image string ) (* declcfg.DeclarativeConfig , error ) {
59+ return t .renderBundle (ctx , image )
60+ }
61+
62+ // Render implements the template.Template interface
63+ func (t * SemverTemplate ) Render (ctx context.Context , reader io.Reader ) (* declcfg.DeclarativeConfig , error ) {
2164 var out declcfg.DeclarativeConfig
2265
23- sv , err := readFile (t . Data )
66+ sv , err := readFile (reader )
2467 if err != nil {
2568 return nil , fmt .Errorf ("render: unable to read file: %v" , err )
2669 }
@@ -58,7 +101,79 @@ func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error
58101 return & out , nil
59102}
60103
61- func buildBundleList (t semverTemplate ) map [string ]string {
104+ // Schema implements the template.Template interface
105+ func (t * SemverTemplate ) Schema () string {
106+ return schema
107+ }
108+
109+ // Factory implements the template.TemplateFactory interface
110+ type Factory struct {}
111+
112+ // CreateTemplate implements the template.TemplateFactory interface
113+ func (f * Factory ) CreateTemplate (renderBundle template.BundleRenderer ) template.Template {
114+ return NewTemplate (renderBundle )
115+ }
116+
117+ // Schema implements the template.TemplateFactory interface
118+ func (f * Factory ) Schema () string {
119+ return schema
120+ }
121+
122+ const schema string = "olm.semver"
123+
124+ // channel "archetypes", restricted in this iteration to just these
125+ type channelArchetype string
126+
127+ const (
128+ candidateChannelArchetype channelArchetype = "candidate"
129+ fastChannelArchetype channelArchetype = "fast"
130+ stableChannelArchetype channelArchetype = "stable"
131+ )
132+
133+ // mapping channel name --> stability, where higher values indicate greater stability
134+ var channelPriorities = map [channelArchetype ]int {candidateChannelArchetype : 0 , fastChannelArchetype : 1 , stableChannelArchetype : 2 }
135+
136+ // sorting capability for a slice according to the assigned channelPriorities
137+ type byChannelPriority []channelArchetype
138+
139+ func (b byChannelPriority ) Len () int { return len (b ) }
140+ func (b byChannelPriority ) Less (i , j int ) bool {
141+ return channelPriorities [b [i ]] < channelPriorities [b [j ]]
142+ }
143+ func (b byChannelPriority ) Swap (i , j int ) { b [i ], b [j ] = b [j ], b [i ] }
144+
145+ type streamType string
146+
147+ const defaultStreamType streamType = ""
148+ const minorStreamType streamType = "minor"
149+ const majorStreamType streamType = "major"
150+
151+ // general preference for minor channels
152+ var streamTypePriorities = map [streamType ]int {minorStreamType : 2 , majorStreamType : 1 , defaultStreamType : 0 }
153+
154+ // map of archetypes --> bundles --> bundle-version from the input file
155+ type bundleVersions map [channelArchetype ]map [string ]semver.Version // e.g. srcv["stable"]["example-operator.v1.0.0"] = 1.0.0
156+
157+ // the "high-water channel" struct functions as a freely-rising indicator of the "most stable" channel head, so we can use that
158+ // later as the package's defaultChannel attribute
159+ type highwaterChannel struct {
160+ archetype channelArchetype
161+ kind streamType
162+ version semver.Version
163+ name string
164+ }
165+
166+ // entryTuple represents a channel entry with its associated metadata
167+ type entryTuple struct {
168+ arch channelArchetype
169+ kind streamType
170+ parent string
171+ name string
172+ version semver.Version
173+ index int
174+ }
175+
176+ func buildBundleList (t SemverTemplateData ) map [string ]string {
62177 dict := make (map [string ]string )
63178 for _ , bl := range []semverTemplateChannelBundles {t .Candidate , t .Fast , t .Stable } {
64179 for _ , b := range bl .Bundles {
@@ -70,13 +185,13 @@ func buildBundleList(t semverTemplate) map[string]string {
70185 return dict
71186}
72187
73- func readFile (reader io.Reader ) (* semverTemplate , error ) {
188+ func readFile (reader io.Reader ) (* SemverTemplateData , error ) {
74189 data , err := io .ReadAll (reader )
75190 if err != nil {
76191 return nil , err
77192 }
78193
79- sv := semverTemplate {}
194+ sv := SemverTemplateData {}
80195 if err := yaml .UnmarshalStrict (data , & sv ); err != nil {
81196 return nil , err
82197 }
@@ -115,7 +230,7 @@ func readFile(reader io.Reader) (*semverTemplate, error) {
115230 return & sv , nil
116231}
117232
118- func (sv * semverTemplate ) getVersionsFromStandardChannels (cfg * declcfg.DeclarativeConfig , bundleDict map [string ]string ) (* bundleVersions , error ) {
233+ func (sv * SemverTemplateData ) getVersionsFromStandardChannels (cfg * declcfg.DeclarativeConfig , bundleDict map [string ]string ) (* bundleVersions , error ) {
119234 versions := bundleVersions {}
120235
121236 bdm , err := sv .getVersionsFromChannel (sv .Candidate .Bundles , bundleDict , cfg )
@@ -148,7 +263,7 @@ func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.Declarati
148263 return & versions , nil
149264}
150265
151- func (sv * semverTemplate ) getVersionsFromChannel (semverBundles []semverTemplateBundleEntry , bundleDict map [string ]string , cfg * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
266+ func (sv * SemverTemplateData ) getVersionsFromChannel (semverBundles []semverTemplateBundleEntry , bundleDict map [string ]string , cfg * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
152267 entries := make (map [string ]semver.Version )
153268
154269 // we iterate over the channel bundles from the template, to:
@@ -210,7 +325,7 @@ func (sv *semverTemplate) getVersionsFromChannel(semverBundles []semverTemplateB
210325// - within the same minor version (Y-stream), the head of the channel should have a 'skips' encompassing all lesser Y.Z versions of the bundle enumerated in the template.
211326// along the way, uses a highwaterChannel marker to identify the "most stable" channel head to be used as the default channel for the generated package
212327
213- func (sv * semverTemplate ) generateChannels (semverChannels * bundleVersions ) []declcfg.Channel {
328+ func (sv * SemverTemplateData ) generateChannels (semverChannels * bundleVersions ) []declcfg.Channel {
214329 outChannels := []declcfg.Channel {}
215330
216331 // sort the channel archetypes in ascending order so we can traverse the bundles in order of
@@ -287,7 +402,7 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
287402 return outChannels
288403}
289404
290- func (sv * semverTemplate ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , entries []entryTuple ) []declcfg.Channel {
405+ func (sv * SemverTemplateData ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , entries []entryTuple ) []declcfg.Channel {
291406 channels := []declcfg.Channel {}
292407
293408 // sort to force partitioning by archetype --> kind --> semver
0 commit comments