Skip to content

Commit 2114da6

Browse files
authored
Switch to catalogd's CatalogMetadata APIs (#343)
Signed-off-by: Mikalai Radchuk <[email protected]>
1 parent b99cc20 commit 2114da6

File tree

6 files changed

+138
-136
lines changed

6 files changed

+138
-136
lines changed

cmd/resolutioncli/entity_source.go

+3-55
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,12 @@ package main
1818

1919
import (
2020
"context"
21-
"encoding/json"
22-
"fmt"
2321

2422
"github.com/operator-framework/deppy/pkg/deppy"
2523
"github.com/operator-framework/deppy/pkg/deppy/input"
2624
"github.com/operator-framework/operator-registry/alpha/action"
27-
"github.com/operator-framework/operator-registry/alpha/declcfg"
28-
"github.com/operator-framework/operator-registry/alpha/model"
29-
"github.com/operator-framework/operator-registry/alpha/property"
3025

31-
olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities"
26+
"github.com/operator-framework/operator-controller/internal/resolution/entitysources"
3227
)
3328

3429
type indexRefEntitySource struct {
@@ -101,12 +96,8 @@ func (es *indexRefEntitySource) entities(ctx context.Context) (input.EntityList,
10196
return nil, err
10297
}
10398

104-
model, err := declcfg.ConvertToModel(*cfg)
105-
if err != nil {
106-
return nil, err
107-
}
108-
109-
entities, err := modelToEntities(model)
99+
// TODO: update empty catalog name string to be catalog name once we support multiple catalogs in CLI
100+
entities, err := entitysources.MetadataToEntities("", cfg.Channels, cfg.Bundles)
110101
if err != nil {
111102
return nil, err
112103
}
@@ -116,46 +107,3 @@ func (es *indexRefEntitySource) entities(ctx context.Context) (input.EntityList,
116107

117108
return es.entitiesCache, nil
118109
}
119-
120-
// TODO: Reduce code duplication: share a function with catalogdEntitySource (see getEntities)
121-
// We don't want to maintain two functions performing conversion into input.EntityList.
122-
// For this we need some common format. So we need a package which will be able
123-
// to convert from declfcg structs into CRD structs directly or via model.Model.
124-
// One option would be to make this piece of code from catalogd reusable and exportable:
125-
// https://github.com/operator-framework/catalogd/blob/9fe45a628de2e74d9cd73c3650fa2582aaac5213/pkg/controllers/core/catalog_controller.go#L200-L360
126-
func modelToEntities(model model.Model) (input.EntityList, error) {
127-
entities := input.EntityList{}
128-
129-
for _, pkg := range model {
130-
for _, ch := range pkg.Channels {
131-
for _, bundle := range ch.Bundles {
132-
props := map[string]string{}
133-
134-
for _, prop := range bundle.Properties {
135-
switch prop.Type {
136-
case property.TypePackage:
137-
// this is already a json marshalled object, so it doesn't need to be marshalled
138-
// like the other ones
139-
props[property.TypePackage] = string(prop.Value)
140-
}
141-
}
142-
143-
imgValue, err := json.Marshal(bundle.Image)
144-
if err != nil {
145-
return nil, err
146-
}
147-
props[olmentity.PropertyBundlePath] = string(imgValue)
148-
149-
channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0})
150-
props[property.TypeChannel] = string(channelValue)
151-
entity := input.Entity{
152-
ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Package.Name, ch.Name)),
153-
Properties: props,
154-
}
155-
entities = append(entities, entity)
156-
}
157-
}
158-
}
159-
160-
return entities, nil
161-
}

config/rbac/role.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ rules:
77
- apiGroups:
88
- catalogd.operatorframework.io
99
resources:
10-
- bundlemetadata
10+
- catalogmetadata
1111
verbs:
1212
- list
1313
- watch

internal/controllers/operator_controller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ type OperatorReconciler struct {
6262

6363
//+kubebuilder:rbac:groups=core.rukpak.io,resources=bundledeployments,verbs=get;list;watch;create;update;patch
6464

65-
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=bundlemetadata,verbs=list;watch
6665
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=packages,verbs=list;watch
6766
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogs,verbs=list;watch
67+
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogmetadata,verbs=list;watch
6868

6969
func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
7070
l := log.FromContext(ctx).WithName("operator-controller")

internal/resolution/entitysources/catalogdsource.go

+99-49
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
99
"github.com/operator-framework/deppy/pkg/deppy"
1010
"github.com/operator-framework/deppy/pkg/deppy/input"
11+
"github.com/operator-framework/operator-registry/alpha/declcfg"
1112
"github.com/operator-framework/operator-registry/alpha/property"
1213
"sigs.k8s.io/controller-runtime/pkg/client"
1314

@@ -71,70 +72,119 @@ func (es *CatalogdEntitySource) Iterate(ctx context.Context, fn input.IteratorFu
7172
return nil
7273
}
7374

74-
func getEntities(ctx context.Context, client client.Client) (input.EntityList, error) {
75-
entityList := input.EntityList{}
76-
bundleMetadatas, packageMetdatas, err := fetchMetadata(ctx, client)
77-
if err != nil {
75+
func getEntities(ctx context.Context, cl client.Client) (input.EntityList, error) {
76+
allEntitiesList := input.EntityList{}
77+
78+
var catalogList catalogd.CatalogList
79+
if err := cl.List(ctx, &catalogList); err != nil {
7880
return nil, err
7981
}
80-
for _, bundle := range bundleMetadatas.Items {
81-
props := map[string]string{}
82-
83-
// TODO: We should make sure all properties are forwarded
84-
// through and avoid a lossy translation from FBC --> entity
85-
for _, prop := range bundle.Spec.Properties {
86-
switch prop.Type {
87-
case property.TypePackage:
88-
// this is already a json marshalled object, so it doesn't need to be marshalled
89-
// like the other ones
90-
props[property.TypePackage] = string(prop.Value)
91-
case entities.PropertyBundleMediaType:
92-
props[entities.PropertyBundleMediaType] = string(prop.Value)
93-
}
82+
for _, catalog := range catalogList.Items {
83+
channels, bundles, err := fetchCatalogMetadata(ctx, cl, catalog.Name)
84+
if err != nil {
85+
return nil, err
9486
}
9587

96-
imgValue, err := json.Marshal(bundle.Spec.Image)
88+
catalogEntitiesList, err := MetadataToEntities(catalog.Name, channels, bundles)
9789
if err != nil {
9890
return nil, err
9991
}
100-
props[entities.PropertyBundlePath] = string(imgValue)
101-
catalogScopedPkgName := fmt.Sprintf("%s-%s", bundle.Spec.Catalog.Name, bundle.Spec.Package)
102-
bundlePkg := packageMetdatas[catalogScopedPkgName]
103-
for _, ch := range bundlePkg.Spec.Channels {
104-
for _, b := range ch.Entries {
105-
catalogScopedEntryName := fmt.Sprintf("%s-%s", bundle.Spec.Catalog.Name, b.Name)
106-
if catalogScopedEntryName == bundle.Name {
107-
channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0})
108-
props[property.TypeChannel] = string(channelValue)
109-
replacesValue, _ := json.Marshal(entities.ChannelEntry{
110-
Name: b.Name,
111-
Replaces: b.Replaces,
112-
})
113-
props[entities.PropertyBundleChannelEntry] = string(replacesValue)
114-
entity := input.Entity{
115-
ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Spec.Package, ch.Name)),
116-
Properties: props,
117-
}
118-
entityList = append(entityList, entity)
92+
93+
allEntitiesList = append(allEntitiesList, catalogEntitiesList...)
94+
}
95+
96+
return allEntitiesList, nil
97+
}
98+
99+
func MetadataToEntities(catalogName string, channels []declcfg.Channel, bundles []declcfg.Bundle) (input.EntityList, error) {
100+
entityList := input.EntityList{}
101+
102+
bundlesMap := map[string]*declcfg.Bundle{}
103+
for i := range bundles {
104+
bundleKey := fmt.Sprintf("%s-%s", bundles[i].Package, bundles[i].Name)
105+
bundlesMap[bundleKey] = &bundles[i]
106+
}
107+
108+
for _, ch := range channels {
109+
for _, chEntry := range ch.Entries {
110+
bundleKey := fmt.Sprintf("%s-%s", ch.Package, chEntry.Name)
111+
bundle, ok := bundlesMap[bundleKey]
112+
if !ok {
113+
return nil, fmt.Errorf("bundle %q not found in catalog %q (package %q, channel %q)", chEntry.Name, catalogName, ch.Package, ch.Name)
114+
}
115+
116+
props := map[string]string{}
117+
118+
for _, prop := range bundle.Properties {
119+
switch prop.Type {
120+
case property.TypePackage:
121+
// this is already a json marshalled object, so it doesn't need to be marshalled
122+
// like the other ones
123+
props[property.TypePackage] = string(prop.Value)
124+
case entities.PropertyBundleMediaType:
125+
props[entities.PropertyBundleMediaType] = string(prop.Value)
119126
}
120127
}
128+
129+
imgValue, err := json.Marshal(bundle.Image)
130+
if err != nil {
131+
return nil, err
132+
}
133+
props[entities.PropertyBundlePath] = string(imgValue)
134+
135+
channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0})
136+
props[property.TypeChannel] = string(channelValue)
137+
replacesValue, _ := json.Marshal(entities.ChannelEntry{
138+
Name: bundle.Name,
139+
Replaces: chEntry.Replaces,
140+
})
141+
props[entities.PropertyBundleChannelEntry] = string(replacesValue)
142+
143+
catalogScopedEntryName := fmt.Sprintf("%s-%s", catalogName, bundle.Name)
144+
entity := input.Entity{
145+
ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", catalogScopedEntryName, bundle.Package, ch.Name)),
146+
Properties: props,
147+
}
148+
entityList = append(entityList, entity)
121149
}
122150
}
151+
123152
return entityList, nil
124153
}
125154

126-
func fetchMetadata(ctx context.Context, client client.Client) (catalogd.BundleMetadataList, map[string]catalogd.Package, error) {
127-
packageMetdatas := catalogd.PackageList{}
128-
if err := client.List(ctx, &packageMetdatas); err != nil {
129-
return catalogd.BundleMetadataList{}, nil, err
155+
func fetchCatalogMetadata(ctx context.Context, cl client.Client, catalogName string) ([]declcfg.Channel, []declcfg.Bundle, error) {
156+
channels, err := fetchCatalogMetadataByScheme[declcfg.Channel](ctx, cl, declcfg.SchemaChannel, catalogName)
157+
if err != nil {
158+
return nil, nil, err
159+
}
160+
bundles, err := fetchCatalogMetadataByScheme[declcfg.Bundle](ctx, cl, declcfg.SchemaBundle, catalogName)
161+
if err != nil {
162+
return nil, nil, err
130163
}
131-
bundleMetadatas := catalogd.BundleMetadataList{}
132-
if err := client.List(ctx, &bundleMetadatas); err != nil {
133-
return catalogd.BundleMetadataList{}, nil, err
164+
165+
return channels, bundles, nil
166+
}
167+
168+
type declcfgSchema interface {
169+
declcfg.Package | declcfg.Bundle | declcfg.Channel
170+
}
171+
172+
// TODO: Cleanup once https://github.com/golang/go/issues/45380 implemented
173+
// We should be able to get rid of the schema arg and switch based on the type passed to this generic
174+
func fetchCatalogMetadataByScheme[T declcfgSchema](ctx context.Context, cl client.Client, schema, catalogName string) ([]T, error) {
175+
cmList := catalogd.CatalogMetadataList{}
176+
if err := cl.List(ctx, &cmList, client.MatchingLabels{"schema": schema, "catalog": catalogName}); err != nil {
177+
return nil, err
134178
}
135-
packages := map[string]catalogd.Package{}
136-
for _, pkg := range packageMetdatas.Items {
137-
packages[pkg.Name] = pkg
179+
180+
contents := []T{}
181+
for _, cm := range cmList.Items {
182+
var content T
183+
if err := json.Unmarshal(cm.Spec.Content, &content); err != nil {
184+
return nil, err
185+
}
186+
contents = append(contents, content)
138187
}
139-
return bundleMetadatas, packages, nil
188+
189+
return contents, nil
140190
}

test/e2e/e2e_suite_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ var _ = AfterSuite(func() {
9595
}).Should(Succeed())
9696

9797
// speed up delete without waiting for gc
98-
Expect(c.DeleteAllOf(ctx, &catalogd.BundleMetadata{})).To(Succeed())
9998
Expect(c.DeleteAllOf(ctx, &catalogd.Package{})).To(Succeed())
99+
Expect(c.DeleteAllOf(ctx, &catalogd.BundleMetadata{})).To(Succeed())
100+
Expect(c.DeleteAllOf(ctx, &catalogd.CatalogMetadata{})).To(Succeed())
100101

101102
Eventually(func(g Gomega) {
102103
// ensure resource cleanup
@@ -107,6 +108,10 @@ var _ = AfterSuite(func() {
107108
bmd := &catalogd.BundleMetadataList{}
108109
g.Expect(c.List(ctx, bmd)).To(Succeed())
109110
g.Expect(bmd.Items).To(BeEmpty())
111+
112+
cmd := &catalogd.CatalogMetadataList{}
113+
g.Expect(c.List(ctx, cmd)).To(Succeed())
114+
g.Expect(bmd.Items).To(BeEmpty())
110115
}).Should(Succeed())
111116
})
112117

test/operator-framework-e2e/operator_framework_test.go

+28-29
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"os"
99
"os/exec"
1010
"path/filepath"
11-
"reflect"
1211
"strings"
1312
"testing"
1413
"time"
@@ -27,6 +26,8 @@ import (
2726
"sigs.k8s.io/controller-runtime/pkg/client"
2827

2928
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
29+
"github.com/operator-framework/operator-registry/alpha/declcfg"
30+
"github.com/operator-framework/operator-registry/alpha/property"
3031
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
3132

3233
operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
@@ -833,8 +834,7 @@ func createCatalogCheckResources(operatorCatalog *catalogd.Catalog, catalogDInfo
833834
// checking if the bundle metadatas are created
834835
By("Eventually checking if bundle metadata is created")
835836
Eventually(func(g Gomega) {
836-
err = validateBundleMetadataCreation(operatorCatalog, catalogDInfo.operatorName, bundleVersions)
837-
g.Expect(err).ToNot(HaveOccurred())
837+
validateCatalogMetadataCreation(g, operatorCatalog, catalogDInfo.operatorName, bundleVersions)
838838
}).Should(Succeed())
839839
return operatorCatalog, nil
840840
}
@@ -886,39 +886,38 @@ func validatePackageCreation(operatorCatalog *catalogd.Catalog, pkgName string)
886886
return nil
887887
}
888888

889-
// Checks if the bundle metadatas are created from the catalog and returns error if not.
889+
// Checks if the CatalogMetadata was created from the catalog and returns error if not.
890890
// The expected pkgNames and their versions are taken as input. This is then compared against the collected bundle versions.
891891
// The collected bundle versions are formed by reading the version from "olm.package" property type whose catalog name
892892
// matches the catalog name and pkgName matches the pkgName under consideration.
893-
func validateBundleMetadataCreation(operatorCatalog *catalogd.Catalog, pkgName string, versions []string) error {
894-
type Package struct {
895-
PackageName string `json:"packageName"`
896-
Version string `json:"version"`
897-
}
898-
var pkgValue Package
899-
collectedBundleVersions := make([]string, 0)
900-
bmList := &catalogd.BundleMetadataList{}
901-
if err := c.List(ctx, bmList); err != nil {
902-
return fmt.Errorf("Error retrieving the bundle metadata after %v catalog instance creation: %v", operatorCatalog.Name, err)
903-
}
904-
905-
for _, bm := range bmList.Items {
906-
if bm.Spec.Catalog.Name == operatorCatalog.Name {
907-
for _, prop := range bm.Spec.Properties {
908-
if prop.Type == "olm.package" {
909-
err := json.Unmarshal(prop.Value, &pkgValue)
910-
if err == nil && pkgValue.PackageName == pkgName {
911-
collectedBundleVersions = append(collectedBundleVersions, pkgValue.Version)
912-
}
913-
}
893+
func validateCatalogMetadataCreation(g Gomega, operatorCatalog *catalogd.Catalog, pkgName string, versions []string) {
894+
cmList := catalogd.CatalogMetadataList{}
895+
err := c.List(ctx, &cmList, client.MatchingLabels{
896+
"schema": declcfg.SchemaBundle,
897+
"catalog": operatorCatalog.Name,
898+
"package": pkgName,
899+
})
900+
g.Expect(err).ToNot(HaveOccurred())
901+
902+
collectedBundleVersions := []string{}
903+
for _, cm := range cmList.Items {
904+
bundle := declcfg.Bundle{}
905+
906+
err := json.Unmarshal(cm.Spec.Content, &bundle)
907+
g.Expect(err).ToNot(HaveOccurred())
908+
909+
for _, prop := range bundle.Properties {
910+
if prop.Type == "olm.package" {
911+
var pkgValue property.Package
912+
err := json.Unmarshal(prop.Value, &pkgValue)
913+
g.Expect(err).ToNot(HaveOccurred())
914+
915+
collectedBundleVersions = append(collectedBundleVersions, pkgValue.Version)
914916
}
915917
}
916918
}
917-
if !reflect.DeepEqual(collectedBundleVersions, versions) {
918-
return fmt.Errorf("Package %v for the catalog %v is not created", pkgName, operatorCatalog.Name)
919-
}
920919

921-
return nil
920+
g.Expect(collectedBundleVersions).To(ConsistOf(versions))
922921
}
923922

924923
// Checks for a successful resolution and bundle path for the operator and returns error if not.

0 commit comments

Comments
 (0)