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

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"kubeform.dev/terraform-backend-sdk/addrs"
     8  	"kubeform.dev/terraform-backend-sdk/command/arguments"
     9  	"kubeform.dev/terraform-backend-sdk/command/clistate"
    10  	"kubeform.dev/terraform-backend-sdk/command/views"
    11  	"kubeform.dev/terraform-backend-sdk/states"
    12  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    13  	"github.com/mitchellh/cli"
    14  )
    15  
    16  // StateReplaceProviderCommand is a Command implementation that allows users
    17  // to change the provider associated with existing resources. This is only
    18  // likely to be useful if a provider is forked or changes its fully-qualified
    19  // name.
    20  
    21  type StateReplaceProviderCommand struct {
    22  	StateMeta
    23  }
    24  
    25  func (c *StateReplaceProviderCommand) Run(args []string) int {
    26  	args = c.Meta.process(args)
    27  
    28  	var autoApprove bool
    29  	cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state replace-provider")
    30  	cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of replacements")
    31  	cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
    32  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states")
    33  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    34  	cmdFlags.StringVar(&c.statePath, "state", "", "path")
    35  	if err := cmdFlags.Parse(args); err != nil {
    36  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    37  		return cli.RunResultHelp
    38  	}
    39  	args = cmdFlags.Args()
    40  	if len(args) != 2 {
    41  		c.Ui.Error("Exactly two arguments expected.\n")
    42  		return cli.RunResultHelp
    43  	}
    44  
    45  	var diags tfdiags.Diagnostics
    46  
    47  	// Parse from/to arguments into providers
    48  	from, fromDiags := addrs.ParseProviderSourceString(args[0])
    49  	if fromDiags.HasErrors() {
    50  		diags = diags.Append(tfdiags.Sourceless(
    51  			tfdiags.Error,
    52  			fmt.Sprintf(`Invalid "from" provider %q`, args[0]),
    53  			fromDiags.Err().Error(),
    54  		))
    55  	}
    56  	to, toDiags := addrs.ParseProviderSourceString(args[1])
    57  	if toDiags.HasErrors() {
    58  		diags = diags.Append(tfdiags.Sourceless(
    59  			tfdiags.Error,
    60  			fmt.Sprintf(`Invalid "to" provider %q`, args[1]),
    61  			toDiags.Err().Error(),
    62  		))
    63  	}
    64  	if diags.HasErrors() {
    65  		c.showDiagnostics(diags)
    66  		return 1
    67  	}
    68  
    69  	// Initialize the state manager as configured
    70  	stateMgr, err := c.State()
    71  	if err != nil {
    72  		c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
    73  		return 1
    74  	}
    75  
    76  	// Acquire lock if requested
    77  	if c.stateLock {
    78  		stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
    79  		if diags := stateLocker.Lock(stateMgr, "state-replace-provider"); diags.HasErrors() {
    80  			c.showDiagnostics(diags)
    81  			return 1
    82  		}
    83  		defer func() {
    84  			if diags := stateLocker.Unlock(); diags.HasErrors() {
    85  				c.showDiagnostics(diags)
    86  			}
    87  		}()
    88  	}
    89  
    90  	// Refresh and load state
    91  	if err := stateMgr.RefreshState(); err != nil {
    92  		c.Ui.Error(fmt.Sprintf("Failed to refresh source state: %s", err))
    93  		return 1
    94  	}
    95  
    96  	state := stateMgr.State()
    97  	if state == nil {
    98  		c.Ui.Error(errStateNotFound)
    99  		return 1
   100  	}
   101  
   102  	// Fetch all resources from the state
   103  	resources, diags := c.lookupAllResources(state)
   104  	if diags.HasErrors() {
   105  		c.showDiagnostics(diags)
   106  		return 1
   107  	}
   108  
   109  	var willReplace []*states.Resource
   110  
   111  	// Update all matching resources with new provider
   112  	for _, resource := range resources {
   113  		if resource.ProviderConfig.Provider.Equals(from) {
   114  			willReplace = append(willReplace, resource)
   115  		}
   116  	}
   117  	c.showDiagnostics(diags)
   118  
   119  	if len(willReplace) == 0 {
   120  		c.Ui.Output("No matching resources found.")
   121  		return 0
   122  	}
   123  
   124  	// Explain the changes
   125  	colorize := c.Colorize()
   126  	c.Ui.Output("Terraform will perform the following actions:\n")
   127  	c.Ui.Output(colorize.Color("  [yellow]~[reset] Updating provider:"))
   128  	c.Ui.Output(colorize.Color(fmt.Sprintf("    [red]-[reset] %s", from)))
   129  	c.Ui.Output(colorize.Color(fmt.Sprintf("    [green]+[reset] %s\n", to)))
   130  
   131  	c.Ui.Output(colorize.Color(fmt.Sprintf("[bold]Changing[reset] %d resources:\n", len(willReplace))))
   132  	for _, resource := range willReplace {
   133  		c.Ui.Output(colorize.Color(fmt.Sprintf("  %s", resource.Addr)))
   134  	}
   135  
   136  	// Confirm
   137  	if !autoApprove {
   138  		c.Ui.Output(colorize.Color(
   139  			"\n[bold]Do you want to make these changes?[reset]\n" +
   140  				"Only 'yes' will be accepted to continue.\n",
   141  		))
   142  		v, err := c.Ui.Ask("Enter a value:")
   143  		if err != nil {
   144  			c.Ui.Error(fmt.Sprintf("Error asking for approval: %s", err))
   145  			return 1
   146  		}
   147  		if v != "yes" {
   148  			c.Ui.Output("Cancelled replacing providers.")
   149  			return 0
   150  		}
   151  	}
   152  
   153  	// Update the provider for each resource
   154  	for _, resource := range willReplace {
   155  		resource.ProviderConfig.Provider = to
   156  	}
   157  
   158  	// Write the updated state
   159  	if err := stateMgr.WriteState(state); err != nil {
   160  		c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
   161  		return 1
   162  	}
   163  	if err := stateMgr.PersistState(); err != nil {
   164  		c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
   165  		return 1
   166  	}
   167  
   168  	c.Ui.Output(fmt.Sprintf("\nSuccessfully replaced provider for %d resources.", len(willReplace)))
   169  	return 0
   170  }
   171  
   172  func (c *StateReplaceProviderCommand) Help() string {
   173  	helpText := `
   174  Usage: terraform [global options] state replace-provider [options] FROM_PROVIDER_FQN TO_PROVIDER_FQN
   175  
   176    Replace provider for resources in the Terraform state.
   177  
   178  Options:
   179  
   180    -auto-approve           Skip interactive approval.
   181  
   182    -lock=false             Don't hold a state lock during the operation. This is
   183                            dangerous if others might concurrently run commands
   184                            against the same workspace.
   185  
   186    -lock-timeout=0s        Duration to retry a state lock.
   187  
   188    -ignore-remote-version  A rare option used for the remote backend only. See
   189                            the remote backend documentation for more information.
   190  
   191    -state, state-out, and -backup are legacy options supported for the local
   192    backend only. For more information, see the local backend's documentation.
   193  
   194  `
   195  	return strings.TrimSpace(helpText)
   196  }
   197  
   198  func (c *StateReplaceProviderCommand) Synopsis() string {
   199  	return "Replace provider in the state"
   200  }