Skip to content

Commit

Permalink
Import flyway check feature from keycloak-bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
fperot74 committed May 27, 2019
1 parent 814dc94 commit f11f8c8
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 10 deletions.
90 changes: 81 additions & 9 deletions database/dbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,40 @@ package database

import (
"database/sql"
"errors"
"fmt"
"regexp"
"strconv"
"time"

cs "github.com/cloudtrust/common-service"
)

// Define an internal structure to manage DB versions
type dbVersion struct {
major int
minor int
}

func newDbVersion(version string) (*dbVersion, error) {
var r = regexp.MustCompile(`^(\d+)\.(\d+)$`)
var match = r.FindStringSubmatch(version)
if match == nil {
return nil, fmt.Errorf("version %s does not match the required format", version)
}
// We don't test the Atoi errors as version matches the regexp
var maj, _ = strconv.Atoi(match[0])
var min, _ = strconv.Atoi(match[1])
return &dbVersion{
major: maj,
minor: min,
}, nil
}

func (v *dbVersion) matchesRequired(required *dbVersion) bool {
return !(v.major < required.major || (v.major == required.major && v.minor < required.minor))
}

// CloudtrustDB interface
type CloudtrustDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Expand All @@ -20,15 +48,17 @@ type CloudtrustDB interface {

// DbConfig Db configuration parameters
type DbConfig struct {
HostPort string
Username string
Password string
Database string
Protocol string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime int
Noop bool
HostPort string
Username string
Password string
Database string
Protocol string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime int
Noop bool
MigrationEnabled bool
MigrationVersion string
}

// ConfigureDbDefault configure default database parameters for a given prefix
Expand All @@ -45,6 +75,8 @@ func ConfigureDbDefault(v cs.Configuration, prefix, envUser, envPasswd string) {
v.SetDefault(prefix+"-max-open-conns", 10)
v.SetDefault(prefix+"-max-idle-conns", 2)
v.SetDefault(prefix+"-conn-max-lifetime", 3600)
v.SetDefault(prefix+"-migration", false)
v.SetDefault(prefix+"-migration-version", "")

v.BindEnv(prefix+"-username", envUser)
v.BindEnv(prefix+"-password", envPasswd)
Expand All @@ -64,6 +96,8 @@ func GetDbConfig(v cs.Configuration, prefix string, noop bool) *DbConfig {
cfg.MaxOpenConns = v.GetInt(prefix + "-max-open-conns")
cfg.MaxIdleConns = v.GetInt(prefix + "-max-idle-conns")
cfg.ConnMaxLifetime = v.GetInt(prefix + "-conn-max-lifetime")
cfg.MigrationEnabled = v.GetBool(prefix + "-migration")
cfg.MigrationVersion = v.GetString(prefix + "-migration-version")
}

return &cfg
Expand All @@ -81,6 +115,20 @@ func (cfg *DbConfig) OpenDatabase() (CloudtrustDB, error) {
return &NoopDB{}, err
}

// DB migration version
// checking that the flyway_schema_history has the minimum imposed migration version
if cfg.MigrationEnabled {
if cfg.MigrationVersion == "" {
// DB schema versioning is enabled but no minimum version was given
return nil, errors.New("Check of database schema is enabled, but no minimum version provided")
}
err = cfg.checkMigrationVersion(dbConn)
if err != nil {
dbConn.Close()
dbConn = nil
}
}

// the config of the DB should have a max_connections > SetMaxOpenConns
if err == nil {
dbConn.SetMaxOpenConns(cfg.MaxOpenConns)
Expand All @@ -91,6 +139,30 @@ func (cfg *DbConfig) OpenDatabase() (CloudtrustDB, error) {
return dbConn, err
}

func (cfg *DbConfig) checkMigrationVersion(conn CloudtrustDB) error {
var flywayVersion string
row := conn.QueryRow(`SELECT version FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1`)
err := row.Scan(&flywayVersion)

//flyway version and db migration version must match the format x.y where x and y are integers
var requiredDbVersion, flywayDbVersion *dbVersion
if err == nil {
requiredDbVersion, err = newDbVersion(cfg.MigrationVersion)
}
if err == nil {
flywayDbVersion, err = newDbVersion(flywayVersion)
}

// compare the two versions of the type x.y (major.minor)

// it is required for the last script version of the flyway is to be "bigger" than the required version
if err == nil && !flywayDbVersion.matchesRequired(requiredDbVersion) {
err = fmt.Errorf("Database schema not up-to-date (current: %s, required: %s)", flywayVersion, cfg.MigrationVersion)
}

return err
}

// NoopDB is a database client that does nothing.
type NoopDB struct{}

Expand Down
23 changes: 22 additions & 1 deletion database/dbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ import (
"github.com/stretchr/testify/assert"
)

func TestDbVersion(t *testing.T) {
var err error

for _, invalidVersion := range []string{"", "1.0.1", "A.b"} {
_, err = newDbVersion(invalidVersion)
assert.NotNil(t, err)
}

var v1, v2 *dbVersion
v1, err = newDbVersion("1.0")
assert.Nil(t, err)

var matchTests = map[string]bool{"0.9": false, "1.0": true, "1.5": true, "2.0": true}
for k, v := range matchTests {
v2, err = newDbVersion(k)
assert.Equal(t, v, v2.matchesRequired(v1))
}
}

func TestConfigureDbDefault(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
Expand All @@ -18,7 +37,7 @@ func TestConfigureDbDefault(t *testing.T) {
var envUser = "the env user"
var envPass = "the env password"

for _, suffix := range []string{"-host-port", "-username", "-password", "-database", "-protocol", "-max-open-conns", "-max-idle-conns", "-conn-max-lifetime"} {
for _, suffix := range []string{"-host-port", "-username", "-password", "-database", "-protocol", "-max-open-conns", "-max-idle-conns", "-conn-max-lifetime", "-migration", "-migration-version"} {
mockConf.EXPECT().SetDefault(prefix+suffix, gomock.Any()).Times(1)
}
mockConf.EXPECT().BindEnv(prefix+"-username", envUser).Times(1)
Expand All @@ -40,6 +59,8 @@ func TestGetDbConfig(t *testing.T) {
for _, suffix := range []string{"-max-open-conns", "-max-idle-conns", "-conn-max-lifetime"} {
mockConf.EXPECT().GetInt(prefix + suffix).Return(1).Times(1)
}
mockConf.EXPECT().GetBool(prefix + "-migration").Return(false).Times(1)
mockConf.EXPECT().GetString(prefix + "-migration-version").Return("1.0").Times(1)

var cfg = GetDbConfig(mockConf, prefix, false)
assert.Equal(t, "value-host-port", cfg.HostPort)
Expand Down

0 comments on commit f11f8c8

Please sign in to comment.