Index: go/database/database.go |
diff --git a/go/database/database.go b/go/database/database.go |
index 4161ccbbb1a0ae746f2cfebff77ec6aa3bc9fe68..19ae99257d08a7b7a226f11753179e9db9bf74fc 100644 |
--- a/go/database/database.go |
+++ b/go/database/database.go |
@@ -1,39 +1,123 @@ |
package database |
import ( |
+ "bufio" |
"database/sql" |
"fmt" |
+ "os" |
"time" |
+ "strings" |
+ |
_ "github.com/go-sql-driver/mysql" |
"github.com/golang/glog" |
- _ "github.com/mattn/go-sqlite3" |
+ "skia.googlesource.com/buildbot.git/go/metadata" |
"skia.googlesource.com/buildbot.git/go/util" |
) |
+const ( |
+ // Template for database address given a hostname and port number. |
+ DB_ADDR_TMPL = "tcp(%s:%s)" |
+ |
+ // Template for DB connection strings. |
+ DB_CONN_TMPL = "%s:%s@%s/%s?parseTime=true" |
+ |
+ // Local test database. |
+ TEST_DB_HOST = "" |
+ TEST_USER_ROOT = "test_root" |
+ TEST_PASSWORD = "" |
+ |
+ // Name of the root user. |
+ USER_ROOT = "root" |
+ |
+ // Name of the readwrite user. |
+ USER_RW = "readwrite" |
+ |
+ // Key of the password for the readwrite user. |
+ RW_METADATA_KEY = "readwrite" |
+) |
+ |
+// AddrFromHostPort takes a host and port and returns a database address. |
+func AddrFromHostPort(host, port string) string { |
+ return fmt.Sprintf(DB_ADDR_TMPL, host, port) |
+} |
+ |
// Config information to create a database connection. |
type DatabaseConfig struct { |
MySQLString string |
- SQLiteFilePath string |
MigrationSteps []MigrationStep |
} |
+// NewDatabaseConfig constructs a DatabaseConfig from the given options. |
+func NewDatabaseConfig(user, password, host, database string, m []MigrationStep) *DatabaseConfig { |
+ return &DatabaseConfig{ |
+ MySQLString: fmt.Sprintf(DB_CONN_TMPL, user, password, host, database), |
+ MigrationSteps: m, |
+ } |
+} |
+ |
+// LocalDatabaseConfig returns a DatabaseConfig appropriate for local use. |
+func LocalDatabaseConfig(database string, m []MigrationStep) *DatabaseConfig { |
+ return NewDatabaseConfig(USER_RW, TEST_PASSWORD, TEST_DB_HOST, database, m) |
+} |
+ |
+// LocalDatabaseConfig returns a DatabaseConfig appropriate use in a local |
+// testing-only database. |
+func LocalTestDatabaseConfig(database string, m []MigrationStep) *DatabaseConfig { |
+ return NewDatabaseConfig(USER_RW, TEST_PASSWORD, TEST_DB_HOST, database, m) |
+} |
+ |
+// LocalDatabaseConfig returns a DatabaseConfig appropriate use in a local |
+// testing-only database with root access. |
+func LocalTestRootDatabaseConfig(database string, m []MigrationStep) *DatabaseConfig { |
+ return NewDatabaseConfig(TEST_USER_ROOT, TEST_PASSWORD, TEST_DB_HOST, database, m) |
+} |
+ |
+// ProdDatabaseConfig returns a DatabaseConfig appropriate for running in |
+// production. |
+func ProdDatabaseConfig(host, database string, m []MigrationStep) *DatabaseConfig { |
+ // First, get the password from the metadata server. |
+ // See https://developers.google.com/compute/docs/metadata#custom. |
+ password, err := metadata.Get(RW_METADATA_KEY) |
+ if err != nil { |
+ glog.Fatalf("Failed to find metadata. Use 'local' flag when running locally.") |
+ } |
+ return NewDatabaseConfig(USER_RW, password, host, database, m) |
+} |
+ |
+// ResolveCustomMySQLString determines whether a password was entered as part |
+// of the given string, prompts for the password if necessary, and then returns |
+// a DatabaseConfig. |
+func ResolveCustomMySQLString(mySQLConnStr string, m []MigrationStep) (*DatabaseConfig, error) { |
+ useMySQLConnStr := mySQLConnStr |
+ if strings.Contains(useMySQLConnStr, "%s") { |
+ // Assume the missing part of the string is a password, prompt |
+ // and replace. |
+ reader := bufio.NewReader(os.Stdin) |
+ fmt.Print("Enter password for MySQL: ") |
+ password, err := reader.ReadString('\n') |
+ if err != nil { |
+ return nil, fmt.Errorf("Failed to get password: %v", err) |
+ } |
+ useMySQLConnStr = fmt.Sprintf(useMySQLConnStr, strings.TrimRight(password, "\n")) |
+ } |
+ return &DatabaseConfig{ |
+ MySQLString: useMySQLConnStr, |
+ MigrationSteps: m, |
+ }, nil |
+} |
+ |
// Single step to migrated from one database version to the next and back. |
type MigrationStep struct { |
- MySQLUp []string |
- MySQLDown []string |
- SQLiteUp []string |
- SQLiteDown []string |
+ MySQLUp []string |
+ MySQLDown []string |
} |
// Database handle to send queries to the underlying database. |
type VersionedDB struct { |
- // Database intance that is either backed by SQLite or MySQl. |
+ // Database intance that is backed by MySQL. |
DB *sql.DB |
- // Keeps track if we are connected to MySQL or SQLite |
- IsMySQL bool |
- |
// List of migration steps for this database. |
migrationSteps []MigrationStep |
} |
@@ -46,33 +130,20 @@ func NewVersionedDB(conf *DatabaseConfig) *VersionedDB { |
// This is for testing only. In production we get the relevant information |
// from the metadata server. |
var err error |
- var isMySQL = true |
var DB *sql.DB = nil |
- if conf.MySQLString != "" { |
- glog.Infoln("Opening SQL database.") |
- if DB, err = sql.Open("mysql", conf.MySQLString); err == nil { |
- glog.Infoln("Sending Ping.") |
- err = DB.Ping() |
- } |
+ glog.Infoln("Opening SQL database.") |
+ if DB, err = sql.Open("mysql", conf.MySQLString); err == nil { |
+ glog.Infoln("Sending Ping.") |
+ err = DB.Ping() |
+ } |
- if err != nil { |
- glog.Fatalln("Failed to open connection to SQL server:", err) |
- } |
- } else { |
- // Open a local SQLite database instead. |
- glog.Infof("Opening local sqlite database at: %s", conf.SQLiteFilePath) |
- // Fallback to sqlite for local use. |
- DB, err = sql.Open("sqlite3", conf.SQLiteFilePath) |
- if err != nil { |
- glog.Fatalln("Failed to open:", err) |
- } |
- isMySQL = false |
+ if err != nil { |
+ glog.Fatalln("Failed to open connection to SQL server:", err) |
} |
result := &VersionedDB{ |
DB: DB, |
- IsMySQL: isMySQL, |
migrationSteps: conf.MigrationSteps, |
} |
@@ -85,12 +156,6 @@ func NewVersionedDB(conf *DatabaseConfig) *VersionedDB { |
} |
glog.Infoln("Version table OK.") |
- // Migrate to the latest version if we are using SQLite, so we don't have |
- // to run the *_migratdb command for a local database. |
- if !result.IsMySQL { |
- result.Migrate(result.MaxDBVersion()) |
- } |
- |
// Ping the database occasionally to keep the connection fresh. |
go func() { |
c := time.Tick(1 * time.Minute) |
@@ -185,11 +250,8 @@ func (vdb *VersionedDB) MaxDBVersion() int { |
// Returns an error if the version table does not exist. |
func (vdb *VersionedDB) checkVersionTable() error { |
- // Check if the table exists in MySQL or SQLite. |
+ // Check if the table exists in MySQL. |
stmt := "SHOW TABLES LIKE 'sk_db_version'" |
- if !vdb.IsMySQL { |
- stmt = "SELECT name FROM sqlite_master WHERE type='table' AND name='sk_db_version';" |
- } |
var temp string |
err := vdb.DB.QueryRow(stmt).Scan(&temp) |
@@ -263,15 +325,10 @@ func (vdb *VersionedDB) getMigrations(currentVersion int, targetVersion int) [][ |
var temp []string |
switch { |
// using mysqlp |
- case (inc > 0) && vdb.IsMySQL: |
- temp = vdb.migrationSteps[idx].MySQLUp |
- case (inc < 0) && vdb.IsMySQL: |
- temp = vdb.migrationSteps[idx].MySQLDown |
- // using sqlite |
case (inc > 0): |
- temp = vdb.migrationSteps[idx].SQLiteUp |
+ temp = vdb.migrationSteps[idx].MySQLUp |
case (inc < 0): |
- temp = vdb.migrationSteps[idx].SQLiteDown |
+ temp = vdb.migrationSteps[idx].MySQLDown |
} |
result = append(result, temp) |
idx += inc |