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 }