github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/meta_backend_migrate.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 15 "github.com/hashicorp/terraform/internal/backend" 16 "github.com/hashicorp/terraform/internal/backend/remote" 17 "github.com/hashicorp/terraform/internal/cloud" 18 "github.com/hashicorp/terraform/internal/command/arguments" 19 "github.com/hashicorp/terraform/internal/command/clistate" 20 "github.com/hashicorp/terraform/internal/command/views" 21 "github.com/hashicorp/terraform/internal/states" 22 "github.com/hashicorp/terraform/internal/states/statemgr" 23 "github.com/hashicorp/terraform/internal/terraform" 24 ) 25 26 type backendMigrateOpts struct { 27 SourceType, DestinationType string 28 Source, Destination backend.Backend 29 ViewType arguments.ViewType 30 31 // Fields below are set internally when migrate is called 32 33 sourceWorkspace string 34 destinationWorkspace string 35 force bool // if true, won't ask for confirmation 36 } 37 38 // backendMigrateState handles migrating (copying) state from one backend 39 // to another. This function handles asking the user for confirmation 40 // as well as the copy itself. 41 // 42 // This function can handle all scenarios of state migration regardless 43 // of the existence of state in either backend. 44 // 45 // After migrating the state, the existing state in the first backend 46 // remains untouched. 47 // 48 // This will attempt to lock both states for the migration. 49 func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { 50 log.Printf("[INFO] backendMigrateState: need to migrate from %q to %q backend config", opts.SourceType, opts.DestinationType) 51 // We need to check what the named state status is. If we're converting 52 // from multi-state to single-state for example, we need to handle that. 53 var sourceSingleState, destinationSingleState, sourceTFC, destinationTFC bool 54 55 _, sourceTFC = opts.Source.(*cloud.Cloud) 56 _, destinationTFC = opts.Destination.(*cloud.Cloud) 57 58 sourceWorkspaces, sourceSingleState, err := retrieveWorkspaces(opts.Source, opts.SourceType) 59 if err != nil { 60 return err 61 } 62 destinationWorkspaces, destinationSingleState, err := retrieveWorkspaces(opts.Destination, opts.SourceType) 63 if err != nil { 64 return err 65 } 66 67 // Set up defaults 68 opts.sourceWorkspace = backend.DefaultStateName 69 opts.destinationWorkspace = backend.DefaultStateName 70 opts.force = m.forceInitCopy 71 72 // Disregard remote Terraform version for the state source backend. If it's a 73 // Terraform Cloud remote backend, we don't care about the remote version, 74 // as we are migrating away and will not break a remote workspace. 75 m.ignoreRemoteVersionConflict(opts.Source) 76 77 // Disregard remote Terraform version if instructed to do so via CLI flag. 78 if m.ignoreRemoteVersion { 79 m.ignoreRemoteVersionConflict(opts.Destination) 80 } else { 81 // Check the remote Terraform version for the state destination backend. If 82 // it's a Terraform Cloud remote backend, we want to ensure that we don't 83 // break the workspace by uploading an incompatible state file. 84 for _, workspace := range destinationWorkspaces { 85 diags := m.remoteVersionCheck(opts.Destination, workspace) 86 if diags.HasErrors() { 87 return diags.Err() 88 } 89 } 90 // If there are no specified destination workspaces, perform a remote 91 // backend version check with the default workspace. 92 // Ensure that we are not dealing with Terraform Cloud migrations, as it 93 // does not support the default name. 94 if len(destinationWorkspaces) == 0 && !destinationTFC { 95 diags := m.remoteVersionCheck(opts.Destination, backend.DefaultStateName) 96 if diags.HasErrors() { 97 return diags.Err() 98 } 99 } 100 } 101 102 // Determine migration behavior based on whether the source/destination 103 // supports multi-state. 104 switch { 105 case sourceTFC || destinationTFC: 106 return m.backendMigrateTFC(opts) 107 108 // Single-state to single-state. This is the easiest case: we just 109 // copy the default state directly. 110 case sourceSingleState && destinationSingleState: 111 return m.backendMigrateState_s_s(opts) 112 113 // Single-state to multi-state. This is easy since we just copy 114 // the default state and ignore the rest in the destination. 115 case sourceSingleState && !destinationSingleState: 116 return m.backendMigrateState_s_s(opts) 117 118 // Multi-state to single-state. If the source has more than the default 119 // state this is complicated since we have to ask the user what to do. 120 case !sourceSingleState && destinationSingleState: 121 // If the source only has one state and it is the default, 122 // treat it as if it doesn't support multi-state. 123 if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName { 124 return m.backendMigrateState_s_s(opts) 125 } 126 127 return m.backendMigrateState_S_s(opts) 128 129 // Multi-state to multi-state. We merge the states together (migrating 130 // each from the source to the destination one by one). 131 case !sourceSingleState && !destinationSingleState: 132 // If the source only has one state and it is the default, 133 // treat it as if it doesn't support multi-state. 134 if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName { 135 return m.backendMigrateState_s_s(opts) 136 } 137 138 return m.backendMigrateState_S_S(opts) 139 } 140 141 return nil 142 } 143 144 //------------------------------------------------------------------- 145 // State Migration Scenarios 146 // 147 // The functions below cover handling all the various scenarios that 148 // can exist when migrating state. They are named in an immediately not 149 // obvious format but is simple: 150 // 151 // Format: backendMigrateState_s1_s2[_suffix] 152 // 153 // When s1 or s2 is lower case, it means that it is a single state backend. 154 // When either is uppercase, it means that state is a multi-state backend. 155 // The suffix is used to disambiguate multiple cases with the same type of 156 // states. 157 // 158 //------------------------------------------------------------------- 159 160 // Multi-state to multi-state. 161 func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error { 162 log.Print("[INFO] backendMigrateState: migrating all named workspaces") 163 164 migrate := opts.force 165 if !migrate { 166 var err error 167 // Ask the user if they want to migrate their existing remote state 168 migrate, err = m.confirm(&terraform.InputOpts{ 169 Id: "backend-migrate-multistate-to-multistate", 170 Query: fmt.Sprintf( 171 "Do you want to migrate all workspaces to %q?", 172 opts.DestinationType), 173 Description: fmt.Sprintf( 174 strings.TrimSpace(inputBackendMigrateMultiToMulti), 175 opts.SourceType, opts.DestinationType), 176 }) 177 if err != nil { 178 return fmt.Errorf( 179 "Error asking for state migration action: %s", err) 180 } 181 } 182 if !migrate { 183 return fmt.Errorf("Migration aborted by user.") 184 } 185 186 // Read all the states 187 sourceWorkspaces, err := opts.Source.Workspaces() 188 if err != nil { 189 return fmt.Errorf(strings.TrimSpace( 190 errMigrateLoadStates), opts.SourceType, err) 191 } 192 193 // Sort the states so they're always copied alphabetically 194 sort.Strings(sourceWorkspaces) 195 196 // Go through each and migrate 197 for _, name := range sourceWorkspaces { 198 // Copy the same names 199 opts.sourceWorkspace = name 200 opts.destinationWorkspace = name 201 202 // Force it, we confirmed above 203 opts.force = true 204 205 // Perform the migration 206 if err := m.backendMigrateState_s_s(opts); err != nil { 207 return fmt.Errorf(strings.TrimSpace( 208 errMigrateMulti), name, opts.SourceType, opts.DestinationType, err) 209 } 210 } 211 212 return nil 213 } 214 215 // Multi-state to single state. 216 func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { 217 log.Printf("[INFO] backendMigrateState: destination backend type %q does not support named workspaces", opts.DestinationType) 218 219 currentWorkspace, err := m.Workspace() 220 if err != nil { 221 return err 222 } 223 224 migrate := opts.force 225 if !migrate { 226 var err error 227 // Ask the user if they want to migrate their existing remote state 228 migrate, err = m.confirm(&terraform.InputOpts{ 229 Id: "backend-migrate-multistate-to-single", 230 Query: fmt.Sprintf( 231 "Destination state %q doesn't support workspaces.\n"+ 232 "Do you want to copy only your current workspace?", 233 opts.DestinationType), 234 Description: fmt.Sprintf( 235 strings.TrimSpace(inputBackendMigrateMultiToSingle), 236 opts.SourceType, opts.DestinationType, currentWorkspace), 237 }) 238 if err != nil { 239 return fmt.Errorf( 240 "Error asking for state migration action: %s", err) 241 } 242 } 243 244 if !migrate { 245 return fmt.Errorf("Migration aborted by user.") 246 } 247 248 // Copy the default state 249 opts.sourceWorkspace = currentWorkspace 250 251 // now switch back to the default env so we can acccess the new backend 252 m.SetWorkspace(backend.DefaultStateName) 253 254 return m.backendMigrateState_s_s(opts) 255 } 256 257 // Single state to single state, assumed default state name. 258 func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { 259 log.Printf("[INFO] backendMigrateState: single-to-single migrating %q workspace to %q workspace", opts.sourceWorkspace, opts.destinationWorkspace) 260 261 sourceState, err := opts.Source.StateMgr(opts.sourceWorkspace) 262 if err != nil { 263 return fmt.Errorf(strings.TrimSpace( 264 errMigrateSingleLoadDefault), opts.SourceType, err) 265 } 266 if err := sourceState.RefreshState(); err != nil { 267 return fmt.Errorf(strings.TrimSpace( 268 errMigrateSingleLoadDefault), opts.SourceType, err) 269 } 270 271 // Do not migrate workspaces without state. 272 if sourceState.State().Empty() { 273 log.Print("[TRACE] backendMigrateState: source workspace has empty state, so nothing to migrate") 274 return nil 275 } 276 277 destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace) 278 if err == backend.ErrDefaultWorkspaceNotSupported { 279 // If the backend doesn't support using the default state, we ask the user 280 // for a new name and migrate the default state to the given named state. 281 destinationState, err = func() (statemgr.Full, error) { 282 log.Print("[TRACE] backendMigrateState: destination doesn't support a default workspace, so we must prompt for a new name") 283 name, err := m.promptNewWorkspaceName(opts.DestinationType) 284 if err != nil { 285 return nil, err 286 } 287 288 // Update the name of the destination state. 289 opts.destinationWorkspace = name 290 291 destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace) 292 if err != nil { 293 return nil, err 294 } 295 296 // Ignore invalid workspace name as it is irrelevant in this context. 297 workspace, _ := m.Workspace() 298 299 // If the currently selected workspace is the default workspace, then set 300 // the named workspace as the new selected workspace. 301 if workspace == backend.DefaultStateName { 302 if err := m.SetWorkspace(opts.destinationWorkspace); err != nil { 303 return nil, fmt.Errorf("Failed to set new workspace: %s", err) 304 } 305 } 306 307 return destinationState, nil 308 }() 309 } 310 if err != nil { 311 return fmt.Errorf(strings.TrimSpace( 312 errMigrateSingleLoadDefault), opts.DestinationType, err) 313 } 314 if err := destinationState.RefreshState(); err != nil { 315 return fmt.Errorf(strings.TrimSpace( 316 errMigrateSingleLoadDefault), opts.DestinationType, err) 317 } 318 319 // Check if we need migration at all. 320 // This is before taking a lock, because they may also correspond to the same lock. 321 source := sourceState.State() 322 destination := destinationState.State() 323 324 // no reason to migrate if the state is already there 325 if source.Equal(destination) { 326 // Equal isn't identical; it doesn't check lineage. 327 sm1, _ := sourceState.(statemgr.PersistentMeta) 328 sm2, _ := destinationState.(statemgr.PersistentMeta) 329 if source != nil && destination != nil { 330 if sm1 == nil || sm2 == nil { 331 log.Print("[TRACE] backendMigrateState: both source and destination workspaces have no state, so no migration is needed") 332 return nil 333 } 334 if sm1.StateSnapshotMeta().Lineage == sm2.StateSnapshotMeta().Lineage { 335 log.Printf("[TRACE] backendMigrateState: both source and destination workspaces have equal state with lineage %q, so no migration is needed", sm1.StateSnapshotMeta().Lineage) 336 return nil 337 } 338 } 339 } 340 341 if m.stateLock { 342 lockCtx := context.Background() 343 vt := arguments.ViewJSON 344 // Set default viewtype if none was set as the StateLocker needs to know exactly 345 // what viewType we want to have. 346 if opts == nil || opts.ViewType != vt { 347 vt = arguments.ViewHuman 348 } 349 view := views.NewStateLocker(vt, m.View) 350 locker := clistate.NewLocker(m.stateLockTimeout, view) 351 352 lockerSource := locker.WithContext(lockCtx) 353 if diags := lockerSource.Lock(sourceState, "migration source state"); diags.HasErrors() { 354 return diags.Err() 355 } 356 defer lockerSource.Unlock() 357 358 lockerDestination := locker.WithContext(lockCtx) 359 if diags := lockerDestination.Lock(destinationState, "migration destination state"); diags.HasErrors() { 360 return diags.Err() 361 } 362 defer lockerDestination.Unlock() 363 364 // We now own a lock, so double check that we have the version 365 // corresponding to the lock. 366 log.Print("[TRACE] backendMigrateState: refreshing source workspace state") 367 if err := sourceState.RefreshState(); err != nil { 368 return fmt.Errorf(strings.TrimSpace( 369 errMigrateSingleLoadDefault), opts.SourceType, err) 370 } 371 log.Print("[TRACE] backendMigrateState: refreshing destination workspace state") 372 if err := destinationState.RefreshState(); err != nil { 373 return fmt.Errorf(strings.TrimSpace( 374 errMigrateSingleLoadDefault), opts.SourceType, err) 375 } 376 377 source = sourceState.State() 378 destination = destinationState.State() 379 } 380 381 var confirmFunc func(statemgr.Full, statemgr.Full, *backendMigrateOpts) (bool, error) 382 switch { 383 // No migration necessary 384 case source.Empty() && destination.Empty(): 385 log.Print("[TRACE] backendMigrateState: both source and destination workspaces have empty state, so no migration is required") 386 return nil 387 388 // No migration necessary if we're inheriting state. 389 case source.Empty() && !destination.Empty(): 390 log.Print("[TRACE] backendMigrateState: source workspace has empty state, so no migration is required") 391 return nil 392 393 // We have existing state moving into no state. Ask the user if 394 // they'd like to do this. 395 case !source.Empty() && destination.Empty(): 396 if opts.SourceType == "cloud" || opts.DestinationType == "cloud" { 397 // HACK: backendMigrateTFC has its own earlier prompt for 398 // whether to migrate state in the cloud case, so we'll skip 399 // this later prompt for Cloud, even though we do still need it 400 // for state backends. 401 confirmFunc = func(statemgr.Full, statemgr.Full, *backendMigrateOpts) (bool, error) { 402 return true, nil // the answer is implied to be "yes" if we reached this point 403 } 404 } else { 405 log.Print("[TRACE] backendMigrateState: destination workspace has empty state, so might copy source workspace state") 406 confirmFunc = m.backendMigrateEmptyConfirm 407 } 408 409 // Both states are non-empty, meaning we need to determine which 410 // state should be used and update accordingly. 411 case !source.Empty() && !destination.Empty(): 412 log.Print("[TRACE] backendMigrateState: both source and destination workspaces have states, so might overwrite destination with source") 413 confirmFunc = m.backendMigrateNonEmptyConfirm 414 } 415 416 if confirmFunc == nil { 417 panic("confirmFunc must not be nil") 418 } 419 420 if !opts.force { 421 // Abort if we can't ask for input. 422 if !m.input { 423 log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") 424 return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) 425 } 426 427 // Confirm with the user whether we want to copy state over 428 confirm, err := confirmFunc(sourceState, destinationState, opts) 429 if err != nil { 430 log.Print("[TRACE] backendMigrateState: error reading input, so aborting migration") 431 return err 432 } 433 if !confirm { 434 log.Print("[TRACE] backendMigrateState: user cancelled at confirmation prompt, so aborting migration") 435 return nil 436 } 437 } 438 439 // Confirmed! We'll have the statemgr package handle the migration, which 440 // includes preserving any lineage/serial information where possible, if 441 // both managers support such metadata. 442 log.Print("[TRACE] backendMigrateState: migration confirmed, so migrating") 443 if err := statemgr.Migrate(destinationState, sourceState); err != nil { 444 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 445 opts.SourceType, opts.DestinationType, err) 446 } 447 // The backend is currently handled before providers are installed during init, 448 // so requiring schemas here could lead to a catch-22 where it requires some manual 449 // intervention to proceed far enough for provider installation. To avoid this, 450 // when migrating to TFC backend, the initial JSON varient of state won't be generated and stored. 451 if err := destinationState.PersistState(nil); err != nil { 452 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 453 opts.SourceType, opts.DestinationType, err) 454 } 455 456 // And we're done. 457 return nil 458 } 459 460 func (m *Meta) backendMigrateEmptyConfirm(source, destination statemgr.Full, opts *backendMigrateOpts) (bool, error) { 461 var inputOpts *terraform.InputOpts 462 if opts.DestinationType == "cloud" { 463 inputOpts = &terraform.InputOpts{ 464 Id: "backend-migrate-copy-to-empty-cloud", 465 Query: "Do you want to copy existing state to Terraform Cloud?", 466 Description: fmt.Sprintf(strings.TrimSpace(inputBackendMigrateEmptyCloud), opts.SourceType), 467 } 468 } else { 469 inputOpts = &terraform.InputOpts{ 470 Id: "backend-migrate-copy-to-empty", 471 Query: "Do you want to copy existing state to the new backend?", 472 Description: fmt.Sprintf( 473 strings.TrimSpace(inputBackendMigrateEmpty), 474 opts.SourceType, opts.DestinationType), 475 } 476 } 477 478 return m.confirm(inputOpts) 479 } 480 481 func (m *Meta) backendMigrateNonEmptyConfirm( 482 sourceState, destinationState statemgr.Full, opts *backendMigrateOpts) (bool, error) { 483 // We need to grab both states so we can write them to a file 484 source := sourceState.State() 485 destination := destinationState.State() 486 487 // Save both to a temporary 488 td, err := ioutil.TempDir("", "terraform") 489 if err != nil { 490 return false, fmt.Errorf("Error creating temporary directory: %s", err) 491 } 492 defer os.RemoveAll(td) 493 494 // Helper to write the state 495 saveHelper := func(n, path string, s *states.State) error { 496 mgr := statemgr.NewFilesystem(path) 497 return mgr.WriteState(s) 498 } 499 500 // Write the states 501 sourcePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.SourceType)) 502 destinationPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.DestinationType)) 503 if err := saveHelper(opts.SourceType, sourcePath, source); err != nil { 504 return false, fmt.Errorf("Error saving temporary state: %s", err) 505 } 506 if err := saveHelper(opts.DestinationType, destinationPath, destination); err != nil { 507 return false, fmt.Errorf("Error saving temporary state: %s", err) 508 } 509 510 // Ask for confirmation 511 var inputOpts *terraform.InputOpts 512 if opts.DestinationType == "cloud" { 513 inputOpts = &terraform.InputOpts{ 514 Id: "backend-migrate-to-tfc", 515 Query: "Do you want to copy existing state to Terraform Cloud?", 516 Description: fmt.Sprintf( 517 strings.TrimSpace(inputBackendMigrateNonEmptyCloud), 518 opts.SourceType, sourcePath, destinationPath), 519 } 520 } else { 521 inputOpts = &terraform.InputOpts{ 522 Id: "backend-migrate-to-backend", 523 Query: "Do you want to copy existing state to the new backend?", 524 Description: fmt.Sprintf( 525 strings.TrimSpace(inputBackendMigrateNonEmpty), 526 opts.SourceType, opts.DestinationType, sourcePath, destinationPath), 527 } 528 } 529 530 // Confirm with the user that the copy should occur 531 return m.confirm(inputOpts) 532 } 533 534 func retrieveWorkspaces(back backend.Backend, sourceType string) ([]string, bool, error) { 535 var singleState bool 536 var err error 537 workspaces, err := back.Workspaces() 538 if err == backend.ErrWorkspacesNotSupported { 539 singleState = true 540 err = nil 541 } 542 if err != nil { 543 return nil, singleState, fmt.Errorf(strings.TrimSpace( 544 errMigrateLoadStates), sourceType, err) 545 } 546 547 return workspaces, singleState, err 548 } 549 550 func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error { 551 _, sourceTFC := opts.Source.(*cloud.Cloud) 552 cloudBackendDestination, destinationTFC := opts.Destination.(*cloud.Cloud) 553 554 sourceWorkspaces, sourceSingleState, err := retrieveWorkspaces(opts.Source, opts.SourceType) 555 if err != nil { 556 return err 557 } 558 //to be used below, not yet implamented 559 // destinationWorkspaces, destinationSingleState 560 _, _, err = retrieveWorkspaces(opts.Destination, opts.SourceType) 561 if err != nil { 562 return err 563 } 564 565 // from TFC to non-TFC backend 566 if sourceTFC && !destinationTFC { 567 // From Terraform Cloud to another backend. This is not yet implemented, and 568 // we recommend people to use the TFC API. 569 return fmt.Errorf(strings.TrimSpace(errTFCMigrateNotYetImplemented)) 570 } 571 572 // Everything below, by the above two conditionals, now assumes that the 573 // destination is always Terraform Cloud (TFC). 574 575 sourceSingle := sourceSingleState || (len(sourceWorkspaces) == 1) 576 if sourceSingle { 577 if cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy { 578 // If we know the name via WorkspaceNameStrategy, then set the 579 // destinationWorkspace to the new Name and skip the user prompt. Here the 580 // destinationWorkspace is not set to `default` thereby we will create it 581 // in TFC if it does not exist. 582 opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name 583 } 584 585 currentWorkspace, err := m.Workspace() 586 if err != nil { 587 return err 588 } 589 opts.sourceWorkspace = currentWorkspace 590 591 log.Printf("[INFO] backendMigrateTFC: single-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace) 592 593 // If the current workspace is has no state we do not need to ask 594 // if they want to migrate the state. 595 sourceState, err := opts.Source.StateMgr(currentWorkspace) 596 if err != nil { 597 return err 598 } 599 if err := sourceState.RefreshState(); err != nil { 600 return err 601 } 602 if sourceState.State().Empty() { 603 log.Printf("[INFO] backendMigrateTFC: skipping migration because source %s is empty", opts.sourceWorkspace) 604 return nil 605 } 606 607 // Run normal single-to-single state migration. 608 // This will handle both situations where the new cloud backend 609 // configuration is using a workspace.name strategy or workspace.tags 610 // strategy. 611 // 612 // We do prompt first though, because state migration is mandatory 613 // for moving to Cloud and the user should get an opportunity to 614 // confirm that first. 615 if migrate, err := m.promptSingleToCloudSingleStateMigration(opts); err != nil { 616 return err 617 } else if !migrate { 618 return nil //skip migrating but return successfully 619 } 620 621 return m.backendMigrateState_s_s(opts) 622 } 623 624 destinationTagsStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceTagsStrategy 625 destinationNameStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy 626 627 multiSource := !sourceSingleState && len(sourceWorkspaces) > 1 628 if multiSource && destinationNameStrategy { 629 currentWorkspace, err := m.Workspace() 630 if err != nil { 631 return err 632 } 633 634 opts.sourceWorkspace = currentWorkspace 635 opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name 636 if err := m.promptMultiToSingleCloudMigration(opts); err != nil { 637 return err 638 } 639 640 log.Printf("[INFO] backendMigrateTFC: multi-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace) 641 642 return m.backendMigrateState_s_s(opts) 643 } 644 645 // Multiple sources, and using tags strategy. So migrate every source 646 // workspace over to new one, prompt for workspace name pattern (*), 647 // and start migrating, and create tags for each workspace. 648 if multiSource && destinationTagsStrategy { 649 log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration from source workspaces %q", sourceWorkspaces) 650 return m.backendMigrateState_S_TFC(opts, sourceWorkspaces) 651 } 652 653 // TODO(omar): after the check for sourceSingle is done, everything following 654 // it has to be multi. So rework the code to not need to check for multi, adn 655 // return m.backendMigrateState_S_TFC here. 656 return nil 657 } 658 659 // migrates a multi-state backend to Terraform Cloud 660 func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspaces []string) error { 661 log.Print("[TRACE] backendMigrateState: migrating all named workspaces") 662 663 currentWorkspace, err := m.Workspace() 664 if err != nil { 665 return err 666 } 667 newCurrentWorkspace := "" 668 669 // This map is used later when doing the migration per source/destination. 670 // If a source has 'default' and has state, then we ask what the new name should be. 671 // And further down when we actually run state migration for each 672 // source/destination workspace, we use this new name (where source is 'default') 673 // and set as destinationWorkspace. If the default workspace does not have 674 // state we will not prompt the user for a new name because empty workspaces 675 // do not get migrated. 676 defaultNewName := map[string]string{} 677 for i := 0; i < len(sourceWorkspaces); i++ { 678 if sourceWorkspaces[i] == backend.DefaultStateName { 679 // For the default workspace we want to look to see if there is any state 680 // before we ask for a workspace name to migrate the default workspace into. 681 sourceState, err := opts.Source.StateMgr(backend.DefaultStateName) 682 if err != nil { 683 return fmt.Errorf(strings.TrimSpace( 684 errMigrateSingleLoadDefault), opts.SourceType, err) 685 } 686 // RefreshState is what actually pulls the state to be evaluated. 687 if err := sourceState.RefreshState(); err != nil { 688 return fmt.Errorf(strings.TrimSpace( 689 errMigrateSingleLoadDefault), opts.SourceType, err) 690 } 691 if !sourceState.State().Empty() { 692 newName, err := m.promptNewWorkspaceName(opts.DestinationType) 693 if err != nil { 694 return err 695 } 696 defaultNewName[sourceWorkspaces[i]] = newName 697 } 698 } 699 } 700 701 // Fetch the pattern that will be used to rename the workspaces for Terraform Cloud. 702 // 703 // * For the general case, this will be a pattern provided by the user. 704 // 705 // * Specifically for a migration from the "remote" backend using 'prefix', we will 706 // instead 'migrate' the workspaces using a pattern based on the old prefix+name, 707 // not allowing a user to accidentally input the wrong pattern to line up with 708 // what the the remote backend was already using before (which presumably already 709 // meets the naming considerations for Terraform Cloud). 710 // In other words, this is a fast-track migration path from the remote backend, retaining 711 // how things already are in Terraform Cloud with no user intervention needed. 712 pattern := "" 713 if remoteBackend, ok := opts.Source.(*remote.Remote); ok { 714 if err := m.promptRemotePrefixToCloudTagsMigration(opts); err != nil { 715 return err 716 } 717 pattern = remoteBackend.WorkspaceNamePattern() 718 log.Printf("[TRACE] backendMigrateTFC: Remote backend reports workspace name pattern as: %q", pattern) 719 } 720 721 if pattern == "" { 722 pattern, err = m.promptMultiStateMigrationPattern(opts.SourceType) 723 if err != nil { 724 return err 725 } 726 } 727 728 // Go through each and migrate 729 for _, name := range sourceWorkspaces { 730 731 // Copy the same names 732 opts.sourceWorkspace = name 733 if newName, ok := defaultNewName[name]; ok { 734 // this has to be done before setting destinationWorkspace 735 name = newName 736 } 737 opts.destinationWorkspace = strings.Replace(pattern, "*", name, -1) 738 739 // Force it, we confirmed above 740 opts.force = true 741 742 // Perform the migration 743 log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration, source workspace %q to destination workspace %q", opts.sourceWorkspace, opts.destinationWorkspace) 744 if err := m.backendMigrateState_s_s(opts); err != nil { 745 return fmt.Errorf(strings.TrimSpace( 746 errMigrateMulti), name, opts.SourceType, opts.DestinationType, err) 747 } 748 749 if currentWorkspace == opts.sourceWorkspace { 750 newCurrentWorkspace = opts.destinationWorkspace 751 } 752 } 753 754 // After migrating multiple workspaces, we need to reselect the current workspace as it may 755 // have been renamed. Query the backend first to be sure it now exists. 756 workspaces, err := opts.Destination.Workspaces() 757 if err != nil { 758 return err 759 } 760 761 var workspacePresent bool 762 for _, name := range workspaces { 763 if name == newCurrentWorkspace { 764 workspacePresent = true 765 } 766 } 767 768 // If we couldn't select the workspace automatically from the backend (maybe it was empty 769 // and wasn't migrated, for instance), ask the user to select one instead and be done. 770 if !workspacePresent { 771 if err = m.selectWorkspace(opts.Destination); err != nil { 772 return err 773 } 774 return nil 775 } 776 777 // The newly renamed current workspace is present, so we'll automatically select it for the 778 // user, as well as display the equivalent of 'workspace list' to show how the workspaces 779 // were changed (as well as the newly selected current workspace). 780 if err = m.SetWorkspace(newCurrentWorkspace); err != nil { 781 return err 782 } 783 784 m.Ui.Output(m.Colorize().Color("[reset][bold]Migration complete! Your workspaces are as follows:[reset]")) 785 var out bytes.Buffer 786 for _, name := range workspaces { 787 if name == newCurrentWorkspace { 788 out.WriteString("* ") 789 } else { 790 out.WriteString(" ") 791 } 792 out.WriteString(name + "\n") 793 } 794 795 m.Ui.Output(out.String()) 796 797 return nil 798 } 799 800 func (m *Meta) promptSingleToCloudSingleStateMigration(opts *backendMigrateOpts) (bool, error) { 801 if !m.input { 802 log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") 803 return false, errors.New(strings.TrimSpace(errInteractiveInputDisabled)) 804 } 805 migrate := opts.force 806 if !migrate { 807 var err error 808 migrate, err = m.confirm(&terraform.InputOpts{ 809 Id: "backend-migrate-state-single-to-cloud-single", 810 Query: "Do you wish to proceed?", 811 Description: strings.TrimSpace(tfcInputBackendMigrateStateSingleToCloudSingle), 812 }) 813 if err != nil { 814 return false, fmt.Errorf("Error asking for state migration action: %s", err) 815 } 816 } 817 818 return migrate, nil 819 } 820 821 func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) error { 822 if !m.input { 823 log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") 824 return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) 825 } 826 migrate := opts.force 827 if !migrate { 828 var err error 829 migrate, err = m.confirm(&terraform.InputOpts{ 830 Id: "backend-migrate-remote-multistate-to-cloud", 831 Query: "Do you wish to proceed?", 832 Description: strings.TrimSpace(tfcInputBackendMigrateRemoteMultiToCloud), 833 }) 834 if err != nil { 835 return fmt.Errorf("Error asking for state migration action: %s", err) 836 } 837 } 838 839 if !migrate { 840 return fmt.Errorf("Migration aborted by user.") 841 } 842 843 return nil 844 } 845 846 // Multi-state to single state. 847 func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error { 848 if !m.input { 849 log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") 850 return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) 851 } 852 migrate := opts.force 853 if !migrate { 854 var err error 855 // Ask the user if they want to migrate their existing remote state 856 migrate, err = m.confirm(&terraform.InputOpts{ 857 Id: "backend-migrate-multistate-to-single", 858 Query: "Do you want to copy only your current workspace?", 859 Description: fmt.Sprintf( 860 strings.TrimSpace(tfcInputBackendMigrateMultiToSingle), 861 opts.SourceType, opts.destinationWorkspace), 862 }) 863 if err != nil { 864 return fmt.Errorf("Error asking for state migration action: %s", err) 865 } 866 } 867 868 if !migrate { 869 return fmt.Errorf("Migration aborted by user.") 870 } 871 872 return nil 873 } 874 875 func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) { 876 message := fmt.Sprintf("[reset][bold][yellow]The %q backend configuration only allows "+ 877 "named workspaces![reset]", destinationType) 878 if destinationType == "cloud" { 879 if !m.input { 880 log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") 881 return "", errors.New(strings.TrimSpace(errInteractiveInputDisabled)) 882 } 883 message = `[reset][bold][yellow]Terraform Cloud requires all workspaces to be given an explicit name.[reset]` 884 } 885 name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ 886 Id: "new-state-name", 887 Query: message, 888 Description: strings.TrimSpace(inputBackendNewWorkspaceName), 889 }) 890 if err != nil { 891 return "", fmt.Errorf("Error asking for new state name: %s", err) 892 } 893 894 return name, nil 895 } 896 897 func (m *Meta) promptMultiStateMigrationPattern(sourceType string) (string, error) { 898 // This is not the first prompt a user would be presented with in the migration to TFC, so no 899 // guard on m.input is needed here. 900 renameWorkspaces, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ 901 Id: "backend-migrate-multistate-to-tfc", 902 Query: fmt.Sprintf("[reset][bold][yellow]%s[reset]", "Would you like to rename your workspaces?"), 903 Description: fmt.Sprintf(strings.TrimSpace(tfcInputBackendMigrateMultiToMulti), sourceType), 904 }) 905 if err != nil { 906 return "", fmt.Errorf("Error asking for state migration action: %s", err) 907 } 908 if renameWorkspaces != "2" && renameWorkspaces != "1" { 909 return "", fmt.Errorf("Please select 1 or 2 as part of this option.") 910 } 911 if renameWorkspaces == "2" { 912 // this means they did not want to rename their workspaces, and we are 913 // returning a generic '*' that means use the same workspace name during 914 // migration. 915 return "*", nil 916 } 917 918 pattern, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ 919 Id: "backend-migrate-multistate-to-tfc-pattern", 920 Query: fmt.Sprintf("[reset][bold][yellow]%s[reset]", "How would you like to rename your workspaces?"), 921 Description: strings.TrimSpace(tfcInputBackendMigrateMultiToMultiPattern), 922 }) 923 if err != nil { 924 return "", fmt.Errorf("Error asking for state migration action: %s", err) 925 } 926 if !strings.Contains(pattern, "*") { 927 return "", fmt.Errorf("The pattern must have an '*'") 928 } 929 930 if count := strings.Count(pattern, "*"); count > 1 { 931 return "", fmt.Errorf("The pattern '*' cannot be used more than once.") 932 } 933 934 return pattern, nil 935 } 936 937 const errMigrateLoadStates = ` 938 Error inspecting states in the %q backend: 939 %s 940 941 Prior to changing backends, Terraform inspects the source and destination 942 states to determine what kind of migration steps need to be taken, if any. 943 Terraform failed to load the states. The data in both the source and the 944 destination remain unmodified. Please resolve the above error and try again. 945 ` 946 947 const errMigrateSingleLoadDefault = ` 948 Error loading state: 949 %[2]s 950 951 Terraform failed to load the default state from the %[1]q backend. 952 State migration cannot occur unless the state can be loaded. Backend 953 modification and state migration has been aborted. The state in both the 954 source and the destination remain unmodified. Please resolve the 955 above error and try again. 956 ` 957 958 const errMigrateMulti = ` 959 Error migrating the workspace %q from the previous %q backend 960 to the newly configured %q backend: 961 %s 962 963 Terraform copies workspaces in alphabetical order. Any workspaces 964 alphabetically earlier than this one have been copied. Any workspaces 965 later than this haven't been modified in the destination. No workspaces 966 in the source state have been modified. 967 968 Please resolve the error above and run the initialization command again. 969 This will attempt to copy (with permission) all workspaces again. 970 ` 971 972 const errBackendStateCopy = ` 973 Error copying state from the previous %q backend to the newly configured 974 %q backend: 975 %s 976 977 The state in the previous backend remains intact and unmodified. Please resolve 978 the error above and try again. 979 ` 980 981 const errTFCMigrateNotYetImplemented = ` 982 Migrating state from Terraform Cloud to another backend is not yet implemented. 983 984 Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html 985 ` 986 987 const errInteractiveInputDisabled = ` 988 Can't ask approval for state migration when interactive input is disabled. 989 990 Please remove the "-input=false" option and try again. 991 ` 992 993 const tfcInputBackendMigrateMultiToMultiPattern = ` 994 Enter a pattern with an asterisk (*) to rename all workspaces based on their 995 previous names. The asterisk represents the current workspace name. 996 997 For example, if a workspace is currently named 'prod', the pattern 'app-*' would yield 998 'app-prod' for a new workspace name; 'app-*-region1' would yield 'app-prod-region1'. 999 ` 1000 1001 const tfcInputBackendMigrateMultiToMulti = ` 1002 Unlike typical Terraform workspaces representing an environment associated with a particular 1003 configuration (e.g. production, staging, development), Terraform Cloud workspaces are named uniquely 1004 across all configurations used within an organization. A typical strategy to start with is 1005 <COMPONENT>-<ENVIRONMENT>-<REGION> (e.g. networking-prod-us-east, networking-staging-us-east). 1006 1007 For more information on workspace naming, see https://www.terraform.io/docs/cloud/workspaces/naming.html 1008 1009 When migrating existing workspaces from the backend %[1]q to Terraform Cloud, would you like to 1010 rename your workspaces? Enter 1 or 2. 1011 1012 1. Yes, I'd like to rename all workspaces according to a pattern I will provide. 1013 2. No, I would not like to rename my workspaces. Migrate them as currently named. 1014 ` 1015 1016 const tfcInputBackendMigrateMultiToSingle = ` 1017 The previous backend %[1]q has multiple workspaces, but Terraform Cloud has 1018 been configured to use a single workspace (%[2]q). By continuing, you will 1019 only migrate your current workspace. If you wish to migrate all workspaces 1020 from the previous backend, you may cancel this operation and use the 'tags' 1021 strategy in your workspace configuration block instead. 1022 1023 Enter "yes" to proceed or "no" to cancel. 1024 ` 1025 1026 const tfcInputBackendMigrateStateSingleToCloudSingle = ` 1027 As part of migrating to Terraform Cloud, Terraform can optionally copy your 1028 current workspace state to the configured Terraform Cloud workspace. 1029 1030 Answer "yes" to copy the latest state snapshot to the configured 1031 Terraform Cloud workspace. 1032 1033 Answer "no" to ignore the existing state and just activate the configured 1034 Terraform Cloud workspace with its existing state, if any. 1035 1036 Should Terraform migrate your existing state? 1037 ` 1038 1039 const tfcInputBackendMigrateRemoteMultiToCloud = ` 1040 When migrating from the 'remote' backend to Terraform's native integration 1041 with Terraform Cloud, Terraform will automatically create or use existing 1042 workspaces based on the previous backend configuration's 'prefix' value. 1043 1044 When the migration is complete, workspace names in Terraform will match the 1045 fully qualified Terraform Cloud workspace name. If necessary, the workspace 1046 tags configured in the 'cloud' option block will be added to the associated 1047 Terraform Cloud workspaces. 1048 1049 Enter "yes" to proceed or "no" to cancel. 1050 ` 1051 1052 const inputBackendMigrateEmpty = ` 1053 Pre-existing state was found while migrating the previous %q backend to the 1054 newly configured %q backend. No existing state was found in the newly 1055 configured %[2]q backend. Do you want to copy this state to the new %[2]q 1056 backend? Enter "yes" to copy and "no" to start with an empty state. 1057 ` 1058 1059 const inputBackendMigrateEmptyCloud = ` 1060 Pre-existing state was found while migrating the previous %q backend to Terraform Cloud. 1061 No existing state was found in Terraform Cloud. Do you want to copy this state to Terraform Cloud? 1062 Enter "yes" to copy and "no" to start with an empty state. 1063 ` 1064 1065 const inputBackendMigrateNonEmpty = ` 1066 Pre-existing state was found while migrating the previous %q backend to the 1067 newly configured %q backend. An existing non-empty state already exists in 1068 the new backend. The two states have been saved to temporary files that will be 1069 removed after responding to this query. 1070 1071 Previous (type %[1]q): %[3]s 1072 New (type %[2]q): %[4]s 1073 1074 Do you want to overwrite the state in the new backend with the previous state? 1075 Enter "yes" to copy and "no" to start with the existing state in the newly 1076 configured %[2]q backend. 1077 ` 1078 1079 const inputBackendMigrateNonEmptyCloud = ` 1080 Pre-existing state was found while migrating the previous %q backend to 1081 Terraform Cloud. An existing non-empty state already exists in Terraform Cloud. 1082 The two states have been saved to temporary files that will be removed after 1083 responding to this query. 1084 1085 Previous (type %[1]q): %[2]s 1086 New (Terraform Cloud): %[3]s 1087 1088 Do you want to overwrite the state in Terraform Cloud with the previous state? 1089 Enter "yes" to copy and "no" to start with the existing state in Terraform Cloud. 1090 ` 1091 1092 const inputBackendMigrateMultiToSingle = ` 1093 The existing %[1]q backend supports workspaces and you currently are 1094 using more than one. The newly configured %[2]q backend doesn't support 1095 workspaces. If you continue, Terraform will copy your current workspace %[3]q 1096 to the default workspace in the new backend. Your existing workspaces in the 1097 source backend won't be modified. If you want to switch workspaces, back them 1098 up, or cancel altogether, answer "no" and Terraform will abort. 1099 ` 1100 1101 const inputBackendMigrateMultiToMulti = ` 1102 Both the existing %[1]q backend and the newly configured %[2]q backend 1103 support workspaces. When migrating between backends, Terraform will copy 1104 all workspaces (with the same names). THIS WILL OVERWRITE any conflicting 1105 states in the destination. 1106 1107 Terraform initialization doesn't currently migrate only select workspaces. 1108 If you want to migrate a select number of workspaces, you must manually 1109 pull and push those states. 1110 1111 If you answer "yes", Terraform will migrate all states. If you answer 1112 "no", Terraform will abort. 1113 ` 1114 1115 const inputBackendNewWorkspaceName = ` 1116 Please provide a new workspace name (e.g. dev, test) that will be used 1117 to migrate the existing default workspace. 1118 ` 1119 1120 const inputBackendSelectWorkspace = ` 1121 This is expected behavior when the selected workspace did not have an 1122 existing non-empty state. Please enter a number to select a workspace: 1123 1124 %s 1125 `