From 0a9456960453fe5bcd34a767331f56e3d9efedc8 Mon Sep 17 00:00:00 2001 From: Ernest Micklei <> Date: Wed, 26 Sep 2018 23:02:05 +0200 Subject: [PATCH 1/3] added initia plan feature implementation --- commands.go | 39 ++++++++++++++++++++++++++++++--------- main.go | 12 ++++++++++++ migration.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 80cc253..7432fa9 100644 --- a/commands.go +++ b/commands.go @@ -22,6 +22,7 @@ const ( pending = "... pending ..." execDo = "... do ..." execUndo = "... undo ..." + execPlan = "... plan ..." stopped = "... stopped ..." ) @@ -60,6 +61,14 @@ func cmdCreateMigration(c *cli.Context) error { } func cmdMigrationsUp(c *cli.Context) error { + return runMigrations(c, !true) +} + +func cmdMigrationsPlan(c *cli.Context) error { + return runMigrations(c, true) +} + +func runMigrations(c *cli.Context, isSimulating bool) error { mtx, err := getMigrationContext(c) if err != nil { printError(err.Error()) @@ -86,16 +95,28 @@ func cmdMigrationsUp(c *cli.Context) error { } for _, each := range all { log.Println(statusSeparator) - log.Println(execDo, pretty(each.Filename)) - if err := ExecuteAll(each.DoSection, mtx.config().shellEnv(), c.GlobalBool("v")); err != nil { - reportError(mtx.stateProvider.Config(), "do", err) - return errAbort + leadingTitle := execDo + if isSimulating { + leadingTitle = execPlan } - mtx.lastApplied = each.Filename - // save after each succesful migration - if err := mtx.stateProvider.SaveState(mtx.lastApplied); err != nil { - reportError(mtx.stateProvider.Config(), "save state", err) - return errAbort + log.Println(leadingTitle, pretty(each.Filename)) + if isSimulating { + log.Println(statusSeparator) + if SimulateAll(each.DoSection, mtx.config().shellEnv(), true); err != nil { + reportError(mtx.stateProvider.Config(), "plan do", err) + return errAbort + } + } else { + if err := ExecuteAll(each.DoSection, mtx.config().shellEnv(), c.GlobalBool("v")); err != nil { + reportError(mtx.stateProvider.Config(), "do", err) + return errAbort + } + mtx.lastApplied = each.Filename + // save after each succesful migration + if err := mtx.stateProvider.SaveState(mtx.lastApplied); err != nil { + reportError(mtx.stateProvider.Config(), "save state", err) + return errAbort + } } // if not empty then stop after applying this migration if stopAfter == each.Filename { diff --git a/main.go b/main.go index df7ce6d..c041f7f 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,18 @@ func newApp() *cli.App { ArgsUsage: `[title] title - what the effect of this migration is on infrastructure.`, }, + { + Name: "plan", + Usage: "Simulates the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one.", + Action: func(c *cli.Context) error { + defer started(c, "plan = simulate pending migrations")() + return cmdMigrationsPlan(c) + }, + Flags: []cli.Flag{migrationsFlag}, + ArgsUsage: `[path] [stop] + path - name of the folder that contains the configuration of the target project. + stop - (optional) the name of the migration file after which applying migrations will stop.`, + }, { Name: "up", Usage: "Runs the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one.", diff --git a/migration.go b/migration.go index 7c68080..4cff473 100644 --- a/migration.go +++ b/migration.go @@ -91,6 +91,38 @@ func ExecuteAll(commands []string, envs []string, verbose bool) error { return nil } +// SimulateAll logs expanded commands using the environment variables of both the config and the OS. +func SimulateAll(commands []string, envs []string, verbose bool) error { + allEnv := append(os.Environ(), envs...) + envMap := map[string]string{} + for _, each := range allEnv { + kv := strings.Split(each, "=") + envMap[kv[0]] = kv[1] + } + for _, each := range commands { + log.Println(expandVarsIn(envMap, each)) + } + return nil +} + +// expandVarsIn returns a command with all occurrences of environment variables replaced by known values. +func expandVarsIn(envs map[string]string, command string) string { + // assume no recurse expand + expanded := command + for k, v := range envs { + // if the value itself is a known variable then skip it + if strings.HasPrefix(v, "$") { + if _, ok := envs[v]; ok { + log.Printf("Warning, skipping non-expandable environment var %s=%v\n", k, v) + continue + } + } + varName := "$" + k + expanded = strings.Replace(expanded, varName, v, -1) + } + return expanded +} + func setupShellScript(verbose bool) string { flag := "-v" if verbose { From 85769876d570bf555956821cb22693b816433658 Mon Sep 17 00:00:00 2001 From: emicklei_kramphub Date: Fri, 28 Sep 2018 09:28:32 +0200 Subject: [PATCH 2/3] cosmetics, update readme,changes --- CHANGES.md | 4 ++++ README.md | 6 ++++++ commands.go | 31 ++++++++++++++++++------------- main.go | 4 ++-- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c2dca8..e20dd60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelist of gmig releases +## v1.7.0, 2018-09-28 + +- add "plan" command that logs all commands that would be executed on "up". + ## v1.6.0 - add options to new to set the commands for the do,undo,view section directly diff --git a/README.md b/README.md index 6f86c22..1f3a5df 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ and use the `view` subcommand. up Runs the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one. down Runs the undo section of the last applied migration only. + plan Log commands of the do section of all pending migrations in order, one after the other. status List all migrations with details compared to the current state. view Runs the view section of all applied migrations to see the current state reported by your infrastructure. force state | do | undo @@ -133,6 +134,11 @@ List all migrations with an indicator (applied,pending) whether is has been appl Run this command in the directory where all migrations are stored. Use `--migrations` for a different location. +### plan [path] [|migration file] [--migrations folder] + +Log commands of the `do` section of all pending migrations in order, one after the other. +If `migration file` is given then stop after applying that one. + ### up [path] [|migration file] [--migrations folder] Executes the `do` section of each pending migration compared to the last applied change to the infrastructure. diff --git a/commands.go b/commands.go index 7432fa9..495bcdb 100644 --- a/commands.go +++ b/commands.go @@ -68,7 +68,7 @@ func cmdMigrationsPlan(c *cli.Context) error { return runMigrations(c, true) } -func runMigrations(c *cli.Context, isSimulating bool) error { +func runMigrations(c *cli.Context, isLogOnly bool) error { mtx, err := getMigrationContext(c) if err != nil { printError(err.Error()) @@ -93,15 +93,16 @@ func runMigrations(c *cli.Context, isSimulating bool) error { reportError(mtx.stateProvider.Config(), "up until stop", errors.New("No such migration file: "+stopAfter)) return errAbort } + prettyWidth := largestWithOf(all) for _, each := range all { log.Println(statusSeparator) leadingTitle := execDo - if isSimulating { + if isLogOnly { leadingTitle = execPlan } - log.Println(leadingTitle, pretty(each.Filename)) - if isSimulating { - log.Println(statusSeparator) + log.Printf("%s %-"+strconv.Itoa(prettyWidth)+"s (%s)\n", leadingTitle, pretty(each.Filename), each.Filename) + if isLogOnly { + log.Println("") if SimulateAll(each.DoSection, mtx.config().shellEnv(), true); err != nil { reportError(mtx.stateProvider.Config(), "plan do", err) return errAbort @@ -124,7 +125,6 @@ func runMigrations(c *cli.Context, isSimulating bool) error { log.Println(statusSeparator) break } - log.Println(statusSeparator) } return nil } @@ -160,6 +160,17 @@ func cmdMigrationsDown(c *cli.Context) error { return nil } +func largestWithOf(list []Migration) int { + prettyWidth := 0 + for _, each := range list { + pf := pretty(each.Filename) + if len(pf) > prettyWidth { + prettyWidth = len(pf) + } + } + return prettyWidth +} + func cmdMigrationsStatus(c *cli.Context) error { mtx, err := getMigrationContext(c) if err != nil { @@ -173,13 +184,7 @@ func cmdMigrationsStatus(c *cli.Context) error { } log.Println(statusSeparator) var last string - prettyWidth := 0 - for _, each := range all { - pf := pretty(each.Filename) - if len(pf) > prettyWidth { - prettyWidth = len(pf) - } - } + prettyWidth := largestWithOf(all) for _, each := range all { status := applied if each.Filename > mtx.lastApplied { diff --git a/main.go b/main.go index c041f7f..ca6569c 100644 --- a/main.go +++ b/main.go @@ -81,9 +81,9 @@ func newApp() *cli.App { }, { Name: "plan", - Usage: "Simulates the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one.", + Usage: "Log commands of the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one.", Action: func(c *cli.Context) error { - defer started(c, "plan = simulate pending migrations")() + defer started(c, "plan = log commands of pending migrations")() return cmdMigrationsPlan(c) }, Flags: []cli.Flag{migrationsFlag}, From 35c090dfb1b544d0f432d2d4ccfae3f16186fe2e Mon Sep 17 00:00:00 2001 From: emicklei_kramphub Date: Fri, 28 Sep 2018 10:13:33 +0200 Subject: [PATCH 3/3] process pr comments --- commands.go | 2 +- migration.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 495bcdb..3ae60fa 100644 --- a/commands.go +++ b/commands.go @@ -103,7 +103,7 @@ func runMigrations(c *cli.Context, isLogOnly bool) error { log.Printf("%s %-"+strconv.Itoa(prettyWidth)+"s (%s)\n", leadingTitle, pretty(each.Filename), each.Filename) if isLogOnly { log.Println("") - if SimulateAll(each.DoSection, mtx.config().shellEnv(), true); err != nil { + if LogAll(each.DoSection, mtx.config().shellEnv(), true); err != nil { reportError(mtx.stateProvider.Config(), "plan do", err) return errAbort } diff --git a/migration.go b/migration.go index 4cff473..5beb513 100644 --- a/migration.go +++ b/migration.go @@ -91,8 +91,8 @@ func ExecuteAll(commands []string, envs []string, verbose bool) error { return nil } -// SimulateAll logs expanded commands using the environment variables of both the config and the OS. -func SimulateAll(commands []string, envs []string, verbose bool) error { +// LogAll logs expanded commands using the environment variables of both the config and the OS. +func LogAll(commands []string, envs []string, verbose bool) error { allEnv := append(os.Environ(), envs...) envMap := map[string]string{} for _, each := range allEnv {