github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/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 preferred 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, DiffDestroy: 153 if result == DiffNone { 154 result = change 155 } 156 case DiffDestroyCreate, DiffUpdate: 157 result = DiffUpdate 158 } 159 } 160 161 return result 162 } 163 164 // Empty returns true if the diff has no changes within this module. 165 func (d *ModuleDiff) Empty() bool { 166 if len(d.Resources) == 0 { 167 return true 168 } 169 170 for _, rd := range d.Resources { 171 if !rd.Empty() { 172 return false 173 } 174 } 175 176 return true 177 } 178 179 // Instances returns the instance diffs for the id given. This can return 180 // multiple instance diffs if there are counts within the resource. 181 func (d *ModuleDiff) Instances(id string) []*InstanceDiff { 182 var result []*InstanceDiff 183 for k, diff := range d.Resources { 184 if k == id || strings.HasPrefix(k, id+".") { 185 if !diff.Empty() { 186 result = append(result, diff) 187 } 188 } 189 } 190 191 return result 192 } 193 194 // IsRoot says whether or not this module diff is for the root module. 195 func (d *ModuleDiff) IsRoot() bool { 196 return reflect.DeepEqual(d.Path, rootModulePath) 197 } 198 199 // String outputs the diff in a long but command-line friendly output 200 // format that users can read to quickly inspect a diff. 201 func (d *ModuleDiff) String() string { 202 var buf bytes.Buffer 203 204 if d.Destroy { 205 buf.WriteString("DESTROY MODULE\n") 206 } 207 208 names := make([]string, 0, len(d.Resources)) 209 for name, _ := range d.Resources { 210 names = append(names, name) 211 } 212 sort.Strings(names) 213 214 for _, name := range names { 215 rdiff := d.Resources[name] 216 217 crud := "UPDATE" 218 switch { 219 case rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted): 220 crud = "DESTROY/CREATE" 221 case rdiff.Destroy: 222 crud = "DESTROY" 223 case rdiff.RequiresNew(): 224 crud = "CREATE" 225 } 226 227 buf.WriteString(fmt.Sprintf( 228 "%s: %s\n", 229 crud, 230 name)) 231 232 keyLen := 0 233 keys := make([]string, 0, len(rdiff.Attributes)) 234 for key, _ := range rdiff.Attributes { 235 if key == "id" { 236 continue 237 } 238 239 keys = append(keys, key) 240 if len(key) > keyLen { 241 keyLen = len(key) 242 } 243 } 244 sort.Strings(keys) 245 246 for _, attrK := range keys { 247 attrDiff := rdiff.Attributes[attrK] 248 249 v := attrDiff.New 250 u := attrDiff.Old 251 if attrDiff.NewComputed { 252 v = "<computed>" 253 } 254 255 if attrDiff.Sensitive { 256 u = "<sensitive>" 257 v = "<sensitive>" 258 } 259 260 updateMsg := "" 261 if attrDiff.RequiresNew { 262 updateMsg = " (forces new resource)" 263 } else if attrDiff.Sensitive { 264 updateMsg = " (attribute changed)" 265 } 266 267 buf.WriteString(fmt.Sprintf( 268 " %s:%s %#v => %#v%s\n", 269 attrK, 270 strings.Repeat(" ", keyLen-len(attrK)), 271 u, 272 v, 273 updateMsg)) 274 } 275 } 276 277 return buf.String() 278 } 279 280 // InstanceDiff is the diff of a resource from some state to another. 281 type InstanceDiff struct { 282 Attributes map[string]*ResourceAttrDiff 283 Destroy bool 284 DestroyTainted bool 285 } 286 287 // ResourceAttrDiff is the diff of a single attribute of a resource. 288 type ResourceAttrDiff struct { 289 Old string // Old Value 290 New string // New Value 291 NewComputed bool // True if new value is computed (unknown currently) 292 NewRemoved bool // True if this attribute is being removed 293 NewExtra interface{} // Extra information for the provider 294 RequiresNew bool // True if change requires new resource 295 Sensitive bool // True if the data should not be displayed in UI output 296 Type DiffAttrType 297 } 298 299 // Empty returns true if the diff for this attr is neutral 300 func (d *ResourceAttrDiff) Empty() bool { 301 return d.Old == d.New && !d.NewComputed && !d.NewRemoved 302 } 303 304 func (d *ResourceAttrDiff) GoString() string { 305 return fmt.Sprintf("*%#v", *d) 306 } 307 308 // DiffAttrType is an enum type that says whether a resource attribute 309 // diff is an input attribute (comes from the configuration) or an 310 // output attribute (comes as a result of applying the configuration). An 311 // example input would be "ami" for AWS and an example output would be 312 // "private_ip". 313 type DiffAttrType byte 314 315 const ( 316 DiffAttrUnknown DiffAttrType = iota 317 DiffAttrInput 318 DiffAttrOutput 319 ) 320 321 func (d *InstanceDiff) init() { 322 if d.Attributes == nil { 323 d.Attributes = make(map[string]*ResourceAttrDiff) 324 } 325 } 326 327 // ChangeType returns the DiffChangeType represented by the diff 328 // for this single instance. 329 func (d *InstanceDiff) ChangeType() DiffChangeType { 330 if d.Empty() { 331 return DiffNone 332 } 333 334 if d.RequiresNew() && (d.Destroy || d.DestroyTainted) { 335 return DiffDestroyCreate 336 } 337 338 if d.Destroy { 339 return DiffDestroy 340 } 341 342 if d.RequiresNew() { 343 return DiffCreate 344 } 345 346 return DiffUpdate 347 } 348 349 // Empty returns true if this diff encapsulates no changes. 350 func (d *InstanceDiff) Empty() bool { 351 if d == nil { 352 return true 353 } 354 355 return !d.Destroy && len(d.Attributes) == 0 356 } 357 358 func (d *InstanceDiff) GoString() string { 359 return fmt.Sprintf("*%#v", *d) 360 } 361 362 // RequiresNew returns true if the diff requires the creation of a new 363 // resource (implying the destruction of the old). 364 func (d *InstanceDiff) RequiresNew() bool { 365 if d == nil { 366 return false 367 } 368 369 if d.DestroyTainted { 370 return true 371 } 372 373 for _, rd := range d.Attributes { 374 if rd != nil && rd.RequiresNew { 375 return true 376 } 377 } 378 379 return false 380 } 381 382 // Same checks whether or not two InstanceDiff's are the "same". When 383 // we say "same", it is not necessarily exactly equal. Instead, it is 384 // just checking that the same attributes are changing, a destroy 385 // isn't suddenly happening, etc. 386 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { 387 if d == nil && d2 == nil { 388 return true, "" 389 } else if d == nil || d2 == nil { 390 return false, "both nil" 391 } 392 393 if d.Destroy != d2.Destroy { 394 return false, fmt.Sprintf( 395 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.Destroy) 396 } 397 if d.RequiresNew() != d2.RequiresNew() { 398 return false, fmt.Sprintf( 399 "diff RequiresNew; old: %t, new: %t", d.RequiresNew(), d2.RequiresNew()) 400 } 401 402 // Go through the old diff and make sure the new diff has all the 403 // same attributes. To start, build up the check map to be all the keys. 404 checkOld := make(map[string]struct{}) 405 checkNew := make(map[string]struct{}) 406 for k, _ := range d.Attributes { 407 checkOld[k] = struct{}{} 408 } 409 for k, _ := range d2.Attributes { 410 checkNew[k] = struct{}{} 411 } 412 413 // Make an ordered list so we are sure the approximated hashes are left 414 // to process at the end of the loop 415 keys := make([]string, 0, len(d.Attributes)) 416 for k, _ := range d.Attributes { 417 keys = append(keys, k) 418 } 419 sort.StringSlice(keys).Sort() 420 421 for _, k := range keys { 422 diffOld := d.Attributes[k] 423 424 if _, ok := checkOld[k]; !ok { 425 // We're not checking this key for whatever reason (see where 426 // check is modified). 427 continue 428 } 429 430 // Remove this key since we'll never hit it again 431 delete(checkOld, k) 432 delete(checkNew, k) 433 434 _, ok := d2.Attributes[k] 435 if !ok { 436 // If there's no new attribute, and the old diff expected the attribute 437 // to be removed, that's just fine. 438 if diffOld.NewRemoved { 439 continue 440 } 441 442 // No exact match, but maybe this is a set containing computed 443 // values. So check if there is an approximate hash in the key 444 // and if so, try to match the key. 445 if strings.Contains(k, "~") { 446 parts := strings.Split(k, ".") 447 parts2 := append([]string(nil), parts...) 448 449 re := regexp.MustCompile(`^~\d+$`) 450 for i, part := range parts { 451 if re.MatchString(part) { 452 // we're going to consider this the base of a 453 // computed hash, and remove all longer matching fields 454 ok = true 455 456 parts2[i] = `\d+` 457 parts2 = parts2[:i+1] 458 break 459 } 460 } 461 462 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) 463 if err != nil { 464 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) 465 } 466 467 for k2, _ := range checkNew { 468 if re.MatchString(k2) { 469 delete(checkNew, k2) 470 } 471 } 472 } 473 474 // This is a little tricky, but when a diff contains a computed 475 // list, set, or map that can only be interpolated after the apply 476 // command has created the dependent resources, it could turn out 477 // that the result is actually the same as the existing state which 478 // would remove the key from the diff. 479 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 480 ok = true 481 } 482 483 // Similarly, in a RequiresNew scenario, a list that shows up in the plan 484 // diff can disappear from the apply diff, which is calculated from an 485 // empty state. 486 if d.RequiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 487 ok = true 488 } 489 490 if !ok { 491 return false, fmt.Sprintf("attribute mismatch: %s", k) 492 } 493 } 494 495 // search for the suffix of the base of a [computed] map, list or set. 496 multiVal := regexp.MustCompile(`\.(#|~#|%)$`) 497 match := multiVal.FindStringSubmatch(k) 498 499 if diffOld.NewComputed && len(match) == 2 { 500 matchLen := len(match[1]) 501 502 // This is a computed list, set, or map, so remove any keys with 503 // this prefix from the check list. 504 kprefix := k[:len(k)-matchLen] 505 for k2, _ := range checkOld { 506 if strings.HasPrefix(k2, kprefix) { 507 delete(checkOld, k2) 508 } 509 } 510 for k2, _ := range checkNew { 511 if strings.HasPrefix(k2, kprefix) { 512 delete(checkNew, k2) 513 } 514 } 515 } 516 517 // TODO: check for the same value if not computed 518 } 519 520 // Check for leftover attributes 521 if len(checkNew) > 0 { 522 extras := make([]string, 0, len(checkNew)) 523 for attr, _ := range checkNew { 524 extras = append(extras, attr) 525 } 526 return false, 527 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) 528 } 529 530 return true, "" 531 }