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