From b74411d389b4861488b9ab66cb6a7b5c8bac6dde Mon Sep 17 00:00:00 2001 From: qbart Date: Sun, 5 Dec 2021 22:26:41 +0100 Subject: [PATCH] Add migration status action --- go.mod | 1 + go.sum | 2 + krab/action_migrate_status.go | 109 +++++++++++++++++++ krabcli/app.go | 4 + spec/action_migrate_down_test.go | 20 +--- spec/action_migrate_dsl_index_test.go | 13 +-- spec/action_migrate_dsl_table_test.go | 15 +-- spec/action_migrate_status_arguments_test.go | 31 ++++++ spec/action_migrate_up_test.go | 8 +- 9 files changed, 159 insertions(+), 44 deletions(-) create mode 100644 krab/action_migrate_status.go create mode 100644 spec/action_migrate_status_arguments_test.go diff --git a/go.mod b/go.mod index f01cf3f..b27659c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/agext/levenshtein v1.2.3 // indirect + github.com/emirpasic/gods v1.12.0 // indirect github.com/hashicorp/hcl/v2 v2.6.0 github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 github.com/jackc/pgx/v4 v4.11.0 diff --git a/go.sum b/go.sum index a6980f0..09ede5c 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= diff --git a/krab/action_migrate_status.go b/krab/action_migrate_status.go new file mode 100644 index 0000000..e10fe33 --- /dev/null +++ b/krab/action_migrate_status.go @@ -0,0 +1,109 @@ +package krab + +import ( + "context" + "fmt" + + "github.com/emirpasic/gods/sets/hashset" + "github.com/ohkrab/krab/cli" + "github.com/ohkrab/krab/cliargs" + "github.com/ohkrab/krab/krabdb" + "github.com/ohkrab/krab/krabtpl" + "github.com/ohkrab/krab/tpls" + "github.com/pkg/errors" + "github.com/wzshiming/ctc" +) + +// ActionMigrateStatus keeps data needed to perform this action. +type ActionMigrateStatus struct { + Ui cli.UI + Set *MigrationSet + Connection krabdb.Connection +} + +func (a *ActionMigrateStatus) Help() string { + return fmt.Sprint( + `Usage: krab migrate status [set]`, + "\n\n", + a.Set.Arguments.Help(), + ` +View migration status for given set. +`, + ) +} + +func (a *ActionMigrateStatus) Synopsis() string { + return fmt.Sprintf("Migration status for `%s`", a.Set.RefName) +} + +// Run in CLI. +func (a *ActionMigrateStatus) Run(args []string) int { + ui := a.Ui + flags := cliargs.New(args) + flags.RequireNonFlagArgs(0) + + for _, arg := range a.Set.Arguments.Args { + flags.Add(arg.Name) + } + + err := flags.Parse() + if err != nil { + ui.Output(a.Help()) + ui.Error(err.Error()) + return 1 + } + + err = a.Set.Arguments.Validate(flags.Values()) + if err != nil { + ui.Output(a.Help()) + ui.Error(err.Error()) + return 1 + } + + templates := tpls.New(flags.Values(), krabtpl.Functions) + + err = a.Connection.Get(func(db krabdb.DB) error { + return a.Do(context.Background(), db, templates, ui) + }) + + if err != nil { + ui.Error(err.Error()) + return 1 + } + + return 0 +} + +// Run performs the action. +func (a *ActionMigrateStatus) Do(ctx context.Context, db krabdb.DB, tpl *tpls.Templates, ui cli.UI) error { + versions := NewSchemaMigrationTable(tpl.Render(a.Set.Schema)) + + hooksRunner := HookRunner{} + err := hooksRunner.SetSearchPath(ctx, db, tpl.Render(a.Set.Schema)) + if err != nil { + return errors.Wrap(err, "Failed to run SetSearchPath hook") + } + migrationRefsInDb, err := versions.SelectAll(ctx, db) + if err != nil { + return err + } + + appliedMigrations := hashset.New() + + for _, migration := range migrationRefsInDb { + appliedMigrations.Add(migration.Version) + } + + for _, migration := range a.Set.Migrations { + pending := !appliedMigrations.Contains(migration.Version) + + if pending { + ui.Error(fmt.Sprint("- ", migration.Version, " ", migration.RefName)) + } else { + ui.Output(fmt.Sprint(ctc.ForegroundGreen, "+ ", ctc.Reset, migration.Version, " ", migration.RefName)) + } + + } + + return nil +} diff --git a/krabcli/app.go b/krabcli/app.go index c55b1d8..44509d4 100644 --- a/krabcli/app.go +++ b/krabcli/app.go @@ -55,6 +55,10 @@ func (a *App) RegisterAll() { for _, set := range a.Config.MigrationSets { localSet := set + a.RegisterCmd(fmt.Sprintln("migrate", "status", set.RefName), func() Command { + return &krab.ActionMigrateStatus{Ui: a.Ui, Set: localSet, Connection: a.connection} + }) + a.RegisterCmd(fmt.Sprintln("migrate", "up", set.RefName), func() Command { return &krab.ActionMigrateUp{Ui: a.Ui, Set: localSet, Connection: a.connection} }) diff --git a/spec/action_migrate_down_test.go b/spec/action_migrate_down_test.go index b1f9ce4..75554dd 100644 --- a/spec/action_migrate_down_test.go +++ b/spec/action_migrate_down_test.go @@ -30,13 +30,8 @@ migration_set "public" { `)) defer c.Teardown() c.AssertSuccessfulRun(t, []string{"migrate", "up", "public"}) - c.AssertOutputContains(t, - ` -create_animals v1 -add_column v2 -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 create_animals") + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv2 add_column") c.AssertSchemaMigrationTable(t, "public", "v1", "v2") c.Insert(t, "animals", "name, emoji", "('Elephant', '🐘')") cols, rows := c.Query(t, "SELECT * from animals") @@ -48,7 +43,7 @@ Done } c.AssertSuccessfulRun(t, []string{"migrate", "down", "public", "-version", "v2"}) - c.AssertOutputContains(t, `Done`) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv2 add_column") c.AssertSchemaMigrationTable(t, "public", "v1") cols, rows = c.Query(t, "SELECT * from animals") @@ -84,13 +79,8 @@ migration_set "public" { `)) defer c.Teardown() c.AssertSuccessfulRun(t, []string{"migrate", "up", "public"}) - c.AssertOutputContains(t, - ` -create_animals v1 -add_column v2 -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 create_animals") + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv2 add_column") c.AssertSchemaMigrationTable(t, "public", "v1", "v2") c.Insert(t, "animals", "name, emoji", "('Elephant', '🐘')") cols, rows := c.Query(t, "SELECT * from animals") diff --git a/spec/action_migrate_dsl_index_test.go b/spec/action_migrate_dsl_index_test.go index 0bb49d9..15865a2 100644 --- a/spec/action_migrate_dsl_index_test.go +++ b/spec/action_migrate_dsl_index_test.go @@ -57,12 +57,7 @@ migration_set "animals" { defer c.Teardown() if c.AssertSuccessfulRun(t, []string{"migrate", "up", "animals"}) { c.AssertSchemaMigrationTable(t, "public", "v1") - c.AssertOutputContains(t, - ` -create_animals v1 -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 create_animals") c.AssertSQLContains(t, ` CREATE TABLE "animals"( "id" bigint, @@ -80,11 +75,7 @@ CREATE INDEX CONCURRENTLY "idx_heavy_animals" ON "animals" ("weight_kg") WHERE ( if c.AssertSuccessfulRun(t, []string{"migrate", "down", "animals", "-version", "v1"}) { c.AssertSchemaMigrationTable(t, "public") - c.AssertOutputContains(t, - ` -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 create_animals") c.AssertSQLContains(t, `DROP INDEX "public"."idx_uniq_name" CASCADE`) c.AssertSQLContains(t, `DROP INDEX CONCURRENTLY "idx_heavy_animals"`) c.AssertSQLContains(t, `DROP TABLE "animals"`) diff --git a/spec/action_migrate_dsl_table_test.go b/spec/action_migrate_dsl_table_test.go index d583fc7..28df588 100644 --- a/spec/action_migrate_dsl_table_test.go +++ b/spec/action_migrate_dsl_table_test.go @@ -95,13 +95,8 @@ migration_set "animals" { defer c.Teardown() if c.AssertSuccessfulRun(t, []string{"migrate", "up", "animals"}) { c.AssertSchemaMigrationTable(t, "public", "v1", "v2") - c.AssertOutputContains(t, - ` -create_categories v1 -create_animals v2 -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 create_categories") + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv2 create_animals") c.AssertSQLContains(t, ` CREATE TABLE "categories"( "id" bigint, @@ -126,11 +121,7 @@ CREATE UNLOGGED TABLE "animals"( if c.AssertSuccessfulRun(t, []string{"migrate", "down", "animals", "-version", "v2"}) { c.AssertSchemaMigrationTable(t, "public", "v1") - c.AssertOutputContains(t, - ` -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv2 create_animals") c.AssertSQLContains(t, ` DROP TABLE "animals" `) diff --git a/spec/action_migrate_status_arguments_test.go b/spec/action_migrate_status_arguments_test.go new file mode 100644 index 0000000..0947510 --- /dev/null +++ b/spec/action_migrate_status_arguments_test.go @@ -0,0 +1,31 @@ +package spec + +import ( + "testing" +) + +func TestActionMigrateStatusArguments(t *testing.T) { + c := mockCli(mockConfig(` +migration "create_animals" { + version = "v1" + + up { sql = "CREATE TABLE animals(name VARCHAR)" } + down { sql = "DROP TABLE animals" } +} + +migration_set "animals" { + arguments { + arg "schema" {} + } + + schema = "{{.Args.schema}}" + + migrations = [migration.create_animals] +} +`)) + defer c.Teardown() + + c.AssertSuccessfulRun(t, []string{"migrate", "up", "animals", "-schema", "custom"}) + c.AssertSuccessfulRun(t, []string{"migrate", "status", "animals", "-schema", "custom"}) + c.AssertOutputContains(t, "\x1b[0;32m+ \x1b[0mv1 create_animals") +} diff --git a/spec/action_migrate_up_test.go b/spec/action_migrate_up_test.go index 9c67bef..5b008a5 100644 --- a/spec/action_migrate_up_test.go +++ b/spec/action_migrate_up_test.go @@ -19,12 +19,7 @@ migration_set "public" { `)) defer c.Teardown() c.AssertSuccessfulRun(t, []string{"migrate", "up", "public"}) - c.AssertOutputContains(t, - ` -do_nothing v1 -Done -`, - ) + c.AssertOutputContains(t, "\x1b[0;32mOK \x1b[0mv1 do_nothing") c.AssertSchemaMigrationTable(t, "public", "v1") } @@ -47,6 +42,7 @@ migration_set "public" { `)) c.AssertFailedRun(t, []string{"migrate", "up", "public"}) + c.AssertOutputContains(t, "\x1b[0;31mERR \x1b[0mv1 do_nothing") c.AssertUiErrorOutputContains(t, `column "invalid" does not exist`, )