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