github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/taint.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/terraform" 11 ) 12 13 // TaintCommand is a cli.Command implementation that manually taints 14 // a resource, marking it for recreation. 15 type TaintCommand struct { 16 Meta 17 } 18 19 func (c *TaintCommand) 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("taint") 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 taint 41 args = cmdFlags.Args() 42 if len(args) != 1 { 43 c.Ui.Error("The taint 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 rsk, err := terraform.ParseResourceStateKey(name) 56 if err != nil { 57 c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err)) 58 return 1 59 } 60 61 if !rsk.Mode.Taintable() { 62 c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name)) 63 return 1 64 } 65 66 // Load the backend 67 b, err := c.Backend(nil) 68 if err != nil { 69 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 70 return 1 71 } 72 73 // Get the state 74 env := c.Workspace() 75 st, err := b.State(env) 76 if err != nil { 77 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 78 return 1 79 } 80 if err := st.RefreshState(); err != nil { 81 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 82 return 1 83 } 84 85 if c.stateLock { 86 stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) 87 if err := stateLocker.Lock(st, "taint"); err != nil { 88 c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) 89 return 1 90 } 91 defer stateLocker.Unlock(nil) 92 } 93 94 // Get the actual state structure 95 s := st.State() 96 if s.Empty() { 97 if allowMissing { 98 return c.allowMissingExit(name, module) 99 } 100 101 c.Ui.Error(fmt.Sprintf( 102 "The state is empty. The most common reason for this is that\n" + 103 "an invalid state file path was given or Terraform has never\n " + 104 "been run for this infrastructure. Infrastructure must exist\n" + 105 "for it to be tainted.")) 106 return 1 107 } 108 109 // Get the proper module we want to taint 110 modPath := strings.Split(module, ".") 111 mod := s.ModuleByPath(modPath) 112 if mod == nil { 113 if allowMissing { 114 return c.allowMissingExit(name, module) 115 } 116 117 c.Ui.Error(fmt.Sprintf( 118 "The module %s could not be found. There is nothing to taint.", 119 module)) 120 return 1 121 } 122 123 // If there are no resources in this module, it is an error 124 if len(mod.Resources) == 0 { 125 if allowMissing { 126 return c.allowMissingExit(name, module) 127 } 128 129 c.Ui.Error(fmt.Sprintf( 130 "The module %s has no resources. There is nothing to taint.", 131 module)) 132 return 1 133 } 134 135 // Get the resource we're looking for 136 rs, ok := mod.Resources[name] 137 if !ok { 138 if allowMissing { 139 return c.allowMissingExit(name, module) 140 } 141 142 c.Ui.Error(fmt.Sprintf( 143 "The resource %s couldn't be found in the module %s.", 144 name, 145 module)) 146 return 1 147 } 148 149 // Taint the resource 150 rs.Taint() 151 152 log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath()) 153 if err := st.WriteState(s); err != nil { 154 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 155 return 1 156 } 157 if err := st.PersistState(); err != nil { 158 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 159 return 1 160 } 161 162 c.Ui.Output(fmt.Sprintf( 163 "The resource %s in the module %s has been marked as tainted!", 164 name, module)) 165 return 0 166 } 167 168 func (c *TaintCommand) Help() string { 169 helpText := ` 170 Usage: terraform taint [options] name 171 172 Manually mark a resource as tainted, forcing a destroy and recreate 173 on the next plan/apply. 174 175 This will not modify your infrastructure. This command changes your 176 state to mark a resource as tainted so that during the next plan or 177 apply, that resource will be destroyed and recreated. This command on 178 its own will not modify infrastructure. This command can be undone by 179 reverting the state backup file that is created. 180 181 Options: 182 183 -allow-missing If specified, the command will succeed (exit code 0) 184 even if the resource is missing. 185 186 -backup=path Path to backup the existing state file before 187 modifying. Defaults to the "-state-out" path with 188 ".backup" extension. Set to "-" to disable backup. 189 190 -lock=true Lock the state file when locking is supported. 191 192 -lock-timeout=0s Duration to retry a state lock. 193 194 -module=path The module path where the resource lives. By 195 default this will be root. Child modules can be specified 196 by names. Ex. "consul" or "consul.vpc" (nested modules). 197 198 -no-color If specified, output won't contain any color. 199 200 -state=path Path to read and save state (unless state-out 201 is specified). Defaults to "terraform.tfstate". 202 203 -state-out=path Path to write updated state file. By default, the 204 "-state" path will be used. 205 206 ` 207 return strings.TrimSpace(helpText) 208 } 209 210 func (c *TaintCommand) Synopsis() string { 211 return "Manually mark a resource for recreation" 212 } 213 214 func (c *TaintCommand) allowMissingExit(name, module string) int { 215 c.Ui.Output(fmt.Sprintf( 216 "The resource %s in the module %s was not found, but\n"+ 217 "-allow-missing is set, so we're exiting successfully.", 218 name, module)) 219 return 0 220 }