-
Notifications
You must be signed in to change notification settings - Fork 260
detect template type from input, but respect explicit specification #1844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,40 +9,33 @@ import ( | |
| "k8s.io/apimachinery/pkg/util/yaml" | ||
|
|
||
| "github.com/operator-framework/operator-registry/alpha/declcfg" | ||
| "github.com/operator-framework/operator-registry/alpha/template" | ||
| ) | ||
|
|
||
| const schema string = "olm.template.basic" | ||
|
|
||
| type Template struct { | ||
| RenderBundle func(context.Context, string) (*declcfg.DeclarativeConfig, error) | ||
| func init() { | ||
| template.GetTemplateRegistry().Register(&Factory{}) | ||
| } | ||
|
|
||
| type BasicTemplate struct { | ||
| Schema string `json:"schema"` | ||
| Entries []*declcfg.Meta `json:"entries"` | ||
| renderBundle template.BundleRenderer | ||
| } | ||
|
|
||
| func parseSpec(reader io.Reader) (*BasicTemplate, error) { | ||
| bt := &BasicTemplate{} | ||
| btDoc := json.RawMessage{} | ||
| btDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096) | ||
| err := btDecoder.Decode(&btDoc) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("decoding template schema: %v", err) | ||
| } | ||
| err = json.Unmarshal(btDoc, bt) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unmarshalling template: %v", err) | ||
| } | ||
|
|
||
| if bt.Schema != schema { | ||
| return nil, fmt.Errorf("template has unknown schema (%q), should be %q", bt.Schema, schema) | ||
| // NewTemplate creates a new basic template instance | ||
| func NewTemplate(renderBundle template.BundleRenderer) template.Template { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we shorter the constructor name to |
||
| return &BasicTemplate{ | ||
| renderBundle: renderBundle, | ||
| } | ||
| } | ||
|
|
||
| return bt, nil | ||
| // RenderBundle implements the template.Template interface | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these docs lines are not very much helpful, can you expand for clarity? |
||
| func (t *BasicTemplate) RenderBundle(ctx context.Context, image string) (*declcfg.DeclarativeConfig, error) { | ||
| return t.renderBundle(ctx, image) | ||
| } | ||
|
|
||
| func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { | ||
| // Render implements the template.Template interface | ||
| func (t *BasicTemplate) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { | ||
| bt, err := parseSpec(reader) | ||
| if err != nil { | ||
| return nil, err | ||
|
|
@@ -68,14 +61,57 @@ func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.Declar | |
| return cfg, nil | ||
| } | ||
|
|
||
| // Schema implements the template.Template interface | ||
| func (t *BasicTemplate) Schema() string { | ||
| return schema | ||
| } | ||
|
|
||
| // Factory implements the template.TemplateFactory interface | ||
| type Factory struct{} | ||
|
|
||
| // CreateTemplate implements the template.TemplateFactory interface | ||
| func (f *Factory) CreateTemplate(renderBundle template.BundleRenderer) template.Template { | ||
| return NewTemplate(renderBundle) | ||
| } | ||
|
|
||
| // Schema implements the template.TemplateFactory interface | ||
| func (f *Factory) Schema() string { | ||
| return schema | ||
| } | ||
|
|
||
| type BasicTemplateData struct { | ||
| Schema string `json:"schema"` | ||
| Entries []*declcfg.Meta `json:"entries"` | ||
| } | ||
|
|
||
| func parseSpec(reader io.Reader) (*BasicTemplateData, error) { | ||
| bt := &BasicTemplateData{} | ||
| btDoc := json.RawMessage{} | ||
| btDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096) | ||
| err := btDecoder.Decode(&btDoc) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("decoding template schema: %v", err) | ||
| } | ||
| err = json.Unmarshal(btDoc, bt) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unmarshalling template: %v", err) | ||
| } | ||
|
|
||
| if bt.Schema != schema { | ||
| return nil, fmt.Errorf("template has unknown schema (%q), should be %q", bt.Schema, schema) | ||
| } | ||
|
|
||
| return bt, nil | ||
| } | ||
|
|
||
| // isBundleTemplate identifies a Bundle template source as having a Schema and Image defined | ||
| // but no Properties, RelatedImages or Package defined | ||
| func isBundleTemplate(b *declcfg.Bundle) bool { | ||
| return b.Schema != "" && b.Image != "" && b.Package == "" && len(b.Properties) == 0 && len(b.RelatedImages) == 0 | ||
| } | ||
|
|
||
| // FromReader reads FBC from a reader and generates a BasicTemplate from it | ||
| func FromReader(r io.Reader) (*BasicTemplate, error) { | ||
| // FromReader reads FBC from a reader and generates a BasicTemplateData from it | ||
| func FromReader(r io.Reader) (*BasicTemplateData, error) { | ||
| var entries []*declcfg.Meta | ||
| if err := declcfg.WalkMetasReader(r, func(meta *declcfg.Meta, err error) error { | ||
| if err != nil { | ||
|
|
@@ -101,7 +137,7 @@ func FromReader(r io.Reader) (*BasicTemplate, error) { | |
| return nil, err | ||
| } | ||
|
|
||
| bt := &BasicTemplate{ | ||
| bt := &BasicTemplateData{ | ||
| Schema: schema, | ||
| Entries: entries, | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package template | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
| "text/tabwriter" | ||
| ) | ||
|
|
||
| var tr = NewTemplateRegistry() | ||
|
|
||
| // GetTemplateRegistry returns the global template registry | ||
| func GetTemplateRegistry() *TemplateRegistry { | ||
| return tr | ||
| } | ||
|
|
||
| func (r *TemplateRegistry) HelpText() string { | ||
| var help strings.Builder | ||
| supportedTypes := r.GetSupportedTypes() | ||
| help.WriteString("\n") | ||
| tabber := tabwriter.NewWriter(&help, 0, 0, 1, ' ', 0) | ||
| for _, item := range supportedTypes { | ||
| fmt.Fprintf(tabber, " - %s\n", item) | ||
| } | ||
| tabber.Flush() | ||
| return help.String() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package template | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
|
|
||
| "k8s.io/apimachinery/pkg/util/yaml" | ||
| ) | ||
|
|
||
| // detectSchema reads the input, extracts the schema field, and returns a reader | ||
| // that includes the consumed data followed by the remaining stream data. | ||
| // This works when the input is stdin or a file (since stdin cannot be closed and reopened) | ||
| // and complies with the requirement that each supplied schema has a defined "schema" field, | ||
| // without attempting to load all input into memory. | ||
| func detectSchema(reader io.Reader) (string, io.Reader, error) { | ||
| // Capture what's read during schema detection | ||
| var capturedData bytes.Buffer | ||
| teeReader := io.TeeReader(reader, &capturedData) | ||
|
|
||
| // Read the input into a raw message | ||
| rawDoc := json.RawMessage{} | ||
| decoder := yaml.NewYAMLOrJSONDecoder(teeReader, 4096) | ||
| err := decoder.Decode(&rawDoc) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("decoding template input: %v", err) | ||
| } | ||
|
|
||
| // Parse the raw message to extract schema | ||
| var schemaDoc struct { | ||
| Schema string `json:"schema"` | ||
| } | ||
| err = json.Unmarshal(rawDoc, &schemaDoc) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("unmarshalling template schema: %v", err) | ||
| } | ||
|
|
||
| if schemaDoc.Schema == "" { | ||
| return "", nil, fmt.Errorf("template input missing required 'schema' field") | ||
| } | ||
|
|
||
| // Create a reader that combines the captured data with the remaining stream | ||
| replayReader := io.MultiReader(&capturedData, reader) | ||
|
|
||
| return schemaDoc.Schema, replayReader, nil | ||
| } | ||
grokspawn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this need to be public given that we have Template interface?