kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/unlock.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"kubeform.dev/terraform-backend-sdk/states/statemgr"
     9  
    10  	"kubeform.dev/terraform-backend-sdk/terraform"
    11  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    12  	"github.com/mitchellh/cli"
    13  )
    14  
    15  // UnlockCommand is a cli.Command implementation that manually unlocks
    16  // the state.
    17  type UnlockCommand struct {
    18  	Meta
    19  }
    20  
    21  func (c *UnlockCommand) Run(args []string) int {
    22  	args = c.Meta.process(args)
    23  	var force bool
    24  	cmdFlags := c.Meta.defaultFlagSet("force-unlock")
    25  	cmdFlags.BoolVar(&force, "force", false, "force")
    26  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    27  	if err := cmdFlags.Parse(args); err != nil {
    28  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    29  		return 1
    30  	}
    31  
    32  	args = cmdFlags.Args()
    33  	if len(args) != 1 {
    34  		c.Ui.Error("Expected a single argument: LOCK_ID")
    35  		return cli.RunResultHelp
    36  	}
    37  
    38  	lockID := args[0]
    39  	args = args[1:]
    40  
    41  	// assume everything is initialized. The user can manually init if this is
    42  	// required.
    43  	configPath, err := ModulePath(args)
    44  	if err != nil {
    45  		c.Ui.Error(err.Error())
    46  		return 1
    47  	}
    48  
    49  	var diags tfdiags.Diagnostics
    50  
    51  	backendConfig, backendDiags := c.loadBackendConfig(configPath)
    52  	diags = diags.Append(backendDiags)
    53  	if diags.HasErrors() {
    54  		c.showDiagnostics(diags)
    55  		return 1
    56  	}
    57  
    58  	// Load the backend
    59  	b, backendDiags := c.Backend(&BackendOpts{
    60  		Config: backendConfig,
    61  	})
    62  	diags = diags.Append(backendDiags)
    63  	if backendDiags.HasErrors() {
    64  		c.showDiagnostics(diags)
    65  		return 1
    66  	}
    67  
    68  	env, err := c.Workspace()
    69  	if err != nil {
    70  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    71  		return 1
    72  	}
    73  	stateMgr, err := b.StateMgr(env)
    74  	if err != nil {
    75  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    76  		return 1
    77  	}
    78  
    79  	_, isLocal := stateMgr.(*statemgr.Filesystem)
    80  
    81  	if !force {
    82  		// Forcing this doesn't do anything, but doesn't break anything either,
    83  		// and allows us to run the basic command test too.
    84  		if isLocal {
    85  			c.Ui.Error("Local state cannot be unlocked by another process")
    86  			return 1
    87  		}
    88  
    89  		desc := "Terraform will remove the lock on the remote state.\n" +
    90  			"This will allow local Terraform commands to modify this state, even though it\n" +
    91  			"may be still be in use. Only 'yes' will be accepted to confirm."
    92  
    93  		v, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{
    94  			Id:          "force-unlock",
    95  			Query:       "Do you really want to force-unlock?",
    96  			Description: desc,
    97  		})
    98  		if err != nil {
    99  			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
   100  			return 1
   101  		}
   102  		if v != "yes" {
   103  			c.Ui.Output("force-unlock cancelled.")
   104  			return 1
   105  		}
   106  	}
   107  
   108  	if err := stateMgr.Unlock(lockID); err != nil {
   109  		c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
   110  		return 1
   111  	}
   112  
   113  	c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputUnlockSuccess)))
   114  	return 0
   115  }
   116  
   117  func (c *UnlockCommand) Help() string {
   118  	helpText := `
   119  Usage: terraform [global options] force-unlock LOCK_ID
   120  
   121    Manually unlock the state for the defined configuration.
   122  
   123    This will not modify your infrastructure. This command removes the lock on the
   124    state for the current workspace. The behavior of this lock is dependent
   125    on the backend being used. Local state files cannot be unlocked by another
   126    process.
   127  
   128  Options:
   129  
   130    -force                 Don't ask for input for unlock confirmation.
   131  `
   132  	return strings.TrimSpace(helpText)
   133  }
   134  
   135  func (c *UnlockCommand) Synopsis() string {
   136  	return "Release a stuck lock on the current workspace"
   137  }
   138  
   139  const outputUnlockSuccess = `
   140  [reset][bold][green]Terraform state has been successfully unlocked![reset][green]
   141  
   142  The state has been unlocked, and Terraform commands should now be able to
   143  obtain a new lock on the remote state.
   144  `