github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/nomad/structs/diff.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strings" 8 9 "github.com/hashicorp/nomad/helper/flatmap" 10 "github.com/mitchellh/hashstructure" 11 ) 12 13 // DiffType denotes the type of a diff object. 14 type DiffType string 15 16 var ( 17 DiffTypeNone DiffType = "None" 18 DiffTypeAdded DiffType = "Added" 19 DiffTypeDeleted DiffType = "Deleted" 20 DiffTypeEdited DiffType = "Edited" 21 ) 22 23 func (d DiffType) Less(other DiffType) bool { 24 // Edited > Added > Deleted > None 25 // But we do a reverse sort 26 if d == other { 27 return false 28 } 29 30 if d == DiffTypeEdited { 31 return true 32 } else if other == DiffTypeEdited { 33 return false 34 } else if d == DiffTypeAdded { 35 return true 36 } else if other == DiffTypeAdded { 37 return false 38 } else if d == DiffTypeDeleted { 39 return true 40 } else if other == DiffTypeDeleted { 41 return false 42 } 43 44 return true 45 } 46 47 // JobDiff contains the diff of two jobs. 48 type JobDiff struct { 49 Type DiffType 50 ID string 51 Fields []*FieldDiff 52 Objects []*ObjectDiff 53 TaskGroups []*TaskGroupDiff 54 } 55 56 // Diff returns a diff of two jobs and a potential error if the Jobs are not 57 // diffable. If contextual diff is enabled, objects within the job will contain 58 // field information even if unchanged. 59 func (j *Job) Diff(other *Job, contextual bool) (*JobDiff, error) { 60 diff := &JobDiff{Type: DiffTypeNone} 61 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 62 filter := []string{"ID", "Status", "StatusDescription", "CreateIndex", "ModifyIndex", "JobModifyIndex"} 63 64 // Have to treat this special since it is a struct literal, not a pointer 65 var jUpdate, otherUpdate *UpdateStrategy 66 67 if j == nil && other == nil { 68 return diff, nil 69 } else if j == nil { 70 j = &Job{} 71 otherUpdate = &other.Update 72 diff.Type = DiffTypeAdded 73 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 74 diff.ID = other.ID 75 } else if other == nil { 76 other = &Job{} 77 jUpdate = &j.Update 78 diff.Type = DiffTypeDeleted 79 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 80 diff.ID = j.ID 81 } else { 82 if j.ID != other.ID { 83 return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID) 84 } 85 86 jUpdate = &j.Update 87 otherUpdate = &other.Update 88 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 89 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 90 diff.ID = other.ID 91 } 92 93 // Diff the primitive fields. 94 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 95 96 // Datacenters diff 97 if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters"); setDiff != nil { 98 diff.Objects = append(diff.Objects, setDiff) 99 } 100 101 // Constraints diff 102 conDiff := primitiveObjectSetDiff( 103 interfaceSlice(j.Constraints), 104 interfaceSlice(other.Constraints), 105 []string{"str"}, 106 "Constraint", 107 contextual) 108 if conDiff != nil { 109 diff.Objects = append(diff.Objects, conDiff...) 110 } 111 112 // Task groups diff 113 tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual) 114 if err != nil { 115 return nil, err 116 } 117 diff.TaskGroups = tgs 118 119 // Update diff 120 if uDiff := primitiveObjectDiff(jUpdate, otherUpdate, nil, "Update", contextual); uDiff != nil { 121 diff.Objects = append(diff.Objects, uDiff) 122 } 123 124 // Periodic diff 125 if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil { 126 diff.Objects = append(diff.Objects, pDiff) 127 } 128 129 // If the job is not a delete or add, determine if there are edits. 130 if diff.Type == DiffTypeNone { 131 tgEdit := false 132 for _, tg := range diff.TaskGroups { 133 if tg.Type != DiffTypeNone { 134 tgEdit = true 135 break 136 } 137 } 138 if tgEdit || len(diff.Fields)+len(diff.Objects) != 0 { 139 diff.Type = DiffTypeEdited 140 } 141 } 142 143 return diff, nil 144 } 145 146 func (j *JobDiff) GoString() string { 147 out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type) 148 149 for _, f := range j.Fields { 150 out += fmt.Sprintf("%#v\n", f) 151 } 152 153 for _, o := range j.Objects { 154 out += fmt.Sprintf("%#v\n", o) 155 } 156 157 for _, tg := range j.TaskGroups { 158 out += fmt.Sprintf("%#v\n", tg) 159 } 160 161 return out 162 } 163 164 // TaskGroupDiff contains the diff of two task groups. 165 type TaskGroupDiff struct { 166 Type DiffType 167 Name string 168 Fields []*FieldDiff 169 Objects []*ObjectDiff 170 Tasks []*TaskDiff 171 Updates map[string]uint64 172 } 173 174 // Diff returns a diff of two task groups. If contextual diff is enabled, 175 // objects' fields will be stored even if no diff occurred as long as one field 176 // changed. 177 func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) { 178 diff := &TaskGroupDiff{Type: DiffTypeNone} 179 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 180 filter := []string{"Name"} 181 182 if tg == nil && other == nil { 183 return diff, nil 184 } else if tg == nil { 185 tg = &TaskGroup{} 186 diff.Type = DiffTypeAdded 187 diff.Name = other.Name 188 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 189 } else if other == nil { 190 other = &TaskGroup{} 191 diff.Type = DiffTypeDeleted 192 diff.Name = tg.Name 193 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 194 } else { 195 if !reflect.DeepEqual(tg, other) { 196 diff.Type = DiffTypeEdited 197 } 198 if tg.Name != other.Name { 199 return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name) 200 } 201 diff.Name = other.Name 202 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 203 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 204 } 205 206 // Diff the primitive fields. 207 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 208 209 // Constraints diff 210 conDiff := primitiveObjectSetDiff( 211 interfaceSlice(tg.Constraints), 212 interfaceSlice(other.Constraints), 213 []string{"str"}, 214 "Constraint", 215 contextual) 216 if conDiff != nil { 217 diff.Objects = append(diff.Objects, conDiff...) 218 } 219 220 // Restart policy diff 221 rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual) 222 if rDiff != nil { 223 diff.Objects = append(diff.Objects, rDiff) 224 } 225 226 // Tasks diff 227 tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual) 228 if err != nil { 229 return nil, err 230 } 231 diff.Tasks = tasks 232 233 return diff, nil 234 } 235 236 func (tg *TaskGroupDiff) GoString() string { 237 out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type) 238 239 if len(tg.Updates) != 0 { 240 out += "Updates {\n" 241 for update, count := range tg.Updates { 242 out += fmt.Sprintf("%d %s\n", count, update) 243 } 244 out += "}\n" 245 } 246 247 for _, f := range tg.Fields { 248 out += fmt.Sprintf("%#v\n", f) 249 } 250 251 for _, o := range tg.Objects { 252 out += fmt.Sprintf("%#v\n", o) 253 } 254 255 for _, t := range tg.Tasks { 256 out += fmt.Sprintf("%#v\n", t) 257 } 258 259 return out 260 } 261 262 // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled, 263 // objects' fields will be stored even if no diff occurred as long as one field 264 // changed. 265 func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) { 266 oldMap := make(map[string]*TaskGroup, len(old)) 267 newMap := make(map[string]*TaskGroup, len(new)) 268 for _, o := range old { 269 oldMap[o.Name] = o 270 } 271 for _, n := range new { 272 newMap[n.Name] = n 273 } 274 275 var diffs []*TaskGroupDiff 276 for name, oldGroup := range oldMap { 277 // Diff the same, deleted and edited 278 diff, err := oldGroup.Diff(newMap[name], contextual) 279 if err != nil { 280 return nil, err 281 } 282 diffs = append(diffs, diff) 283 } 284 285 for name, newGroup := range newMap { 286 // Diff the added 287 if old, ok := oldMap[name]; !ok { 288 diff, err := old.Diff(newGroup, contextual) 289 if err != nil { 290 return nil, err 291 } 292 diffs = append(diffs, diff) 293 } 294 } 295 296 sort.Sort(TaskGroupDiffs(diffs)) 297 return diffs, nil 298 } 299 300 // For sorting TaskGroupDiffs 301 type TaskGroupDiffs []*TaskGroupDiff 302 303 func (tg TaskGroupDiffs) Len() int { return len(tg) } 304 func (tg TaskGroupDiffs) Swap(i, j int) { tg[i], tg[j] = tg[j], tg[i] } 305 func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name } 306 307 // TaskDiff contains the diff of two Tasks 308 type TaskDiff struct { 309 Type DiffType 310 Name string 311 Fields []*FieldDiff 312 Objects []*ObjectDiff 313 Annotations []string 314 } 315 316 // Diff returns a diff of two tasks. If contextual diff is enabled, objects 317 // within the task will contain field information even if unchanged. 318 func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { 319 diff := &TaskDiff{Type: DiffTypeNone} 320 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 321 filter := []string{"Name", "Config"} 322 323 if t == nil && other == nil { 324 return diff, nil 325 } else if t == nil { 326 t = &Task{} 327 diff.Type = DiffTypeAdded 328 diff.Name = other.Name 329 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 330 } else if other == nil { 331 other = &Task{} 332 diff.Type = DiffTypeDeleted 333 diff.Name = t.Name 334 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 335 } else { 336 if !reflect.DeepEqual(t, other) { 337 diff.Type = DiffTypeEdited 338 } 339 if t.Name != other.Name { 340 return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name) 341 } 342 diff.Name = other.Name 343 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 344 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 345 } 346 347 // Diff the primitive fields. 348 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 349 350 // Constraints diff 351 conDiff := primitiveObjectSetDiff( 352 interfaceSlice(t.Constraints), 353 interfaceSlice(other.Constraints), 354 []string{"str"}, 355 "Constraint", 356 contextual) 357 if conDiff != nil { 358 diff.Objects = append(diff.Objects, conDiff...) 359 } 360 361 // Config diff 362 if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil { 363 diff.Objects = append(diff.Objects, cDiff) 364 } 365 366 // Resources diff 367 if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil { 368 diff.Objects = append(diff.Objects, rDiff) 369 } 370 371 // LogConfig diff 372 lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual) 373 if lDiff != nil { 374 diff.Objects = append(diff.Objects, lDiff) 375 } 376 377 // Artifacts diff 378 diffs := primitiveObjectSetDiff( 379 interfaceSlice(t.Artifacts), 380 interfaceSlice(other.Artifacts), 381 nil, 382 "Artifact", 383 contextual) 384 if diffs != nil { 385 diff.Objects = append(diff.Objects, diffs...) 386 } 387 388 // Services diff 389 if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil { 390 diff.Objects = append(diff.Objects, sDiffs...) 391 } 392 393 return diff, nil 394 } 395 396 func (t *TaskDiff) GoString() string { 397 var out string 398 if len(t.Annotations) == 0 { 399 out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type) 400 } else { 401 out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ",")) 402 } 403 404 for _, f := range t.Fields { 405 out += fmt.Sprintf("%#v\n", f) 406 } 407 408 for _, o := range t.Objects { 409 out += fmt.Sprintf("%#v\n", o) 410 } 411 412 return out 413 } 414 415 // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged 416 // fields within objects nested in the tasks will be returned. 417 func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) { 418 oldMap := make(map[string]*Task, len(old)) 419 newMap := make(map[string]*Task, len(new)) 420 for _, o := range old { 421 oldMap[o.Name] = o 422 } 423 for _, n := range new { 424 newMap[n.Name] = n 425 } 426 427 var diffs []*TaskDiff 428 for name, oldGroup := range oldMap { 429 // Diff the same, deleted and edited 430 diff, err := oldGroup.Diff(newMap[name], contextual) 431 if err != nil { 432 return nil, err 433 } 434 diffs = append(diffs, diff) 435 } 436 437 for name, newGroup := range newMap { 438 // Diff the added 439 if old, ok := oldMap[name]; !ok { 440 diff, err := old.Diff(newGroup, contextual) 441 if err != nil { 442 return nil, err 443 } 444 diffs = append(diffs, diff) 445 } 446 } 447 448 sort.Sort(TaskDiffs(diffs)) 449 return diffs, nil 450 } 451 452 // For sorting TaskDiffs 453 type TaskDiffs []*TaskDiff 454 455 func (t TaskDiffs) Len() int { return len(t) } 456 func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 457 func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name } 458 459 // serviceDiff returns the diff of two service objects. If contextual diff is 460 // enabled, all fields will be returned, even if no diff occurred. 461 func serviceDiff(old, new *Service, contextual bool) *ObjectDiff { 462 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 463 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 464 465 if reflect.DeepEqual(old, new) { 466 return nil 467 } else if old == nil { 468 old = &Service{} 469 diff.Type = DiffTypeAdded 470 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 471 } else if new == nil { 472 new = &Service{} 473 diff.Type = DiffTypeDeleted 474 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 475 } else { 476 diff.Type = DiffTypeEdited 477 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 478 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 479 } 480 481 // Diff the primitive fields. 482 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 483 484 // Checks diffs 485 if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil { 486 diff.Objects = append(diff.Objects, cDiffs...) 487 } 488 489 return diff 490 } 491 492 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 493 // fields within objects nested in the tasks will be returned. 494 func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff { 495 oldMap := make(map[string]*Service, len(old)) 496 newMap := make(map[string]*Service, len(new)) 497 for _, o := range old { 498 oldMap[o.Name] = o 499 } 500 for _, n := range new { 501 newMap[n.Name] = n 502 } 503 504 var diffs []*ObjectDiff 505 for name, oldService := range oldMap { 506 // Diff the same, deleted and edited 507 if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil { 508 diffs = append(diffs, diff) 509 } 510 } 511 512 for name, newService := range newMap { 513 // Diff the added 514 if old, ok := oldMap[name]; !ok { 515 if diff := serviceDiff(old, newService, contextual); diff != nil { 516 diffs = append(diffs, diff) 517 } 518 } 519 } 520 521 sort.Sort(ObjectDiffs(diffs)) 522 return diffs 523 } 524 525 // serviceCheckDiff returns the diff of two service check objects. If contextual 526 // diff is enabled, all fields will be returned, even if no diff occurred. 527 func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff { 528 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"} 529 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 530 531 if reflect.DeepEqual(old, new) { 532 return nil 533 } else if old == nil { 534 old = &ServiceCheck{} 535 diff.Type = DiffTypeAdded 536 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 537 } else if new == nil { 538 new = &ServiceCheck{} 539 diff.Type = DiffTypeDeleted 540 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 541 } else { 542 diff.Type = DiffTypeEdited 543 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 544 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 545 } 546 547 // Diff the primitive fields. 548 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 549 return diff 550 } 551 552 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 553 // enabled, unchanged fields within objects nested in the tasks will be 554 // returned. 555 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 556 oldMap := make(map[string]*ServiceCheck, len(old)) 557 newMap := make(map[string]*ServiceCheck, len(new)) 558 for _, o := range old { 559 oldMap[o.Name] = o 560 } 561 for _, n := range new { 562 newMap[n.Name] = n 563 } 564 565 var diffs []*ObjectDiff 566 for name, oldService := range oldMap { 567 // Diff the same, deleted and edited 568 if diff := serviceCheckDiff(oldService, newMap[name], contextual); diff != nil { 569 diffs = append(diffs, diff) 570 } 571 } 572 573 for name, newService := range newMap { 574 // Diff the added 575 if old, ok := oldMap[name]; !ok { 576 if diff := serviceCheckDiff(old, newService, contextual); diff != nil { 577 diffs = append(diffs, diff) 578 } 579 } 580 } 581 582 sort.Sort(ObjectDiffs(diffs)) 583 return diffs 584 } 585 586 // Diff returns a diff of two resource objects. If contextual diff is enabled, 587 // non-changed fields will still be returned. 588 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 589 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 590 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 591 592 if reflect.DeepEqual(r, other) { 593 return nil 594 } else if r == nil { 595 r = &Resources{} 596 diff.Type = DiffTypeAdded 597 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 598 } else if other == nil { 599 other = &Resources{} 600 diff.Type = DiffTypeDeleted 601 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 602 } else { 603 diff.Type = DiffTypeEdited 604 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 605 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 606 } 607 608 // Diff the primitive fields. 609 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 610 611 // Network Resources diff 612 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 613 diff.Objects = append(diff.Objects, nDiffs...) 614 } 615 616 return diff 617 } 618 619 // Diff returns a diff of two network resources. If contextual diff is enabled, 620 // non-changed fields will still be returned. 621 func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 622 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 623 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 624 filter := []string{"Device", "CIDR", "IP"} 625 626 if reflect.DeepEqual(r, other) { 627 return nil 628 } else if r == nil { 629 r = &NetworkResource{} 630 diff.Type = DiffTypeAdded 631 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 632 } else if other == nil { 633 other = &NetworkResource{} 634 diff.Type = DiffTypeDeleted 635 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 636 } else { 637 diff.Type = DiffTypeEdited 638 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 639 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 640 } 641 642 // Diff the primitive fields. 643 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 644 645 // Port diffs 646 resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual) 647 dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual) 648 if resPorts != nil { 649 diff.Objects = append(diff.Objects, resPorts...) 650 } 651 if dynPorts != nil { 652 diff.Objects = append(diff.Objects, dynPorts...) 653 } 654 655 return diff 656 } 657 658 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 659 // non-changed fields will still be returned. 660 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 661 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 662 objMap := make(map[string]*NetworkResource, len(objects)) 663 for _, obj := range objects { 664 hash, err := hashstructure.Hash(obj, nil) 665 if err != nil { 666 panic(err) 667 } 668 objMap[fmt.Sprintf("%d", hash)] = obj 669 } 670 671 return objMap 672 } 673 674 oldSet := makeSet(old) 675 newSet := makeSet(new) 676 677 var diffs []*ObjectDiff 678 for k, oldV := range oldSet { 679 if newV, ok := newSet[k]; !ok { 680 if diff := oldV.Diff(newV, contextual); diff != nil { 681 diffs = append(diffs, diff) 682 } 683 } 684 } 685 for k, newV := range newSet { 686 if oldV, ok := oldSet[k]; !ok { 687 if diff := oldV.Diff(newV, contextual); diff != nil { 688 diffs = append(diffs, diff) 689 } 690 } 691 } 692 693 sort.Sort(ObjectDiffs(diffs)) 694 return diffs 695 696 } 697 698 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 699 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 700 // non-changed fields will still be returned. 701 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 702 makeSet := func(ports []Port) map[string]Port { 703 portMap := make(map[string]Port, len(ports)) 704 for _, port := range ports { 705 portMap[port.Label] = port 706 } 707 708 return portMap 709 } 710 711 oldPorts := makeSet(old) 712 newPorts := makeSet(new) 713 714 var filter []string 715 name := "Static Port" 716 if dynamic { 717 filter = []string{"Value"} 718 name = "Dynamic Port" 719 } 720 721 var diffs []*ObjectDiff 722 for portLabel, oldPort := range oldPorts { 723 // Diff the same, deleted and edited 724 if newPort, ok := newPorts[portLabel]; ok { 725 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 726 if diff != nil { 727 diffs = append(diffs, diff) 728 } 729 } else { 730 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 731 if diff != nil { 732 diffs = append(diffs, diff) 733 } 734 } 735 } 736 for label, newPort := range newPorts { 737 // Diff the added 738 if _, ok := oldPorts[label]; !ok { 739 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 740 if diff != nil { 741 diffs = append(diffs, diff) 742 } 743 } 744 } 745 746 sort.Sort(ObjectDiffs(diffs)) 747 return diffs 748 749 } 750 751 // configDiff returns the diff of two Task Config objects. If contextual diff is 752 // enabled, all fields will be returned, even if no diff occurred. 753 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 754 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 755 if reflect.DeepEqual(old, new) { 756 return nil 757 } else if len(old) == 0 { 758 diff.Type = DiffTypeAdded 759 } else if len(new) == 0 { 760 diff.Type = DiffTypeDeleted 761 } else { 762 diff.Type = DiffTypeEdited 763 } 764 765 // Diff the primitive fields. 766 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 767 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 768 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 769 return diff 770 } 771 772 // ObjectDiff contains the diff of two generic objects. 773 type ObjectDiff struct { 774 Type DiffType 775 Name string 776 Fields []*FieldDiff 777 Objects []*ObjectDiff 778 } 779 780 func (o *ObjectDiff) GoString() string { 781 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 782 for _, f := range o.Fields { 783 out += fmt.Sprintf("%#v\n", f) 784 } 785 for _, o := range o.Objects { 786 out += fmt.Sprintf("%#v\n", o) 787 } 788 out += "}" 789 return out 790 } 791 792 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 793 if reflect.DeepEqual(o, other) { 794 return false 795 } else if other == nil { 796 return false 797 } else if o == nil { 798 return true 799 } 800 801 if o.Name != other.Name { 802 return o.Name < other.Name 803 } 804 805 if o.Type != other.Type { 806 return o.Type.Less(other.Type) 807 } 808 809 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 810 return lO < lOther 811 } 812 813 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 814 return lO < lOther 815 } 816 817 // Check each field 818 sort.Sort(FieldDiffs(o.Fields)) 819 sort.Sort(FieldDiffs(other.Fields)) 820 821 for i, oV := range o.Fields { 822 if oV.Less(other.Fields[i]) { 823 return true 824 } 825 } 826 827 // Check each object 828 sort.Sort(ObjectDiffs(o.Objects)) 829 sort.Sort(ObjectDiffs(other.Objects)) 830 for i, oV := range o.Objects { 831 if oV.Less(other.Objects[i]) { 832 return true 833 } 834 } 835 836 return false 837 } 838 839 // For sorting ObjectDiffs 840 type ObjectDiffs []*ObjectDiff 841 842 func (o ObjectDiffs) Len() int { return len(o) } 843 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 844 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 845 846 type FieldDiff struct { 847 Type DiffType 848 Name string 849 Old, New string 850 Annotations []string 851 } 852 853 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 854 // returns nil. If contextual diff is enabled, even non-changed fields will be 855 // returned. 856 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 857 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 858 if old == new { 859 if !contextual { 860 return nil 861 } 862 diff.Old, diff.New = old, new 863 return diff 864 } 865 866 if old == "" { 867 diff.Type = DiffTypeAdded 868 diff.New = new 869 } else if new == "" { 870 diff.Type = DiffTypeDeleted 871 diff.Old = old 872 } else { 873 diff.Type = DiffTypeEdited 874 diff.Old = old 875 diff.New = new 876 } 877 return diff 878 } 879 880 func (f *FieldDiff) GoString() string { 881 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 882 if len(f.Annotations) != 0 { 883 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 884 } 885 886 return out 887 } 888 889 func (f *FieldDiff) Less(other *FieldDiff) bool { 890 if reflect.DeepEqual(f, other) { 891 return false 892 } else if other == nil { 893 return false 894 } else if f == nil { 895 return true 896 } 897 898 if f.Name != other.Name { 899 return f.Name < other.Name 900 } else if f.Old != other.Old { 901 return f.Old < other.Old 902 } 903 904 return f.New < other.New 905 } 906 907 // For sorting FieldDiffs 908 type FieldDiffs []*FieldDiff 909 910 func (f FieldDiffs) Len() int { return len(f) } 911 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 912 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 913 914 // fieldDiffs takes a map of field names to their values and returns a set of 915 // field diffs. If contextual diff is enabled, even non-changed fields will be 916 // returned. 917 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 918 var diffs []*FieldDiff 919 visited := make(map[string]struct{}) 920 for k, oldV := range old { 921 visited[k] = struct{}{} 922 newV := new[k] 923 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 924 diffs = append(diffs, diff) 925 } 926 } 927 928 for k, newV := range new { 929 if _, ok := visited[k]; !ok { 930 if diff := fieldDiff("", newV, k, contextual); diff != nil { 931 diffs = append(diffs, diff) 932 } 933 } 934 } 935 936 sort.Sort(FieldDiffs(diffs)) 937 return diffs 938 } 939 940 // stringSetDiff diffs two sets of strings with the given name. 941 func stringSetDiff(old, new []string, name string) *ObjectDiff { 942 oldMap := make(map[string]struct{}, len(old)) 943 newMap := make(map[string]struct{}, len(new)) 944 for _, o := range old { 945 oldMap[o] = struct{}{} 946 } 947 for _, n := range new { 948 newMap[n] = struct{}{} 949 } 950 if reflect.DeepEqual(oldMap, newMap) { 951 return nil 952 } 953 954 diff := &ObjectDiff{Name: name} 955 var added, removed bool 956 for k := range oldMap { 957 if _, ok := newMap[k]; !ok { 958 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, false)) 959 removed = true 960 } 961 } 962 963 for k := range newMap { 964 if _, ok := oldMap[k]; !ok { 965 diff.Fields = append(diff.Fields, fieldDiff("", k, name, false)) 966 added = true 967 } 968 } 969 970 sort.Sort(FieldDiffs(diff.Fields)) 971 972 // Determine the type 973 if added && removed { 974 diff.Type = DiffTypeEdited 975 } else if added { 976 diff.Type = DiffTypeAdded 977 } else { 978 diff.Type = DiffTypeDeleted 979 } 980 981 return diff 982 } 983 984 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 985 // The filter field can be used to exclude fields from the diff. The name is the 986 // name of the objects. If contextual is set, non-changed fields will also be 987 // stored in the object diff. 988 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 989 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 990 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 991 delete(oldPrimitiveFlat, "") 992 delete(newPrimitiveFlat, "") 993 994 diff := &ObjectDiff{Name: name} 995 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 996 997 var added, deleted, edited bool 998 for _, f := range diff.Fields { 999 switch f.Type { 1000 case DiffTypeEdited: 1001 edited = true 1002 break 1003 case DiffTypeDeleted: 1004 deleted = true 1005 case DiffTypeAdded: 1006 added = true 1007 } 1008 } 1009 1010 if edited || added && deleted { 1011 diff.Type = DiffTypeEdited 1012 } else if added { 1013 diff.Type = DiffTypeAdded 1014 } else if deleted { 1015 diff.Type = DiffTypeDeleted 1016 } else { 1017 return nil 1018 } 1019 1020 return diff 1021 } 1022 1023 // primitiveObjectSetDiff does a set difference of the old and new sets. The 1024 // filter parameter can be used to filter a set of primitive fields in the 1025 // passed structs. The name corresponds to the name of the passed objects. If 1026 // contextual diff is enabled, objects' primtive fields will be returned even if 1027 // no diff exists. 1028 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 1029 makeSet := func(objects []interface{}) map[string]interface{} { 1030 objMap := make(map[string]interface{}, len(objects)) 1031 for _, obj := range objects { 1032 hash, err := hashstructure.Hash(obj, nil) 1033 if err != nil { 1034 panic(err) 1035 } 1036 objMap[fmt.Sprintf("%d", hash)] = obj 1037 } 1038 1039 return objMap 1040 } 1041 1042 oldSet := makeSet(old) 1043 newSet := makeSet(new) 1044 1045 var diffs []*ObjectDiff 1046 for k, v := range oldSet { 1047 // Deleted 1048 if _, ok := newSet[k]; !ok { 1049 diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual)) 1050 } 1051 } 1052 for k, v := range newSet { 1053 // Added 1054 if _, ok := oldSet[k]; !ok { 1055 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 1056 } 1057 } 1058 1059 sort.Sort(ObjectDiffs(diffs)) 1060 return diffs 1061 } 1062 1063 // interfaceSlice is a helper method that takes a slice of typed elements and 1064 // returns a slice of interface. This method will panic if given a non-slice 1065 // input. 1066 func interfaceSlice(slice interface{}) []interface{} { 1067 s := reflect.ValueOf(slice) 1068 if s.Kind() != reflect.Slice { 1069 panic("InterfaceSlice() given a non-slice type") 1070 } 1071 1072 ret := make([]interface{}, s.Len()) 1073 1074 for i := 0; i < s.Len(); i++ { 1075 ret[i] = s.Index(i).Interface() 1076 } 1077 1078 return ret 1079 }