github.com/pulumi/terraform@v1.4.0/pkg/command/untaint.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/pulumi/terraform/pkg/addrs" 8 "github.com/pulumi/terraform/pkg/command/arguments" 9 "github.com/pulumi/terraform/pkg/command/clistate" 10 "github.com/pulumi/terraform/pkg/command/views" 11 "github.com/pulumi/terraform/pkg/states" 12 "github.com/pulumi/terraform/pkg/terraform" 13 "github.com/pulumi/terraform/pkg/tfdiags" 14 ) 15 16 // UntaintCommand is a cli.Command implementation that manually untaints 17 // a resource, marking it as primary and ready for service. 18 type UntaintCommand struct { 19 Meta 20 } 21 22 func (c *UntaintCommand) Run(args []string) int { 23 args = c.Meta.process(args) 24 var allowMissing bool 25 cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("untaint") 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 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 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 // Load the backend 56 b, backendDiags := c.Backend(nil) 57 diags = diags.Append(backendDiags) 58 if backendDiags.HasErrors() { 59 c.showDiagnostics(diags) 60 return 1 61 } 62 63 // Determine the workspace name 64 workspace, err := c.Workspace() 65 if err != nil { 66 c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err)) 67 return 1 68 } 69 70 // Check remote Terraform version is compatible 71 remoteVersionDiags := c.remoteVersionCheck(b, workspace) 72 diags = diags.Append(remoteVersionDiags) 73 c.showDiagnostics(diags) 74 if diags.HasErrors() { 75 return 1 76 } 77 78 // Get the state 79 stateMgr, err := b.StateMgr(workspace) 80 if err != nil { 81 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 82 return 1 83 } 84 85 if c.stateLock { 86 stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View)) 87 if diags := stateLocker.Lock(stateMgr, "untaint"); diags.HasErrors() { 88 c.showDiagnostics(diags) 89 return 1 90 } 91 defer func() { 92 if diags := stateLocker.Unlock(); diags.HasErrors() { 93 c.showDiagnostics(diags) 94 } 95 }() 96 } 97 98 if err := stateMgr.RefreshState(); err != nil { 99 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 100 return 1 101 } 102 103 // Get the actual state structure 104 state := stateMgr.State() 105 if state.Empty() { 106 if allowMissing { 107 return c.allowMissingExit(addr) 108 } 109 110 diags = diags.Append(tfdiags.Sourceless( 111 tfdiags.Error, 112 "No such resource instance", 113 "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.", 114 )) 115 c.showDiagnostics(diags) 116 return 1 117 } 118 119 ss := state.SyncWrapper() 120 121 // Get the resource and instance we're going to taint 122 rs := ss.Resource(addr.ContainingResource()) 123 is := ss.ResourceInstance(addr) 124 if is == nil { 125 if allowMissing { 126 return c.allowMissingExit(addr) 127 } 128 129 diags = diags.Append(tfdiags.Sourceless( 130 tfdiags.Error, 131 "No such resource instance", 132 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), 133 )) 134 c.showDiagnostics(diags) 135 return 1 136 } 137 138 obj := is.Current 139 if obj == nil { 140 if len(is.Deposed) != 0 { 141 diags = diags.Append(tfdiags.Sourceless( 142 tfdiags.Error, 143 "No such resource instance", 144 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), 145 )) 146 } else { 147 // Don't know why we're here, but we'll produce a generic error message anyway. 148 diags = diags.Append(tfdiags.Sourceless( 149 tfdiags.Error, 150 "No such resource instance", 151 fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr), 152 )) 153 } 154 c.showDiagnostics(diags) 155 return 1 156 } 157 158 if obj.Status != states.ObjectTainted { 159 diags = diags.Append(tfdiags.Sourceless( 160 tfdiags.Error, 161 "Resource instance is not tainted", 162 fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr), 163 )) 164 c.showDiagnostics(diags) 165 return 1 166 } 167 168 // Get schemas, if possible, before writing state 169 var schemas *terraform.Schemas 170 if isCloudMode(b) { 171 var schemaDiags tfdiags.Diagnostics 172 schemas, schemaDiags = c.MaybeGetSchemas(state, nil) 173 diags = diags.Append(schemaDiags) 174 } 175 176 obj.Status = states.ObjectReady 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 successfully untainted.", addr)) 190 return 0 191 } 192 193 func (c *UntaintCommand) Help() string { 194 helpText := ` 195 Usage: terraform [global options] untaint [options] name 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 the "terraform taint" command. 201 202 This command removes that state from a resource instance, causing 203 Terraform to see it as fully-functional and not in need of 204 replacement. 205 206 This will not modify your infrastructure directly. It only avoids 207 Terraform planning to replace a tainted instance in a future operation. 208 209 Options: 210 211 -allow-missing If specified, the command will succeed (exit code 0) 212 even if the resource is missing. 213 214 -lock=false Don't hold a state lock during the operation. This is 215 dangerous if others might concurrently run commands 216 against the same workspace. 217 218 -lock-timeout=0s Duration to retry a state lock. 219 220 -ignore-remote-version A rare option used for the remote backend only. See 221 the remote backend documentation for more information. 222 223 -state, state-out, and -backup are legacy options supported for the local 224 backend only. For more information, see the local backend's documentation. 225 226 ` 227 return strings.TrimSpace(helpText) 228 } 229 230 func (c *UntaintCommand) Synopsis() string { 231 return "Remove the 'tainted' state from a resource instance" 232 } 233 234 func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int { 235 c.showDiagnostics(tfdiags.Sourceless( 236 tfdiags.Warning, 237 "No such resource instance", 238 fmt.Sprintf("Resource instance %s was not found, but this is not an error because -allow-missing was set.", name), 239 )) 240 return 0 241 }