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