Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add list command for listing unapplied migrations #56

Merged
merged 4 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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