diff --git a/cmd/opm/registry/serve.go b/cmd/opm/registry/serve.go index 8b30ec5b2..45244bf0b 100644 --- a/cmd/opm/registry/serve.go +++ b/cmd/opm/registry/serve.go @@ -2,6 +2,7 @@ package registry import ( "context" + "database/sql" "net" "github.com/sirupsen/logrus" @@ -64,8 +65,19 @@ func runRegistryServeCmdFunc(cmd *cobra.Command, args []string) error { logger := logrus.WithFields(logrus.Fields{"database": dbName, "port": port}) + db, err := sql.Open("sqlite3", dbName) + if err != nil { + return err + } + + // Migrate database to latest version before serving + err = migrateToLatest(db, dbName) + if err != nil { + return err + } + var store registry.Query - store, err = sqlite.NewSQLLiteQuerier(dbName) + store = sqlite.NewSQLLiteQuerierFromDb(db) if err != nil { logger.WithError(err).Warnf("failed to load db") } @@ -98,3 +110,17 @@ func runRegistryServeCmdFunc(cmd *cobra.Command, args []string) error { return nil } + +func migrateToLatest(db *sql.DB, dbName string) error { + migrator, err := sqlite.NewSQLLiteMigrator(db, "") + if err != nil { + return err + } + defer migrator.CleanUpMigrator() + + err = migrator.MigrateUp(dbName) + if err != nil { + return err + } + return nil +} diff --git a/pkg/sqlite/migrator.go b/pkg/sqlite/migrator.go index e687ef9f2..0e63319c0 100644 --- a/pkg/sqlite/migrator.go +++ b/pkg/sqlite/migrator.go @@ -18,7 +18,7 @@ import ( type SQLMigrator struct { db *sql.DB migrationsPath string - generated bool + generated bool } // NewSQLLiteMigrator returns a SQLMigrator. The SQLMigrator takes a sql database and directory for migrations @@ -44,7 +44,7 @@ func NewSQLLiteMigrator(db *sql.DB, migrationsPath string) (*SQLMigrator, error) if err != nil { return nil, err } - + f, err := os.Create(fmt.Sprintf("%s/%s", tempDir, file)) if err != nil { return nil, err @@ -60,18 +60,18 @@ func NewSQLLiteMigrator(db *sql.DB, migrationsPath string) (*SQLMigrator, error) return &SQLMigrator{ db: db, migrationsPath: tempDir, - generated: true, + generated: true, }, nil } return &SQLMigrator{ db: db, migrationsPath: migrationsPath, - generated: false, + generated: false, }, nil } -// CleanUpMigrator deletes any unnecessary data generated just for the scope of the migrator. +// CleanUpMigrator deletes any unnecessary data generated just for the scope of the migrator. // Call this function once the scope of the Migrator is no longer required func (m *SQLMigrator) CleanUpMigrator() { if m.generated { @@ -111,6 +111,31 @@ func (m *SQLMigrator) InitMigrationVersion() error { return nil } +// MigrateUp is a wrapper around golang-migrate's Up. Up +// looks at the currently active migration version and will +// migrate all the way up (applying all up migrations). +func (m *SQLMigrator) MigrateUp(dbName string) error { + instance, err := sqlite3.WithInstance(m.db, &sqlite3.Config{DatabaseName: dbName}) + if err != nil { + return err + } + + migrator, err := migrate.NewWithDatabaseInstance(fmt.Sprintf("file://%s", m.migrationsPath), "registrydb", instance) + if err != nil { + return err + } + + err = migrator.Up() + if err != nil { + if err == migrate.ErrNoChange { + return nil + } + return err + } + + return nil +} + // CurrentVersion returns the version of the database associated with the migrator func (m *SQLMigrator) CurrentVersion() (uint, error) { instance, err := sqlite3.WithInstance(m.db, &sqlite3.Config{}) diff --git a/pkg/sqlite/migrator_test.go b/pkg/sqlite/migrator_test.go index 854fbcce3..a9ebc6490 100644 --- a/pkg/sqlite/migrator_test.go +++ b/pkg/sqlite/migrator_test.go @@ -1,6 +1,7 @@ package sqlite import ( + "database/sql" "os" "testing" @@ -54,9 +55,29 @@ func TestGeneratedMigrations(t *testing.T) { store, err := NewSQLLiteLoader(WithDBName("test.db")) require.NoError(t, err) defer os.Remove("test.db") - + migrator, err := NewSQLLiteMigrator(store.db, "") defer migrator.CleanUpMigrator() require.NoError(t, err, "Unable to initialize migrator with generated migrations") } + +func TestUpMigration(t *testing.T) { + migrationsPath := "./testdata/test_db_migrations/migrations" + + db, err := sql.Open("sqlite3", "test.db") + require.NoError(t, err) + + defer os.Remove("test.db") + + migrator, err := NewSQLLiteMigrator(db, migrationsPath) + require.NoError(t, err, "Unable to initialize migrator") + + err = migrator.MigrateUp("test.db") // Migrating up adds a new table `test` + require.NoError(t, err) + + var name string + err = db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='test';").Scan(&name) + require.NoError(t, err) + require.Equal(t, name, "test") +} diff --git a/pkg/sqlite/query.go b/pkg/sqlite/query.go index e7fc03168..d7c0f16b8 100644 --- a/pkg/sqlite/query.go +++ b/pkg/sqlite/query.go @@ -25,6 +25,10 @@ func NewSQLLiteQuerier(dbFilename string) (*SQLQuerier, error) { return &SQLQuerier{db}, nil } +func NewSQLLiteQuerierFromDb(db *sql.DB) *SQLQuerier { + return &SQLQuerier{db} +} + func (s *SQLQuerier) ListTables(ctx context.Context) ([]string, error) { query := "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" rows, err := s.db.QueryContext(ctx, query) diff --git a/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.down.sql b/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.down.sql new file mode 100644 index 000000000..14abb703b --- /dev/null +++ b/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS test \ No newline at end of file diff --git a/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.up.sql b/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.up.sql new file mode 100644 index 000000000..56225c3d3 --- /dev/null +++ b/pkg/sqlite/testdata/test_db_migrations/migrations/194708150000_test_up_migration.up.sql @@ -0,0 +1,3 @@ +CREATE TABLE IF NOT EXISTS test( + test_column VARCHAR (50) PRIMARY KEY +); \ No newline at end of file