@@ -14,12 +14,55 @@ import (
1414
1515	"github.com/operator-framework/operator-registry/alpha/declcfg" 
1616	"github.com/operator-framework/operator-registry/alpha/property" 
17+ 	"github.com/operator-framework/operator-registry/alpha/template" 
1718)
1819
19- func  (t  Template ) Render (ctx  context.Context ) (* declcfg.DeclarativeConfig , error ) {
20+ // IO structs -- BEGIN 
21+ type  semverTemplateBundleEntry  struct  {
22+ 	Image  string  `json:"image,omitempty"` 
23+ }
24+ 
25+ type  semverTemplateChannelBundles  struct  {
26+ 	Bundles  []semverTemplateBundleEntry  `json:"bundles,omitempty"` 
27+ }
28+ 
29+ type  SemverTemplateData  struct  {
30+ 	Schema                        string                        `json:"schema"` 
31+ 	GenerateMajorChannels         bool                          `json:"generateMajorChannels,omitempty"` 
32+ 	GenerateMinorChannels         bool                          `json:"generateMinorChannels,omitempty"` 
33+ 	DefaultChannelTypePreference  streamType                    `json:"defaultChannelTypePreference,omitempty"` 
34+ 	Candidate                     semverTemplateChannelBundles  `json:"candidate,omitempty"` 
35+ 	Fast                          semverTemplateChannelBundles  `json:"fast,omitempty"` 
36+ 	Stable                        semverTemplateChannelBundles  `json:"stable,omitempty"` 
37+ 
38+ 	pkg             string  `json:"-"`  // the derived package name 
39+ 	defaultChannel  string  `json:"-"`  // detected "most stable" channel head 
40+ }
41+ 
42+ // IO structs -- END 
43+ 
44+ // SemverTemplate implements the common template interface 
45+ type  SemverTemplate  struct  {
46+ 	renderBundle  template.BundleRenderer 
47+ }
48+ 
49+ // NewTemplate creates a new semver template instance 
50+ func  NewTemplate (renderBundle  template.BundleRenderer ) template.Template  {
51+ 	return  & SemverTemplate {
52+ 		renderBundle : renderBundle ,
53+ 	}
54+ }
55+ 
56+ // RenderBundle implements the template.Template interface 
57+ func  (t  * SemverTemplate ) RenderBundle (ctx  context.Context , image  string ) (* declcfg.DeclarativeConfig , error ) {
58+ 	return  t .renderBundle (ctx , image )
59+ }
60+ 
61+ // Render implements the template.Template interface 
62+ func  (t  * SemverTemplate ) Render (ctx  context.Context , reader  io.Reader ) (* declcfg.DeclarativeConfig , error ) {
2063	var  out  declcfg.DeclarativeConfig 
2164
22- 	sv , err  :=  readFile (t . Data )
65+ 	sv , err  :=  readFile (reader )
2366	if  err  !=  nil  {
2467		return  nil , fmt .Errorf ("render: unable to read file: %v" , err )
2568	}
@@ -57,7 +100,69 @@ func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error
57100	return  & out , nil 
58101}
59102
60- func  buildBundleList (t  semverTemplate ) map [string ]string  {
103+ // Schema implements the template.Template interface 
104+ func  (t  * SemverTemplate ) Schema () string  {
105+ 	return  schema 
106+ }
107+ 
108+ // Factory implements the template.TemplateFactory interface 
109+ type  Factory  struct {}
110+ 
111+ // CreateTemplate implements the template.TemplateFactory interface 
112+ func  (f  * Factory ) CreateTemplate (renderBundle  template.BundleRenderer ) template.Template  {
113+ 	return  NewTemplate (renderBundle )
114+ }
115+ 
116+ // Schema implements the template.TemplateFactory interface 
117+ func  (f  * Factory ) Schema () string  {
118+ 	return  schema 
119+ }
120+ 
121+ const  schema  string  =  "olm.semver" 
122+ 
123+ // channel "archetypes", restricted in this iteration to just these 
124+ type  channelArchetype  string 
125+ 
126+ const  (
127+ 	candidateChannelArchetype  channelArchetype  =  "candidate" 
128+ 	fastChannelArchetype       channelArchetype  =  "fast" 
129+ 	stableChannelArchetype     channelArchetype  =  "stable" 
130+ )
131+ 
132+ // mapping channel name --> stability, where higher values indicate greater stability 
133+ var  channelPriorities  =  map [channelArchetype ]int {candidateChannelArchetype : 0 , fastChannelArchetype : 1 , stableChannelArchetype : 2 }
134+ 
135+ // sorting capability for a slice according to the assigned channelPriorities 
136+ type  byChannelPriority  []channelArchetype 
137+ 
138+ func  (b  byChannelPriority ) Len () int  { return  len (b ) }
139+ func  (b  byChannelPriority ) Less (i , j  int ) bool  {
140+ 	return  channelPriorities [b [i ]] <  channelPriorities [b [j ]]
141+ }
142+ func  (b  byChannelPriority ) Swap (i , j  int ) { b [i ], b [j ] =  b [j ], b [i ] }
143+ 
144+ type  streamType  string 
145+ 
146+ const  defaultStreamType  streamType  =  "" 
147+ const  minorStreamType  streamType  =  "minor" 
148+ const  majorStreamType  streamType  =  "major" 
149+ 
150+ // general preference for minor channels 
151+ var  streamTypePriorities  =  map [streamType ]int {minorStreamType : 2 , majorStreamType : 1 , defaultStreamType : 0 }
152+ 
153+ // map of archetypes --> bundles --> bundle-version from the input file 
154+ type  bundleVersions  map [channelArchetype ]map [string ]semver.Version  // e.g. srcv["stable"]["example-operator.v1.0.0"] = 1.0.0 
155+ 
156+ // the "high-water channel" struct functions as a freely-rising indicator of the "most stable" channel head, so we can use that 
157+ // later as the package's defaultChannel attribute 
158+ type  highwaterChannel  struct  {
159+ 	archetype  channelArchetype 
160+ 	kind       streamType 
161+ 	version    semver.Version 
162+ 	name       string 
163+ }
164+ 
165+ func  buildBundleList (t  SemverTemplateData ) map [string ]string  {
61166	dict  :=  make (map [string ]string )
62167	for  _ , bl  :=  range  []semverTemplateChannelBundles {t .Candidate , t .Fast , t .Stable } {
63168		for  _ , b  :=  range  bl .Bundles  {
@@ -69,13 +174,13 @@ func buildBundleList(t semverTemplate) map[string]string {
69174	return  dict 
70175}
71176
72- func  readFile (reader  io.Reader ) (* semverTemplate , error ) {
177+ func  readFile (reader  io.Reader ) (* SemverTemplateData , error ) {
73178	data , err  :=  io .ReadAll (reader )
74179	if  err  !=  nil  {
75180		return  nil , err 
76181	}
77182
78- 	sv  :=  semverTemplate {}
183+ 	sv  :=  SemverTemplateData {}
79184	if  err  :=  yaml .UnmarshalStrict (data , & sv ); err  !=  nil  {
80185		return  nil , err 
81186	}
@@ -114,7 +219,7 @@ func readFile(reader io.Reader) (*semverTemplate, error) {
114219	return  & sv , nil 
115220}
116221
117- func  (sv  * semverTemplate ) getVersionsFromStandardChannels (cfg  * declcfg.DeclarativeConfig , bundleDict  map [string ]string ) (* bundleVersions , error ) {
222+ func  (sv  * SemverTemplateData ) getVersionsFromStandardChannels (cfg  * declcfg.DeclarativeConfig , bundleDict  map [string ]string ) (* bundleVersions , error ) {
118223	versions  :=  bundleVersions {}
119224
120225	bdm , err  :=  sv .getVersionsFromChannel (sv .Candidate .Bundles , bundleDict , cfg )
@@ -147,7 +252,7 @@ func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.Declarati
147252	return  & versions , nil 
148253}
149254
150- func  (sv  * semverTemplate ) getVersionsFromChannel (semverBundles  []semverTemplateBundleEntry , bundleDict  map [string ]string , cfg  * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
255+ func  (sv  * SemverTemplateData ) getVersionsFromChannel (semverBundles  []semverTemplateBundleEntry , bundleDict  map [string ]string , cfg  * declcfg.DeclarativeConfig ) (map [string ]semver.Version , error ) {
151256	entries  :=  make (map [string ]semver.Version )
152257
153258	// we iterate over the channel bundles from the template, to: 
@@ -209,7 +314,7 @@ func (sv *semverTemplate) getVersionsFromChannel(semverBundles []semverTemplateB
209314// - 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. 
210315// 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 
211316
212- func  (sv  * semverTemplate ) generateChannels (semverChannels  * bundleVersions ) []declcfg.Channel  {
317+ func  (sv  * SemverTemplateData ) generateChannels (semverChannels  * bundleVersions ) []declcfg.Channel  {
213318	outChannels  :=  []declcfg.Channel {}
214319
215320	// sort the channel archetypes in ascending order so we can traverse the bundles in order of 
@@ -284,7 +389,7 @@ func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []dec
284389	return  outChannels 
285390}
286391
287- func  (sv  * semverTemplate ) linkChannels (unlinkedChannels  map [string ]* declcfg.Channel , harvestedVersions  * bundleVersions ) []declcfg.Channel  {
392+ func  (sv  * SemverTemplateData ) linkChannels (unlinkedChannels  map [string ]* declcfg.Channel , harvestedVersions  * bundleVersions ) []declcfg.Channel  {
288393	// bundle --> version lookup 
289394	bundleVersions  :=  make (map [string ]semver.Version )
290395	for  _ , vs  :=  range  * harvestedVersions  {
0 commit comments