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