github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/command/untaint.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/command/clistate"
    10  )
    11  
    12  // UntaintCommand is a cli.Command implementation that manually untaints
    13  // a resource, marking it as primary and ready for service.
    14  type UntaintCommand struct {
    15  	Meta
    16  }
    17  
    18  func (c *UntaintCommand) Run(args []string) int {
    19  	args, err := c.Meta.process(args, false)
    20  	if err != nil {
    21  		return 1
    22  	}
    23  
    24  	var allowMissing bool
    25  	var module string
    26  	cmdFlags := c.Meta.flagSet("untaint")
    27  	cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module")
    28  	cmdFlags.StringVar(&module, "module", "", "module")
    29  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    30  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    31  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    32  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    33  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    34  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    35  	if err := cmdFlags.Parse(args); err != nil {
    36  		return 1
    37  	}
    38  
    39  	// Require the one argument for the resource to untaint
    40  	args = cmdFlags.Args()
    41  	if len(args) != 1 {
    42  		c.Ui.Error("The untaint command expects exactly one argument.")
    43  		cmdFlags.Usage()
    44  		return 1
    45  	}
    46  
    47  	name := args[0]
    48  	if module == "" {
    49  		module = "root"
    50  	} else {
    51  		module = "root." + module
    52  	}
    53  
    54  	// Load the backend
    55  	b, err := c.Backend(nil)
    56  	if err != nil {
    57  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
    58  		return 1
    59  	}
    60  
    61  	// Get the state
    62  	env := c.Workspace()
    63  	st, err := b.State(env)
    64  	if err != nil {
    65  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    66  		return 1
    67  	}
    68  	if err := st.RefreshState(); err != nil {
    69  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    70  		return 1
    71  	}
    72  
    73  	if c.stateLock {
    74  		stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
    75  		if err := stateLocker.Lock(st, "untaint"); err != nil {
    76  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
    77  			return 1
    78  		}
    79  		defer stateLocker.Unlock(nil)
    80  	}
    81  
    82  	// Get the actual state structure
    83  	s := st.State()
    84  	if s.Empty() {
    85  		if allowMissing {
    86  			return c.allowMissingExit(name, module)
    87  		}
    88  
    89  		c.Ui.Error(fmt.Sprintf(
    90  			"The state is empty. The most common reason for this is that\n" +
    91  				"an invalid state file path was given or Terraform has never\n " +
    92  				"been run for this infrastructure. Infrastructure must exist\n" +
    93  				"for it to be untainted."))
    94  		return 1
    95  	}
    96  
    97  	// Get the proper module holding the resource we want to untaint
    98  	modPath := strings.Split(module, ".")
    99  	mod := s.ModuleByPath(modPath)
   100  	if mod == nil {
   101  		if allowMissing {
   102  			return c.allowMissingExit(name, module)
   103  		}
   104  
   105  		c.Ui.Error(fmt.Sprintf(
   106  			"The module %s could not be found. There is nothing to untaint.",
   107  			module))
   108  		return 1
   109  	}
   110  
   111  	// If there are no resources in this module, it is an error
   112  	if len(mod.Resources) == 0 {
   113  		if allowMissing {
   114  			return c.allowMissingExit(name, module)
   115  		}
   116  
   117  		c.Ui.Error(fmt.Sprintf(
   118  			"The module %s has no resources. There is nothing to untaint.",
   119  			module))
   120  		return 1
   121  	}
   122  
   123  	// Get the resource we're looking for
   124  	rs, ok := mod.Resources[name]
   125  	if !ok {
   126  		if allowMissing {
   127  			return c.allowMissingExit(name, module)
   128  		}
   129  
   130  		c.Ui.Error(fmt.Sprintf(
   131  			"The resource %s couldn't be found in the module %s.",
   132  			name,
   133  			module))
   134  		return 1
   135  	}
   136  
   137  	// Untaint the resource
   138  	rs.Untaint()
   139  
   140  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   141  	if err := st.WriteState(s); err != nil {
   142  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   143  		return 1
   144  	}
   145  	if err := st.PersistState(); err != nil {
   146  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   147  		return 1
   148  	}
   149  
   150  	c.Ui.Output(fmt.Sprintf(
   151  		"The resource %s in the module %s has been successfully untainted!",
   152  		name, module))
   153  	return 0
   154  }
   155  
   156  func (c *UntaintCommand) Help() string {
   157  	helpText := `
   158  Usage: terraform untaint [options] name
   159  
   160    Manually unmark a resource as tainted, restoring it as the primary
   161    instance in the state.  This reverses either a manual 'terraform taint'
   162    or the result of provisioners failing on a resource.
   163  
   164    This will not modify your infrastructure. This command changes your
   165    state to unmark a resource as tainted.  This command can be undone by
   166    reverting the state backup file that is created, or by running
   167    'terraform taint' on the resource.
   168  
   169  Options:
   170  
   171    -allow-missing      If specified, the command will succeed (exit code 0)
   172                        even if the resource is missing.
   173  
   174    -backup=path        Path to backup the existing state file before
   175                        modifying. Defaults to the "-state-out" path with
   176                        ".backup" extension. Set to "-" to disable backup.
   177  
   178    -lock=true          Lock the state file when locking is supported.
   179  
   180    -lock-timeout=0s    Duration to retry a state lock.
   181  
   182    -module=path        The module path where the resource lives. By
   183                        default this will be root. Child modules can be specified
   184                        by names. Ex. "consul" or "consul.vpc" (nested modules).
   185  
   186    -no-color           If specified, output won't contain any color.
   187  
   188    -state=path         Path to read and save state (unless state-out
   189                        is specified). Defaults to "terraform.tfstate".
   190  
   191    -state-out=path     Path to write updated state file. By default, the
   192                        "-state" path will be used.
   193  
   194  `
   195  	return strings.TrimSpace(helpText)
   196  }
   197  
   198  func (c *UntaintCommand) Synopsis() string {
   199  	return "Manually unmark a resource as tainted"
   200  }
   201  
   202  func (c *UntaintCommand) allowMissingExit(name, module string) int {
   203  	c.Ui.Output(fmt.Sprintf(
   204  		"The resource %s in the module %s was not found, but\n"+
   205  			"-allow-missing is set, so we're exiting successfully.",
   206  		name, module))
   207  	return 0
   208  }