github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/state_mv.go (about)

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