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 `