github.com/ggriffiths/terraform@v0.9.0-beta1.0.20170222213024-79c4935604cb/command/taint.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 clistate "github.com/hashicorp/terraform/command/state" 9 "github.com/hashicorp/terraform/state" 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 = c.Meta.process(args, false) 21 22 var allowMissing bool 23 var module string 24 cmdFlags := c.Meta.flagSet("taint") 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.Usage = func() { c.Ui.Error(c.Help()) } 32 if err := cmdFlags.Parse(args); err != nil { 33 return 1 34 } 35 36 // Require the one argument for the resource to taint 37 args = cmdFlags.Args() 38 if len(args) != 1 { 39 c.Ui.Error("The taint command expects exactly one argument.") 40 cmdFlags.Usage() 41 return 1 42 } 43 44 name := args[0] 45 if module == "" { 46 module = "root" 47 } else { 48 module = "root." + module 49 } 50 51 rsk, err := terraform.ParseResourceStateKey(name) 52 if err != nil { 53 c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err)) 54 return 1 55 } 56 57 if !rsk.Mode.Taintable() { 58 c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name)) 59 return 1 60 } 61 62 // Load the backend 63 b, err := c.Backend(nil) 64 if err != nil { 65 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 66 return 1 67 } 68 69 // Get the state 70 st, err := b.State() 71 if err != nil { 72 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 73 return 1 74 } 75 76 if c.Meta.stateLock { 77 lockInfo := state.NewLockInfo() 78 lockInfo.Operation = "taint" 79 lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize()) 80 if err != nil { 81 c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) 82 return 1 83 } 84 85 defer clistate.Unlock(st, lockID, c.Ui, c.Colorize()) 86 } 87 88 // Get the actual state structure 89 s := st.State() 90 if s.Empty() { 91 if allowMissing { 92 return c.allowMissingExit(name, module) 93 } 94 95 c.Ui.Error(fmt.Sprintf( 96 "The state is empty. The most common reason for this is that\n" + 97 "an invalid state file path was given or Terraform has never\n " + 98 "been run for this infrastructure. Infrastructure must exist\n" + 99 "for it to be tainted.")) 100 return 1 101 } 102 103 // Get the proper module we want to taint 104 modPath := strings.Split(module, ".") 105 mod := s.ModuleByPath(modPath) 106 if mod == nil { 107 if allowMissing { 108 return c.allowMissingExit(name, module) 109 } 110 111 c.Ui.Error(fmt.Sprintf( 112 "The module %s could not be found. There is nothing to taint.", 113 module)) 114 return 1 115 } 116 117 // If there are no resources in this module, it is an error 118 if len(mod.Resources) == 0 { 119 if allowMissing { 120 return c.allowMissingExit(name, module) 121 } 122 123 c.Ui.Error(fmt.Sprintf( 124 "The module %s has no resources. There is nothing to taint.", 125 module)) 126 return 1 127 } 128 129 // Get the resource we're looking for 130 rs, ok := mod.Resources[name] 131 if !ok { 132 if allowMissing { 133 return c.allowMissingExit(name, module) 134 } 135 136 c.Ui.Error(fmt.Sprintf( 137 "The resource %s couldn't be found in the module %s.", 138 name, 139 module)) 140 return 1 141 } 142 143 // Taint the resource 144 rs.Taint() 145 146 log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath()) 147 if err := st.WriteState(s); err != nil { 148 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 149 return 1 150 } 151 if err := st.PersistState(); err != nil { 152 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 153 return 1 154 } 155 156 c.Ui.Output(fmt.Sprintf( 157 "The resource %s in the module %s has been marked as tainted!", 158 name, module)) 159 return 0 160 } 161 162 func (c *TaintCommand) Help() string { 163 helpText := ` 164 Usage: terraform taint [options] name 165 166 Manually mark a resource as tainted, forcing a destroy and recreate 167 on the next plan/apply. 168 169 This will not modify your infrastructure. This command changes your 170 state to mark a resource as tainted so that during the next plan or 171 apply, that resource will be destroyed and recreated. This command on 172 its own will not modify infrastructure. This command can be undone by 173 reverting the state backup file that is created. 174 175 Options: 176 177 -allow-missing If specified, the command will succeed (exit code 0) 178 even if the resource is missing. 179 180 -backup=path Path to backup the existing state file before 181 modifying. Defaults to the "-state-out" path with 182 ".backup" extension. Set to "-" to disable backup. 183 184 -lock=true Lock the state file when locking is supported. 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 *TaintCommand) Synopsis() string { 203 return "Manually mark a resource for recreation" 204 } 205 206 func (c *TaintCommand) 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 }