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