github.com/burmuley/terraform@v0.11.12-beta1/command/state_mv.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/command/clistate"
     9  	"github.com/hashicorp/terraform/terraform"
    10  	"github.com/mitchellh/cli"
    11  )
    12  
    13  // StateMvCommand is a Command implementation that shows a single resource.
    14  type StateMvCommand struct {
    15  	StateMeta
    16  }
    17  
    18  func (c *StateMvCommand) Run(args []string) int {
    19  	args, err := c.Meta.process(args, true)
    20  	if err != nil {
    21  		return 1
    22  	}
    23  
    24  	// We create two metas to track the two states
    25  	var backupPathOut, statePathOut string
    26  
    27  	cmdFlags := c.Meta.flagSet("state mv")
    28  	cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
    29  	cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
    30  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    31  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    32  	cmdFlags.StringVar(&c.statePath, "state", "", "path")
    33  	cmdFlags.StringVar(&statePathOut, "state-out", "", "path")
    34  	if err := cmdFlags.Parse(args); err != nil {
    35  		return cli.RunResultHelp
    36  	}
    37  	args = cmdFlags.Args()
    38  	if len(args) != 2 {
    39  		c.Ui.Error("Exactly two arguments expected.\n")
    40  		return cli.RunResultHelp
    41  	}
    42  
    43  	// Read the from state
    44  	stateFrom, err := c.State()
    45  	if err != nil {
    46  		c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
    47  		return 1
    48  	}
    49  
    50  	if c.stateLock {
    51  		stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
    52  		if err := stateLocker.Lock(stateFrom, "state-mv"); err != nil {
    53  			c.Ui.Error(fmt.Sprintf("Error locking source state: %s", err))
    54  			return 1
    55  		}
    56  		defer stateLocker.Unlock(nil)
    57  	}
    58  
    59  	if err := stateFrom.RefreshState(); err != nil {
    60  		c.Ui.Error(fmt.Sprintf("Failed to load source state: %s", err))
    61  		return 1
    62  	}
    63  
    64  	stateFromReal := stateFrom.State()
    65  	if stateFromReal == nil {
    66  		c.Ui.Error(fmt.Sprintf(errStateNotFound))
    67  		return 1
    68  	}
    69  
    70  	// Read the destination state
    71  	stateTo := stateFrom
    72  	stateToReal := stateFromReal
    73  
    74  	if statePathOut != "" {
    75  		c.statePath = statePathOut
    76  		c.backupPath = backupPathOut
    77  		stateTo, err = c.State()
    78  		if err != nil {
    79  			c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
    80  			return 1
    81  		}
    82  
    83  		if c.stateLock {
    84  			stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
    85  			if err := stateLocker.Lock(stateTo, "state-mv"); err != nil {
    86  				c.Ui.Error(fmt.Sprintf("Error locking destination state: %s", err))
    87  				return 1
    88  			}
    89  			defer stateLocker.Unlock(nil)
    90  		}
    91  
    92  		if err := stateTo.RefreshState(); err != nil {
    93  			c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
    94  			return 1
    95  		}
    96  
    97  		stateToReal = stateTo.State()
    98  		if stateToReal == nil {
    99  			stateToReal = terraform.NewState()
   100  		}
   101  	}
   102  
   103  	// Filter what we're moving
   104  	filter := &terraform.StateFilter{State: stateFromReal}
   105  	results, err := filter.Filter(args[0])
   106  	if err != nil {
   107  		c.Ui.Error(fmt.Sprintf(errStateMv, err))
   108  		return cli.RunResultHelp
   109  	}
   110  	if len(results) == 0 {
   111  		c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0]))
   112  		return 1
   113  	}
   114  
   115  	// Get the item to add to the state
   116  	add := c.addableResult(results)
   117  
   118  	// Do the actual move
   119  	if err := stateFromReal.Remove(args[0]); err != nil {
   120  		c.Ui.Error(fmt.Sprintf(errStateMv, err))
   121  		return 1
   122  	}
   123  
   124  	if err := stateToReal.Add(args[0], args[1], add); err != nil {
   125  		c.Ui.Error(fmt.Sprintf(errStateMv, err))
   126  		return 1
   127  	}
   128  
   129  	// Write the new state
   130  	if err := stateTo.WriteState(stateToReal); err != nil {
   131  		c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
   132  		return 1
   133  	}
   134  
   135  	if err := stateTo.PersistState(); err != nil {
   136  		c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
   137  		return 1
   138  	}
   139  
   140  	// Write the old state if it is different
   141  	if stateTo != stateFrom {
   142  		if err := stateFrom.WriteState(stateFromReal); err != nil {
   143  			c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
   144  			return 1
   145  		}
   146  
   147  		if err := stateFrom.PersistState(); err != nil {
   148  			c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
   149  			return 1
   150  		}
   151  	}
   152  
   153  	c.Ui.Output(fmt.Sprintf(
   154  		"Moved %s to %s", args[0], args[1]))
   155  	return 0
   156  }
   157  
   158  // addableResult takes the result from a filter operation and returns what to
   159  // call State.Add with. The reason we do this is because in the module case
   160  // we must add the list of all modules returned versus just the root module.
   161  func (c *StateMvCommand) addableResult(results []*terraform.StateFilterResult) interface{} {
   162  	switch v := results[0].Value.(type) {
   163  	case *terraform.ModuleState:
   164  		// If a module state then we should add the full list of modules
   165  		result := []*terraform.ModuleState{v}
   166  		if len(results) > 1 {
   167  			for _, r := range results[1:] {
   168  				if ms, ok := r.Value.(*terraform.ModuleState); ok {
   169  					result = append(result, ms)
   170  				}
   171  			}
   172  		}
   173  
   174  		return result
   175  
   176  	case *terraform.ResourceState:
   177  		// If a resource state with more than one result, it has a multi-count
   178  		// and we need to add all of them.
   179  		result := []*terraform.ResourceState{v}
   180  		if len(results) > 1 {
   181  			for _, r := range results[1:] {
   182  				rs, ok := r.Value.(*terraform.ResourceState)
   183  				if !ok {
   184  					continue
   185  				}
   186  
   187  				if rs.Type == v.Type {
   188  					result = append(result, rs)
   189  				}
   190  			}
   191  		}
   192  
   193  		// If we only have one item, add it directly
   194  		if len(result) == 1 {
   195  			return result[0]
   196  		}
   197  
   198  		return result
   199  
   200  	default:
   201  		// By default just add the first result
   202  		return v
   203  	}
   204  }
   205  
   206  func (c *StateMvCommand) Help() string {
   207  	helpText := `
   208  Usage: terraform state mv [options] SOURCE DESTINATION
   209  
   210   This command will move an item matched by the address given to the
   211   destination address. This command can also move to a destination address
   212   in a completely different state file.
   213  
   214   This can be used for simple resource renaming, moving items to and from
   215   a module, moving entire modules, and more. And because this command can also
   216   move data to a completely new state, it can also be used for refactoring
   217   one configuration into multiple separately managed Terraform configurations.
   218  
   219   This command will output a backup copy of the state prior to saving any
   220   changes. The backup cannot be disabled. Due to the destructive nature
   221   of this command, backups are required.
   222  
   223   If you're moving an item to a different state file, a backup will be created
   224   for each state file.
   225  
   226  Options:
   227  
   228    -backup=PATH        Path where Terraform should write the backup for the original
   229                        state. This can't be disabled. If not set, Terraform
   230                        will write it to the same path as the statefile with
   231                        a ".backup" extension.
   232  
   233    -backup-out=PATH    Path where Terraform should write the backup for the destination
   234                        state. This can't be disabled. If not set, Terraform
   235                        will write it to the same path as the destination state
   236                        file with a backup extension. This only needs
   237                        to be specified if -state-out is set to a different path
   238                        than -state.
   239  
   240    -lock=true          Lock the state file when locking is supported.
   241  
   242    -lock-timeout=0s    Duration to retry a state lock.
   243  
   244    -state=PATH         Path to the source state file. Defaults to the configured
   245                        backend, or "terraform.tfstate"
   246  
   247    -state-out=PATH     Path to the destination state file to write to. If this
   248                        isn't specified, the source state file will be used. This
   249                        can be a new or existing path.
   250  
   251  `
   252  	return strings.TrimSpace(helpText)
   253  }
   254  
   255  func (c *StateMvCommand) Synopsis() string {
   256  	return "Move an item in the state"
   257  }
   258  
   259  const errStateMv = `Error moving state: %[1]s
   260  
   261  Please ensure your addresses and state paths are valid. No
   262  state was persisted. Your existing states are untouched.`
   263  
   264  const errStateMvPersist = `Error saving the state: %s
   265  
   266  The state wasn't saved properly. If the error happening after a partial
   267  write occurred, a backup file will have been created. Otherwise, the state
   268  is in the same state it was when the operation started.`