forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
diff.go
258 lines (215 loc) · 5.4 KB
/
diff.go
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package terraform
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"io"
"sort"
"strings"
"sync"
)
// The format byte is prefixed into the diff file format so that we have
// the ability in the future to change the file format if we want for any
// reason.
const diffFormatByte byte = 1
// Diff tracks the differences between resources to apply.
type Diff struct {
Resources map[string]*ResourceDiff
once sync.Once
}
// ReadDiff reads a diff structure out of a reader in the format that
// was written by WriteDiff.
func ReadDiff(src io.Reader) (*Diff, error) {
var result *Diff
var formatByte [1]byte
n, err := src.Read(formatByte[:])
if err != nil {
return nil, err
}
if n != len(formatByte) {
return nil, errors.New("failed to read diff version byte")
}
if formatByte[0] != diffFormatByte {
return nil, fmt.Errorf("unknown diff file version: %d", formatByte[0])
}
dec := gob.NewDecoder(src)
if err := dec.Decode(&result); err != nil {
return nil, err
}
return result, nil
}
// WriteDiff writes a diff somewhere in a binary format.
func WriteDiff(d *Diff, dst io.Writer) error {
n, err := dst.Write([]byte{diffFormatByte})
if err != nil {
return err
}
if n != 1 {
return errors.New("failed to write diff version byte")
}
return gob.NewEncoder(dst).Encode(d)
}
func (d *Diff) init() {
d.once.Do(func() {
if d.Resources == nil {
d.Resources = make(map[string]*ResourceDiff)
}
})
}
// Empty returns true if the diff has no changes.
func (d *Diff) Empty() bool {
if len(d.Resources) == 0 {
return true
}
for _, rd := range d.Resources {
if !rd.Empty() {
return false
}
}
return true
}
// String outputs the diff in a long but command-line friendly output
// format that users can read to quickly inspect a diff.
func (d *Diff) String() string {
var buf bytes.Buffer
names := make([]string, 0, len(d.Resources))
for name, _ := range d.Resources {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
rdiff := d.Resources[name]
crud := "UPDATE"
if rdiff.RequiresNew() && rdiff.Destroy {
crud = "DESTROY/CREATE"
} else if rdiff.Destroy {
crud = "DESTROY"
} else if rdiff.RequiresNew() {
crud = "CREATE"
}
buf.WriteString(fmt.Sprintf(
"%s: %s\n",
crud,
name))
keyLen := 0
keys := make([]string, 0, len(rdiff.Attributes))
for key, _ := range rdiff.Attributes {
if key == "id" {
continue
}
keys = append(keys, key)
if len(key) > keyLen {
keyLen = len(key)
}
}
sort.Strings(keys)
for _, attrK := range keys {
attrDiff := rdiff.Attributes[attrK]
v := attrDiff.New
if attrDiff.NewComputed {
v = "<computed>"
}
newResource := ""
if attrDiff.RequiresNew {
newResource = " (forces new resource)"
}
buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
attrDiff.Old,
v,
newResource))
}
}
return buf.String()
}
// ResourceDiff is the diff of a resource from some state to another.
type ResourceDiff struct {
Attributes map[string]*ResourceAttrDiff
Destroy bool
once sync.Once
}
// ResourceAttrDiff is the diff of a single attribute of a resource.
type ResourceAttrDiff struct {
Old string // Old Value
New string // New Value
NewComputed bool // True if new value is computed (unknown currently)
NewRemoved bool // True if this attribute is being removed
NewExtra interface{} // Extra information for the provider
RequiresNew bool // True if change requires new resource
Type DiffAttrType
}
func (d *ResourceAttrDiff) GoString() string {
return fmt.Sprintf("*%#v", *d)
}
// DiffAttrType is an enum type that says whether a resource attribute
// diff is an input attribute (comes from the configuration) or an
// output attribute (comes as a result of applying the configuration). An
// example input would be "ami" for AWS and an example output would be
// "private_ip".
type DiffAttrType byte
const (
DiffAttrUnknown DiffAttrType = iota
DiffAttrInput
DiffAttrOutput
)
func (d *ResourceDiff) init() {
d.once.Do(func() {
if d.Attributes == nil {
d.Attributes = make(map[string]*ResourceAttrDiff)
}
})
}
// Empty returns true if this diff encapsulates no changes.
func (d *ResourceDiff) Empty() bool {
if d == nil {
return true
}
return !d.Destroy && len(d.Attributes) == 0
}
// RequiresNew returns true if the diff requires the creation of a new
// resource (implying the destruction of the old).
func (d *ResourceDiff) RequiresNew() bool {
if d == nil {
return false
}
for _, rd := range d.Attributes {
if rd != nil && rd.RequiresNew {
return true
}
}
return false
}
// Same checks whether or not to ResourceDiffs are the "same." When
// we say "same", it is not necessarily exactly equal. Instead, it is
// just checking that the same attributes are changing, a destroy
// isn't suddenly happening, etc.
func (d *ResourceDiff) Same(d2 *ResourceDiff) bool {
if d == nil && d2 == nil {
return true
} else if d == nil || d2 == nil {
return false
}
if d.Destroy != d2.Destroy {
return false
}
if d.RequiresNew() != d2.RequiresNew() {
return false
}
if len(d.Attributes) != len(d2.Attributes) {
return false
}
ks := make(map[string]struct{})
for k, _ := range d.Attributes {
ks[k] = struct{}{}
}
for k, _ := range d2.Attributes {
delete(ks, k)
}
if len(ks) > 0 {
return false
}
return true
}