github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/taint.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/kevinklinger/open_terraform/noninternal/addrs" 8 "github.com/kevinklinger/open_terraform/noninternal/command/arguments" 9 "github.com/kevinklinger/open_terraform/noninternal/command/clistate" 10 "github.com/kevinklinger/open_terraform/noninternal/command/views" 11 "github.com/kevinklinger/open_terraform/noninternal/states" 12 "github.com/kevinklinger/open_terraform/noninternal/terraform" 13 "github.com/kevinklinger/open_terraform/noninternal/tfdiags" 14 ) 15 16 // TaintCommand is a cli.Command implementation that manually taints 17 // a resource, marking it for recreation. 18 type TaintCommand struct { 19 Meta 20 } 21 22 func (c *TaintCommand) Run(args []string) int { 23 args = c.Meta.process(args) 24 var allowMissing bool 25 cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("taint") 26 cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "allow missing") 27 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 28 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 29 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 30 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 31 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 32 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 33 if err := cmdFlags.Parse(args); err != nil { 34 c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) 35 return 1 36 } 37 38 var diags tfdiags.Diagnostics 39 40 // Require the one argument for the resource to taint 41 args = cmdFlags.Args() 42 if len(args) != 1 { 43 c.Ui.Error("The taint command expects exactly one argument.") 44 cmdFlags.Usage() 45 return 1 46 } 47 48 addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0]) 49 diags = diags.Append(addrDiags) 50 if addrDiags.HasErrors() { 51 c.showDiagnostics(diags) 52 return 1 53 } 54 55 if addr.Resource.Resource.Mode != addrs.ManagedResourceMode { 56 c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr)) 57 return 1 58 } 59 60 if diags := c.Meta.checkRequiredVersion(); diags != nil { 61 c.showDiagnostics(diags) 62 return 1 63 } 64 65 // Load the backend 66 b, backendDiags := c.Backend(nil) 67 diags = diags.Append(backendDiags) 68 if backendDiags.HasErrors() { 69 c.showDiagnostics(diags) 70 return 1 71 } 72 73 // Determine the workspace name 74 workspace, err := c.Workspace() 75 if err != nil { 76 c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err)) 77 return 1 78 } 79 80 // Check remote Terraform version is compatible 81 remoteVersionDiags := c.remoteVersionCheck(b, workspace) 82 diags = diags.Append(remoteVersionDiags) 83 c.showDiagnostics(diags) 84 if diags.HasErrors() { 85 return 1 86 } 87 88 // Get the state 89 stateMgr, err := b.StateMgr(workspace) 90 if err != nil { 91 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 92 return 1 93 } 94 95 if c.stateLock { 96 stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View)) 97 if diags := stateLocker.Lock(stateMgr, "taint"); diags.HasErrors() { 98 c.showDiagnostics(diags) 99 return 1 100 } 101 defer func() { 102 if diags := stateLocker.Unlock(); diags.HasErrors() { 103 c.showDiagnostics(diags) 104 } 105 }() 106 } 107 108 if err := stateMgr.RefreshState(); err != nil { 109 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 110 return 1 111 } 112 113 // Get the actual state structure 114 state := stateMgr.State() 115 if state.Empty() { 116 if allowMissing { 117 return c.allowMissingExit(addr) 118 } 119 120 diags = diags.Append(tfdiags.Sourceless( 121 tfdiags.Error, 122 "No such resource instance", 123 "The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.", 124 )) 125 c.showDiagnostics(diags) 126 return 1 127 } 128 129 // Get schemas, if possible, before writing state 130 var schemas *terraform.Schemas 131 if isCloudMode(b) { 132 var schemaDiags tfdiags.Diagnostics 133 schemas, schemaDiags = c.MaybeGetSchemas(state, nil) 134 diags = diags.Append(schemaDiags) 135 } 136 137 ss := state.SyncWrapper() 138 139 // Get the resource and instance we're going to taint 140 rs := ss.Resource(addr.ContainingResource()) 141 is := ss.ResourceInstance(addr) 142 if is == nil { 143 if allowMissing { 144 return c.allowMissingExit(addr) 145 } 146 147 diags = diags.Append(tfdiags.Sourceless( 148 tfdiags.Error, 149 "No such resource instance", 150 fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr), 151 )) 152 c.showDiagnostics(diags) 153 return 1 154 } 155 156 obj := is.Current 157 if obj == nil { 158 if len(is.Deposed) != 0 { 159 diags = diags.Append(tfdiags.Sourceless( 160 tfdiags.Error, 161 "No such resource instance", 162 fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr), 163 )) 164 } else { 165 // Don't know why we're here, but we'll produce a generic error message anyway. 166 diags = diags.Append(tfdiags.Sourceless( 167 tfdiags.Error, 168 "No such resource instance", 169 fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr), 170 )) 171 } 172 c.showDiagnostics(diags) 173 return 1 174 } 175 176 obj.Status = states.ObjectTainted 177 ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) 178 179 if err := stateMgr.WriteState(state); err != nil { 180 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 181 return 1 182 } 183 if err := stateMgr.PersistState(schemas); err != nil { 184 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 185 return 1 186 } 187 188 c.showDiagnostics(diags) 189 c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr)) 190 return 0 191 } 192 193 func (c *TaintCommand) Help() string { 194 helpText := ` 195 Usage: terraform [global options] taint [options] <address> 196 197 Terraform uses the term "tainted" to describe a resource instance 198 which may not be fully functional, either because its creation 199 partially failed or because you've manually marked it as such using 200 this command. 201 202 This will not modify your infrastructure directly, but subsequent 203 Terraform plans will include actions to destroy the remote object 204 and create a new object to replace it. 205 206 You can remove the "taint" state from a resource instance using 207 the "terraform untaint" command. 208 209 The address is in the usual resource address syntax, such as: 210 aws_instance.foo 211 aws_instance.bar[1] 212 module.foo.module.bar.aws_instance.baz 213 214 Use your shell's quoting or escaping syntax to ensure that the 215 address will reach Terraform correctly, without any special 216 interpretation. 217 218 Options: 219 220 -allow-missing If specified, the command will succeed (exit code 0) 221 even if the resource is missing. 222 223 -lock=false Don't hold a state lock during the operation. This is 224 dangerous if others might concurrently run commands 225 against the same workspace. 226 227 -lock-timeout=0s Duration to retry a state lock. 228 229 -ignore-remote-version A rare option used for the remote backend only. See 230 the remote backend documentation for more information. 231 232 -state, state-out, and -backup are legacy options supported for the local 233 backend only. For more information, see the local backend's documentation. 234 235 ` 236 return strings.TrimSpace(helpText) 237 } 238 239 func (c *TaintCommand) Synopsis() string { 240 return "Mark a resource instance as not fully functional" 241 } 242 243 func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int { 244 c.showDiagnostics(tfdiags.Sourceless( 245 tfdiags.Warning, 246 "No such resource instance", 247 fmt.Sprintf("Resource instance %s was not found, but this is not an error because -allow-missing was set.", name), 248 )) 249 return 0 250 }