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