diff --git a/cmd/opm/alpha/bundle/cmd.go b/cmd/opm/alpha/bundle/cmd.go index 6abccd54e..0fea0a57c 100644 --- a/cmd/opm/alpha/bundle/cmd.go +++ b/cmd/opm/alpha/bundle/cmd.go @@ -13,5 +13,6 @@ func NewCmd() *cobra.Command { runCmd.AddCommand(newBundleGenerateCmd()) runCmd.AddCommand(newBundleBuildCmd()) + runCmd.AddCommand(extractCmd) return runCmd } diff --git a/cmd/opm/alpha/bundle/extract.go b/cmd/opm/alpha/bundle/extract.go new file mode 100644 index 000000000..45f8932fc --- /dev/null +++ b/cmd/opm/alpha/bundle/extract.go @@ -0,0 +1,65 @@ +package bundle + +import ( + "fmt" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/pkg/configmap" +) + +var extractCmd = &cobra.Command{ + Use: "extract", + Short: "Extracts the data in a bundle directory via ConfigMap", + Long: "Extract takes as input a directory containing manifests and writes the per file contents to a ConfipMap", + + PreRunE: func(cmd *cobra.Command, args []string) error { + if debug, _ := cmd.Flags().GetBool("debug"); debug { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + }, + + RunE: runExtractCmd, +} + +func init() { + extractCmd.Flags().Bool("debug", false, "enable debug logging") + extractCmd.Flags().StringP("kubeconfig", "k", "", "absolute path to kubeconfig file") + extractCmd.Flags().StringP("manifestsdir", "m", "/", "path to directory containing manifests") + extractCmd.Flags().StringP("configmapname", "c", "", "name of configmap to write bundle data") + extractCmd.Flags().StringP("namespace", "n", "openshift-operator-lifecycle-manager", "namespace to write configmap data") + extractCmd.Flags().Uint64P("datalimit", "l", 1<<20, "maximum limit in bytes for total bundle data") + extractCmd.MarkPersistentFlagRequired("configmapname") +} + +func runExtractCmd(cmd *cobra.Command, args []string) error { + manifestsDir, err := cmd.Flags().GetString("manifestsdir") + if err != nil { + return err + } + kubeconfig, err := cmd.Flags().GetString("kubeconfig") + if err != nil { + return err + } + configmapName, err := cmd.Flags().GetString("configmapname") + if err != nil { + return err + } + namespace, err := cmd.Flags().GetString("namespace") + if err != nil { + return err + } + datalimit, err := cmd.Flags().GetUint64("datalimit") + if err != nil { + return err + } + + loader := configmap.NewConfigMapLoaderForDirectory(configmapName, namespace, manifestsDir, kubeconfig) + if err := loader.Populate(datalimit); err != nil { + return fmt.Errorf("error loading manifests from directory: %s", err) + } + + return nil +} diff --git a/go.mod b/go.mod index 0a8190b9d..86c9a95d9 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db github.com/imdario/mergo v0.3.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 + github.com/onsi/ginkgo v1.8.0 + github.com/onsi/gomega v1.5.0 github.com/otiai10/copy v1.0.1 github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect github.com/sirupsen/logrus v1.4.2 diff --git a/pkg/appregistry/appregistry.go b/pkg/appregistry/appregistry.go index 99d8bb426..a19a28296 100644 --- a/pkg/appregistry/appregistry.go +++ b/pkg/appregistry/appregistry.go @@ -5,10 +5,8 @@ import ( "github.com/sirupsen/logrus" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" + "github.com/operator-framework/operator-registry/pkg/client" "github.com/operator-framework/operator-registry/pkg/registry" ) @@ -19,7 +17,7 @@ import ( // downloadPath specifies the folder where the downloaded nested bundle(s) will // be stored. func NewLoader(kubeconfig string, dbName string, downloadPath string, logger *logrus.Entry) (*AppregistryLoader, error) { - kubeClient, err := NewKubeClient(kubeconfig, logger) + kubeClient, err := client.NewKubeClient(kubeconfig, logger.Logger) if err != nil { return nil, err } @@ -104,23 +102,3 @@ func (a *AppregistryLoader) Load(csvSources []string, csvPackages string) (regis return store, utilerrors.NewAggregate(errs) } - -func NewKubeClient(kubeconfig string, logger *logrus.Entry) (clientset *kubernetes.Clientset, err error) { - var config *rest.Config - - if kubeconfig != "" { - logger.Infof("Loading kube client config from path %q", kubeconfig) - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - } else { - logger.Infof("Using in-cluster kube client config") - config, err = rest.InClusterConfig() - } - - if err != nil { - err = fmt.Errorf("Cannot load config for REST client: %v", err) - return - } - - clientset, err = kubernetes.NewForConfig(config) - return -} diff --git a/pkg/client/kubeclient.go b/pkg/client/kubeclient.go new file mode 100644 index 000000000..17a6532f8 --- /dev/null +++ b/pkg/client/kubeclient.go @@ -0,0 +1,35 @@ +package client + +import ( + "fmt" + "os" + + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +func NewKubeClient(kubeconfig string, logger *logrus.Logger) (clientset *kubernetes.Clientset, err error) { + var config *rest.Config + + if overrideConfig := os.Getenv(clientcmd.RecommendedConfigPathEnvVar); overrideConfig != "" { + kubeconfig = overrideConfig + } + + if kubeconfig != "" { + logger.Infof("Loading kube client config from path %q", kubeconfig) + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + } else { + logger.Infof("Using in-cluster kube client config") + config, err = rest.InClusterConfig() + } + + if err != nil { + err = fmt.Errorf("Cannot load config for REST client: %v", err) + return + } + + clientset, err = kubernetes.NewForConfig(config) + return +} diff --git a/pkg/configmap/configmap_writer.go b/pkg/configmap/configmap_writer.go new file mode 100644 index 000000000..9a3a2f511 --- /dev/null +++ b/pkg/configmap/configmap_writer.go @@ -0,0 +1,222 @@ +package configmap + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + + "github.com/ghodss/yaml" + _ "github.com/mattn/go-sqlite3" + "github.com/sirupsen/logrus" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/operator-framework/operator-registry/pkg/client" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" +) + +// configmap keys can contain underscores, but configmap names can not +var unallowedKeyChars = regexp.MustCompile("[^-A-Za-z0-9_.]") + +const ( + EnvContainerImage = "CONTAINER_IMAGE" + ConfigMapImageAnnotationKey = "olm.sourceImage" +) + +type AnnotationsFile struct { + Annotations struct { + Resources string `json:"operators.operatorframework.io.bundle.manifests.v1"` + MediaType string `json:"operators.operatorframework.io.bundle.mediatype.v1"` + Metadata string `json:"operators.operatorframework.io.bundle.metadata.v1"` + Package string `json:"operators.operatorframework.io.bundle.package.v1"` + Channels string `json:"operators.operatorframework.io.bundle.channels.v1"` + ChannelDefault string `json:"operators.operatorframework.io.bundle.channel.default.v1"` + } `json:"annotations"` +} + +type ConfigMapWriter struct { + manifestsDir string + configMapName string + namespace string + clientset *kubernetes.Clientset +} + +func NewConfigMapLoaderForDirectory(configMapName, namespace, manifestsDir, kubeconfig string) *ConfigMapWriter { + clientset, err := client.NewKubeClient(kubeconfig, logrus.StandardLogger()) + if err != nil { + logrus.Fatalf("cluster config failed: %v", err) + } + + return &ConfigMapWriter{ + manifestsDir: manifestsDir, + configMapName: configMapName, + namespace: namespace, + clientset: clientset, + } +} + +func TranslateInvalidChars(input string) string { + validConfigMapKey := unallowedKeyChars.ReplaceAllString(input, "~") + return validConfigMapKey +} + +func (c *ConfigMapWriter) Populate(maxDataSizeLimit uint64) error { + subDirs := []string{"manifests/", "metadata/"} + + configMapPopulate, err := c.clientset.CoreV1().ConfigMaps(c.namespace).Get(c.configMapName, metav1.GetOptions{}) + if err != nil { + return err + } + configMapPopulate.Data = map[string]string{} + + var totalSize uint64 + for _, dir := range subDirs { + completePath := c.manifestsDir + dir + files, err := ioutil.ReadDir(completePath) + if err != nil { + logrus.Errorf("read dir failed: %v", err) + return err + } + + for _, file := range files { + log := logrus.WithField("file", completePath+file.Name()) + log.Info("Reading file") + content, err := ioutil.ReadFile(completePath + file.Name()) + if err != nil { + log.Errorf("read failed: %v", err) + return err + } + totalSize += uint64(len(content)) + if totalSize > maxDataSizeLimit { + log.Errorf("File with size %v exceeded %v limit, aboring", len(content), maxDataSizeLimit) + return fmt.Errorf("file %v bigger than total allowed limit", file.Name()) + } + + validConfigMapKey := TranslateInvalidChars(file.Name()) + if validConfigMapKey != file.Name() { + logrus.WithFields(logrus.Fields{ + "file.Name": file.Name(), + "validConfigMapKey": validConfigMapKey, + }).Info("translated filename for configmap comptability") + } + if file.Name() == bundle.AnnotationsFile { + var annotationsFile AnnotationsFile + err := yaml.Unmarshal(content, &annotationsFile) + if err != nil { + return err + } + configMapPopulate.SetAnnotations(map[string]string{ + bundle.ManifestsLabel: annotationsFile.Annotations.Resources, + bundle.MediatypeLabel: annotationsFile.Annotations.MediaType, + bundle.MetadataLabel: annotationsFile.Annotations.Metadata, + bundle.PackageLabel: annotationsFile.Annotations.Package, + bundle.ChannelsLabel: annotationsFile.Annotations.Channels, + bundle.ChannelDefaultLabel: annotationsFile.Annotations.ChannelDefault, + }) + } else { + configMapPopulate.Data[validConfigMapKey] = string(content) + } + } + } + + if sourceImage := os.Getenv(EnvContainerImage); sourceImage != "" { + annotations := configMapPopulate.GetAnnotations() + annotations[ConfigMapImageAnnotationKey] = sourceImage + } + + _, err = c.clientset.CoreV1().ConfigMaps(c.namespace).Update(configMapPopulate) + if err != nil { + return err + } + return nil +} + +// LaunchBundleImage will launch a bundle image and also create a configmap for +// storing the data that will be updated to contain the bundle image data. It is +// the responsibility of the caller to delete the job, the pod, and the configmap +// when done. This function is intended to be called from OLM, but is put here +// for locality. +func LaunchBundleImage(kubeclient kubernetes.Interface, bundleImage, initImage, namespace string) (*corev1.ConfigMap, *batchv1.Job, error) { + // create configmap for bundle image data to write to (will be returned) + newConfigMap, err := kubeclient.CoreV1().ConfigMaps(namespace).Create(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "bundle-image-", + }, + }) + if err != nil { + return nil, nil, err + } + + launchJob := batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "deploy-bundle-image-", + }, + Spec: batchv1.JobSpec{ + //ttlSecondsAfterFinished: 0 // can use in the future to not have to clean up job + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bundle-image", + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + Containers: []corev1.Container{ + { + Name: "bundle-image", + Image: bundleImage, + ImagePullPolicy: "Never", + Command: []string{"/injected/opm", "alpha", "bundle", "extract", "-n", namespace, "-c", newConfigMap.GetName()}, + Env: []corev1.EnvVar{ + { + Name: EnvContainerImage, + Value: bundleImage, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "copydir", + MountPath: "/injected", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "copy-binary", + Image: initImage, + ImagePullPolicy: "Never", + Command: []string{"/bin/cp", "opm", "/copy-dest"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "copydir", + MountPath: "/copy-dest", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "copydir", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + }, + }, + } + launchedJob, err := kubeclient.BatchV1().Jobs(namespace).Create(&launchJob) + if err != nil { + err := kubeclient.CoreV1().ConfigMaps(namespace).Delete(newConfigMap.GetName(), &metav1.DeleteOptions{}) + if err != nil { + // already in an error, so just report it + logrus.Errorf("failed to remove configmap: %v", err) + } + return nil, nil, err + } + + return newConfigMap, launchedJob, nil +} diff --git a/test/e2e/bundle_image_test.go b/test/e2e/bundle_image_test.go new file mode 100644 index 000000000..1cce283a9 --- /dev/null +++ b/test/e2e/bundle_image_test.go @@ -0,0 +1,773 @@ +package e2e_test + +import ( + "fmt" + "os" + "os/exec" + "time" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + + "github.com/sirupsen/logrus" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + + "github.com/operator-framework/operator-registry/pkg/client" + "github.com/operator-framework/operator-registry/pkg/configmap" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" +) + +var builderCmd string + +const ( + imageDirectory = "image-bundle/" +) + +func Logf(format string, a ...interface{}) { + fmt.Fprintf(ginkgo.GinkgoWriter, "INFO: "+format+"\n", a...) +} + +// checks command that it exists in $PATH, isn't a directory, and has executable permissions set +func checkCommand(filename string) string { + path, err := exec.LookPath(filename) + if err != nil { + Logf("LookPath error: %v", err) + return "" + } + + info, err := os.Stat(path) + if os.IsNotExist(err) { + Logf("IsNotExist error: %v", err) + return "" + } + if info.IsDir() { + return "" + } + perm := info.Mode() + if perm&0111 != 0x49 { // 000100100100 - execute bits set for user/group/everybody + Logf("permissions failure: %#v %#v", perm, perm&0111) + return "" + } + + Logf("Using builder at '%v'\n", path) + return path +} + +func init() { + logrus.SetOutput(ginkgo.GinkgoWriter) + + if builderCmd = checkCommand("docker"); builderCmd != "" { + return + } + if builderCmd = checkCommand("podman"); builderCmd != "" { + return + } +} + +// newlines included in each section, uses spaces and no tabs +func getConfigMapDataSection() map[string]string { + return map[string]string{ + "kiali.crd.yaml": `apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: kialis.kiali.io + labels: + app: kiali-operator +spec: + group: kiali.io + names: + kind: Kiali + listKind: KialiList + plural: kialis + singular: kiali + scope: Namespaced + subresources: + status: {} + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +`, + "kiali.monitoringdashboards.crd.yaml": `apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: monitoringdashboards.monitoring.kiali.io + labels: + app: kiali +spec: + group: monitoring.kiali.io + names: + kind: MonitoringDashboard + listKind: MonitoringDashboardList + plural: monitoringdashboards + singular: monitoringdashboard + scope: Namespaced + version: v1alpha1 +`, + "kiali.package.yaml": `packageName: kiali +channels: +- name: alpha + currentCSV: kiali-operator.v1.4.2 +- name: stable + currentCSV: kiali-operator.v1.4.2 +defaultChannel: stable +`, + "kiali.v1.4.2.clusterserviceversion.yaml": `apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: kiali-operator.v1.4.2 + namespace: placeholder + annotations: + categories: Monitoring,Logging & Tracing + certified: "false" + containerImage: quay.io/kiali/kiali-operator:v1.4.2 + capabilities: Basic Install + support: Kiali + description: "Kiali project provides answers to the questions: What microservices are part of my Istio service mesh and how are they connected?" + repository: https://github.com/kiali/kiali + createdAt: 2019-09-12T00:00:00Z + alm-examples: |- + [ + { + "apiVersion": "kiali.io/v1alpha1", + "kind": "Kiali", + "metadata": { + "name": "kiali" + }, + "spec": { + "installation_tag": "My Kiali", + "istio_namespace": "istio-system", + "deployment": { + "namespace": "istio-system", + "verbose_mode": "4", + "view_only_mode": false + }, + "external_services": { + "grafana": { + "url": "" + }, + "prometheus": { + "url": "" + }, + "tracing": { + "url": "" + } + }, + "server": { + "web_root": "/mykiali" + } + } + }, + { + "apiVersion": "monitoring.kiali.io/v1alpha1", + "kind": "MonitoringDashboard", + "metadata": { + "name": "myappdashboard" + }, + "spec": { + "title": "My App Dashboard", + "items": [ + { + "chart": { + "name": "My App Processing Duration", + "unit": "seconds", + "spans": 6, + "metricName": "my_app_duration_seconds", + "dataType": "histogram", + "aggregations": [ + { + "label": "id", + "displayName": "ID" + } + ] + } + } + ] + } + } + ] +spec: + version: 1.4.2 + maturity: stable + replaces: kiali-operator.v1.3.1 + displayName: Kiali Operator + description: |- + A Microservice Architecture breaks up the monolith into many smaller pieces that are composed together. Patterns to secure the communication between services like fault tolerance (via timeout, retry, circuit breaking, etc.) have come up as well as distributed tracing to be able to see where calls are going. + + A service mesh can now provide these services on a platform level and frees the application writers from those tasks. Routing decisions are done at the mesh level. + + Kiali works with Istio, in OpenShift or Kubernetes, to visualize the service mesh topology, to provide visibility into features like circuit breakers, request rates and more. It offers insights about the mesh components at different levels, from abstract Applications to Services and Workloads. + + See [https://www.kiali.io](https://www.kiali.io) to read more. + + ### Prerequisites + + Today Kiali works with Istio. So before you install Kiali, you must have already installed Istio. Note that Istio can come pre-bundled with Kiali (specifically if you installed the Istio demo helm profile or if you installed Istio with the helm option '--set kiali.enabled=true'). If you already have the pre-bundled Kiali in your Istio environment and you want to install Kiali via the Kiali Operator, uninstall the pre-bundled Kiali first. You can do this via this command: + + kubectl delete --ignore-not-found=true all,secrets,sa,templates,configmaps,deployments,clusterroles,clusterrolebindings,ingresses,customresourcedefinitions --selector="app=kiali" -n istio-system + + When you install Kiali in a non-OpenShift Kubernetes environment, the authentication strategy will default to ` + "`" + "login" + "`" + ". When using the authentication strategy of " + "`" + "login" + "`" + ", you are required to create a Kubernetes Secret with a " + "`" + "username" + "`" + " and " + "`" + "passphrase" + "`" + " that you want users to provide in order to successfully log into Kiali. Here is an example command you can execute to create such a secret (with a username of " + "`" + "admin" + "`" + " and a passphrase of " + "`" + "admin" + "`" + `): + + kubectl create secret generic kiali -n istio-system --from-literal "username=admin" --from-literal "passphrase=admin" + + ### Kiali Custom Resource Configuration Settings + + For quick descriptions of all the settings you can configure in the Kiali Custom Resource (CR), see the file [kiali_cr.yaml](https://github.com/kiali/kiali/blob/v1.4.2/operator/deploy/kiali/kiali_cr.yaml) + + ### Accessing the UI + + By default, the Kiali operator exposes the Kiali UI as a Route on OpenShift or Ingress on Kubernetes. + On OpenShift, the default root context path is '/' and on Kubernetes it is '/kiali' though you can change this by configuring the 'web_root' setting in the Kiali CR. + icon: + - base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIyLjAuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMjgwIDEyODAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyODAgMTI4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiMwMTMxNDQ7fQoJLnN0MXtmaWxsOiMwMDkzREQ7fQo8L3N0eWxlPgo8Zz4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04MTAuOSwxODAuOWMtMjUzLjYsMC00NTkuMSwyMDUuNS00NTkuMSw0NTkuMXMyMDUuNSw0NTkuMSw0NTkuMSw0NTkuMVMxMjcwLDg5My42LDEyNzAsNjQwCgkJUzEwNjQuNSwxODAuOSw4MTAuOSwxODAuOXogTTgxMC45LDEwMjkuMmMtMjE1LDAtMzg5LjItMTc0LjMtMzg5LjItMzg5LjJjMC0yMTUsMTc0LjMtMzg5LjIsMzg5LjItMzg5LjJTMTIwMC4xLDQyNSwxMjAwLjEsNjQwCgkJUzEwMjUuOSwxMDI5LjIsODEwLjksMTAyOS4yeiIvPgoJPHBhdGggY2xhc3M9InN0MSIgZD0iTTY1My4zLDI4NGMtMTM2LjQsNjAuNS0yMzEuNiwxOTcuMS0yMzEuNiwzNTZjMCwxNTguOCw5NS4yLDI5NS41LDIzMS42LDM1NmM5OC40LTg3LjEsMTYwLjQtMjE0LjMsMTYwLjQtMzU2CgkJQzgxMy43LDQ5OC4zLDc1MS42LDM3MS4xLDY1My4zLDI4NHoiLz4KCTxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0zNTEuOCw2NDBjMC0xMDkuOCwzOC42LTIxMC41LDEwMi44LTI4OS41Yy0zOS42LTE4LjItODMuNi0yOC4zLTEzMC0yOC4zQzE1MC45LDMyMi4yLDEwLDQ2NC41LDEwLDY0MAoJCXMxNDAuOSwzMTcuOCwzMTQuNiwzMTcuOGM0Ni4zLDAsOTAuNC0xMC4xLDEzMC0yOC4zQzM5MC4zLDg1MC41LDM1MS44LDc0OS44LDM1MS44LDY0MHoiLz4KPC9nPgo8L3N2Zz4K + mediatype: image/svg+xml + keywords: ['service-mesh', 'observability', 'monitoring', 'maistra', 'istio'] + maintainers: + - name: Kiali Developers Google Group + email: kiali-dev@googlegroups.com + provider: + name: Kiali + labels: + name: kiali-operator + selector: + matchLabels: + name: kiali-operator + links: + - name: Getting Started Guide + url: https://www.kiali.io/documentation/getting-started/ + - name: Features + url: https://www.kiali.io/documentation/features + - name: Documentation Home + url: https://www.kiali.io/documentation + - name: Blogs and Articles + url: https://medium.com/kialiproject + - name: Server Source Code + url: https://github.com/kiali/kiali + - name: UI Source Code + url: https://github.com/kiali/kiali-ui + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + customresourcedefinitions: + owned: + - name: kialis.kiali.io + group: kiali.io + description: A configuration file for a Kiali installation. + displayName: Kiali + kind: Kiali + version: v1alpha1 + resources: + - kind: Deployment + version: apps/v1 + - kind: Pod + version: v1 + - kind: Service + version: v1 + - kind: ConfigMap + version: v1 + - kind: OAuthClient + version: oauth.openshift.io/v1 + - kind: Route + version: route.openshift.io/v1 + - kind: Ingress + version: extensions/v1beta1 + specDescriptors: + - displayName: Authentication Strategy + description: "Determines how a user is to log into Kiali. Choose 'login' to use a username and passphrase as defined in a Secret. Choose 'anonymous' to allow full access to Kiali without requiring credentials (use this at your own risk). Choose 'openshift' if on OpenShift to use the OpenShift OAuth login which controls access based on the individual's OpenShift RBAC roles. Default: openshift (when deployed in OpenShift); login (when deployed in Kubernetes)" + path: auth.strategy + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: Kiali Namespace + description: "The namespace where Kiali and its associated resources will be created. Default: istio-system" + path: deployment.namespace + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: Secret Name + description: "If Kiali is configured with auth.strategy 'login', an admin must create a Secret with credentials ('username' and 'passphrase') which will be used to authenticate users logging into Kiali. This setting defines the name of that secret. Default: kiali" + path: deployment.secret_name + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Secret' + - displayName: Verbose Mode + description: "Determines the priority levels of log messages Kiali will output. Typical values are '3' for INFO and higher priority messages, '4' for DEBUG and higher priority messages (this makes the logs more noisy). Default: 3" + path: deployment.verbose_mode + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: View Only Mode + description: "When true, Kiali will be in 'view only' mode, allowing the user to view and retrieve management and monitoring data for the service mesh, but not allow the user to modify the service mesh. Default: false" + path: deployment.view_only_mode + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:booleanSwitch' + - displayName: Web Root + description: "Defines the root context path for the Kiali console, API endpoints and readiness/liveness probes. Default: / (when deployed on OpenShift; /kiali (when deployed on Kubernetes)" + path: server.web_root + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - name: monitoringdashboards.monitoring.kiali.io + group: monitoring.kiali.io + description: A configuration file for defining an individual metric dashboard. + displayName: Monitoring Dashboard + kind: MonitoringDashboard + version: v1alpha1 + resources: [] + specDescriptors: + - displayName: Title + description: "The title of the dashboard." + path: title + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + apiservicedefinitions: {} + install: + strategy: deployment + spec: + deployments: + - name: kiali-operator + spec: + replicas: 1 + selector: + matchLabels: + app: kiali-operator + template: + metadata: + name: kiali-operator + labels: + app: kiali-operator + version: v1.4.2 + spec: + serviceAccountName: kiali-operator + containers: + - name: ansible + command: + - /usr/local/bin/ao-logs + - /tmp/ansible-operator/runner + - stdout + image: quay.io/kiali/kiali-operator:v1.4.2 + imagePullPolicy: "IfNotPresent" + volumeMounts: + - mountPath: /tmp/ansible-operator/runner + name: runner + readOnly: true + - name: operator + image: quay.io/kiali/kiali-operator:v1.4.2 + imagePullPolicy: "IfNotPresent" + volumeMounts: + - mountPath: /tmp/ansible-operator/runner + name: runner + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "kiali-operator" + volumes: + - name: runner + emptyDir: {} + clusterPermissions: + - rules: + - apiGroups: [""] + resources: + - configmaps + - endpoints + - events + - persistentvolumeclaims + - pods + - serviceaccounts + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: [""] + resources: + - namespaces + verbs: + - get + - list + - patch + - apiGroups: ["apps"] + resources: + - deployments + - replicasets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["monitoring.coreos.com"] + resources: + - servicemonitors + verbs: + - create + - get + - apiGroups: ["apps"] + resourceNames: + - kiali-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: ["kiali.io"] + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["apiextensions.k8s.io"] + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch + - apiGroups: ["extensions"] + resources: + - ingresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["route.openshift.io"] + resources: + - routes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["oauth.openshift.io"] + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["monitoring.kiali.io"] + resources: + - monitoringdashboards + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + # The permissions below are for Kiali itself; operator needs these so it can escalate when creating Kiali's roles + - apiGroups: [""] + resources: + - configmaps + - endpoints + - namespaces + - nodes + - pods + - pods/log + - replicationcontrollers + - services + verbs: + - get + - list + - watch + - apiGroups: ["extensions", "apps"] + resources: + - deployments + - replicasets + - statefulsets + verbs: + - get + - list + - watch + - apiGroups: ["autoscaling"] + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: ["batch"] + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: ["config.istio.io"] + resources: + - adapters + - apikeys + - bypasses + - authorizations + - checknothings + - circonuses + - cloudwatches + - deniers + - dogstatsds + - edges + - fluentds + - handlers + - instances + - kubernetesenvs + - kuberneteses + - listcheckers + - listentries + - logentries + - memquotas + - metrics + - noops + - opas + - prometheuses + - quotas + - quotaspecbindings + - quotaspecs + - rbacs + - redisquotas + - reportnothings + - rules + - signalfxs + - solarwindses + - stackdrivers + - statsds + - stdios + - templates + - tracespans + - zipkins + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["networking.istio.io"] + resources: + - destinationrules + - gateways + - serviceentries + - sidecars + - virtualservices + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["authentication.istio.io"] + resources: + - meshpolicies + - policies + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["rbac.istio.io"] + resources: + - clusterrbacconfigs + - rbacconfigs + - servicerolebindings + - serviceroles + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["authentication.maistra.io"] + resources: + - servicemeshpolicies + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["rbac.maistra.io"] + resources: + - servicemeshrbacconfigs + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["apps.openshift.io"] + resources: + - deploymentconfigs + verbs: + - get + - list + - watch + - apiGroups: ["project.openshift.io"] + resources: + - projects + verbs: + - get + - apiGroups: ["route.openshift.io"] + resources: + - routes + verbs: + - get + - apiGroups: ["monitoring.kiali.io"] + resources: + - monitoringdashboards + verbs: + - get + - list + serviceAccountName: kiali-operator +`, + } +} + +func buildContainer(tag, dockerfilePath, context string) { + cmd := exec.Command(builderCmd, "build", "-t", tag, "-f", dockerfilePath, context) + err := cmd.Run() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +} + +var _ = ginkgo.Describe("Launch bundle", func() { + namespace := "default" + correctConfigMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Annotations: map[string]string{ + bundle.MediatypeLabel: "registry+v1", + bundle.ManifestsLabel: "/manifests/", + bundle.MetadataLabel: "/metadata/", + bundle.PackageLabel: "kiali-operator.v1.4.2", + bundle.ChannelsLabel: "alpha,stable", + bundle.ChannelDefaultLabel: "stable", + configmap.ConfigMapImageAnnotationKey: "bundle-image:latest", + }, + }, + Data: getConfigMapDataSection(), + } + + ginkgo.Context("Deploy bundle job", func() { + ginkgo.It("should build required images", func() { + buildContainer("init-operator-manifest", imageDirectory+"Dockerfile.serve", "../../bin") + buildContainer("bundle-image", imageDirectory+"Dockerfile.bundle", imageDirectory) + }) + + ginkgo.It("should populate specified configmap", func() { + kubeclient, err := client.NewKubeClient("", logrus.StandardLogger()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + bundleDataConfigMap, job, err := configmap.LaunchBundleImage(kubeclient, "bundle-image:latest", "init-operator-manifest:latest", namespace) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // wait for job to complete + jobWatcher, err := kubeclient.BatchV1().Jobs(namespace).Watch(metav1.ListOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + done := make(chan struct{}) + quit := make(chan struct{}) + defer close(quit) + go func() { + for { + select { + case <-quit: + return + case evt, ok := <-jobWatcher.ResultChan(): + if !ok { + Logf("watch channel closed unexpectedly") + return + } + if evt.Type == watch.Modified { + job, ok := evt.Object.(*batchv1.Job) + if !ok { + continue + } + for _, condition := range job.Status.Conditions { + if condition.Type == batchv1.JobComplete && condition.Status == corev1.ConditionTrue { + done <- struct{}{} + } + } + } + case <-time.After(15 * time.Second): + done <- struct{}{} + } + } + }() + + Logf("Waiting on job to update status") + <-done + Logf("Job complete") + + bundleDataConfigMap, err = kubeclient.CoreV1().ConfigMaps(namespace).Get(bundleDataConfigMap.GetName(), metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + assert.EqualValues(ginkgo.GinkgoT(), correctConfigMap.Annotations, bundleDataConfigMap.Annotations) + assert.EqualValues(ginkgo.GinkgoT(), correctConfigMap.Data, bundleDataConfigMap.Data) + + // clean up, perhaps better handled elsewhere + err = kubeclient.CoreV1().ConfigMaps(namespace).Delete(bundleDataConfigMap.GetName(), &metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // job deletion does not clean up underlying pods (but using kubectl will do the clean up) + pods, err := kubeclient.CoreV1().Pods(namespace).List(metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", job.GetName())}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = kubeclient.CoreV1().Pods(namespace).Delete(pods.Items[0].GetName(), &metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + err = kubeclient.BatchV1().Jobs(namespace).Delete(job.GetName(), &metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) +}) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go new file mode 100644 index 000000000..8065dca0d --- /dev/null +++ b/test/e2e/e2e_suite_test.go @@ -0,0 +1,13 @@ +package e2e_test + +import ( + "testing" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +func TestE2e(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "E2e Suite") +} diff --git a/test/e2e/image-bundle/Dockerfile.bundle b/test/e2e/image-bundle/Dockerfile.bundle new file mode 100644 index 000000000..acac4dd91 --- /dev/null +++ b/test/e2e/image-bundle/Dockerfile.bundle @@ -0,0 +1,5 @@ +# docker build -t bundle-image . +FROM fedora + +COPY manifests /manifests +COPY metadata /metadata diff --git a/test/e2e/image-bundle/Dockerfile.serve b/test/e2e/image-bundle/Dockerfile.serve new file mode 100644 index 000000000..ceb165ea7 --- /dev/null +++ b/test/e2e/image-bundle/Dockerfile.serve @@ -0,0 +1,4 @@ +# docker build -t init-operator-manifest . +FROM busybox + +COPY opm / diff --git a/test/e2e/image-bundle/manifests/kiali.crd.yaml b/test/e2e/image-bundle/manifests/kiali.crd.yaml new file mode 100644 index 000000000..d20f087d0 --- /dev/null +++ b/test/e2e/image-bundle/manifests/kiali.crd.yaml @@ -0,0 +1,21 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: kialis.kiali.io + labels: + app: kiali-operator +spec: + group: kiali.io + names: + kind: Kiali + listKind: KialiList + plural: kialis + singular: kiali + scope: Namespaced + subresources: + status: {} + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/test/e2e/image-bundle/manifests/kiali.monitoringdashboards.crd.yaml b/test/e2e/image-bundle/manifests/kiali.monitoringdashboards.crd.yaml new file mode 100644 index 000000000..9607638e2 --- /dev/null +++ b/test/e2e/image-bundle/manifests/kiali.monitoringdashboards.crd.yaml @@ -0,0 +1,15 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: monitoringdashboards.monitoring.kiali.io + labels: + app: kiali +spec: + group: monitoring.kiali.io + names: + kind: MonitoringDashboard + listKind: MonitoringDashboardList + plural: monitoringdashboards + singular: monitoringdashboard + scope: Namespaced + version: v1alpha1 diff --git a/test/e2e/image-bundle/manifests/kiali.package.yaml b/test/e2e/image-bundle/manifests/kiali.package.yaml new file mode 100644 index 000000000..f7eaa9cf2 --- /dev/null +++ b/test/e2e/image-bundle/manifests/kiali.package.yaml @@ -0,0 +1,7 @@ +packageName: kiali +channels: +- name: alpha + currentCSV: kiali-operator.v1.4.2 +- name: stable + currentCSV: kiali-operator.v1.4.2 +defaultChannel: stable diff --git a/test/e2e/image-bundle/manifests/kiali.v1.4.2.clusterserviceversion.yaml b/test/e2e/image-bundle/manifests/kiali.v1.4.2.clusterserviceversion.yaml new file mode 100644 index 000000000..8c0d1b074 --- /dev/null +++ b/test/e2e/image-bundle/manifests/kiali.v1.4.2.clusterserviceversion.yaml @@ -0,0 +1,555 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: kiali-operator.v1.4.2 + namespace: placeholder + annotations: + categories: Monitoring,Logging & Tracing + certified: "false" + containerImage: quay.io/kiali/kiali-operator:v1.4.2 + capabilities: Basic Install + support: Kiali + description: "Kiali project provides answers to the questions: What microservices are part of my Istio service mesh and how are they connected?" + repository: https://github.com/kiali/kiali + createdAt: 2019-09-12T00:00:00Z + alm-examples: |- + [ + { + "apiVersion": "kiali.io/v1alpha1", + "kind": "Kiali", + "metadata": { + "name": "kiali" + }, + "spec": { + "installation_tag": "My Kiali", + "istio_namespace": "istio-system", + "deployment": { + "namespace": "istio-system", + "verbose_mode": "4", + "view_only_mode": false + }, + "external_services": { + "grafana": { + "url": "" + }, + "prometheus": { + "url": "" + }, + "tracing": { + "url": "" + } + }, + "server": { + "web_root": "/mykiali" + } + } + }, + { + "apiVersion": "monitoring.kiali.io/v1alpha1", + "kind": "MonitoringDashboard", + "metadata": { + "name": "myappdashboard" + }, + "spec": { + "title": "My App Dashboard", + "items": [ + { + "chart": { + "name": "My App Processing Duration", + "unit": "seconds", + "spans": 6, + "metricName": "my_app_duration_seconds", + "dataType": "histogram", + "aggregations": [ + { + "label": "id", + "displayName": "ID" + } + ] + } + } + ] + } + } + ] +spec: + version: 1.4.2 + maturity: stable + replaces: kiali-operator.v1.3.1 + displayName: Kiali Operator + description: |- + A Microservice Architecture breaks up the monolith into many smaller pieces that are composed together. Patterns to secure the communication between services like fault tolerance (via timeout, retry, circuit breaking, etc.) have come up as well as distributed tracing to be able to see where calls are going. + + A service mesh can now provide these services on a platform level and frees the application writers from those tasks. Routing decisions are done at the mesh level. + + Kiali works with Istio, in OpenShift or Kubernetes, to visualize the service mesh topology, to provide visibility into features like circuit breakers, request rates and more. It offers insights about the mesh components at different levels, from abstract Applications to Services and Workloads. + + See [https://www.kiali.io](https://www.kiali.io) to read more. + + ### Prerequisites + + Today Kiali works with Istio. So before you install Kiali, you must have already installed Istio. Note that Istio can come pre-bundled with Kiali (specifically if you installed the Istio demo helm profile or if you installed Istio with the helm option '--set kiali.enabled=true'). If you already have the pre-bundled Kiali in your Istio environment and you want to install Kiali via the Kiali Operator, uninstall the pre-bundled Kiali first. You can do this via this command: + + kubectl delete --ignore-not-found=true all,secrets,sa,templates,configmaps,deployments,clusterroles,clusterrolebindings,ingresses,customresourcedefinitions --selector="app=kiali" -n istio-system + + When you install Kiali in a non-OpenShift Kubernetes environment, the authentication strategy will default to `login`. When using the authentication strategy of `login`, you are required to create a Kubernetes Secret with a `username` and `passphrase` that you want users to provide in order to successfully log into Kiali. Here is an example command you can execute to create such a secret (with a username of `admin` and a passphrase of `admin`): + + kubectl create secret generic kiali -n istio-system --from-literal "username=admin" --from-literal "passphrase=admin" + + ### Kiali Custom Resource Configuration Settings + + For quick descriptions of all the settings you can configure in the Kiali Custom Resource (CR), see the file [kiali_cr.yaml](https://github.com/kiali/kiali/blob/v1.4.2/operator/deploy/kiali/kiali_cr.yaml) + + ### Accessing the UI + + By default, the Kiali operator exposes the Kiali UI as a Route on OpenShift or Ingress on Kubernetes. + On OpenShift, the default root context path is '/' and on Kubernetes it is '/kiali' though you can change this by configuring the 'web_root' setting in the Kiali CR. + icon: + - base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIyLjAuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMjgwIDEyODAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyODAgMTI4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiMwMTMxNDQ7fQoJLnN0MXtmaWxsOiMwMDkzREQ7fQo8L3N0eWxlPgo8Zz4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04MTAuOSwxODAuOWMtMjUzLjYsMC00NTkuMSwyMDUuNS00NTkuMSw0NTkuMXMyMDUuNSw0NTkuMSw0NTkuMSw0NTkuMVMxMjcwLDg5My42LDEyNzAsNjQwCgkJUzEwNjQuNSwxODAuOSw4MTAuOSwxODAuOXogTTgxMC45LDEwMjkuMmMtMjE1LDAtMzg5LjItMTc0LjMtMzg5LjItMzg5LjJjMC0yMTUsMTc0LjMtMzg5LjIsMzg5LjItMzg5LjJTMTIwMC4xLDQyNSwxMjAwLjEsNjQwCgkJUzEwMjUuOSwxMDI5LjIsODEwLjksMTAyOS4yeiIvPgoJPHBhdGggY2xhc3M9InN0MSIgZD0iTTY1My4zLDI4NGMtMTM2LjQsNjAuNS0yMzEuNiwxOTcuMS0yMzEuNiwzNTZjMCwxNTguOCw5NS4yLDI5NS41LDIzMS42LDM1NmM5OC40LTg3LjEsMTYwLjQtMjE0LjMsMTYwLjQtMzU2CgkJQzgxMy43LDQ5OC4zLDc1MS42LDM3MS4xLDY1My4zLDI4NHoiLz4KCTxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0zNTEuOCw2NDBjMC0xMDkuOCwzOC42LTIxMC41LDEwMi44LTI4OS41Yy0zOS42LTE4LjItODMuNi0yOC4zLTEzMC0yOC4zQzE1MC45LDMyMi4yLDEwLDQ2NC41LDEwLDY0MAoJCXMxNDAuOSwzMTcuOCwzMTQuNiwzMTcuOGM0Ni4zLDAsOTAuNC0xMC4xLDEzMC0yOC4zQzM5MC4zLDg1MC41LDM1MS44LDc0OS44LDM1MS44LDY0MHoiLz4KPC9nPgo8L3N2Zz4K + mediatype: image/svg+xml + keywords: ['service-mesh', 'observability', 'monitoring', 'maistra', 'istio'] + maintainers: + - name: Kiali Developers Google Group + email: kiali-dev@googlegroups.com + provider: + name: Kiali + labels: + name: kiali-operator + selector: + matchLabels: + name: kiali-operator + links: + - name: Getting Started Guide + url: https://www.kiali.io/documentation/getting-started/ + - name: Features + url: https://www.kiali.io/documentation/features + - name: Documentation Home + url: https://www.kiali.io/documentation + - name: Blogs and Articles + url: https://medium.com/kialiproject + - name: Server Source Code + url: https://github.com/kiali/kiali + - name: UI Source Code + url: https://github.com/kiali/kiali-ui + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + customresourcedefinitions: + owned: + - name: kialis.kiali.io + group: kiali.io + description: A configuration file for a Kiali installation. + displayName: Kiali + kind: Kiali + version: v1alpha1 + resources: + - kind: Deployment + version: apps/v1 + - kind: Pod + version: v1 + - kind: Service + version: v1 + - kind: ConfigMap + version: v1 + - kind: OAuthClient + version: oauth.openshift.io/v1 + - kind: Route + version: route.openshift.io/v1 + - kind: Ingress + version: extensions/v1beta1 + specDescriptors: + - displayName: Authentication Strategy + description: "Determines how a user is to log into Kiali. Choose 'login' to use a username and passphrase as defined in a Secret. Choose 'anonymous' to allow full access to Kiali without requiring credentials (use this at your own risk). Choose 'openshift' if on OpenShift to use the OpenShift OAuth login which controls access based on the individual's OpenShift RBAC roles. Default: openshift (when deployed in OpenShift); login (when deployed in Kubernetes)" + path: auth.strategy + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: Kiali Namespace + description: "The namespace where Kiali and its associated resources will be created. Default: istio-system" + path: deployment.namespace + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: Secret Name + description: "If Kiali is configured with auth.strategy 'login', an admin must create a Secret with credentials ('username' and 'passphrase') which will be used to authenticate users logging into Kiali. This setting defines the name of that secret. Default: kiali" + path: deployment.secret_name + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Secret' + - displayName: Verbose Mode + description: "Determines the priority levels of log messages Kiali will output. Typical values are '3' for INFO and higher priority messages, '4' for DEBUG and higher priority messages (this makes the logs more noisy). Default: 3" + path: deployment.verbose_mode + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - displayName: View Only Mode + description: "When true, Kiali will be in 'view only' mode, allowing the user to view and retrieve management and monitoring data for the service mesh, but not allow the user to modify the service mesh. Default: false" + path: deployment.view_only_mode + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:booleanSwitch' + - displayName: Web Root + description: "Defines the root context path for the Kiali console, API endpoints and readiness/liveness probes. Default: / (when deployed on OpenShift; /kiali (when deployed on Kubernetes)" + path: server.web_root + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + - name: monitoringdashboards.monitoring.kiali.io + group: monitoring.kiali.io + description: A configuration file for defining an individual metric dashboard. + displayName: Monitoring Dashboard + kind: MonitoringDashboard + version: v1alpha1 + resources: [] + specDescriptors: + - displayName: Title + description: "The title of the dashboard." + path: title + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:label' + apiservicedefinitions: {} + install: + strategy: deployment + spec: + deployments: + - name: kiali-operator + spec: + replicas: 1 + selector: + matchLabels: + app: kiali-operator + template: + metadata: + name: kiali-operator + labels: + app: kiali-operator + version: v1.4.2 + spec: + serviceAccountName: kiali-operator + containers: + - name: ansible + command: + - /usr/local/bin/ao-logs + - /tmp/ansible-operator/runner + - stdout + image: quay.io/kiali/kiali-operator:v1.4.2 + imagePullPolicy: "IfNotPresent" + volumeMounts: + - mountPath: /tmp/ansible-operator/runner + name: runner + readOnly: true + - name: operator + image: quay.io/kiali/kiali-operator:v1.4.2 + imagePullPolicy: "IfNotPresent" + volumeMounts: + - mountPath: /tmp/ansible-operator/runner + name: runner + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "kiali-operator" + volumes: + - name: runner + emptyDir: {} + clusterPermissions: + - rules: + - apiGroups: [""] + resources: + - configmaps + - endpoints + - events + - persistentvolumeclaims + - pods + - serviceaccounts + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: [""] + resources: + - namespaces + verbs: + - get + - list + - patch + - apiGroups: ["apps"] + resources: + - deployments + - replicasets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["monitoring.coreos.com"] + resources: + - servicemonitors + verbs: + - create + - get + - apiGroups: ["apps"] + resourceNames: + - kiali-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: ["kiali.io"] + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["apiextensions.k8s.io"] + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch + - apiGroups: ["extensions"] + resources: + - ingresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["route.openshift.io"] + resources: + - routes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["oauth.openshift.io"] + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: ["monitoring.kiali.io"] + resources: + - monitoringdashboards + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + # The permissions below are for Kiali itself; operator needs these so it can escalate when creating Kiali's roles + - apiGroups: [""] + resources: + - configmaps + - endpoints + - namespaces + - nodes + - pods + - pods/log + - replicationcontrollers + - services + verbs: + - get + - list + - watch + - apiGroups: ["extensions", "apps"] + resources: + - deployments + - replicasets + - statefulsets + verbs: + - get + - list + - watch + - apiGroups: ["autoscaling"] + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: ["batch"] + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: ["config.istio.io"] + resources: + - adapters + - apikeys + - bypasses + - authorizations + - checknothings + - circonuses + - cloudwatches + - deniers + - dogstatsds + - edges + - fluentds + - handlers + - instances + - kubernetesenvs + - kuberneteses + - listcheckers + - listentries + - logentries + - memquotas + - metrics + - noops + - opas + - prometheuses + - quotas + - quotaspecbindings + - quotaspecs + - rbacs + - redisquotas + - reportnothings + - rules + - signalfxs + - solarwindses + - stackdrivers + - statsds + - stdios + - templates + - tracespans + - zipkins + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["networking.istio.io"] + resources: + - destinationrules + - gateways + - serviceentries + - sidecars + - virtualservices + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["authentication.istio.io"] + resources: + - meshpolicies + - policies + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["rbac.istio.io"] + resources: + - clusterrbacconfigs + - rbacconfigs + - servicerolebindings + - serviceroles + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["authentication.maistra.io"] + resources: + - servicemeshpolicies + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["rbac.maistra.io"] + resources: + - servicemeshrbacconfigs + verbs: + - create + - delete + - get + - list + - patch + - watch + - apiGroups: ["apps.openshift.io"] + resources: + - deploymentconfigs + verbs: + - get + - list + - watch + - apiGroups: ["project.openshift.io"] + resources: + - projects + verbs: + - get + - apiGroups: ["route.openshift.io"] + resources: + - routes + verbs: + - get + - apiGroups: ["monitoring.kiali.io"] + resources: + - monitoringdashboards + verbs: + - get + - list + serviceAccountName: kiali-operator diff --git a/test/e2e/image-bundle/metadata/annotations.yaml b/test/e2e/image-bundle/metadata/annotations.yaml new file mode 100644 index 000000000..f8005f08a --- /dev/null +++ b/test/e2e/image-bundle/metadata/annotations.yaml @@ -0,0 +1,7 @@ +annotations: + operators.operatorframework.io.bundle.mediatype.v1: "registry+v1" + operators.operatorframework.io.bundle.manifests.v1: "/manifests/" + operators.operatorframework.io.bundle.metadata.v1: "/metadata/" + operators.operatorframework.io.bundle.package.v1: "kiali-operator.v1.4.2" + operators.operatorframework.io.bundle.channels.v1: "alpha,stable" + operators.operatorframework.io.bundle.channel.default.v1: "stable"