Skip to content

Commit 3b16530

Browse files
committed
detect template type from input, but respect explicit specification
Signed-off-by: grokspawn <[email protected]>
1 parent 30f9439 commit 3b16530

File tree

13 files changed

+588
-525
lines changed

13 files changed

+588
-525
lines changed

alpha/template/basic/basic.go

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,29 @@ import (
99
"k8s.io/apimachinery/pkg/util/yaml"
1010

1111
"github.com/operator-framework/operator-registry/alpha/declcfg"
12+
"github.com/operator-framework/operator-registry/alpha/template"
1213
)
1314

1415
const schema string = "olm.template.basic"
1516

16-
type Template struct {
17-
RenderBundle func(context.Context, string) (*declcfg.DeclarativeConfig, error)
18-
}
19-
2017
type BasicTemplate struct {
21-
Schema string `json:"schema"`
22-
Entries []*declcfg.Meta `json:"entries"`
18+
renderBundle template.BundleRenderer
2319
}
2420

25-
func parseSpec(reader io.Reader) (*BasicTemplate, error) {
26-
bt := &BasicTemplate{}
27-
btDoc := json.RawMessage{}
28-
btDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
29-
err := btDecoder.Decode(&btDoc)
30-
if err != nil {
31-
return nil, fmt.Errorf("decoding template schema: %v", err)
32-
}
33-
err = json.Unmarshal(btDoc, bt)
34-
if err != nil {
35-
return nil, fmt.Errorf("unmarshalling template: %v", err)
36-
}
37-
38-
if bt.Schema != schema {
39-
return nil, fmt.Errorf("template has unknown schema (%q), should be %q", bt.Schema, schema)
21+
// NewTemplate creates a new basic template instance
22+
func NewTemplate(renderBundle template.BundleRenderer) template.Template {
23+
return &BasicTemplate{
24+
renderBundle: renderBundle,
4025
}
26+
}
4127

42-
return bt, nil
28+
// RenderBundle implements the template.Template interface
29+
func (t *BasicTemplate) RenderBundle(ctx context.Context, image string) (*declcfg.DeclarativeConfig, error) {
30+
return t.renderBundle(ctx, image)
4331
}
4432

45-
func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) {
33+
// Render implements the template.Template interface
34+
func (t *BasicTemplate) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) {
4635
bt, err := parseSpec(reader)
4736
if err != nil {
4837
return nil, err
@@ -68,14 +57,57 @@ func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.Declar
6857
return cfg, nil
6958
}
7059

60+
// Schema implements the template.Template interface
61+
func (t *BasicTemplate) Schema() string {
62+
return schema
63+
}
64+
65+
// Factory implements the template.TemplateFactory interface
66+
type Factory struct{}
67+
68+
// CreateTemplate implements the template.TemplateFactory interface
69+
func (f *Factory) CreateTemplate(renderBundle template.BundleRenderer) template.Template {
70+
return NewTemplate(renderBundle)
71+
}
72+
73+
// Schema implements the template.TemplateFactory interface
74+
func (f *Factory) Schema() string {
75+
return schema
76+
}
77+
78+
type BasicTemplateData struct {
79+
Schema string `json:"schema"`
80+
Entries []*declcfg.Meta `json:"entries"`
81+
}
82+
83+
func parseSpec(reader io.Reader) (*BasicTemplateData, error) {
84+
bt := &BasicTemplateData{}
85+
btDoc := json.RawMessage{}
86+
btDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
87+
err := btDecoder.Decode(&btDoc)
88+
if err != nil {
89+
return nil, fmt.Errorf("decoding template schema: %v", err)
90+
}
91+
err = json.Unmarshal(btDoc, bt)
92+
if err != nil {
93+
return nil, fmt.Errorf("unmarshalling template: %v", err)
94+
}
95+
96+
if bt.Schema != schema {
97+
return nil, fmt.Errorf("template has unknown schema (%q), should be %q", bt.Schema, schema)
98+
}
99+
100+
return bt, nil
101+
}
102+
71103
// isBundleTemplate identifies a Bundle template source as having a Schema and Image defined
72104
// but no Properties, RelatedImages or Package defined
73105
func isBundleTemplate(b *declcfg.Bundle) bool {
74106
return b.Schema != "" && b.Image != "" && b.Package == "" && len(b.Properties) == 0 && len(b.RelatedImages) == 0
75107
}
76108

77-
// FromReader reads FBC from a reader and generates a BasicTemplate from it
78-
func FromReader(r io.Reader) (*BasicTemplate, error) {
109+
// FromReader reads FBC from a reader and generates a BasicTemplateData from it
110+
func FromReader(r io.Reader) (*BasicTemplateData, error) {
79111
var entries []*declcfg.Meta
80112
if err := declcfg.WalkMetasReader(r, func(meta *declcfg.Meta, err error) error {
81113
if err != nil {
@@ -101,7 +133,7 @@ func FromReader(r io.Reader) (*BasicTemplate, error) {
101133
return nil, err
102134
}
103135

104-
bt := &BasicTemplate{
136+
bt := &BasicTemplateData{
105137
Schema: schema,
106138
Entries: entries,
107139
}

alpha/template/schema.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package template
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
8+
"k8s.io/apimachinery/pkg/util/yaml"
9+
)
10+
11+
// detectSchema reads the input and extracts the schema field
12+
func detectSchema(reader io.Reader) (string, error) {
13+
// Read the input into a raw message
14+
rawDoc := json.RawMessage{}
15+
decoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
16+
err := decoder.Decode(&rawDoc)
17+
if err != nil {
18+
return "", fmt.Errorf("decoding template input: %v", err)
19+
}
20+
21+
// Parse the raw message to extract schema
22+
var schemaDoc struct {
23+
Schema string `json:"schema"`
24+
}
25+
err = json.Unmarshal(rawDoc, &schemaDoc)
26+
if err != nil {
27+
return "", fmt.Errorf("unmarshalling template schema: %v", err)
28+
}
29+
30+
if schemaDoc.Schema == "" {
31+
return "", fmt.Errorf("template input missing required 'schema' field")
32+
}
33+
34+
return schemaDoc.Schema, nil
35+
}

alpha/template/semver/semver.go

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)