diff --git a/config/crd/bases/catalogd.operatorframework.io_bundlemetadata.yaml b/config/crd/bases/catalogd.operatorframework.io_bundlemetadata.yaml index 404333e4..694c95b5 100644 --- a/config/crd/bases/catalogd.operatorframework.io_bundlemetadata.yaml +++ b/config/crd/bases/catalogd.operatorframework.io_bundlemetadata.yaml @@ -34,10 +34,16 @@ spec: spec: description: BundleMetadataSpec defines the desired state of BundleMetadata properties: - catalogSource: - description: CatalogSource is the name of the Catalog that provides - this bundle - type: string + catalog: + description: Catalog is the name of the Catalog that provides this + bundle + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic image: description: Image is a reference to the image that provides the bundle contents @@ -54,7 +60,6 @@ spec: type: type: string value: - type: object x-kubernetes-preserve-unknown-fields: true required: - type @@ -79,7 +84,7 @@ spec: type: object type: array required: - - catalogSource + - catalog - image - package - properties diff --git a/config/crd/bases/catalogd.operatorframework.io_packages.yaml b/config/crd/bases/catalogd.operatorframework.io_packages.yaml index d4a0ce19..aa1d49ed 100644 --- a/config/crd/bases/catalogd.operatorframework.io_packages.yaml +++ b/config/crd/bases/catalogd.operatorframework.io_packages.yaml @@ -34,14 +34,16 @@ spec: spec: description: PackageSpec defines the desired state of Package properties: - catalogSource: - description: CatalogSource is the name of the Catalog this package - belongs to - type: string - catalogSourceDisplayName: - type: string - catalogSourcePublisher: - type: string + catalog: + description: Catalog is the name of the Catalog this package belongs + to + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic channels: description: Channels are the declared channels for the package, ala `stable` or `alpha`. @@ -89,16 +91,21 @@ spec: description: Icon is the Base64data image of the package for console display properties: - base64data: + data: + format: byte type: string mediatype: type: string type: object + packageName: + description: Name is the name of the package, ala `etcd`. + type: string required: - - catalogSource + - catalog - channels - defaultChannel - description + - packageName type: object status: description: PackageStatus defines the observed state of Package diff --git a/go.mod b/go.mod index a3ec1927..1ddfb2e2 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 sigs.k8s.io/controller-runtime v0.14.0 ) @@ -75,7 +76,6 @@ require ( k8s.io/component-base v0.26.0 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/pkg/apis/core/v1beta1/bundlemetadata_types.go b/pkg/apis/core/v1beta1/bundlemetadata_types.go index c1071e81..4d410396 100644 --- a/pkg/apis/core/v1beta1/bundlemetadata_types.go +++ b/pkg/apis/core/v1beta1/bundlemetadata_types.go @@ -17,8 +17,10 @@ limitations under the License. package v1beta1 import ( + "encoding/json" + + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ) //+kubebuilder:object:root=true @@ -45,8 +47,8 @@ type BundleMetadataList struct { // BundleMetadataSpec defines the desired state of BundleMetadata type BundleMetadataSpec struct { - // CatalogSource is the name of the Catalog that provides this bundle - CatalogSource string `json:"catalogSource"` + // Catalog is the name of the Catalog that provides this bundle + Catalog corev1.LocalObjectReference `json:"catalog"` // Package is the name of the package that provides this bundle Package string `json:"package"` @@ -65,7 +67,8 @@ type Property struct { Type string `json:"type"` // +kubebuilder:pruning:PreserveUnknownFields - Value runtime.RawExtension `json:"value"` + // +kubebuilder:validation:Schemaless + Value json.RawMessage `json:"value"` } // TODO: In the future we should remove this in favor of using `declcfg.RelatedImage` (or similar) from diff --git a/pkg/apis/core/v1beta1/package_types.go b/pkg/apis/core/v1beta1/package_types.go index 8c0cd051..89373065 100644 --- a/pkg/apis/core/v1beta1/package_types.go +++ b/pkg/apis/core/v1beta1/package_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -44,14 +45,11 @@ type PackageList struct { // PackageSpec defines the desired state of Package type PackageSpec struct { - // CatalogSource is the name of the Catalog this package belongs to - CatalogSource string `json:"catalogSource"` - CatalogSourceDisplayName string `json:"catalogSourceDisplayName,omitempty"` - CatalogSourcePublisher string `json:"catalogSourcePublisher,omitempty"` + // Catalog is the name of the Catalog this package belongs to + Catalog corev1.LocalObjectReference `json:"catalog"` - // TODO(everettraven): can we remove this? Can the package metadata.name can be used instead? - // // PackageName is the name of the overall package, ala `etcd`. - // PackageName string `json:"packageName"` + // Name is the name of the package, ala `etcd`. + Name string `json:"packageName"` // Description is the description of the package Description string `json:"description"` @@ -60,7 +58,7 @@ type PackageSpec struct { Channels []PackageChannel `json:"channels"` //Icon is the Base64data image of the package for console display - Icon Icon `json:"icon,omitempty"` + Icon *Icon `json:"icon,omitempty"` // DefaultChannel is, if specified, the name of the default channel for the package. The // default channel will be installed if no other channel is explicitly given. If the package @@ -87,8 +85,8 @@ type ChannelEntry struct { // Icon defines a base64 encoded icon and media type type Icon struct { - Base64Data string `json:"base64data,omitempty"` - Mediatype string `json:"mediatype,omitempty"` + Data []byte `json:"data,omitempty"` + MediaType string `json:"mediatype,omitempty"` } // PackageStatus defines the observed state of Package diff --git a/pkg/apis/core/v1beta1/zz_generated.deepcopy.go b/pkg/apis/core/v1beta1/zz_generated.deepcopy.go index fc788d63..a2f1c22e 100644 --- a/pkg/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/core/v1beta1/zz_generated.deepcopy.go @@ -22,8 +22,9 @@ limitations under the License. package v1beta1 import ( + "encoding/json" "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -88,6 +89,7 @@ func (in *BundleMetadataList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BundleMetadataSpec) DeepCopyInto(out *BundleMetadataSpec) { *out = *in + out.Catalog = in.Catalog if in.Properties != nil { in, out := &in.Properties, &out.Properties *out = make([]Property, len(*in)) @@ -272,6 +274,11 @@ func (in *ChannelEntry) DeepCopy() *ChannelEntry { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Icon) DeepCopyInto(out *Icon) { *out = *in + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make([]byte, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Icon. @@ -383,6 +390,7 @@ func (in *PackageList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageSpec) DeepCopyInto(out *PackageSpec) { *out = *in + out.Catalog = in.Catalog if in.Channels != nil { in, out := &in.Channels, &out.Channels *out = make([]PackageChannel, len(*in)) @@ -390,7 +398,11 @@ func (in *PackageSpec) DeepCopyInto(out *PackageSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.Icon = in.Icon + if in.Icon != nil { + in, out := &in.Icon, &out.Icon + *out = new(Icon) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSpec. @@ -421,7 +433,11 @@ func (in *PackageStatus) DeepCopy() *PackageStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Property) DeepCopyInto(out *Property) { *out = *in - in.Value.DeepCopyInto(&out.Value) + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Property. diff --git a/pkg/controllers/core/catalog_controller.go b/pkg/controllers/core/catalog_controller.go index a77a4319..984b929f 100644 --- a/pkg/controllers/core/catalog_controller.go +++ b/pkg/controllers/core/catalog_controller.go @@ -25,12 +25,12 @@ import ( "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" apimacherrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -133,11 +133,11 @@ func (r *CatalogReconciler) reconcile(ctx context.Context, catalog *corev1beta1. return ctrl.Result{}, updateStatusUnpackFailing(&catalog.Status, fmt.Errorf("load FBC from filesystem: %v", err)) } - if err := r.createPackages(ctx, fbc, catalog); err != nil { + if err := r.syncPackages(ctx, fbc, catalog); err != nil { return ctrl.Result{}, updateStatusUnpackFailing(&catalog.Status, fmt.Errorf("create package objects: %v", err)) } - if err := r.createBundleMetadata(ctx, fbc, catalog); err != nil { + if err := r.syncBundleMetadata(ctx, fbc, catalog); err != nil { return ctrl.Result{}, updateStatusUnpackFailing(&catalog.Status, fmt.Errorf("create bundle metadata objects: %v", err)) } @@ -194,21 +194,38 @@ func updateStatusUnpackFailing(status *corev1beta1.CatalogStatus, err error) err return err } -// createBundleMetadata will create a `BundleMetadata` resource for each +// syncBundleMetadata will create a `BundleMetadata` resource for each // "olm.bundle" object that exists for the given catalog contents. Returns an // error if any are encountered. -func (r *CatalogReconciler) createBundleMetadata(ctx context.Context, declCfg *declcfg.DeclarativeConfig, catalog *corev1beta1.Catalog) error { +func (r *CatalogReconciler) syncBundleMetadata(ctx context.Context, declCfg *declcfg.DeclarativeConfig, catalog *corev1beta1.Catalog) error { + newBundles := map[string]*corev1beta1.BundleMetadata{} + for _, bundle := range declCfg.Bundles { + bundleName := fmt.Sprintf("%s-%s", catalog.Name, bundle.Name) + bundleMeta := corev1beta1.BundleMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1beta1.GroupVersion.String(), + Kind: "BundleMetadata", + }, ObjectMeta: metav1.ObjectMeta{ - Name: bundle.Name, + Name: bundleName, + Labels: map[string]string{ + "catalog": catalog.Name, + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: corev1beta1.GroupVersion.String(), + Kind: "Catalog", + Name: catalog.Name, + UID: catalog.UID, + BlockOwnerDeletion: pointer.Bool(true), + Controller: pointer.Bool(true), + }}, }, Spec: corev1beta1.BundleMetadataSpec{ - CatalogSource: catalog.Name, - Package: bundle.Package, - Image: bundle.Image, - Properties: []corev1beta1.Property{}, - RelatedImages: []corev1beta1.RelatedImage{}, + Catalog: corev1.LocalObjectReference{Name: catalog.Name}, + Package: bundle.Package, + Image: bundle.Image, }, } @@ -227,69 +244,116 @@ func (r *CatalogReconciler) createBundleMetadata(ctx context.Context, declCfg *d bundleMeta.Spec.Properties = append(bundleMeta.Spec.Properties, corev1beta1.Property{ Type: prop.Type, - Value: runtime.RawExtension{Raw: prop.Value}, + Value: prop.Value, }) } + newBundles[bundleName] = &bundleMeta + } - if err := ctrlutil.SetOwnerReference(catalog, &bundleMeta, r.Client.Scheme()); err != nil { - return fmt.Errorf("setting ownerreference on bundlemetadata %q: %w", bundleMeta.Name, err) + var existingBundles corev1beta1.BundleMetadataList + if err := r.List(ctx, &existingBundles); err != nil { + return fmt.Errorf("list existing bundle metadatas: %v", err) + } + for _, existingBundle := range existingBundles.Items { + if _, ok := newBundles[existingBundle.Name]; !ok { + if err := r.Delete(ctx, &existingBundle); err != nil { + return fmt.Errorf("delete existing bundle metadata %q: %v", existingBundle.Name, err) + } } + } - if err := r.Client.Create(ctx, &bundleMeta); err != nil { - return fmt.Errorf("creating bundlemetadata %q: %w", bundleMeta.Name, err) + ordered := sets.List(sets.KeySet(newBundles)) + for _, bundleName := range ordered { + newBundle := newBundles[bundleName] + if err := r.Client.Patch(ctx, newBundle, client.Apply, &client.PatchOptions{Force: pointer.Bool(true), FieldManager: "catalog-controller"}); err != nil { + return fmt.Errorf("applying bundle metadata %q: %w", newBundle.Name, err) } } - return nil } -// createPackages will create a `Package` resource for each +// syncPackages will create a `Package` resource for each // "olm.package" object that exists for the given catalog contents. // `Package.Spec.Channels` is populated by filtering all "olm.channel" objects // where the "packageName" == `Package.Name`. Returns an error if any are encountered. -func (r *CatalogReconciler) createPackages(ctx context.Context, declCfg *declcfg.DeclarativeConfig, catalog *corev1beta1.Catalog) error { +func (r *CatalogReconciler) syncPackages(ctx context.Context, declCfg *declcfg.DeclarativeConfig, catalog *corev1beta1.Catalog) error { + newPkgs := map[string]*corev1beta1.Package{} + for _, pkg := range declCfg.Packages { - pack := corev1beta1.Package{ + name := fmt.Sprintf("%s-%s", catalog.Name, pkg.Name) + var icon *corev1beta1.Icon + if pkg.Icon != nil { + icon = &corev1beta1.Icon{ + Data: pkg.Icon.Data, + MediaType: pkg.Icon.MediaType, + } + } + newPkgs[name] = &corev1beta1.Package{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1beta1.GroupVersion.String(), + Kind: "Package", + }, ObjectMeta: metav1.ObjectMeta{ - // TODO: If we just provide the name of the package, then - // we are inherently saying no other catalog sources can provide a package - // of the same name due to this being a cluster scoped resource. We should - // look into options for configuring admission criteria for the Package - // resource to resolve this potential clash. - Name: pkg.Name, + Name: name, + Labels: map[string]string{ + "catalog": catalog.Name, + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: corev1beta1.GroupVersion.String(), + Kind: "Catalog", + Name: catalog.Name, + UID: catalog.UID, + BlockOwnerDeletion: pointer.Bool(true), + Controller: pointer.Bool(true), + }}, }, Spec: corev1beta1.PackageSpec{ - CatalogSource: catalog.Name, + Catalog: corev1.LocalObjectReference{Name: catalog.Name}, + Name: pkg.Name, DefaultChannel: pkg.DefaultChannel, - Channels: []corev1beta1.PackageChannel{}, Description: pkg.Description, + Icon: icon, + Channels: []corev1beta1.PackageChannel{}, }, } - for _, ch := range declCfg.Channels { - if ch.Package == pkg.Name { - packChannel := corev1beta1.PackageChannel{ - Name: ch.Name, - Entries: []corev1beta1.ChannelEntry{}, - } - for _, entry := range ch.Entries { - packChannel.Entries = append(packChannel.Entries, corev1beta1.ChannelEntry{ - Name: entry.Name, - Replaces: entry.Replaces, - Skips: entry.Skips, - SkipRange: entry.SkipRange, - }) - } - - pack.Spec.Channels = append(pack.Spec.Channels, packChannel) - } + } + + for _, ch := range declCfg.Channels { + pkgName := fmt.Sprintf("%s-%s", catalog.Name, ch.Package) + pkg, ok := newPkgs[pkgName] + if !ok { + return fmt.Errorf("channel %q references package %q which does not exist", ch.Name, ch.Package) } + pkgChannel := corev1beta1.PackageChannel{Name: ch.Name} + for _, entry := range ch.Entries { + pkgChannel.Entries = append(pkgChannel.Entries, corev1beta1.ChannelEntry{ + Name: entry.Name, + Replaces: entry.Replaces, + Skips: entry.Skips, + SkipRange: entry.SkipRange, + }) + } + pkg.Spec.Channels = append(pkg.Spec.Channels, pkgChannel) + } - if err := ctrlutil.SetOwnerReference(catalog, &pack, r.Client.Scheme()); err != nil { - return fmt.Errorf("setting ownerreference on package %q: %w", pack.Name, err) + var existingPkgs corev1beta1.PackageList + if err := r.List(ctx, &existingPkgs); err != nil { + return fmt.Errorf("list existing packages: %v", err) + } + for _, existingPkg := range existingPkgs.Items { + if _, ok := newPkgs[existingPkg.Name]; !ok { + // delete existing package + if err := r.Delete(ctx, &existingPkg); err != nil { + return fmt.Errorf("delete existing package %q: %v", existingPkg.Name, err) + } } + } - if err := r.Client.Create(ctx, &pack); err != nil { - return fmt.Errorf("creating package %q: %w", pack.Name, err) + ordered := sets.List(sets.KeySet(newPkgs)) + for _, pkgName := range ordered { + newPkg := newPkgs[pkgName] + if err := r.Client.Patch(ctx, newPkg, client.Apply, &client.PatchOptions{Force: pointer.Bool(true), FieldManager: "catalog-controller"}); err != nil { + return fmt.Errorf("applying package %q: %w", newPkg.Name, err) } } return nil diff --git a/pkg/controllers/core/catalog_controller_test.go b/pkg/controllers/core/catalog_controller_test.go index 0776853c..9774f014 100644 --- a/pkg/controllers/core/catalog_controller_test.go +++ b/pkg/controllers/core/catalog_controller_test.go @@ -9,15 +9,16 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/operator-framework/catalogd/internal/source" - catalogdv1beta1 "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" - "github.com/operator-framework/catalogd/pkg/controllers/core" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/operator-framework/catalogd/internal/source" + catalogdv1beta1 "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" + "github.com/operator-framework/catalogd/pkg/controllers/core" ) var _ source.Unpacker = &MockSource{} @@ -228,8 +229,14 @@ var _ = Describe("Catalogd Controller Test", func() { testPackage = fmt.Sprintf(testPackageTemplate, testPackageDefaultChannel, testPackageName) testBundle = fmt.Sprintf(testBundleTemplate, testBundleImage, testBundleName, testPackageName, testBundleRelatedImageName, testBundleRelatedImageImage, testBundleObjectData) testChannel = fmt.Sprintf(testChannelTemplate, testPackageName, testChannelName, testBundleName) + + testBundleMetaName string + testPackageMetaName string ) BeforeEach(func() { + testBundleMetaName = fmt.Sprintf("%s-%s", catalog.Name, testBundleName) + testPackageMetaName = fmt.Sprintf("%s-%s", catalog.Name, testPackageName) + filesys := &fstest.MapFS{ "bundle.yaml": &fstest.MapFile{Data: []byte(testBundle), Mode: os.ModePerm}, "package.yaml": &fstest.MapFile{Data: []byte(testPackage), Mode: os.ModePerm}, @@ -253,7 +260,7 @@ var _ = Describe("Catalogd Controller Test", func() { // clean up package pkg := &catalogdv1beta1.Package{ ObjectMeta: metav1.ObjectMeta{ - Name: testPackageName, + Name: testPackageMetaName, }, } Expect(cl.Delete(ctx, pkg)).NotTo(HaveOccurred()) @@ -261,7 +268,7 @@ var _ = Describe("Catalogd Controller Test", func() { // clean up bundlemetadata bm := &catalogdv1beta1.BundleMetadata{ ObjectMeta: metav1.ObjectMeta{ - Name: testBundleName, + Name: testBundleMetaName, }, } Expect(cl.Delete(ctx, bm)).NotTo(HaveOccurred()) @@ -285,9 +292,9 @@ var _ = Describe("Catalogd Controller Test", func() { Expect(cl.List(ctx, bundlemetadatas)).To(Succeed()) Expect(bundlemetadatas.Items).To(HaveLen(1)) bundlemetadata := bundlemetadatas.Items[0] - Expect(bundlemetadata.Name).To(Equal(testBundleName)) + Expect(bundlemetadata.Name).To(Equal(testBundleMetaName)) Expect(bundlemetadata.Spec.Image).To(Equal(testBundleImage)) - Expect(bundlemetadata.Spec.CatalogSource).To(Equal(catalog.Name)) + Expect(bundlemetadata.Spec.Catalog.Name).To(Equal(catalog.Name)) Expect(bundlemetadata.Spec.Package).To(Equal(testPackageName)) Expect(bundlemetadata.Spec.RelatedImages).To(HaveLen(1)) Expect(bundlemetadata.Spec.RelatedImages[0].Name).To(Equal(testBundleRelatedImageName)) @@ -301,9 +308,9 @@ var _ = Describe("Catalogd Controller Test", func() { Expect(cl.List(ctx, packages)).To(Succeed()) Expect(packages.Items).To(HaveLen(1)) pack := packages.Items[0] - Expect(pack.Name).To(Equal(testPackageName)) + Expect(pack.Name).To(Equal(testPackageMetaName)) Expect(pack.Spec.DefaultChannel).To(Equal(testPackageDefaultChannel)) - Expect(pack.Spec.CatalogSource).To(Equal(catalog.Name)) + Expect(pack.Spec.Catalog.Name).To(Equal(catalog.Name)) Expect(pack.Spec.Channels).To(HaveLen(1)) Expect(pack.Spec.Channels[0].Name).To(Equal(testChannelName)) Expect(pack.Spec.Channels[0].Entries).To(HaveLen(1))