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