github.com/ggriffiths/terraform@v0.9.0-beta1.0.20170222213024-79c4935604cb/command/meta_backend_migrate.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	clistate "github.com/hashicorp/terraform/command/state"
    11  	"github.com/hashicorp/terraform/state"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  // backendMigrateState handles migrating (copying) state from one backend
    16  // to another. This function handles asking the user for confirmation
    17  // as well as the copy itself.
    18  //
    19  // This function can handle all scenarios of state migration regardless
    20  // of the existence of state in either backend.
    21  //
    22  // After migrating the state, the existing state in the first backend
    23  // remains untouched.
    24  //
    25  // This will attempt to lock both states for the migration.
    26  func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
    27  	lockInfoOne := state.NewLockInfo()
    28  	lockInfoOne.Operation = "migration"
    29  	lockInfoOne.Info = "source state"
    30  
    31  	lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize())
    32  	if err != nil {
    33  		return fmt.Errorf("Error locking source state: %s", err)
    34  	}
    35  	defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize())
    36  
    37  	lockInfoTwo := state.NewLockInfo()
    38  	lockInfoTwo.Operation = "migration"
    39  	lockInfoTwo.Info = "destination state"
    40  
    41  	lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize())
    42  	if err != nil {
    43  		return fmt.Errorf("Error locking destination state: %s", err)
    44  	}
    45  	defer clistate.Unlock(opts.Two, lockIDTwo, m.Ui, m.Colorize())
    46  
    47  	one := opts.One.State()
    48  	two := opts.Two.State()
    49  
    50  	var confirmFunc func(opts *backendMigrateOpts) (bool, error)
    51  	switch {
    52  	// No migration necessary
    53  	case one.Empty() && two.Empty():
    54  		return nil
    55  
    56  	// No migration necessary if we're inheriting state.
    57  	case one.Empty() && !two.Empty():
    58  		return nil
    59  
    60  	// We have existing state moving into no state. Ask the user if
    61  	// they'd like to do this.
    62  	case !one.Empty() && two.Empty():
    63  		confirmFunc = m.backendMigrateEmptyConfirm
    64  
    65  	// Both states are non-empty, meaning we need to determine which
    66  	// state should be used and update accordingly.
    67  	case !one.Empty() && !two.Empty():
    68  		confirmFunc = m.backendMigrateNonEmptyConfirm
    69  	}
    70  
    71  	if confirmFunc == nil {
    72  		panic("confirmFunc must not be nil")
    73  	}
    74  
    75  	// Confirm with the user whether we want to copy state over
    76  	confirm, err := confirmFunc(opts)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if !confirm {
    81  		return nil
    82  	}
    83  
    84  	// Confirmed! Write.
    85  	if err := opts.Two.WriteState(one); err != nil {
    86  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
    87  			opts.OneType, opts.TwoType, err)
    88  	}
    89  	if err := opts.Two.PersistState(); err != nil {
    90  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
    91  			opts.OneType, opts.TwoType, err)
    92  	}
    93  
    94  	// And we're done.
    95  	return nil
    96  }
    97  
    98  func (m *Meta) backendMigrateEmptyConfirm(opts *backendMigrateOpts) (bool, error) {
    99  	inputOpts := &terraform.InputOpts{
   100  		Id: "backend-migrate-to-backend",
   101  		Query: fmt.Sprintf(
   102  			"Do you want to copy state from %q to %q?",
   103  			opts.OneType, opts.TwoType),
   104  		Description: fmt.Sprintf(
   105  			strings.TrimSpace(inputBackendMigrateEmpty),
   106  			opts.OneType, opts.TwoType),
   107  	}
   108  
   109  	// Confirm with the user that the copy should occur
   110  	for {
   111  		v, err := m.UIInput().Input(inputOpts)
   112  		if err != nil {
   113  			return false, fmt.Errorf(
   114  				"Error asking for state copy action: %s", err)
   115  		}
   116  
   117  		switch strings.ToLower(v) {
   118  		case "no":
   119  			return false, nil
   120  
   121  		case "yes":
   122  			return true, nil
   123  		}
   124  	}
   125  }
   126  
   127  func (m *Meta) backendMigrateNonEmptyConfirm(opts *backendMigrateOpts) (bool, error) {
   128  	// We need to grab both states so we can write them to a file
   129  	one := opts.One.State()
   130  	two := opts.Two.State()
   131  
   132  	// Save both to a temporary
   133  	td, err := ioutil.TempDir("", "terraform")
   134  	if err != nil {
   135  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   136  	}
   137  	defer os.RemoveAll(td)
   138  
   139  	// Helper to write the state
   140  	saveHelper := func(n, path string, s *terraform.State) error {
   141  		f, err := os.Create(path)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		defer f.Close()
   146  
   147  		return terraform.WriteState(s, f)
   148  	}
   149  
   150  	// Write the states
   151  	onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
   152  	twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
   153  	if err := saveHelper(opts.OneType, onePath, one); err != nil {
   154  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   155  	}
   156  	if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
   157  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   158  	}
   159  
   160  	// Ask for confirmation
   161  	inputOpts := &terraform.InputOpts{
   162  		Id: "backend-migrate-to-backend",
   163  		Query: fmt.Sprintf(
   164  			"Do you want to copy state from %q to %q?",
   165  			opts.OneType, opts.TwoType),
   166  		Description: fmt.Sprintf(
   167  			strings.TrimSpace(inputBackendMigrateNonEmpty),
   168  			opts.OneType, opts.TwoType, onePath, twoPath),
   169  	}
   170  
   171  	// Confirm with the user that the copy should occur
   172  	for {
   173  		v, err := m.UIInput().Input(inputOpts)
   174  		if err != nil {
   175  			return false, fmt.Errorf(
   176  				"Error asking for state copy action: %s", err)
   177  		}
   178  
   179  		switch strings.ToLower(v) {
   180  		case "no":
   181  			return false, nil
   182  
   183  		case "yes":
   184  			return true, nil
   185  		}
   186  	}
   187  }
   188  
   189  type backendMigrateOpts struct {
   190  	OneType, TwoType string
   191  	One, Two         state.State
   192  }
   193  
   194  const errBackendStateCopy = `
   195  Error copying state from %q to %q: %s
   196  
   197  The state in %[1]q remains intact and unmodified. Please resolve the
   198  error above and try again.
   199  `
   200  
   201  const inputBackendMigrateEmpty = `
   202  Pre-existing state was found in %q while migrating to %q. No existing
   203  state was found in %[2]q. Do you want to copy the state from %[1]q to
   204  %[2]q? Enter "yes" to copy and "no" to start with an empty state.
   205  `
   206  
   207  const inputBackendMigrateNonEmpty = `
   208  Pre-existing state was found in %q while migrating to %q. An existing
   209  non-empty state exists in %[2]q. The two states have been saved to temporary
   210  files that will be removed after responding to this query.
   211  
   212  One (%[1]q): %[3]s
   213  Two (%[2]q): %[4]s
   214  
   215  Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy
   216  and "no" to start with the existing state in %[2]q.
   217  `