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