From d6d4527093b17b76b41a0b7bb4560672e7f2331d Mon Sep 17 00:00:00 2001 From: Diogo Recharte Date: Mon, 29 Jan 2024 10:31:53 +0000 Subject: [PATCH 1/5] EVEREST-633 Add multi-namespace support (#271) * EVEREST-633 force everest install to percona-everest namespace * EVEREST-633 remove deprecated name flag * EVEREST-633 add multi-namespace support * EVEREST-633 remove operator channel flags * EVEREST-633 update backend and operator go mods * EVEREST-633 shut up linter, we know this code is dead --- cli-tests/Makefile | 1 + cli-tests/tests/flow/all-operators.spec.ts | 50 +-- cli-tests/tests/flow/mongodb-operator.spec.ts | 11 +- cli-tests/tests/flow/pg-operator.spec.ts | 13 +- cli-tests/tests/flow/pxc-operator.spec.ts | 11 +- commands/delete.go | 2 +- commands/install.go | 19 +- commands/uninstall.go | 2 - commands/upgrade.go | 4 +- pkg/install/install.go | 298 +++++++++++++----- pkg/kubernetes/client/client.go | 75 ++--- pkg/kubernetes/client/kubeclient_interface.go | 7 +- .../client/mock_kube_client_connector.go | 79 ++++- pkg/kubernetes/kubernetes.go | 182 +++++++++-- pkg/kubernetes/olm_operator_test.go | 8 +- pkg/monitoring/enable.go | 9 +- pkg/uninstall/uninstall.go | 39 +-- pkg/upgrade/upgrade.go | 87 +++-- 18 files changed, 578 insertions(+), 319 deletions(-) diff --git a/cli-tests/Makefile b/cli-tests/Makefile index 59935e6d..ccbb94b4 100644 --- a/cli-tests/Makefile +++ b/cli-tests/Makefile @@ -4,6 +4,7 @@ init: ## Install dependencies install-operators: ## Install operators to k8s ../bin/everest install \ + --namespace percona-everest-operators \ --skip-wizard \ test-cli: ## Run all tests diff --git a/cli-tests/tests/flow/all-operators.spec.ts b/cli-tests/tests/flow/all-operators.spec.ts index b3be40ad..43753b0f 100644 --- a/cli-tests/tests/flow/all-operators.spec.ts +++ b/cli-tests/tests/flow/all-operators.spec.ts @@ -27,13 +27,18 @@ test.describe('Everest CLI install', async () => { test('install all operators', async ({ page, cli, request }) => { const verifyClusterResources = async () => { await test.step('verify installed operators in k8s', async () => { + const perconaEverestPodsOut = await cli.exec('kubectl get pods --namespace=percona-everest'); + + await perconaEverestPodsOut.outContainsNormalizedMany([ + 'everest-operator-controller-manager', + ]); + const out = await cli.exec('kubectl get pods --namespace=percona-everest-all'); await out.outContainsNormalizedMany([ 'percona-xtradb-cluster-operator', 'percona-server-mongodb-operator', 'percona-postgresql-operator', - 'everest-operator-controller-manager', ]); }); }; @@ -41,7 +46,7 @@ test.describe('Everest CLI install', async () => { await test.step('run everest install command', async () => { const out = await cli.everestExecSkipWizard( - `install --name=${clusterName} --namespace=percona-everest-all`, + `install --namespace=percona-everest-all`, ); await out.assertSuccess(); @@ -64,7 +69,7 @@ test.describe('Everest CLI install', async () => { await out.outContains( 'name: DISABLE_TELEMETRY\n value: "false"', ); - out = await cli.exec(`kubectl patch service everest --patch '{"spec": {"type": "LoadBalancer"}}' --namespace=percona-everest-all`) + out = await cli.exec(`kubectl patch service everest --patch '{"spec": {"type": "LoadBalancer"}}' --namespace=percona-everest`) await out.assertSuccess(); @@ -81,42 +86,20 @@ test.describe('Everest CLI install', async () => { 'name: DISABLE_TELEMETRY\n value: "true"', ); // check that the spec.type is not overrided - out = await cli.exec('kubectl get service/everest --namespace=percona-everest-all -o yaml'); + out = await cli.exec('kubectl get service/everest --namespace=percona-everest -o yaml'); await out.outContains( 'type: LoadBalancer', ); }); - await test.step('run everest install command using a different namespace', async () => { - const install = await cli.everestExecSkipWizard( - `install --namespace=different-everest`, - ); - - await install.assertSuccess(); - - let out = await cli.exec('kubectl get clusterrolebinding everest-admin-cluster-role-binding -o yaml'); - await out.assertSuccess(); - - await out.outContainsNormalizedMany([ - 'namespace: percona-everest-all', - 'namespace: different-everest', - ]); - await cli.everestExec('uninstall --namespace=different-everest --assume-yes'); - // Check that uninstall will fail because there's no everest deployment - out = await cli.everestExec('uninstall --namespace=different-everest --assume-yes'); - await out.outErrContainsNormalizedMany([ - 'no Everest deployment in different-everest namespace', - ]); - - }); await test.step('uninstall Everest', async () => { let out = await cli.everestExec( - `uninstall --namespace=percona-everest-all --assume-yes`, + `uninstall --assume-yes`, ); await out.assertSuccess(); // check that the deployment does not exist - out = await cli.exec('kubectl get deploy percona-everest -n percona-everest-all'); + out = await cli.exec('kubectl get deploy percona-everest -n percona-everest'); await out.outErrContainsNormalizedMany([ 'Error from server (NotFound): deployments.apps "percona-everest" not found', @@ -124,16 +107,5 @@ test.describe('Everest CLI install', async () => { }); - await test.step('uninstall Everest non existent namespace', async () => { - let out = await cli.everestExec( - `uninstall --namespace=not-exist --assume-yes`, - ); - - await out.outErrContainsNormalizedMany([ - 'namespace not-exist is not found', - ]); - - }); - }); }); diff --git a/cli-tests/tests/flow/mongodb-operator.spec.ts b/cli-tests/tests/flow/mongodb-operator.spec.ts index 53108fa2..01a55009 100644 --- a/cli-tests/tests/flow/mongodb-operator.spec.ts +++ b/cli-tests/tests/flow/mongodb-operator.spec.ts @@ -27,11 +27,16 @@ test.describe('Everest CLI install', async () => { test('install only mongodb-operator', async ({ page, cli, request }) => { const verifyClusterResources = async () => { await test.step('verify installed operators in k8s', async () => { - const out = await cli.exec('kubectl get pods --namespace=percona-everest'); + const perconaEverestPodsOut = await cli.exec('kubectl get pods --namespace=percona-everest'); + + await perconaEverestPodsOut.outContainsNormalizedMany([ + 'everest-operator-controller-manager', + ]); + + const out = await cli.exec('kubectl get pods --namespace=percona-everest-operators'); await out.outContainsNormalizedMany([ 'percona-server-mongodb-operator', - 'everest-operator-controller-manager', ]); await out.outNotContains([ @@ -44,7 +49,7 @@ test.describe('Everest CLI install', async () => { await test.step('run everest install command', async () => { const out = await cli.everestExecSkipWizard( - `install --operator.mongodb=true --operator.postgresql=false --operator.xtradb-cluster=false --name=${clusterName}`, + `install --operator.mongodb=true --operator.postgresql=false --operator.xtradb-cluster=false --namespace=percona-everest-operators`, ); await out.assertSuccess(); diff --git a/cli-tests/tests/flow/pg-operator.spec.ts b/cli-tests/tests/flow/pg-operator.spec.ts index 6be0f71d..72c13ca5 100644 --- a/cli-tests/tests/flow/pg-operator.spec.ts +++ b/cli-tests/tests/flow/pg-operator.spec.ts @@ -27,11 +27,16 @@ test.describe('Everest CLI install', async () => { test('install only postgresql-operator', async ({ page, cli, request }) => { const verifyClusterResources = async () => { await test.step('verify installed operators in k8s', async () => { - const out = await cli.exec('kubectl get pods --namespace=percona-everest'); + const perconaEverestPodsOut = await cli.exec('kubectl get pods --namespace=percona-everest'); + + await perconaEverestPodsOut.outContainsNormalizedMany([ + 'everest-operator-controller-manager', + ]); + + const out = await cli.exec('kubectl get pods --namespace=percona-everest-operators'); await out.outContainsNormalizedMany([ 'percona-postgresql-operator', - 'everest-operator-controller-manager', ]); await out.outNotContains([ @@ -43,7 +48,7 @@ test.describe('Everest CLI install', async () => { await test.step('run everest install command', async () => { const out = await cli.everestExecSkipWizard( - `install --operator.mongodb=false --operator.postgresql=true --operator.xtradb-cluster=false --name=${clusterName}`, + `install --operator.mongodb=false --operator.postgresql=true --operator.xtradb-cluster=false --namespace=percona-everest-operators`, ); await out.assertSuccess(); @@ -63,7 +68,7 @@ test.describe('Everest CLI install', async () => { await operator.assertSuccess(); const out = await cli.everestExecSkipWizard( - `install --operator.mongodb=false --operator.postgresql=true --operator.xtradb-cluster=true --name=${clusterName}`, + `install --operator.mongodb=false --operator.postgresql=true --operator.xtradb-cluster=true --namespace=percona-everest-operators`, ); const restartedOperator = await cli.exec(`kubectl -n percona-everest get po | grep everest|awk {'print $1'}`); await restartedOperator.assertSuccess(); diff --git a/cli-tests/tests/flow/pxc-operator.spec.ts b/cli-tests/tests/flow/pxc-operator.spec.ts index 142bcd8b..580502bf 100644 --- a/cli-tests/tests/flow/pxc-operator.spec.ts +++ b/cli-tests/tests/flow/pxc-operator.spec.ts @@ -27,11 +27,16 @@ test.describe('Everest CLI install', async () => { test('install only xtradb-cluster-operator', async ({ page, cli, request }) => { const verifyClusterResources = async () => { await test.step('verify installed operators in k8s', async () => { - const out = await cli.exec('kubectl get pods --namespace=percona-everest'); + const perconaEverestPodsOut = await cli.exec('kubectl get pods --namespace=percona-everest'); + + await perconaEverestPodsOut.outContainsNormalizedMany([ + 'everest-operator-controller-manager', + ]); + + const out = await cli.exec('kubectl get pods --namespace=percona-everest-operators'); await out.outContainsNormalizedMany([ 'percona-xtradb-cluster-operator', - 'everest-operator-controller-manager', ]); await out.outNotContains([ @@ -44,7 +49,7 @@ test.describe('Everest CLI install', async () => { await test.step('run everest install command', async () => { const out = await cli.everestExecSkipWizard( - `install --operator.mongodb=false --operator.postgresql=false --operator.xtradb-cluster=true --name=${clusterName}`, + `install --operator.mongodb=false --operator.postgresql=false --operator.xtradb-cluster=true --namespace=percona-everest-operators`, ); await out.assertSuccess(); diff --git a/commands/delete.go b/commands/delete.go index 2bb599e6..ae16f198 100644 --- a/commands/delete.go +++ b/commands/delete.go @@ -23,7 +23,7 @@ import ( "github.com/percona/percona-everest-cli/commands/delete" ) -func newDeleteCmd(l *zap.SugaredLogger) *cobra.Command { +func newDeleteCmd(l *zap.SugaredLogger) *cobra.Command { //nolint:deadcode,unused cmd := &cobra.Command{ Use: "delete", } diff --git a/commands/install.go b/commands/install.go index 24a5c223..cdc59bba 100644 --- a/commands/install.go +++ b/commands/install.go @@ -29,7 +29,8 @@ import ( func newInstallCmd(l *zap.SugaredLogger) *cobra.Command { cmd := &cobra.Command{ - Use: "install", + Use: "install", + Example: "everestctl install --namespace dev --namespace staging --namespace prod --operator.mongodb=true --operator.postgresql=true --operator.xtradb-cluster=true --skip-wizard", Run: func(cmd *cobra.Command, args []string) { initInstallViperFlags(cmd) c := &install.Config{} @@ -57,19 +58,12 @@ func newInstallCmd(l *zap.SugaredLogger) *cobra.Command { func initInstallFlags(cmd *cobra.Command) { cmd.Flags().StringP("kubeconfig", "k", "~/.kube/config", "Path to a kubeconfig") - cmd.Flags().StringP("name", "n", "", "Kubernetes cluster name") - cmd.Flags().String("namespace", "percona-everest", "Namespace into which Percona Everest components are deployed to") + cmd.Flags().StringArray("namespace", []string{}, "Namespaces list Percona Everest can manage") cmd.Flags().Bool("skip-wizard", false, "Skip installation wizard") cmd.Flags().Bool("operator.mongodb", true, "Install MongoDB operator") cmd.Flags().Bool("operator.postgresql", true, "Install PostgreSQL operator") cmd.Flags().Bool("operator.xtradb-cluster", true, "Install XtraDB Cluster operator") - - cmd.Flags().String("channel.everest", "stable-v0", "Channel for Everest operator") - cmd.Flags().String("channel.victoria-metrics", "stable-v0", "Channel for VictoriaMetrics operator") - cmd.Flags().String("channel.xtradb-cluster", "stable-v1", "Channel for XtraDB Cluster operator") - cmd.Flags().String("channel.mongodb", "stable-v1", "Channel for MongoDB operator") - cmd.Flags().String("channel.postgresql", "fast-v2", "Channel for PostgreSQL operator") } func initInstallViperFlags(cmd *cobra.Command) { @@ -77,16 +71,9 @@ func initInstallViperFlags(cmd *cobra.Command) { viper.BindEnv("kubeconfig") //nolint:errcheck,gosec viper.BindPFlag("kubeconfig", cmd.Flags().Lookup("kubeconfig")) //nolint:errcheck,gosec - viper.BindPFlag("name", cmd.Flags().Lookup("name")) //nolint:errcheck,gosec viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace")) //nolint:errcheck,gosec viper.BindPFlag("operator.mongodb", cmd.Flags().Lookup("operator.mongodb")) //nolint:errcheck,gosec viper.BindPFlag("operator.postgresql", cmd.Flags().Lookup("operator.postgresql")) //nolint:errcheck,gosec viper.BindPFlag("operator.xtradb-cluster", cmd.Flags().Lookup("operator.xtradb-cluster")) //nolint:errcheck,gosec - - viper.BindPFlag("channel.victoria-metrics", cmd.Flags().Lookup("channel.victoria-metrics")) //nolint:errcheck,gosec - viper.BindPFlag("channel.xtradb-cluster", cmd.Flags().Lookup("channel.xtradb-cluster")) //nolint:errcheck,gosec - viper.BindPFlag("channel.mongodb", cmd.Flags().Lookup("channel.mongodb")) //nolint:errcheck,gosec - viper.BindPFlag("channel.postgresql", cmd.Flags().Lookup("channel.postgresql")) //nolint:errcheck,gosec - viper.BindPFlag("channel.everest", cmd.Flags().Lookup("channel.everest")) //nolint:errcheck,gosec } diff --git a/commands/uninstall.go b/commands/uninstall.go index 8523cabb..f6b389d9 100644 --- a/commands/uninstall.go +++ b/commands/uninstall.go @@ -58,7 +58,6 @@ func newUninstallCmd(l *zap.SugaredLogger) *cobra.Command { func initUninstallFlags(cmd *cobra.Command) { cmd.Flags().StringP("kubeconfig", "k", "~/.kube/config", "Path to a kubeconfig") - cmd.Flags().String("namespace", "percona-everest", "Namespace into which Percona Everest components are deployed to") cmd.Flags().BoolP("assume-yes", "y", false, "Assume yes to all questions") cmd.Flags().BoolP("force", "f", false, "Force removal in case there are database clusters running") } @@ -66,7 +65,6 @@ func initUninstallFlags(cmd *cobra.Command) { func initUninstallViperFlags(cmd *cobra.Command) { viper.BindEnv("kubeconfig") //nolint:errcheck,gosec viper.BindPFlag("kubeconfig", cmd.Flags().Lookup("kubeconfig")) //nolint:errcheck,gosec - viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace")) //nolint:errcheck,gosec viper.BindPFlag("assume-yes", cmd.Flags().Lookup("assume-yes")) //nolint:errcheck,gosec viper.BindPFlag("force", cmd.Flags().Lookup("force")) //nolint:errcheck,gosec } diff --git a/commands/upgrade.go b/commands/upgrade.go index 7118de5a..3c6f5f54 100644 --- a/commands/upgrade.go +++ b/commands/upgrade.go @@ -59,8 +59,7 @@ func newUpgradeCmd(l *zap.SugaredLogger) *cobra.Command { func initUpgradeFlags(cmd *cobra.Command) { cmd.Flags().StringP("kubeconfig", "k", "~/.kube/config", "Path to a kubeconfig") - cmd.Flags().StringP("name", "n", "", "Kubernetes cluster name") - cmd.Flags().String("namespace", "percona-everest", "Namespace into which Percona Everest components are deployed to") + cmd.Flags().StringArray("namespace", []string{}, "Namespaces list Percona Everest can manage") cmd.Flags().Bool("upgrade-olm", false, "Upgrade OLM distribution") cmd.Flags().Bool("skip-wizard", false, "Skip installation wizard") } @@ -68,7 +67,6 @@ func initUpgradeFlags(cmd *cobra.Command) { func initUpgradeViperFlags(cmd *cobra.Command) { viper.BindEnv("kubeconfig") //nolint:errcheck,gosec viper.BindPFlag("kubeconfig", cmd.Flags().Lookup("kubeconfig")) //nolint:errcheck,gosec - viper.BindPFlag("name", cmd.Flags().Lookup("name")) //nolint:errcheck,gosec viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace")) //nolint:errcheck,gosec viper.BindPFlag("upgrade-olm", cmd.Flags().Lookup("upgrade-olm")) //nolint:errcheck,gosec viper.BindPFlag("skip-wizard", cmd.Flags().Lookup("skip-wizard")) //nolint:errcheck,gosec diff --git a/pkg/install/install.go b/pkg/install/install.go index dd42c6ff..7fa051f3 100644 --- a/pkg/install/install.go +++ b/pkg/install/install.go @@ -22,11 +22,14 @@ import ( "errors" "fmt" "net/url" + "strings" "github.com/AlecAivazis/survey/v2" "github.com/operator-framework/api/pkg/operators/v1alpha1" "go.uber.org/zap" "golang.org/x/sync/errgroup" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "github.com/percona/percona-everest-cli/pkg/kubernetes" @@ -42,26 +45,45 @@ type Install struct { } const ( - everestOperatorName = "everest-operator" - pxcOperatorName = "percona-xtradb-cluster-operator" - psmdbOperatorName = "percona-server-mongodb-operator" - pgOperatorName = "percona-postgresql-operator" - operatorInstallThreads = 1 + everestBackendServiceName = "percona-everest-backend" + everestOperatorName = "everest-operator" + pxcOperatorName = "percona-xtradb-cluster-operator" + psmdbOperatorName = "percona-server-mongodb-operator" + pgOperatorName = "percona-postgresql-operator" + operatorInstallThreads = 1 + + everestServiceAccount = "everest-admin" + everestServiceAccountRole = "everest-admin-role" + everestServiceAccountRoleBinding = "everest-admin-role-binding" + everestServiceAccountClusterRoleBinding = "everest-admin-cluster-role-binding" + + everestOperatorChannel = "stable-v0" + pxcOperatorChannel = "stable-v1" + psmdbOperatorChannel = "stable-v1" + pgOperatorChannel = "fast-v2" + // VMOperatorChannel is the catalog channel for the VM Operator. + VMOperatorChannel = "stable-v0" + + // CatalogSourceNamespace is the namespace where the catalog source is installed. + CatalogSourceNamespace = "olm" + // CatalogSource is the name of the catalog source. + CatalogSource = "percona-everest-catalog" + // OperatorGroup is the name of the operator group. + OperatorGroup = "percona-operators-group" + // EverestNamespace is the namespace where everest is installed. + EverestNamespace = "percona-everest" ) type ( // Config stores configuration for the operators. Config struct { - // Name of the Kubernetes Cluster - Name string - // Namespace defines the namespace operators shall be installed to. - Namespace string + // Namespaces defines namespaces that everest can operate in. + Namespaces []string `mapstructure:"namespace"` // SkipWizard skips wizard during installation. SkipWizard bool `mapstructure:"skip-wizard"` // KubeconfigPath is a path to a kubeconfig KubeconfigPath string `mapstructure:"kubeconfig"` - Channel ChannelConfig Operator OperatorConfig } @@ -74,19 +96,6 @@ type ( // PXC stores if XtraDB Cluster shall be installed. PXC bool `mapstructure:"xtradb-cluster"` } - // ChannelConfig stores configuration for operator channels. - ChannelConfig struct { - // Everest stores channel for Everest. - Everest string - // PG stores channel for PostgreSQL. - PG string `mapstructure:"postgresql"` - // PSMDB stores channel for MongoDB. - PSMDB string `mapstructure:"mongodb"` - // PXC stores channel for xtradb cluster. - PXC string `mapstructure:"xtradb-cluster"` - // VictoriaMetrics stores channel for VictoriaMetrics. - VictoriaMetrics string `mapstructure:"victoria-metrics"` - } ) // NewInstall returns a new Install struct. @@ -110,17 +119,42 @@ func NewInstall(c Config, l *zap.SugaredLogger) (*Install, error) { } // Run runs the operators installation process. -func (o *Install) Run(ctx context.Context) error { +func (o *Install) Run(ctx context.Context) error { //nolint:cyclop if err := o.populateConfig(); err != nil { return err } - if err := o.provisionNamespace(); err != nil { + + if len(o.config.Namespaces) == 0 { + return errors.New("namespace list is empty. Specify at least one namespace using the --namespace flag") + } + for _, ns := range o.config.Namespaces { + if ns == EverestNamespace { + return fmt.Errorf("'%s' namespace is reserved for Everest internals. Please specify another namespace", ns) + } + } + + if err := o.createNamespace(EverestNamespace); err != nil { + return err + } + if err := o.provisionOLM(ctx); err != nil { + return err + } + o.l.Info("Creating operator group for the everest") + if err := o.kubeClient.CreateOperatorGroup(ctx, OperatorGroup, EverestNamespace, o.config.Namespaces); err != nil { return err } - if err := o.performProvisioning(ctx); err != nil { + if err := o.provisionAllNamespaces(ctx); err != nil { return err } - _, err := o.kubeClient.GetSecret(ctx, token.SecretName, o.config.Namespace) + if err := o.installEverest(ctx); err != nil { + return err + } + o.l.Info("Updating cluster role bindings for the everest-admin") + if err := o.kubeClient.UpdateClusterRoleBinding(ctx, everestServiceAccountClusterRoleBinding, o.config.Namespaces); err != nil { + return err + } + + _, err := o.kubeClient.GetSecret(ctx, token.SecretName, EverestNamespace) if err != nil && !k8serrors.IsNotFound(err) { return errors.Join(err, errors.New("could not get the everest token secret")) } @@ -142,18 +176,14 @@ func (o *Install) populateConfig() error { } } - if o.config.Name == "" { - o.config.Name = o.kubeClient.ClusterName() - } - return nil } -func (o *Install) performProvisioning(ctx context.Context) error { - if err := o.provisionAllOperators(ctx); err != nil { +func (o *Install) installEverest(ctx context.Context) error { + if err := o.installOperator(ctx, everestOperatorChannel, everestOperatorName, EverestNamespace)(); err != nil { return err } - d, err := o.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, o.config.Namespace) + d, err := o.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, EverestNamespace) var everestExists bool if err != nil && !k8serrors.IsNotFound(err) { return err @@ -163,15 +193,58 @@ func (o *Install) performProvisioning(ctx context.Context) error { } if !everestExists { - o.l.Info(fmt.Sprintf("Deploying Everest to %s", o.config.Namespace)) - err = o.kubeClient.InstallEverest(ctx, o.config.Namespace) + o.l.Info(fmt.Sprintf("Deploying Everest to %s", EverestNamespace)) + err = o.kubeClient.InstallEverest(ctx, EverestNamespace) if err != nil { return err } + } else { + o.l.Info("Restarting Everest") + if err := o.kubeClient.RestartEverest(ctx, everestOperatorName, EverestNamespace); err != nil { + return err + } + if err := o.kubeClient.RestartEverest(ctx, everestBackendServiceName, EverestNamespace); err != nil { + return err + } } return nil } +func (o *Install) provisionAllNamespaces(ctx context.Context) error { + for _, namespace := range o.config.Namespaces { + namespace := namespace + if err := o.createNamespace(namespace); err != nil { + return err + } + if err := o.kubeClient.CreateOperatorGroup(ctx, OperatorGroup, namespace, []string{}); err != nil { + return err + } + + o.l.Infof("Installing operators into %s namespace", namespace) + if err := o.provisionOperators(ctx, namespace); err != nil { + return err + } + o.l.Info("Creating role for the Everest service account") + err := o.kubeClient.CreateRole(namespace, everestServiceAccountRole, o.serviceAccountRolePolicyRules()) + if err != nil { + return errors.Join(err, errors.New("could not create role")) + } + + o.l.Info("Binding role to the Everest Service account") + err = o.kubeClient.CreateRoleBinding( + namespace, + everestServiceAccountRoleBinding, + everestServiceAccountRole, + everestServiceAccount, + ) + if err != nil { + return errors.Join(err, errors.New("could not create role binding")) + } + } + + return nil +} + // runWizard runs installation wizard. func (o *Install) runWizard() error { if err := o.runEverestWizard(); err != nil { @@ -182,11 +255,34 @@ func (o *Install) runWizard() error { } func (o *Install) runEverestWizard() error { + var namespaces string pNamespace := &survey.Input{ - Message: "Namespace to deploy Everest to", - Default: o.config.Namespace, + Message: "Namespaces managed by Everest (comma separated)", + Default: namespaces, + } + if err := survey.AskOne(pNamespace, &namespaces); err != nil { + return err + } + + nsList := strings.Split(namespaces, ",") + for _, ns := range nsList { + ns = strings.TrimSpace(ns) + if ns == "" { + continue + } + + if ns == EverestNamespace { + return fmt.Errorf("'%s' namespace is reserved for Everest internals. Please specify another namespace", ns) + } + + o.config.Namespaces = append(o.config.Namespaces, ns) + } + + if len(o.config.Namespaces) == 0 { + return errors.New("namespace list is empty. Specify at least one namespace") } - return survey.AskOne(pNamespace, &o.config.Namespace) + + return nil } func (o *Install) runInstallWizard() error { @@ -241,30 +337,15 @@ func (o *Install) runInstallWizard() error { return nil } -// provisionNamespace provisions a namespace for Everest. -func (o *Install) provisionNamespace() error { - o.l.Infof("Creating namespace %s", o.config.Namespace) - err := o.kubeClient.CreateNamespace(o.config.Namespace) +// createNamespace provisions a namespace for Everest. +func (o *Install) createNamespace(namespace string) error { + o.l.Infof("Creating namespace %s", namespace) + err := o.kubeClient.CreateNamespace(namespace) if err != nil { return errors.Join(err, errors.New("could not provision namespace")) } - o.l.Infof("Namespace %s has been created", o.config.Namespace) - return nil -} - -// provisionAllOperators provisions all configured operators to a k8s cluster. -func (o *Install) provisionAllOperators(ctx context.Context) error { - o.l.Info("Started provisioning the cluster") - - if err := o.provisionOLM(ctx); err != nil { - return err - } - - if err := o.provisionInstall(ctx); err != nil { - return err - } - + o.l.Infof("Namespace %s has been created", namespace) return nil } @@ -285,11 +366,7 @@ func (o *Install) provisionOLM(ctx context.Context) error { return nil } -func (o *Install) provisionInstall(ctx context.Context) error { - deploymentsBefore, err := o.kubeClient.ListEngineDeploymentNames(ctx, o.config.Namespace) - if err != nil { - return err - } +func (o *Install) provisionOperators(ctx context.Context, namespace string) error { g, gCtx := errgroup.WithContext(ctx) // We set the limit to 1 since operator installation // requires an update to the same installation plan which @@ -298,32 +375,22 @@ func (o *Install) provisionInstall(ctx context.Context) error { g.SetLimit(operatorInstallThreads) if o.config.Operator.PXC { - g.Go(o.installOperator(gCtx, o.config.Channel.PXC, pxcOperatorName)) + g.Go(o.installOperator(gCtx, pxcOperatorChannel, pxcOperatorName, namespace)) } if o.config.Operator.PSMDB { - g.Go(o.installOperator(gCtx, o.config.Channel.PSMDB, psmdbOperatorName)) + g.Go(o.installOperator(gCtx, psmdbOperatorChannel, psmdbOperatorName, namespace)) } if o.config.Operator.PG { - g.Go(o.installOperator(gCtx, o.config.Channel.PG, pgOperatorName)) + g.Go(o.installOperator(gCtx, pgOperatorChannel, pgOperatorName, namespace)) } if err := g.Wait(); err != nil { return err } - if err := o.installOperator(ctx, o.config.Channel.Everest, everestOperatorName)(); err != nil { - return err - } - deploymentsAfter, err := o.kubeClient.ListEngineDeploymentNames(ctx, o.config.Namespace) - if err != nil { - return err - } - if len(deploymentsBefore) != 0 && len(deploymentsBefore) != len(deploymentsAfter) { - return o.restartEverestOperatorPod(ctx) - } return nil } -func (o *Install) installOperator(ctx context.Context, channel, operatorName string) func() error { +func (o *Install) installOperator(ctx context.Context, channel, operatorName, namespace string) func() error { return func() error { // We check if the context has not been cancelled yet to return early if err := ctx.Err(); err != nil { @@ -334,14 +401,25 @@ func (o *Install) installOperator(ctx context.Context, channel, operatorName str o.l.Infof("Installing %s operator", operatorName) params := kubernetes.InstallOperatorRequest{ - Namespace: o.config.Namespace, + Namespace: namespace, Name: operatorName, - OperatorGroup: kubernetes.OperatorGroup, - CatalogSource: kubernetes.CatalogSource, - CatalogSourceNamespace: kubernetes.CatalogSourceNamespace, + OperatorGroup: OperatorGroup, + CatalogSource: CatalogSource, + CatalogSourceNamespace: CatalogSourceNamespace, Channel: channel, InstallPlanApproval: v1alpha1.ApprovalManual, } + if len(o.config.Namespaces) != 0 && operatorName == everestOperatorName { + params.TargetNamespaces = o.config.Namespaces + params.SubscriptionConfig = &v1alpha1.SubscriptionConfig{ + Env: []corev1.EnvVar{ + { + Name: kubernetes.EverestWatchNamespacesEnvVar, + Value: strings.Join(o.config.Namespaces, ","), + }, + }, + } + } if err := o.kubeClient.InstallOperator(ctx, params); err != nil { o.l.Errorf("failed installing %s operator", operatorName) @@ -353,13 +431,63 @@ func (o *Install) installOperator(ctx context.Context, channel, operatorName str } } +func (o *Install) serviceAccountRolePolicyRules() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"databaseclusters"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"databaseengines"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"databaseclusterrestores"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"databaseclusterbackups"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"backupstorages"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"everest.percona.com"}, + Resources: []string{"monitoringconfigs"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"operator.victoriametrics.com"}, + Resources: []string{"vmagents"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"*"}, + }, + } +} + func (o *Install) generateToken(ctx context.Context) (*token.ResetResponse, error) { o.l.Info("Creating token for Everest") r, err := token.NewReset( token.ResetConfig{ KubeconfigPath: o.config.KubeconfigPath, - Namespace: o.config.Namespace, + Namespace: EverestNamespace, }, o.l, ) @@ -374,7 +502,3 @@ func (o *Install) generateToken(ctx context.Context) (*token.ResetResponse, erro return res, nil } - -func (o *Install) restartEverestOperatorPod(ctx context.Context) error { - return o.kubeClient.RestartEverest(ctx, "everest-operator", o.config.Namespace) -} diff --git a/pkg/kubernetes/client/client.go b/pkg/kubernetes/client/client.go index 18f4d5b7..7a8ccb3e 100644 --- a/pkg/kubernetes/client/client.go +++ b/pkg/kubernetes/client/client.go @@ -41,6 +41,7 @@ import ( "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -84,8 +85,6 @@ const ( defaultAPIURIPath = "/api" defaultAPIsURIPath = "/apis" - - disableTelemetryEnvVar = "DISABLE_TELEMETRY" ) // Each level has 2 spaces for PrefixWriter. @@ -822,21 +821,6 @@ func (c *Client) setEverestServiceType(u *unstructured.Unstructured, namespace s } func (c *Client) updateClusterRoleBinding(u *unstructured.Unstructured, namespace string) error { - cl, err := c.kubeClient() - if err != nil { - return err - } - binding := &unstructured.Unstructured{} - binding.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "rbac.authorization.k8s.io", - Kind: "ClusterRoleBinding", - Version: "v1", - }) - err = cl.Get(context.Background(), types.NamespacedName{Name: "everest-admin-cluster-role-binding"}, binding) - if err != nil && !apierrors.IsNotFound(err) { - return err - } - sub, ok, err := unstructured.NestedFieldNoCopy(u.Object, "subjects") if err != nil { return err @@ -861,22 +845,6 @@ func (c *Client) updateClusterRoleBinding(u *unstructured.Unstructured, namespac return err } } - if binding.GetName() == "" { - return nil - } - - bindingSub, ok, err := unstructured.NestedFieldNoCopy(binding.Object, "subjects") - if err != nil { - return err - } - if !ok { - return nil - } - bindingSubjects, ok := bindingSub.([]interface{}) - if !ok { - return nil - } - subjects = append(subjects, bindingSubjects...) return unstructured.SetNestedSlice(u.Object, subjects, "subjects") } @@ -1178,7 +1146,7 @@ func (c *Client) GetOperatorGroup(ctx context.Context, namespace, name string) ( } // CreateOperatorGroup creates an operator group to be used as part of a subscription. -func (c *Client) CreateOperatorGroup(ctx context.Context, namespace, name string) (*v1.OperatorGroup, error) { +func (c *Client) CreateOperatorGroup(ctx context.Context, namespace, name string, targetNamespaces []string) (*v1.OperatorGroup, error) { operatorClient, err := versioned.NewForConfig(c.restConfig) if err != nil { return nil, errors.Join(err, errors.New("cannot create an operator client instance")) @@ -1193,7 +1161,7 @@ func (c *Client) CreateOperatorGroup(ctx context.Context, namespace, name string Namespace: namespace, }, Spec: v1.OperatorGroupSpec{ - TargetNamespaces: []string{namespace}, + TargetNamespaces: targetNamespaces, }, Status: v1.OperatorGroupStatus{ LastUpdated: &metav1.Time{ @@ -1205,6 +1173,25 @@ func (c *Client) CreateOperatorGroup(ctx context.Context, namespace, name string return operatorClient.OperatorsV1().OperatorGroups(namespace).Create(ctx, og, metav1.CreateOptions{}) } +// CreateSubscription creates an OLM subscription. +func (c *Client) CreateSubscription(ctx context.Context, namespace string, subscription *v1alpha1.Subscription) (*v1alpha1.Subscription, error) { + operatorClient, err := versioned.NewForConfig(c.restConfig) + if err != nil { + return nil, errors.Join(err, errors.New("cannot create an operator client instance")) + } + sub, err := operatorClient. + OperatorsV1alpha1(). + Subscriptions(namespace). + Create(ctx, subscription, metav1.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + return sub, nil + } + return sub, err + } + return sub, nil +} + // CreateSubscriptionForCatalog creates an OLM subscription. func (c *Client) CreateSubscriptionForCatalog(ctx context.Context, namespace, name, catalogNamespace, catalog, packageName, channel, startingCSV string, approval v1alpha1.Approval, @@ -1214,11 +1201,6 @@ func (c *Client) CreateSubscriptionForCatalog(ctx context.Context, namespace, na return nil, errors.Join(err, errors.New("cannot create an operator client instance")) } - disableTelemetry, ok := os.LookupEnv(disableTelemetryEnvVar) - if !ok || disableTelemetry != "true" { - disableTelemetry = "false" - } - subscription := &v1alpha1.Subscription{ TypeMeta: metav1.TypeMeta{ Kind: v1alpha1.SubscriptionKind, @@ -1235,14 +1217,6 @@ func (c *Client) CreateSubscriptionForCatalog(ctx context.Context, namespace, na Channel: channel, StartingCSV: startingCSV, InstallPlanApproval: approval, - Config: &v1alpha1.SubscriptionConfig{ - Env: []corev1.EnvVar{ - { - Name: disableTelemetryEnvVar, - Value: disableTelemetry, - }, - }, - }, }, } sub, err := operatorClient. @@ -1414,3 +1388,8 @@ func (c *Client) DeleteFile(fileBytes []byte) error { func (c *Client) GetService(ctx context.Context, namespace, name string) (*corev1.Service, error) { return c.clientset.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{}) } + +// GetClusterRoleBinding returns cluster role binding by given name. +func (c *Client) GetClusterRoleBinding(ctx context.Context, name string) (*rbacv1.ClusterRoleBinding, error) { + return c.clientset.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{}) +} diff --git a/pkg/kubernetes/client/kubeclient_interface.go b/pkg/kubernetes/client/kubeclient_interface.go index e5c55768..7eadd8fd 100644 --- a/pkg/kubernetes/client/kubeclient_interface.go +++ b/pkg/kubernetes/client/kubeclient_interface.go @@ -11,6 +11,7 @@ import ( everestv1alpha1 "github.com/percona/everest-operator/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -87,7 +88,9 @@ type KubeClientConnector interface { // GetOperatorGroup retrieves an operator group details by namespace and name. GetOperatorGroup(ctx context.Context, namespace, name string) (*v1.OperatorGroup, error) // CreateOperatorGroup creates an operator group to be used as part of a subscription. - CreateOperatorGroup(ctx context.Context, namespace, name string) (*v1.OperatorGroup, error) + CreateOperatorGroup(ctx context.Context, namespace, name string, targetNamespaces []string) (*v1.OperatorGroup, error) + // CreateSubscription creates an OLM subscription. + CreateSubscription(ctx context.Context, namespace string, subscription *v1alpha1.Subscription) (*v1alpha1.Subscription, error) // CreateSubscriptionForCatalog creates an OLM subscription. CreateSubscriptionForCatalog(ctx context.Context, namespace, name, catalogNamespace, catalog, packageName, channel, startingCSV string, approval v1alpha1.Approval) (*v1alpha1.Subscription, error) // GetSubscription retrieves an OLM subscription by namespace and name. @@ -115,6 +118,8 @@ type KubeClientConnector interface { DeleteFile(fileBytes []byte) error // GetService returns k8s service by provided namespace and name. GetService(ctx context.Context, namespace, name string) (*corev1.Service, error) + // GetClusterRoleBinding returns cluster role binding by given name. + GetClusterRoleBinding(ctx context.Context, name string) (*rbacv1.ClusterRoleBinding, error) // DeleteAllMonitoringResources deletes all resources related to monitoring from k8s cluster. DeleteAllMonitoringResources(ctx context.Context, namespace string) error // GetNamespace returns a namespace. diff --git a/pkg/kubernetes/client/mock_kube_client_connector.go b/pkg/kubernetes/client/mock_kube_client_connector.go index 2f3fef35..d1b24962 100644 --- a/pkg/kubernetes/client/mock_kube_client_connector.go +++ b/pkg/kubernetes/client/mock_kube_client_connector.go @@ -12,6 +12,7 @@ import ( mock "github.com/stretchr/testify/mock" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -138,9 +139,9 @@ func (_m *MockKubeClientConnector) CreateNamespace(name string) error { return r0 } -// CreateOperatorGroup provides a mock function with given fields: ctx, namespace, name -func (_m *MockKubeClientConnector) CreateOperatorGroup(ctx context.Context, namespace string, name string) (*v1.OperatorGroup, error) { - ret := _m.Called(ctx, namespace, name) +// CreateOperatorGroup provides a mock function with given fields: ctx, namespace, name, targetNamespaces +func (_m *MockKubeClientConnector) CreateOperatorGroup(ctx context.Context, namespace string, name string, targetNamespaces []string) (*v1.OperatorGroup, error) { + ret := _m.Called(ctx, namespace, name, targetNamespaces) if len(ret) == 0 { panic("no return value specified for CreateOperatorGroup") @@ -148,19 +149,49 @@ func (_m *MockKubeClientConnector) CreateOperatorGroup(ctx context.Context, name var r0 *v1.OperatorGroup var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*v1.OperatorGroup, error)); ok { - return rf(ctx, namespace, name) + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) (*v1.OperatorGroup, error)); ok { + return rf(ctx, namespace, name, targetNamespaces) } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1.OperatorGroup); ok { - r0 = rf(ctx, namespace, name) + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) *v1.OperatorGroup); ok { + r0 = rf(ctx, namespace, name, targetNamespaces) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*v1.OperatorGroup) } } - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, namespace, name) + if rf, ok := ret.Get(1).(func(context.Context, string, string, []string) error); ok { + r1 = rf(ctx, namespace, name, targetNamespaces) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateSubscription provides a mock function with given fields: ctx, namespace, subscription +func (_m *MockKubeClientConnector) CreateSubscription(ctx context.Context, namespace string, subscription *v1alpha1.Subscription) (*v1alpha1.Subscription, error) { + ret := _m.Called(ctx, namespace, subscription) + + if len(ret) == 0 { + panic("no return value specified for CreateSubscription") + } + + var r0 *v1alpha1.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *v1alpha1.Subscription) (*v1alpha1.Subscription, error)); ok { + return rf(ctx, namespace, subscription) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *v1alpha1.Subscription) *v1alpha1.Subscription); ok { + r0 = rf(ctx, namespace, subscription) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1alpha1.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *v1alpha1.Subscription) error); ok { + r1 = rf(ctx, namespace, subscription) } else { r1 = ret.Error(1) } @@ -372,6 +403,36 @@ func (_m *MockKubeClientConnector) GenerateKubeConfigWithToken(user string, secr return r0, r1 } +// GetClusterRoleBinding provides a mock function with given fields: ctx, name +func (_m *MockKubeClientConnector) GetClusterRoleBinding(ctx context.Context, name string) (*rbacv1.ClusterRoleBinding, error) { + ret := _m.Called(ctx, name) + + if len(ret) == 0 { + panic("no return value specified for GetClusterRoleBinding") + } + + var r0 *rbacv1.ClusterRoleBinding + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*rbacv1.ClusterRoleBinding, error)); ok { + return rf(ctx, name) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *rbacv1.ClusterRoleBinding); ok { + r0 = rf(ctx, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rbacv1.ClusterRoleBinding) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetClusterServiceVersion provides a mock function with given fields: ctx, key func (_m *MockKubeClientConnector) GetClusterServiceVersion(ctx context.Context, key types.NamespacedName) (*v1alpha1.ClusterServiceVersion, error) { ret := _m.Called(ctx, key) diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index c0f1892b..d1b694b7 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -26,15 +26,18 @@ import ( "io/fs" "log" "net/http" + "os" "strings" "time" - "github.com/operator-framework/api/pkg/operators/v1alpha1" + olmv1 "github.com/operator-framework/api/pkg/operators/v1" + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" everestv1alpha1 "github.com/percona/everest-operator/api/v1alpha1" "go.uber.org/zap" yamlv3 "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -67,15 +70,12 @@ const ( // PerconaEverestDeploymentName stores the name of everest backend deployment. PerconaEverestDeploymentName = "percona-everest" - // CatalogSourceNamespace defines a namespace to use to find a catalog source. - CatalogSourceNamespace = "olm" - // CatalogSource is the name of OLM catalog source. - CatalogSource = "percona-everest-catalog" - // OperatorGroup defines the name of the configuration for subscriptions. - OperatorGroup = "percona-operators-group" // EverestOperatorDeploymentName is the name of the deployment for everest operator. EverestOperatorDeploymentName = "everest-operator-controller-manager" + // EverestWatchNamespacesEnvVar is the name of the environment variable. + EverestWatchNamespacesEnvVar = "WATCH_NAMESPACES" + pxcDeploymentName = "percona-xtradb-cluster-operator" psmdbDeploymentName = "percona-server-mongodb-operator" postgresDeploymentName = "percona-postgresql-operator" @@ -86,6 +86,7 @@ const ( databaseClusterAPIVersion = "everest.percona.com/v1alpha1" restartAnnotationKey = "everest.percona.com/restart" managedByKey = "everest.percona.com/managed-by" + disableTelemetryEnvVar = "DISABLE_TELEMETRY" // ContainerStateWaiting represents a state when container requires some // operations being done in order to complete start up. ContainerStateWaiting ContainerState = "waiting" @@ -473,9 +474,9 @@ func (k *Kubernetes) InstallOLMOperator(ctx context.Context, upgrade bool) error func (k *Kubernetes) applyCSVs(ctx context.Context, resources []unstructured.Unstructured) error { subscriptions := filterResources(resources, func(r unstructured.Unstructured) bool { return r.GroupVersionKind() == schema.GroupVersionKind{ - Group: v1alpha1.GroupName, - Version: v1alpha1.GroupVersion, - Kind: v1alpha1.SubscriptionKind, + Group: olmv1alpha1.GroupName, + Version: olmv1alpha1.GroupVersion, + Kind: olmv1alpha1.SubscriptionKind, } }) @@ -621,20 +622,46 @@ type InstallOperatorRequest struct { CatalogSource string CatalogSourceNamespace string Channel string - InstallPlanApproval v1alpha1.Approval + InstallPlanApproval olmv1alpha1.Approval StartingCSV string + TargetNamespaces []string + SubscriptionConfig *olmv1alpha1.SubscriptionConfig } // InstallOperator installs an operator via OLM. -func (k *Kubernetes) InstallOperator(ctx context.Context, req InstallOperatorRequest) error { - if err := createOperatorGroupIfNeeded(ctx, k.client, req.OperatorGroup, req.Namespace); err != nil { - return err +func (k *Kubernetes) InstallOperator(ctx context.Context, req InstallOperatorRequest) error { //nolint:funlen + disableTelemetry, ok := os.LookupEnv(disableTelemetryEnvVar) + if !ok || disableTelemetry != "true" { + disableTelemetry = "false" + } + config := &olmv1alpha1.SubscriptionConfig{Env: []corev1.EnvVar{}} + if req.SubscriptionConfig != nil { + config = req.SubscriptionConfig + } + config.Env = append(config.Env, corev1.EnvVar{ + Name: disableTelemetryEnvVar, + Value: disableTelemetry, + }) + subscription := &olmv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: olmv1alpha1.SubscriptionKind, + APIVersion: olmv1alpha1.SubscriptionCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: req.Name, + }, + Spec: &olmv1alpha1.SubscriptionSpec{ + CatalogSource: req.CatalogSource, + CatalogSourceNamespace: req.CatalogSourceNamespace, + Package: req.Name, + Channel: req.Channel, + StartingCSV: req.StartingCSV, + InstallPlanApproval: req.InstallPlanApproval, + Config: config, + }, } - - subs, err := k.client.CreateSubscriptionForCatalog( - ctx, req.Namespace, req.Name, "olm", req.CatalogSource, - req.Name, req.Channel, req.StartingCSV, v1alpha1.ApprovalManual, - ) + subs, err := k.client.CreateSubscription(ctx, req.Namespace, subscription) if err != nil { return errors.Join(err, errors.New("cannot create a subscription to install the operator")) } @@ -691,23 +718,38 @@ func (k *Kubernetes) approveInstallPlan(ctx context.Context, namespace, installP return true, nil } -func createOperatorGroupIfNeeded( - ctx context.Context, - client client.KubeClientConnector, - name, namespace string, -) error { - _, err := client.GetOperatorGroup(ctx, namespace, name) - if err == nil { +// CreateOperatorGroup creates operator group in the given namespace. +func (k *Kubernetes) CreateOperatorGroup(ctx context.Context, name, namespace string, targetNamespaces []string) error { + targetNamespaces = append(targetNamespaces, namespace) + og, err := k.client.GetOperatorGroup(ctx, namespace, name) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if err != nil && apierrors.IsNotFound(err) { + _, err = k.client.CreateOperatorGroup(ctx, namespace, name, targetNamespaces) + if err != nil { + return err + } return nil } - - _, err = client.CreateOperatorGroup(ctx, namespace, name) - - return err + og.Kind = olmv1.OperatorGroupKind + og.APIVersion = "operators.coreos.com/v1" + var update bool + for _, namespace := range targetNamespaces { + namespace := namespace + if !arrayContains(og.Spec.TargetNamespaces, namespace) { + update = true + } + } + if update { + og.Spec.TargetNamespaces = targetNamespaces + return k.client.ApplyObject(og) + } + return nil } // ListSubscriptions all the subscriptions in the namespace. -func (k *Kubernetes) ListSubscriptions(ctx context.Context, namespace string) (*v1alpha1.SubscriptionList, error) { +func (k *Kubernetes) ListSubscriptions(ctx context.Context, namespace string) (*olmv1alpha1.SubscriptionList, error) { return k.client.ListSubscriptions(ctx, namespace) } @@ -729,8 +771,8 @@ func (k *Kubernetes) UpgradeOperator(ctx context.Context, namespace, name string return err } -func (k *Kubernetes) getInstallPlan(ctx context.Context, namespace, name string) (*v1alpha1.InstallPlan, error) { - var subs *v1alpha1.Subscription +func (k *Kubernetes) getInstallPlan(ctx context.Context, namespace, name string) (*olmv1alpha1.InstallPlan, error) { + var subs *olmv1alpha1.Subscription // If the subscription was recently created, the install plan might not be ready yet. err := wait.PollUntilContextTimeout(ctx, pollInterval, pollDuration, false, func(ctx context.Context) (bool, error) { @@ -769,7 +811,7 @@ func (k *Kubernetes) GetServerVersion() (*version.Info, error) { func (k *Kubernetes) GetClusterServiceVersion( ctx context.Context, key types.NamespacedName, -) (*v1alpha1.ClusterServiceVersion, error) { +) (*olmv1alpha1.ClusterServiceVersion, error) { return k.client.GetClusterServiceVersion(ctx, key) } @@ -777,7 +819,7 @@ func (k *Kubernetes) GetClusterServiceVersion( func (k *Kubernetes) ListClusterServiceVersion( ctx context.Context, namespace string, -) (*v1alpha1.ClusterServiceVersionList, error) { +) (*olmv1alpha1.ClusterServiceVersionList, error) { return k.client.ListClusterServiceVersion(ctx, namespace) } @@ -955,6 +997,28 @@ func (k *Kubernetes) DeleteEverest(ctx context.Context, namespace string) error return nil } +// GetWatchedNamespaces returns list of watched namespaces. +func (k *Kubernetes) GetWatchedNamespaces(ctx context.Context, namespace string) ([]string, error) { + deployment, err := k.GetDeployment(ctx, EverestOperatorDeploymentName, namespace) + if err != nil { + return nil, err + } + + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name != everestOperatorContainerName { + continue + } + for _, envVar := range container.Env { + if envVar.Name != EverestWatchNamespacesEnvVar { + continue + } + return strings.Split(envVar.Value, ","), nil + } + } + + return nil, errors.New("failed to get watched namespaces") +} + // GetDeployment returns k8s deployment by provided name and namespace. func (k *Kubernetes) GetDeployment(ctx context.Context, name, namespace string) (*appsv1.Deployment, error) { return k.client.GetDeployment(ctx, name, namespace) @@ -964,3 +1028,51 @@ func (k *Kubernetes) GetDeployment(ctx context.Context, name, namespace string) func (k *Kubernetes) WaitForRollout(ctx context.Context, name, namespace string) error { return k.client.DoRolloutWait(ctx, types.NamespacedName{Name: name, Namespace: namespace}) } + +// UpdateClusterRoleBinding updates namespaces list for the cluster role by provided name. +func (k *Kubernetes) UpdateClusterRoleBinding(ctx context.Context, name string, namespaces []string) error { + binding, err := k.client.GetClusterRoleBinding(ctx, name) + if err != nil { + return err + } + if len(binding.Subjects) == 0 { + return fmt.Errorf("no subjects available for the cluster role binding %s", name) + } + var needsUpdate bool + for _, namespace := range namespaces { + namespace := namespace + if !subjectsContains(binding.Subjects, namespace) { + subject := binding.Subjects[0] + subject.Namespace = namespace + binding.Subjects = append(binding.Subjects, subject) + needsUpdate = true + } + } + if needsUpdate { + binding.Kind = "ClusterRoleBinding" + binding.APIVersion = "rbac.authorization.k8s.io/v1" + return k.client.ApplyObject(binding) + } + + return nil +} + +func arrayContains(s []string, e string) bool { + for _, a := range s { + a := a + if a == e { + return true + } + } + return false +} + +func subjectsContains(s []rbacv1.Subject, n string) bool { + for _, a := range s { + a := a + if a.Namespace == n { + return true + } + } + return false +} diff --git a/pkg/kubernetes/olm_operator_test.go b/pkg/kubernetes/olm_operator_test.go index 2b40bcf5..e82863d4 100644 --- a/pkg/kubernetes/olm_operator_test.go +++ b/pkg/kubernetes/olm_operator_test.go @@ -46,8 +46,7 @@ func TestInstallOlmOperator(t *testing.T) { //nolint:paralleltest t.Run("Install OLM Operator", func(t *testing.T) { k8sclient.On( - "CreateSubscriptionForCatalog", mock.Anything, mock.Anything, mock.Anything, - mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, + "CreateSubscription", mock.Anything, mock.Anything, mock.Anything, ).Return(&v1alpha1.Subscription{}, nil) k8sclient.On("GetDeployment", ctx, mock.Anything, "olm").Return(&appsv1.Deployment{}, nil) k8sclient.On("ApplyFile", mock.Anything).Return(nil) @@ -85,9 +84,8 @@ func TestInstallOlmOperator(t *testing.T) { }, } k8sclient.On( - "CreateSubscriptionForCatalog", - mock.Anything, subscriptionNamespace, operatorName, "olm", - catalogSource, operatorName, "stable", "", v1alpha1.ApprovalManual, + "CreateSubscription", + mock.Anything, subscriptionNamespace, mockSubscription, ).Return(mockSubscription, nil) k8sclient.On("GetSubscription", mock.Anything, subscriptionNamespace, operatorName).Return(mockSubscription, nil) mockInstallPlan := &v1alpha1.InstallPlan{} diff --git a/pkg/monitoring/enable.go b/pkg/monitoring/enable.go index 7134151c..30a6607f 100644 --- a/pkg/monitoring/enable.go +++ b/pkg/monitoring/enable.go @@ -33,6 +33,7 @@ import ( "github.com/percona/percona-everest-cli/commands/common" everestClient "github.com/percona/percona-everest-cli/pkg/everest/client" + "github.com/percona/percona-everest-cli/pkg/install" "github.com/percona/percona-everest-cli/pkg/kubernetes" ) @@ -181,10 +182,10 @@ func (m *Monitoring) installVMOperator(ctx context.Context) error { params := kubernetes.InstallOperatorRequest{ Namespace: m.config.Namespace, Name: vmOperatorName, - OperatorGroup: kubernetes.OperatorGroup, - CatalogSource: kubernetes.CatalogSource, - CatalogSourceNamespace: kubernetes.CatalogSourceNamespace, - Channel: "stable-v0", + OperatorGroup: install.OperatorGroup, + CatalogSource: install.CatalogSource, + CatalogSourceNamespace: install.CatalogSourceNamespace, + Channel: install.VMOperatorChannel, InstallPlanApproval: v1alpha1.ApprovalManual, } diff --git a/pkg/uninstall/uninstall.go b/pkg/uninstall/uninstall.go index 8e4fc53a..e5b29293 100644 --- a/pkg/uninstall/uninstall.go +++ b/pkg/uninstall/uninstall.go @@ -25,6 +25,7 @@ import ( "go.uber.org/zap" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/percona/percona-everest-cli/pkg/install" "github.com/percona/percona-everest-cli/pkg/kubernetes" ) @@ -39,8 +40,6 @@ type Uninstall struct { type Config struct { // KubeconfigPath is a path to a kubeconfig KubeconfigPath string `mapstructure:"kubeconfig"` - // Namespace defines the namespace operators shall be installed to. - Namespace string // AssumeYes is true when all questions can be skipped. AssumeYes bool `mapstructure:"assume-yes"` // Force is true when we shall not prompt for removal. @@ -62,32 +61,8 @@ func NewUninstall(c Config, l *zap.SugaredLogger) (*Uninstall, error) { return cli, nil } -func (u *Uninstall) runEverestWizard() error { - if !u.config.AssumeYes { - pNamespace := &survey.Input{ - Message: "Please select namespace", - Default: u.config.Namespace, - } - if err := survey.AskOne( - pNamespace, - &u.config.Namespace, - ); err != nil { - return err - } - } - - return nil -} - // Run runs the cluster command. func (u *Uninstall) Run(ctx context.Context) error { - if err := u.runEverestWizard(); err != nil { - return err - } - if u.config.Namespace == "" { - return errors.New("namespace is not provided") - } - if !u.config.AssumeYes { msg := `You are about to uninstall Everest from the Kubernetes cluster. This will uninstall Everest and all monitoring resources deployed by it. All other resources such as Databases and Database Backups will not be affected.` @@ -112,7 +87,7 @@ This will uninstall Everest and all monitoring resources deployed by it. All oth if err := u.uninstallK8sResources(ctx); err != nil { return err } - if err := u.kubeClient.DeleteEverest(ctx, u.config.Namespace); err != nil { + if err := u.kubeClient.DeleteEverest(ctx, install.EverestNamespace); err != nil { return err } @@ -120,23 +95,23 @@ This will uninstall Everest and all monitoring resources deployed by it. All oth } func (u *Uninstall) checkResourcesExist(ctx context.Context) error { - _, err := u.kubeClient.GetNamespace(ctx, u.config.Namespace) + _, err := u.kubeClient.GetNamespace(ctx, install.EverestNamespace) if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("namespace %s is not found", u.config.Namespace) + return fmt.Errorf("namespace %s is not found", install.EverestNamespace) } if err != nil && !k8serrors.IsNotFound(err) { return err } - _, err = u.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, u.config.Namespace) + _, err = u.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, install.EverestNamespace) if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("no Everest deployment in %s namespace", u.config.Namespace) + return fmt.Errorf("no Everest deployment in %s namespace", install.EverestNamespace) } return err } func (u *Uninstall) uninstallK8sResources(ctx context.Context) error { u.l.Info("Deleting all Kubernetes monitoring resources in Kubernetes cluster") - if err := u.kubeClient.DeleteAllMonitoringResources(ctx, u.config.Namespace); err != nil { + if err := u.kubeClient.DeleteAllMonitoringResources(ctx, install.EverestNamespace); err != nil { return errors.Join(err, errors.New("could not uninstall monitoring resources from the Kubernetes cluster")) } diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index fb613598..3dd52b3d 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -19,27 +19,25 @@ package upgrade import ( "context" "errors" + "fmt" "net/url" "os" "github.com/AlecAivazis/survey/v2" goversion "github.com/hashicorp/go-version" - "github.com/operator-framework/api/pkg/operators/v1alpha1" "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "github.com/percona/percona-everest-cli/data" + "github.com/percona/percona-everest-cli/pkg/install" "github.com/percona/percona-everest-cli/pkg/kubernetes" ) type ( // Config defines configuration required for upgrade command. Config struct { - // Name of the Kubernetes Cluster - Name string - // Namespace defines the namespace operators shall be installed to. - Namespace string + // Namespaces defines namespaces that everest can operate in. + Namespaces []string `mapstructure:"namespace"` // KubeconfigPath is a path to a kubeconfig KubeconfigPath string `mapstructure:"kubeconfig"` // UpgradeOLM defines do we need to upgrade OLM or not. @@ -78,6 +76,12 @@ func NewUpgrade(c Config, l *zap.SugaredLogger) (*Upgrade, error) { // Run runs the operators installation process. func (u *Upgrade) Run(ctx context.Context) error { + if err := u.runEverestWizard(ctx); err != nil { + return err + } + if len(u.config.Namespaces) == 0 { + return errors.New("namespace list is empty. Specify at least one namespace") + } if err := u.upgradeOLM(ctx); err != nil { return err } @@ -92,37 +96,66 @@ func (u *Upgrade) Run(ctx context.Context) error { } u.l.Info("Subscriptions have been patched") u.l.Info("Upgrading Everest") - if err := u.kubeClient.InstallEverest(ctx, u.config.Namespace); err != nil { + if err := u.kubeClient.InstallEverest(ctx, install.EverestNamespace); err != nil { return err } u.l.Info("Everest has been upgraded") return nil } -func (u *Upgrade) patchSubscriptions(ctx context.Context) error { - subList, err := u.kubeClient.ListSubscriptions(ctx, u.config.Namespace) - if err != nil { - return err - } - disableTelemetryEnvVar := "DISABLE_TELEMETRY" - disableTelemetry, ok := os.LookupEnv(disableTelemetryEnvVar) - if !ok || disableTelemetry != "true" { - disableTelemetry = "false" - } - for _, subscription := range subList.Items { - subscription := subscription - subscription.Spec.Config = &v1alpha1.SubscriptionConfig{ - Env: []corev1.EnvVar{ - { - Name: disableTelemetryEnvVar, - Value: disableTelemetry, - }, - }, +func (u *Upgrade) runEverestWizard(ctx context.Context) error { + if !u.config.SkipWizard { + namespaces, err := u.kubeClient.GetWatchedNamespaces(ctx, install.EverestNamespace) + if err != nil { + return err } - if err := u.kubeClient.ApplyObject(&subscription); err != nil { + pNamespace := &survey.MultiSelect{ + Message: "Please select namespaces", + Options: namespaces, + } + if err := survey.AskOne( + pNamespace, + &u.config.Namespaces, + survey.WithValidator(survey.MinItems(1)), + ); err != nil { return err } } + + return nil +} + +func (u *Upgrade) patchSubscriptions(ctx context.Context) error { + for _, namespace := range u.config.Namespaces { + namespace := namespace + subList, err := u.kubeClient.ListSubscriptions(ctx, namespace) + if err != nil { + return err + } + if len(subList.Items) == 0 { + u.l.Warn(fmt.Sprintf("No subscriptions found in '%s' namespace", namespace)) + continue + } + disableTelemetryEnvVar := "DISABLE_TELEMETRY" + disableTelemetry, ok := os.LookupEnv(disableTelemetryEnvVar) + if !ok || disableTelemetry != "true" { + disableTelemetry = "false" + } + for _, subscription := range subList.Items { + u.l.Info(fmt.Sprintf("Patching %s subscription in '%s' namespace", subscription.Name, subscription.Namespace)) + subscription := subscription + for i := range subscription.Spec.Config.Env { + env := subscription.Spec.Config.Env[i] + if env.Name == disableTelemetryEnvVar { + env.Value = disableTelemetry + subscription.Spec.Config.Env[i] = env + } + } + if err := u.kubeClient.ApplyObject(&subscription); err != nil { + return err + } + } + } return nil } From 25a61975f247a72d71765c6e2bb3d6a794ac8d64 Mon Sep 17 00:00:00 2001 From: Diogo Recharte Date: Wed, 7 Feb 2024 16:46:00 +0000 Subject: [PATCH 2/5] EVEREST-633 Install monitoring stack onto a separate namespace (#281) * EVEREST-633 install monitoring stack with install command By moving to a multi-namespace everest-operator installation we configured the operator group of the percona-everest namespace to have N target namespaces. The VM Operator doesn't support multi-namespaces so we can't deploy it to the same namespace as the everest-operator. Therefore we install it onto a separate namespace for managing everything monitoring related. * EVEREST-633 remove monitoring command * EVEREST-633 Update everest-operator go mod * EVEREST-633 Update percona-everest-backend go mod * EVEREST-633 forbid installs in the monitoring namespace * EVEREST-633 update namespaces env var name * EVEREST-633 Update everest-operator go mod --- commands/monitoring.go | 34 -- commands/monitoring/enable.go | 98 ---- commands/root.go | 1 - ...baas-catalog.yaml => everest-catalog.yaml} | 4 +- go.mod | 10 +- go.sum | 32 +- pkg/install/install.go | 165 +++++-- pkg/kubernetes/kubernetes.go | 13 +- pkg/monitoring/deps.go | 42 -- pkg/monitoring/enable.go | 427 ------------------ .../mock_everest_client_connector_test.go | 138 ------ pkg/monitoring/monitoring_test.go | 153 ------- pkg/uninstall/uninstall.go | 12 +- pkg/upgrade/upgrade.go | 4 +- 14 files changed, 155 insertions(+), 978 deletions(-) delete mode 100644 commands/monitoring.go delete mode 100644 commands/monitoring/enable.go rename data/crds/olm/{percona-dbaas-catalog.yaml => everest-catalog.yaml} (77%) delete mode 100644 pkg/monitoring/deps.go delete mode 100644 pkg/monitoring/enable.go delete mode 100644 pkg/monitoring/mock_everest_client_connector_test.go delete mode 100644 pkg/monitoring/monitoring_test.go diff --git a/commands/monitoring.go b/commands/monitoring.go deleted file mode 100644 index 8e9c6a26..00000000 --- a/commands/monitoring.go +++ /dev/null @@ -1,34 +0,0 @@ -// percona-everest-cli -// Copyright (C) 2023 Percona LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package commands ... -package commands - -import ( - "github.com/spf13/cobra" - "go.uber.org/zap" - - "github.com/percona/percona-everest-cli/commands/monitoring" -) - -func newMonitoringCmd(l *zap.SugaredLogger) *cobra.Command { - cmd := &cobra.Command{ - Use: "monitoring", - } - - cmd.AddCommand(monitoring.NewMonitoringCmd(l)) - - return cmd -} diff --git a/commands/monitoring/enable.go b/commands/monitoring/enable.go deleted file mode 100644 index e2641836..00000000 --- a/commands/monitoring/enable.go +++ /dev/null @@ -1,98 +0,0 @@ -// percona-everest-cli -// Copyright (C) 2023 Percona LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package monitoring holds commands for monitoring command. -package monitoring - -import ( - "os" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "go.uber.org/zap" - - "github.com/percona/percona-everest-cli/pkg/monitoring" - "github.com/percona/percona-everest-cli/pkg/output" -) - -// NewMonitoringCmd returns a new enable monitoring command. -func NewMonitoringCmd(l *zap.SugaredLogger) *cobra.Command { - cmd := &cobra.Command{ - Use: "enable", - Run: func(cmd *cobra.Command, args []string) { - initMonitoringViperFlags(cmd) - - c, err := parseResetConfig() - if err != nil { - os.Exit(1) - } - - command, err := monitoring.NewMonitoring(*c, l) - if err != nil { - output.PrintError(err, l) - os.Exit(1) - } - - err = command.Run(cmd.Context()) - if err != nil { - output.PrintError(err, l) - os.Exit(1) - } - }, - } - - initMonitoringFlags(cmd) - - return cmd -} - -func initMonitoringFlags(cmd *cobra.Command) { - cmd.Flags().StringP("kubeconfig", "k", "~/.kube/config", "Path to a kubeconfig") - cmd.Flags().String("namespace", "percona-everest", "Namespace where Percona Everest is deployed") - cmd.Flags().Bool("skip-wizard", false, "Skip configuration wizard") - cmd.Flags().String("everest-url", "", "A URL to connect to Everest") - cmd.Flags().String("everest-token", "", "A Token to authenticate in Everest") - cmd.Flags().String("instance-name", "", - "Monitoring instance name from Everest. If defined, other monitoring configuration is ignored", - ) - cmd.Flags().String("new-instance-name", "", - "Name for a new monitoring instance if it's going to be created", - ) - cmd.Flags().String("type", "pmm", "Monitoring type") - cmd.Flags().String("pmm.endpoint", "http://127.0.0.1", "PMM endpoint URL") - cmd.Flags().String("pmm.username", "admin", "PMM username") - cmd.Flags().String("pmm.password", "", "PMM password") -} - -func initMonitoringViperFlags(cmd *cobra.Command) { - viper.BindEnv("kubeconfig") //nolint:errcheck,gosec - viper.BindPFlag("skip-wizard", cmd.Flags().Lookup("skip-wizard")) //nolint:errcheck,gosec - viper.BindPFlag("kubeconfig", cmd.Flags().Lookup("kubeconfig")) //nolint:errcheck,gosec - viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace")) //nolint:errcheck,gosec - viper.BindPFlag("everest-url", cmd.Flags().Lookup("everest-url")) //nolint:errcheck,gosec - viper.BindPFlag("everest-token", cmd.Flags().Lookup("everest-token")) //nolint:errcheck,gosec - viper.BindPFlag("instance-name", cmd.Flags().Lookup("instance-name")) //nolint:errcheck,gosec - viper.BindPFlag("new-instance-name", cmd.Flags().Lookup("new-instance-name")) //nolint:errcheck,gosec - viper.BindPFlag("type", cmd.Flags().Lookup("type")) //nolint:errcheck,gosec - viper.BindPFlag("pmm.endpoint", cmd.Flags().Lookup("pmm.endpoint")) //nolint:errcheck,gosec - viper.BindPFlag("pmm.username", cmd.Flags().Lookup("pmm.username")) //nolint:errcheck,gosec - viper.BindPFlag("pmm.password", cmd.Flags().Lookup("pmm.password")) //nolint:errcheck,gosec -} - -func parseResetConfig() (*monitoring.Config, error) { - c := &monitoring.Config{} - err := viper.Unmarshal(c) - return c, err -} diff --git a/commands/root.go b/commands/root.go index cbb78f3b..7c87b2a5 100644 --- a/commands/root.go +++ b/commands/root.go @@ -40,7 +40,6 @@ func NewRootCmd(l *zap.SugaredLogger) *cobra.Command { // rootCmd.AddCommand(newProvisionCmd(l)) // rootCmd.AddCommand(newListCmd(l)) // rootCmd.AddCommand(newDeleteCmd(l)) - rootCmd.AddCommand(newMonitoringCmd(l)) rootCmd.AddCommand(newTokenCmd(l)) rootCmd.AddCommand(newVersionCmd(l)) rootCmd.AddCommand(newUpgradeCmd(l)) diff --git a/data/crds/olm/percona-dbaas-catalog.yaml b/data/crds/olm/everest-catalog.yaml similarity index 77% rename from data/crds/olm/percona-dbaas-catalog.yaml rename to data/crds/olm/everest-catalog.yaml index c3895b16..f88ed1d9 100644 --- a/data/crds/olm/percona-dbaas-catalog.yaml +++ b/data/crds/olm/everest-catalog.yaml @@ -1,10 +1,10 @@ apiVersion: operators.coreos.com/v1alpha1 kind: CatalogSource metadata: - name: percona-everest-catalog + name: everest-catalog namespace: olm spec: - displayName: Percona Everest Catalog + displayName: Everest Catalog publisher: Percona sourceType: grpc grpcPodConfig: diff --git a/go.mod b/go.mod index 9627fef0..c2a634a5 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/operator-framework/api v0.22.0 github.com/operator-framework/operator-lifecycle-manager v0.26.0 - github.com/percona/everest-operator v0.6.0-dev1.0.20240202093727-ceb0bb0fb02f - github.com/percona/percona-everest-backend v0.5.1-0.20240202100350-4bc9d2a75a1b + github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9 + github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 @@ -45,13 +45,13 @@ require ( github.com/evanphx/json-patch/v5 v5.7.0 // indirect github.com/flosch/pongo2/v6 v6.0.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/getkin/kin-openapi v0.122.0 // indirect + github.com/getkin/kin-openapi v0.123.0 // indirect github.com/go-errors/errors v1.5.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/swag v0.22.8 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index 15ef3d3d..3d1257de 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= -github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= +github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -222,8 +222,8 @@ github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= @@ -254,8 +254,8 @@ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= @@ -327,8 +327,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -344,8 +344,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRid github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -539,12 +539,12 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/percona/everest-operator v0.6.0-dev1.0.20240202093727-ceb0bb0fb02f h1:ueUTTEw7Fc2yJyIq9pugBB8qai8LuO5uOBsuDMmJL70= -github.com/percona/everest-operator v0.6.0-dev1.0.20240202093727-ceb0bb0fb02f/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9 h1:M8vHkH1ITw2KP/2MZVeDzcrB0jq8ZbuhRB3ttUxhwqU= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901 h1:BDgsZRCjEuxl2/z4yWBqB0s8d20shuIDks7/RVdZiLs= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901/go.mod h1:fZRCMpUqkWlLVdRKqqaj001LoVP2eo6F0ZhoMPeXDng= -github.com/percona/percona-everest-backend v0.5.1-0.20240202100350-4bc9d2a75a1b h1:xFQ/6kAPx25C91g7pEcn/86EWiFpBtrk9isi6xpEyO0= -github.com/percona/percona-everest-backend v0.5.1-0.20240202100350-4bc9d2a75a1b/go.mod h1:Z54PtwvAOvCalNYSRdIQiy+KRhNa7XyDDG+1FfwUf3Y= +github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a h1:2CZcbM4NWnKq3/gE5OySzKMiJhjMwARa6tegJ2XIT48= +github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a/go.mod h1:Ql0t0eWcZV17MUTse4YYZIgXrSbLom8C96z30/bnyj0= github.com/percona/percona-postgresql-operator v0.0.0-20231220140959-ad5eef722609 h1:+UOK4gcHrRgqjo4smgfwT7/0apF6PhAJdQIdAV4ub/M= github.com/percona/percona-postgresql-operator v0.0.0-20231220140959-ad5eef722609/go.mod h1:znzhtSTF6moUOGNPzVpgfFEMaqIe/ijTSHY8BNDkiEA= github.com/percona/percona-server-mongodb-operator v1.15.0 h1:pcP9GMi9f05VFi8e/TQifBrR+cvOkYMiv1xAftkESBs= @@ -592,8 +592,8 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/install/install.go b/pkg/install/install.go index 7fa051f3..e9b51c69 100644 --- a/pkg/install/install.go +++ b/pkg/install/install.go @@ -50,6 +50,7 @@ const ( pxcOperatorName = "percona-xtradb-cluster-operator" psmdbOperatorName = "percona-server-mongodb-operator" pgOperatorName = "percona-postgresql-operator" + vmOperatorName = "victoriametrics-operator" operatorInstallThreads = 1 everestServiceAccount = "everest-admin" @@ -61,17 +62,24 @@ const ( pxcOperatorChannel = "stable-v1" psmdbOperatorChannel = "stable-v1" pgOperatorChannel = "fast-v2" - // VMOperatorChannel is the catalog channel for the VM Operator. - VMOperatorChannel = "stable-v0" - - // CatalogSourceNamespace is the namespace where the catalog source is installed. - CatalogSourceNamespace = "olm" - // CatalogSource is the name of the catalog source. - CatalogSource = "percona-everest-catalog" - // OperatorGroup is the name of the operator group. - OperatorGroup = "percona-operators-group" - // EverestNamespace is the namespace where everest is installed. - EverestNamespace = "percona-everest" + vmOperatorChannel = "stable-v0" + + // catalogSourceNamespace is the namespace where the catalog source is installed. + catalogSourceNamespace = "olm" + // catalogSource is the name of the catalog source. + catalogSource = "everest-catalog" + + // systemOperatorGroup is the name of the system operator group. + systemOperatorGroup = "everest-system" + // monitoringOperatorGroup is the name of the monitoring operator group. + monitoringOperatorGroup = "everest-monitoring" + // dbsOperatorGroup is the name of the database operator group. + dbsOperatorGroup = "everest-databases" + + // SystemNamespace is the namespace where everest is installed. + SystemNamespace = "percona-everest" + // monitoringNamespace is the namespace where the monitoring stack is installed. + monitoringNamespace = "percona-everest-monitoring" ) type ( @@ -102,7 +110,7 @@ type ( func NewInstall(c Config, l *zap.SugaredLogger) (*Install, error) { cli := &Install{ config: c, - l: l.With("component", "install/operators"), + l: l.With("component", "install"), } k, err := kubernetes.New(c.KubeconfigPath, cli.l) @@ -119,42 +127,31 @@ func NewInstall(c Config, l *zap.SugaredLogger) (*Install, error) { } // Run runs the operators installation process. -func (o *Install) Run(ctx context.Context) error { //nolint:cyclop +func (o *Install) Run(ctx context.Context) error { if err := o.populateConfig(); err != nil { return err } - if len(o.config.Namespaces) == 0 { - return errors.New("namespace list is empty. Specify at least one namespace using the --namespace flag") - } - for _, ns := range o.config.Namespaces { - if ns == EverestNamespace { - return fmt.Errorf("'%s' namespace is reserved for Everest internals. Please specify another namespace", ns) - } - } - - if err := o.createNamespace(EverestNamespace); err != nil { - return err - } if err := o.provisionOLM(ctx); err != nil { return err } - o.l.Info("Creating operator group for the everest") - if err := o.kubeClient.CreateOperatorGroup(ctx, OperatorGroup, EverestNamespace, o.config.Namespaces); err != nil { + + if err := o.provisionMonitoringStack(ctx); err != nil { return err } - if err := o.provisionAllNamespaces(ctx); err != nil { + + if err := o.provisionDBNamespaces(ctx); err != nil { return err } - if err := o.installEverest(ctx); err != nil { + + if err := o.provisionEverestOperator(ctx); err != nil { return err } - o.l.Info("Updating cluster role bindings for the everest-admin") - if err := o.kubeClient.UpdateClusterRoleBinding(ctx, everestServiceAccountClusterRoleBinding, o.config.Namespaces); err != nil { + + if err := o.provisionEverest(ctx); err != nil { return err } - - _, err := o.kubeClient.GetSecret(ctx, token.SecretName, EverestNamespace) + _, err := o.kubeClient.GetSecret(ctx, token.SecretName, SystemNamespace) if err != nil && !k8serrors.IsNotFound(err) { return errors.Join(err, errors.New("could not get the everest token secret")) } @@ -176,14 +173,80 @@ func (o *Install) populateConfig() error { } } + if len(o.config.Namespaces) == 0 { + return errors.New("namespace list is empty. Specify at least one namespace using the --namespace flag") + } + for _, ns := range o.config.Namespaces { + if ns == SystemNamespace || ns == monitoringNamespace { + return fmt.Errorf("'%s' namespace is reserved for Everest internals. Please specify another namespace", ns) + } + } + return nil } -func (o *Install) installEverest(ctx context.Context) error { - if err := o.installOperator(ctx, everestOperatorChannel, everestOperatorName, EverestNamespace)(); err != nil { +func (o *Install) installVMOperator(ctx context.Context) error { + o.l.Info("Creating operator group for everest") + if err := o.kubeClient.CreateOperatorGroup(ctx, monitoringOperatorGroup, monitoringNamespace, []string{}); err != nil { return err } - d, err := o.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, EverestNamespace) + o.l.Infof("Installing %s operator", vmOperatorName) + + params := kubernetes.InstallOperatorRequest{ + Namespace: monitoringNamespace, + Name: vmOperatorName, + OperatorGroup: monitoringOperatorGroup, + CatalogSource: catalogSource, + CatalogSourceNamespace: catalogSourceNamespace, + Channel: vmOperatorChannel, + InstallPlanApproval: v1alpha1.ApprovalManual, + } + + if err := o.kubeClient.InstallOperator(ctx, params); err != nil { + o.l.Errorf("failed installing %s operator", vmOperatorName) + return err + } + o.l.Infof("%s operator has been installed", vmOperatorName) + return nil +} + +func (o *Install) provisionMonitoringStack(ctx context.Context) error { + l := o.l.With("action", "monitoring") + if err := o.createNamespace(monitoringNamespace); err != nil { + return err + } + + l.Info("Preparing k8s cluster for monitoring") + if err := o.installVMOperator(ctx); err != nil { + return err + } + if err := o.kubeClient.ProvisionMonitoring(monitoringNamespace); err != nil { + return errors.Join(err, errors.New("could not provision monitoring configuration")) + } + + l.Info("K8s cluster monitoring has been provisioned successfully") + return nil +} + +func (o *Install) provisionEverestOperator(ctx context.Context) error { + if err := o.createNamespace(SystemNamespace); err != nil { + return err + } + + o.l.Info("Creating operator group for everest") + if err := o.kubeClient.CreateOperatorGroup(ctx, systemOperatorGroup, SystemNamespace, o.config.Namespaces); err != nil { + return err + } + + if err := o.installOperator(ctx, everestOperatorChannel, everestOperatorName, SystemNamespace)(); err != nil { + return err + } + + return nil +} + +func (o *Install) provisionEverest(ctx context.Context) error { + d, err := o.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, SystemNamespace) var everestExists bool if err != nil && !k8serrors.IsNotFound(err) { return err @@ -193,30 +256,36 @@ func (o *Install) installEverest(ctx context.Context) error { } if !everestExists { - o.l.Info(fmt.Sprintf("Deploying Everest to %s", EverestNamespace)) - err = o.kubeClient.InstallEverest(ctx, EverestNamespace) + o.l.Info(fmt.Sprintf("Deploying Everest to %s", SystemNamespace)) + err = o.kubeClient.InstallEverest(ctx, SystemNamespace) if err != nil { return err } } else { o.l.Info("Restarting Everest") - if err := o.kubeClient.RestartEverest(ctx, everestOperatorName, EverestNamespace); err != nil { + if err := o.kubeClient.RestartEverest(ctx, everestOperatorName, SystemNamespace); err != nil { return err } - if err := o.kubeClient.RestartEverest(ctx, everestBackendServiceName, EverestNamespace); err != nil { + if err := o.kubeClient.RestartEverest(ctx, everestBackendServiceName, SystemNamespace); err != nil { return err } } + + o.l.Info("Updating cluster role bindings for everest-admin") + if err := o.kubeClient.UpdateClusterRoleBinding(ctx, everestServiceAccountClusterRoleBinding, o.config.Namespaces); err != nil { + return err + } + return nil } -func (o *Install) provisionAllNamespaces(ctx context.Context) error { +func (o *Install) provisionDBNamespaces(ctx context.Context) error { for _, namespace := range o.config.Namespaces { namespace := namespace if err := o.createNamespace(namespace); err != nil { return err } - if err := o.kubeClient.CreateOperatorGroup(ctx, OperatorGroup, namespace, []string{}); err != nil { + if err := o.kubeClient.CreateOperatorGroup(ctx, dbsOperatorGroup, namespace, []string{}); err != nil { return err } @@ -271,7 +340,7 @@ func (o *Install) runEverestWizard() error { continue } - if ns == EverestNamespace { + if ns == SystemNamespace { return fmt.Errorf("'%s' namespace is reserved for Everest internals. Please specify another namespace", ns) } @@ -403,9 +472,9 @@ func (o *Install) installOperator(ctx context.Context, channel, operatorName, na params := kubernetes.InstallOperatorRequest{ Namespace: namespace, Name: operatorName, - OperatorGroup: OperatorGroup, - CatalogSource: CatalogSource, - CatalogSourceNamespace: CatalogSourceNamespace, + OperatorGroup: systemOperatorGroup, + CatalogSource: catalogSource, + CatalogSourceNamespace: catalogSourceNamespace, Channel: channel, InstallPlanApproval: v1alpha1.ApprovalManual, } @@ -414,7 +483,7 @@ func (o *Install) installOperator(ctx context.Context, channel, operatorName, na params.SubscriptionConfig = &v1alpha1.SubscriptionConfig{ Env: []corev1.EnvVar{ { - Name: kubernetes.EverestWatchNamespacesEnvVar, + Name: kubernetes.EverestDBNamespacesEnvVar, Value: strings.Join(o.config.Namespaces, ","), }, }, @@ -487,7 +556,7 @@ func (o *Install) generateToken(ctx context.Context) (*token.ResetResponse, erro r, err := token.NewReset( token.ResetConfig{ KubeconfigPath: o.config.KubeconfigPath, - Namespace: EverestNamespace, + Namespace: SystemNamespace, }, o.l, ) diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index d1b694b7..b09b7792 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -73,8 +73,9 @@ const ( // EverestOperatorDeploymentName is the name of the deployment for everest operator. EverestOperatorDeploymentName = "everest-operator-controller-manager" - // EverestWatchNamespacesEnvVar is the name of the environment variable. - EverestWatchNamespacesEnvVar = "WATCH_NAMESPACES" + // EverestDBNamespacesEnvVar is the name of the environment variable that + // contains the list of monitored namespaces. + EverestDBNamespacesEnvVar = "DB_NAMESPACES" pxcDeploymentName = "percona-xtradb-cluster-operator" psmdbDeploymentName = "percona-server-mongodb-operator" @@ -498,7 +499,7 @@ func (k *Kubernetes) applyCSVs(ctx context.Context, resources []unstructured.Uns // InstallPerconaCatalog installs percona catalog and ensures that packages are available. func (k *Kubernetes) InstallPerconaCatalog(ctx context.Context) error { - data, err := fs.ReadFile(data.OLMCRDs, "crds/olm/percona-dbaas-catalog.yaml") + data, err := fs.ReadFile(data.OLMCRDs, "crds/olm/everest-catalog.yaml") if err != nil { return errors.Join(err, errors.New("failed to read percona catalog file")) } @@ -997,8 +998,8 @@ func (k *Kubernetes) DeleteEverest(ctx context.Context, namespace string) error return nil } -// GetWatchedNamespaces returns list of watched namespaces. -func (k *Kubernetes) GetWatchedNamespaces(ctx context.Context, namespace string) ([]string, error) { +// GetDBNamespaces returns a list of namespaces that are monitored by the Everest operator. +func (k *Kubernetes) GetDBNamespaces(ctx context.Context, namespace string) ([]string, error) { deployment, err := k.GetDeployment(ctx, EverestOperatorDeploymentName, namespace) if err != nil { return nil, err @@ -1009,7 +1010,7 @@ func (k *Kubernetes) GetWatchedNamespaces(ctx context.Context, namespace string) continue } for _, envVar := range container.Env { - if envVar.Name != EverestWatchNamespacesEnvVar { + if envVar.Name != EverestDBNamespacesEnvVar { continue } return strings.Split(envVar.Value, ","), nil diff --git a/pkg/monitoring/deps.go b/pkg/monitoring/deps.go deleted file mode 100644 index 22c72be6..00000000 --- a/pkg/monitoring/deps.go +++ /dev/null @@ -1,42 +0,0 @@ -// percona-everest-cli -// Copyright (C) 2023 Percona LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package monitoring ... -package monitoring - -import ( - "context" - - "github.com/percona/percona-everest-backend/client" -) - -//go:generate ../../bin/mockery --name=everestClientConnector --case=snake --inpackage --testonly - -type everestClientConnector interface { - CreateMonitoringInstance( - ctx context.Context, - body client.CreateMonitoringInstanceJSONRequestBody, - ) (*client.MonitoringInstance, error) - GetMonitoringInstance( - ctx context.Context, - pmmInstanceID string, - ) (*client.MonitoringInstance, error) - ListMonitoringInstances(ctx context.Context) ([]client.MonitoringInstance, error) - - SetKubernetesClusterMonitoring( - ctx context.Context, - body client.SetKubernetesClusterMonitoringJSONRequestBody, - ) error -} diff --git a/pkg/monitoring/enable.go b/pkg/monitoring/enable.go deleted file mode 100644 index 30a6607f..00000000 --- a/pkg/monitoring/enable.go +++ /dev/null @@ -1,427 +0,0 @@ -// percona-everest-cli -// Copyright (C) 2023 Percona LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package monitoring holds the main logic for provision monitoring -package monitoring - -import ( - "context" - "errors" - "fmt" - "net/url" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/percona/percona-everest-backend/client" - "go.uber.org/zap" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/wait" - - "github.com/percona/percona-everest-cli/commands/common" - everestClient "github.com/percona/percona-everest-cli/pkg/everest/client" - "github.com/percona/percona-everest-cli/pkg/install" - "github.com/percona/percona-everest-cli/pkg/kubernetes" -) - -const ( - everestBackendServiceName = "percona-everest-backend" - everestBackendDeploymentName = "percona-everest" - vmOperatorName = "victoriametrics-operator" -) - -// Monitoring implements the logic for provisioning monitoring. -type Monitoring struct { - l *zap.SugaredLogger - - config Config - everestClient everestClientConnector - kubeClient *kubernetes.Kubernetes - - // monitoringInstanceName stores the resolved monitoring instance name. - monitoringInstanceName string -} - -type ( - // monitoringType identifies type of monitoring to be used. - monitoringType string - - // Config stores configuration for the operators. - Config struct { - // Namespace defines the namespace operators shall be installed to. - Namespace string - // SkipWizard skips wizard during installation. - SkipWizard bool `mapstructure:"skip-wizard"` - // KubeconfigPath is a path to a kubeconfig - KubeconfigPath string `mapstructure:"kubeconfig"` - - // EverestToken defines a token to connect to Everest - EverestToken string `mapstructure:"everest-token"` - // EverestURL defines an URL to connect to Everest - EverestURL string `mapstructure:"everest-url"` - - // InstanceName stores monitoring instance name from Everest. - // If provided, the other monitoring configuration is ignored. - InstanceName string `mapstructure:"instance-name"` - // NewInstanceName defines name for a new monitoring instance - // if it's created. - NewInstanceName string `mapstructure:"new-instance-name"` - // Type stores the type of monitoring to be used. - Type monitoringType - // PMM stores configuration for PMM monitoring type. - PMM *PMMConfig - } - - // PMMConfig stores configuration for PMM monitoring type. - PMMConfig struct { - // Endpoint stores URL to PMM. - Endpoint string - // Username stores username for authentication against PMM. - Username string - // Password stores password for authentication against PMM. - Password string - } -) - -// NewMonitoring returns a new Monitoring struct. -func NewMonitoring(c Config, l *zap.SugaredLogger) (*Monitoring, error) { - cli := &Monitoring{ - config: c, - l: l.With("component", "monitoring/enable"), - } - - k, err := kubernetes.New(c.KubeconfigPath, cli.l) - if err != nil { - var u *url.Error - if errors.As(err, &u) { - cli.l.Error("Could not connect to Kubernetes. " + - "Make sure Kubernetes is running and is accessible from this computer/server.") - } - return nil, err - } - cli.kubeClient = k - return cli, nil -} - -// Run runs the operators installation process. -func (m *Monitoring) Run(ctx context.Context) error { - if err := m.populateConfig(ctx); err != nil { - return err - } - if err := m.checkNamespace(ctx); err != nil { - return err - } - if err := m.provisionMonitoring(ctx); err != nil { - return err - } - - return nil -} - -func (m *Monitoring) populateConfig(ctx context.Context) error { - if !m.config.SkipWizard { - if err := m.runEverestWizard(); err != nil { - return err - } - if err := m.runMonitoringWizard(); err != nil { - return err - } - } - m.config.EverestURL = strings.TrimSpace(m.config.EverestURL) - - if err := m.configureEverestConnector(); err != nil { - return err - } - if err := m.checkEverestConnection(ctx); err != nil { - return err - } - - return nil -} - -// checkNamespace provisions a namespace for Everest. -func (m *Monitoring) checkNamespace(ctx context.Context) error { - _, err := m.kubeClient.GetNamespace(ctx, m.config.Namespace) - if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("namespace %s is not found", m.config.Namespace) - } - if err != nil { - return err - } - - _, err = m.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, m.config.Namespace) - if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("no Everest installation exist in the %s namespace. Monitoring can be provisioned into the namespace where everest components are deployed", m.config.Namespace) - } - if err != nil { - return err - } - _, err = m.kubeClient.GetDeployment(ctx, kubernetes.EverestOperatorDeploymentName, m.config.Namespace) - if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("no Everest installation exist in the %s namespace. Monitoring can be provisioned into the namespace where everest components are deployed", m.config.Namespace) - } - return err -} - -func (m *Monitoring) installVMOperator(ctx context.Context) error { - m.l.Infof("Installing %s operator", vmOperatorName) - - params := kubernetes.InstallOperatorRequest{ - Namespace: m.config.Namespace, - Name: vmOperatorName, - OperatorGroup: install.OperatorGroup, - CatalogSource: install.CatalogSource, - CatalogSourceNamespace: install.CatalogSourceNamespace, - Channel: install.VMOperatorChannel, - InstallPlanApproval: v1alpha1.ApprovalManual, - } - - if err := m.kubeClient.InstallOperator(ctx, params); err != nil { - m.l.Errorf("failed installing %s operator", vmOperatorName) - return err - } - m.l.Infof("%s operator has been installed", vmOperatorName) - return nil -} - -func (m *Monitoring) provisionMonitoring(ctx context.Context) error { - l := m.l.With("action", "monitoring") - l.Info("Preparing k8s cluster for monitoring") - if err := m.installVMOperator(ctx); err != nil { - return err - } - if err := m.kubeClient.ProvisionMonitoring(m.config.Namespace); err != nil { - return errors.Join(err, errors.New("could not provision monitoring configuration")) - } - - l.Info("K8s cluster monitoring has been provisioned successfully") - if err := m.resolveMonitoringInstanceName(ctx); err != nil { - return err - } - m.l.Info("Deploying VMAgent to k8s cluster") - if err := m.kubeClient.RestartEverest(ctx, everestBackendServiceName, m.config.Namespace); err != nil { - return err - } - if err := m.kubeClient.WaitForRollout(ctx, everestBackendDeploymentName, m.config.Namespace); err != nil { - return errors.Join(err, errors.New("failed waiting for Everest to be ready")) - } - - if err := m.waitForEverestConnection(ctx); err != nil { - return err - } - - // We retry for a bit since the MonitoringConfig may not be properly - // deployed yet and we get a HTTP 500 in this case. - err := wait.PollUntilContextTimeout(ctx, 3*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) { - m.l.Debug("Trying to enable Kubernetes cluster monitoring") - err := m.everestClient.SetKubernetesClusterMonitoring(ctx, client.KubernetesClusterMonitoring{ - Enable: true, - MonitoringInstanceName: m.monitoringInstanceName, - }) - if err != nil { - m.l.Debug(errors.Join(err, errors.New("could not enable Kubernetes cluster monitoring"))) - return false, nil - } - - return true, nil - }) - if err != nil { - return errors.Join(err, errors.New("could not enable Kubernetes cluster monitoring")) - } - - m.l.Info("VMAgent deployed successfully") - return nil -} - -func (m *Monitoring) waitForEverestConnection(ctx context.Context) error { - sleep := time.Second - for i := 0; i < 3; i++ { - time.Sleep(sleep) - sleep *= 2 - if err := m.checkEverestConnection(ctx); err != nil { - if i != 2 { - continue - } - var u *url.Error - if errors.As(err, &u) { - m.l.Debug(err) - - l := m.l.WithOptions(zap.AddStacktrace(zap.DPanicLevel)) - l.Error("Could not connect to Everest. " + - "Make sure Everest is running and is accessible from this machine.", - ) - return common.ErrExitWithError - } - - return errors.Join(err, errors.New("could not check connection to Everest")) - } - } - return nil -} - -func (m *Monitoring) resolveMonitoringInstanceName(ctx context.Context) error { - if m.config.InstanceName != "" { - i, err := m.everestClient.GetMonitoringInstance(ctx, m.config.InstanceName) - if err != nil { - return errors.Join(err, fmt.Errorf("could not get monitoring instance with name %s from Everest", m.config.InstanceName)) - } - m.monitoringInstanceName = i.Name - return nil - } - - if m.config.NewInstanceName == "" && m.monitoringInstanceName == "" { - return errors.New("new-instance-name is required when creating a new monitoring instance") - } - - err := m.createPMMMonitoringInstance( - ctx, m.config.NewInstanceName, m.config.PMM.Endpoint, - m.config.PMM.Username, m.config.PMM.Password, - ) - if err != nil { - return errors.Join(err, errors.New("could not create a new PMM monitoring instance in Everest")) - } - - m.monitoringInstanceName = m.config.NewInstanceName - - return nil -} - -func (m *Monitoring) createPMMMonitoringInstance(ctx context.Context, name, url, username, password string) error { - _, err := m.everestClient.CreateMonitoringInstance(ctx, client.MonitoringInstanceCreateParams{ - Type: client.MonitoringInstanceCreateParamsTypePmm, - Name: name, - Url: url, - Pmm: &client.PMMMonitoringInstanceSpec{ - User: username, - Password: password, - }, - }) - if err != nil { - return errors.Join(err, errors.New("could not create a new monitoring instance")) - } - - return nil -} - -func (m *Monitoring) configureEverestConnector() error { - e, err := everestClient.NewEverestFromURL(m.config.EverestURL, m.config.EverestToken) - if err != nil { - return err - } - m.everestClient = e - return nil -} - -func (m *Monitoring) runEverestWizard() error { - pURL := &survey.Input{ - Message: "Everest URL endpoint", - Default: m.config.EverestURL, - } - if err := survey.AskOne( - pURL, - &m.config.EverestURL, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - pToken := &survey.Password{Message: "Everest Token"} - if err := survey.AskOne( - pToken, - &m.config.EverestToken, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - return nil -} - -func (m *Monitoring) runMonitoringWizard() error { - if m.config.PMM == nil { - m.config.PMM = &PMMConfig{} - } - pName := &survey.Input{ - Message: "Registered instance name", - } - if err := survey.AskOne( - pName, - &m.config.InstanceName, - ); err != nil { - return err - } - - if m.config.InstanceName == "" { - if err := m.runMonitoringNewURLWizard(); err != nil { - return err - } - } - - return nil -} - -func (m *Monitoring) runMonitoringNewURLWizard() error { - pURL := &survey.Input{ - Message: "PMM URL Endpoint", - Default: m.config.PMM.Endpoint, - } - if err := survey.AskOne( - pURL, - &m.config.PMM.Endpoint, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - m.config.PMM.Endpoint = strings.TrimSpace(m.config.PMM.Endpoint) - - pUser := &survey.Input{ - Message: "Username", - Default: m.config.PMM.Username, - } - if err := survey.AskOne( - pUser, - &m.config.PMM.Username, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - - pPass := &survey.Password{Message: "Password"} - if err := survey.AskOne( - pPass, - &m.config.PMM.Password, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - - pName := &survey.Input{ - Message: "Name for the new monitoring instance", - Default: m.config.NewInstanceName, - } - if err := survey.AskOne( - pName, - &m.config.NewInstanceName, - survey.WithValidator(survey.Required), - ); err != nil { - return err - } - - return nil -} - -func (m *Monitoring) checkEverestConnection(ctx context.Context) error { - _, err := m.everestClient.ListMonitoringInstances(ctx) - return err -} diff --git a/pkg/monitoring/mock_everest_client_connector_test.go b/pkg/monitoring/mock_everest_client_connector_test.go deleted file mode 100644 index d74c4b3f..00000000 --- a/pkg/monitoring/mock_everest_client_connector_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. - -package monitoring - -import ( - context "context" - - client "github.com/percona/percona-everest-backend/client" - mock "github.com/stretchr/testify/mock" -) - -// mockEverestClientConnector is an autogenerated mock type for the everestClientConnector type -type mockEverestClientConnector struct { - mock.Mock -} - -// CreateMonitoringInstance provides a mock function with given fields: ctx, body -func (_m *mockEverestClientConnector) CreateMonitoringInstance(ctx context.Context, body client.MonitoringInstanceCreateParams) (*client.MonitoringInstanceBaseWithName, error) { - ret := _m.Called(ctx, body) - - if len(ret) == 0 { - panic("no return value specified for CreateMonitoringInstance") - } - - var r0 *client.MonitoringInstanceBaseWithName - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, client.MonitoringInstanceCreateParams) (*client.MonitoringInstanceBaseWithName, error)); ok { - return rf(ctx, body) - } - if rf, ok := ret.Get(0).(func(context.Context, client.MonitoringInstanceCreateParams) *client.MonitoringInstanceBaseWithName); ok { - r0 = rf(ctx, body) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*client.MonitoringInstanceBaseWithName) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, client.MonitoringInstanceCreateParams) error); ok { - r1 = rf(ctx, body) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetMonitoringInstance provides a mock function with given fields: ctx, pmmInstanceID -func (_m *mockEverestClientConnector) GetMonitoringInstance(ctx context.Context, pmmInstanceID string) (*client.MonitoringInstanceBaseWithName, error) { - ret := _m.Called(ctx, pmmInstanceID) - - if len(ret) == 0 { - panic("no return value specified for GetMonitoringInstance") - } - - var r0 *client.MonitoringInstanceBaseWithName - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*client.MonitoringInstanceBaseWithName, error)); ok { - return rf(ctx, pmmInstanceID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *client.MonitoringInstanceBaseWithName); ok { - r0 = rf(ctx, pmmInstanceID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*client.MonitoringInstanceBaseWithName) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, pmmInstanceID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListMonitoringInstances provides a mock function with given fields: ctx -func (_m *mockEverestClientConnector) ListMonitoringInstances(ctx context.Context) ([]client.MonitoringInstanceBaseWithName, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for ListMonitoringInstances") - } - - var r0 []client.MonitoringInstanceBaseWithName - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]client.MonitoringInstanceBaseWithName, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []client.MonitoringInstanceBaseWithName); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]client.MonitoringInstanceBaseWithName) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SetKubernetesClusterMonitoring provides a mock function with given fields: ctx, body -func (_m *mockEverestClientConnector) SetKubernetesClusterMonitoring(ctx context.Context, body client.KubernetesClusterMonitoring) error { - ret := _m.Called(ctx, body) - - if len(ret) == 0 { - panic("no return value specified for SetKubernetesClusterMonitoring") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, client.KubernetesClusterMonitoring) error); ok { - r0 = rf(ctx, body) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// newMockEverestClientConnector creates a new instance of mockEverestClientConnector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockEverestClientConnector(t interface { - mock.TestingT - Cleanup(func()) -}, -) *mockEverestClientConnector { - mock := &mockEverestClientConnector{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/monitoring/monitoring_test.go b/pkg/monitoring/monitoring_test.go deleted file mode 100644 index a97418f3..00000000 --- a/pkg/monitoring/monitoring_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// percona-everest-cli -// Copyright (C) 2023 Percona LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package monitoring - -import ( - "context" - "errors" - "strings" - "testing" - - "github.com/percona/percona-everest-backend/client" - mock "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -const ( - iName = "monitoring-instance" -) - -func TestInstall_resolveMonitoringInstanceName(t *testing.T) { - t.Parallel() - - l, err := zap.NewDevelopment() - require.NoError(t, err) - - t.Run("shall work with monitoring instance name", func(t *testing.T) { - t.Parallel() - - m := &mockEverestClientConnector{} - m.Mock.On("GetMonitoringInstance", mock.Anything, "123").Return(&client.MonitoringInstance{Name: iName}, nil) - defer m.AssertExpectations(t) - - o := &Monitoring{ - l: l.Sugar(), - config: Config{ - InstanceName: "123", - }, - everestClient: m, - } - - err := o.resolveMonitoringInstanceName(context.Background()) - require.NoError(t, err) - require.Equal(t, iName, o.monitoringInstanceName) - }) - - t.Run("shall fail with monitoring instance name not found", func(t *testing.T) { - t.Parallel() - - m := &mockEverestClientConnector{} - m.Mock.On("GetMonitoringInstance", mock.Anything, "123").Return(nil, errors.New("not-found")) - defer m.AssertExpectations(t) - - o := &Monitoring{ - l: l.Sugar(), - config: Config{ - InstanceName: "123", - }, - everestClient: m, - } - - err := o.resolveMonitoringInstanceName(context.Background()) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "not-found")) - }) - - t.Run("shall prefer monitoring instance name", func(t *testing.T) { - t.Parallel() - - m := &mockEverestClientConnector{} - m.Mock.On("GetMonitoringInstance", mock.Anything, "123").Return(&client.MonitoringInstance{Name: iName}, nil) - defer m.AssertExpectations(t) - - o := &Monitoring{ - l: l.Sugar(), - config: Config{ - InstanceName: "123", - PMM: &PMMConfig{ - Endpoint: "http://localhost", - Username: "admin", - Password: "admin", - }, - }, - everestClient: m, - } - - err := o.resolveMonitoringInstanceName(context.Background()) - require.NoError(t, err) - require.Equal(t, iName, o.monitoringInstanceName) - }) - - t.Run("shall fail without new instance name defined when creating a new instance", func(t *testing.T) { - t.Parallel() - - m := &mockEverestClientConnector{} - defer m.AssertExpectations(t) - - o := &Monitoring{ - l: l.Sugar(), - config: Config{}, - everestClient: m, - } - - err := o.resolveMonitoringInstanceName(context.Background()) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "new-instance-name is required")) - }) - - t.Run("shall create a new PMM instance", func(t *testing.T) { - t.Parallel() - - m := &mockEverestClientConnector{} - m.Mock.On("CreateMonitoringInstance", mock.Anything, client.MonitoringInstanceCreateParams{ - Type: client.MonitoringInstanceCreateParamsTypePmm, - Name: "new-instance", - Url: "http://monitoring-url", - Pmm: &client.PMMMonitoringInstanceSpec{ - User: "user", - Password: "pass", - }, - }).Return(&client.MonitoringInstance{}, nil) - defer m.AssertExpectations(t) - - o := &Monitoring{ - l: l.Sugar(), - config: Config{ - NewInstanceName: "new-instance", - PMM: &PMMConfig{ - Endpoint: "http://monitoring-url", - Username: "user", - Password: "pass", - }, - }, - everestClient: m, - } - - err := o.resolveMonitoringInstanceName(context.Background()) - require.NoError(t, err) - require.Equal(t, "new-instance", o.monitoringInstanceName) - }) -} diff --git a/pkg/uninstall/uninstall.go b/pkg/uninstall/uninstall.go index e5b29293..d0e4e2fe 100644 --- a/pkg/uninstall/uninstall.go +++ b/pkg/uninstall/uninstall.go @@ -87,7 +87,7 @@ This will uninstall Everest and all monitoring resources deployed by it. All oth if err := u.uninstallK8sResources(ctx); err != nil { return err } - if err := u.kubeClient.DeleteEverest(ctx, install.EverestNamespace); err != nil { + if err := u.kubeClient.DeleteEverest(ctx, install.SystemNamespace); err != nil { return err } @@ -95,23 +95,23 @@ This will uninstall Everest and all monitoring resources deployed by it. All oth } func (u *Uninstall) checkResourcesExist(ctx context.Context) error { - _, err := u.kubeClient.GetNamespace(ctx, install.EverestNamespace) + _, err := u.kubeClient.GetNamespace(ctx, install.SystemNamespace) if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("namespace %s is not found", install.EverestNamespace) + return fmt.Errorf("namespace %s is not found", install.SystemNamespace) } if err != nil && !k8serrors.IsNotFound(err) { return err } - _, err = u.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, install.EverestNamespace) + _, err = u.kubeClient.GetDeployment(ctx, kubernetes.PerconaEverestDeploymentName, install.SystemNamespace) if err != nil && k8serrors.IsNotFound(err) { - return fmt.Errorf("no Everest deployment in %s namespace", install.EverestNamespace) + return fmt.Errorf("no Everest deployment in %s namespace", install.SystemNamespace) } return err } func (u *Uninstall) uninstallK8sResources(ctx context.Context) error { u.l.Info("Deleting all Kubernetes monitoring resources in Kubernetes cluster") - if err := u.kubeClient.DeleteAllMonitoringResources(ctx, install.EverestNamespace); err != nil { + if err := u.kubeClient.DeleteAllMonitoringResources(ctx, install.SystemNamespace); err != nil { return errors.Join(err, errors.New("could not uninstall monitoring resources from the Kubernetes cluster")) } diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index 3dd52b3d..2c94f6f6 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -96,7 +96,7 @@ func (u *Upgrade) Run(ctx context.Context) error { } u.l.Info("Subscriptions have been patched") u.l.Info("Upgrading Everest") - if err := u.kubeClient.InstallEverest(ctx, install.EverestNamespace); err != nil { + if err := u.kubeClient.InstallEverest(ctx, install.SystemNamespace); err != nil { return err } u.l.Info("Everest has been upgraded") @@ -105,7 +105,7 @@ func (u *Upgrade) Run(ctx context.Context) error { func (u *Upgrade) runEverestWizard(ctx context.Context) error { if !u.config.SkipWizard { - namespaces, err := u.kubeClient.GetWatchedNamespaces(ctx, install.EverestNamespace) + namespaces, err := u.kubeClient.GetDBNamespaces(ctx, install.SystemNamespace) if err != nil { return err } From b5de8f200fe8887705fa15045ab8763dac15cfe3 Mon Sep 17 00:00:00 2001 From: Diogo Recharte Date: Wed, 7 Feb 2024 17:00:54 +0000 Subject: [PATCH 3/5] EVEREST-633 Use PGO stable channel (#282) * EVEREST-633 install monitoring stack with install command By moving to a multi-namespace everest-operator installation we configured the operator group of the percona-everest namespace to have N target namespaces. The VM Operator doesn't support multi-namespaces so we can't deploy it to the same namespace as the everest-operator. Therefore we install it onto a separate namespace for managing everything monitoring related. * EVEREST-633 remove monitoring command * EVEREST-633 Update everest-operator go mod * EVEREST-633 Update percona-everest-backend go mod * EVEREST-633 forbid installs in the monitoring namespace * EVEREST-633 use PGO stable channel --- go.mod | 2 +- go.sum | 4 ++-- pkg/install/install.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c2a634a5..dc750ba6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/operator-framework/api v0.22.0 github.com/operator-framework/operator-lifecycle-manager v0.26.0 - github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9 + github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28 github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 diff --git a/go.sum b/go.sum index 3d1257de..bcf8884b 100644 --- a/go.sum +++ b/go.sum @@ -539,8 +539,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9 h1:M8vHkH1ITw2KP/2MZVeDzcrB0jq8ZbuhRB3ttUxhwqU= -github.com/percona/everest-operator v0.6.0-dev1.0.20240207102146-b96be266f4d9/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28 h1:N9dZVyeXzUTK+xRdz9DBcaWj3X6oUPpezEwdH2jT4cg= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901 h1:BDgsZRCjEuxl2/z4yWBqB0s8d20shuIDks7/RVdZiLs= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901/go.mod h1:fZRCMpUqkWlLVdRKqqaj001LoVP2eo6F0ZhoMPeXDng= github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a h1:2CZcbM4NWnKq3/gE5OySzKMiJhjMwARa6tegJ2XIT48= diff --git a/pkg/install/install.go b/pkg/install/install.go index e9b51c69..22d3dfb9 100644 --- a/pkg/install/install.go +++ b/pkg/install/install.go @@ -61,7 +61,7 @@ const ( everestOperatorChannel = "stable-v0" pxcOperatorChannel = "stable-v1" psmdbOperatorChannel = "stable-v1" - pgOperatorChannel = "fast-v2" + pgOperatorChannel = "stable-v2" vmOperatorChannel = "stable-v0" // catalogSourceNamespace is the namespace where the catalog source is installed. From 1aab5f64a10f1b46032172ac842d784eb7806f0b Mon Sep 17 00:00:00 2001 From: Diogo Recharte Date: Wed, 7 Feb 2024 19:40:34 +0000 Subject: [PATCH 4/5] EVEREST-495 Update everest-operator go mod --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dc750ba6..33bb1be0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/operator-framework/api v0.22.0 github.com/operator-framework/operator-lifecycle-manager v0.26.0 - github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28 + github.com/percona/everest-operator v0.6.0-dev1.0.20240207193854-cdd70b8eb1e6 github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 diff --git a/go.sum b/go.sum index bcf8884b..fd751f79 100644 --- a/go.sum +++ b/go.sum @@ -539,8 +539,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28 h1:N9dZVyeXzUTK+xRdz9DBcaWj3X6oUPpezEwdH2jT4cg= -github.com/percona/everest-operator v0.6.0-dev1.0.20240207144724-d5253b875e28/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207193854-cdd70b8eb1e6 h1:leGa/XuWVstdYyj61r92xByjuT52jsbVroHB0fj4j7A= +github.com/percona/everest-operator v0.6.0-dev1.0.20240207193854-cdd70b8eb1e6/go.mod h1:45pGpvWrPy495qiQqxNuOJor4wif+vTTTJP4Qee8qZk= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901 h1:BDgsZRCjEuxl2/z4yWBqB0s8d20shuIDks7/RVdZiLs= github.com/percona/percona-backup-mongodb v1.8.1-0.20230920143330-3b1c2e263901/go.mod h1:fZRCMpUqkWlLVdRKqqaj001LoVP2eo6F0ZhoMPeXDng= github.com/percona/percona-everest-backend v0.5.1-0.20240205094045-e23451782e1a h1:2CZcbM4NWnKq3/gE5OySzKMiJhjMwARa6tegJ2XIT48= From 3b7dacacdd59dbdbf194b17aac4d14308a21091e Mon Sep 17 00:00:00 2001 From: Diogo Recharte Date: Wed, 7 Feb 2024 20:07:44 +0000 Subject: [PATCH 5/5] EVEREST-495 Fix missing monitoring namespace env var --- pkg/install/install.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/install/install.go b/pkg/install/install.go index 22d3dfb9..b467ade5 100644 --- a/pkg/install/install.go +++ b/pkg/install/install.go @@ -80,6 +80,8 @@ const ( SystemNamespace = "percona-everest" // monitoringNamespace is the namespace where the monitoring stack is installed. monitoringNamespace = "percona-everest-monitoring" + // EverestMonitoringNamespaceEnvVar is the name of the environment variable that holds the monitoring namespace. + EverestMonitoringNamespaceEnvVar = "MONITORING_NAMESPACE" ) type ( @@ -486,6 +488,10 @@ func (o *Install) installOperator(ctx context.Context, channel, operatorName, na Name: kubernetes.EverestDBNamespacesEnvVar, Value: strings.Join(o.config.Namespaces, ","), }, + { + Name: EverestMonitoringNamespaceEnvVar, + Value: monitoringNamespace, + }, }, } }