github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/taint.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 // TaintCommand is a cli.Command implementation that manually taints 15 // a resource, marking it for recreation. 16 type TaintCommand struct { 17 Meta 18 } 19 20 func (c *TaintCommand) 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("taint") 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 taint 45 args = cmdFlags.Args() 46 if len(args) != 1 { 47 c.Ui.Error("The taint 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 if addr.Resource.Resource.Mode != addrs.ManagedResourceMode { 65 c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr)) 66 return 1 67 } 68 69 // Load the backend 70 b, backendDiags := c.Backend(nil) 71 diags = diags.Append(backendDiags) 72 if backendDiags.HasErrors() { 73 c.showDiagnostics(diags) 74 return 1 75 } 76 77 // Get the state 78 env := c.Workspace() 79 stateMgr, err := b.StateMgr(env) 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(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) 87 if err := stateLocker.Lock(stateMgr, "taint"); err != nil { 88 c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) 89 return 1 90 } 91 defer stateLocker.Unlock(nil) 92 } 93 94 if err := stateMgr.RefreshState(); err != nil { 95 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 96 return 1 97 } 98 99 // Get the actual state structure 100 state := stateMgr.State() 101 if state.Empty() { 102 if allowMissing { 103 return c.allowMissingExit(addr) 104 } 105 106 diags = diags.Append(tfdiags.Sourceless( 107 tfdiags.Error, 108 "No such resource instance", 109 "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.", 110 )) 111 c.showDiagnostics(diags) 112 return 1 113 } 114 115 ss := state.SyncWrapper() 116 117 // Get the resource and instance we're going to taint 118 rs := ss.Resource(addr.ContainingResource()) 119 is := ss.ResourceInstance(addr) 120 if is == nil { 121 if allowMissing { 122 return c.allowMissingExit(addr) 123 } 124 125 diags = diags.Append(tfdiags.Sourceless( 126 tfdiags.Error, 127 "No such resource instance", 128 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), 129 )) 130 c.showDiagnostics(diags) 131 return 1 132 } 133 134 obj := is.Current 135 if obj == nil { 136 if len(is.Deposed) != 0 { 137 diags = diags.Append(tfdiags.Sourceless( 138 tfdiags.Error, 139 "No such resource instance", 140 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), 141 )) 142 } else { 143 // Don't know why we're here, but we'll produce a generic error message anyway. 144 diags = diags.Append(tfdiags.Sourceless( 145 tfdiags.Error, 146 "No such resource instance", 147 fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr), 148 )) 149 } 150 c.showDiagnostics(diags) 151 return 1 152 } 153 154 obj.Status = states.ObjectTainted 155 ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) 156 157 if err := stateMgr.WriteState(state); err != nil { 158 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 159 return 1 160 } 161 if err := stateMgr.PersistState(); err != nil { 162 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 163 return 1 164 } 165 166 c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr)) 167 return 0 168 } 169 170 func (c *TaintCommand) Help() string { 171 helpText := ` 172 Usage: terraform taint [options] <address> 173 174 Manually mark a resource as tainted, forcing a destroy and recreate 175 on the next plan/apply. 176 177 This will not modify your infrastructure. This command changes your 178 state to mark a resource as tainted so that during the next plan or 179 apply that resource will be destroyed and recreated. This command on 180 its own will not modify infrastructure. This command can be undone 181 using the "terraform untaint" command with the same address. 182 183 The address is in the usual resource address syntax, as shown in 184 the output from other commands, such as: 185 aws_instance.foo 186 aws_instance.bar[1] 187 module.foo.module.bar.aws_instance.baz 188 189 Options: 190 191 -allow-missing If specified, the command will succeed (exit code 0) 192 even if the resource is missing. 193 194 -backup=path Path to backup the existing state file before 195 modifying. Defaults to the "-state-out" path with 196 ".backup" extension. Set to "-" to disable backup. 197 198 -lock=true Lock the state file when locking is supported. 199 200 -lock-timeout=0s Duration to retry a state lock. 201 202 -state=path Path to read and save state (unless state-out 203 is specified). Defaults to "terraform.tfstate". 204 205 -state-out=path Path to write updated state file. By default, the 206 "-state" path will be used. 207 208 ` 209 return strings.TrimSpace(helpText) 210 } 211 212 func (c *TaintCommand) Synopsis() string { 213 return "Manually mark a resource for recreation" 214 } 215 216 func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int { 217 c.showDiagnostics(tfdiags.Sourceless( 218 tfdiags.Warning, 219 "No such resource instance", 220 "Resource instance %s was not found, but this is not an error because -allow-missing was set.", 221 )) 222 return 0 223 }