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