Skip to content

Commit

Permalink
Merge pull request #56 from minamijoyo/add-list-command
Browse files Browse the repository at this point in the history
Add list command for listing unapplied migrations
  • Loading branch information
minamijoyo authored Nov 30, 2021
2 parents b84b247 + 98850bb commit f662c0d
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
103 changes: 103 additions & 0 deletions command/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package command

import (
"context"
"fmt"
"log"
"strings"

"github.com/minamijoyo/tfmigrate/config"
"github.com/minamijoyo/tfmigrate/history"
flag "github.com/spf13/pflag"
)

// ListCommand is a command which lists migrations.
type ListCommand struct {
Meta
status string
}

// Run runs the procedure of this command.
func (c *ListCommand) Run(args []string) int {
cmdFlags := flag.NewFlagSet("list", flag.ContinueOnError)
cmdFlags.StringVar(&c.configFile, "config", defaultConfigFile, "A path to tfmigrate config file")
cmdFlags.StringVar(&c.status, "status", "all", "A filter for migration status")

if err := cmdFlags.Parse(args); err != nil {
c.UI.Error(fmt.Sprintf("failed to parse arguments: %s", err))
return 1
}

var err error
if c.config, err = newConfig(c.configFile); err != nil {
c.UI.Error(fmt.Sprintf("failed to load config file: %s", err))
return 1
}
log.Printf("[DEBUG] [command] config: %#v\n", c.config)

c.Option = newOption()
// The option may contains sensitive values such as environment variables.
// So logging the option set log level to DEBUG instead of INFO.
log.Printf("[DEBUG] [command] option: %#v\n", c.Option)

if c.config.History == nil {
// non-history mode
c.UI.Error("no history setting")
return 1
}

// history mode
ctx := context.Background()
out, err := listMigrations(ctx, c.config, c.status)
if err != nil {
c.UI.Error(err.Error())
return 1
}
c.UI.Output(out)
return 0
}

// listMigrations lists migrations.
func listMigrations(ctx context.Context, config *config.TfmigrateConfig, status string) (string, error) {
hc, err := history.NewController(ctx, config.MigrationDir, config.History)
if err != nil {
return "", err
}

var migrations []string
switch status {
case "all":
migrations = hc.Migrations()

case "unapplied":
migrations = hc.UnappliedMigrations()

default:
return "", fmt.Errorf("unknown filter for status: %s", status)
}

out := strings.Join(migrations, "\n")
return out, nil
}

// Help returns long-form help text.
func (c *ListCommand) Help() string {
helpText := `
Usage: tfmigrate list
List migrations.
Options:
--config A path to tfmigrate config file
--status A filter for migration status
Valid values are as follows:
- all (default)
- unapplied
`
return strings.TrimSpace(helpText)
}

// Synopsis returns one-line help text.
func (c *ListCommand) Synopsis() string {
return "List migrations"
}
118 changes: 118 additions & 0 deletions command/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package command

import (
"context"
"testing"

"github.com/minamijoyo/tfmigrate/config"
"github.com/minamijoyo/tfmigrate/history"
)

func TestListMigrations(t *testing.T) {
migrations := map[string]string{
"20201109000001_test1.hcl": `
migration "mock" "test1" {
plan_error = false
apply_error = false
}
`,
"20201109000002_test2.hcl": `
migration "mock" "test2" {
plan_error = false
apply_error = false
}
`,
"20201109000003_test3.hcl": `
migration "mock" "test3" {
plan_error = false
apply_error = false
}
`,
"20201109000004_test4.hcl": `
migration "mock" "test4" {
plan_error = false
apply_error = false
}
`,
}
historyFile := `{
"version": 1,
"records": {
"20201109000001_test1.hcl": {
"type": "mock",
"name": "test1",
"applied_at": "2020-11-10T00:00:01Z"
},
"20201109000002_test2.hcl": {
"type": "mock",
"name": "test2",
"applied_at": "2020-11-10T00:00:02Z"
}
}
}`

cases := []struct {
desc string
status string
migrations map[string]string
historyFile string
want string
ok bool
}{
{
desc: "all",
status: "all",
migrations: migrations,
historyFile: historyFile,
want: `20201109000001_test1.hcl
20201109000002_test2.hcl
20201109000003_test3.hcl
20201109000004_test4.hcl`,
ok: true,
},
{
desc: "unapplied",
status: "unapplied",
migrations: migrations,
historyFile: historyFile,
want: `20201109000003_test3.hcl
20201109000004_test4.hcl`,
ok: true,
},
{
desc: "unknown status",
status: "foo",
migrations: migrations,
historyFile: historyFile,
want: "",
ok: false,
},
}

for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
migrationDir := setupMigrationDir(t, tc.migrations)
storage := &history.MockStorageConfig{
Data: tc.historyFile,
WriteError: false,
ReadError: false,
}
config := &config.TfmigrateConfig{
MigrationDir: migrationDir,
History: &history.Config{
Storage: storage,
},
}
got, err := listMigrations(context.Background(), config, tc.status)
if tc.ok && err != nil {
t.Fatalf("unexpected err: %s", err)
}
if !tc.ok && err == nil {
t.Fatal("expected to return an error, but no error")
}
if got != tc.want {
t.Errorf("got = %#v, want = %#v", got, tc.want)
}
})
}
}
5 changes: 5 additions & 0 deletions history/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ func (c *Controller) Save(ctx context.Context) error {
return s.Write(ctx, b)
}

// Migrations returns a list of all migration file names.
func (c *Controller) Migrations() []string {
return c.migrations
}

// UnappliedMigrations returns a list of migration file names which have not
// been applied yet.
func (c *Controller) UnappliedMigrations() []string {
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func initCommands(ui cli.Ui) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"list": func() (cli.Command, error) {
return &command.ListCommand{
Meta: meta,
}, nil
},
}

return commands
Expand Down

0 comments on commit f662c0d

Please sign in to comment.