diff --git a/cmd/resolutioncli/entity_source.go b/cmd/resolutioncli/entity_source.go index b5d6e1128..14fa3583e 100644 --- a/cmd/resolutioncli/entity_source.go +++ b/cmd/resolutioncli/entity_source.go @@ -18,17 +18,13 @@ package main import ( "context" - "encoding/json" - "fmt" "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/operator-registry/alpha/action" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/resolution/entitysources" ) type indexRefEntitySource struct { @@ -106,7 +102,8 @@ func (es *indexRefEntitySource) entities(ctx context.Context) (input.EntityList, return nil, err } - entities, err := modelToEntities(model) + // TODO: update empty catalog name string to be catalog name once we support multiple catalogs in CLI + entities, err := entitysources.ModelToEntities(model, "") if err != nil { return nil, err } @@ -116,46 +113,3 @@ func (es *indexRefEntitySource) entities(ctx context.Context) (input.EntityList, return es.entitiesCache, nil } - -// TODO: Reduce code duplication: share a function with catalogdEntitySource (see getEntities) -// We don't want to maintain two functions performing conversion into input.EntityList. -// For this we need some common format. So we need a package which will be able -// to convert from declfcg structs into CRD structs directly or via model.Model. -// One option would be to make this piece of code from catalogd reusable and exportable: -// https://github.com/operator-framework/catalogd/blob/9fe45a628de2e74d9cd73c3650fa2582aaac5213/pkg/controllers/core/catalog_controller.go#L200-L360 -func modelToEntities(model model.Model) (input.EntityList, error) { - entities := input.EntityList{} - - for _, pkg := range model { - for _, ch := range pkg.Channels { - for _, bundle := range ch.Bundles { - props := map[string]string{} - - for _, prop := range bundle.Properties { - switch prop.Type { - case property.TypePackage: - // this is already a json marshalled object, so it doesn't need to be marshalled - // like the other ones - props[property.TypePackage] = string(prop.Value) - } - } - - imgValue, err := json.Marshal(bundle.Image) - if err != nil { - return nil, err - } - props[olmentity.PropertyBundlePath] = string(imgValue) - - channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0}) - props[property.TypeChannel] = string(channelValue) - entity := input.Entity{ - ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Package.Name, ch.Name)), - Properties: props, - } - entities = append(entities, entity) - } - } - } - - return entities, nil -} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 49d3c2458..5bbc421c2 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -7,7 +7,7 @@ rules: - apiGroups: - catalogd.operatorframework.io resources: - - bundlemetadata + - catalogmetadata verbs: - list - watch diff --git a/internal/controllers/operator_controller.go b/internal/controllers/operator_controller.go index a842b8d20..154f293cf 100644 --- a/internal/controllers/operator_controller.go +++ b/internal/controllers/operator_controller.go @@ -58,9 +58,9 @@ type OperatorReconciler struct { //+kubebuilder:rbac:groups=core.rukpak.io,resources=bundledeployments,verbs=get;list;watch;create;update;patch -//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=bundlemetadata,verbs=list;watch //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=packages,verbs=list;watch //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogs,verbs=list;watch +//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogmetadata,verbs=list;watch func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx).WithName("operator-controller") diff --git a/internal/resolution/entitysources/catalogdsource.go b/internal/resolution/entitysources/catalogdsource.go index 6c9460e68..1a2a54012 100644 --- a/internal/resolution/entitysources/catalogdsource.go +++ b/internal/resolution/entitysources/catalogdsource.go @@ -8,6 +8,8 @@ import ( catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" "sigs.k8s.io/controller-runtime/pkg/client" @@ -71,70 +73,123 @@ func (es *CatalogdEntitySource) Iterate(ctx context.Context, fn input.IteratorFu return nil } -func getEntities(ctx context.Context, client client.Client) (input.EntityList, error) { - entityList := input.EntityList{} - bundleMetadatas, packageMetdatas, err := fetchMetadata(ctx, client) - if err != nil { +func getEntities(ctx context.Context, cl client.Client) (input.EntityList, error) { + allEntitiesList := input.EntityList{} + + var catalogList catalogd.CatalogList + if err := cl.List(ctx, &catalogList); err != nil { return nil, err } - for _, bundle := range bundleMetadatas.Items { - props := map[string]string{} - - // TODO: We should make sure all properties are forwarded - // through and avoid a lossy translation from FBC --> entity - for _, prop := range bundle.Spec.Properties { - switch prop.Type { - case property.TypePackage: - // this is already a json marshalled object, so it doesn't need to be marshalled - // like the other ones - props[property.TypePackage] = string(prop.Value) - case entities.PropertyBundleMediaType: - props[entities.PropertyBundleMediaType] = string(prop.Value) - } + for _, catalog := range catalogList.Items { + model, err := fetchCatalogModel(ctx, cl, catalog.Name) + if err != nil { + return nil, err } - imgValue, err := json.Marshal(bundle.Spec.Image) + catalogEntitiesList, err := ModelToEntities(model, catalog.Name) if err != nil { return nil, err } - props[entities.PropertyBundlePath] = string(imgValue) - catalogScopedPkgName := fmt.Sprintf("%s-%s", bundle.Spec.Catalog.Name, bundle.Spec.Package) - bundlePkg := packageMetdatas[catalogScopedPkgName] - for _, ch := range bundlePkg.Spec.Channels { - for _, b := range ch.Entries { - catalogScopedEntryName := fmt.Sprintf("%s-%s", bundle.Spec.Catalog.Name, b.Name) - if catalogScopedEntryName == bundle.Name { - channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0}) - props[property.TypeChannel] = string(channelValue) - replacesValue, _ := json.Marshal(entities.ChannelEntry{ - Name: b.Name, - Replaces: b.Replaces, - }) - props[entities.PropertyBundleChannelEntry] = string(replacesValue) - entity := input.Entity{ - ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Spec.Package, ch.Name)), - Properties: props, - } - entityList = append(entityList, entity) - } - } - } + + allEntitiesList = append(allEntitiesList, catalogEntitiesList...) } - return entityList, nil + + return allEntitiesList, nil } -func fetchMetadata(ctx context.Context, client client.Client) (catalogd.BundleMetadataList, map[string]catalogd.Package, error) { - packageMetdatas := catalogd.PackageList{} - if err := client.List(ctx, &packageMetdatas); err != nil { - return catalogd.BundleMetadataList{}, nil, err +func fetchCatalogModel(ctx context.Context, cl client.Client, catalogName string) (model.Model, error) { + packages, err := fetch[declcfg.Package](ctx, cl, declcfg.SchemaPackage, catalogName) + if err != nil { + return nil, err } - bundleMetadatas := catalogd.BundleMetadataList{} - if err := client.List(ctx, &bundleMetadatas); err != nil { - return catalogd.BundleMetadataList{}, nil, err + channels, err := fetch[declcfg.Channel](ctx, cl, declcfg.SchemaChannel, catalogName) + if err != nil { + return nil, err } - packages := map[string]catalogd.Package{} - for _, pkg := range packageMetdatas.Items { - packages[pkg.Name] = pkg + bundles, err := fetch[declcfg.Bundle](ctx, cl, declcfg.SchemaBundle, catalogName) + if err != nil { + return nil, err + } + + cfg := &declcfg.DeclarativeConfig{ + Packages: packages, + Channels: channels, + Bundles: bundles, } - return bundleMetadatas, packages, nil + model, err := declcfg.ConvertToModel(*cfg) + if err != nil { + return nil, err + } + + return model, nil +} + +type declcfgSchemas interface { + declcfg.Package | declcfg.Bundle | declcfg.Channel +} + +// TODO: Cleanup once https://github.com/golang/go/issues/45380 implemented +// We should be able to get rid of the schema arg and switch based on the type based to this generic +func fetch[T declcfgSchemas](ctx context.Context, cl client.Client, schema, catalogName string) ([]T, error) { + cmList := catalogd.CatalogMetadataList{} + if err := cl.List(ctx, &cmList, client.MatchingLabels{"schema": schema, "catalog": catalogName}); err != nil { + return nil, err + } + + contents := []T{} + for _, cm := range cmList.Items { + var content T + if err := json.Unmarshal(cm.Spec.Content, &content); err != nil { + return nil, err + } + contents = append(contents, content) + } + + return contents, nil +} + +func ModelToEntities(model model.Model, catalogName string) (input.EntityList, error) { + entityList := input.EntityList{} + + for _, pkg := range model { + for _, ch := range pkg.Channels { + for _, bundle := range ch.Bundles { + props := map[string]string{} + + for _, prop := range bundle.Properties { + switch prop.Type { + case property.TypePackage: + // this is already a json marshalled object, so it doesn't need to be marshalled + // like the other ones + props[property.TypePackage] = string(prop.Value) + case entities.PropertyBundleMediaType: + props[entities.PropertyBundleMediaType] = string(prop.Value) + } + } + + imgValue, err := json.Marshal(bundle.Image) + if err != nil { + return nil, err + } + props[entities.PropertyBundlePath] = string(imgValue) + + channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0}) + props[property.TypeChannel] = string(channelValue) + replacesValue, _ := json.Marshal(entities.ChannelEntry{ + Name: bundle.Name, + Replaces: bundle.Replaces, + }) + props[entities.PropertyBundleChannelEntry] = string(replacesValue) + + catalogScopedEntryName := fmt.Sprintf("%s-%s", catalogName, bundle.Name) + entity := input.Entity{ + ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", catalogScopedEntryName, bundle.Package.Name, ch.Name)), + Properties: props, + } + entityList = append(entityList, entity) + } + } + } + + return entityList, nil } diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index d5dc9d4e0..05cd7eb0a 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -95,8 +95,9 @@ var _ = AfterSuite(func() { }).Should(Succeed()) // speed up delete without waiting for gc - Expect(c.DeleteAllOf(ctx, &catalogd.BundleMetadata{})).To(Succeed()) Expect(c.DeleteAllOf(ctx, &catalogd.Package{})).To(Succeed()) + Expect(c.DeleteAllOf(ctx, &catalogd.BundleMetadata{})).To(Succeed()) + Expect(c.DeleteAllOf(ctx, &catalogd.CatalogMetadata{})).To(Succeed()) Eventually(func(g Gomega) { // ensure resource cleanup @@ -107,6 +108,10 @@ var _ = AfterSuite(func() { bmd := &catalogd.BundleMetadataList{} g.Expect(c.List(ctx, bmd)).To(Succeed()) g.Expect(bmd.Items).To(BeEmpty()) + + cmd := &catalogd.CatalogMetadataList{} + g.Expect(c.List(ctx, cmd)).To(Succeed()) + g.Expect(bmd.Items).To(BeEmpty()) }).Should(Succeed()) })