From ef9054562e076d212d7aa6d25585e1e0c2e96d3d Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Wed, 21 Nov 2018 15:35:27 +0100 Subject: [PATCH] commands: make sure the correct flagset is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lot of commands used `c.Meta.flagSet()` to create the initial flagset for the command, while quite a few of them didn’t actually use or support the flags that are then added. So I updated a few commands to use `flag.NewFlagSet()` instead to only add the flags that are actually needed/supported. Additionally this prevents a few commands from using locking while they actually don’t need locking (as locking is enabled as a default in `c.Meta.flagSet()`. --- command/012_config_upgrade.go | 2 +- command/apply.go | 2 +- command/console.go | 3 +- command/debug_json2dot.go | 2 +- command/fmt.go | 4 +- command/get.go | 3 +- command/graph.go | 14 ++--- command/import.go | 2 +- command/init.go | 4 +- command/meta.go | 49 ++++++++-------- command/meta_backend_test.go | 2 +- command/meta_config.go | 3 + command/meta_test.go | 10 ++-- command/output.go | 3 +- command/plan.go | 5 +- command/providers.go | 2 +- command/refresh.go | 6 +- command/show.go | 5 +- command/state_list.go | 2 +- command/state_mv.go | 10 +++- command/state_pull.go | 2 +- command/state_push.go | 8 ++- command/state_rm.go | 10 +++- command/state_show.go | 11 +--- command/taint.go | 15 ++--- command/unlock.go | 10 ++-- command/untaint.go | 18 +++--- command/validate.go | 102 ++++++++++++++++++---------------- command/workspace_command.go | 2 +- command/workspace_delete.go | 53 +++++++++++------- command/workspace_list.go | 3 +- command/workspace_new.go | 50 ++++++++++------- command/workspace_select.go | 4 +- command/workspace_show.go | 2 +- 34 files changed, 227 insertions(+), 196 deletions(-) diff --git a/command/012_config_upgrade.go b/command/012_config_upgrade.go index dd601a1f3..28a57ff73 100644 --- a/command/012_config_upgrade.go +++ b/command/012_config_upgrade.go @@ -29,7 +29,7 @@ func (c *ZeroTwelveUpgradeCommand) Run(args []string) int { var skipConfirm, force bool - flags := c.Meta.flagSet("0.12upgrade") + flags := c.Meta.extendedFlagSet("0.12upgrade") flags.BoolVar(&skipConfirm, "yes", false, "skip confirmation prompt") flags.BoolVar(&force, "force", false, "override duplicate upgrade heuristic") if err := flags.Parse(args); err != nil { diff --git a/command/apply.go b/command/apply.go index 0ce426b00..01751b7be 100644 --- a/command/apply.go +++ b/command/apply.go @@ -38,7 +38,7 @@ func (c *ApplyCommand) Run(args []string) int { cmdName = "destroy" } - cmdFlags := c.Meta.flagSet(cmdName) + cmdFlags := c.Meta.extendedFlagSet(cmdName) cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying") if c.Destroy { cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve") diff --git a/command/console.go b/command/console.go index 3c1f9d356..cf309621f 100644 --- a/command/console.go +++ b/command/console.go @@ -25,7 +25,7 @@ func (c *ConsoleCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("console") + cmdFlags := c.Meta.defaultFlagSet("console") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -74,6 +74,7 @@ func (c *ConsoleCommand) Run(args []string) int { c.showDiagnostics(diags) return 1 } + { var moreDiags tfdiags.Diagnostics opReq.Variables, moreDiags = c.collectVariableValues() diff --git a/command/debug_json2dot.go b/command/debug_json2dot.go index 64361bcf1..5b05e1773 100644 --- a/command/debug_json2dot.go +++ b/command/debug_json2dot.go @@ -20,7 +20,7 @@ func (c *DebugJSON2DotCommand) Run(args []string) int { if err != nil { return 1 } - cmdFlags := c.Meta.flagSet("debug json2dot") + cmdFlags := c.Meta.extendedFlagSet("debug json2dot") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp diff --git a/command/fmt.go b/command/fmt.go index 3e86a8c54..e3a785326 100644 --- a/command/fmt.go +++ b/command/fmt.go @@ -2,7 +2,6 @@ package command import ( "bytes" - "flag" "fmt" "io" "io/ioutil" @@ -46,14 +45,13 @@ func (c *FmtCommand) Run(args []string) int { return 1 } - cmdFlags := flag.NewFlagSet("fmt", flag.ContinueOnError) + cmdFlags := c.Meta.defaultFlagSet("fmt") cmdFlags.BoolVar(&c.list, "list", true, "list") cmdFlags.BoolVar(&c.write, "write", true, "write") cmdFlags.BoolVar(&c.diff, "diff", false, "diff") cmdFlags.BoolVar(&c.check, "check", false, "check") cmdFlags.BoolVar(&c.recursive, "recursive", false, "recursive") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } - if err := cmdFlags.Parse(args); err != nil { return 1 } diff --git a/command/get.go b/command/get.go index ba8729d79..c588108d1 100644 --- a/command/get.go +++ b/command/get.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "strings" @@ -22,7 +21,7 @@ func (c *GetCommand) Run(args []string) int { return 1 } - cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError) + cmdFlags := c.Meta.defaultFlagSet("get") cmdFlags.BoolVar(&update, "update", false, "update") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { diff --git a/command/graph.go b/command/graph.go index d923ce437..b6f051404 100644 --- a/command/graph.go +++ b/command/graph.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "strings" @@ -20,21 +19,21 @@ type GraphCommand struct { } func (c *GraphCommand) Run(args []string) int { - var moduleDepth int - var verbose bool var drawCycles bool var graphTypeStr string + var moduleDepth int + var verbose bool args, err := c.Meta.process(args, false) if err != nil { return 1 } - cmdFlags := flag.NewFlagSet("graph", flag.ContinueOnError) - cmdFlags.IntVar(&moduleDepth, "module-depth", -1, "module-depth") - cmdFlags.BoolVar(&verbose, "verbose", false, "verbose") + cmdFlags := c.Meta.defaultFlagSet("graph") cmdFlags.BoolVar(&drawCycles, "draw-cycles", false, "draw-cycles") cmdFlags.StringVar(&graphTypeStr, "type", "", "type") + cmdFlags.IntVar(&moduleDepth, "module-depth", -1, "module-depth") + cmdFlags.BoolVar(&verbose, "verbose", false, "verbose") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -187,12 +186,9 @@ Options: -module-depth=n Specifies the depth of modules to show in the output. By default this is -1, which will expand all. - -no-color If specified, output won't contain any color. - -type=plan Type of graph to output. Can be: plan, plan-destroy, apply, validate, input, refresh. - ` return strings.TrimSpace(helpText) } diff --git a/command/import.go b/command/import.go index eadc201b7..0b6bde83e 100644 --- a/command/import.go +++ b/command/import.go @@ -37,7 +37,7 @@ func (c *ImportCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("import") + cmdFlags := c.Meta.extendedFlagSet("import") cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", 0, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") diff --git a/command/init.go b/command/init.go index e8a8c24e1..bf6f1234c 100644 --- a/command/init.go +++ b/command/init.go @@ -50,7 +50,8 @@ func (c *InitCommand) Run(args []string) int { if err != nil { return 1 } - cmdFlags := c.flagSet("init") + + cmdFlags := c.Meta.extendedFlagSet("init") cmdFlags.BoolVar(&flagBackend, "backend", true, "") cmdFlags.Var(flagConfigExtra, "backend-config", "") cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init") @@ -63,7 +64,6 @@ func (c *InitCommand) Run(args []string) int { cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory") cmdFlags.BoolVar(&flagVerifyPlugins, "verify-plugins", true, "verify plugins") - cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 diff --git a/command/meta.go b/command/meta.go index b73989c35..53c59831b 100644 --- a/command/meta.go +++ b/command/meta.go @@ -136,8 +136,6 @@ type Meta struct { // parallelism is used to control the number of concurrent operations // allowed when walking the graph // - // shadow is used to enable/disable the shadow graph - // // provider is to specify specific resource providers // // stateLock is set to false to disable state locking @@ -153,7 +151,6 @@ type Meta struct { stateOutPath string backupPath string parallelism int - shadow bool provider string stateLock bool stateLockTimeout time.Duration @@ -350,25 +347,9 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { return &opts } -// flags adds the meta flags to the given FlagSet. -func (m *Meta) flagSet(n string) *flag.FlagSet { +// defaultFlagSet creates a default flag set for commands. +func (m *Meta) defaultFlagSet(n string) *flag.FlagSet { f := flag.NewFlagSet(n, flag.ContinueOnError) - f.BoolVar(&m.input, "input", true, "input") - f.Var((*FlagTargetSlice)(&m.targets), "target", "resource to target") - - if m.variableArgs.items == nil { - m.variableArgs = newRawFlags("-var") - } - varValues := m.variableArgs.Alias("-var") - varFiles := m.variableArgs.Alias("-var-file") - f.Var(varValues, "var", "variables") - f.Var(varFiles, "var-file", "variable file") - - // Advanced (don't need documentation, or unlikely to be set) - f.BoolVar(&m.shadow, "shadow", true, "shadow graph") - - // Experimental features - experiment.Flag(f) // Create an io.Writer that writes to our Ui properly for errors. // This is kind of a hack, but it does the job. Basically: create @@ -393,8 +374,30 @@ func (m *Meta) flagSet(n string) *flag.FlagSet { // Set the default Usage to empty f.Usage = func() {} - // command that bypass locking will supply their own flag on this var, but - // set the initial meta value to true as a failsafe. + return f +} + +// extendedFlagSet adds custom flags that are mostly used by commands +// that are used to run an operation like plan or apply. +func (m *Meta) extendedFlagSet(n string) *flag.FlagSet { + f := m.defaultFlagSet(n) + + f.BoolVar(&m.input, "input", true, "input") + f.Var((*FlagTargetSlice)(&m.targets), "target", "resource to target") + + if m.variableArgs.items == nil { + m.variableArgs = newRawFlags("-var") + } + varValues := m.variableArgs.Alias("-var") + varFiles := m.variableArgs.Alias("-var-file") + f.Var(varValues, "var", "variables") + f.Var(varFiles, "var-file", "variable file") + + // Experimental features + experiment.Flag(f) + + // commands that bypass locking will supply their own flag on this var, + // but set the initial meta value to true as a failsafe. m.stateLock = true return f diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index b6e23a8f0..1d363cd8e 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -1864,7 +1864,7 @@ func testMetaBackend(t *testing.T, args []string) *Meta { var m Meta m.Ui = new(cli.MockUi) m.process(args, true) - f := m.flagSet("test") + f := m.extendedFlagSet("test") if err := f.Parse(args); err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/command/meta_config.go b/command/meta_config.go index b7619dccf..7c17c923d 100644 --- a/command/meta_config.go +++ b/command/meta_config.go @@ -389,6 +389,9 @@ func (f rawFlags) Empty() bool { } func (f rawFlags) AllItems() []rawFlag { + if f.items == nil { + return nil + } return *f.items } diff --git a/command/meta_test.go b/command/meta_test.go index b03fdff64..978dca8c7 100644 --- a/command/meta_test.go +++ b/command/meta_test.go @@ -73,7 +73,7 @@ func TestMetaInputMode(t *testing.T) { m := new(Meta) args := []string{} - fs := m.flagSet("foo") + fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } @@ -92,7 +92,7 @@ func TestMetaInputMode_envVar(t *testing.T) { m := new(Meta) args := []string{} - fs := m.flagSet("foo") + fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } @@ -124,7 +124,7 @@ func TestMetaInputMode_disable(t *testing.T) { m := new(Meta) args := []string{"-input=false"} - fs := m.flagSet("foo") + fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } @@ -160,7 +160,7 @@ func TestMetaInputMode_defaultVars(t *testing.T) { t.Fatalf("err: %s", err) } - fs := m.flagSet("foo") + fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } @@ -177,7 +177,7 @@ func TestMetaInputMode_vars(t *testing.T) { m := new(Meta) args := []string{"-var", "foo=bar"} - fs := m.flagSet("foo") + fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/output.go b/command/output.go index 23484ddeb..090f320c6 100644 --- a/command/output.go +++ b/command/output.go @@ -3,7 +3,6 @@ package command import ( "bytes" "encoding/json" - "flag" "fmt" "sort" "strings" @@ -31,7 +30,7 @@ func (c *OutputCommand) Run(args []string) int { var module string var jsonOutput bool - cmdFlags := flag.NewFlagSet("output", flag.ContinueOnError) + cmdFlags := c.Meta.defaultFlagSet("output") cmdFlags.BoolVar(&jsonOutput, "json", false, "json") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&module, "module", "", "module") diff --git a/command/plan.go b/command/plan.go index 1e1664609..836b8d14e 100644 --- a/command/plan.go +++ b/command/plan.go @@ -25,12 +25,11 @@ func (c *PlanCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("plan") + cmdFlags := c.Meta.extendedFlagSet("plan") cmdFlags.BoolVar(&destroy, "destroy", false, "destroy") cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.StringVar(&outPath, "out", "", "path") - cmdFlags.IntVar( - &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") + cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") diff --git a/command/providers.go b/command/providers.go index 7faefcf7b..095518050 100644 --- a/command/providers.go +++ b/command/providers.go @@ -29,7 +29,7 @@ func (c *ProvidersCommand) Synopsis() string { func (c *ProvidersCommand) Run(args []string) int { c.Meta.process(args, false) - cmdFlags := c.Meta.flagSet("providers") + cmdFlags := c.Meta.defaultFlagSet("providers") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 diff --git a/command/refresh.go b/command/refresh.go index 309d61d27..79dab83c0 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -21,7 +21,7 @@ func (c *RefreshCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("refresh") + cmdFlags := c.Meta.extendedFlagSet("refresh") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", 0, "parallelism") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") @@ -72,13 +72,15 @@ func (c *RefreshCommand) Run(args []string) int { // Build the operation opReq := c.Operation(b) - opReq.Type = backend.OperationTypeRefresh opReq.ConfigDir = configPath + opReq.Type = backend.OperationTypeRefresh + opReq.ConfigLoader, err = c.initConfigLoader() if err != nil { c.showDiagnostics(err) return 1 } + { var moreDiags tfdiags.Diagnostics opReq.Variables, moreDiags = c.collectVariableValues() diff --git a/command/show.go b/command/show.go index 66140a4a3..477bd8978 100644 --- a/command/show.go +++ b/command/show.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "os" "strings" @@ -28,8 +27,7 @@ func (c *ShowCommand) Run(args []string) int { return 1 } - cmdFlags := flag.NewFlagSet("show", flag.ContinueOnError) - + cmdFlags := c.Meta.defaultFlagSet("show") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -87,6 +85,7 @@ func (c *ShowCommand) Run(args []string) int { return 1 } + // Get the schemas from the context schemas := ctx.Schemas() env := c.Workspace() diff --git a/command/state_list.go b/command/state_list.go index 8925ff5a0..0ee2d236b 100644 --- a/command/state_list.go +++ b/command/state_list.go @@ -21,7 +21,7 @@ func (c *StateListCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("state list") + cmdFlags := c.Meta.defaultFlagSet("state list") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.") if err := cmdFlags.Parse(args); err != nil { diff --git a/command/state_mv.go b/command/state_mv.go index db6ae74a0..05fff8c7b 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -24,11 +24,13 @@ func (c *StateMvCommand) Run(args []string) int { var backupPathOut, statePathOut string var dryRun bool - cmdFlags := c.Meta.flagSet("state mv") + cmdFlags := c.Meta.defaultFlagSet("state mv") cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") - cmdFlags.StringVar(&c.statePath, "state", "", "path") cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup") + cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states") + cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") + cmdFlags.StringVar(&c.statePath, "state", "", "path") cmdFlags.StringVar(&statePathOut, "state-out", "", "path") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp @@ -301,6 +303,10 @@ Options: to be specified if -state-out is set to a different path than -state. + -lock=true Lock the state files when locking is supported. + + -lock-timeout=0s Duration to retry a state lock. + -state=PATH Path to the source state file. Defaults to the configured backend, or "terraform.tfstate" diff --git a/command/state_pull.go b/command/state_pull.go index 0ac531aa1..fbd546b45 100644 --- a/command/state_pull.go +++ b/command/state_pull.go @@ -22,7 +22,7 @@ func (c *StatePullCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("state pull") + cmdFlags := c.Meta.defaultFlagSet("state pull") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp } diff --git a/command/state_push.go b/command/state_push.go index 5df0af528..8de0ad9f7 100644 --- a/command/state_push.go +++ b/command/state_push.go @@ -26,8 +26,10 @@ func (c *StatePushCommand) Run(args []string) int { } var flagForce bool - cmdFlags := c.Meta.flagSet("state push") + cmdFlags := c.Meta.defaultFlagSet("state push") cmdFlags.BoolVar(&flagForce, "force", false, "") + cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") + cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp } @@ -139,6 +141,10 @@ Options: -force Write the state even if lineages don't match or the remote serial is higher. + -lock=true Lock the state file when locking is supported. + + -lock-timeout=0s Duration to retry a state lock. + ` return strings.TrimSpace(helpText) } diff --git a/command/state_rm.go b/command/state_rm.go index 0c57a33c1..f3965810d 100644 --- a/command/state_rm.go +++ b/command/state_rm.go @@ -22,15 +22,17 @@ func (c *StateRmCommand) Run(args []string) int { } var dryRun bool - cmdFlags := c.Meta.flagSet("state show") + cmdFlags := c.Meta.defaultFlagSet("state rm") cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") + cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") + cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.StringVar(&c.statePath, "state", "", "path") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp } - args = cmdFlags.Args() + args = cmdFlags.Args() if len(args) < 1 { c.Ui.Error("At least one address is required.\n") return cli.RunResultHelp @@ -165,6 +167,10 @@ Options: will write it to the same path as the statefile with a backup extension. + -lock=true Lock the state file when locking is supported. + + -lock-timeout=0s Duration to retry a state lock. + -state=PATH Path to the source state file. Defaults to the configured backend, or "terraform.tfstate" diff --git a/command/state_show.go b/command/state_show.go index 79a3afc49..a20dc8c59 100644 --- a/command/state_show.go +++ b/command/state_show.go @@ -24,7 +24,7 @@ func (c *StateShowCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("state show") + cmdFlags := c.Meta.defaultFlagSet("state show") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp @@ -66,6 +66,7 @@ func (c *StateShowCommand) Run(args []string) int { // Build the operation (required to get the schemas) opReq := c.Operation(b) opReq.ConfigDir = cwd + opReq.ConfigLoader, err = c.initConfigLoader() if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing config loader: %s", err)) @@ -79,14 +80,6 @@ func (c *StateShowCommand) Run(args []string) int { return 1 } - // Make sure to unlock the state - defer func() { - err := opReq.StateLocker.Unlock(nil) - if err != nil { - c.Ui.Error(err.Error()) - } - }() - // Get the schemas from the context schemas := ctx.Schemas() diff --git a/command/taint.go b/command/taint.go index ec9188d3a..80ecfb70d 100644 --- a/command/taint.go +++ b/command/taint.go @@ -5,10 +5,9 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/states" - "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/command/clistate" + "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) @@ -24,16 +23,16 @@ func (c *TaintCommand) Run(args []string) int { return 1 } - var allowMissing bool var module string - cmdFlags := c.Meta.flagSet("taint") + var allowMissing bool + cmdFlags := c.Meta.defaultFlagSet("taint") cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module") - cmdFlags.StringVar(&module, "module", "", "module") - cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") - cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") + cmdFlags.StringVar(&module, "module", "", "module") + cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -199,8 +198,6 @@ Options: -lock-timeout=0s Duration to retry a state lock. - -no-color If specified, output won't contain any color. - -state=path Path to read and save state (unless state-out is specified). Defaults to "terraform.tfstate". diff --git a/command/unlock.go b/command/unlock.go index cf974030a..8d8ec3034 100644 --- a/command/unlock.go +++ b/command/unlock.go @@ -23,8 +23,8 @@ func (c *UnlockCommand) Run(args []string) int { return 1 } - force := false - cmdFlags := c.Meta.flagSet("force-unlock") + var force bool + cmdFlags := c.Meta.defaultFlagSet("force-unlock") cmdFlags.BoolVar(&force, "force", false, "force") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -68,13 +68,13 @@ func (c *UnlockCommand) Run(args []string) int { } env := c.Workspace() - st, err := b.StateMgr(env) + stateMgr, err := b.StateMgr(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 } - _, isLocal := st.(*statemgr.Filesystem) + _, isLocal := stateMgr.(*statemgr.Filesystem) if !force { // Forcing this doesn't do anything, but doesn't break anything either, @@ -103,7 +103,7 @@ func (c *UnlockCommand) Run(args []string) int { } } - if err := st.Unlock(lockID); err != nil { + if err := stateMgr.Unlock(lockID); err != nil { c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err)) return 1 } diff --git a/command/untaint.go b/command/untaint.go index ce1733642..a23f3c3ff 100644 --- a/command/untaint.go +++ b/command/untaint.go @@ -5,12 +5,10 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/states" - - "github.com/hashicorp/terraform/tfdiags" - "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/command/clistate" + "github.com/hashicorp/terraform/states" + "github.com/hashicorp/terraform/tfdiags" ) // UntaintCommand is a cli.Command implementation that manually untaints @@ -25,16 +23,16 @@ func (c *UntaintCommand) Run(args []string) int { return 1 } - var allowMissing bool var module string - cmdFlags := c.Meta.flagSet("untaint") + var allowMissing bool + cmdFlags := c.Meta.defaultFlagSet("untaint") cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module") - cmdFlags.StringVar(&module, "module", "", "module") - cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") - cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") + cmdFlags.StringVar(&module, "module", "", "module") + cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -202,8 +200,6 @@ Options: default this will be root. Child modules can be specified by names. Ex. "consul" or "consul.vpc" (nested modules). - -no-color If specified, output won't contain any color. - -state=path Path to read and save state (unless state-out is specified). Defaults to "terraform.tfstate". diff --git a/command/validate.go b/command/validate.go index 7c9188ac7..3041c4785 100644 --- a/command/validate.go +++ b/command/validate.go @@ -25,13 +25,18 @@ func (c *ValidateCommand) Run(args []string) int { return 1 } - var jsonOutput bool - - cmdFlags := c.Meta.flagSet("validate") - cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output") - cmdFlags.Usage = func() { - c.Ui.Error(c.Help()) + if c.Meta.variableArgs.items == nil { + c.Meta.variableArgs = newRawFlags("-var") } + varValues := c.Meta.variableArgs.Alias("-var") + varFiles := c.Meta.variableArgs.Alias("-var-file") + + var jsonOutput bool + cmdFlags := c.Meta.defaultFlagSet("validate") + cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output") + cmdFlags.Var(varValues, "var", "variables") + cmdFlags.Var(varFiles, "var-file", "variable file") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } @@ -68,49 +73,6 @@ func (c *ValidateCommand) Run(args []string) int { return c.showResults(diags, jsonOutput) } -func (c *ValidateCommand) Synopsis() string { - return "Validates the Terraform files" -} - -func (c *ValidateCommand) Help() string { - helpText := ` -Usage: terraform validate [options] [dir] - - Validate the configuration files in a directory, referring only to the - configuration and not accessing any remote services such as remote state, - provider APIs, etc. - - Validate runs checks that verify whether a configuration is - internally-consistent, regardless of any provided variables or existing - state. It is thus primarily useful for general verification of reusable - modules, including correctness of attribute names and value types. - - To verify configuration in the context of a particular run (a particular - target workspace, operation variables, etc), use the following command - instead: - terraform plan -validate-only - - It is safe to run this command automatically, for example as a post-save - check in a text editor or as a test step for a re-usable module in a CI - system. - - Validation requires an initialized working directory with any referenced - plugins and modules installed. To initialize a working directory for - validation without accessing any configured remote backend, use: - terraform init -backend=false - - If dir is not specified, then the current directory will be used. - -Options: - - -json Produce output in a machine-readable JSON format, suitable for - use in e.g. text editor integrations. - - -no-color If specified, output won't contain any color. -` - return strings.TrimSpace(helpText) -} - func (c *ValidateCommand) validate(dir string) tfdiags.Diagnostics { var diags tfdiags.Diagnostics @@ -254,3 +216,45 @@ func (c *ValidateCommand) showResults(diags tfdiags.Diagnostics, jsonOutput bool } return 0 } + +func (c *ValidateCommand) Synopsis() string { + return "Validates the Terraform files" +} + +func (c *ValidateCommand) Help() string { + helpText := ` +Usage: terraform validate [options] [dir] + + Validate the configuration files in a directory, referring only to the + configuration and not accessing any remote services such as remote state, + provider APIs, etc. + + Validate runs checks that verify whether a configuration is + internally-consistent, regardless of any provided variables or existing + state. It is thus primarily useful for general verification of reusable + modules, including correctness of attribute names and value types. + + To verify configuration in the context of a particular run (a particular + target workspace, operation variables, etc), use the following command + instead: + terraform plan -validate-only + + It is safe to run this command automatically, for example as a post-save + check in a text editor or as a test step for a re-usable module in a CI + system. + + Validation requires an initialized working directory with any referenced + plugins and modules installed. To initialize a working directory for + validation without accessing any configured remote backend, use: + terraform init -backend=false + + If dir is not specified, then the current directory will be used. + +Options: + + -json Produce output in a machine-readable JSON format, suitable for + use in e.g. text editor integrations. + +` + return strings.TrimSpace(helpText) +} diff --git a/command/workspace_command.go b/command/workspace_command.go index cfe2bcf13..ebf3aece8 100644 --- a/command/workspace_command.go +++ b/command/workspace_command.go @@ -22,7 +22,7 @@ func (c *WorkspaceCommand) Run(args []string) int { envCommandShowWarning(c.Ui, c.LegacyName) - cmdFlags := c.Meta.flagSet("workspace") + cmdFlags := c.Meta.extendedFlagSet("workspace") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } c.Ui.Output(c.Help()) diff --git a/command/workspace_delete.go b/command/workspace_delete.go index 0604e3f87..e363b5e6a 100644 --- a/command/workspace_delete.go +++ b/command/workspace_delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/tfdiags" @@ -24,23 +25,28 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { envCommandShowWarning(c.Ui, c.LegacyName) - force := false - cmdFlags := c.Meta.flagSet("workspace") + var force bool + var stateLock bool + var stateLockTimeout time.Duration + cmdFlags := c.Meta.defaultFlagSet("workspace delete") cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace") + cmdFlags.BoolVar(&stateLock, "lock", true, "lock state") + cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } + args = cmdFlags.Args() if len(args) == 0 { c.Ui.Error("expected NAME.\n") return cli.RunResultHelp } - delEnv := args[0] + workspace := args[0] - if !validWorkspaceName(delEnv) { - c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv)) + if !validWorkspaceName(workspace) { + c.Ui.Error(fmt.Sprintf(envInvalidName, workspace)) return 1 } @@ -69,41 +75,41 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { return 1 } - states, err := b.Workspaces() + workspaces, err := b.Workspaces() if err != nil { c.Ui.Error(err.Error()) return 1 } exists := false - for _, s := range states { - if delEnv == s { + for _, ws := range workspaces { + if workspace == ws { exists = true break } } if !exists { - c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), delEnv)) + c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), workspace)) return 1 } - if delEnv == c.Workspace() { - c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), delEnv)) + if workspace == c.Workspace() { + c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), workspace)) return 1 } // we need the actual state to see if it's empty - sMgr, err := b.StateMgr(delEnv) + stateMgr, err := b.StateMgr(workspace) if err != nil { c.Ui.Error(err.Error()) return 1 } var stateLocker clistate.Locker - if c.stateLock { - stateLocker = clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) - if err := stateLocker.Lock(sMgr, "workspace_delete"); err != nil { + if stateLock { + stateLocker = clistate.NewLocker(context.Background(), stateLockTimeout, c.Ui, c.Colorize()) + if err := stateLocker.Lock(stateMgr, "workspace_delete"); err != nil { c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) return 1 } @@ -111,15 +117,15 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { stateLocker = clistate.NewNoopLocker() } - if err := sMgr.RefreshState(); err != nil { + if err := stateMgr.RefreshState(); err != nil { c.Ui.Error(err.Error()) return 1 } - hasResources := sMgr.State().HasResources() + hasResources := stateMgr.State().HasResources() if hasResources && !force { - c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), delEnv)) + c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), workspace)) return 1 } @@ -134,7 +140,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { // be delegated from the Backend to the State itself. stateLocker.Unlock(nil) - err = b.DeleteWorkspace(delEnv) + err = b.DeleteWorkspace(workspace) if err != nil { c.Ui.Error(err.Error()) return 1 @@ -142,14 +148,14 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { c.Ui.Output( c.Colorize().Color( - fmt.Sprintf(envDeleted, delEnv), + fmt.Sprintf(envDeleted, workspace), ), ) if hasResources { c.Ui.Output( c.Colorize().Color( - fmt.Sprintf(envWarnNotEmpty, delEnv), + fmt.Sprintf(envWarnNotEmpty, workspace), ), ) } @@ -181,6 +187,11 @@ Usage: terraform workspace delete [OPTIONS] NAME [DIR] Options: -force remove a non-empty workspace. + + -lock=true Lock the state file when locking is supported. + + -lock-timeout=0s Duration to retry a state lock. + ` return strings.TrimSpace(helpText) } diff --git a/command/workspace_list.go b/command/workspace_list.go index 080b654a1..93eeb6c13 100644 --- a/command/workspace_list.go +++ b/command/workspace_list.go @@ -21,7 +21,7 @@ func (c *WorkspaceListCommand) Run(args []string) int { envCommandShowWarning(c.Ui, c.LegacyName) - cmdFlags := c.Meta.flagSet("workspace list") + cmdFlags := c.Meta.defaultFlagSet("workspace list") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -93,6 +93,7 @@ func (c *WorkspaceListCommand) Help() string { Usage: terraform workspace list [DIR] List Terraform workspaces. + ` return strings.TrimSpace(helpText) } diff --git a/command/workspace_new.go b/command/workspace_new.go index 2b8cac099..e26984d8e 100644 --- a/command/workspace_new.go +++ b/command/workspace_new.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "strings" + "time" "github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/states/statefile" @@ -26,30 +27,34 @@ func (c *WorkspaceNewCommand) Run(args []string) int { envCommandShowWarning(c.Ui, c.LegacyName) - statePath := "" - - cmdFlags := c.Meta.flagSet("workspace new") + var stateLock bool + var stateLockTimeout time.Duration + var statePath string + cmdFlags := c.Meta.defaultFlagSet("workspace new") + cmdFlags.BoolVar(&stateLock, "lock", true, "lock state") + cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.StringVar(&statePath, "state", "", "terraform state file") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } + args = cmdFlags.Args() if len(args) == 0 { c.Ui.Error("Expected a single argument: NAME.\n") return cli.RunResultHelp } - newEnv := args[0] + workspace := args[0] - if !validWorkspaceName(newEnv) { - c.Ui.Error(fmt.Sprintf(envInvalidName, newEnv)) + if !validWorkspaceName(workspace) { + c.Ui.Error(fmt.Sprintf(envInvalidName, workspace)) return 1 } // You can't ask to create a workspace when you're overriding the // workspace name to be something different. - if current, isOverridden := c.WorkspaceOverridden(); current != newEnv && isOverridden { + if current, isOverridden := c.WorkspaceOverridden(); current != workspace && isOverridden { c.Ui.Error(envIsOverriddenNewError) return 1 } @@ -79,32 +84,32 @@ func (c *WorkspaceNewCommand) Run(args []string) int { return 1 } - states, err := b.Workspaces() + workspaces, err := b.Workspaces() if err != nil { c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err)) return 1 } - for _, s := range states { - if newEnv == s { - c.Ui.Error(fmt.Sprintf(envExists, newEnv)) + for _, ws := range workspaces { + if workspace == ws { + c.Ui.Error(fmt.Sprintf(envExists, workspace)) return 1 } } - _, err = b.StateMgr(newEnv) + _, err = b.StateMgr(workspace) if err != nil { c.Ui.Error(err.Error()) return 1 } // now set the current workspace locally - if err := c.SetWorkspace(newEnv); err != nil { + if err := c.SetWorkspace(workspace); err != nil { c.Ui.Error(fmt.Sprintf("Error selecting new workspace: %s", err)) return 1 } c.Ui.Output(c.Colorize().Color(fmt.Sprintf( - strings.TrimSpace(envCreated), newEnv))) + strings.TrimSpace(envCreated), workspace))) if statePath == "" { // if we're not loading a state, then we're done @@ -112,15 +117,15 @@ func (c *WorkspaceNewCommand) Run(args []string) int { } // load the new Backend state - sMgr, err := b.StateMgr(newEnv) + stateMgr, err := b.StateMgr(workspace) if err != nil { c.Ui.Error(err.Error()) return 1 } - if c.stateLock { - stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) - if err := stateLocker.Lock(sMgr, "workspace_delete"); err != nil { + if stateLock { + stateLocker := clistate.NewLocker(context.Background(), stateLockTimeout, c.Ui, c.Colorize()) + if err := stateLocker.Lock(stateMgr, "workspace_new"); err != nil { c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) return 1 } @@ -141,12 +146,12 @@ func (c *WorkspaceNewCommand) Run(args []string) int { } // save the existing state in the new Backend. - err = sMgr.WriteState(stateFile.State) + err = stateMgr.WriteState(stateFile.State) if err != nil { c.Ui.Error(err.Error()) return 1 } - err = sMgr.PersistState() + err = stateMgr.PersistState() if err != nil { c.Ui.Error(err.Error()) return 1 @@ -178,7 +183,12 @@ Usage: terraform workspace new [OPTIONS] NAME [DIR] Options: + -lock=true Lock the state file when locking is supported. + + -lock-timeout=0s Duration to retry a state lock. + -state=path Copy an existing state file into the new workspace. + ` return strings.TrimSpace(helpText) } diff --git a/command/workspace_select.go b/command/workspace_select.go index 432eea30a..28b1755db 100644 --- a/command/workspace_select.go +++ b/command/workspace_select.go @@ -22,11 +22,12 @@ func (c *WorkspaceSelectCommand) Run(args []string) int { envCommandShowWarning(c.Ui, c.LegacyName) - cmdFlags := c.Meta.flagSet("workspace select") + cmdFlags := c.Meta.defaultFlagSet("workspace select") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } + args = cmdFlags.Args() if len(args) == 0 { c.Ui.Error("Expected a single argument: NAME.\n") @@ -131,6 +132,7 @@ func (c *WorkspaceSelectCommand) Help() string { Usage: terraform workspace select NAME [DIR] Select a different Terraform workspace. + ` return strings.TrimSpace(helpText) } diff --git a/command/workspace_show.go b/command/workspace_show.go index cca688d78..cccc4f586 100644 --- a/command/workspace_show.go +++ b/command/workspace_show.go @@ -16,7 +16,7 @@ func (c *WorkspaceShowCommand) Run(args []string) int { return 1 } - cmdFlags := c.Meta.flagSet("workspace show") + cmdFlags := c.Meta.extendedFlagSet("workspace show") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1