github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/command/meta_backend_migrate.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/terraform/backend" 12 clistate "github.com/hashicorp/terraform/command/state" 13 "github.com/hashicorp/terraform/state" 14 "github.com/hashicorp/terraform/terraform" 15 ) 16 17 // backendMigrateState handles migrating (copying) state from one backend 18 // to another. This function handles asking the user for confirmation 19 // as well as the copy itself. 20 // 21 // This function can handle all scenarios of state migration regardless 22 // of the existence of state in either backend. 23 // 24 // After migrating the state, the existing state in the first backend 25 // remains untouched. 26 // 27 // This will attempt to lock both states for the migration. 28 func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { 29 // We need to check what the named state status is. If we're converting 30 // from multi-state to single-state for example, we need to handle that. 31 var oneSingle, twoSingle bool 32 oneStates, err := opts.One.States() 33 if err == backend.ErrNamedStatesNotSupported { 34 oneSingle = true 35 err = nil 36 } 37 if err != nil { 38 return fmt.Errorf(strings.TrimSpace( 39 errMigrateLoadStates), opts.OneType, err) 40 } 41 42 _, err = opts.Two.States() 43 if err == backend.ErrNamedStatesNotSupported { 44 twoSingle = true 45 err = nil 46 } 47 if err != nil { 48 return fmt.Errorf(strings.TrimSpace( 49 errMigrateLoadStates), opts.TwoType, err) 50 } 51 52 // Setup defaults 53 opts.oneEnv = backend.DefaultStateName 54 opts.twoEnv = backend.DefaultStateName 55 opts.force = false 56 57 // Determine migration behavior based on whether the source/destionation 58 // supports multi-state. 59 switch { 60 // Single-state to single-state. This is the easiest case: we just 61 // copy the default state directly. 62 case oneSingle && twoSingle: 63 return m.backendMigrateState_s_s(opts) 64 65 // Single-state to multi-state. This is easy since we just copy 66 // the default state and ignore the rest in the destination. 67 case oneSingle && !twoSingle: 68 return m.backendMigrateState_s_s(opts) 69 70 // Multi-state to single-state. If the source has more than the default 71 // state this is complicated since we have to ask the user what to do. 72 case !oneSingle && twoSingle: 73 // If the source only has one state and it is the default, 74 // treat it as if it doesn't support multi-state. 75 if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName { 76 return m.backendMigrateState_s_s(opts) 77 } 78 79 return m.backendMigrateState_S_s(opts) 80 81 // Multi-state to multi-state. We merge the states together (migrating 82 // each from the source to the destination one by one). 83 case !oneSingle && !twoSingle: 84 // If the source only has one state and it is the default, 85 // treat it as if it doesn't support multi-state. 86 if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName { 87 return m.backendMigrateState_s_s(opts) 88 } 89 90 return m.backendMigrateState_S_S(opts) 91 } 92 93 return nil 94 } 95 96 //------------------------------------------------------------------- 97 // State Migration Scenarios 98 // 99 // The functions below cover handling all the various scenarios that 100 // can exist when migrating state. They are named in an immediately not 101 // obvious format but is simple: 102 // 103 // Format: backendMigrateState_s1_s2[_suffix] 104 // 105 // When s1 or s2 is lower case, it means that it is a single state backend. 106 // When either is uppercase, it means that state is a multi-state backend. 107 // The suffix is used to disambiguate multiple cases with the same type of 108 // states. 109 // 110 //------------------------------------------------------------------- 111 112 // Multi-state to multi-state. 113 func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error { 114 // Ask the user if they want to migrate their existing remote state 115 migrate, err := m.confirm(&terraform.InputOpts{ 116 Id: "backend-migrate-multistate-to-multistate", 117 Query: fmt.Sprintf( 118 "Do you want to migrate all environments to %q?", 119 opts.TwoType), 120 Description: fmt.Sprintf( 121 strings.TrimSpace(inputBackendMigrateMultiToMulti), 122 opts.OneType, opts.TwoType), 123 }) 124 if err != nil { 125 return fmt.Errorf( 126 "Error asking for state migration action: %s", err) 127 } 128 if !migrate { 129 return fmt.Errorf("Migration aborted by user.") 130 } 131 132 // Read all the states 133 oneStates, err := opts.One.States() 134 if err != nil { 135 return fmt.Errorf(strings.TrimSpace( 136 errMigrateLoadStates), opts.OneType, err) 137 } 138 139 // Sort the states so they're always copied alphabetically 140 sort.Strings(oneStates) 141 142 // Go through each and migrate 143 for _, name := range oneStates { 144 // Copy the same names 145 opts.oneEnv = name 146 opts.twoEnv = name 147 148 // Force it, we confirmed above 149 opts.force = true 150 151 // Perform the migration 152 if err := m.backendMigrateState_s_s(opts); err != nil { 153 return fmt.Errorf(strings.TrimSpace( 154 errMigrateMulti), name, opts.OneType, opts.TwoType, err) 155 } 156 } 157 158 return nil 159 } 160 161 // Multi-state to single state. 162 func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { 163 currentEnv := m.Env() 164 165 migrate := m.forceInitCopy 166 if !migrate { 167 var err error 168 // Ask the user if they want to migrate their existing remote state 169 migrate, err = m.confirm(&terraform.InputOpts{ 170 Id: "backend-migrate-multistate-to-single", 171 Query: fmt.Sprintf( 172 "Destination state %q doesn't support environments (named states).\n"+ 173 "Do you want to copy only your current environment?", 174 opts.TwoType), 175 Description: fmt.Sprintf( 176 strings.TrimSpace(inputBackendMigrateMultiToSingle), 177 opts.OneType, opts.TwoType, currentEnv), 178 }) 179 if err != nil { 180 return fmt.Errorf( 181 "Error asking for state migration action: %s", err) 182 } 183 } 184 185 if !migrate { 186 return fmt.Errorf("Migration aborted by user.") 187 } 188 189 // Copy the default state 190 opts.oneEnv = currentEnv 191 192 // now switch back to the default env so we can acccess the new backend 193 m.SetEnv(backend.DefaultStateName) 194 195 return m.backendMigrateState_s_s(opts) 196 } 197 198 // Single state to single state, assumed default state name. 199 func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { 200 stateOne, err := opts.One.State(opts.oneEnv) 201 if err != nil { 202 return fmt.Errorf(strings.TrimSpace( 203 errMigrateSingleLoadDefault), opts.OneType, err) 204 } 205 if err := stateOne.RefreshState(); err != nil { 206 return fmt.Errorf(strings.TrimSpace( 207 errMigrateSingleLoadDefault), opts.OneType, err) 208 } 209 210 stateTwo, err := opts.Two.State(opts.twoEnv) 211 if err != nil { 212 return fmt.Errorf(strings.TrimSpace( 213 errMigrateSingleLoadDefault), opts.TwoType, err) 214 } 215 if err := stateTwo.RefreshState(); err != nil { 216 return fmt.Errorf(strings.TrimSpace( 217 errMigrateSingleLoadDefault), opts.TwoType, err) 218 } 219 220 lockInfoOne := state.NewLockInfo() 221 lockInfoOne.Operation = "migration" 222 lockInfoOne.Info = "source state" 223 224 lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize()) 225 if err != nil { 226 return fmt.Errorf("Error locking source state: %s", err) 227 } 228 defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize()) 229 230 lockInfoTwo := state.NewLockInfo() 231 lockInfoTwo.Operation = "migration" 232 lockInfoTwo.Info = "destination state" 233 234 lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize()) 235 if err != nil { 236 return fmt.Errorf("Error locking destination state: %s", err) 237 } 238 defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize()) 239 240 one := stateOne.State() 241 two := stateTwo.State() 242 243 // Clear the legacy remote state in both cases. If we're at the migration 244 // step then this won't be used anymore. 245 if one != nil { 246 one.Remote = nil 247 } 248 if two != nil { 249 two.Remote = nil 250 } 251 252 var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error) 253 switch { 254 // No migration necessary 255 case one.Empty() && two.Empty(): 256 return nil 257 258 // No migration necessary if we're inheriting state. 259 case one.Empty() && !two.Empty(): 260 return nil 261 262 // We have existing state moving into no state. Ask the user if 263 // they'd like to do this. 264 case !one.Empty() && two.Empty(): 265 confirmFunc = m.backendMigrateEmptyConfirm 266 267 // Both states are non-empty, meaning we need to determine which 268 // state should be used and update accordingly. 269 case !one.Empty() && !two.Empty(): 270 confirmFunc = m.backendMigrateNonEmptyConfirm 271 } 272 273 if confirmFunc == nil { 274 panic("confirmFunc must not be nil") 275 } 276 277 if !opts.force { 278 // Confirm with the user whether we want to copy state over 279 confirm, err := confirmFunc(stateOne, stateTwo, opts) 280 if err != nil { 281 return err 282 } 283 if !confirm { 284 return nil 285 } 286 } 287 288 // Confirmed! Write. 289 if err := stateTwo.WriteState(one); err != nil { 290 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 291 opts.OneType, opts.TwoType, err) 292 } 293 if err := stateTwo.PersistState(); err != nil { 294 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 295 opts.OneType, opts.TwoType, err) 296 } 297 298 // And we're done. 299 return nil 300 } 301 302 func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) { 303 if m.forceInitCopy { 304 return true, nil 305 } 306 307 inputOpts := &terraform.InputOpts{ 308 Id: "backend-migrate-copy-to-empty", 309 Query: fmt.Sprintf( 310 "Do you want to copy state from %q to %q?", 311 opts.OneType, opts.TwoType), 312 Description: fmt.Sprintf( 313 strings.TrimSpace(inputBackendMigrateEmpty), 314 opts.OneType, opts.TwoType), 315 } 316 317 // Confirm with the user that the copy should occur 318 for { 319 v, err := m.UIInput().Input(inputOpts) 320 if err != nil { 321 return false, fmt.Errorf( 322 "Error asking for state copy action: %s", err) 323 } 324 325 switch strings.ToLower(v) { 326 case "no": 327 return false, nil 328 329 case "yes": 330 return true, nil 331 } 332 } 333 } 334 335 func (m *Meta) backendMigrateNonEmptyConfirm( 336 stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) { 337 // We need to grab both states so we can write them to a file 338 one := stateOne.State() 339 two := stateTwo.State() 340 341 // Save both to a temporary 342 td, err := ioutil.TempDir("", "terraform") 343 if err != nil { 344 return false, fmt.Errorf("Error creating temporary directory: %s", err) 345 } 346 defer os.RemoveAll(td) 347 348 // Helper to write the state 349 saveHelper := func(n, path string, s *terraform.State) error { 350 f, err := os.Create(path) 351 if err != nil { 352 return err 353 } 354 defer f.Close() 355 356 return terraform.WriteState(s, f) 357 } 358 359 // Write the states 360 onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType)) 361 twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType)) 362 if err := saveHelper(opts.OneType, onePath, one); err != nil { 363 return false, fmt.Errorf("Error saving temporary state: %s", err) 364 } 365 if err := saveHelper(opts.TwoType, twoPath, two); err != nil { 366 return false, fmt.Errorf("Error saving temporary state: %s", err) 367 } 368 369 if m.forceInitCopy { 370 return true, nil 371 } 372 373 // Ask for confirmation 374 inputOpts := &terraform.InputOpts{ 375 Id: "backend-migrate-to-backend", 376 Query: fmt.Sprintf( 377 "Do you want to copy state from %q to %q?", 378 opts.OneType, opts.TwoType), 379 Description: fmt.Sprintf( 380 strings.TrimSpace(inputBackendMigrateNonEmpty), 381 opts.OneType, opts.TwoType, onePath, twoPath), 382 } 383 384 // Confirm with the user that the copy should occur 385 for { 386 v, err := m.UIInput().Input(inputOpts) 387 if err != nil { 388 return false, fmt.Errorf( 389 "Error asking for state copy action: %s", err) 390 } 391 392 switch strings.ToLower(v) { 393 case "no": 394 return false, nil 395 396 case "yes": 397 return true, nil 398 } 399 } 400 } 401 402 type backendMigrateOpts struct { 403 OneType, TwoType string 404 One, Two backend.Backend 405 406 // Fields below are set internally when migrate is called 407 408 oneEnv string // source env 409 twoEnv string // dest env 410 force bool // if true, won't ask for confirmation 411 } 412 413 const errMigrateLoadStates = ` 414 Error inspecting state in %q: %s 415 416 Prior to changing backends, Terraform inspects the source and destionation 417 states to determine what kind of migration steps need to be taken, if any. 418 Terraform failed to load the states. The data in both the source and the 419 destination remain unmodified. Please resolve the above error and try again. 420 ` 421 422 const errMigrateSingleLoadDefault = ` 423 Error loading state from %q: %s 424 425 Terraform failed to load the default state from %[1]q. 426 State migration cannot occur unless the state can be loaded. Backend 427 modification and state migration has been aborted. The state in both the 428 source and the destination remain unmodified. Please resolve the 429 above error and try again. 430 ` 431 432 const errMigrateMulti = ` 433 Error migrating the environment %q from %q to %q: 434 435 %s 436 437 Terraform copies environments in alphabetical order. Any environments 438 alphabetically earlier than this one have been copied. Any environments 439 later than this haven't been modified in the destination. No environments 440 in the source state have been modified. 441 442 Please resolve the error above and run the initialization command again. 443 This will attempt to copy (with permission) all environments again. 444 ` 445 446 const errBackendStateCopy = ` 447 Error copying state from %q to %q: %s 448 449 The state in %[1]q remains intact and unmodified. Please resolve the 450 error above and try again. 451 ` 452 453 const inputBackendMigrateEmpty = ` 454 Pre-existing state was found in %q while migrating to %q. No existing 455 state was found in %[2]q. Do you want to copy the state from %[1]q to 456 %[2]q? Enter "yes" to copy and "no" to start with an empty state. 457 ` 458 459 const inputBackendMigrateNonEmpty = ` 460 Pre-existing state was found in %q while migrating to %q. An existing 461 non-empty state exists in %[2]q. The two states have been saved to temporary 462 files that will be removed after responding to this query. 463 464 One (%[1]q): %[3]s 465 Two (%[2]q): %[4]s 466 467 Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy 468 and "no" to start with the existing state in %[2]q. 469 ` 470 471 const inputBackendMigrateMultiToSingle = ` 472 The existing backend %[1]q supports environments and you currently are 473 using more than one. The target backend %[2]q doesn't support environments. 474 If you continue, Terraform will offer to copy your current environment 475 %[3]q to the default environment in the target. Your existing environments 476 in the source backend won't be modified. If you want to switch environments, 477 back them up, or cancel altogether, answer "no" and Terraform will abort. 478 ` 479 480 const inputBackendMigrateMultiToMulti = ` 481 Both the existing backend %[1]q and the target backend %[2]q support 482 environments. When migrating between backends, Terraform will copy all 483 environments (with the same names). THIS WILL OVERWRITE any conflicting 484 states in the destination. 485 486 Terraform initialization doesn't currently migrate only select environments. 487 If you want to migrate a select number of environments, you must manually 488 pull and push those states. 489 490 If you answer "yes", Terraform will migrate all states. If you answer 491 "no", Terraform will abort. 492 `