CLI migrate command (#304)

This commit is contained in:
Marko Gaćeša 2023-02-07 12:50:46 +01:00 committed by GitHub
parent eabf098f96
commit efcd415603
8 changed files with 208 additions and 9 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/harness/gitness/cli/operations/account"
"github.com/harness/gitness/cli/operations/hooks"
"github.com/harness/gitness/cli/operations/migrate"
"github.com/harness/gitness/cli/operations/user"
"github.com/harness/gitness/cli/operations/users"
"github.com/harness/gitness/cli/server"
@ -29,6 +30,8 @@ func Command() {
args := getArguments()
app := kingpin.New(application, description)
migrate.Register(app)
server.Register(app)
user.Register(app)

View File

@ -0,0 +1,49 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package migrate
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/internal/store/database/migrate"
"gopkg.in/alecthomas/kingpin.v2"
)
type commandCurrent struct {
envfile string
}
func (c *commandCurrent) run(*kingpin.ParseContext) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
db, err := getDB(ctx, c.envfile)
if err != nil {
return err
}
version, err := migrate.Current(ctx, db)
if err != nil {
return err
}
fmt.Println(version)
return nil
}
func registerCurrent(app *kingpin.CmdClause) {
c := &commandCurrent{}
cmd := app.Command("current", "display the current version of the database").
Action(c.run)
cmd.Arg("envfile", "load the environment variable file").
Default("").
StringVar(&c.envfile)
}

View File

@ -0,0 +1,40 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package migrate
import (
"context"
"fmt"
"github.com/harness/gitness/cli/server"
"github.com/harness/gitness/internal/store/database"
"github.com/jmoiron/sqlx"
"github.com/joho/godotenv"
"gopkg.in/alecthomas/kingpin.v2"
)
func getDB(ctx context.Context, envfile string) (*sqlx.DB, error) {
_ = godotenv.Load(envfile)
config, err := server.LoadConfig()
if err != nil {
return nil, fmt.Errorf("failed to load configuration: %w", err)
}
db, err := database.Connect(ctx, config.Database.Driver, config.Database.Datasource)
if err != nil {
return nil, fmt.Errorf("failed to create database handle: %w", err)
}
return db, nil
}
// Register the server command.
func Register(app *kingpin.Application) {
cmd := app.Command("migrate", "database migration tool")
registerCurrent(cmd)
registerTo(cmd)
}

View File

@ -0,0 +1,46 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package migrate
import (
"context"
"time"
"github.com/harness/gitness/internal/store/database/migrate"
"gopkg.in/alecthomas/kingpin.v2"
)
type commandTo struct {
envfile string
version string
}
func (c *commandTo) run(k *kingpin.ParseContext) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
db, err := getDB(ctx, c.envfile)
if err != nil {
return err
}
return migrate.To(ctx, db, c.version)
}
func registerTo(app *kingpin.CmdClause) {
c := &commandTo{}
cmd := app.Command("to", "migrates the database to the provided version").
Action(c.run)
cmd.Arg("version", "database version to migrate to").
Required().
StringVar(&c.version)
cmd.Arg("envfile", "load the environment variable file").
Default("").
StringVar(&c.envfile)
}

View File

@ -50,7 +50,7 @@ func (c *command) run(*kingpin.ParseContext) error {
}
// configure the log level
setupLogger(config)
SetupLogger(config)
// add logger to context
log := log.Logger.With().Logger()
@ -119,9 +119,8 @@ func (c *command) run(*kingpin.ParseContext) error {
return err
}
// helper function configures the global logger from
// the loaded configuration.
func setupLogger(config *types.Config) {
// SetupLogger configures the global logger from the loaded configuration.
func SetupLogger(config *types.Config) {
// configure the log level
switch {
case config.Trace:

View File

@ -8,6 +8,7 @@ import (
"context"
"database/sql"
"embed"
"fmt"
"io/fs"
"github.com/jmoiron/sqlx"
@ -21,8 +22,59 @@ var postgres embed.FS
//go:embed sqlite/*.sql
var sqlite embed.FS
const tableName = "migrations"
// Migrate performs the database migration.
func Migrate(ctx context.Context, db *sqlx.DB) error {
opts := getMigrator(db)
return migrate.New(opts).MigrateUp(ctx)
}
// To performs the database migration to the specific version.
func To(ctx context.Context, db *sqlx.DB, version string) error {
opts := getMigrator(db)
return migrate.New(opts).MigrateTo(ctx, version)
}
// Current returns the current version ID (the latest migration applied) of the database.
func Current(ctx context.Context, db *sqlx.DB) (string, error) {
var (
query string
migrationTableCount int
)
switch db.DriverName() {
case "sqlite3":
query = `
SELECT count(*)
FROM sqlite_master
WHERE name = ? and type = 'table'`
default:
query = `
SELECT count(*)
FROM information_schema.tables
WHERE table_name = ? and table_schema = 'public'`
}
if err := db.QueryRowContext(ctx, query, tableName).Scan(&migrationTableCount); err != nil {
return "", fmt.Errorf("failed to check migration table existence: %w", err)
}
if migrationTableCount == 0 {
return "", nil
}
var version string
query = "select version from " + tableName + " limit 1"
if err := db.QueryRowContext(ctx, query).Scan(&version); err != nil {
return "", fmt.Errorf("failed to read current DB version from migration table: %w", err)
}
return version, nil
}
func getMigrator(db *sqlx.DB) migrate.Options {
before := func(_ context.Context, _ *sql.Tx, version string) error {
log.Trace().Str("version", version).Msg("migration started")
return nil
@ -38,7 +90,7 @@ func Migrate(ctx context.Context, db *sqlx.DB) error {
Before: before,
DB: db.DB,
FS: sqlite,
Table: "migrations",
Table: tableName,
}
switch db.DriverName() {
@ -51,5 +103,5 @@ func Migrate(ctx context.Context, db *sqlx.DB) error {
opts.FS = folder
}
return migrate.New(opts).MigrateUp(ctx)
return opts
}

View File

@ -46,7 +46,17 @@ func Connect(ctx context.Context, driver string, datasource string) (*sqlx.DB, e
return nil, fmt.Errorf("failed to ping the db: %w", err)
}
if err = setupDatabase(ctx, dbx); err != nil {
return dbx, nil
}
// ConnectAndMigrate creates the database handle and migrates the database.
func ConnectAndMigrate(ctx context.Context, driver string, datasource string) (*sqlx.DB, error) {
dbx, err := Connect(ctx, driver, datasource)
if err != nil {
return nil, err
}
if err = migrateDatabase(ctx, dbx); err != nil {
return nil, fmt.Errorf("failed to setup the db: %w", err)
}
@ -111,6 +121,6 @@ func pingDatabase(ctx context.Context, db *sqlx.DB) error {
// helper function to setup the database by performing automated
// database migration steps.
func setupDatabase(ctx context.Context, db *sqlx.DB) error {
func migrateDatabase(ctx context.Context, db *sqlx.DB) error {
return migrate.Migrate(ctx, db)
}

View File

@ -34,7 +34,7 @@ var WireSet = wire.NewSet(
// ProvideDatabase provides a database connection.
func ProvideDatabase(ctx context.Context, config *types.Config) (*sqlx.DB, error) {
return Connect(
return ConnectAndMigrate(
ctx,
config.Database.Driver,
config.Database.Datasource,