From 8db648386e8b27421ef3b932d445329c0ca1125f Mon Sep 17 00:00:00 2001 From: Marco Cadetg Date: Mon, 6 May 2024 12:31:48 +0200 Subject: [PATCH] adding postgres e2e test --- .../relationaldatabaseprovider_types.go | 8 +- ...lagoon.sh_relationaldatabaseproviders.yaml | 18 +- ...pha1_relationaldatabaseprovider_mysql.yaml | 2 +- .../controller/databaserequest_controller.go | 6 +- .../databaserequest_controller_test.go | 2 +- .../relationaldatabaseprovider_controller.go | 56 ++-- ...ationaldatabaseprovider_controller_test.go | 4 +- internal/database/database.go | 180 ++++++----- test/e2e/e2e_test.go | 287 ++++++++++-------- test/e2e/testdata/mysql-client-pod.yaml | 2 +- test/e2e/testdata/mysql.yaml | 4 +- test/utils/utils.go | 50 ++- 12 files changed, 343 insertions(+), 276 deletions(-) diff --git a/api/v1alpha1/relationaldatabaseprovider_types.go b/api/v1alpha1/relationaldatabaseprovider_types.go index a237586..1dd9219 100644 --- a/api/v1alpha1/relationaldatabaseprovider_types.go +++ b/api/v1alpha1/relationaldatabaseprovider_types.go @@ -63,10 +63,10 @@ type Connection struct { type RelationalDatabaseProviderSpec struct { //+kubebuilder:required //+kubebuilder:validation:Required - //+kubebuilder:validation:Enum=mysql;postgresql - // Kind is the kind of the relational database provider - // it can be either "mysql" or "postgresql" - Kind string `json:"kind"` + //+kubebuilder:validation:Enum=mysql;postgres + // Type is the type of the relational database provider + // it can be either "mysql" or "postgres" + Type string `json:"type"` //+kubebuilder:required //+kubebuilder:validation:Required diff --git a/config/crd/bases/crd.lagoon.sh_relationaldatabaseproviders.yaml b/config/crd/bases/crd.lagoon.sh_relationaldatabaseproviders.yaml index 601e3a3..b6295d5 100644 --- a/config/crd/bases/crd.lagoon.sh_relationaldatabaseproviders.yaml +++ b/config/crd/bases/crd.lagoon.sh_relationaldatabaseproviders.yaml @@ -101,14 +101,6 @@ spec: type: object minItems: 1 type: array - kind: - description: |- - Kind is the kind of the relational database provider - it can be either "mysql" or "postgresql" - enum: - - mysql - - postgresql - type: string scope: default: development description: |- @@ -119,10 +111,18 @@ spec: - development - custom type: string + type: + description: |- + Type is the type of the relational database provider + it can be either "mysql" or "postgres" + enum: + - mysql + - postgres + type: string required: - connections - - kind - scope + - type type: object status: description: RelationalDatabaseProviderStatus defines the observed state diff --git a/config/samples/crd_v1alpha1_relationaldatabaseprovider_mysql.yaml b/config/samples/crd_v1alpha1_relationaldatabaseprovider_mysql.yaml index a2a9167..f27e361 100644 --- a/config/samples/crd_v1alpha1_relationaldatabaseprovider_mysql.yaml +++ b/config/samples/crd_v1alpha1_relationaldatabaseprovider_mysql.yaml @@ -6,7 +6,7 @@ metadata: app.kubernetes.io/managed-by: kustomize name: relationaldatabaseprovider-mysql-sample spec: - kind: mysql + type: mysql scope: development connections: - name: primary-test-mysql-connection diff --git a/internal/controller/databaserequest_controller.go b/internal/controller/databaserequest_controller.go index 9b3f2bc..043794e 100644 --- a/internal/controller/databaserequest_controller.go +++ b/internal/controller/databaserequest_controller.go @@ -574,7 +574,7 @@ func (r *DatabaseRequestReconciler) relationalDatabaseOperation( } conn := reldbConn{ - kind: databaseRequest.Spec.Type, + dbType: databaseRequest.Spec.Type, name: connection.Name, hostname: connection.Hostname, username: connection.Username, @@ -679,7 +679,7 @@ func (r *DatabaseRequestReconciler) findRelationalDatabaseProvider( var provider *crdv1alpha1.RelationalDatabaseProvider var connName string for _, dbProvider := range dbProviders.Items { - if dbProvider.Spec.Scope == databaseRequest.Spec.Scope { + if dbProvider.Spec.Scope == databaseRequest.Spec.Scope && dbProvider.Spec.Type == databaseRequest.Spec.Type { log.FromContext(ctx).Info("Found provider", "provider", dbProvider.Name) for _, dbConnection := range dbProvider.Spec.Connections { if dbConnection.Enabled { @@ -700,7 +700,7 @@ func (r *DatabaseRequestReconciler) findRelationalDatabaseProvider( } conn := reldbConn{ - kind: databaseRequest.Spec.Type, + dbType: databaseRequest.Spec.Type, name: dbConnection.Name, hostname: dbConnection.Hostname, username: dbConnection.Username, diff --git a/internal/controller/databaserequest_controller_test.go b/internal/controller/databaserequest_controller_test.go index f7a3c25..d858f74 100644 --- a/internal/controller/databaserequest_controller_test.go +++ b/internal/controller/databaserequest_controller_test.go @@ -75,7 +75,7 @@ var _ = Describe("DatabaseRequest Controller", func() { Name: dbMySQLProviderResource, }, Spec: crdv1alpha1.RelationalDatabaseProviderSpec{ - Kind: "mysql", + Type: "mysql", Scope: "development", Connections: []crdv1alpha1.Connection{ { diff --git a/internal/controller/relationaldatabaseprovider_controller.go b/internal/controller/relationaldatabaseprovider_controller.go index e35c4d7..3daa6dc 100644 --- a/internal/controller/relationaldatabaseprovider_controller.go +++ b/internal/controller/relationaldatabaseprovider_controller.go @@ -49,7 +49,7 @@ var ( Name: "relationaldatabaseprovider_reconcile_error_total", Help: "The total number of reconciled relational database providers errors", }, - []string{"kind", "name", "scope", "error"}, + []string{"type", "name", "scope", "error"}, ) // promRelationalDatabaseProviderStatus is the gauge for the relational database provider status @@ -58,7 +58,7 @@ var ( Name: "relationaldatabaseprovider_status", Help: "The status of the relational database provider", }, - []string{"kind", "name", "scope"}, + []string{"type", "name", "scope"}, ) // promRelationalDatabaseProviderConnectionVersion is the gauge for the relational database provider connection version @@ -67,7 +67,7 @@ var ( Name: "relationaldatabaseprovider_connection_version", Help: "The version of the relational database provider connection", }, - []string{"kind", "name", "scope", "hostname", "username", "version"}, + []string{"type", "name", "scope", "hostname", "username", "version"}, ) ) @@ -103,7 +103,7 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re "", req.Name, "", "get-relationaldbprovider").Inc() return ctrl.Result{}, err } - logger = logger.WithValues("kind", instance.Spec.Kind, "scope", instance.Spec.Scope) + logger = logger.WithValues("type", instance.Spec.Type, "scope", instance.Spec.Scope) if instance.DeletionTimestamp != nil && !instance.DeletionTimestamp.IsZero() { // The object is being deleted // To be discussed whether we need to delete all the database requests using this provider... @@ -148,15 +148,15 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re return r.handleError( ctx, instance, - fmt.Sprintf("%s-empty-password", instance.Spec.Kind), + fmt.Sprintf("%s-empty-password", instance.Spec.Type), fmt.Errorf( "%s connection secret %s in namespace %s has empty password", - instance.Spec.Kind, secret.Name, secret.Namespace, + instance.Spec.Type, secret.Name, secret.Namespace, ), ) } conns = append(conns, reldbConn{ - kind: instance.Spec.Kind, + dbType: instance.Spec.Type, name: conn.Name, hostname: conn.Hostname, replicaHostnames: conn.ReplicaHostnames, @@ -172,8 +172,8 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re return r.handleError( ctx, instance, - fmt.Sprintf("%s-unique-name-error", instance.Spec.Kind), - fmt.Errorf("%s database connections must have unique names", instance.Spec.Kind), + fmt.Sprintf("%s-unique-name-error", instance.Spec.Type), + fmt.Errorf("%s database connections must have unique names", instance.Spec.Type), ) } @@ -184,7 +184,7 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re // make a ping to the database to check if it's up and running and we can connect to it // if not, we should return an error and set the status to 0 // Note we could periodically check the status of the database and update the status accordingly... - if err := r.RelDBClient.Ping(ctx, conn.getDSN(), instance.Spec.Kind); err != nil { + if err := r.RelDBClient.Ping(ctx, conn.getDSN(), instance.Spec.Type); err != nil { errors = append(errors, err) dbStatus = append(dbStatus, crdv1alpha1.ConnectionStatus{ Name: conn.name, @@ -194,7 +194,7 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re }) continue } - version, err := r.RelDBClient.Version(ctx, conn.getDSN(), instance.Spec.Kind) + version, err := r.RelDBClient.Version(ctx, conn.getDSN(), instance.Spec.Type) if err != nil { errors = append(errors, err) dbStatus = append(dbStatus, crdv1alpha1.ConnectionStatus{ @@ -207,7 +207,7 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re } // check if the database is initialized - err = r.RelDBClient.Initialize(ctx, conn.getDSN(), instance.Spec.Kind) + err = r.RelDBClient.Initialize(ctx, conn.getDSN(), instance.Spec.Type) if err != nil { errors = append(errors, err) dbStatus = append(dbStatus, crdv1alpha1.ConnectionStatus{ @@ -220,7 +220,7 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re } promRelationalDatabaseProviderConnectionVersion.WithLabelValues( - instance.Spec.Kind, req.Name, instance.Spec.Scope, conn.hostname, conn.username, version).Set(1) + instance.Spec.Type, req.Name, instance.Spec.Scope, conn.hostname, conn.username, version).Set(1) dbStatus = append(dbStatus, crdv1alpha1.ConnectionStatus{ Name: conn.name, Hostname: conn.hostname, @@ -241,16 +241,16 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re return r.handleError( ctx, instance, - fmt.Sprintf("%s-connection-error", instance.Spec.Kind), - fmt.Errorf("failed to connect to any of the %s databases: %v", instance.Spec.Kind, errors), + fmt.Sprintf("%s-connection-error", instance.Spec.Type), + fmt.Errorf("failed to connect to any of the %s databases: %v", instance.Spec.Type, errors), ) } if !foundEnabledDatabase { return r.handleError( ctx, instance, - fmt.Sprintf("%s-connection-not-any-enabled", instance.Spec.Kind), - fmt.Errorf("no enabled working %s database found", instance.Spec.Kind), + fmt.Sprintf("%s-connection-not-any-enabled", instance.Spec.Type), + fmt.Errorf("no enabled working %s database found", instance.Spec.Type), ) } @@ -264,13 +264,13 @@ func (r *RelationalDatabaseProviderReconciler) Reconcile(ctx context.Context, re // update the status if err := r.Status().Update(ctx, instance); err != nil { promRelationalDatabaseProviderReconcileErrorCounter.WithLabelValues( - instance.Spec.Kind, req.Name, instance.Spec.Scope, "update-status").Inc() - promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Kind, req.Name, instance.Spec.Scope).Set(0) + instance.Spec.Type, req.Name, instance.Spec.Scope, "update-status").Inc() + promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Type, req.Name, instance.Spec.Scope).Set(0) return ctrl.Result{}, err } r.Recorder.Event(instance, "Normal", "Reconciled", "RelationalDatabaseProvider reconciled") - promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Kind, req.Name, instance.Spec.Scope).Set(1) + promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Type, req.Name, instance.Spec.Scope).Set(1) return ctrl.Result{}, nil } @@ -282,8 +282,8 @@ func (r *RelationalDatabaseProviderReconciler) handleError( err error, ) (ctrl.Result, error) { promRelationalDatabaseProviderReconcileErrorCounter.WithLabelValues( - instance.Spec.Kind, instance.Name, instance.Spec.Scope, promErr).Inc() - promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Kind, instance.Name, instance.Spec.Scope).Set(0) + instance.Spec.Type, instance.Name, instance.Spec.Scope, promErr).Inc() + promRelationalDatabaseProviderStatus.WithLabelValues(instance.Spec.Type, instance.Name, instance.Spec.Scope).Set(0) r.Recorder.Event(instance, v1.EventTypeWarning, errTypeToEventReason(promErr), err.Error()) // set the status condition to false @@ -297,7 +297,7 @@ func (r *RelationalDatabaseProviderReconciler) handleError( // update the status if err := r.Status().Update(ctx, instance); err != nil { promRelationalDatabaseProviderReconcileErrorCounter.WithLabelValues( - instance.Spec.Kind, instance.Name, instance.Spec.Scope, "update-status").Inc() + instance.Spec.Type, instance.Name, instance.Spec.Scope, "update-status").Inc() log.FromContext(ctx).Error(err, "Failed to update status") } @@ -306,7 +306,7 @@ func (r *RelationalDatabaseProviderReconciler) handleError( // reldbConn is the connection to a MySQL or PostgreSQL database type reldbConn struct { - kind string + dbType string name string hostname string replicaHostnames []string @@ -318,12 +318,12 @@ type reldbConn struct { // getDSN constructs the DSN string for the MySQL or PostgreSQL connection. func (rc *reldbConn) getDSN() string { - if rc.kind == "mysql" { + if rc.dbType == "mysql" { return fmt.Sprintf("%s:%s@tcp(%s:%d)/", rc.username, rc.password, rc.hostname, rc.port) - } else if rc.kind == "postgresql" { + } else if rc.dbType == "postgres" { return fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", - rc.hostname, rc.port, rc.username, rc.password, rc.name, + "host=%s port=%d user=%s password=%s sslmode=disable", + rc.hostname, rc.port, rc.username, rc.password, ) } else { return "" diff --git a/internal/controller/relationaldatabaseprovider_controller_test.go b/internal/controller/relationaldatabaseprovider_controller_test.go index a21d434..d3f5248 100644 --- a/internal/controller/relationaldatabaseprovider_controller_test.go +++ b/internal/controller/relationaldatabaseprovider_controller_test.go @@ -46,7 +46,7 @@ var _ = Describe("RelationalDatabaseProvider Controller", func() { relationaldatabaseprovider := &crdv1alpha1.RelationalDatabaseProvider{} BeforeEach(func() { - By("creating the custom resource for the Kind RelationalDatabaseProvider") + By("creating the custom resource for the RelationalDatabaseProvider") secret := &v1.Secret{} err := k8sClient.Get(ctx, types.NamespacedName{ Name: "test-rel-db-provider-secret", @@ -74,7 +74,7 @@ var _ = Describe("RelationalDatabaseProvider Controller", func() { Namespace: "default", }, Spec: crdv1alpha1.RelationalDatabaseProviderSpec{ - Kind: "mysql", + Type: "mysql", Scope: "custom", Connections: []crdv1alpha1.Connection{ { diff --git a/internal/database/database.go b/internal/database/database.go index 43515c2..2bcff05 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -7,6 +7,7 @@ import ( "math/rand" _ "github.com/go-sql-driver/mysql" + "github.com/lib/pq" _ "github.com/lib/pq" "sigs.k8s.io/controller-runtime/pkg/log" @@ -19,9 +20,9 @@ const ( maxPasswordLength = 24 // maxDatabaseNameLength MySQL and PostgreSQL database name must use valid characters and be at most 63 characters long maxDatabaseNameLength = 63 - // mysql is the kind for MySQL + // mysql is the type for MySQL mysql = "mysql" - // postgres is the kind for PostgreSQL + // postgres is the type for PostgreSQL postgres = "postgres" ) @@ -39,36 +40,36 @@ type RelationalDatabaseInfo struct { // Note that the implementation of this interface should be idempotent. type RelationalDatabaseInterface interface { // GetConnection returns a connection to the relational database - GetConnection(ctx context.Context, dsn string, kind string) (*sql.DB, error) + GetConnection(ctx context.Context, dsn string, dbType string) (*sql.DB, error) // Ping pings the relational database - Ping(ctx context.Context, dsn string, kind string) error + Ping(ctx context.Context, dsn string, dbType string) error // Version returns the version of the relational database - Version(ctx context.Context, dsn string, kind string) (string, error) + Version(ctx context.Context, dsn string, dbType string) (string, error) // Load of the database measured in MB of data and index size. // Higher values indicate more data and indexes. - Load(ctx context.Context, dsn string, kind string) (int, error) + Load(ctx context.Context, dsn string, dbType string) (int, error) // Initialize initializes the relational database // This is used by the database {MySQL,PostgreSQL} provider to initialize the relational database. // It does setup the dbass_controller database. // This function is idempotent and can be called multiple times without side effects. - Initialize(ctx context.Context, dsn string, kind string) error + Initialize(ctx context.Context, dsn string, dbType string) error // CreateDatabase creates a database in the relational database if it does not exist. // It also creates a user and grants the user permissions on the database. // This function is idempotent and can be called multiple times without side effects. // returns the database name, username, and password - CreateDatabase(ctx context.Context, dsn, name, namespace, kind string) (RelationalDatabaseInfo, error) + CreateDatabase(ctx context.Context, dsn, name, namespace, dbType string) (RelationalDatabaseInfo, error) // DropDatabase drops a database in the MySQL or PostgreSQL database if it exists. // This function is idempotent and can be called multiple times without side effects. - DropDatabase(ctx context.Context, dsn, name, namespace, kind string) error + DropDatabase(ctx context.Context, dsn, name, namespace, dbType string) error // GetDatabase returns the database name, username, and password for the given name and namespace. - GetDatabase(ctx context.Context, dsn, name, namespace, kind string) (RelationalDatabaseInfo, error) + GetDatabase(ctx context.Context, dsn, name, namespace, dbType string) (RelationalDatabaseInfo, error) } // RelationalDatabaseImpl is the implementation of the RelationalDatabaseInterface @@ -87,46 +88,48 @@ func New() *RelationalDatabaseImpl { } // GetConnection returns a connection to the MySQL or PostgreSQL database -func (ri *RelationalDatabaseImpl) GetConnection(ctx context.Context, dsn string, kind string) (*sql.DB, error) { +func (ri *RelationalDatabaseImpl) GetConnection(ctx context.Context, dsn string, dbType string) (*sql.DB, error) { if db, ok := ri.connectionCache[dsn]; ok { return db, nil } - db, err := sql.Open(kind, dsn) + + log.FromContext(ctx).Info("Opening new database connection", "dbType", dbType) + db, err := sql.Open(dbType, dsn) if err != nil { - return nil, fmt.Errorf("failed to open %s database: %w", kind, err) + return nil, fmt.Errorf("failed to open %s database: %w", dbType, err) } - log.FromContext(ctx).Info("Opening %s database connection", "kind", kind) + ri.connectionCache[dsn] = db return db, nil } // Ping pings the relational database -func (ri *RelationalDatabaseImpl) Ping(ctx context.Context, dsn string, kind string) error { - log.FromContext(ctx).Info("Pinging database", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) +func (ri *RelationalDatabaseImpl) Ping(ctx context.Context, dsn string, dbType string) error { + log.FromContext(ctx).Info("Pinging database", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return fmt.Errorf("ping failed to open %s database: %w", kind, err) + return fmt.Errorf("ping failed to open %s database: %w", dbType, err) } if err := db.PingContext(ctx); err != nil { - return fmt.Errorf("failed to ping %s database: %w", kind, err) + return fmt.Errorf("failed to ping %s database: %w", dbType, err) } return nil } // Version returns the version of the MySQL or PostgreSQL database -func (ri *RelationalDatabaseImpl) Version(ctx context.Context, dsn string, kind string) (string, error) { - log.FromContext(ctx).Info("Getting database version", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) +func (ri *RelationalDatabaseImpl) Version(ctx context.Context, dsn string, dbType string) (string, error) { + log.FromContext(ctx).Info("Getting database version", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return "", fmt.Errorf("version failed to open %s database: %w", kind, err) + return "", fmt.Errorf("version failed to open %s database: %w", dbType, err) } var version string err = db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version) if err != nil { - return "", fmt.Errorf("version failed to get %s database version: %w", kind, err) + return "", fmt.Errorf("version failed to get %s database version: %w", dbType, err) } return version, nil @@ -134,26 +137,26 @@ func (ri *RelationalDatabaseImpl) Version(ctx context.Context, dsn string, kind // Load returns the load of the MySQL or PostgreSQL database measured in MB of data and index size. // Note it doesn't include CPU or memory usage which could be obtained from other sources. -func (ri *RelationalDatabaseImpl) Load(ctx context.Context, dsn string, kind string) (int, error) { - log.FromContext(ctx).Info("Getting database load", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) +func (ri *RelationalDatabaseImpl) Load(ctx context.Context, dsn string, dbType string) (int, error) { + log.FromContext(ctx).Info("Getting database load", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return 0, fmt.Errorf("load failed to open %s database: %w", kind, err) + return 0, fmt.Errorf("load failed to open %s database: %w", dbType, err) } var totalLoad float64 - if kind == mysql { + if dbType == mysql { err = db.QueryRowContext(ctx, "SELECT data_length + index_length FROM information_schema.tables").Scan(&totalLoad) if err != nil { - return 0, fmt.Errorf("load failed to get %s database load: %w", kind, err) + return 0, fmt.Errorf("load failed to get %s database load: %w", dbType, err) } - } else if kind == postgres { + } else if dbType == postgres { err = db.QueryRowContext(ctx, "SELECT pg_database_size(current_database())").Scan(&totalLoad) if err != nil { - return 0, fmt.Errorf("load failed to get %s database load: %w", kind, err) + return 0, fmt.Errorf("load failed to get %s database load: %w", dbType, err) } } else { - return 0, fmt.Errorf("load failed to get %s database load: unsupported kind", kind) + return 0, fmt.Errorf("load failed to get %s database load: unsupported dbType", dbType) } // convert bytes to MB totalLoadMB := totalLoad / 1024 / 1024 @@ -164,22 +167,22 @@ func (ri *RelationalDatabaseImpl) Load(ctx context.Context, dsn string, kind str // This is used by the database {MySQL,PostgreSQL} provider to initialize the MySQL or PostgreSQL database. // It does setup the dbass_controller database. // This function is idempotent and can be called multiple times without side effects. -func (ri *RelationalDatabaseImpl) Initialize(ctx context.Context, dsn string, kind string) error { - log.FromContext(ctx).Info("Initializing database", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) +func (ri *RelationalDatabaseImpl) Initialize(ctx context.Context, dsn string, dbType string) error { + log.FromContext(ctx).Info("Initializing database", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return fmt.Errorf("initialize failed to open %s database: %w", kind, err) + return fmt.Errorf("initialize failed to open %s database: %w", dbType, err) } - if kind == mysql { + if dbType == mysql { _, err = db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS dbaas_controller") if err != nil { - return fmt.Errorf("initialize failed to create %s database: %w", kind, err) + return fmt.Errorf("initialize failed to create %s database: %w", dbType, err) } _, err = db.ExecContext(ctx, "USE dbaas_controller") if err != nil { - return fmt.Errorf("initialize failed to use %s database: %w", kind, err) + return fmt.Errorf("initialize failed to use %s database: %w", dbType, err) } _, err = db.ExecContext(ctx, ` @@ -191,17 +194,14 @@ func (ri *RelationalDatabaseImpl) Initialize(ctx context.Context, dsn string, ki password VARCHAR(255) NOT NULL, dbname VARCHAR(255) NOT NULL UNIQUE, CONSTRAINT unique_name_namespace UNIQUE (name, namespace) - ) ENGINE=InnoDB;`) + ) ENGINE=InnoDB`) if err != nil { - return fmt.Errorf("initialize failed to create %s table: %w", kind, err) + return fmt.Errorf("initialize failed to create %s table: %w", dbType, err) } - } else if kind == postgres { - _, err := db.ExecContext( - ctx, - "SELECT 'CREATE DATABASE dbaas_controller' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'dbaas_controller')", // nolint: lll - ) + } else if dbType == postgres { + _, err := db.ExecContext(ctx, "CREATE SCHEMA IF NOT EXISTS dbaas_controller") if err != nil { - return fmt.Errorf("initialize failed to create %s database: %w", kind, err) + return fmt.Errorf("initialize failed to create %s database: %w", dbType, err) } _, err = db.ExecContext(ctx, ` @@ -211,14 +211,14 @@ func (ri *RelationalDatabaseImpl) Initialize(ctx context.Context, dsn string, ki namespace VARCHAR(255) NOT NULL, username VARCHAR(16) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, - dbname VARCHAR(255) NOT NULL UNIQUE + dbname VARCHAR(255) NOT NULL UNIQUE, CONSTRAINT unique_name_namespace UNIQUE (name, namespace) - );`) + )`) if err != nil { - return fmt.Errorf("initialize failed to create %s table: %w", kind, err) + return fmt.Errorf("initialize failed to create %s table: %w", dbType, err) } } else { - return fmt.Errorf("initialize failed to initialize %s database: unsupported kind", kind) + return fmt.Errorf("initialize failed to initialize %s database: unsupported dbType", dbType) } return nil @@ -228,32 +228,32 @@ func (ri *RelationalDatabaseImpl) Initialize(ctx context.Context, dsn string, ki func (ri *RelationalDatabaseImpl) CreateDatabase( ctx context.Context, dsn, name, namespace string, - kind string, + dbType string, ) (RelationalDatabaseInfo, error) { - log.FromContext(ctx).Info("Creating database", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) + log.FromContext(ctx).Info("Creating database", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return RelationalDatabaseInfo{}, fmt.Errorf("create database failed to open %s database: %w", kind, err) + return RelationalDatabaseInfo{}, fmt.Errorf("create database failed to open %s database: %w", dbType, err) } var info RelationalDatabaseInfo - if kind == mysql { + if dbType == mysql { info, err = ri.databaseInfoMySQL(ctx, dsn, name, namespace) if err != nil { - return info, fmt.Errorf("create %s database failed to get database info: %w", kind, err) + return info, fmt.Errorf("create %s database failed to get database info: %w", dbType, err) } // Create the database _, err = db.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", info.Dbname)) if err != nil { return info, fmt.Errorf( - "create %s database error in creating the database `%s`: %w", kind, info.Dbname, err) + "create %s database error in creating the database `%s`: %w", dbType, info.Dbname, err) } // Create the user and grant permissions // Use prepared statements to avoid SQL injection vulnerabilities. _, err = db.ExecContext( ctx, fmt.Sprintf("CREATE USER IF NOT EXISTS '%s'@'%%' IDENTIFIED BY '%s'", info.Username, info.Password)) if err != nil { - return info, fmt.Errorf("create %s database error creating user `%s`: %w", kind, info.Username, err) + return info, fmt.Errorf("create %s database error creating user `%s`: %w", dbType, info.Username, err) } _, err = db.ExecContext(ctx, fmt.Sprintf( @@ -264,28 +264,25 @@ func (ri *RelationalDatabaseImpl) CreateDatabase( if err != nil { return info, fmt.Errorf( "create %s database error granting privileges to user `%s` on database `%s`: %w", - kind, info.Username, info.Dbname, err) + dbType, info.Username, info.Dbname, err) } _, err = db.ExecContext(ctx, "FLUSH PRIVILEGES") if err != nil { - return info, fmt.Errorf("create %s database error flushing privileges: %w", kind, err) + return info, fmt.Errorf("create %s database error flushing privileges: %w", dbType, err) } - } else if kind == postgres { + } else if dbType == postgres { info, err = ri.databaseInfoPostgreSQL(ctx, dsn, name, namespace) if err != nil { - return info, fmt.Errorf("create database failed to get %s database info: %w", kind, err) + return info, fmt.Errorf("create database failed to get %s database info: %w", dbType, err) } // Create the database - _, err = db.Exec( - fmt.Sprintf( - "SELECT 'CREATE DATABASE \"%s\"' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '%s')", - info.Dbname, info.Dbname, - ), - ) - if err != nil { + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE \"%s\"", info.Dbname)) + if pqErr, ok := err.(*pq.Error); !ok || ok && pqErr.Code != "42P04" { + // either the error is not a pq.Error or it is a pq.Error but not a duplicate_database error + // 42P04 is the error code for duplicate_database return info, fmt.Errorf( - "create %s database error in creating the database `%s`: %w", kind, info.Dbname, err) + "create %s database error in creating the database `%s`: %w", dbType, info.Dbname, err) } // Check if user exists and create or update the user @@ -293,7 +290,7 @@ func (ri *RelationalDatabaseImpl) CreateDatabase( err = db.QueryRow(fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s'", info.Username)).Scan(&userExists) if err != nil && err != sql.ErrNoRows { return info, fmt.Errorf( - "create %s database error in check if user exists in database `%s`: %w", kind, info.Dbname, err) + "create %s database error in check if user exists in database `%s`: %w", dbType, info.Dbname, err) } if userExists == 0 { @@ -301,7 +298,7 @@ func (ri *RelationalDatabaseImpl) CreateDatabase( _, err = db.Exec(fmt.Sprintf("CREATE USER \"%s\" WITH ENCRYPTED PASSWORD '%s'", info.Username, info.Password)) if err != nil { return info, fmt.Errorf( - "create %s database error in create user in database `%s`: %w", kind, info.Dbname, err) + "create %s database error in create user in database `%s`: %w", dbType, info.Dbname, err) } } @@ -309,25 +306,26 @@ func (ri *RelationalDatabaseImpl) CreateDatabase( _, err = db.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE \"%s\" TO \"%s\"", info.Dbname, info.Username)) if err != nil { return info, fmt.Errorf( - "create %s database error in grant privileges in database `%s`: %w", kind, info.Dbname, err) + "create %s database error in grant privileges in database `%s`: %w", dbType, info.Dbname, err) } } else { - return RelationalDatabaseInfo{}, fmt.Errorf("create database failed to create %s database: unsupported kind", kind) + return RelationalDatabaseInfo{}, fmt.Errorf( + "create database failed to create %s database: unsupported dbType", dbType) } return info, nil } // DropDatabase drops a database in the MySQL or PostgreSQL database if it exists. -func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, namespace, kind string) error { - log.FromContext(ctx).Info("Dropping database", "kind", kind) - db, err := ri.GetConnection(ctx, dsn, kind) +func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, namespace, dbType string) error { + log.FromContext(ctx).Info("Dropping database", "dbType", dbType) + db, err := ri.GetConnection(ctx, dsn, dbType) if err != nil { - return fmt.Errorf("drop database failed to open %s database: %w", kind, err) + return fmt.Errorf("drop database failed to open %s database: %w", dbType, err) } info := RelationalDatabaseInfo{} - if kind == mysql { + if dbType == mysql { info, err = ri.databaseInfoMySQL(ctx, dsn, name, namespace) if err != nil { return fmt.Errorf("drop database failed to get database info: %w", err) @@ -335,7 +333,7 @@ func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, n // Drop the database _, err = db.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", info.Dbname)) if err != nil { - return fmt.Errorf("drop database failed to drop %s database: %w", kind, err) + return fmt.Errorf("drop database failed to drop %s database: %w", dbType, err) } // Drop the user _, err = db.ExecContext(ctx, fmt.Sprintf("DROP USER IF EXISTS '%s'@'%%'", info.Username)) @@ -347,7 +345,7 @@ func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, n if err != nil { return fmt.Errorf("drop database failed to flush privileges: %w", err) } - } else if kind == postgres { + } else if dbType == postgres { info, err = ri.databaseInfoPostgreSQL(ctx, dsn, name, namespace) if err != nil { return fmt.Errorf("drop database failed to get database info: %w", err) @@ -360,12 +358,12 @@ func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, n ), ) if err != nil { - return fmt.Errorf("drop database failed to disconnect users from %s database: %w", kind, err) + return fmt.Errorf("drop database failed to disconnect users from %s database: %w", dbType, err) } // Drop the database _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS \"%s\"", info.Dbname)) if err != nil { - return fmt.Errorf("drop database failed to drop %s database: %w", kind, err) + return fmt.Errorf("drop database failed to drop %s database: %w", dbType, err) } // Drop the user _, err = db.Exec(fmt.Sprintf("DROP USER IF EXISTS \"%s\"", info.Username)) @@ -373,7 +371,7 @@ func (ri *RelationalDatabaseImpl) DropDatabase(ctx context.Context, dsn, name, n return fmt.Errorf("drop database failed to drop user: %w", err) } } else { - return fmt.Errorf("drop database failed to drop %s database: unsupported kind", kind) + return fmt.Errorf("drop database failed to drop %s database: unsupported dbType", dbType) } return nil } @@ -494,13 +492,13 @@ func (ri *RelationalDatabaseImpl) databaseInfoPostgreSQL( // GetDatabase returns the database name, username, and password for the given name and namespace. func (ri *RelationalDatabaseImpl) GetDatabase( ctx context.Context, - dsn, name, namespace, kind string, + dsn, name, namespace, dbType string, ) (RelationalDatabaseInfo, error) { - log.FromContext(ctx).Info("Getting database", "kind", kind, "name", name, "namespace", namespace) - if kind == "mysql" { + log.FromContext(ctx).Info("Getting database", "dbType", dbType, "name", name, "namespace", namespace) + if dbType == "mysql" { return ri.databaseInfoMySQL(ctx, dsn, name, namespace) - } else if kind == "postgres" { + } else if dbType == "postgres" { return ri.databaseInfoPostgreSQL(ctx, dsn, name, namespace) } - return RelationalDatabaseInfo{}, fmt.Errorf("get database failed to get %s database: unsupported kind", kind) + return RelationalDatabaseInfo{}, fmt.Errorf("get database failed to get %s database: unsupported dbType", dbType) } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 5b20921..72f16ce 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -37,8 +37,8 @@ var _ = Describe("controller", Ordered, func() { By("installing the cert-manager") Expect(utils.InstallCertManager()).To(Succeed()) - By("installing MySQL pod") - Expect(utils.InstallMySQL()).To(Succeed()) + By("installing relational databases pods") + Expect(utils.InstallRelationalDatabases()).To(Succeed()) By("creating manager namespace") cmd := exec.Command("kubectl", "create", "ns", namespace) @@ -53,51 +53,55 @@ var _ = Describe("controller", Ordered, func() { utils.UninstallCertManager() By("removing the RelationalDatabaseProvider resource") - // we enforce the deletion by removing the finalizer - cmd := exec.Command( - "kubectl", - "patch", - "relationaldatabaseprovider", - "relationaldatabaseprovider-mysql-sample", - "-p", - `{"metadata":{"finalizers":[]}}`, - "--type=merge", - ) - _, _ = utils.Run(cmd) - - cmd = exec.Command( - "kubectl", "delete", "--force", "relationaldatabaseprovider", "relationaldatabaseprovider-mysql-sample") - _, _ = utils.Run(cmd) - + for _, name := range []string{"mysql", "postgres"} { + cmd := exec.Command( + "kubectl", + "patch", + "relationaldatabaseprovider", + fmt.Sprintf("relationaldatabaseprovider-%s-sample", name), + "-p", + `{"metadata":{"finalizers":[]}}`, + "--type=merge", + ) + _, _ = utils.Run(cmd) + cmd = exec.Command( + "kubectl", "delete", "--force", "relationaldatabaseprovider", fmt.Sprintf( + "relationaldatabaseprovider-%s-sample", name)) + _, _ = utils.Run(cmd) + } By("removing the DatabaseRequest resource") - // we enforce the deletion by removing the finalizer - cmd = exec.Command( - "kubectl", - "patch", - "databaserequest", - "databaserequest-mysql-sample", - "-p", - `{"metadata":{"finalizers":[]}}`, - "--type=merge", - ) - _, _ = utils.Run(cmd) - cmd = exec.Command("kubectl", "delete", "--force", "databaserequest", "databaserequest-mysql-sample") - _, _ = utils.Run(cmd) - + for _, name := range []string{"mysql", "postgres"} { + cmd := exec.Command( + "kubectl", + "patch", + "databaserequest", + fmt.Sprintf("databaserequest-%s-sample", name), + "-p", + `{"metadata":{"finalizers":[]}}`, + "--type=merge", + ) + _, _ = utils.Run(cmd) + cmd = exec.Command( + "kubectl", "delete", "--force", "databaserequest", fmt.Sprintf( + "databaserequest-%s-sample", name)) + _, _ = utils.Run(cmd) + } By("removing manager namespace") - cmd = exec.Command("kubectl", "delete", "ns", namespace) + cmd := exec.Command("kubectl", "delete", "ns", namespace) _, _ = utils.Run(cmd) - By("uninstalling MySQL pod") - utils.UninstallMySQLPod() + By("uninstalling relational databases pods") + utils.UninstallRelationalDatabases() By("removing service and secret") - cmd = exec.Command( - "kubectl", "delete", "service", "-n", "default", "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample") - _, _ = utils.Run(cmd) - cmd = exec.Command( - "kubectl", "delete", "secret", "-n", "default", "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample") - _, _ = utils.Run(cmd) + for _, name := range []string{"mysql", "postgres"} { + cmd = exec.Command( + "kubectl", "delete", "service", "-n", "default", "-l", "app.kubernetes.io/instance=databaserequest-"+name+"-sample") + _, _ = utils.Run(cmd) + cmd = exec.Command( + "kubectl", "delete", "secret", "-n", "default", "-l", "app.kubernetes.io/instance=databaserequest-"+name+"-sample") + _, _ = utils.Run(cmd) + } }) Context("Operator", func() { @@ -163,98 +167,115 @@ var _ = Describe("controller", Ordered, func() { } EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed()) - By("creating a RelationalDatabaseProvider resource") - cmd = exec.Command("kubectl", "apply", "-f", "config/samples/crd_v1alpha1_relationaldatabaseprovider_mysql.yaml") - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("validating that the RelationalDatabaseProvider resource is created") - cmd = exec.Command( - "kubectl", - "wait", - "--for=condition=Ready", - "relationaldatabaseprovider", - "relationaldatabaseprovider-mysql-sample", - "--timeout=60s", - ) - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("creating a DatabaseRequest resource") - cmd = exec.Command("kubectl", "apply", "-f", "config/samples/crd_v1alpha1_databaserequest_mysql.yaml") - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("validating that the DatabaseRequest resource is created") - cmd = exec.Command( - "kubectl", - "wait", - "--for=condition=Ready", - "databaserequest", - "databaserequest-mysql-sample", - "--timeout=60s", - ) - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - // verify that the service and secret got created - By("validating that the service is created") - cmd = exec.Command( - "kubectl", - "get", - "service", - "-n", "default", - "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample", - ) - serviceOutput, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - serviceNames := utils.GetNonEmptyLines(string(serviceOutput)) - ExpectWithOffset(1, serviceNames).Should(HaveLen(2)) - ExpectWithOffset(1, serviceNames[1]).Should(ContainSubstring("first-mysql-db")) - - By("validating that the secret is created") - cmd = exec.Command( - "kubectl", - "get", - "secret", - "-n", "default", - "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample", - ) - secretOutput, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - secretNames := utils.GetNonEmptyLines(string(secretOutput)) - ExpectWithOffset(1, secretNames).Should(HaveLen(2)) - - By("deleting the DatabaseRequest resource the database is getting deprovisioned") - cmd = exec.Command("kubectl", "delete", "databaserequest", "databaserequest-mysql-sample") - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("validating that the service is deleted") - cmd = exec.Command( - "kubectl", - "get", - "service", - "-n", "default", - "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample", - ) - serviceOutput, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - serviceNames = utils.GetNonEmptyLines(string(serviceOutput)) - ExpectWithOffset(1, serviceNames).Should(HaveLen(1)) - - By("validating that the secret is deleted") - cmd = exec.Command( - "kubectl", - "get", - "secret", - "-n", "default", - "-l", "app.kubernetes.io/instance=databaserequest-mysql-sample", - ) - secretOutput, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - secretNames = utils.GetNonEmptyLines(string(secretOutput)) - ExpectWithOffset(1, secretNames).Should(HaveLen(1)) + for _, name := range []string{"mysql", "postgres"} { + By("creating a RelationalDatabaseProvider resource") + cmd = exec.Command( + "kubectl", + "apply", + "-f", + fmt.Sprintf("config/samples/crd_v1alpha1_relationaldatabaseprovider_%s.yaml", name), + ) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("validating that the RelationalDatabaseProvider resource is created") + cmd = exec.Command( + "kubectl", + "wait", + "--for=condition=Ready", + "relationaldatabaseprovider", + fmt.Sprintf("relationaldatabaseprovider-%s-sample", name), + "--timeout=60s", + ) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("creating a DatabaseRequest resource") + cmd = exec.Command( + "kubectl", + "apply", + "-f", + fmt.Sprintf("config/samples/crd_v1alpha1_databaserequest_%s.yaml", name), + ) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("validating that the DatabaseRequest resource is created") + cmd = exec.Command( + "kubectl", + "wait", + "--for=condition=Ready", + "databaserequest", + fmt.Sprintf("databaserequest-%s-sample", name), + "--timeout=60s", + ) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + // verify that the service and secret got created + By("validating that the service is created") + cmd = exec.Command( + "kubectl", + "get", + "service", + "-n", "default", + "-l", fmt.Sprintf("app.kubernetes.io/instance=databaserequest-%s-sample", name), + ) + serviceOutput, err := utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + serviceNames := utils.GetNonEmptyLines(string(serviceOutput)) + ExpectWithOffset(1, serviceNames).Should(HaveLen(2)) + ExpectWithOffset(1, serviceNames[1]).Should(ContainSubstring(fmt.Sprintf("first-%s-db", name))) + + By("validating that the secret is created") + cmd = exec.Command( + "kubectl", + "get", + "secret", + "-n", "default", + "-l", fmt.Sprintf("app.kubernetes.io/instance=databaserequest-%s-sample", name), + ) + secretOutput, err := utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + secretNames := utils.GetNonEmptyLines(string(secretOutput)) + ExpectWithOffset(1, secretNames).Should(HaveLen(2)) + + By("deleting the DatabaseRequest resource the database is getting deprovisioned") + cmd = exec.Command( + "kubectl", + "delete", + "databaserequest", + fmt.Sprintf("databaserequest-%s-sample", name), + ) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("validating that the service is deleted") + cmd = exec.Command( + "kubectl", + "get", + "service", + "-n", "default", + "-l", fmt.Sprintf("app.kubernetes.io/instance=databaserequest-%s-sample", name), + ) + serviceOutput, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + serviceNames = utils.GetNonEmptyLines(string(serviceOutput)) + ExpectWithOffset(1, serviceNames).Should(HaveLen(1)) + + By("validating that the secret is deleted") + cmd = exec.Command( + "kubectl", + "get", + "secret", + "-n", "default", + "-l", fmt.Sprintf("app.kubernetes.io/instance=databaserequest-%s-sample", name), + ) + secretOutput, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + secretNames = utils.GetNonEmptyLines(string(secretOutput)) + ExpectWithOffset(1, secretNames).Should(HaveLen(1)) + } // TODO(marco): maybe add a test connecting to the mysql database... diff --git a/test/e2e/testdata/mysql-client-pod.yaml b/test/e2e/testdata/mysql-client-pod.yaml index 1fc3a17..0bb69e6 100644 --- a/test/e2e/testdata/mysql-client-pod.yaml +++ b/test/e2e/testdata/mysql-client-pod.yaml @@ -13,4 +13,4 @@ spec: command: ["sh", "-c"] args: - | - mysql -h mysql-service.mysql -uroot -pe2e-test-password -e "CREATE DATABASE IF NOT EXISTS seed-database;" \ No newline at end of file + mysql -h mysql-service.mysql -uroot -pe2e-mysql-password -e "CREATE DATABASE IF NOT EXISTS seed-database;" \ No newline at end of file diff --git a/test/e2e/testdata/mysql.yaml b/test/e2e/testdata/mysql.yaml index c2af64d..e3e0f39 100644 --- a/test/e2e/testdata/mysql.yaml +++ b/test/e2e/testdata/mysql.yaml @@ -18,7 +18,7 @@ spec: # out of the box env: - name: MYSQL_ROOT_PASSWORD - value: "e2e-test-password" + value: "e2e-mysql-password" ports: - containerPort: 3306 name: mysql @@ -43,4 +43,4 @@ metadata: namespace: mysql type: Opaque data: - password: ZTJlLXRlc3QtcGFzc3dvcmQ= \ No newline at end of file + password: ZTJlLW15c3FsLXBhc3N3b3Jk \ No newline at end of file diff --git a/test/utils/utils.go b/test/utils/utils.go index c66eac2..849a244 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -33,13 +33,61 @@ const ( certmanagerVersion = "v1.5.3" certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml" - mysqlYaml = "test/e2e/testdata/mysql.yaml" + mysqlYaml = "test/e2e/testdata/mysql.yaml" + postgresYaml = "test/e2e/testdata/postgres.yaml" ) func warnError(err error) { fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) } +// InstallRelationalDatabases installs both MySQL and PostgreSQL pods to be used for testing. +func InstallRelationalDatabases() error { + dir, err := GetProjectDir() + if err != nil { + return err + } + errChan := make(chan error, 2) + for _, yaml := range []string{mysqlYaml, postgresYaml} { + cmd := exec.Command("kubectl", "apply", "-f", yaml) + cmd.Dir = dir + fmt.Fprintf(GinkgoWriter, "running: %s in directory: %s\n", strings.Join(cmd.Args, " "), dir) + go func() { + _, err := Run(cmd) + errChan <- err + }() + } + for i := 0; i < 2; i++ { + if err := <-errChan; err != nil { + return err + } + } + return nil +} + +// UninstallRelationalDatabases uninstalls both MySQL and PostgreSQL pods. +func UninstallRelationalDatabases() { + dir, err := GetProjectDir() + if err != nil { + warnError(err) + } + errChan := make(chan error, 2) + for _, yaml := range []string{mysqlYaml, postgresYaml} { + cmd := exec.Command("kubectl", "delete", "-f", yaml) + cmd.Dir = dir + fmt.Fprintf(GinkgoWriter, "running: %s in directory: %s\n", strings.Join(cmd.Args, " "), dir) + go func() { + _, err := Run(cmd) + errChan <- err + }() + } + for i := 0; i < 2; i++ { + if err := <-errChan; err != nil { + warnError(err) + } + } +} + // InstallMySQL installs a MySQL pod to be used for testing. func InstallMySQL() error { dir, err := GetProjectDir()