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