github.com/ggriffiths/terraform@v0.9.0-beta1.0.20170222213024-79c4935604cb/command/meta_backend_migrate.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 10 clistate "github.com/hashicorp/terraform/command/state" 11 "github.com/hashicorp/terraform/state" 12 "github.com/hashicorp/terraform/terraform" 13 ) 14 15 // backendMigrateState handles migrating (copying) state from one backend 16 // to another. This function handles asking the user for confirmation 17 // as well as the copy itself. 18 // 19 // This function can handle all scenarios of state migration regardless 20 // of the existence of state in either backend. 21 // 22 // After migrating the state, the existing state in the first backend 23 // remains untouched. 24 // 25 // This will attempt to lock both states for the migration. 26 func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { 27 lockInfoOne := state.NewLockInfo() 28 lockInfoOne.Operation = "migration" 29 lockInfoOne.Info = "source state" 30 31 lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize()) 32 if err != nil { 33 return fmt.Errorf("Error locking source state: %s", err) 34 } 35 defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize()) 36 37 lockInfoTwo := state.NewLockInfo() 38 lockInfoTwo.Operation = "migration" 39 lockInfoTwo.Info = "destination state" 40 41 lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize()) 42 if err != nil { 43 return fmt.Errorf("Error locking destination state: %s", err) 44 } 45 defer clistate.Unlock(opts.Two, lockIDTwo, m.Ui, m.Colorize()) 46 47 one := opts.One.State() 48 two := opts.Two.State() 49 50 var confirmFunc func(opts *backendMigrateOpts) (bool, error) 51 switch { 52 // No migration necessary 53 case one.Empty() && two.Empty(): 54 return nil 55 56 // No migration necessary if we're inheriting state. 57 case one.Empty() && !two.Empty(): 58 return nil 59 60 // We have existing state moving into no state. Ask the user if 61 // they'd like to do this. 62 case !one.Empty() && two.Empty(): 63 confirmFunc = m.backendMigrateEmptyConfirm 64 65 // Both states are non-empty, meaning we need to determine which 66 // state should be used and update accordingly. 67 case !one.Empty() && !two.Empty(): 68 confirmFunc = m.backendMigrateNonEmptyConfirm 69 } 70 71 if confirmFunc == nil { 72 panic("confirmFunc must not be nil") 73 } 74 75 // Confirm with the user whether we want to copy state over 76 confirm, err := confirmFunc(opts) 77 if err != nil { 78 return err 79 } 80 if !confirm { 81 return nil 82 } 83 84 // Confirmed! Write. 85 if err := opts.Two.WriteState(one); err != nil { 86 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 87 opts.OneType, opts.TwoType, err) 88 } 89 if err := opts.Two.PersistState(); err != nil { 90 return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), 91 opts.OneType, opts.TwoType, err) 92 } 93 94 // And we're done. 95 return nil 96 } 97 98 func (m *Meta) backendMigrateEmptyConfirm(opts *backendMigrateOpts) (bool, error) { 99 inputOpts := &terraform.InputOpts{ 100 Id: "backend-migrate-to-backend", 101 Query: fmt.Sprintf( 102 "Do you want to copy state from %q to %q?", 103 opts.OneType, opts.TwoType), 104 Description: fmt.Sprintf( 105 strings.TrimSpace(inputBackendMigrateEmpty), 106 opts.OneType, opts.TwoType), 107 } 108 109 // Confirm with the user that the copy should occur 110 for { 111 v, err := m.UIInput().Input(inputOpts) 112 if err != nil { 113 return false, fmt.Errorf( 114 "Error asking for state copy action: %s", err) 115 } 116 117 switch strings.ToLower(v) { 118 case "no": 119 return false, nil 120 121 case "yes": 122 return true, nil 123 } 124 } 125 } 126 127 func (m *Meta) backendMigrateNonEmptyConfirm(opts *backendMigrateOpts) (bool, error) { 128 // We need to grab both states so we can write them to a file 129 one := opts.One.State() 130 two := opts.Two.State() 131 132 // Save both to a temporary 133 td, err := ioutil.TempDir("", "terraform") 134 if err != nil { 135 return false, fmt.Errorf("Error creating temporary directory: %s", err) 136 } 137 defer os.RemoveAll(td) 138 139 // Helper to write the state 140 saveHelper := func(n, path string, s *terraform.State) error { 141 f, err := os.Create(path) 142 if err != nil { 143 return err 144 } 145 defer f.Close() 146 147 return terraform.WriteState(s, f) 148 } 149 150 // Write the states 151 onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType)) 152 twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType)) 153 if err := saveHelper(opts.OneType, onePath, one); err != nil { 154 return false, fmt.Errorf("Error saving temporary state: %s", err) 155 } 156 if err := saveHelper(opts.TwoType, twoPath, two); err != nil { 157 return false, fmt.Errorf("Error saving temporary state: %s", err) 158 } 159 160 // Ask for confirmation 161 inputOpts := &terraform.InputOpts{ 162 Id: "backend-migrate-to-backend", 163 Query: fmt.Sprintf( 164 "Do you want to copy state from %q to %q?", 165 opts.OneType, opts.TwoType), 166 Description: fmt.Sprintf( 167 strings.TrimSpace(inputBackendMigrateNonEmpty), 168 opts.OneType, opts.TwoType, onePath, twoPath), 169 } 170 171 // Confirm with the user that the copy should occur 172 for { 173 v, err := m.UIInput().Input(inputOpts) 174 if err != nil { 175 return false, fmt.Errorf( 176 "Error asking for state copy action: %s", err) 177 } 178 179 switch strings.ToLower(v) { 180 case "no": 181 return false, nil 182 183 case "yes": 184 return true, nil 185 } 186 } 187 } 188 189 type backendMigrateOpts struct { 190 OneType, TwoType string 191 One, Two state.State 192 } 193 194 const errBackendStateCopy = ` 195 Error copying state from %q to %q: %s 196 197 The state in %[1]q remains intact and unmodified. Please resolve the 198 error above and try again. 199 ` 200 201 const inputBackendMigrateEmpty = ` 202 Pre-existing state was found in %q while migrating to %q. No existing 203 state was found in %[2]q. Do you want to copy the state from %[1]q to 204 %[2]q? Enter "yes" to copy and "no" to start with an empty state. 205 ` 206 207 const inputBackendMigrateNonEmpty = ` 208 Pre-existing state was found in %q while migrating to %q. An existing 209 non-empty state exists in %[2]q. The two states have been saved to temporary 210 files that will be removed after responding to this query. 211 212 One (%[1]q): %[3]s 213 Two (%[2]q): %[4]s 214 215 Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy 216 and "no" to start with the existing state in %[2]q. 217 `