github.com/mattermost/mattermost-server/v5@v5.39.3/store/storetest/settings.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package storetest
     5  
     6  import (
     7  	"database/sql"
     8  	"flag"
     9  	"fmt"
    10  	"net/url"
    11  	"os"
    12  	"path"
    13  
    14  	"github.com/go-sql-driver/mysql"
    15  	_ "github.com/go-sql-driver/mysql"
    16  	_ "github.com/lib/pq"
    17  	"github.com/pkg/errors"
    18  
    19  	"github.com/mattermost/mattermost-server/v5/model"
    20  )
    21  
    22  const (
    23  	defaultMysqlDSN        = "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s&multiStatements=true"
    24  	defaultPostgresqlDSN   = "postgres://mmuser:mostest@localhost:5432/mattermost_test?sslmode=disable&connect_timeout=10"
    25  	defaultMysqlRootPWD    = "mostest"
    26  	defaultMysqlReplicaDSN = "root:mostest@tcp(localhost:3307)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s"
    27  )
    28  
    29  func getEnv(name, defaultValue string) string {
    30  	if value := os.Getenv(name); value != "" {
    31  		return value
    32  	}
    33  	return defaultValue
    34  }
    35  
    36  func log(message string) {
    37  	verbose := false
    38  	if verboseFlag := flag.Lookup("test.v"); verboseFlag != nil {
    39  		verbose = verboseFlag.Value.String() != ""
    40  	}
    41  	if verboseFlag := flag.Lookup("v"); verboseFlag != nil {
    42  		verbose = verboseFlag.Value.String() != ""
    43  	}
    44  
    45  	if verbose {
    46  		fmt.Println(message)
    47  	}
    48  }
    49  
    50  // MySQLSettings returns the database settings to connect to the MySQL unittesting database.
    51  // The database name is generated randomly and must be created before use.
    52  func MySQLSettings(withReplica bool) *model.SqlSettings {
    53  	dsn := getEnv("TEST_DATABASE_MYSQL_DSN", defaultMysqlDSN)
    54  	cfg, err := mysql.ParseDSN(dsn)
    55  	if err != nil {
    56  		panic("failed to parse dsn " + dsn + ": " + err.Error())
    57  	}
    58  
    59  	cfg.DBName = "db" + model.NewId()
    60  
    61  	mySQLSettings := databaseSettings("mysql", cfg.FormatDSN())
    62  
    63  	if withReplica {
    64  		mySQLSettings.DataSourceReplicas = []string{getEnv("TEST_DATABASE_MYSQL_REPLICA_DSN", defaultMysqlReplicaDSN)}
    65  	}
    66  
    67  	return mySQLSettings
    68  }
    69  
    70  // PostgresSQLSettings returns the database settings to connect to the PostgreSQL unittesting database.
    71  // The database name is generated randomly and must be created before use.
    72  func PostgreSQLSettings() *model.SqlSettings {
    73  	dsn := getEnv("TEST_DATABASE_POSTGRESQL_DSN", defaultPostgresqlDSN)
    74  	dsnUrl, err := url.Parse(dsn)
    75  	if err != nil {
    76  		panic("failed to parse dsn " + dsn + ": " + err.Error())
    77  	}
    78  
    79  	// Generate a random database name
    80  	dsnUrl.Path = "db" + model.NewId()
    81  
    82  	return databaseSettings("postgres", dsnUrl.String())
    83  }
    84  
    85  func mySQLRootDSN(dsn string) string {
    86  	rootPwd := getEnv("TEST_DATABASE_MYSQL_ROOT_PASSWD", defaultMysqlRootPWD)
    87  	cfg, err := mysql.ParseDSN(dsn)
    88  	if err != nil {
    89  		panic("failed to parse dsn " + dsn + ": " + err.Error())
    90  	}
    91  
    92  	cfg.User = "root"
    93  	cfg.Passwd = rootPwd
    94  	cfg.DBName = "mysql"
    95  
    96  	return cfg.FormatDSN()
    97  }
    98  
    99  func postgreSQLRootDSN(dsn string) string {
   100  	dsnUrl, err := url.Parse(dsn)
   101  	if err != nil {
   102  		panic("failed to parse dsn " + dsn + ": " + err.Error())
   103  	}
   104  
   105  	// // Assume the unittesting database has the same password.
   106  	// password := ""
   107  	// if dsnUrl.User != nil {
   108  	// 	password, _ = dsnUrl.User.Password()
   109  	// }
   110  
   111  	// dsnUrl.User = url.UserPassword("", password)
   112  	dsnUrl.Path = "postgres"
   113  
   114  	return dsnUrl.String()
   115  }
   116  
   117  func mySQLDSNDatabase(dsn string) string {
   118  	cfg, err := mysql.ParseDSN(dsn)
   119  	if err != nil {
   120  		panic("failed to parse dsn " + dsn + ": " + err.Error())
   121  	}
   122  
   123  	return cfg.DBName
   124  }
   125  
   126  func postgreSQLDSNDatabase(dsn string) string {
   127  	dsnUrl, err := url.Parse(dsn)
   128  	if err != nil {
   129  		panic("failed to parse dsn " + dsn + ": " + err.Error())
   130  	}
   131  
   132  	return path.Base(dsnUrl.Path)
   133  }
   134  
   135  func databaseSettings(driver, dataSource string) *model.SqlSettings {
   136  	settings := &model.SqlSettings{
   137  		DriverName:                  &driver,
   138  		DataSource:                  &dataSource,
   139  		DataSourceReplicas:          []string{},
   140  		DataSourceSearchReplicas:    []string{},
   141  		MaxIdleConns:                new(int),
   142  		ConnMaxLifetimeMilliseconds: new(int),
   143  		ConnMaxIdleTimeMilliseconds: new(int),
   144  		MaxOpenConns:                new(int),
   145  		Trace:                       model.NewBool(false),
   146  		AtRestEncryptKey:            model.NewString(model.NewRandomString(32)),
   147  		QueryTimeout:                new(int),
   148  	}
   149  	*settings.MaxIdleConns = 10
   150  	*settings.ConnMaxLifetimeMilliseconds = 3600000
   151  	*settings.ConnMaxIdleTimeMilliseconds = 300000
   152  	*settings.MaxOpenConns = 100
   153  	*settings.QueryTimeout = 60
   154  
   155  	return settings
   156  }
   157  
   158  // execAsRoot executes the given sql as root against the testing database
   159  func execAsRoot(settings *model.SqlSettings, sqlCommand string) error {
   160  	var dsn string
   161  	var driver = *settings.DriverName
   162  
   163  	switch driver {
   164  	case model.DATABASE_DRIVER_MYSQL:
   165  		dsn = mySQLRootDSN(*settings.DataSource)
   166  	case model.DATABASE_DRIVER_POSTGRES:
   167  		dsn = postgreSQLRootDSN(*settings.DataSource)
   168  	default:
   169  		return fmt.Errorf("unsupported driver %s", driver)
   170  	}
   171  
   172  	db, err := sql.Open(driver, dsn)
   173  	if err != nil {
   174  		return errors.Wrapf(err, "failed to connect to %s database as root", driver)
   175  	}
   176  	defer db.Close()
   177  	if _, err = db.Exec(sqlCommand); err != nil {
   178  		return errors.Wrapf(err, "failed to execute `%s` against %s database as root", sqlCommand, driver)
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func replaceMySQLDatabaseName(dsn, newDBName string) string {
   185  	cfg, err := mysql.ParseDSN(dsn)
   186  	if err != nil {
   187  		panic("failed to parse dsn " + dsn + ": " + err.Error())
   188  	}
   189  	cfg.DBName = newDBName
   190  	return cfg.FormatDSN()
   191  }
   192  
   193  // MakeSqlSettings creates a randomly named database and returns the corresponding sql settings
   194  func MakeSqlSettings(driver string, withReplica bool) *model.SqlSettings {
   195  	var settings *model.SqlSettings
   196  	var dbName string
   197  
   198  	switch driver {
   199  	case model.DATABASE_DRIVER_MYSQL:
   200  		settings = MySQLSettings(withReplica)
   201  		dbName = mySQLDSNDatabase(*settings.DataSource)
   202  		newDSRs := []string{}
   203  		for _, dataSource := range settings.DataSourceReplicas {
   204  			newDSRs = append(newDSRs, replaceMySQLDatabaseName(dataSource, dbName))
   205  		}
   206  		settings.DataSourceReplicas = newDSRs
   207  	case model.DATABASE_DRIVER_POSTGRES:
   208  		settings = PostgreSQLSettings()
   209  		dbName = postgreSQLDSNDatabase(*settings.DataSource)
   210  	default:
   211  		panic("unsupported driver " + driver)
   212  	}
   213  
   214  	if err := execAsRoot(settings, "CREATE DATABASE "+dbName); err != nil {
   215  		panic("failed to create temporary database " + dbName + ": " + err.Error())
   216  	}
   217  
   218  	switch driver {
   219  	case model.DATABASE_DRIVER_MYSQL:
   220  		if err := execAsRoot(settings, "GRANT ALL PRIVILEGES ON "+dbName+".* TO 'mmuser'"); err != nil {
   221  			panic("failed to grant mmuser permission to " + dbName + ":" + err.Error())
   222  		}
   223  	case model.DATABASE_DRIVER_POSTGRES:
   224  		if err := execAsRoot(settings, "GRANT ALL PRIVILEGES ON DATABASE \""+dbName+"\" TO mmuser"); err != nil {
   225  			panic("failed to grant mmuser permission to " + dbName + ":" + err.Error())
   226  		}
   227  	default:
   228  		panic("unsupported driver " + driver)
   229  	}
   230  
   231  	log("Created temporary " + driver + " database " + dbName)
   232  
   233  	return settings
   234  }
   235  
   236  func CleanupSqlSettings(settings *model.SqlSettings) {
   237  	var driver = *settings.DriverName
   238  	var dbName string
   239  
   240  	switch driver {
   241  	case model.DATABASE_DRIVER_MYSQL:
   242  		dbName = mySQLDSNDatabase(*settings.DataSource)
   243  	case model.DATABASE_DRIVER_POSTGRES:
   244  		dbName = postgreSQLDSNDatabase(*settings.DataSource)
   245  	default:
   246  		panic("unsupported driver " + driver)
   247  	}
   248  
   249  	if err := execAsRoot(settings, "DROP DATABASE "+dbName); err != nil {
   250  		panic("failed to drop temporary database " + dbName + ": " + err.Error())
   251  	}
   252  
   253  	log("Dropped temporary database " + dbName)
   254  }