github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/package-server/provider/registry_test.go (about)

     1  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../client/fakes/fake_registry_client.go github.com/operator-framework/operator-registry/pkg/api.RegistryClient
     2  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../client/fakes/fake_list_packages_client.go github.com/operator-framework/operator-registry/pkg/api.Registry_ListPackagesClient
     3  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../client/fakes/fake_list_bundles_client.go github.com/operator-framework/operator-registry/pkg/api.Registry_ListBundlesClient
     4  package provider
     5  
     6  import (
     7  	"context"
     8  	"database/sql"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"net"
    13  	"os"
    14  	"path/filepath"
    15  	"testing"
    16  	"time"
    17  
    18  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    19  	"github.com/operator-framework/operator-registry/pkg/api"
    20  	registryserver "github.com/operator-framework/operator-registry/pkg/server"
    21  	"github.com/operator-framework/operator-registry/pkg/sqlite"
    22  	"github.com/sirupsen/logrus"
    23  	"github.com/stretchr/testify/require"
    24  	"google.golang.org/grpc"
    25  	"google.golang.org/grpc/credentials/insecure"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/selection"
    30  	k8sfake "k8s.io/client-go/kubernetes/fake"
    31  
    32  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
    33  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    34  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
    35  	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators"
    36  	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client/fakes"
    37  )
    38  
    39  const (
    40  	port    = "50054"
    41  	address = "localhost:"
    42  	dbName  = "test.db"
    43  )
    44  
    45  func server() {
    46  	_ = os.Remove(dbName)
    47  	lis, err := net.Listen("tcp", "localhost:"+port)
    48  	if err != nil {
    49  		logrus.Fatalf("failed to listen: %v", err)
    50  	}
    51  	s := grpc.NewServer()
    52  
    53  	db, err := sql.Open("sqlite3", dbName)
    54  	if err != nil {
    55  		logrus.Fatal(err)
    56  	}
    57  	load, err := sqlite.NewSQLLiteLoader(db)
    58  	if err != nil {
    59  		logrus.Fatal(err)
    60  	}
    61  	if err := load.Migrate(context.TODO()); err != nil {
    62  		logrus.Fatal(err)
    63  	}
    64  
    65  	loader := sqlite.NewSQLLoaderForDirectory(load, filepath.Join("testdata", "manifests"))
    66  	if err := loader.Populate(); err != nil {
    67  		logrus.Fatal(err)
    68  	}
    69  	const bundlePath = "localhost:5000/etcdoperator:v0.6.0"
    70  	if _, err := db.Exec(`UPDATE operatorbundle SET bundlePath=? WHERE name="etcdoperator.v0.6.0"`, bundlePath); err != nil {
    71  		logrus.Fatal(err)
    72  	}
    73  	load.DeprecateBundle(bundlePath)
    74  	if err := db.Close(); err != nil {
    75  		logrus.Fatal(err)
    76  	}
    77  
    78  	store, err := sqlite.NewSQLLiteQuerier(dbName)
    79  	if err != nil {
    80  		logrus.Fatal(err)
    81  	}
    82  
    83  	api.RegisterRegistryServer(s, registryserver.NewRegistryServer(store))
    84  	if err := s.Serve(lis); err != nil {
    85  		logrus.Fatalf("failed to serve: %v", err)
    86  	}
    87  }
    88  
    89  func NewFakeRegistryProvider(ctx context.Context, clientObjs []runtime.Object, k8sObjs []runtime.Object, globalNamespace string) (*RegistryProvider, error) {
    90  	clientFake := fake.NewSimpleClientset(clientObjs...)
    91  	k8sClientFake := k8sfake.NewSimpleClientset(k8sObjs...)
    92  	opClientFake := operatorclient.NewClient(k8sClientFake, nil, nil)
    93  
    94  	op, err := queueinformer.NewOperator(opClientFake.KubernetesInterface().Discovery())
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	resyncInterval := 5 * time.Minute
   100  
   101  	return NewRegistryProvider(ctx, clientFake, op, resyncInterval, globalNamespace)
   102  }
   103  
   104  func catalogSource(name, namespace string) *operatorsv1alpha1.CatalogSource {
   105  	return &operatorsv1alpha1.CatalogSource{
   106  		ObjectMeta: metav1.ObjectMeta{
   107  			Name:      name,
   108  			Namespace: namespace,
   109  		},
   110  	}
   111  }
   112  
   113  func withRegistryServiceStatus(catalogSource *operatorsv1alpha1.CatalogSource, protocol, serviceName, serviceNamespace, port string, createdAt metav1.Time) *operatorsv1alpha1.CatalogSource {
   114  	out := catalogSource.DeepCopy()
   115  	out.Status.RegistryServiceStatus = &operatorsv1alpha1.RegistryServiceStatus{
   116  		Protocol:         protocol,
   117  		ServiceName:      serviceName,
   118  		ServiceNamespace: serviceNamespace,
   119  		Port:             port,
   120  		CreatedAt:        createdAt,
   121  	}
   122  
   123  	return out
   124  }
   125  
   126  func TestMain(m *testing.M) {
   127  	go server()
   128  	exit := m.Run()
   129  	if err := os.Remove(dbName); err != nil {
   130  		logrus.Warnf("couldn't remove db")
   131  	}
   132  	os.Exit(exit)
   133  }
   134  
   135  var (
   136  	etcdCSVJSON           = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"version\":\"0.9.2\"}}"
   137  	etcdCSVJSONv10        = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.10.1\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"version\":\"0.10.1\"}}"
   138  	etcdWithLabelsCSVJSON = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"labels\": {\"test\": \"label\"},\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"version\":\"0.9.2\"}}"
   139  	etcdBackupsCRDJSON    = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}"
   140  	etcdUpgradesCRDJSON   = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}"
   141  	etcdRestoresCRDJSON   = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}"
   142  	prometheusCSVJSON     = `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"alm-examples":"[{\"apiVersion\":\"monitoring.coreos.com/v1\",\"kind\":\"Prometheus\",\"metadata\":{\"name\":\"example\",\"labels\":{\"prometheus\":\"k8s\"}},\"spec\":{\"replicas\":2,\"version\":\"v2.3.2\",\"serviceAccountName\":\"prometheus-k8s\",\"securityContext\": {}, \"serviceMonitorSelector\":{\"matchExpressions\":[{\"key\":\"k8s-app\",\"operator\":\"Exists\"}]},\"ruleSelector\":{\"matchLabels\":{\"role\":\"prometheus-rulefiles\",\"prometheus\":\"k8s\"}},\"alerting\":{\"alertmanagers\":[{\"namespace\":\"monitoring\",\"name\":\"alertmanager-main\",\"port\":\"web\"}]}}},{\"apiVersion\":\"monitoring.coreos.com/v1\",\"kind\":\"ServiceMonitor\",\"metadata\":{\"name\":\"example\",\"labels\":{\"k8s-app\":\"prometheus\"}},\"spec\":{\"selector\":{\"matchLabels\":{\"k8s-app\":\"prometheus\"}},\"endpoints\":[{\"port\":\"web\",\"interval\":\"30s\"}]}},{\"apiVersion\":\"monitoring.coreos.com/v1\",\"kind\":\"Alertmanager\",\"metadata\":{\"name\":\"alertmanager-main\"},\"spec\":{\"replicas\":3, \"securityContext\": {}}}]"},"name":"prometheusoperator.0.22.2","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"owned":[{"description":"A running Prometheus instance","displayName":"Prometheus","kind":"Prometheus","name":"prometheuses.monitoring.coreos.com","resources":[{"kind":"StatefulSet","version":"v1beta2"},{"kind":"Pod","version":"v1"}],"specDescriptors":[{"description":"Desired number of Pods for the cluster","displayName":"Size","path":"replicas","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:podCount"]},{"description":"A selector for the ConfigMaps from which to load rule files","displayName":"Rule Config Map Selector","path":"ruleSelector","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap"]},{"description":"ServiceMonitors to be selected for target discovery","displayName":"Service Monitor Selector","path":"serviceMonitorSelector","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor"]},{"description":"The ServiceAccount to use to run the Prometheus pods","displayName":"Service Account","path":"serviceAccountName","x-descriptors":["urn:alm:descriptor:io.kubernetes:ServiceAccount"]},{"description":"Limits describes the minimum/maximum amount of compute resources required/allowed","displayName":"Resource Requirements","path":"resources","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:resourceRequirements"]}],"version":"v1"},{"description":"A Prometheus Rule configures groups of sequentially evaluated recording and alerting rules.","displayName":"Prometheus Rule","kind":"PrometheusRule","name":"prometheusrules.monitoring.coreos.com","version":"v1"},{"description":"Configures prometheus to monitor a particular k8s service","displayName":"Service Monitor","kind":"ServiceMonitor","name":"servicemonitors.monitoring.coreos.com","resources":[{"kind":"Pod","version":"v1"}],"specDescriptors":[{"description":"The label to use to retrieve the job name from","displayName":"Job Label","path":"jobLabel","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:label"]},{"description":"A list of endpoints allowed as part of this ServiceMonitor","displayName":"Endpoints","path":"endpoints","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:endpointList"]}],"version":"v1"},{"description":"Configures an Alertmanager for the namespace","displayName":"Alertmanager","kind":"Alertmanager","name":"alertmanagers.monitoring.coreos.com","resources":[{"kind":"StatefulSet","version":"v1beta2"},{"kind":"Pod","version":"v1"}],"specDescriptors":[{"description":"Desired number of Pods for the cluster","displayName":"Size","path":"replicas","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:podCount"]},{"description":"Limits describes the minimum/maximum amount of compute resources required/allowed","displayName":"Resource Requirements","path":"resources","x-descriptors":["urn:alm:descriptor:com.tectonic.ui:resourceRequirements"]}],"version":"v1"}]},"description":"The Prometheus Operator for Kubernetes provides easy monitoring definitions for Kubernetes services and deployment and management of Prometheus instances.\n\nOnce installed, the Prometheus Operator provides the following features:\n\n* **Create/Destroy**: Easily launch a Prometheus instance for your Kubernetes namespace, a specific application or team easily using the Operator.\n\n* **Simple Configuration**: Configure the fundamentals of Prometheus like versions, persistence, retention policies, and replicas from a native Kubernetes resource.\n\n* **Target Services via Labels**: Automatically generate monitoring target configurations based on familiar Kubernetes label queries; no need to learn a Prometheus specific configuration language.\n\n### Other Supported Features\n\n**High availability**\n\nMultiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most.\n\n**Updates via automated operations**\n\nNew Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date.\n\n**Handles the dynamic nature of containers**\n\nAlerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment.\n","displayName":"Prometheus Operator","icon":[{"base64data":"PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg==","mediatype":"image/svg+xml"}],"install":{"spec":{"deployments":[{"name":"prometheus-operator","spec":{"replicas":1,"selector":{"matchLabels":{"k8s-app":"prometheus-operator"}},"template":{"metadata":{"labels":{"k8s-app":"prometheus-operator"}},"spec":{"containers":[{"args":["-namespace=$(K8S_NAMESPACE)","-manage-crds=false","-logtostderr=true","--config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1","--prometheus-config-reloader=quay.io/coreos/prometheus-config-reloader:v0.22.2"],"env":[{"name":"K8S_NAMESPACE","valueFrom":{"fieldRef":{"fieldPath":"metadata.namespace"}}}],"image":"quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf","name":"prometheus-operator","ports":[{"containerPort":8080,"name":"http"}],"resources":{"limits":{"cpu":"200m","memory":"100Mi"},"requests":{"cpu":"100m","memory":"50Mi"}},"securityContext":{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true}}],"nodeSelector":{"kubernetes.io/os":"linux"},"serviceAccount":"prometheus-operator-0-22-2"}}}}],"permissions":[{"rules":[{"apiGroups":[""],"resources":["nodes","services","endpoints","pods"],"verbs":["get","list","watch"]},{"apiGroups":[""],"resources":["configmaps"],"verbs":["get"]}],"serviceAccountName":"prometheus-k8s"},{"rules":[{"apiGroups":["apiextensions.k8s.io"],"resources":["customresourcedefinitions"],"verbs":["*"]},{"apiGroups":["monitoring.coreos.com"],"resources":["alertmanagers","prometheuses","prometheuses/finalizers","alertmanagers/finalizers","servicemonitors","prometheusrules"],"verbs":["*"]},{"apiGroups":["apps"],"resources":["statefulsets"],"verbs":["*"]},{"apiGroups":[""],"resources":["configmaps","secrets"],"verbs":["*"]},{"apiGroups":[""],"resources":["pods"],"verbs":["list","delete"]},{"apiGroups":[""],"resources":["services","endpoints"],"verbs":["get","create","update"]},{"apiGroups":[""],"resources":["nodes"],"verbs":["list","watch"]},{"apiGroups":[""],"resources":["namespaces"],"verbs":["list","watch"]}],"serviceAccountName":"prometheus-operator-0-22-2"}]},"strategy":"deployment"},"keywords":["prometheus","monitoring","tsdb","alerting"],"labels":{"alm-owner-prometheus":"prometheusoperator","alm-status-descriptors":"prometheusoperator.0.22.2"},"links":[{"name":"Prometheus","url":"https://www.prometheus.io/"},{"name":"Documentation","url":"https://coreos.com/operators/prometheus/docs/latest/"},{"name":"Prometheus Operator","url":"https://github.com/coreos/prometheus-operator"}],"maintainers":[{"email":"openshift-operators@redhat.com","name":"Red Hat"}],"maturity":"beta","provider":{"name":"Red Hat"},"replaces":"prometheusoperator.0.15.0","selector":{"matchLabels":{"alm-owner-prometheus":"prometheusoperator"}},"version":"0.22.2"}}`
   143  
   144  	etcdChannelEntries = []operators.ChannelEntry{
   145  		{
   146  			Name:    "etcdoperator.v0.9.2",
   147  			Version: "0.9.2",
   148  		},
   149  		{
   150  			Name:    "etcdoperator.v0.9.0",
   151  			Version: "0.9.0",
   152  		},
   153  		{
   154  			Name:    "etcdoperator.v0.6.1",
   155  			Version: "0.6.1",
   156  		},
   157  	}
   158  
   159  	prometheusChannelEntries = []operators.ChannelEntry{
   160  		{
   161  			Name:    "prometheusoperator.0.22.2",
   162  			Version: "0.22.2",
   163  		},
   164  		{
   165  			Name:    "prometheusoperator.0.15.0",
   166  			Version: "0.15.0",
   167  		},
   168  		{
   169  			Name:    "prometheusoperator.0.14.0",
   170  			Version: "0.14.0",
   171  		},
   172  	}
   173  )
   174  
   175  func TestToPackageManifest(t *testing.T) {
   176  	tests := []struct {
   177  		name           string
   178  		apiPkg         *api.Package
   179  		channelEntries map[string][]operators.ChannelEntry
   180  		catalogSource  *operatorsv1alpha1.CatalogSource
   181  		bundle         *api.Bundle
   182  		expectedErr    string
   183  		expected       *operators.PackageManifest
   184  	}{
   185  		{
   186  			name: "GoodBundle",
   187  			apiPkg: &api.Package{
   188  				Name: "etcd",
   189  				Channels: []*api.Channel{
   190  					{
   191  						Name:    "alpha",
   192  						CsvName: "etcdoperator.v0.9.2",
   193  					},
   194  				},
   195  				DefaultChannelName: "alpha",
   196  			},
   197  			catalogSource: catalogSource("cool-operators", "ns"),
   198  			bundle: &api.Bundle{
   199  				CsvName:     "etcdoperator.v0.9.2",
   200  				PackageName: "etcd",
   201  				ChannelName: "alpha",
   202  				CsvJson:     etcdCSVJSON,
   203  				Object: []string{
   204  					etcdCSVJSON,
   205  					etcdBackupsCRDJSON,
   206  					etcdUpgradesCRDJSON,
   207  					etcdRestoresCRDJSON,
   208  				},
   209  			},
   210  			expectedErr: "",
   211  			expected: &operators.PackageManifest{
   212  				ObjectMeta: metav1.ObjectMeta{
   213  					Name:      "etcd",
   214  					Namespace: "ns",
   215  					Labels: labels.Set{
   216  						"catalog":                         "cool-operators",
   217  						"catalog-namespace":               "ns",
   218  						"provider":                        "CoreOS, Inc",
   219  						"provider-url":                    "",
   220  						"operatorframework.io/arch.amd64": "supported",
   221  						"operatorframework.io/os.linux":   "supported",
   222  					},
   223  				},
   224  				Status: operators.PackageManifestStatus{
   225  					CatalogSource:          "cool-operators",
   226  					CatalogSourceNamespace: "ns",
   227  					PackageName:            "etcd",
   228  					Provider: operators.AppLink{
   229  						Name: "CoreOS, Inc",
   230  					},
   231  					DefaultChannel: "alpha",
   232  					Channels: []operators.PackageChannel{
   233  						{
   234  							Name:       "alpha",
   235  							CurrentCSV: "etcdoperator.v0.9.2",
   236  							CurrentCSVDesc: func() operators.CSVDescription {
   237  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   238  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   239  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   240  							}(),
   241  						},
   242  					},
   243  				},
   244  			},
   245  		},
   246  		{
   247  			name: "GoodBundle/ExtraLabels",
   248  			apiPkg: &api.Package{
   249  				Name: "etcd",
   250  				Channels: []*api.Channel{
   251  					{
   252  						Name:    "alpha",
   253  						CsvName: "etcdoperator.v0.9.2",
   254  					},
   255  				},
   256  				DefaultChannelName: "alpha",
   257  			},
   258  			catalogSource: catalogSource("cool-operators", "ns"),
   259  			bundle: &api.Bundle{
   260  				CsvName:     "etcdoperator.v0.9.2",
   261  				PackageName: "etcd",
   262  				ChannelName: "alpha",
   263  				CsvJson:     etcdWithLabelsCSVJSON,
   264  				Object: []string{
   265  					etcdWithLabelsCSVJSON,
   266  					etcdBackupsCRDJSON,
   267  					etcdUpgradesCRDJSON,
   268  					etcdRestoresCRDJSON,
   269  				},
   270  			},
   271  			expectedErr: "",
   272  			expected: &operators.PackageManifest{
   273  				ObjectMeta: metav1.ObjectMeta{
   274  					Name:      "etcd",
   275  					Namespace: "ns",
   276  					Labels: labels.Set{
   277  						"test":                            "label",
   278  						"catalog":                         "cool-operators",
   279  						"catalog-namespace":               "ns",
   280  						"provider":                        "CoreOS, Inc",
   281  						"provider-url":                    "",
   282  						"operatorframework.io/arch.amd64": "supported",
   283  						"operatorframework.io/os.linux":   "supported",
   284  					},
   285  				},
   286  				Status: operators.PackageManifestStatus{
   287  					CatalogSource:          "cool-operators",
   288  					CatalogSourceNamespace: "ns",
   289  					PackageName:            "etcd",
   290  					Provider: operators.AppLink{
   291  						Name: "CoreOS, Inc",
   292  					},
   293  					DefaultChannel: "alpha",
   294  					Channels: []operators.PackageChannel{
   295  						{
   296  							Name:       "alpha",
   297  							CurrentCSV: "etcdoperator.v0.9.2",
   298  							CurrentCSVDesc: func() operators.CSVDescription {
   299  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   300  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   301  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   302  							}(),
   303  						},
   304  					},
   305  				},
   306  			},
   307  		},
   308  		{
   309  			name: "MissingBundle/ChannelElided",
   310  			apiPkg: &api.Package{
   311  				Name: "etcd",
   312  				Channels: []*api.Channel{
   313  					{
   314  						Name:    "alpha",
   315  						CsvName: "etcdoperator.v0.9.2",
   316  					},
   317  					{
   318  						Name:    "missing-bundle",
   319  						CsvName: "etcdoperator.v0.9.1",
   320  					},
   321  				},
   322  				DefaultChannelName: "alpha",
   323  			},
   324  			catalogSource: catalogSource("cool-operators", "ns"),
   325  			bundle: &api.Bundle{
   326  				CsvName:     "etcdoperator.v0.9.2",
   327  				PackageName: "etcd",
   328  				ChannelName: "alpha",
   329  				CsvJson:     etcdCSVJSON,
   330  				Object: []string{
   331  					etcdCSVJSON,
   332  					etcdBackupsCRDJSON,
   333  					etcdUpgradesCRDJSON,
   334  					etcdRestoresCRDJSON,
   335  				},
   336  			},
   337  			expectedErr: "",
   338  			expected: &operators.PackageManifest{
   339  				ObjectMeta: metav1.ObjectMeta{
   340  					Name:      "etcd",
   341  					Namespace: "ns",
   342  					Labels: labels.Set{
   343  						"catalog":                         "cool-operators",
   344  						"catalog-namespace":               "ns",
   345  						"provider":                        "CoreOS, Inc",
   346  						"provider-url":                    "",
   347  						"operatorframework.io/arch.amd64": "supported",
   348  						"operatorframework.io/os.linux":   "supported",
   349  					},
   350  				},
   351  				Status: operators.PackageManifestStatus{
   352  					CatalogSource:          "cool-operators",
   353  					CatalogSourceNamespace: "ns",
   354  					PackageName:            "etcd",
   355  					Provider: operators.AppLink{
   356  						Name: "CoreOS, Inc",
   357  					},
   358  					DefaultChannel: "alpha",
   359  					Channels: []operators.PackageChannel{
   360  						{
   361  							Name:       "alpha",
   362  							CurrentCSV: "etcdoperator.v0.9.2",
   363  							CurrentCSVDesc: func() operators.CSVDescription {
   364  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   365  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   366  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   367  							}(),
   368  						},
   369  					},
   370  				},
   371  			},
   372  		},
   373  		{
   374  			name: "MissingBundle/DefaultChannelElided",
   375  			apiPkg: &api.Package{
   376  				Name: "etcd",
   377  				Channels: []*api.Channel{
   378  					{
   379  						Name:    "alpha",
   380  						CsvName: "etcdoperator.v0.9.2",
   381  					},
   382  					{
   383  						Name:    "missing-bundle",
   384  						CsvName: "etcdoperator.v0.9.1",
   385  					},
   386  				},
   387  				DefaultChannelName: "missing-bundle",
   388  			},
   389  			catalogSource: catalogSource("cool-operators", "ns"),
   390  			bundle: &api.Bundle{
   391  				CsvName:     "etcdoperator.v0.9.2",
   392  				PackageName: "etcd",
   393  				ChannelName: "alpha",
   394  				CsvJson:     etcdCSVJSON,
   395  				Object: []string{
   396  					etcdCSVJSON,
   397  					etcdBackupsCRDJSON,
   398  					etcdUpgradesCRDJSON,
   399  					etcdRestoresCRDJSON,
   400  				},
   401  			},
   402  			expectedErr: "",
   403  			expected: &operators.PackageManifest{
   404  				ObjectMeta: metav1.ObjectMeta{
   405  					Name:      "etcd",
   406  					Namespace: "ns",
   407  					Labels: labels.Set{
   408  						"catalog":                         "cool-operators",
   409  						"catalog-namespace":               "ns",
   410  						"provider":                        "CoreOS, Inc",
   411  						"provider-url":                    "",
   412  						"operatorframework.io/arch.amd64": "supported",
   413  						"operatorframework.io/os.linux":   "supported",
   414  					},
   415  				},
   416  				Status: operators.PackageManifestStatus{
   417  					CatalogSource:          "cool-operators",
   418  					CatalogSourceNamespace: "ns",
   419  					PackageName:            "etcd",
   420  					Provider: operators.AppLink{
   421  						Name: "CoreOS, Inc",
   422  					},
   423  					DefaultChannel: "alpha",
   424  					Channels: []operators.PackageChannel{
   425  						{
   426  							Name:       "alpha",
   427  							CurrentCSV: "etcdoperator.v0.9.2",
   428  							CurrentCSVDesc: func() operators.CSVDescription {
   429  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   430  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   431  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   432  							}(),
   433  						},
   434  					},
   435  				},
   436  			},
   437  		},
   438  		{
   439  			name: "NoBundles/NoValidChannels",
   440  			apiPkg: &api.Package{
   441  				Name: "etcd",
   442  				Channels: []*api.Channel{
   443  					{
   444  						Name:    "alpha",
   445  						CsvName: "etcdoperator.v0.9.2",
   446  					},
   447  				},
   448  				DefaultChannelName: "alpha",
   449  			},
   450  			catalogSource: catalogSource("cool-operators", "ns"),
   451  			bundle: &api.Bundle{
   452  				CsvName:     "etcdoperator.v0.9.2",
   453  				PackageName: "etcd",
   454  				ChannelName: "alpha",
   455  			},
   456  			expectedErr: "packagemanifest has no valid channels",
   457  		},
   458  		{
   459  			name: "DeprecatedPackage",
   460  			apiPkg: &api.Package{
   461  				Name: "etcd",
   462  				Channels: []*api.Channel{
   463  					{
   464  						Name:    "alpha",
   465  						CsvName: "etcdoperator.v0.9.2",
   466  					},
   467  				},
   468  				DefaultChannelName: "alpha",
   469  				Deprecation: &api.Deprecation{
   470  					Message: "This package is deprecated",
   471  				},
   472  			},
   473  			catalogSource: catalogSource("cool-operators", "ns"),
   474  			bundle: &api.Bundle{
   475  				CsvName:     "etcdoperator.v0.9.2",
   476  				PackageName: "etcd",
   477  				ChannelName: "alpha",
   478  				CsvJson:     etcdCSVJSON,
   479  				Object: []string{
   480  					etcdCSVJSON,
   481  					etcdBackupsCRDJSON,
   482  					etcdUpgradesCRDJSON,
   483  					etcdRestoresCRDJSON,
   484  				},
   485  			},
   486  			expectedErr: "",
   487  			expected: &operators.PackageManifest{
   488  				ObjectMeta: metav1.ObjectMeta{
   489  					Name:      "etcd",
   490  					Namespace: "ns",
   491  					Labels: labels.Set{
   492  						"catalog":                         "cool-operators",
   493  						"catalog-namespace":               "ns",
   494  						"provider":                        "CoreOS, Inc",
   495  						"provider-url":                    "",
   496  						"operatorframework.io/arch.amd64": "supported",
   497  						"operatorframework.io/os.linux":   "supported",
   498  					},
   499  				},
   500  				Status: operators.PackageManifestStatus{
   501  					CatalogSource:          "cool-operators",
   502  					CatalogSourceNamespace: "ns",
   503  					PackageName:            "etcd",
   504  					Provider: operators.AppLink{
   505  						Name: "CoreOS, Inc",
   506  					},
   507  					Deprecation: &operators.Deprecation{
   508  						Message: "This package is deprecated",
   509  					},
   510  					DefaultChannel: "alpha",
   511  					Channels: []operators.PackageChannel{
   512  						{
   513  							Name:       "alpha",
   514  							CurrentCSV: "etcdoperator.v0.9.2",
   515  							CurrentCSVDesc: func() operators.CSVDescription {
   516  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   517  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   518  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   519  							}(),
   520  						},
   521  					},
   522  				},
   523  			},
   524  		},
   525  		{
   526  			name: "DeprecatedChannel",
   527  			apiPkg: &api.Package{
   528  				Name: "etcd",
   529  				Channels: []*api.Channel{
   530  					{
   531  						Name:    "alpha",
   532  						CsvName: "etcdoperator.v0.9.2",
   533  						Deprecation: &api.Deprecation{
   534  							Message: "This channel of the package is deprecated",
   535  						},
   536  					},
   537  				},
   538  				DefaultChannelName: "alpha",
   539  			},
   540  			catalogSource: catalogSource("cool-operators", "ns"),
   541  			bundle: &api.Bundle{
   542  				CsvName:     "etcdoperator.v0.9.2",
   543  				PackageName: "etcd",
   544  				ChannelName: "alpha",
   545  				CsvJson:     etcdCSVJSON,
   546  				Object: []string{
   547  					etcdCSVJSON,
   548  					etcdBackupsCRDJSON,
   549  					etcdUpgradesCRDJSON,
   550  					etcdRestoresCRDJSON,
   551  				},
   552  			},
   553  			expectedErr: "",
   554  			expected: &operators.PackageManifest{
   555  				ObjectMeta: metav1.ObjectMeta{
   556  					Name:      "etcd",
   557  					Namespace: "ns",
   558  					Labels: labels.Set{
   559  						"catalog":                         "cool-operators",
   560  						"catalog-namespace":               "ns",
   561  						"provider":                        "CoreOS, Inc",
   562  						"provider-url":                    "",
   563  						"operatorframework.io/arch.amd64": "supported",
   564  						"operatorframework.io/os.linux":   "supported",
   565  					},
   566  				},
   567  				Status: operators.PackageManifestStatus{
   568  					CatalogSource:          "cool-operators",
   569  					CatalogSourceNamespace: "ns",
   570  					PackageName:            "etcd",
   571  					Provider: operators.AppLink{
   572  						Name: "CoreOS, Inc",
   573  					},
   574  					DefaultChannel: "alpha",
   575  					Channels: []operators.PackageChannel{
   576  						{
   577  							Name: "alpha",
   578  							Deprecation: &operators.Deprecation{
   579  								Message: "This channel of the package is deprecated",
   580  							},
   581  							CurrentCSV: "etcdoperator.v0.9.2",
   582  							CurrentCSVDesc: func() operators.CSVDescription {
   583  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   584  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   585  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   586  							}(),
   587  						},
   588  					},
   589  				},
   590  			},
   591  		},
   592  		{
   593  			name: "DeprecatedEntries",
   594  			apiPkg: &api.Package{
   595  				Name: "etcd",
   596  				Channels: []*api.Channel{
   597  					{
   598  						Name:    "alpha",
   599  						CsvName: "etcdoperator.v0.9.2",
   600  						Deprecation: &api.Deprecation{
   601  							Message: "This channel of the package is deprecated",
   602  						},
   603  					},
   604  				},
   605  				DefaultChannelName: "alpha",
   606  			},
   607  			channelEntries: map[string][]operators.ChannelEntry{
   608  				"alpha": {
   609  					{
   610  						Name:    "etcdoperator.v0.9.2",
   611  						Version: "0.9.2",
   612  					},
   613  					{
   614  						Name:    "etcdoperator.v0.9.0",
   615  						Version: "0.9.0",
   616  						Deprecation: &operators.Deprecation{
   617  							Message: "This bundle of the package is deprecated",
   618  						},
   619  					},
   620  					{
   621  						Name:    "etcdoperator.v0.6.1",
   622  						Version: "0.6.1",
   623  						Deprecation: &operators.Deprecation{
   624  							Message: "This bundle of the package is also deprecated and has a unique message",
   625  						},
   626  					},
   627  				},
   628  			},
   629  			catalogSource: catalogSource("cool-operators", "ns"),
   630  			bundle: &api.Bundle{
   631  				CsvName:     "etcdoperator.v0.9.2",
   632  				PackageName: "etcd",
   633  				ChannelName: "alpha",
   634  				CsvJson:     etcdCSVJSON,
   635  				Object: []string{
   636  					etcdCSVJSON,
   637  					etcdBackupsCRDJSON,
   638  					etcdUpgradesCRDJSON,
   639  					etcdRestoresCRDJSON,
   640  				},
   641  			},
   642  			expectedErr: "",
   643  			expected: &operators.PackageManifest{
   644  				ObjectMeta: metav1.ObjectMeta{
   645  					Name:      "etcd",
   646  					Namespace: "ns",
   647  					Labels: labels.Set{
   648  						"catalog":                         "cool-operators",
   649  						"catalog-namespace":               "ns",
   650  						"provider":                        "CoreOS, Inc",
   651  						"provider-url":                    "",
   652  						"operatorframework.io/arch.amd64": "supported",
   653  						"operatorframework.io/os.linux":   "supported",
   654  					},
   655  				},
   656  				Status: operators.PackageManifestStatus{
   657  					CatalogSource:          "cool-operators",
   658  					CatalogSourceNamespace: "ns",
   659  					PackageName:            "etcd",
   660  					Provider: operators.AppLink{
   661  						Name: "CoreOS, Inc",
   662  					},
   663  					DefaultChannel: "alpha",
   664  					Channels: []operators.PackageChannel{
   665  						{
   666  							Name: "alpha",
   667  							Deprecation: &operators.Deprecation{
   668  								Message: "This channel of the package is deprecated",
   669  							},
   670  							CurrentCSV: "etcdoperator.v0.9.2",
   671  							CurrentCSVDesc: func() operators.CSVDescription {
   672  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   673  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   674  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   675  							}(),
   676  							Entries: []operators.ChannelEntry{
   677  								{
   678  									Name:    "etcdoperator.v0.9.2",
   679  									Version: "0.9.2",
   680  								},
   681  								{
   682  									Name:    "etcdoperator.v0.9.0",
   683  									Version: "0.9.0",
   684  									Deprecation: &operators.Deprecation{
   685  										Message: "This bundle of the package is deprecated",
   686  									},
   687  								},
   688  								{
   689  									Name:    "etcdoperator.v0.6.1",
   690  									Version: "0.6.1",
   691  									Deprecation: &operators.Deprecation{
   692  										Message: "This bundle of the package is also deprecated and has a unique message",
   693  									},
   694  								},
   695  							},
   696  						},
   697  					},
   698  				},
   699  			},
   700  		},
   701  		{
   702  			name: "DeprecatedPackageChannelAndEntries",
   703  			apiPkg: &api.Package{
   704  				Name: "etcd",
   705  				Channels: []*api.Channel{
   706  					{
   707  						Name:    "alpha",
   708  						CsvName: "etcdoperator.v0.9.2",
   709  						Deprecation: &api.Deprecation{
   710  							Message: "This channel of the package is deprecated",
   711  						},
   712  					},
   713  				},
   714  				DefaultChannelName: "alpha",
   715  				Deprecation: &api.Deprecation{
   716  					Message: "This package is deprecated",
   717  				},
   718  			},
   719  			channelEntries: map[string][]operators.ChannelEntry{
   720  				"alpha": {
   721  					{
   722  						Name:    "etcdoperator.v0.9.2",
   723  						Version: "0.9.2",
   724  					},
   725  					{
   726  						Name:    "etcdoperator.v0.9.0",
   727  						Version: "0.9.0",
   728  						Deprecation: &operators.Deprecation{
   729  							Message: "This bundle of the package is deprecated",
   730  						},
   731  					},
   732  					{
   733  						Name:    "etcdoperator.v0.6.1",
   734  						Version: "0.6.1",
   735  						Deprecation: &operators.Deprecation{
   736  							Message: "This bundle of the package is also deprecated and has a unique message",
   737  						},
   738  					},
   739  				},
   740  			},
   741  			catalogSource: catalogSource("cool-operators", "ns"),
   742  			bundle: &api.Bundle{
   743  				CsvName:     "etcdoperator.v0.9.2",
   744  				PackageName: "etcd",
   745  				ChannelName: "alpha",
   746  				CsvJson:     etcdCSVJSON,
   747  				Object: []string{
   748  					etcdCSVJSON,
   749  					etcdBackupsCRDJSON,
   750  					etcdUpgradesCRDJSON,
   751  					etcdRestoresCRDJSON,
   752  				},
   753  			},
   754  			expectedErr: "",
   755  			expected: &operators.PackageManifest{
   756  				ObjectMeta: metav1.ObjectMeta{
   757  					Name:      "etcd",
   758  					Namespace: "ns",
   759  					Labels: labels.Set{
   760  						"catalog":                         "cool-operators",
   761  						"catalog-namespace":               "ns",
   762  						"provider":                        "CoreOS, Inc",
   763  						"provider-url":                    "",
   764  						"operatorframework.io/arch.amd64": "supported",
   765  						"operatorframework.io/os.linux":   "supported",
   766  					},
   767  				},
   768  				Status: operators.PackageManifestStatus{
   769  					CatalogSource:          "cool-operators",
   770  					CatalogSourceNamespace: "ns",
   771  					PackageName:            "etcd",
   772  					Provider: operators.AppLink{
   773  						Name: "CoreOS, Inc",
   774  					},
   775  					Deprecation: &operators.Deprecation{
   776  						Message: "This package is deprecated",
   777  					},
   778  					DefaultChannel: "alpha",
   779  					Channels: []operators.PackageChannel{
   780  						{
   781  							Name: "alpha",
   782  							Deprecation: &operators.Deprecation{
   783  								Message: "This channel of the package is deprecated",
   784  							},
   785  							CurrentCSV: "etcdoperator.v0.9.2",
   786  							CurrentCSVDesc: func() operators.CSVDescription {
   787  								csv := operatorsv1alpha1.ClusterServiceVersion{}
   788  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   789  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   790  							}(),
   791  							Entries: []operators.ChannelEntry{
   792  								{
   793  									Name:    "etcdoperator.v0.9.2",
   794  									Version: "0.9.2",
   795  								},
   796  								{
   797  									Name:    "etcdoperator.v0.9.0",
   798  									Version: "0.9.0",
   799  									Deprecation: &operators.Deprecation{
   800  										Message: "This bundle of the package is deprecated",
   801  									},
   802  								},
   803  								{
   804  									Name:    "etcdoperator.v0.6.1",
   805  									Version: "0.6.1",
   806  									Deprecation: &operators.Deprecation{
   807  										Message: "This bundle of the package is also deprecated and has a unique message",
   808  									},
   809  								},
   810  							},
   811  						},
   812  					},
   813  				},
   814  			},
   815  		},
   816  	}
   817  
   818  	for _, test := range tests {
   819  		t.Run(test.name, func(t *testing.T) {
   820  			clientFake := &fakes.FakeRegistryClient{}
   821  			clientFake.GetBundleForChannelReturnsOnCall(0, test.bundle, nil)
   822  
   823  			client := &registryClient{
   824  				RegistryClient: clientFake,
   825  				catsrc:         test.catalogSource,
   826  			}
   827  
   828  			packageManifest, err := newPackageManifest(context.Background(), logrus.NewEntry(logrus.New()), test.apiPkg, client, test.channelEntries)
   829  			if test.expectedErr != "" {
   830  				require.Error(t, err)
   831  				require.Equal(t, test.expectedErr, err.Error())
   832  			} else {
   833  				require.NoError(t, err)
   834  			}
   835  			require.Equal(t, test.expected, packageManifest)
   836  		})
   837  	}
   838  }
   839  
   840  func TestPackageManifestChannelsDisplayOrder(t *testing.T) {
   841  	apiPkg := &api.Package{
   842  		Name: "etcd",
   843  		Channels: []*api.Channel{
   844  			{
   845  				Name:    "beta",
   846  				CsvName: "etcdoperator.v0.10.1",
   847  			},
   848  			{
   849  				Name:    "alpha",
   850  				CsvName: "etcdoperator.v0.9.2",
   851  			},
   852  		},
   853  		DefaultChannelName: "alpha",
   854  	}
   855  	clientFake := &fakes.FakeRegistryClient{}
   856  	clientFake.GetBundleForChannelReturnsOnCall(0, &api.Bundle{
   857  		CsvName:     "etcdoperator.v0.9.2",
   858  		PackageName: "etcd",
   859  		ChannelName: "alpha",
   860  		CsvJson:     etcdCSVJSON,
   861  		Object: []string{
   862  			etcdCSVJSON,
   863  			etcdBackupsCRDJSON,
   864  			etcdUpgradesCRDJSON,
   865  			etcdRestoresCRDJSON,
   866  		},
   867  	}, nil)
   868  	clientFake.GetBundleForChannelReturnsOnCall(1, &api.Bundle{
   869  		CsvName:     "etcdoperator.v0.10.1",
   870  		PackageName: "etcd",
   871  		ChannelName: "beta",
   872  		CsvJson:     etcdCSVJSONv10,
   873  		Object: []string{
   874  			etcdCSVJSON,
   875  			etcdBackupsCRDJSON,
   876  			etcdUpgradesCRDJSON,
   877  			etcdRestoresCRDJSON,
   878  		},
   879  	}, nil)
   880  
   881  	client := &registryClient{
   882  		RegistryClient: clientFake,
   883  		catsrc:         catalogSource("cool-operators", "ns"),
   884  	}
   885  
   886  	packageManifest, err := newPackageManifest(context.Background(), logrus.NewEntry(logrus.New()), apiPkg, client, nil)
   887  	require.NoError(t, err)
   888  	require.Equal(t, &operators.PackageManifest{
   889  		ObjectMeta: metav1.ObjectMeta{
   890  			Name:      "etcd",
   891  			Namespace: "ns",
   892  			Labels: labels.Set{
   893  				"catalog":                         "cool-operators",
   894  				"catalog-namespace":               "ns",
   895  				"provider":                        "CoreOS, Inc",
   896  				"provider-url":                    "",
   897  				"operatorframework.io/arch.amd64": "supported",
   898  				"operatorframework.io/os.linux":   "supported",
   899  			},
   900  		},
   901  		Status: operators.PackageManifestStatus{
   902  			CatalogSource:          "cool-operators",
   903  			CatalogSourceNamespace: "ns",
   904  			PackageName:            "etcd",
   905  			Provider: operators.AppLink{
   906  				Name: "CoreOS, Inc",
   907  			},
   908  			DefaultChannel: "alpha",
   909  			Channels: []operators.PackageChannel{
   910  				{
   911  					Name:       "alpha",
   912  					CurrentCSV: "etcdoperator.v0.9.2",
   913  					CurrentCSVDesc: func() operators.CSVDescription {
   914  						csv := operatorsv1alpha1.ClusterServiceVersion{}
   915  						require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
   916  						return operators.CreateCSVDescription(&csv, etcdCSVJSON)
   917  					}(),
   918  				},
   919  				{
   920  					Name:       "beta",
   921  					CurrentCSV: "etcdoperator.v0.10.1",
   922  					CurrentCSVDesc: func() operators.CSVDescription {
   923  						csv := operatorsv1alpha1.ClusterServiceVersion{}
   924  						require.NoError(t, json.Unmarshal([]byte(etcdCSVJSONv10), &csv))
   925  						return operators.CreateCSVDescription(&csv, etcdCSVJSONv10)
   926  					}(),
   927  				},
   928  			},
   929  		},
   930  	}, packageManifest)
   931  }
   932  
   933  func TestRegistryProviderGet(t *testing.T) {
   934  	type getRequest struct {
   935  		packageNamespace string
   936  		packageName      string
   937  	}
   938  	tests := []struct {
   939  		name           string
   940  		namespaces     []string
   941  		globalNS       string
   942  		catalogSources []runtime.Object
   943  		request        getRequest
   944  		expectedErr    string
   945  		expected       *operators.PackageManifest
   946  	}{
   947  		{
   948  			name:       "SingleNamespace/PackageManifestNotFound",
   949  			namespaces: []string{"ns"},
   950  			globalNS:   "ns",
   951  			catalogSources: []runtime.Object{
   952  				withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now())),
   953  			},
   954  			request: getRequest{
   955  				packageNamespace: "ns",
   956  				packageName:      "amq",
   957  			},
   958  			expectedErr: "",
   959  			expected:    nil,
   960  		},
   961  		{
   962  			name:       "SingleNamespace/PackageManifestFound",
   963  			namespaces: []string{"ns"},
   964  			globalNS:   "ns",
   965  			catalogSources: []runtime.Object{
   966  				withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now())),
   967  			},
   968  			request: getRequest{
   969  				// A package known to exist in the directory-loaded registry.
   970  				packageNamespace: "ns",
   971  				packageName:      "etcd",
   972  			},
   973  			expectedErr: "",
   974  			expected: &operators.PackageManifest{
   975  				ObjectMeta: metav1.ObjectMeta{
   976  					Name:      "etcd",
   977  					Namespace: "ns",
   978  					Labels: labels.Set{
   979  						"catalog":                         "cool-operators",
   980  						"catalog-namespace":               "ns",
   981  						"provider":                        "CoreOS, Inc",
   982  						"provider-url":                    "",
   983  						"operatorframework.io/arch.amd64": "supported",
   984  						"operatorframework.io/os.linux":   "supported",
   985  					},
   986  				},
   987  				Status: operators.PackageManifestStatus{
   988  					CatalogSource:          "cool-operators",
   989  					CatalogSourceNamespace: "ns",
   990  					PackageName:            "etcd",
   991  					Provider: operators.AppLink{
   992  						Name: "CoreOS, Inc",
   993  					},
   994  					DefaultChannel: "alpha",
   995  					Channels: []operators.PackageChannel{
   996  						{
   997  							Name:       "alpha",
   998  							CurrentCSV: "etcdoperator.v0.9.2",
   999  							CurrentCSVDesc: func() operators.CSVDescription {
  1000  								csv := operatorsv1alpha1.ClusterServiceVersion{}
  1001  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1002  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1003  							}(),
  1004  							Entries: etcdChannelEntries,
  1005  						},
  1006  					},
  1007  				},
  1008  			},
  1009  		},
  1010  		{
  1011  			name:       "SingleNamespace/TwoCatalogs/OneBadConnection/PackageManifestFound",
  1012  			namespaces: []string{"ns"},
  1013  			globalNS:   "ns",
  1014  			catalogSources: []runtime.Object{
  1015  				withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now())),
  1016  				withRegistryServiceStatus(catalogSource("not-so-cool-operators", "ns"), "grpc", "not-so-cool-operators", "ns", "50052", metav1.NewTime(time.Now())),
  1017  			},
  1018  			request: getRequest{
  1019  				// A package known to exist in the directory-loaded registry.
  1020  				packageNamespace: "ns",
  1021  				packageName:      "etcd",
  1022  			},
  1023  			expectedErr: "",
  1024  			expected: &operators.PackageManifest{
  1025  				ObjectMeta: metav1.ObjectMeta{
  1026  					Name:      "etcd",
  1027  					Namespace: "ns",
  1028  					Labels: labels.Set{
  1029  						"catalog":                         "cool-operators",
  1030  						"catalog-namespace":               "ns",
  1031  						"provider":                        "CoreOS, Inc",
  1032  						"provider-url":                    "",
  1033  						"operatorframework.io/arch.amd64": "supported",
  1034  						"operatorframework.io/os.linux":   "supported",
  1035  					},
  1036  				},
  1037  				Status: operators.PackageManifestStatus{
  1038  					CatalogSource:          "cool-operators",
  1039  					CatalogSourceNamespace: "ns",
  1040  					PackageName:            "etcd",
  1041  					Provider: operators.AppLink{
  1042  						Name: "CoreOS, Inc",
  1043  					},
  1044  					DefaultChannel: "alpha",
  1045  					Channels: []operators.PackageChannel{
  1046  						{
  1047  							Name:       "alpha",
  1048  							CurrentCSV: "etcdoperator.v0.9.2",
  1049  							CurrentCSVDesc: func() operators.CSVDescription {
  1050  								csv := operatorsv1alpha1.ClusterServiceVersion{}
  1051  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1052  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1053  							}(),
  1054  							Entries: etcdChannelEntries,
  1055  						},
  1056  					},
  1057  				},
  1058  			},
  1059  		},
  1060  		{
  1061  			name:       "GlobalNamespace/PackageManifestFound",
  1062  			namespaces: []string{"ns", "global"},
  1063  			globalNS:   "global",
  1064  			catalogSources: []runtime.Object{
  1065  				withRegistryServiceStatus(catalogSource("cool-operators", "global"), "grpc", "cool-operators", "global", port, metav1.NewTime(time.Now())),
  1066  			},
  1067  			request: getRequest{
  1068  				// A package known to exist in the directory-loaded registry.
  1069  				packageNamespace: "ns",
  1070  				packageName:      "etcd",
  1071  			},
  1072  			expectedErr: "",
  1073  			expected: &operators.PackageManifest{
  1074  				ObjectMeta: metav1.ObjectMeta{
  1075  					Name:      "etcd",
  1076  					Namespace: "ns",
  1077  					Labels: labels.Set{
  1078  						"catalog":                         "cool-operators",
  1079  						"catalog-namespace":               "global",
  1080  						"provider":                        "CoreOS, Inc",
  1081  						"provider-url":                    "",
  1082  						"operatorframework.io/arch.amd64": "supported",
  1083  						"operatorframework.io/os.linux":   "supported",
  1084  					},
  1085  				},
  1086  				Status: operators.PackageManifestStatus{
  1087  					CatalogSource:          "cool-operators",
  1088  					CatalogSourceNamespace: "global",
  1089  					PackageName:            "etcd",
  1090  					Provider: operators.AppLink{
  1091  						Name: "CoreOS, Inc",
  1092  					},
  1093  					DefaultChannel: "alpha",
  1094  					Channels: []operators.PackageChannel{
  1095  						{
  1096  							Name:       "alpha",
  1097  							CurrentCSV: "etcdoperator.v0.9.2",
  1098  							CurrentCSVDesc: func() operators.CSVDescription {
  1099  								csv := operatorsv1alpha1.ClusterServiceVersion{}
  1100  								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1101  								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1102  							}(),
  1103  							Entries: etcdChannelEntries,
  1104  						},
  1105  					},
  1106  				},
  1107  			},
  1108  		},
  1109  	}
  1110  
  1111  	for _, test := range tests {
  1112  		t.Run(test.name, func(t *testing.T) {
  1113  			ctx, cancel := context.WithCancel(context.TODO())
  1114  			defer cancel()
  1115  			provider, err := NewFakeRegistryProvider(ctx, nil, nil, test.globalNS)
  1116  			require.NoError(t, err)
  1117  
  1118  			for _, cs := range test.catalogSources {
  1119  				catsrc := cs.(*operatorsv1alpha1.CatalogSource)
  1120  				require.NoError(t, provider.refreshCache(ctx, newTestRegistryClient(t, catsrc)))
  1121  			}
  1122  
  1123  			packageManifest, err := provider.Get(test.request.packageNamespace, test.request.packageName)
  1124  			if test.expectedErr != "" {
  1125  				require.NotNil(t, err)
  1126  				require.Equal(t, test.expectedErr, err.Error())
  1127  			} else {
  1128  				require.Nil(t, err)
  1129  			}
  1130  
  1131  			require.EqualValues(t, test.expected, packageManifest)
  1132  		})
  1133  	}
  1134  }
  1135  
  1136  func TestRegistryProviderList(t *testing.T) {
  1137  	tests := []struct {
  1138  		name             string
  1139  		globalNS         string
  1140  		registryClients  []*registryClient
  1141  		requestNamespace string
  1142  		expectedErr      string
  1143  		expected         *operators.PackageManifestList
  1144  	}{
  1145  		{
  1146  			name:             "NoPackages",
  1147  			globalNS:         "ns",
  1148  			requestNamespace: "wisconsin",
  1149  			expectedErr:      "",
  1150  			expected:         &operators.PackageManifestList{},
  1151  		},
  1152  		{
  1153  			name:     "PackagesFound",
  1154  			globalNS: "ns",
  1155  			registryClients: []*registryClient{
  1156  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1157  			},
  1158  			requestNamespace: "ns",
  1159  			expectedErr:      "",
  1160  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1161  				{
  1162  					ObjectMeta: metav1.ObjectMeta{
  1163  						Name:      "prometheus",
  1164  						Namespace: "ns",
  1165  						Labels: labels.Set{
  1166  							"catalog":                         "cool-operators",
  1167  							"catalog-namespace":               "ns",
  1168  							"provider":                        "Red Hat",
  1169  							"provider-url":                    "",
  1170  							"operatorframework.io/arch.amd64": "supported",
  1171  							"operatorframework.io/os.linux":   "supported",
  1172  						},
  1173  					},
  1174  					Status: operators.PackageManifestStatus{
  1175  						CatalogSource:          "cool-operators",
  1176  						CatalogSourceNamespace: "ns",
  1177  						PackageName:            "prometheus",
  1178  						Provider: operators.AppLink{
  1179  							Name: "Red Hat",
  1180  						},
  1181  						DefaultChannel: "preview",
  1182  						Channels: []operators.PackageChannel{
  1183  							{
  1184  								Name:       "preview",
  1185  								CurrentCSV: "prometheusoperator.0.22.2",
  1186  								CurrentCSVDesc: func() operators.CSVDescription {
  1187  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1188  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1189  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1190  								}(),
  1191  								Entries: prometheusChannelEntries,
  1192  							},
  1193  						},
  1194  					},
  1195  				},
  1196  				{
  1197  					ObjectMeta: metav1.ObjectMeta{
  1198  						Name:      "etcd",
  1199  						Namespace: "ns",
  1200  						Labels: labels.Set{
  1201  							"catalog":                         "cool-operators",
  1202  							"catalog-namespace":               "ns",
  1203  							"provider":                        "CoreOS, Inc",
  1204  							"provider-url":                    "",
  1205  							"operatorframework.io/arch.amd64": "supported",
  1206  							"operatorframework.io/os.linux":   "supported",
  1207  						},
  1208  					},
  1209  					Status: operators.PackageManifestStatus{
  1210  						CatalogSource:          "cool-operators",
  1211  						CatalogSourceNamespace: "ns",
  1212  						PackageName:            "etcd",
  1213  						Provider: operators.AppLink{
  1214  							Name: "CoreOS, Inc",
  1215  						},
  1216  						DefaultChannel: "alpha",
  1217  						Channels: []operators.PackageChannel{
  1218  							{
  1219  								Name:       "alpha",
  1220  								CurrentCSV: "etcdoperator.v0.9.2",
  1221  								CurrentCSVDesc: func() operators.CSVDescription {
  1222  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1223  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1224  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1225  								}(),
  1226  								Entries: etcdChannelEntries,
  1227  							},
  1228  						},
  1229  					},
  1230  				},
  1231  			}},
  1232  		},
  1233  		{
  1234  			name:     "TwoCatalogs/OneBadConnection/PackagesFound",
  1235  			globalNS: "ns",
  1236  			registryClients: []*registryClient{
  1237  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1238  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("not-so-cool-operators", "ns"), "grpc", "not-so-cool-operators", "ns", "50052", metav1.NewTime(time.Now()))),
  1239  			},
  1240  			requestNamespace: "ns",
  1241  			expectedErr:      "",
  1242  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1243  				{
  1244  					ObjectMeta: metav1.ObjectMeta{
  1245  						Name:      "prometheus",
  1246  						Namespace: "ns",
  1247  						Labels: labels.Set{
  1248  							"catalog":                         "cool-operators",
  1249  							"catalog-namespace":               "ns",
  1250  							"provider":                        "Red Hat",
  1251  							"provider-url":                    "",
  1252  							"operatorframework.io/arch.amd64": "supported",
  1253  							"operatorframework.io/os.linux":   "supported",
  1254  						},
  1255  					},
  1256  					Status: operators.PackageManifestStatus{
  1257  						CatalogSource:          "cool-operators",
  1258  						CatalogSourceNamespace: "ns",
  1259  						PackageName:            "prometheus",
  1260  						Provider: operators.AppLink{
  1261  							Name: "Red Hat",
  1262  						},
  1263  						DefaultChannel: "preview",
  1264  						Channels: []operators.PackageChannel{
  1265  							{
  1266  								Name:       "preview",
  1267  								CurrentCSV: "prometheusoperator.0.22.2",
  1268  								CurrentCSVDesc: func() operators.CSVDescription {
  1269  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1270  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1271  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1272  								}(),
  1273  								Entries: prometheusChannelEntries,
  1274  							},
  1275  						},
  1276  					},
  1277  				},
  1278  				{
  1279  					ObjectMeta: metav1.ObjectMeta{
  1280  						Name:      "etcd",
  1281  						Namespace: "ns",
  1282  						Labels: labels.Set{
  1283  							"catalog":                         "cool-operators",
  1284  							"catalog-namespace":               "ns",
  1285  							"provider":                        "CoreOS, Inc",
  1286  							"provider-url":                    "",
  1287  							"operatorframework.io/arch.amd64": "supported",
  1288  							"operatorframework.io/os.linux":   "supported",
  1289  						},
  1290  					},
  1291  					Status: operators.PackageManifestStatus{
  1292  						CatalogSource:          "cool-operators",
  1293  						CatalogSourceNamespace: "ns",
  1294  						PackageName:            "etcd",
  1295  						Provider: operators.AppLink{
  1296  							Name: "CoreOS, Inc",
  1297  						},
  1298  						DefaultChannel: "alpha",
  1299  						Channels: []operators.PackageChannel{
  1300  							{
  1301  								Name:       "alpha",
  1302  								CurrentCSV: "etcdoperator.v0.9.2",
  1303  								CurrentCSVDesc: func() operators.CSVDescription {
  1304  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1305  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1306  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1307  								}(),
  1308  								Entries: etcdChannelEntries,
  1309  							},
  1310  						},
  1311  					},
  1312  				},
  1313  			}},
  1314  		},
  1315  		{
  1316  			name:     "TwoCatalogs/SameNamespace/DuplicatePackages/PackagesFound",
  1317  			globalNS: "ns",
  1318  			registryClients: []*registryClient{
  1319  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1320  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators-2", "ns"), "grpc", "cool-operators-2", "ns", port, metav1.NewTime(time.Now()))),
  1321  			},
  1322  			requestNamespace: "ns",
  1323  			expectedErr:      "",
  1324  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1325  				{
  1326  					ObjectMeta: metav1.ObjectMeta{
  1327  						Name:      "prometheus",
  1328  						Namespace: "ns",
  1329  						Labels: labels.Set{
  1330  							"catalog":                         "cool-operators",
  1331  							"catalog-namespace":               "ns",
  1332  							"provider":                        "Red Hat",
  1333  							"provider-url":                    "",
  1334  							"operatorframework.io/arch.amd64": "supported",
  1335  							"operatorframework.io/os.linux":   "supported",
  1336  						},
  1337  					},
  1338  					Status: operators.PackageManifestStatus{
  1339  						CatalogSource:          "cool-operators",
  1340  						CatalogSourceNamespace: "ns",
  1341  						PackageName:            "prometheus",
  1342  						Provider: operators.AppLink{
  1343  							Name: "Red Hat",
  1344  						},
  1345  						DefaultChannel: "preview",
  1346  						Channels: []operators.PackageChannel{
  1347  							{
  1348  								Name:       "preview",
  1349  								CurrentCSV: "prometheusoperator.0.22.2",
  1350  								CurrentCSVDesc: func() operators.CSVDescription {
  1351  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1352  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1353  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1354  								}(),
  1355  								Entries: prometheusChannelEntries,
  1356  							},
  1357  						},
  1358  					},
  1359  				},
  1360  				{
  1361  					ObjectMeta: metav1.ObjectMeta{
  1362  						Name:      "etcd",
  1363  						Namespace: "ns",
  1364  						Labels: labels.Set{
  1365  							"catalog":                         "cool-operators",
  1366  							"catalog-namespace":               "ns",
  1367  							"provider":                        "CoreOS, Inc",
  1368  							"provider-url":                    "",
  1369  							"operatorframework.io/arch.amd64": "supported",
  1370  							"operatorframework.io/os.linux":   "supported",
  1371  						},
  1372  					},
  1373  					Status: operators.PackageManifestStatus{
  1374  						CatalogSource:          "cool-operators",
  1375  						CatalogSourceNamespace: "ns",
  1376  						PackageName:            "etcd",
  1377  						Provider: operators.AppLink{
  1378  							Name: "CoreOS, Inc",
  1379  						},
  1380  						DefaultChannel: "alpha",
  1381  						Channels: []operators.PackageChannel{
  1382  							{
  1383  								Name:       "alpha",
  1384  								CurrentCSV: "etcdoperator.v0.9.2",
  1385  								CurrentCSVDesc: func() operators.CSVDescription {
  1386  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1387  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1388  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1389  								}(),
  1390  								Entries: etcdChannelEntries,
  1391  							},
  1392  						},
  1393  					},
  1394  				},
  1395  				{
  1396  					ObjectMeta: metav1.ObjectMeta{
  1397  						Name:      "prometheus",
  1398  						Namespace: "ns",
  1399  						Labels: labels.Set{
  1400  							"catalog":                         "cool-operators-2",
  1401  							"catalog-namespace":               "ns",
  1402  							"provider":                        "Red Hat",
  1403  							"provider-url":                    "",
  1404  							"operatorframework.io/arch.amd64": "supported",
  1405  							"operatorframework.io/os.linux":   "supported",
  1406  						},
  1407  					},
  1408  					Status: operators.PackageManifestStatus{
  1409  						CatalogSource:          "cool-operators-2",
  1410  						CatalogSourceNamespace: "ns",
  1411  						PackageName:            "prometheus",
  1412  						Provider: operators.AppLink{
  1413  							Name: "Red Hat",
  1414  						},
  1415  						DefaultChannel: "preview",
  1416  						Channels: []operators.PackageChannel{
  1417  							{
  1418  								Name:       "preview",
  1419  								CurrentCSV: "prometheusoperator.0.22.2",
  1420  								CurrentCSVDesc: func() operators.CSVDescription {
  1421  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1422  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1423  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1424  								}(),
  1425  								Entries: prometheusChannelEntries,
  1426  							},
  1427  						},
  1428  					},
  1429  				},
  1430  				{
  1431  					ObjectMeta: metav1.ObjectMeta{
  1432  						Name:      "etcd",
  1433  						Namespace: "ns",
  1434  						Labels: labels.Set{
  1435  							"catalog":                         "cool-operators-2",
  1436  							"catalog-namespace":               "ns",
  1437  							"provider":                        "CoreOS, Inc",
  1438  							"provider-url":                    "",
  1439  							"operatorframework.io/arch.amd64": "supported",
  1440  							"operatorframework.io/os.linux":   "supported",
  1441  						},
  1442  					},
  1443  					Status: operators.PackageManifestStatus{
  1444  						CatalogSource:          "cool-operators-2",
  1445  						CatalogSourceNamespace: "ns",
  1446  						PackageName:            "etcd",
  1447  						Provider: operators.AppLink{
  1448  							Name: "CoreOS, Inc",
  1449  						},
  1450  						DefaultChannel: "alpha",
  1451  						Channels: []operators.PackageChannel{
  1452  							{
  1453  								Name:       "alpha",
  1454  								CurrentCSV: "etcdoperator.v0.9.2",
  1455  								CurrentCSVDesc: func() operators.CSVDescription {
  1456  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1457  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1458  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1459  								}(),
  1460  								Entries: etcdChannelEntries,
  1461  							},
  1462  						},
  1463  					},
  1464  				},
  1465  			}},
  1466  		},
  1467  		{
  1468  			name:     "OneCatalog/ManyPackages/OneMissingBundle/Elided",
  1469  			globalNS: "ns",
  1470  			registryClients: []*registryClient{
  1471  				func() *registryClient {
  1472  					catsrc := catalogSource("cool-operators", "ns")
  1473  
  1474  					listBundlesFake := &fakes.FakeRegistry_ListBundlesClient{}
  1475  					listBundlesFake.RecvReturnsOnCall(0, &api.Bundle{
  1476  						PackageName: "has-bundle",
  1477  						ChannelName: "alpha",
  1478  						CsvName:     "etcdoperator.v0.9.2",
  1479  						Version:     "0.9.2",
  1480  					}, nil)
  1481  					listBundlesFake.RecvReturnsOnCall(1, nil, io.EOF)
  1482  
  1483  					listFake := &fakes.FakeRegistry_ListPackagesClient{}
  1484  					listFake.RecvReturnsOnCall(0, &api.PackageName{Name: "no-bundle"}, nil)
  1485  					listFake.RecvReturnsOnCall(1, &api.PackageName{Name: "has-bundle"}, nil)
  1486  					listFake.RecvReturnsOnCall(2, nil, io.EOF)
  1487  
  1488  					clientFake := &fakes.FakeRegistryClient{}
  1489  					clientFake.ListBundlesReturns(listBundlesFake, nil)
  1490  					clientFake.ListPackagesReturns(listFake, nil)
  1491  					clientFake.GetPackageReturnsOnCall(0, &api.Package{
  1492  						Name: "no-bundle",
  1493  						Channels: []*api.Channel{
  1494  							{
  1495  								Name:    "alpha",
  1496  								CsvName: "xanthoporessa.v0.0.0",
  1497  							},
  1498  						},
  1499  						DefaultChannelName: "alpha",
  1500  					}, nil)
  1501  					clientFake.GetPackageReturnsOnCall(1, &api.Package{
  1502  						Name: "has-bundle",
  1503  						Channels: []*api.Channel{
  1504  							{
  1505  								Name:    "alpha",
  1506  								CsvName: "etcdoperator.v0.9.2",
  1507  							},
  1508  						},
  1509  						DefaultChannelName: "alpha",
  1510  					}, nil)
  1511  					clientFake.GetBundleForChannelReturnsOnCall(0, nil, fmt.Errorf("no bundle found"))
  1512  					clientFake.GetBundleForChannelReturnsOnCall(1, &api.Bundle{
  1513  						CsvName:     "etcdoperator.v0.9.2",
  1514  						PackageName: "has-bundle",
  1515  						ChannelName: "alpha",
  1516  						CsvJson:     etcdCSVJSON,
  1517  						Object: []string{
  1518  							etcdCSVJSON,
  1519  							etcdBackupsCRDJSON,
  1520  							etcdUpgradesCRDJSON,
  1521  							etcdRestoresCRDJSON,
  1522  						},
  1523  					}, nil)
  1524  
  1525  					return &registryClient{clientFake, catsrc, nil}
  1526  				}(),
  1527  			},
  1528  			requestNamespace: "ns",
  1529  			expectedErr:      "",
  1530  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1531  				{
  1532  					ObjectMeta: metav1.ObjectMeta{
  1533  						Name:      "has-bundle",
  1534  						Namespace: "ns",
  1535  						Labels: labels.Set{
  1536  							"catalog":                         "cool-operators",
  1537  							"catalog-namespace":               "ns",
  1538  							"provider":                        "CoreOS, Inc",
  1539  							"provider-url":                    "",
  1540  							"operatorframework.io/arch.amd64": "supported",
  1541  							"operatorframework.io/os.linux":   "supported",
  1542  						},
  1543  					},
  1544  					Status: operators.PackageManifestStatus{
  1545  						CatalogSource:          "cool-operators",
  1546  						CatalogSourceNamespace: "ns",
  1547  						PackageName:            "has-bundle",
  1548  						Provider: operators.AppLink{
  1549  							Name: "CoreOS, Inc",
  1550  						},
  1551  						DefaultChannel: "alpha",
  1552  						Channels: []operators.PackageChannel{
  1553  							{
  1554  								Name:       "alpha",
  1555  								CurrentCSV: "etcdoperator.v0.9.2",
  1556  								CurrentCSVDesc: func() operators.CSVDescription {
  1557  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1558  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1559  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1560  								}(),
  1561  								Entries: []operators.ChannelEntry{
  1562  									{
  1563  										Name:    "etcdoperator.v0.9.2",
  1564  										Version: "0.9.2",
  1565  									},
  1566  								},
  1567  							},
  1568  						},
  1569  					},
  1570  				},
  1571  			}},
  1572  		},
  1573  		{
  1574  			name:     "OneCatalog/DeprecatedPackage/DeprecatedChannel/DeprecatedBundle",
  1575  			globalNS: "ns",
  1576  			registryClients: []*registryClient{
  1577  				func() *registryClient {
  1578  					catsrc := catalogSource("cool-operators", "ns")
  1579  
  1580  					listBundlesFake := &fakes.FakeRegistry_ListBundlesClient{}
  1581  					listBundlesFake.RecvReturnsOnCall(0, &api.Bundle{
  1582  						PackageName: "etcd",
  1583  						ChannelName: "alpha",
  1584  						CsvName:     "etcdoperator.v0.9.2",
  1585  						Version:     "0.9.2",
  1586  					}, nil)
  1587  
  1588  					listBundlesFake.RecvReturnsOnCall(1, &api.Bundle{
  1589  						PackageName: "etcd",
  1590  						ChannelName: "deprecated",
  1591  						CsvName:     "etcdoperator.v0.9.2",
  1592  						Version:     "0.9.2",
  1593  						Deprecation: &api.Deprecation{
  1594  							Message: "This version of the package is deprecated",
  1595  						},
  1596  					}, nil)
  1597  					listBundlesFake.RecvReturnsOnCall(2, nil, io.EOF)
  1598  
  1599  					listFake := &fakes.FakeRegistry_ListPackagesClient{}
  1600  					listFake.RecvReturnsOnCall(0, &api.PackageName{Name: "etcd"}, nil)
  1601  					listFake.RecvReturnsOnCall(1, nil, io.EOF)
  1602  
  1603  					clientFake := &fakes.FakeRegistryClient{}
  1604  					clientFake.ListBundlesReturns(listBundlesFake, nil)
  1605  					clientFake.ListPackagesReturns(listFake, nil)
  1606  					clientFake.GetPackageReturnsOnCall(0, &api.Package{
  1607  						Name: "etcd",
  1608  						Deprecation: &api.Deprecation{
  1609  							Message: "This package is deprecated",
  1610  						},
  1611  						Channels: []*api.Channel{
  1612  							{
  1613  								Name:    "alpha",
  1614  								CsvName: "etcdperator.v0.9.2",
  1615  							},
  1616  							{
  1617  								Name:    "deprecated",
  1618  								CsvName: "etcdoperator.v0.9.2",
  1619  								Deprecation: &api.Deprecation{
  1620  									Message: "This channel is deprecated",
  1621  								},
  1622  							},
  1623  						},
  1624  						DefaultChannelName: "alpha",
  1625  					}, nil)
  1626  					clientFake.GetBundleForChannelReturnsOnCall(0, &api.Bundle{
  1627  						CsvName:     "etcdoperator.v0.9.2",
  1628  						PackageName: "etcd",
  1629  						ChannelName: "alpha",
  1630  						CsvJson:     etcdCSVJSON,
  1631  						Object: []string{
  1632  							etcdCSVJSON,
  1633  							etcdBackupsCRDJSON,
  1634  							etcdUpgradesCRDJSON,
  1635  							etcdRestoresCRDJSON,
  1636  						},
  1637  					}, nil)
  1638  
  1639  					clientFake.GetBundleForChannelReturnsOnCall(1, &api.Bundle{
  1640  						CsvName:     "etcdoperator.v0.9.2",
  1641  						PackageName: "etcd",
  1642  						ChannelName: "alpha",
  1643  						CsvJson:     etcdCSVJSON,
  1644  						Object: []string{
  1645  							etcdCSVJSON,
  1646  							etcdBackupsCRDJSON,
  1647  							etcdUpgradesCRDJSON,
  1648  							etcdRestoresCRDJSON,
  1649  						},
  1650  						Deprecation: &api.Deprecation{
  1651  							Message: "This channel is deprecated",
  1652  						},
  1653  					}, nil)
  1654  
  1655  					return &registryClient{clientFake, catsrc, nil}
  1656  				}(),
  1657  			},
  1658  			requestNamespace: "ns",
  1659  			expectedErr:      "",
  1660  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1661  				{
  1662  					ObjectMeta: metav1.ObjectMeta{
  1663  						Name:      "etcd",
  1664  						Namespace: "ns",
  1665  						Labels: labels.Set{
  1666  							"catalog":                         "cool-operators",
  1667  							"catalog-namespace":               "ns",
  1668  							"provider":                        "CoreOS, Inc",
  1669  							"provider-url":                    "",
  1670  							"operatorframework.io/arch.amd64": "supported",
  1671  							"operatorframework.io/os.linux":   "supported",
  1672  						},
  1673  					},
  1674  					Status: operators.PackageManifestStatus{
  1675  						CatalogSource:          "cool-operators",
  1676  						CatalogSourceNamespace: "ns",
  1677  						PackageName:            "etcd",
  1678  						Provider: operators.AppLink{
  1679  							Name: "CoreOS, Inc",
  1680  						},
  1681  						Deprecation: &operators.Deprecation{
  1682  							Message: "This package is deprecated",
  1683  						},
  1684  						DefaultChannel: "alpha",
  1685  						Channels: []operators.PackageChannel{
  1686  							{
  1687  								Name:       "alpha",
  1688  								CurrentCSV: "etcdoperator.v0.9.2",
  1689  								CurrentCSVDesc: func() operators.CSVDescription {
  1690  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1691  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1692  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1693  								}(),
  1694  								Entries: []operators.ChannelEntry{
  1695  									{
  1696  										Name:    "etcdoperator.v0.9.2",
  1697  										Version: "0.9.2",
  1698  									},
  1699  								},
  1700  							},
  1701  							{
  1702  								Name: "deprecated",
  1703  								Entries: []operators.ChannelEntry{
  1704  									{
  1705  										Name:    "etcdoperator.v0.9.2",
  1706  										Version: "0.9.2",
  1707  										Deprecation: &operators.Deprecation{
  1708  											Message: "This version of the package is deprecated",
  1709  										},
  1710  									},
  1711  								},
  1712  								Deprecation: &operators.Deprecation{
  1713  									Message: "This channel is deprecated",
  1714  								},
  1715  								CurrentCSV: "etcdoperator.v0.9.2",
  1716  								CurrentCSVDesc: func() operators.CSVDescription {
  1717  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1718  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1719  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1720  								}(),
  1721  							},
  1722  						},
  1723  					},
  1724  				},
  1725  			}},
  1726  		},
  1727  	}
  1728  
  1729  	for _, test := range tests {
  1730  		t.Run(test.name, func(t *testing.T) {
  1731  			ctx, cancel := context.WithCancel(context.TODO())
  1732  			defer cancel()
  1733  			provider, err := NewFakeRegistryProvider(ctx, nil, nil, test.globalNS)
  1734  			require.NoError(t, err)
  1735  
  1736  			for _, c := range test.registryClients {
  1737  				require.NoError(t, provider.refreshCache(ctx, c))
  1738  			}
  1739  
  1740  			packageManifestList, err := provider.List(test.requestNamespace, labels.Everything())
  1741  			if test.expectedErr != "" {
  1742  				require.NotNil(t, err)
  1743  				require.Equal(t, test.expectedErr, err.Error())
  1744  			} else {
  1745  				require.Nil(t, err)
  1746  			}
  1747  			require.Equal(t, len(test.expected.Items), len(packageManifestList.Items))
  1748  			require.ElementsMatch(t, test.expected.Items, packageManifestList.Items)
  1749  		})
  1750  	}
  1751  }
  1752  
  1753  type LabelReq struct {
  1754  	key       string
  1755  	op        selection.Operator
  1756  	strValues []string
  1757  }
  1758  
  1759  func TestRegistryProviderListLabels(t *testing.T) {
  1760  	tests := []struct {
  1761  		name             string
  1762  		globalNS         string
  1763  		labelReq         *LabelReq
  1764  		registryClients  []*registryClient
  1765  		requestNamespace string
  1766  		expectedErr      string
  1767  		expected         *operators.PackageManifestList
  1768  	}{
  1769  		{
  1770  			name:     "PackagesFound/LabelsSupported/SingleNS",
  1771  			globalNS: "ns",
  1772  			labelReq: &LabelReq{
  1773  				key: "catalog",
  1774  				op:  selection.Exists,
  1775  			},
  1776  			registryClients: []*registryClient{
  1777  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1778  			},
  1779  			requestNamespace: "ns",
  1780  			expectedErr:      "",
  1781  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1782  				{
  1783  					ObjectMeta: metav1.ObjectMeta{
  1784  						Name:      "prometheus",
  1785  						Namespace: "ns",
  1786  						Labels: labels.Set{
  1787  							"catalog":                         "cool-operators",
  1788  							"catalog-namespace":               "ns",
  1789  							"provider":                        "Red Hat",
  1790  							"provider-url":                    "",
  1791  							"operatorframework.io/arch.amd64": "supported",
  1792  							"operatorframework.io/os.linux":   "supported",
  1793  						},
  1794  					},
  1795  					Status: operators.PackageManifestStatus{
  1796  						CatalogSource:          "cool-operators",
  1797  						CatalogSourceNamespace: "ns",
  1798  						PackageName:            "prometheus",
  1799  						Provider: operators.AppLink{
  1800  							Name: "Red Hat",
  1801  						},
  1802  						DefaultChannel: "preview",
  1803  						Channels: []operators.PackageChannel{
  1804  							{
  1805  								Name:       "preview",
  1806  								CurrentCSV: "prometheusoperator.0.22.2",
  1807  								CurrentCSVDesc: func() operators.CSVDescription {
  1808  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1809  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1810  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1811  								}(),
  1812  								Entries: prometheusChannelEntries,
  1813  							},
  1814  						},
  1815  					},
  1816  				},
  1817  				{
  1818  					ObjectMeta: metav1.ObjectMeta{
  1819  						Name:      "etcd",
  1820  						Namespace: "ns",
  1821  						Labels: labels.Set{
  1822  							"catalog":                         "cool-operators",
  1823  							"catalog-namespace":               "ns",
  1824  							"provider":                        "CoreOS, Inc",
  1825  							"provider-url":                    "",
  1826  							"operatorframework.io/arch.amd64": "supported",
  1827  							"operatorframework.io/os.linux":   "supported",
  1828  						},
  1829  					},
  1830  					Status: operators.PackageManifestStatus{
  1831  						CatalogSource:          "cool-operators",
  1832  						CatalogSourceNamespace: "ns",
  1833  						PackageName:            "etcd",
  1834  						Provider: operators.AppLink{
  1835  							Name: "CoreOS, Inc",
  1836  						},
  1837  						DefaultChannel: "alpha",
  1838  						Channels: []operators.PackageChannel{
  1839  							{
  1840  								Name:       "alpha",
  1841  								CurrentCSV: "etcdoperator.v0.9.2",
  1842  								CurrentCSVDesc: func() operators.CSVDescription {
  1843  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1844  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1845  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1846  								}(),
  1847  								Entries: etcdChannelEntries,
  1848  							},
  1849  						},
  1850  					},
  1851  				},
  1852  			}},
  1853  		},
  1854  		{
  1855  			name:     "PackagesFound/LabelsSupported/GlobalNS",
  1856  			globalNS: "ns",
  1857  			labelReq: &LabelReq{
  1858  				key: "catalog",
  1859  				op:  selection.Exists,
  1860  			},
  1861  			registryClients: []*registryClient{
  1862  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1863  			},
  1864  			requestNamespace: "",
  1865  			expectedErr:      "",
  1866  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1867  				{
  1868  					ObjectMeta: metav1.ObjectMeta{
  1869  						Name:      "prometheus",
  1870  						Namespace: "ns",
  1871  						Labels: labels.Set{
  1872  							"catalog":                         "cool-operators",
  1873  							"catalog-namespace":               "ns",
  1874  							"provider":                        "Red Hat",
  1875  							"provider-url":                    "",
  1876  							"operatorframework.io/arch.amd64": "supported",
  1877  							"operatorframework.io/os.linux":   "supported",
  1878  						},
  1879  					},
  1880  					Status: operators.PackageManifestStatus{
  1881  						CatalogSource:          "cool-operators",
  1882  						CatalogSourceNamespace: "ns",
  1883  						PackageName:            "prometheus",
  1884  						Provider: operators.AppLink{
  1885  							Name: "Red Hat",
  1886  						},
  1887  						DefaultChannel: "preview",
  1888  						Channels: []operators.PackageChannel{
  1889  							{
  1890  								Name:       "preview",
  1891  								CurrentCSV: "prometheusoperator.0.22.2",
  1892  								CurrentCSVDesc: func() operators.CSVDescription {
  1893  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1894  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1895  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1896  								}(),
  1897  								Entries: prometheusChannelEntries,
  1898  							},
  1899  						},
  1900  					},
  1901  				},
  1902  				{
  1903  					ObjectMeta: metav1.ObjectMeta{
  1904  						Name:      "etcd",
  1905  						Namespace: "ns",
  1906  						Labels: labels.Set{
  1907  							"catalog":                         "cool-operators",
  1908  							"catalog-namespace":               "ns",
  1909  							"provider":                        "CoreOS, Inc",
  1910  							"provider-url":                    "",
  1911  							"operatorframework.io/arch.amd64": "supported",
  1912  							"operatorframework.io/os.linux":   "supported",
  1913  						},
  1914  					},
  1915  					Status: operators.PackageManifestStatus{
  1916  						CatalogSource:          "cool-operators",
  1917  						CatalogSourceNamespace: "ns",
  1918  						PackageName:            "etcd",
  1919  						Provider: operators.AppLink{
  1920  							Name: "CoreOS, Inc",
  1921  						},
  1922  						DefaultChannel: "alpha",
  1923  						Channels: []operators.PackageChannel{
  1924  							{
  1925  								Name:       "alpha",
  1926  								CurrentCSV: "etcdoperator.v0.9.2",
  1927  								CurrentCSVDesc: func() operators.CSVDescription {
  1928  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1929  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  1930  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  1931  								}(),
  1932  								Entries: etcdChannelEntries,
  1933  							},
  1934  						},
  1935  					},
  1936  				},
  1937  			}},
  1938  		},
  1939  		{
  1940  			name:     "PackagesNotFound/LabelsNotSupported/GlobalNS",
  1941  			globalNS: "",
  1942  			labelReq: &LabelReq{
  1943  				key: "catalog",
  1944  				op:  selection.DoesNotExist,
  1945  			},
  1946  			registryClients: []*registryClient{
  1947  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", ""), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1948  			},
  1949  			requestNamespace: "",
  1950  			expectedErr:      "",
  1951  			expected:         &operators.PackageManifestList{Items: []operators.PackageManifest{}},
  1952  		},
  1953  		{
  1954  			name:     "PackagesFound/LabelsNotProvided/GlobalNS",
  1955  			globalNS: "",
  1956  			registryClients: []*registryClient{
  1957  				newTestRegistryClient(t, withRegistryServiceStatus(catalogSource("cool-operators", "ns"), "grpc", "cool-operators", "ns", port, metav1.NewTime(time.Now()))),
  1958  			},
  1959  			requestNamespace: "",
  1960  			expectedErr:      "",
  1961  			expected: &operators.PackageManifestList{Items: []operators.PackageManifest{
  1962  				{
  1963  					ObjectMeta: metav1.ObjectMeta{
  1964  						Name:      "prometheus",
  1965  						Namespace: "ns",
  1966  						Labels: labels.Set{
  1967  							"catalog":                         "cool-operators",
  1968  							"catalog-namespace":               "ns",
  1969  							"provider":                        "Red Hat",
  1970  							"provider-url":                    "",
  1971  							"operatorframework.io/arch.amd64": "supported",
  1972  							"operatorframework.io/os.linux":   "supported",
  1973  						},
  1974  					},
  1975  					Status: operators.PackageManifestStatus{
  1976  						CatalogSource:          "cool-operators",
  1977  						CatalogSourceNamespace: "ns",
  1978  						PackageName:            "prometheus",
  1979  						Provider: operators.AppLink{
  1980  							Name: "Red Hat",
  1981  						},
  1982  						DefaultChannel: "preview",
  1983  						Channels: []operators.PackageChannel{
  1984  							{
  1985  								Name:       "preview",
  1986  								CurrentCSV: "prometheusoperator.0.22.2",
  1987  								CurrentCSVDesc: func() operators.CSVDescription {
  1988  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  1989  									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
  1990  									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
  1991  								}(),
  1992  								Entries: prometheusChannelEntries,
  1993  							},
  1994  						},
  1995  					},
  1996  				},
  1997  				{
  1998  					ObjectMeta: metav1.ObjectMeta{
  1999  						Name:      "etcd",
  2000  						Namespace: "ns",
  2001  						Labels: labels.Set{
  2002  							"catalog":                         "cool-operators",
  2003  							"catalog-namespace":               "ns",
  2004  							"provider":                        "CoreOS, Inc",
  2005  							"provider-url":                    "",
  2006  							"operatorframework.io/arch.amd64": "supported",
  2007  							"operatorframework.io/os.linux":   "supported",
  2008  						},
  2009  					},
  2010  					Status: operators.PackageManifestStatus{
  2011  						CatalogSource:          "cool-operators",
  2012  						CatalogSourceNamespace: "ns",
  2013  						PackageName:            "etcd",
  2014  						Provider: operators.AppLink{
  2015  							Name: "CoreOS, Inc",
  2016  						},
  2017  						DefaultChannel: "alpha",
  2018  						Channels: []operators.PackageChannel{
  2019  							{
  2020  								Name:       "alpha",
  2021  								CurrentCSV: "etcdoperator.v0.9.2",
  2022  								CurrentCSVDesc: func() operators.CSVDescription {
  2023  									csv := operatorsv1alpha1.ClusterServiceVersion{}
  2024  									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
  2025  									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
  2026  								}(),
  2027  								Entries: etcdChannelEntries,
  2028  							},
  2029  						},
  2030  					},
  2031  				},
  2032  			}},
  2033  		},
  2034  	}
  2035  
  2036  	for _, test := range tests {
  2037  		t.Run(test.name, func(t *testing.T) {
  2038  			ctx, cancel := context.WithCancel(context.TODO())
  2039  			defer cancel()
  2040  
  2041  			lab := labels.NewSelector()
  2042  			if test.labelReq != nil {
  2043  				req, err := labels.NewRequirement(test.labelReq.key, test.labelReq.op, test.labelReq.strValues)
  2044  				require.NoError(t, err)
  2045  				lab = lab.Add(*req)
  2046  			}
  2047  
  2048  			provider, err := NewFakeRegistryProvider(ctx, nil, nil, test.globalNS)
  2049  			require.NoError(t, err)
  2050  
  2051  			for _, c := range test.registryClients {
  2052  				require.NoError(t, provider.refreshCache(ctx, c))
  2053  			}
  2054  
  2055  			packageManifestList, err := provider.List(test.requestNamespace, lab)
  2056  			if test.expectedErr != "" {
  2057  				require.NotNil(t, err)
  2058  				require.Equal(t, test.expectedErr, err.Error())
  2059  			} else {
  2060  				require.Nil(t, err)
  2061  			}
  2062  
  2063  			require.Equal(t, len(test.expected.Items), len(packageManifestList.Items))
  2064  			require.ElementsMatch(t, test.expected.Items, packageManifestList.Items)
  2065  		})
  2066  	}
  2067  }
  2068  
  2069  func newTestRegistryClient(t *testing.T, catsrc *operatorsv1alpha1.CatalogSource) *registryClient {
  2070  	conn, err := grpc.Dial(address+catsrc.Status.RegistryServiceStatus.Port, grpc.WithTransportCredentials(insecure.NewCredentials()))
  2071  	require.NoError(t, err, "could not set up test grpc connection")
  2072  	return newRegistryClient(catsrc, conn)
  2073  }