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