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\":\"iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC\",\"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\":\"iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC\",\"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 := ®istryClient{ 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 := ®istryClient{ 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 ®istryClient{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 ®istryClient{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 }