github.com/mehmetalisavas/terraform@v0.7.10/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.stateOutPath, "backup", "", "backup")
    24  	cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path")
    25  	cmdFlags.StringVar(&meta2.stateOutPath, "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.
   197  
   198    -backup-out=PATH    Path where Terraform should write the backup for the destination
   199                        state. This can't be disabled. If not set, Terraform
   200                        will write it to the same path as the destination state
   201                        file with a backup extension. This only needs
   202                        to be specified if -state-out is set to a different path
   203                        than -state.
   204  
   205    -state=PATH         Path to a Terraform state file to use to look
   206                        up Terraform-managed resources. By default it will
   207                        use the state "terraform.tfstate" if it exists.
   208  
   209    -state-out=PATH     Path to the destination state file to move the item
   210                        to. This defaults to the same statefile. This will
   211                        overwrite the destination state file.
   212  
   213  `
   214  	return strings.TrimSpace(helpText)
   215  }
   216  
   217  func (c *StateMvCommand) Synopsis() string {
   218  	return "Move an item in the state"
   219  }
   220  
   221  const errStateMv = `Error moving state: %[1]s
   222  
   223  Please ensure your addresses and state paths are valid. No
   224  state was persisted. Your existing states are untouched.`
   225  
   226  const errStateMvPersist = `Error saving the state: %s
   227  
   228  The state wasn't saved properly. If the error happening after a partial
   229  write occurred, a backup file will have been created. Otherwise, the state
   230  is in the same state it was when the operation started.`