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