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