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