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