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