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