@@ -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,83 @@ 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+ func init () {
125+ template .GetTemplateRegistry ().Register (& Factory {})
126+ }
127+
128+ // channel "archetypes", restricted in this iteration to just these
129+ type channelArchetype string
130+
131+ const (
132+ candidateChannelArchetype channelArchetype = "candidate"
133+ fastChannelArchetype channelArchetype = "fast"
134+ stableChannelArchetype channelArchetype = "stable"
135+ )
136+
137+ // mapping channel name --> stability, where higher values indicate greater stability
138+ var channelPriorities = map [channelArchetype ]int {candidateChannelArchetype : 0 , fastChannelArchetype : 1 , stableChannelArchetype : 2 }
139+
140+ // sorting capability for a slice according to the assigned channelPriorities
141+ type byChannelPriority []channelArchetype
142+
143+ func (b byChannelPriority ) Len () int { return len (b ) }
144+ func (b byChannelPriority ) Less (i , j int ) bool {
145+ return channelPriorities [b [i ]] < channelPriorities [b [j ]]
146+ }
147+ func (b byChannelPriority ) Swap (i , j int ) { b [i ], b [j ] = b [j ], b [i ] }
148+
149+ type streamType string
150+
151+ const defaultStreamType streamType = ""
152+ const minorStreamType streamType = "minor"
153+ const majorStreamType streamType = "major"
154+
155+ // general preference for minor channels
156+ var streamTypePriorities = map [streamType ]int {minorStreamType : 2 , majorStreamType : 1 , defaultStreamType : 0 }
157+
158+ // map of archetypes --> bundles --> bundle-version from the input file
159+ type bundleVersions map [channelArchetype ]map [string ]semver.Version // e.g. srcv["stable"]["example-operator.v1.0.0"] = 1.0.0
160+
161+ // the "high-water channel" struct functions as a freely-rising indicator of the "most stable" channel head, so we can use that
162+ // later as the package's defaultChannel attribute
163+ type highwaterChannel struct {
164+ archetype channelArchetype
165+ kind streamType
166+ version semver.Version
167+ name string
168+ }
169+
170+ // entryTuple represents a channel entry with its associated metadata
171+ type entryTuple struct {
172+ arch channelArchetype
173+ kind streamType
174+ parent string
175+ name string
176+ version semver.Version
177+ index int
178+ }
179+
180+ func buildBundleList (t SemverTemplateData ) map [string ]string {
62181 dict := make (map [string ]string )
63182 for _ , bl := range []semverTemplateChannelBundles {t .Candidate , t .Fast , t .Stable } {
64183 for _ , b := range bl .Bundles {
@@ -70,13 +189,13 @@ func buildBundleList(t semverTemplate) map[string]string {
70189 return dict
71190}
72191
73- func readFile (reader io.Reader ) (* semverTemplate , error ) {
192+ func readFile (reader io.Reader ) (* SemverTemplateData , error ) {
74193 data , err := io .ReadAll (reader )
75194 if err != nil {
76195 return nil , err
77196 }
78197
79- sv := semverTemplate {}
198+ sv := SemverTemplateData {}
80199 if err := yaml .UnmarshalStrict (data , & sv ); err != nil {
81200 return nil , err
82201 }
@@ -115,7 +234,7 @@ func readFile(reader io.Reader) (*semverTemplate, error) {
115234 return & sv , nil
116235}
117236
118- func (sv * semverTemplate ) getVersionsFromStandardChannels (cfg * declcfg.DeclarativeConfig , bundleDict map [string ]string ) (* bundleVersions , error ) {
237+ func (sv * SemverTemplateData ) getVersionsFromStandardChannels (cfg * declcfg.DeclarativeConfig , bundleDict map [string ]string ) (* bundleVersions , error ) {
119238 versions := bundleVersions {}
120239
121240 bdm , err := sv .getVersionsFromChannel (sv .Candidate .Bundles , bundleDict , cfg )
@@ -148,7 +267,7 @@ func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.Declarati
148267 return & versions , nil
149268}
150269
151- func (sv * semverTemplate ) getVersionsFromChannel (semverBundles []semverTemplateBundleEntry , bundleDict map [string ]string , cfg * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
270+ func (sv * SemverTemplateData ) getVersionsFromChannel (semverBundles []semverTemplateBundleEntry , bundleDict map [string ]string , cfg * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
152271 entries := make (map [string ]semver.Version )
153272
154273 // we iterate over the channel bundles from the template, to:
@@ -210,7 +329,7 @@ func (sv *semverTemplate) getVersionsFromChannel(semverBundles []semverTemplateB
210329// - 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.
211330// 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
212331
213- func (sv * semverTemplate ) generateChannels (semverChannels * bundleVersions ) []declcfg.Channel {
332+ func (sv * SemverTemplateData ) generateChannels (semverChannels * bundleVersions ) []declcfg.Channel {
214333 outChannels := []declcfg.Channel {}
215334
216335 // sort the channel archetypes in ascending order so we can traverse the bundles in order of
@@ -287,7 +406,7 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
287406 return outChannels
288407}
289408
290- func (sv * semverTemplate ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , entries []entryTuple ) []declcfg.Channel {
409+ func (sv * SemverTemplateData ) linkChannels (unlinkedChannels map [string ]* declcfg.Channel , entries []entryTuple ) []declcfg.Channel {
291410 channels := []declcfg.Channel {}
292411
293412 // sort to force partitioning by archetype --> kind --> semver
0 commit comments