github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/terraform/diff.go (about) 1 package terraform 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "reflect" 8 "sort" 9 "strings" 10 ) 11 12 // DiffChangeType is an enum with the kind of changes a diff has planned. 13 type DiffChangeType byte 14 15 const ( 16 DiffInvalid DiffChangeType = iota 17 DiffNone 18 DiffCreate 19 DiffUpdate 20 DiffDestroy 21 DiffDestroyCreate 22 ) 23 24 // Diff trackes the changes that are necessary to apply a configuration 25 // to an existing infrastructure. 26 type Diff struct { 27 // Modules contains all the modules that have a diff 28 Modules []*ModuleDiff 29 } 30 31 // AddModule adds the module with the given path to the diff. 32 // 33 // This should be the preferred method to add module diffs since it 34 // allows us to optimize lookups later as well as control sorting. 35 func (d *Diff) AddModule(path []string) *ModuleDiff { 36 m := &ModuleDiff{Path: path} 37 m.init() 38 d.Modules = append(d.Modules, m) 39 return m 40 } 41 42 // ModuleByPath is used to lookup the module diff for the given path. 43 // This should be the prefered lookup mechanism as it allows for future 44 // lookup optimizations. 45 func (d *Diff) ModuleByPath(path []string) *ModuleDiff { 46 if d == nil { 47 return nil 48 } 49 for _, mod := range d.Modules { 50 if mod.Path == nil { 51 panic("missing module path") 52 } 53 if reflect.DeepEqual(mod.Path, path) { 54 return mod 55 } 56 } 57 return nil 58 } 59 60 // RootModule returns the ModuleState for the root module 61 func (d *Diff) RootModule() *ModuleDiff { 62 root := d.ModuleByPath(rootModulePath) 63 if root == nil { 64 panic("missing root module") 65 } 66 return root 67 } 68 69 // Empty returns true if the diff has no changes. 70 func (d *Diff) Empty() bool { 71 for _, m := range d.Modules { 72 if !m.Empty() { 73 return false 74 } 75 } 76 77 return true 78 } 79 80 func (d *Diff) String() string { 81 var buf bytes.Buffer 82 for _, m := range d.Modules { 83 mStr := m.String() 84 85 // If we're the root module, we just write the output directly. 86 if reflect.DeepEqual(m.Path, rootModulePath) { 87 buf.WriteString(mStr + "\n") 88 continue 89 } 90 91 buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], "."))) 92 93 s := bufio.NewScanner(strings.NewReader(mStr)) 94 for s.Scan() { 95 buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) 96 } 97 } 98 99 return strings.TrimSpace(buf.String()) 100 } 101 102 func (d *Diff) init() { 103 if d.Modules == nil { 104 rootDiff := &ModuleDiff{Path: rootModulePath} 105 d.Modules = []*ModuleDiff{rootDiff} 106 } 107 for _, m := range d.Modules { 108 m.init() 109 } 110 } 111 112 // ModuleDiff tracks the differences between resources to apply within 113 // a single module. 114 type ModuleDiff struct { 115 Path []string 116 Resources map[string]*InstanceDiff 117 } 118 119 func (d *ModuleDiff) init() { 120 if d.Resources == nil { 121 d.Resources = make(map[string]*InstanceDiff) 122 } 123 for _, r := range d.Resources { 124 r.init() 125 } 126 } 127 128 // ChangeType returns the type of changes that the diff for this 129 // module includes. 130 // 131 // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or 132 // DiffCreate. If an instance within the module has a DiffDestroyCreate 133 // then this will register as a DiffCreate for a module. 134 func (d *ModuleDiff) ChangeType() DiffChangeType { 135 result := DiffNone 136 for _, r := range d.Resources { 137 change := r.ChangeType() 138 switch change { 139 case DiffCreate: 140 fallthrough 141 case DiffDestroy: 142 if result == DiffNone { 143 result = change 144 } 145 case DiffDestroyCreate: 146 fallthrough 147 case DiffUpdate: 148 result = DiffUpdate 149 } 150 } 151 152 return result 153 } 154 155 // Empty returns true if the diff has no changes within this module. 156 func (d *ModuleDiff) Empty() bool { 157 if len(d.Resources) == 0 { 158 return true 159 } 160 161 for _, rd := range d.Resources { 162 if !rd.Empty() { 163 return false 164 } 165 } 166 167 return true 168 } 169 170 // Instances returns the instance diffs for the id given. This can return 171 // multiple instance diffs if there are counts within the resource. 172 func (d *ModuleDiff) Instances(id string) []*InstanceDiff { 173 var result []*InstanceDiff 174 for k, diff := range d.Resources { 175 if k == id || strings.HasPrefix(k, id+".") { 176 if !diff.Empty() { 177 result = append(result, diff) 178 } 179 } 180 } 181 182 return result 183 } 184 185 // IsRoot says whether or not this module diff is for the root module. 186 func (d *ModuleDiff) IsRoot() bool { 187 return reflect.DeepEqual(d.Path, rootModulePath) 188 } 189 190 // String outputs the diff in a long but command-line friendly output 191 // format that users can read to quickly inspect a diff. 192 func (d *ModuleDiff) String() string { 193 var buf bytes.Buffer 194 195 names := make([]string, 0, len(d.Resources)) 196 for name, _ := range d.Resources { 197 names = append(names, name) 198 } 199 sort.Strings(names) 200 201 for _, name := range names { 202 rdiff := d.Resources[name] 203 204 crud := "UPDATE" 205 if rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted) { 206 crud = "DESTROY/CREATE" 207 } else if rdiff.Destroy { 208 crud = "DESTROY" 209 } else if rdiff.RequiresNew() { 210 crud = "CREATE" 211 } 212 213 buf.WriteString(fmt.Sprintf( 214 "%s: %s\n", 215 crud, 216 name)) 217 218 keyLen := 0 219 keys := make([]string, 0, len(rdiff.Attributes)) 220 for key, _ := range rdiff.Attributes { 221 if key == "id" { 222 continue 223 } 224 225 keys = append(keys, key) 226 if len(key) > keyLen { 227 keyLen = len(key) 228 } 229 } 230 sort.Strings(keys) 231 232 for _, attrK := range keys { 233 attrDiff := rdiff.Attributes[attrK] 234 235 v := attrDiff.New 236 if attrDiff.NewComputed { 237 v = "<computed>" 238 } 239 240 newResource := "" 241 if attrDiff.RequiresNew { 242 newResource = " (forces new resource)" 243 } 244 245 buf.WriteString(fmt.Sprintf( 246 " %s:%s %#v => %#v%s\n", 247 attrK, 248 strings.Repeat(" ", keyLen-len(attrK)), 249 attrDiff.Old, 250 v, 251 newResource)) 252 } 253 } 254 255 return buf.String() 256 } 257 258 // InstanceDiff is the diff of a resource from some state to another. 259 type InstanceDiff struct { 260 Attributes map[string]*ResourceAttrDiff 261 Destroy bool 262 DestroyTainted bool 263 } 264 265 // ResourceAttrDiff is the diff of a single attribute of a resource. 266 type ResourceAttrDiff struct { 267 Old string // Old Value 268 New string // New Value 269 NewComputed bool // True if new value is computed (unknown currently) 270 NewRemoved bool // True if this attribute is being removed 271 NewExtra interface{} // Extra information for the provider 272 RequiresNew bool // True if change requires new resource 273 Type DiffAttrType 274 } 275 276 func (d *ResourceAttrDiff) GoString() string { 277 return fmt.Sprintf("*%#v", *d) 278 } 279 280 // DiffAttrType is an enum type that says whether a resource attribute 281 // diff is an input attribute (comes from the configuration) or an 282 // output attribute (comes as a result of applying the configuration). An 283 // example input would be "ami" for AWS and an example output would be 284 // "private_ip". 285 type DiffAttrType byte 286 287 const ( 288 DiffAttrUnknown DiffAttrType = iota 289 DiffAttrInput 290 DiffAttrOutput 291 ) 292 293 func (d *InstanceDiff) init() { 294 if d.Attributes == nil { 295 d.Attributes = make(map[string]*ResourceAttrDiff) 296 } 297 } 298 299 // ChangeType returns the DiffChangeType represented by the diff 300 // for this single instance. 301 func (d *InstanceDiff) ChangeType() DiffChangeType { 302 if d.Empty() { 303 return DiffNone 304 } 305 306 if d.RequiresNew() && (d.Destroy || d.DestroyTainted) { 307 return DiffDestroyCreate 308 } 309 310 if d.Destroy { 311 return DiffDestroy 312 } 313 314 if d.RequiresNew() { 315 return DiffCreate 316 } 317 318 return DiffUpdate 319 } 320 321 // Empty returns true if this diff encapsulates no changes. 322 func (d *InstanceDiff) Empty() bool { 323 if d == nil { 324 return true 325 } 326 327 return !d.Destroy && len(d.Attributes) == 0 328 } 329 330 // RequiresNew returns true if the diff requires the creation of a new 331 // resource (implying the destruction of the old). 332 func (d *InstanceDiff) RequiresNew() bool { 333 if d == nil { 334 return false 335 } 336 337 for _, rd := range d.Attributes { 338 if rd != nil && rd.RequiresNew { 339 return true 340 } 341 } 342 343 return false 344 } 345 346 // Same checks whether or not to InstanceDiff are the "same." When 347 // we say "same", it is not necessarily exactly equal. Instead, it is 348 // just checking that the same attributes are changing, a destroy 349 // isn't suddenly happening, etc. 350 func (d *InstanceDiff) Same(d2 *InstanceDiff) bool { 351 if d == nil && d2 == nil { 352 return true 353 } else if d == nil || d2 == nil { 354 return false 355 } 356 357 if d.Destroy != d2.Destroy { 358 return false 359 } 360 if d.RequiresNew() != d2.RequiresNew() { 361 return false 362 } 363 364 // Go through the old diff and make sure the new diff has all the 365 // same attributes. To start, build up the check map to be all the keys. 366 checkOld := make(map[string]struct{}) 367 checkNew := make(map[string]struct{}) 368 for k, _ := range d.Attributes { 369 checkOld[k] = struct{}{} 370 } 371 for k, _ := range d2.Attributes { 372 checkNew[k] = struct{}{} 373 } 374 for k, diffOld := range d.Attributes { 375 if _, ok := checkOld[k]; !ok { 376 // We're not checking this key for whatever reason (see where 377 // check is modified). 378 continue 379 } 380 381 // Remove this key since we'll never hit it again 382 delete(checkOld, k) 383 delete(checkNew, k) 384 385 _, ok := d2.Attributes[k] 386 if !ok { 387 // The matching attribute was not found, we're different 388 return false 389 } 390 391 if diffOld.NewComputed && strings.HasSuffix(k, ".#") { 392 // This is a computed list, so remove any keys with this 393 // prefix from the check list. 394 kprefix := k[0:len(k)-2] + "." 395 for k2, _ := range checkOld { 396 if strings.HasPrefix(k2, kprefix) { 397 delete(checkOld, k2) 398 } 399 } 400 for k2, _ := range checkNew { 401 if strings.HasPrefix(k2, kprefix) { 402 delete(checkNew, k2) 403 } 404 } 405 } 406 407 // TODO: check for the same value if not computed 408 } 409 410 // Check for leftover attributes 411 if len(checkNew) > 0 { 412 return false 413 } 414 415 return true 416 }