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 }