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