package sqlstore import ( "fmt" "net/url" "os" "path" "path/filepath" "strings" "testing" "time" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/setting" "github.com/go-sql-driver/mysql" "github.com/go-xorm/xorm" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" _ "github.com/grafana/grafana/pkg/tsdb/mssql" ) var ( x *xorm.Engine dialect migrator.Dialect sqlog log.Logger = log.New("sqlstore") ) func init() { registry.Register(®istry.Descriptor{ Name: "SqlStore", Instance: &SqlStore{}, InitPriority: registry.High, }) } type SqlStore struct { Cfg *setting.Cfg `inject:""` dbCfg DatabaseConfig engine *xorm.Engine log log.Logger skipEnsureAdmin bool } func (ss *SqlStore) Init() error { ss.log = log.New("sqlstore") ss.readConfig() engine, err := ss.getEngine() if err != nil { return fmt.Errorf("Fail to connect to database: %v", err) } ss.engine = engine // temporarily still set global var x = engine dialect = migrator.NewDialect(x) migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) if err := migrator.Start(); err != nil { return fmt.Errorf("Migration failed err: %v", err) } // Init repo instances annotations.SetRepository(&SqlAnnotationRepo{}) // ensure admin user if ss.skipEnsureAdmin { return nil } return ss.ensureAdminUser() } func (ss *SqlStore) ensureAdminUser() error { systemUserCountQuery := m.GetSystemUserCountStatsQuery{} if err := bus.Dispatch(&systemUserCountQuery); err != nil { fmt.Errorf("Could not determine if admin user exists: %v", err) } if systemUserCountQuery.Result.Count > 0 { return nil } cmd := m.CreateUserCommand{} cmd.Login = setting.AdminUser cmd.Email = setting.AdminUser + "@localhost" cmd.Password = setting.AdminPassword cmd.IsAdmin = true if err := bus.Dispatch(&cmd); err != nil { return fmt.Errorf("Failed to create admin user: %v", err) } ss.log.Info("Created default admin user: %v", setting.AdminUser) return nil } func (ss *SqlStore) buildConnectionString() (string, error) { cnnstr := ss.dbCfg.ConnectionString // special case used by integration tests if cnnstr != "" { return cnnstr, nil } switch ss.dbCfg.Type { case migrator.MYSQL: protocol := "tcp" if strings.HasPrefix(ss.dbCfg.Host, "/") { protocol = "unix" } cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&allowNativePasswords=true", ss.dbCfg.User, ss.dbCfg.Pwd, protocol, ss.dbCfg.Host, ss.dbCfg.Name) if ss.dbCfg.SslMode == "true" || ss.dbCfg.SslMode == "skip-verify" { tlsCert, err := makeCert("custom", ss.dbCfg) if err != nil { return "", err } mysql.RegisterTLSConfig("custom", tlsCert) cnnstr += "&tls=custom" } case migrator.POSTGRES: var host, port = "127.0.0.1", "5432" fields := strings.Split(ss.dbCfg.Host, ":") if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 { host = fields[0] } if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 { port = fields[1] } if ss.dbCfg.Pwd == "" { ss.dbCfg.Pwd = "''" } if ss.dbCfg.User == "" { ss.dbCfg.User = "''" } cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", ss.dbCfg.User, ss.dbCfg.Pwd, host, port, ss.dbCfg.Name, ss.dbCfg.SslMode, ss.dbCfg.ClientCertPath, ss.dbCfg.ClientKeyPath, ss.dbCfg.CaCertPath) case migrator.SQLITE: // special case for tests if !filepath.IsAbs(ss.dbCfg.Path) { ss.dbCfg.Path = filepath.Join(setting.DataPath, ss.dbCfg.Path) } os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm) cnnstr = "file:" + ss.dbCfg.Path + "?cache=shared&mode=rwc" default: return "", fmt.Errorf("Unknown database type: %s", ss.dbCfg.Type) } return cnnstr, nil } func (ss *SqlStore) getEngine() (*xorm.Engine, error) { connectionString, err := ss.buildConnectionString() if err != nil { return nil, err } sqlog.Info("Connecting to DB", "dbtype", ss.dbCfg.Type) engine, err := xorm.NewEngine(ss.dbCfg.Type, connectionString) if err != nil { return nil, err } engine.SetMaxOpenConns(ss.dbCfg.MaxOpenConn) engine.SetMaxIdleConns(ss.dbCfg.MaxIdleConn) engine.SetConnMaxLifetime(time.Second * time.Duration(ss.dbCfg.ConnMaxLifetime)) // configure sql logging debugSql := ss.Cfg.Raw.Section("database").Key("log_queries").MustBool(false) if !debugSql { engine.SetLogger(&xorm.DiscardLogger{}) } else { engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm"))) engine.ShowSQL(true) engine.ShowExecTime(true) } return engine, nil } func (ss *SqlStore) readConfig() { sec := ss.Cfg.Raw.Section("database") cfgURL := sec.Key("url").String() if len(cfgURL) != 0 { dbURL, _ := url.Parse(cfgURL) ss.dbCfg.Type = dbURL.Scheme ss.dbCfg.Host = dbURL.Host pathSplit := strings.Split(dbURL.Path, "/") if len(pathSplit) > 1 { ss.dbCfg.Name = pathSplit[1] } userInfo := dbURL.User if userInfo != nil { ss.dbCfg.User = userInfo.Username() ss.dbCfg.Pwd, _ = userInfo.Password() } } else { ss.dbCfg.Type = sec.Key("type").String() ss.dbCfg.Host = sec.Key("host").String() ss.dbCfg.Name = sec.Key("name").String() ss.dbCfg.User = sec.Key("user").String() ss.dbCfg.ConnectionString = sec.Key("connection_string").String() ss.dbCfg.Pwd = sec.Key("password").String() } ss.dbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0) ss.dbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(2) ss.dbCfg.ConnMaxLifetime = sec.Key("conn_max_lifetime").MustInt(14400) ss.dbCfg.SslMode = sec.Key("ssl_mode").String() ss.dbCfg.CaCertPath = sec.Key("ca_cert_path").String() ss.dbCfg.ClientKeyPath = sec.Key("client_key_path").String() ss.dbCfg.ClientCertPath = sec.Key("client_cert_path").String() ss.dbCfg.ServerCertName = sec.Key("server_cert_name").String() ss.dbCfg.Path = sec.Key("path").MustString("data/grafana.db") } func InitTestDB(t *testing.T) *SqlStore { sqlstore := &SqlStore{} sqlstore.skipEnsureAdmin = true dbType := migrator.SQLITE // environment variable present for test db? if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { dbType = db } // set test db config sqlstore.Cfg = setting.NewCfg() sec, _ := sqlstore.Cfg.Raw.NewSection("database") sec.NewKey("type", dbType) switch dbType { case "mysql": sec.NewKey("connection_string", sqlutil.TestDB_Mysql.ConnStr) case "postgres": sec.NewKey("connection_string", sqlutil.TestDB_Postgres.ConnStr) default: sec.NewKey("connection_string", sqlutil.TestDB_Sqlite3.ConnStr) } // need to get engine to clean db before we init engine, err := xorm.NewEngine(dbType, sec.Key("connection_string").String()) if err != nil { t.Fatalf("Failed to init test database: %v", err) } dialect = migrator.NewDialect(engine) if err := dialect.CleanDB(); err != nil { t.Fatalf("Failed to clean test db %v", err) } if err := sqlstore.Init(); err != nil { t.Fatalf("Failed to init test database: %v", err) } //// sqlstore.engine.DatabaseTZ = time.UTC //// sqlstore.engine.TZLocation = time.UTC return sqlstore } func IsTestDbMySql() bool { if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { return db == migrator.MYSQL } return false } func IsTestDbPostgres() bool { if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { return db == migrator.POSTGRES } return false } type DatabaseConfig struct { Type, Host, Name, User, Pwd, Path, SslMode string CaCertPath string ClientKeyPath string ClientCertPath string ServerCertName string ConnectionString string MaxOpenConn int MaxIdleConn int ConnMaxLifetime int }