forked from operator-framework/api
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Jordan Keister <[email protected]>
- Loading branch information
Showing
21 changed files
with
5,425 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package declcfg | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
"github.com/operator-framework/operator-registry/alpha/property" | ||
) | ||
|
||
const ( | ||
SchemaPackage = "olm.package" | ||
SchemaChannel = "olm.channel" | ||
SchemaBundle = "olm.bundle" | ||
) | ||
|
||
type DeclarativeConfig struct { | ||
Packages []Package | ||
Channels []Channel | ||
Bundles []Bundle | ||
Others []Meta | ||
} | ||
|
||
type Package struct { | ||
Schema string `json:"schema"` | ||
Name string `json:"name"` | ||
DefaultChannel string `json:"defaultChannel"` | ||
Icon *Icon `json:"icon,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
Properties []property.Property `json:"properties,omitempty" hash:"set"` | ||
} | ||
|
||
type Icon struct { | ||
Data []byte `json:"base64data"` | ||
MediaType string `json:"mediatype"` | ||
} | ||
|
||
type Channel struct { | ||
Schema string `json:"schema"` | ||
Name string `json:"name"` | ||
Package string `json:"package"` | ||
Entries []ChannelEntry `json:"entries"` | ||
Properties []property.Property `json:"properties,omitempty" hash:"set"` | ||
} | ||
|
||
type ChannelEntry struct { | ||
Name string `json:"name"` | ||
Replaces string `json:"replaces,omitempty"` | ||
Skips []string `json:"skips,omitempty"` | ||
SkipRange string `json:"skipRange,omitempty"` | ||
} | ||
|
||
// Bundle specifies all metadata and data of a bundle object. | ||
// Top-level fields are the source of truth, i.e. not CSV values. | ||
// | ||
// Notes: | ||
// - Any field slice type field or type containing a slice somewhere | ||
// where two types/fields are equal if their contents are equal regardless | ||
// of order must have a `hash:"set"` field tag for bundle comparison. | ||
// - Any fields that have a `json:"-"` tag must be included in the equality | ||
// evaluation in bundlesEqual(). | ||
type Bundle struct { | ||
Schema string `json:"schema"` | ||
Name string `json:"name"` | ||
Package string `json:"package"` | ||
Image string `json:"image"` | ||
Properties []property.Property `json:"properties,omitempty" hash:"set"` | ||
RelatedImages []RelatedImage `json:"relatedImages,omitempty" hash:"set"` | ||
|
||
// These fields are present so that we can continue serving | ||
// the GRPC API the way packageserver expects us to in a | ||
// backwards-compatible way. These are populated from | ||
// any `olm.bundle.object` properties. | ||
// | ||
// These fields will never be persisted in the bundle blob as | ||
// first class fields. | ||
CsvJSON string `json:"-"` | ||
Objects []string `json:"-"` | ||
} | ||
|
||
type RelatedImage struct { | ||
Name string `json:"name"` | ||
Image string `json:"image"` | ||
} | ||
|
||
type Meta struct { | ||
Schema string | ||
Package string | ||
|
||
Blob json.RawMessage | ||
} | ||
|
||
func (m Meta) MarshalJSON() ([]byte, error) { | ||
return m.Blob, nil | ||
} | ||
|
||
func (m *Meta) UnmarshalJSON(blob []byte) error { | ||
type tmp struct { | ||
Schema string `json:"schema"` | ||
Package string `json:"package,omitempty"` | ||
Properties []property.Property `json:"properties,omitempty"` | ||
} | ||
var t tmp | ||
if err := json.Unmarshal(blob, &t); err != nil { | ||
return err | ||
} | ||
m.Schema = t.Schema | ||
m.Package = t.Package | ||
m.Blob = blob | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package declcfg | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/blang/semver/v4" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
|
||
"github.com/operator-framework/operator-registry/alpha/model" | ||
"github.com/operator-framework/operator-registry/alpha/property" | ||
) | ||
|
||
func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { | ||
mpkgs := model.Model{} | ||
defaultChannels := map[string]string{} | ||
for _, p := range cfg.Packages { | ||
if p.Name == "" { | ||
return nil, fmt.Errorf("config contains package with no name") | ||
} | ||
|
||
if _, ok := mpkgs[p.Name]; ok { | ||
return nil, fmt.Errorf("duplicate package %q", p.Name) | ||
} | ||
|
||
mpkg := &model.Package{ | ||
Name: p.Name, | ||
Description: p.Description, | ||
Channels: map[string]*model.Channel{}, | ||
} | ||
if p.Icon != nil { | ||
mpkg.Icon = &model.Icon{ | ||
Data: p.Icon.Data, | ||
MediaType: p.Icon.MediaType, | ||
} | ||
} | ||
defaultChannels[p.Name] = p.DefaultChannel | ||
mpkgs[p.Name] = mpkg | ||
} | ||
|
||
channelDefinedEntries := map[string]sets.String{} | ||
for _, c := range cfg.Channels { | ||
mpkg, ok := mpkgs[c.Package] | ||
if !ok { | ||
return nil, fmt.Errorf("unknown package %q for channel %q", c.Package, c.Name) | ||
} | ||
|
||
if c.Name == "" { | ||
return nil, fmt.Errorf("package %q contains channel with no name", c.Package) | ||
} | ||
|
||
if _, ok := mpkg.Channels[c.Name]; ok { | ||
return nil, fmt.Errorf("package %q has duplicate channel %q", c.Package, c.Name) | ||
} | ||
|
||
mch := &model.Channel{ | ||
Package: mpkg, | ||
Name: c.Name, | ||
Bundles: map[string]*model.Bundle{}, | ||
// NOTICE: The field Properties of the type Channel is for internal use only. | ||
// DO NOT use it for any public-facing functionalities. | ||
// This API is in alpha stage and it is subject to change. | ||
Properties: c.Properties, | ||
} | ||
|
||
cde := sets.NewString() | ||
for _, entry := range c.Entries { | ||
if _, ok := mch.Bundles[entry.Name]; ok { | ||
return nil, fmt.Errorf("invalid package %q, channel %q: duplicate entry %q", c.Package, c.Name, entry.Name) | ||
} | ||
cde = cde.Insert(entry.Name) | ||
mch.Bundles[entry.Name] = &model.Bundle{ | ||
Package: mpkg, | ||
Channel: mch, | ||
Name: entry.Name, | ||
Replaces: entry.Replaces, | ||
Skips: entry.Skips, | ||
SkipRange: entry.SkipRange, | ||
} | ||
} | ||
channelDefinedEntries[c.Package] = cde | ||
|
||
mpkg.Channels[c.Name] = mch | ||
|
||
defaultChannelName := defaultChannels[c.Package] | ||
if defaultChannelName == c.Name { | ||
mpkg.DefaultChannel = mch | ||
} | ||
} | ||
|
||
// packageBundles tracks the set of bundle names for each package | ||
// and is used to detect duplicate bundles. | ||
packageBundles := map[string]sets.String{} | ||
|
||
for _, b := range cfg.Bundles { | ||
if b.Package == "" { | ||
return nil, fmt.Errorf("package name must be set for bundle %q", b.Name) | ||
} | ||
mpkg, ok := mpkgs[b.Package] | ||
if !ok { | ||
return nil, fmt.Errorf("unknown package %q for bundle %q", b.Package, b.Name) | ||
} | ||
|
||
bundles, ok := packageBundles[b.Package] | ||
if !ok { | ||
bundles = sets.NewString() | ||
} | ||
if bundles.Has(b.Name) { | ||
return nil, fmt.Errorf("package %q has duplicate bundle %q", b.Package, b.Name) | ||
} | ||
bundles.Insert(b.Name) | ||
packageBundles[b.Package] = bundles | ||
|
||
props, err := property.Parse(b.Properties) | ||
if err != nil { | ||
return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) | ||
} | ||
|
||
if len(props.Packages) != 1 { | ||
return nil, fmt.Errorf("package %q bundle %q must have exactly 1 %q property, found %d", b.Package, b.Name, property.TypePackage, len(props.Packages)) | ||
} | ||
|
||
if b.Package != props.Packages[0].PackageName { | ||
return nil, fmt.Errorf("package %q does not match %q property %q", b.Package, property.TypePackage, props.Packages[0].PackageName) | ||
} | ||
|
||
// Parse version from the package property. | ||
rawVersion := props.Packages[0].Version | ||
ver, err := semver.Parse(rawVersion) | ||
if err != nil { | ||
return nil, fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err) | ||
} | ||
|
||
channelDefinedEntries[b.Package] = channelDefinedEntries[b.Package].Delete(b.Name) | ||
found := false | ||
for _, mch := range mpkg.Channels { | ||
if mb, ok := mch.Bundles[b.Name]; ok { | ||
found = true | ||
mb.Image = b.Image | ||
mb.Properties = b.Properties | ||
mb.RelatedImages = relatedImagesToModelRelatedImages(b.RelatedImages) | ||
mb.CsvJSON = b.CsvJSON | ||
mb.Objects = b.Objects | ||
mb.PropertiesP = props | ||
mb.Version = ver | ||
} | ||
} | ||
if !found { | ||
return nil, fmt.Errorf("package %q, bundle %q not found in any channel entries", b.Package, b.Name) | ||
} | ||
} | ||
|
||
for pkg, entries := range channelDefinedEntries { | ||
if entries.Len() > 0 { | ||
return nil, fmt.Errorf("no olm.bundle blobs found in package %q for olm.channel entries %s", pkg, entries.List()) | ||
} | ||
} | ||
|
||
for _, mpkg := range mpkgs { | ||
defaultChannelName := defaultChannels[mpkg.Name] | ||
if defaultChannelName != "" && mpkg.DefaultChannel == nil { | ||
dch := &model.Channel{ | ||
Package: mpkg, | ||
Name: defaultChannelName, | ||
Bundles: map[string]*model.Bundle{}, | ||
} | ||
mpkg.DefaultChannel = dch | ||
mpkg.Channels[dch.Name] = dch | ||
} | ||
} | ||
|
||
if err := mpkgs.Validate(); err != nil { | ||
return nil, err | ||
} | ||
mpkgs.Normalize() | ||
return mpkgs, nil | ||
} | ||
|
||
func relatedImagesToModelRelatedImages(in []RelatedImage) []model.RelatedImage { | ||
var out []model.RelatedImage | ||
for _, p := range in { | ||
out = append(out, model.RelatedImage{ | ||
Name: p.Name, | ||
Image: p.Image, | ||
}) | ||
} | ||
return out | ||
} |
Oops, something went wrong.