Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 22
EntitySource implementation for the v1 registry client#84
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d44935b678b19a41a9d942395840fe8cc76File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package catalogsource_test | ||
| import ( | ||
| "testing" | ||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| ) | ||
| func TestDeppy(t *testing.T) { | ||
| RegisterFailHandler(Fail) | ||
| RunSpecs(t, "Deppy Suite") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| package catalogsource | ||
| import ( | ||
| "fmt" | ||
| "sort" | ||
| "github.com/operator-framework/operator-registry/alpha/property" | ||
| catalogsourceapi "github.com/operator-framework/operator-registry/pkg/api" | ||
| "k8s.io/apimachinery/pkg/util/errors" | ||
| "github.com/operator-framework/deppy/pkg/deppy" | ||
| "github.com/operator-framework/deppy/pkg/deppy/input" | ||
| "github.com/operator-framework/deppy/pkg/lib/util" | ||
| ) | ||
| type UpgradeEdge struct { | ||
| property.Channel | ||
| Replaces string `json:"replaces,omitempty"` | ||
| Skips []string `json:"skips,omitempty"` | ||
| SkipRange string `json:"skipRange,omitempty"` | ||
| Version string `json:"version,omitempty"` | ||
| } | ||
| type DefaultChannel struct { | ||
| DefaultChannel string `json:"defaultchannel"` | ||
| } | ||
| const ( | ||
| TypeDefaultChannel = "olm.package.defaultChannel" | ||
| TypeBundleSource = "olm.bundle.path" | ||
| TypeLabel = "olm.label" | ||
| TypeLabelRequired = "olm.label.required" | ||
| ) | ||
| func EntityFromBundle(catsrcID string, pkg *catalogsourceapi.Package, bundle *catalogsourceapi.Bundle) (*input.Entity, error) { | ||
| properties := map[string]string{} | ||
| var errs []error | ||
| // Multivalue properties | ||
| propsList := map[string]map[string]struct{}{} | ||
| setPropertyValue := func(key, value string) { | ||
| if _, ok := propsList[key]; !ok { | ||
| propsList[key] = map[string]struct{}{} | ||
| } | ||
| if _, ok := propsList[key][value]; !ok { | ||
| propsList[key][value] = struct{}{} | ||
| } | ||
| } | ||
| for _, prvAPI := range bundle.ProvidedApis { | ||
Contributor 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. It might be good to break these steps into their own functions to make this function a bit smaller and more readable. wdyt? | ||
| apiValue, err := util.JSONMarshal(prvAPI) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| setPropertyValue(property.TypeGVK, string(apiValue)) | ||
| } | ||
| for _, reqAPI := range bundle.RequiredApis { | ||
| apiValue, err := util.JSONMarshal(reqAPI) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| setPropertyValue(property.TypeGVKRequired, string(apiValue)) | ||
| } | ||
| for _, reqAPI := range bundle.Dependencies { | ||
| switch reqAPI.Type { | ||
| case property.TypeGVK: | ||
| setPropertyValue(property.TypeGVKRequired, reqAPI.Value) | ||
| case property.TypePackage: | ||
| setPropertyValue(property.TypePackageRequired, reqAPI.Value) | ||
| case TypeLabel: // legacy property | ||
| setPropertyValue(TypeLabelRequired, reqAPI.Value) | ||
| default: | ||
| setPropertyValue(reqAPI.Type, reqAPI.Value) | ||
| } | ||
| } | ||
| ignoredProperties := map[string]struct{}{ | ||
| property.TypeBundleObject: {}, | ||
| } | ||
| for _, p := range bundle.Properties { | ||
| if _, ok := ignoredProperties[p.Type]; ok { | ||
| continue | ||
| } | ||
| setPropertyValue(p.Type, p.Value) | ||
| } | ||
| for pType, pValues := range propsList { | ||
| var prop []interface{} | ||
| for pValue := range pValues { | ||
| var v interface{} | ||
| err := util.JSONUnmarshal([]byte(pValue), &v) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| prop = append(prop, v) | ||
| } | ||
| if len(prop) == 0 { | ||
| continue | ||
| } | ||
| if len(prop) > 1 { | ||
| sort.Slice(prop, func(i, j int) bool { | ||
| // enforce some ordering for deterministic properties. Possibly a neater way to do this. | ||
| return fmt.Sprintf("%v", prop[i]) < fmt.Sprintf("%v", prop[j]) | ||
| }) | ||
| } | ||
| pValue, err := util.JSONMarshal(prop) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| properties[pType] = string(pValue) | ||
| } | ||
| // Singleton properties. | ||
| // `olm.package`, `olm.channel`, `olm.defaultChannel` | ||
| pkgValue, err := util.JSONMarshal(property.Package{ | ||
| PackageName: bundle.PackageName, | ||
| Version: bundle.Version, | ||
| }) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| } else { | ||
| properties[property.TypePackage] = string(pkgValue) | ||
| } | ||
| upValue, err := util.JSONMarshal(UpgradeEdge{ | ||
| Channel: property.Channel{ | ||
| ChannelName: bundle.ChannelName, | ||
| }, | ||
| Replaces: bundle.Replaces, | ||
| Skips: bundle.Skips, | ||
| SkipRange: bundle.SkipRange, | ||
| // Version: bundle.Version, | ||
| }) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| } else { | ||
| properties[property.TypeChannel] = string(upValue) | ||
| } | ||
| properties[TypeDefaultChannel] = pkg.DefaultChannelName | ||
| properties[TypeBundleSource] = bundle.BundlePath | ||
| if len(errs) > 0 { | ||
| return nil, fmt.Errorf("failed to parse properties for bundle %s/%s in %s: %v", bundle.GetPackageName(), bundle.GetVersion(), catsrcID, errors.NewAggregate(errs)) | ||
| } | ||
| // Since multiple instances of bundle may exist for different channels, entityID must include reference to channel | ||
| entityIDFromBundle := func(catsrcID string, bundle *catalogsourceapi.Bundle) deppy.Identifier { | ||
Contributor 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. should we just push this out into a proper function rather than an inline one? | ||
| return deppy.Identifier(fmt.Sprintf("%s/%s/%s/%s", catsrcID, bundle.PackageName, bundle.ChannelName, bundle.Version)) | ||
| } | ||
| return input.NewEntity(entityIDFromBundle(catsrcID, bundle), properties), nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| package catalogsource_test | ||
| import ( | ||
| "github.com/operator-framework/deppy/pkg/deppy/input" | ||
| "github.com/operator-framework/deppy/pkg/deppy/input/catalogsource" | ||
| catalogsourceapi "github.com/operator-framework/operator-registry/pkg/api" | ||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| ) | ||
| var _ = Describe("RegistryBundleConverter", func() { | ||
| It("generates entity from bundle", func() { | ||
Contributor 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. you may want to also poke at some of the error cases | ||
| // When converting a bundle to an entity, package, channel, defaultChannel and bundlePath | ||
| // must be created as special properties that are not overwritten | ||
| // The remaining entity properties must be aggregated from the bundle ProvidedAPIs, RequiredAPIs, | ||
| // Dependencies and Properties. | ||
| // Properties must be preserved except for `olm.bundle.object` properties, which are irrelevant to resolution. | ||
| // ProvidedAPIs, RequiredAPIs and Dependencies must be converted from their legacy format | ||
| // Values in the aggregated property set must not have duplicates. | ||
| b := &catalogsourceapi.Bundle{ | ||
| CsvName: "test", | ||
| PackageName: "test", | ||
| ChannelName: "beta", | ||
| BundlePath: "path/to/bundle", | ||
| Version: "0.1.4", | ||
| SkipRange: "< 0.1.4", | ||
| Replaces: "test-operator.v0.0.1", | ||
| Skips: []string{"test-operator.v0.0.2", "test-operator.v0.0.3"}, | ||
| ProvidedApis: []*catalogsourceapi.GroupVersionKind{{ | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "prov1", | ||
| }, { | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "prov2", | ||
| }}, | ||
| RequiredApis: []*catalogsourceapi.GroupVersionKind{{ | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "req1", | ||
| }, { | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "req2", | ||
| }, { | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "req3", | ||
| }, { | ||
| Group: "foo", | ||
| Version: "v1", | ||
| Kind: "req4", | ||
| }}, | ||
| Dependencies: []*catalogsourceapi.Dependency{ | ||
| // legacy constraint types | ||
| { | ||
| Type: "olm.gvk", | ||
| Value: `{"group":"foo","version":"v1","kind":"req1"}`, | ||
| }, { | ||
| Type: "olm.gvk", | ||
| Value: `{"group":"foo","version":"v1","kind":"req2"}`, | ||
| }, { | ||
| Type: "olm.package", | ||
| Value: `{"packageName":"dep1","version":"1.1.0"}`, | ||
| }, { | ||
| Type: "olm.package", | ||
| Value: `{"packageName":"dep2","version":"<1.1.0"}`, | ||
| }}, | ||
| Properties: []*catalogsourceapi.Property{ | ||
| { | ||
| Type: "olm.package", | ||
| Value: `{"packageName":"other","version":"0.1.4"}`, | ||
| }, { | ||
| Type: "olm.gvk", | ||
| Value: `{"group":"foo","version":"v1","kind":"prov1"}`, | ||
| }, { | ||
| Type: "olm.gvk.required", | ||
| Value: `{"group":"foo","version":"v1","kind":"req1"}`, | ||
| }, { | ||
| Type: "olm.gvk.required", | ||
| Value: `{"group":"foo","version":"v1","kind":"req3"}`, | ||
| }, { | ||
| Type: "olm.package.required", | ||
| Value: `{"packageName":"dep1","version":"1.1.0"}`, | ||
| }, { | ||
| Type: "olm.maxOpenShiftVersion", | ||
| Value: "4.12", | ||
| }, { | ||
| Type: "olm.deprecated", | ||
| Value: "{}", | ||
| }, { | ||
| //must be omitted | ||
| Type: "olm.bundle.object", | ||
| Value: `{"data": "eyJraW5kIjogIkN1c3RvbVJlc291cmNlRGVmaW5pdGlvbiIsICJhcGlWZXJzaW9uIjogImFwaWV4dGVuc2lvbnMuazhzLmlvL3YxIn0="}`, | ||
| }}, | ||
| } | ||
| p := &catalogsourceapi.Package{DefaultChannelName: "stable"} | ||
| entity, err := catalogsource.EntityFromBundle("test-catalog", p, b) | ||
| Expect(err).To(BeNil()) | ||
| Expect(entity).To(Equal(&input.Entity{ | ||
| ID: "test-catalog/test/beta/0.1.4", | ||
| Properties: map[string]string{ | ||
| "olm.package": `{"packageName":"test","version":"0.1.4"}`, | ||
| "olm.channel": `{"channelName":"beta","priority":0,"replaces":"test-operator.v0.0.1","skips":["test-operator.v0.0.2","test-operator.v0.0.3"],"skipRange":"< 0.1.4"}`, | ||
| "olm.package.defaultChannel": "stable", | ||
| "olm.bundle.path": "path/to/bundle", | ||
| "olm.maxOpenShiftVersion": "[4.12]", | ||
| "olm.deprecated": "[{}]", | ||
| "olm.package.required": `[{"packageName":"dep1","version":"1.1.0"},{"packageName":"dep2","version":"<1.1.0"}]`, | ||
| "olm.gvk": `[{"group":"foo","kind":"prov1","version":"v1"},{"group":"foo","kind":"prov2","version":"v1"}]`, | ||
| "olm.gvk.required": `[{"group":"foo","kind":"req1","version":"v1"},{"group":"foo","kind":"req2","version":"v1"},{"group":"foo","kind":"req3","version":"v1"},{"group":"foo","kind":"req4","version":"v1"}]`, | ||
| }, | ||
| })) | ||
| }) | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
Should it be "RegistryQuerier Suite" or something like that?