github.com/kjmkznr/terraform@v0.5.2-0.20180216194316-1d0f5fdac99e/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 workspaces 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.Workspace() 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 workspaces.\n"+ 175 "Do you want to copy only your current workspace?", 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.SetWorkspace(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: input 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: "Do you want to copy existing state to the new backend?", 342 Description: fmt.Sprintf( 343 strings.TrimSpace(inputBackendMigrateEmpty), 344 opts.OneType, opts.TwoType), 345 } 346 347 return m.confirm(inputOpts) 348 } 349 350 func (m *Meta) backendMigrateNonEmptyConfirm( 351 stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) { 352 // We need to grab both states so we can write them to a file 353 one := stateOne.State() 354 two := stateTwo.State() 355 356 // Save both to a temporary 357 td, err := ioutil.TempDir("", "terraform") 358 if err != nil { 359 return false, fmt.Errorf("Error creating temporary directory: %s", err) 360 } 361 defer os.RemoveAll(td) 362 363 // Helper to write the state 364 saveHelper := func(n, path string, s *terraform.State) error { 365 f, err := os.Create(path) 366 if err != nil { 367 return err 368 } 369 defer f.Close() 370 371 return terraform.WriteState(s, f) 372 } 373 374 // Write the states 375 onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType)) 376 twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType)) 377 if err := saveHelper(opts.OneType, onePath, one); err != nil { 378 return false, fmt.Errorf("Error saving temporary state: %s", err) 379 } 380 if err := saveHelper(opts.TwoType, twoPath, two); err != nil { 381 return false, fmt.Errorf("Error saving temporary state: %s", err) 382 } 383 384 // Ask for confirmation 385 inputOpts := &terraform.InputOpts{ 386 Id: "backend-migrate-to-backend", 387 Query: "Do you want to copy existing state to the new backend?", 388 Description: fmt.Sprintf( 389 strings.TrimSpace(inputBackendMigrateNonEmpty), 390 opts.OneType, opts.TwoType, onePath, twoPath), 391 } 392 393 // Confirm with the user that the copy should occur 394 return m.confirm(inputOpts) 395 } 396 397 type backendMigrateOpts struct { 398 OneType, TwoType string 399 One, Two backend.Backend 400 401 // Fields below are set internally when migrate is called 402 403 oneEnv string // source env 404 twoEnv string // dest env 405 force bool // if true, won't ask for confirmation 406 } 407 408 const errMigrateLoadStates = ` 409 Error inspecting states in the %q backend: 410 %s 411 412 Prior to changing backends, Terraform inspects the source and destination 413 states to determine what kind of migration steps need to be taken, if any. 414 Terraform failed to load the states. The data in both the source and the 415 destination remain unmodified. Please resolve the above error and try again. 416 ` 417 418 const errMigrateSingleLoadDefault = ` 419 Error loading state: 420 %[2]s 421 422 Terraform failed to load the default state from the %[1]q backend. 423 State migration cannot occur unless the state can be loaded. Backend 424 modification and state migration has been aborted. The state in both the 425 source and the destination remain unmodified. Please resolve the 426 above error and try again. 427 ` 428 429 const errMigrateMulti = ` 430 Error migrating the workspace %q from the previous %q backend to the newly 431 configured %q backend: 432 %s 433 434 Terraform copies workspaces in alphabetical order. Any workspaces 435 alphabetically earlier than this one have been copied. Any workspaces 436 later than this haven't been modified in the destination. No workspaces 437 in the source state have been modified. 438 439 Please resolve the error above and run the initialization command again. 440 This will attempt to copy (with permission) all workspaces again. 441 ` 442 443 const errBackendStateCopy = ` 444 Error copying state from the previous %q backend to the newly configured %q backend: 445 %s 446 447 The state in the previous backend remains intact and unmodified. Please resolve 448 the error above and try again. 449 ` 450 451 const inputBackendMigrateEmpty = ` 452 Pre-existing state was found while migrating the previous %q backend to the 453 newly configured %q backend. No existing state was found in the newly 454 configured %[2]q backend. Do you want to copy this state to the new %[2]q 455 backend? Enter "yes" to copy and "no" to start with an empty state. 456 ` 457 458 const inputBackendMigrateNonEmpty = ` 459 Pre-existing state was found while migrating the previous %q backend to the 460 newly configured %q backend. An existing non-empty state already exists in 461 the new backend. The two states have been saved to temporary files that will be 462 removed after responding to this query. 463 464 Previous (type %[1]q): %[3]s 465 New (type %[2]q): %[4]s 466 467 Do you want to overwrite the state in the new backend with the previous state? 468 Enter "yes" to copy and "no" to start with the existing state in the newly 469 configured %[2]q backend. 470 ` 471 472 const inputBackendMigrateMultiToSingle = ` 473 The existing %[1]q backend supports workspaces and you currently are 474 using more than one. The newly configured %[2]q backend doesn't support 475 workspaces. If you continue, Terraform will copy your current workspace %[3]q 476 to the default workspace in the target backend. Your existing workspaces in the 477 source backend won't be modified. If you want to switch workspaces, back them 478 up, or cancel altogether, answer "no" and Terraform will abort. 479 ` 480 481 const inputBackendMigrateMultiToMulti = ` 482 Both the existing %[1]q backend and the newly configured %[2]q backend support 483 workspaces. When migrating between backends, Terraform will copy all 484 workspaces (with the same names). THIS WILL OVERWRITE any conflicting 485 states in the destination. 486 487 Terraform initialization doesn't currently migrate only select workspaces. 488 If you want to migrate a select number of workspaces, you must manually 489 pull and push those states. 490 491 If you answer "yes", Terraform will migrate all states. If you answer 492 "no", Terraform will abort. 493 `