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