diff --git a/command/taint.go b/command/taint.go index 4876b2eb37..02227d5b66 100644 --- a/command/taint.go +++ b/command/taint.go @@ -15,8 +15,10 @@ type TaintCommand struct { func (c *TaintCommand) Run(args []string) int { args = c.Meta.process(args, false) + var allowMissing bool var module string cmdFlags := c.Meta.flagSet("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") @@ -33,7 +35,13 @@ func (c *TaintCommand) Run(args []string) int { cmdFlags.Usage() return 1 } + name := args[0] + if module == "" { + module = "root" + } else { + module = "root." + module + } // Get the state that we'll be modifying state, err := c.State() @@ -45,6 +53,10 @@ func (c *TaintCommand) Run(args []string) int { // Get the actual state structure s := state.State() if s.Empty() { + if allowMissing { + return c.allowMissingExit(name, module) + } + c.Ui.Error(fmt.Sprintf( "The state is empty. The most common reason for this is that\n" + "an invalid state file path was given or Terraform has never\n " + @@ -54,14 +66,13 @@ func (c *TaintCommand) Run(args []string) int { } // Get the proper module we want to taint - if module == "" { - module = "root" - } else { - module = "root." + module - } modPath := strings.Split(module, ".") mod := s.ModuleByPath(modPath) if mod == nil { + if allowMissing { + return c.allowMissingExit(name, module) + } + c.Ui.Error(fmt.Sprintf( "The module %s could not be found. There is nothing to taint.", module)) @@ -70,6 +81,10 @@ func (c *TaintCommand) Run(args []string) int { // If there are no resources in this module, it is an error if len(mod.Resources) == 0 { + if allowMissing { + return c.allowMissingExit(name, module) + } + c.Ui.Error(fmt.Sprintf( "The module %s has no resources. There is nothing to taint.", module)) @@ -79,6 +94,10 @@ func (c *TaintCommand) Run(args []string) int { // Get the resource we're looking for rs, ok := mod.Resources[name] if !ok { + if allowMissing { + return c.allowMissingExit(name, module) + } + c.Ui.Error(fmt.Sprintf( "The resource %s couldn't be found in the module %s.", name, @@ -116,6 +135,9 @@ Usage: terraform taint [options] name Options: + -allow-missing If specified, the command will succeed (exit code 0) + even if the resource is missing. + -backup=path Path to backup the existing state file before modifying. Defaults to the "-state-out" path with ".backup" extension. Set to "-" to disable backup. @@ -139,3 +161,11 @@ Options: func (c *TaintCommand) Synopsis() string { return "Manually mark a resource for recreation" } + +func (c *TaintCommand) allowMissingExit(name, module string) int { + c.Ui.Output(fmt.Sprintf( + "The resource %s in the module %s was not found, but\n"+ + "-allow-missing is set, so we're exiting successfully.", + name, module)) + return 0 +} diff --git a/command/taint_test.go b/command/taint_test.go index f6e79a50cc..617555b003 100644 --- a/command/taint_test.go +++ b/command/taint_test.go @@ -187,6 +187,75 @@ func TestTaint_defaultState(t *testing.T) { testStateOutput(t, path, testTaintStr) } +func TestTaint_missing(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + statePath := testStateFile(t, state) + + ui := new(cli.MockUi) + c := &TaintCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "test_instance.bar", + } + if code := c.Run(args); code == 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.OutputWriter.String()) + } +} + +func TestTaint_missingAllow(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + statePath := testStateFile(t, state) + + ui := new(cli.MockUi) + c := &TaintCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{ + "-allow-missing", + "-state", statePath, + "test_instance.bar", + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + func TestTaint_stateOut(t *testing.T) { // Get a temp cwd tmp, cwd := testCwd(t) diff --git a/website/source/docs/commands/taint.html.markdown b/website/source/docs/commands/taint.html.markdown index c5685e2a73..05ea6301aa 100644 --- a/website/source/docs/commands/taint.html.markdown +++ b/website/source/docs/commands/taint.html.markdown @@ -40,6 +40,10 @@ The format of this argument is `TYPE.NAME`, such as `aws_instance.foo`. The command-line flags are all optional. The list of available flags are: +* `-allow-missing` - If specified, the command will succeed (exit code 0) + even if the resource is missing. The command can still error, but only + in critically erroneous cases. + * `-backup=path` - Path to the backup file. Defaults to `-state-out` with the ".backup" extension. Disabled by setting to "-".