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