github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/state_replace_provider.go (about)

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