[NOD-327] Add --migrate cli flag to API server (#407)

* [NOD-327] Add --migrate cli flag to API server

* [NOD-327] Change log messages

* [NOD-327] Remove `required` flag from API server RPC CLI arguments

* [NOD-327] Add database version in migrations logs
This commit is contained in:
Ori Newman 2019-09-18 13:51:20 +03:00 committed by Svarog
parent f4850b9e7a
commit 2429b623fc
3 changed files with 80 additions and 22 deletions

View File

@ -23,9 +23,9 @@ var (
// Config defines the configuration options for the API server. // Config defines the configuration options for the API server.
type Config struct { type Config struct {
LogDir string `long:"logdir" description:"Directory to log output."` LogDir string `long:"logdir" description:"Directory to log output."`
RPCUser string `short:"u" long:"rpcuser" description:"RPC username" required:"true"` RPCUser string `short:"u" long:"rpcuser" description:"RPC username"`
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password" required:"true"` RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"`
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to" required:"true"` RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"` RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
DisableTLS bool `long:"notls" description:"Disable TLS"` DisableTLS bool `long:"notls" description:"Disable TLS"`
DBAddress string `long:"dbaddress" description:"Database address"` DBAddress string `long:"dbaddress" description:"Database address"`
@ -33,6 +33,7 @@ type Config struct {
DBPassword string `long:"dbpass" description:"Database password" required:"true"` DBPassword string `long:"dbpass" description:"Database password" required:"true"`
DBName string `long:"dbname" description:"Database name" required:"true"` DBName string `long:"dbname" description:"Database name" required:"true"`
HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8080)"` HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8080)"`
Migrate bool `long:"migrate" description:"Migrate the database to the latest version. The server will not start when using this flag."`
} }
// Parse parses the CLI arguments and returns a config struct. // Parse parses the CLI arguments and returns a config struct.
@ -49,6 +50,18 @@ func Parse() (*Config, error) {
return nil, err return nil, err
} }
if !cfg.Migrate {
if cfg.RPCUser == "" {
return nil, errors.New("--rpcuser is required if --migrate flag is not used")
}
if cfg.RPCPassword == "" {
return nil, errors.New("--rpcpass is required if --migrate flag is not used")
}
if cfg.RPCServer == "" {
return nil, errors.New("--rpcserver is required if --migrate flag is not used")
}
}
if cfg.RPCCert == "" && !cfg.DisableTLS { if cfg.RPCCert == "" && !cfg.DisableTLS {
return nil, errors.New("--notls has to be disabled if --cert is used") return nil, errors.New("--notls has to be disabled if --cert is used")
} }

View File

@ -34,13 +34,17 @@ func (l gormLogger) Print(v ...interface{}) {
// config variable. // config variable.
func Connect(cfg *config.Config) error { func Connect(cfg *config.Config) error {
connectionString := buildConnectionString(cfg) connectionString := buildConnectionString(cfg)
isCurrent, err := isCurrent(connectionString) migrator, driver, err := openMigrator(connectionString)
if err != nil {
return err
}
isCurrent, version, err := isCurrent(migrator, driver)
if err != nil { if err != nil {
return fmt.Errorf("Error checking whether the database is current: %s", err) return fmt.Errorf("Error checking whether the database is current: %s", err)
} }
if !isCurrent { if !isCurrent {
return fmt.Errorf("Database is not current. Please migrate" + return fmt.Errorf("Database is not current (version %d). Please migrate"+
" the database and start again.") " the database by running the server with --migrate flag and then run it again.", version)
} }
db, err = gorm.Open("mysql", connectionString) db, err = gorm.Open("mysql", connectionString)
@ -68,35 +72,68 @@ func buildConnectionString(cfg *config.Config) string {
// isCurrent resolves whether the database is on the latest // isCurrent resolves whether the database is on the latest
// version of the schema. // version of the schema.
func isCurrent(connectionString string) (bool, error) { func isCurrent(migrator *migrate.Migrate, driver source.Driver) (bool, uint, error) {
driver, err := source.Open("file://migrations")
if err != nil {
return false, err
}
migrator, err := migrate.NewWithSourceInstance(
"migrations", driver, "mysql://"+connectionString)
if err != nil {
return false, err
}
// Get the current version // Get the current version
version, isDirty, err := migrator.Version() version, isDirty, err := migrator.Version()
if err == migrate.ErrNilVersion { if err == migrate.ErrNilVersion {
return false, nil return false, 0, nil
} }
if err != nil { if err != nil {
return false, err return false, 0, err
} }
if isDirty { if isDirty {
return false, fmt.Errorf("Database is dirty") return false, 0, fmt.Errorf("Database is dirty")
} }
// The database is current if Next returns ErrNotExist // The database is current if Next returns ErrNotExist
_, err = driver.Next(version) _, err = driver.Next(version)
if pathErr, ok := err.(*os.PathError); ok { if pathErr, ok := err.(*os.PathError); ok {
if pathErr.Err == os.ErrNotExist { if pathErr.Err == os.ErrNotExist {
return true, nil return true, version, nil
} }
} }
return false, err return false, version, err
}
func openMigrator(connectionString string) (*migrate.Migrate, source.Driver, error) {
driver, err := source.Open("file://migrations")
if err != nil {
return nil, nil, err
}
migrator, err := migrate.NewWithSourceInstance(
"migrations", driver, "mysql://"+connectionString)
if err != nil {
return nil, nil, err
}
return migrator, driver, nil
}
// Migrate database to the latest version.
func Migrate(cfg *config.Config) error {
connectionString := buildConnectionString(cfg)
migrator, driver, err := openMigrator(connectionString)
if err != nil {
return err
}
isCurrent, version, err := isCurrent(migrator, driver)
if err != nil {
return fmt.Errorf("Error checking whether the database is current: %s", err)
}
if isCurrent {
log.Infof("Database is already up-to-date (version %d)", version)
return nil
}
err = migrator.Up()
if err != nil {
return err
}
version, isDirty, err := migrator.Version()
if err != nil {
return err
}
if isDirty {
return fmt.Errorf("error migrating database: database is dirty")
}
log.Infof("Migrated database to the latest version (version %d)", version)
return nil
} }

View File

@ -23,6 +23,14 @@ func main() {
panic(fmt.Errorf("Error parsing command-line arguments: %s", err)) panic(fmt.Errorf("Error parsing command-line arguments: %s", err))
} }
if cfg.Migrate {
err := database.Migrate(cfg)
if err != nil {
panic(fmt.Errorf("Error migrating database: %s", err))
}
return
}
err = database.Connect(cfg) err = database.Connect(cfg)
if err != nil { if err != nil {
panic(fmt.Errorf("Error connecting to database: %s", err)) panic(fmt.Errorf("Error connecting to database: %s", err))