github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/untaint.go (about) 1 package command 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/hashicorp/terraform/addrs" 9 "github.com/hashicorp/terraform/command/clistate" 10 "github.com/hashicorp/terraform/states" 11 "github.com/hashicorp/terraform/tfdiags" 12 ) 13 14 // UntaintCommand is a cli.Command implementation that manually untaints 15 // a resource, marking it as primary and ready for service. 16 type UntaintCommand struct { 17 Meta 18 } 19 20 func (c *UntaintCommand) Run(args []string) int { 21 args, err := c.Meta.process(args, false) 22 if err != nil { 23 return 1 24 } 25 26 var module string 27 var allowMissing bool 28 cmdFlags := c.Meta.defaultFlagSet("untaint") 29 cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module") 30 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 31 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 32 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 33 cmdFlags.StringVar(&module, "module", "", "module") 34 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 35 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 36 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 37 if err := cmdFlags.Parse(args); err != nil { 38 c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) 39 return 1 40 } 41 42 var diags tfdiags.Diagnostics 43 44 // Require the one argument for the resource to untaint 45 args = cmdFlags.Args() 46 if len(args) != 1 { 47 c.Ui.Error("The untaint command expects exactly one argument.") 48 cmdFlags.Usage() 49 return 1 50 } 51 52 if module != "" { 53 c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".") 54 return 1 55 } 56 57 addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0]) 58 diags = diags.Append(addrDiags) 59 if addrDiags.HasErrors() { 60 c.showDiagnostics(diags) 61 return 1 62 } 63 64 // Load the backend 65 b, backendDiags := c.Backend(nil) 66 diags = diags.Append(backendDiags) 67 if backendDiags.HasErrors() { 68 c.showDiagnostics(diags) 69 return 1 70 } 71 72 // Get the state 73 workspace := c.Workspace() 74 stateMgr, err := b.StateMgr(workspace) 75 if err != nil { 76 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 77 return 1 78 } 79 80 if c.stateLock { 81 stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) 82 if err := stateLocker.Lock(stateMgr, "untaint"); err != nil { 83 c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) 84 return 1 85 } 86 defer stateLocker.Unlock(nil) 87 } 88 89 if err := stateMgr.RefreshState(); err != nil { 90 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 91 return 1 92 } 93 94 // Get the actual state structure 95 state := stateMgr.State() 96 if state.Empty() { 97 if allowMissing { 98 return c.allowMissingExit(addr) 99 } 100 101 diags = diags.Append(tfdiags.Sourceless( 102 tfdiags.Error, 103 "No such resource instance", 104 "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.", 105 )) 106 c.showDiagnostics(diags) 107 return 1 108 } 109 110 ss := state.SyncWrapper() 111 112 // Get the resource and instance we're going to taint 113 rs := ss.Resource(addr.ContainingResource()) 114 is := ss.ResourceInstance(addr) 115 if is == nil { 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 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), 124 )) 125 c.showDiagnostics(diags) 126 return 1 127 } 128 129 obj := is.Current 130 if obj == nil { 131 if len(is.Deposed) != 0 { 132 diags = diags.Append(tfdiags.Sourceless( 133 tfdiags.Error, 134 "No such resource instance", 135 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), 136 )) 137 } else { 138 // Don't know why we're here, but we'll produce a generic error message anyway. 139 diags = diags.Append(tfdiags.Sourceless( 140 tfdiags.Error, 141 "No such resource instance", 142 fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr), 143 )) 144 } 145 c.showDiagnostics(diags) 146 return 1 147 } 148 149 if obj.Status != states.ObjectTainted { 150 diags = diags.Append(tfdiags.Sourceless( 151 tfdiags.Error, 152 "Resource instance is not tainted", 153 fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr), 154 )) 155 c.showDiagnostics(diags) 156 return 1 157 } 158 obj.Status = states.ObjectReady 159 ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) 160 161 if err := stateMgr.WriteState(state); err != nil { 162 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 163 return 1 164 } 165 if err := stateMgr.PersistState(); err != nil { 166 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 167 return 1 168 } 169 170 c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr)) 171 return 0 172 } 173 174 func (c *UntaintCommand) Help() string { 175 helpText := ` 176 Usage: terraform untaint [options] name 177 178 Manually unmark a resource as tainted, restoring it as the primary 179 instance in the state. This reverses either a manual 'terraform taint' 180 or the result of provisioners failing on a resource. 181 182 This will not modify your infrastructure. This command changes your 183 state to unmark a resource as tainted. This command can be undone by 184 reverting the state backup file that is created, or by running 185 'terraform taint' on the resource. 186 187 Options: 188 189 -allow-missing If specified, the command will succeed (exit code 0) 190 even if the resource is missing. 191 192 -backup=path Path to backup the existing state file before 193 modifying. Defaults to the "-state-out" path with 194 ".backup" extension. Set to "-" to disable backup. 195 196 -lock=true Lock the state file when locking is supported. 197 198 -lock-timeout=0s Duration to retry a state lock. 199 200 -module=path The module path where the resource lives. By 201 default this will be root. Child modules can be specified 202 by names. Ex. "consul" or "consul.vpc" (nested modules). 203 204 -state=path Path to read and save state (unless state-out 205 is specified). Defaults to "terraform.tfstate". 206 207 -state-out=path Path to write updated state file. By default, the 208 "-state" path will be used. 209 210 ` 211 return strings.TrimSpace(helpText) 212 } 213 214 func (c *UntaintCommand) Synopsis() string { 215 return "Manually unmark a resource as tainted" 216 } 217 218 func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int { 219 c.showDiagnostics(tfdiags.Sourceless( 220 tfdiags.Warning, 221 "No such resource instance", 222 "Resource instance %s was not found, but this is not an error because -allow-missing was set.", 223 )) 224 return 0 225 }