github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/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 := context.Background() 237 238 lockerOne := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize()) 239 if err := lockerOne.Lock(stateOne, "migration source state"); err != nil { 240 return fmt.Errorf("Error locking source state: %s", err) 241 } 242 defer lockerOne.Unlock(nil) 243 244 lockerTwo := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize()) 245 if err := lockerTwo.Lock(stateTwo, "migration destination state"); err != nil { 246 return fmt.Errorf("Error locking destination state: %s", err) 247 } 248 defer lockerTwo.Unlock(nil) 249 250 // We now own a lock, so double check that we have the version 251 // corresponding to the lock. 252 if err := stateOne.RefreshState(); err != nil { 253 return fmt.Errorf(strings.TrimSpace( 254 errMigrateSingleLoadDefault), opts.OneType, err) 255 } 256 if err := stateTwo.RefreshState(); err != nil { 257 return fmt.Errorf(strings.TrimSpace( 258 errMigrateSingleLoadDefault), opts.OneType, err) 259 } 260 261 one = stateOne.State() 262 two = stateTwo.State() 263 } 264 265 // Clear the legacy remote state in both cases. If we're at the migration 266 // step then this won't be used anymore. 267 if one != nil { 268 one.Remote = nil 269 } 270 if two != nil { 271 two.Remote = nil 272 } 273 274 var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error) 275 switch { 276 // No migration necessary 277 case one.Empty() && two.Empty(): 278 return nil 279 280 // No migration necessary if we're inheriting state. 281 case one.Empty() && !two.Empty(): 282 return nil 283 284 // We have existing state moving into no state. Ask the user if 285 // they'd like to do this. 286 case !one.Empty() && two.Empty(): 287 confirmFunc = m.backendMigrateEmptyConfirm 288 289 // Both states are non-empty, meaning we need to determine which 290 // state should be used and update accordingly. 291 case !one.Empty() && !two.Empty(): 292 confirmFunc = m.backendMigrateNonEmptyConfirm 293 } 294 295 if confirmFunc == nil { 296 panic("confirmFunc must not be nil") 297 } 298 299 if !opts.force { 300 // Abort if we can't ask for input. 301 if !m.input { 302 return errors.New("error asking for state migration action: input disabled") 303 } 304 305 // Confirm with the user whether we want to copy state over 306 confirm, err := confirmFunc(stateOne, stateTwo, opts) 307 if err != nil { 308 return err 309 } 310 if !confirm { 311 return nil 312 } 313 } 314 315 // Confirmed! Write. 316 if err := stateTwo.WriteState(one); err != nil { 317 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 318 opts.OneType, opts.TwoType, err) 319 } 320 if err := stateTwo.PersistState(); err != nil { 321 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 322 opts.OneType, opts.TwoType, err) 323 } 324 325 // And we're done. 326 return nil 327 } 328 329 func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) { 330 inputOpts := &terraform.InputOpts{ 331 Id: "backend-migrate-copy-to-empty", 332 Query: "Do you want to copy existing state to the new backend?", 333 Description: fmt.Sprintf( 334 strings.TrimSpace(inputBackendMigrateEmpty), 335 opts.OneType, opts.TwoType), 336 } 337 338 return m.confirm(inputOpts) 339 } 340 341 func (m *Meta) backendMigrateNonEmptyConfirm( 342 stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) { 343 // We need to grab both states so we can write them to a file 344 one := stateOne.State() 345 two := stateTwo.State() 346 347 // Save both to a temporary 348 td, err := ioutil.TempDir("", "terraform") 349 if err != nil { 350 return false, fmt.Errorf("Error creating temporary directory: %s", err) 351 } 352 defer os.RemoveAll(td) 353 354 // Helper to write the state 355 saveHelper := func(n, path string, s *terraform.State) error { 356 f, err := os.Create(path) 357 if err != nil { 358 return err 359 } 360 defer f.Close() 361 362 return terraform.WriteState(s, f) 363 } 364 365 // Write the states 366 onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType)) 367 twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType)) 368 if err := saveHelper(opts.OneType, onePath, one); err != nil { 369 return false, fmt.Errorf("Error saving temporary state: %s", err) 370 } 371 if err := saveHelper(opts.TwoType, twoPath, two); err != nil { 372 return false, fmt.Errorf("Error saving temporary state: %s", err) 373 } 374 375 // Ask for confirmation 376 inputOpts := &terraform.InputOpts{ 377 Id: "backend-migrate-to-backend", 378 Query: "Do you want to copy existing state to the new backend?", 379 Description: fmt.Sprintf( 380 strings.TrimSpace(inputBackendMigrateNonEmpty), 381 opts.OneType, opts.TwoType, onePath, twoPath), 382 } 383 384 // Confirm with the user that the copy should occur 385 return m.confirm(inputOpts) 386 } 387 388 type backendMigrateOpts struct { 389 OneType, TwoType string 390 One, Two backend.Backend 391 392 // Fields below are set internally when migrate is called 393 394 oneEnv string // source env 395 twoEnv string // dest env 396 force bool // if true, won't ask for confirmation 397 } 398 399 const errMigrateLoadStates = ` 400 Error inspecting states in the %q backend: 401 %s 402 403 Prior to changing backends, Terraform inspects the source and destination 404 states to determine what kind of migration steps need to be taken, if any. 405 Terraform failed to load the states. The data in both the source and the 406 destination remain unmodified. Please resolve the above error and try again. 407 ` 408 409 const errMigrateSingleLoadDefault = ` 410 Error loading state: 411 %[2]s 412 413 Terraform failed to load the default state from the %[1]q backend. 414 State migration cannot occur unless the state can be loaded. Backend 415 modification and state migration has been aborted. The state in both the 416 source and the destination remain unmodified. Please resolve the 417 above error and try again. 418 ` 419 420 const errMigrateMulti = ` 421 Error migrating the workspace %q from the previous %q backend to the newly 422 configured %q backend: 423 %s 424 425 Terraform copies workspaces in alphabetical order. Any workspaces 426 alphabetically earlier than this one have been copied. Any workspaces 427 later than this haven't been modified in the destination. No workspaces 428 in the source state have been modified. 429 430 Please resolve the error above and run the initialization command again. 431 This will attempt to copy (with permission) all workspaces again. 432 ` 433 434 const errBackendStateCopy = ` 435 Error copying state from the previous %q backend to the newly configured %q backend: 436 %s 437 438 The state in the previous backend remains intact and unmodified. Please resolve 439 the error above and try again. 440 ` 441 442 const inputBackendMigrateEmpty = ` 443 Pre-existing state was found while migrating the previous %q backend to the 444 newly configured %q backend. No existing state was found in the newly 445 configured %[2]q backend. Do you want to copy this state to the new %[2]q 446 backend? Enter "yes" to copy and "no" to start with an empty state. 447 ` 448 449 const inputBackendMigrateNonEmpty = ` 450 Pre-existing state was found while migrating the previous %q backend to the 451 newly configured %q backend. An existing non-empty state already exists in 452 the new backend. The two states have been saved to temporary files that will be 453 removed after responding to this query. 454 455 Previous (type %[1]q): %[3]s 456 New (type %[2]q): %[4]s 457 458 Do you want to overwrite the state in the new backend with the previous state? 459 Enter "yes" to copy and "no" to start with the existing state in the newly 460 configured %[2]q backend. 461 ` 462 463 const inputBackendMigrateMultiToSingle = ` 464 The existing %[1]q backend supports workspaces and you currently are 465 using more than one. The newly configured %[2]q backend doesn't support 466 workspaces. If you continue, Terraform will copy your current workspace %[3]q 467 to the default workspace in the target backend. Your existing workspaces in the 468 source backend won't be modified. If you want to switch workspaces, back them 469 up, or cancel altogether, answer "no" and Terraform will abort. 470 ` 471 472 const inputBackendMigrateMultiToMulti = ` 473 Both the existing %[1]q backend and the newly configured %[2]q backend support 474 workspaces. When migrating between backends, Terraform will copy all 475 workspaces (with the same names). THIS WILL OVERWRITE any conflicting 476 states in the destination. 477 478 Terraform initialization doesn't currently migrate only select workspaces. 479 If you want to migrate a select number of workspaces, you must manually 480 pull and push those states. 481 482 If you answer "yes", Terraform will migrate all states. If you answer 483 "no", Terraform will abort. 484 `