github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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 // EphemeralDisk diff 227 diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual) 228 if diskDiff != nil { 229 diff.Objects = append(diff.Objects, diskDiff) 230 } 231 232 // Tasks diff 233 tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual) 234 if err != nil { 235 return nil, err 236 } 237 diff.Tasks = tasks 238 239 return diff, nil 240 } 241 242 func (tg *TaskGroupDiff) GoString() string { 243 out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type) 244 245 if len(tg.Updates) != 0 { 246 out += "Updates {\n" 247 for update, count := range tg.Updates { 248 out += fmt.Sprintf("%d %s\n", count, update) 249 } 250 out += "}\n" 251 } 252 253 for _, f := range tg.Fields { 254 out += fmt.Sprintf("%#v\n", f) 255 } 256 257 for _, o := range tg.Objects { 258 out += fmt.Sprintf("%#v\n", o) 259 } 260 261 for _, t := range tg.Tasks { 262 out += fmt.Sprintf("%#v\n", t) 263 } 264 265 return out 266 } 267 268 // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled, 269 // objects' fields will be stored even if no diff occurred as long as one field 270 // changed. 271 func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) { 272 oldMap := make(map[string]*TaskGroup, len(old)) 273 newMap := make(map[string]*TaskGroup, len(new)) 274 for _, o := range old { 275 oldMap[o.Name] = o 276 } 277 for _, n := range new { 278 newMap[n.Name] = n 279 } 280 281 var diffs []*TaskGroupDiff 282 for name, oldGroup := range oldMap { 283 // Diff the same, deleted and edited 284 diff, err := oldGroup.Diff(newMap[name], contextual) 285 if err != nil { 286 return nil, err 287 } 288 diffs = append(diffs, diff) 289 } 290 291 for name, newGroup := range newMap { 292 // Diff the added 293 if old, ok := oldMap[name]; !ok { 294 diff, err := old.Diff(newGroup, contextual) 295 if err != nil { 296 return nil, err 297 } 298 diffs = append(diffs, diff) 299 } 300 } 301 302 sort.Sort(TaskGroupDiffs(diffs)) 303 return diffs, nil 304 } 305 306 // For sorting TaskGroupDiffs 307 type TaskGroupDiffs []*TaskGroupDiff 308 309 func (tg TaskGroupDiffs) Len() int { return len(tg) } 310 func (tg TaskGroupDiffs) Swap(i, j int) { tg[i], tg[j] = tg[j], tg[i] } 311 func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name } 312 313 // TaskDiff contains the diff of two Tasks 314 type TaskDiff struct { 315 Type DiffType 316 Name string 317 Fields []*FieldDiff 318 Objects []*ObjectDiff 319 Annotations []string 320 } 321 322 // Diff returns a diff of two tasks. If contextual diff is enabled, objects 323 // within the task will contain field information even if unchanged. 324 func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { 325 diff := &TaskDiff{Type: DiffTypeNone} 326 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 327 filter := []string{"Name", "Config"} 328 329 if t == nil && other == nil { 330 return diff, nil 331 } else if t == nil { 332 t = &Task{} 333 diff.Type = DiffTypeAdded 334 diff.Name = other.Name 335 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 336 } else if other == nil { 337 other = &Task{} 338 diff.Type = DiffTypeDeleted 339 diff.Name = t.Name 340 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 341 } else { 342 if !reflect.DeepEqual(t, other) { 343 diff.Type = DiffTypeEdited 344 } 345 if t.Name != other.Name { 346 return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name) 347 } 348 diff.Name = other.Name 349 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 350 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 351 } 352 353 // Diff the primitive fields. 354 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 355 356 // Constraints diff 357 conDiff := primitiveObjectSetDiff( 358 interfaceSlice(t.Constraints), 359 interfaceSlice(other.Constraints), 360 []string{"str"}, 361 "Constraint", 362 contextual) 363 if conDiff != nil { 364 diff.Objects = append(diff.Objects, conDiff...) 365 } 366 367 // Config diff 368 if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil { 369 diff.Objects = append(diff.Objects, cDiff) 370 } 371 372 // Resources diff 373 if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil { 374 diff.Objects = append(diff.Objects, rDiff) 375 } 376 377 // LogConfig diff 378 lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual) 379 if lDiff != nil { 380 diff.Objects = append(diff.Objects, lDiff) 381 } 382 383 // Artifacts diff 384 diffs := primitiveObjectSetDiff( 385 interfaceSlice(t.Artifacts), 386 interfaceSlice(other.Artifacts), 387 nil, 388 "Artifact", 389 contextual) 390 if diffs != nil { 391 diff.Objects = append(diff.Objects, diffs...) 392 } 393 394 // Services diff 395 if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil { 396 diff.Objects = append(diff.Objects, sDiffs...) 397 } 398 399 return diff, nil 400 } 401 402 func (t *TaskDiff) GoString() string { 403 var out string 404 if len(t.Annotations) == 0 { 405 out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type) 406 } else { 407 out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ",")) 408 } 409 410 for _, f := range t.Fields { 411 out += fmt.Sprintf("%#v\n", f) 412 } 413 414 for _, o := range t.Objects { 415 out += fmt.Sprintf("%#v\n", o) 416 } 417 418 return out 419 } 420 421 // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged 422 // fields within objects nested in the tasks will be returned. 423 func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) { 424 oldMap := make(map[string]*Task, len(old)) 425 newMap := make(map[string]*Task, len(new)) 426 for _, o := range old { 427 oldMap[o.Name] = o 428 } 429 for _, n := range new { 430 newMap[n.Name] = n 431 } 432 433 var diffs []*TaskDiff 434 for name, oldGroup := range oldMap { 435 // Diff the same, deleted and edited 436 diff, err := oldGroup.Diff(newMap[name], contextual) 437 if err != nil { 438 return nil, err 439 } 440 diffs = append(diffs, diff) 441 } 442 443 for name, newGroup := range newMap { 444 // Diff the added 445 if old, ok := oldMap[name]; !ok { 446 diff, err := old.Diff(newGroup, contextual) 447 if err != nil { 448 return nil, err 449 } 450 diffs = append(diffs, diff) 451 } 452 } 453 454 sort.Sort(TaskDiffs(diffs)) 455 return diffs, nil 456 } 457 458 // For sorting TaskDiffs 459 type TaskDiffs []*TaskDiff 460 461 func (t TaskDiffs) Len() int { return len(t) } 462 func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 463 func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name } 464 465 // serviceDiff returns the diff of two service objects. If contextual diff is 466 // enabled, all fields will be returned, even if no diff occurred. 467 func serviceDiff(old, new *Service, contextual bool) *ObjectDiff { 468 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 469 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 470 471 if reflect.DeepEqual(old, new) { 472 return nil 473 } else if old == nil { 474 old = &Service{} 475 diff.Type = DiffTypeAdded 476 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 477 } else if new == nil { 478 new = &Service{} 479 diff.Type = DiffTypeDeleted 480 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 481 } else { 482 diff.Type = DiffTypeEdited 483 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 484 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 485 } 486 487 // Diff the primitive fields. 488 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 489 490 // Checks diffs 491 if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil { 492 diff.Objects = append(diff.Objects, cDiffs...) 493 } 494 495 return diff 496 } 497 498 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 499 // fields within objects nested in the tasks will be returned. 500 func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff { 501 oldMap := make(map[string]*Service, len(old)) 502 newMap := make(map[string]*Service, len(new)) 503 for _, o := range old { 504 oldMap[o.Name] = o 505 } 506 for _, n := range new { 507 newMap[n.Name] = n 508 } 509 510 var diffs []*ObjectDiff 511 for name, oldService := range oldMap { 512 // Diff the same, deleted and edited 513 if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil { 514 diffs = append(diffs, diff) 515 } 516 } 517 518 for name, newService := range newMap { 519 // Diff the added 520 if old, ok := oldMap[name]; !ok { 521 if diff := serviceDiff(old, newService, contextual); diff != nil { 522 diffs = append(diffs, diff) 523 } 524 } 525 } 526 527 sort.Sort(ObjectDiffs(diffs)) 528 return diffs 529 } 530 531 // serviceCheckDiff returns the diff of two service check objects. If contextual 532 // diff is enabled, all fields will be returned, even if no diff occurred. 533 func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff { 534 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"} 535 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 536 537 if reflect.DeepEqual(old, new) { 538 return nil 539 } else if old == nil { 540 old = &ServiceCheck{} 541 diff.Type = DiffTypeAdded 542 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 543 } else if new == nil { 544 new = &ServiceCheck{} 545 diff.Type = DiffTypeDeleted 546 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 547 } else { 548 diff.Type = DiffTypeEdited 549 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 550 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 551 } 552 553 // Diff the primitive fields. 554 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 555 return diff 556 } 557 558 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 559 // enabled, unchanged fields within objects nested in the tasks will be 560 // returned. 561 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 562 oldMap := make(map[string]*ServiceCheck, len(old)) 563 newMap := make(map[string]*ServiceCheck, len(new)) 564 for _, o := range old { 565 oldMap[o.Name] = o 566 } 567 for _, n := range new { 568 newMap[n.Name] = n 569 } 570 571 var diffs []*ObjectDiff 572 for name, oldService := range oldMap { 573 // Diff the same, deleted and edited 574 if diff := serviceCheckDiff(oldService, newMap[name], contextual); diff != nil { 575 diffs = append(diffs, diff) 576 } 577 } 578 579 for name, newService := range newMap { 580 // Diff the added 581 if old, ok := oldMap[name]; !ok { 582 if diff := serviceCheckDiff(old, newService, contextual); diff != nil { 583 diffs = append(diffs, diff) 584 } 585 } 586 } 587 588 sort.Sort(ObjectDiffs(diffs)) 589 return diffs 590 } 591 592 // Diff returns a diff of two resource objects. If contextual diff is enabled, 593 // non-changed fields will still be returned. 594 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 595 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 596 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 597 598 if reflect.DeepEqual(r, other) { 599 return nil 600 } else if r == nil { 601 r = &Resources{} 602 diff.Type = DiffTypeAdded 603 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 604 } else if other == nil { 605 other = &Resources{} 606 diff.Type = DiffTypeDeleted 607 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 608 } else { 609 diff.Type = DiffTypeEdited 610 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 611 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 612 } 613 614 // Diff the primitive fields. 615 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 616 617 // Network Resources diff 618 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 619 diff.Objects = append(diff.Objects, nDiffs...) 620 } 621 622 return diff 623 } 624 625 // Diff returns a diff of two network resources. If contextual diff is enabled, 626 // non-changed fields will still be returned. 627 func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 628 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 629 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 630 filter := []string{"Device", "CIDR", "IP"} 631 632 if reflect.DeepEqual(r, other) { 633 return nil 634 } else if r == nil { 635 r = &NetworkResource{} 636 diff.Type = DiffTypeAdded 637 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 638 } else if other == nil { 639 other = &NetworkResource{} 640 diff.Type = DiffTypeDeleted 641 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 642 } else { 643 diff.Type = DiffTypeEdited 644 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 645 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 646 } 647 648 // Diff the primitive fields. 649 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 650 651 // Port diffs 652 resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual) 653 dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual) 654 if resPorts != nil { 655 diff.Objects = append(diff.Objects, resPorts...) 656 } 657 if dynPorts != nil { 658 diff.Objects = append(diff.Objects, dynPorts...) 659 } 660 661 return diff 662 } 663 664 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 665 // non-changed fields will still be returned. 666 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 667 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 668 objMap := make(map[string]*NetworkResource, len(objects)) 669 for _, obj := range objects { 670 hash, err := hashstructure.Hash(obj, nil) 671 if err != nil { 672 panic(err) 673 } 674 objMap[fmt.Sprintf("%d", hash)] = obj 675 } 676 677 return objMap 678 } 679 680 oldSet := makeSet(old) 681 newSet := makeSet(new) 682 683 var diffs []*ObjectDiff 684 for k, oldV := range oldSet { 685 if newV, ok := newSet[k]; !ok { 686 if diff := oldV.Diff(newV, contextual); diff != nil { 687 diffs = append(diffs, diff) 688 } 689 } 690 } 691 for k, newV := range newSet { 692 if oldV, ok := oldSet[k]; !ok { 693 if diff := oldV.Diff(newV, contextual); diff != nil { 694 diffs = append(diffs, diff) 695 } 696 } 697 } 698 699 sort.Sort(ObjectDiffs(diffs)) 700 return diffs 701 702 } 703 704 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 705 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 706 // non-changed fields will still be returned. 707 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 708 makeSet := func(ports []Port) map[string]Port { 709 portMap := make(map[string]Port, len(ports)) 710 for _, port := range ports { 711 portMap[port.Label] = port 712 } 713 714 return portMap 715 } 716 717 oldPorts := makeSet(old) 718 newPorts := makeSet(new) 719 720 var filter []string 721 name := "Static Port" 722 if dynamic { 723 filter = []string{"Value"} 724 name = "Dynamic Port" 725 } 726 727 var diffs []*ObjectDiff 728 for portLabel, oldPort := range oldPorts { 729 // Diff the same, deleted and edited 730 if newPort, ok := newPorts[portLabel]; ok { 731 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 732 if diff != nil { 733 diffs = append(diffs, diff) 734 } 735 } else { 736 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 737 if diff != nil { 738 diffs = append(diffs, diff) 739 } 740 } 741 } 742 for label, newPort := range newPorts { 743 // Diff the added 744 if _, ok := oldPorts[label]; !ok { 745 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 746 if diff != nil { 747 diffs = append(diffs, diff) 748 } 749 } 750 } 751 752 sort.Sort(ObjectDiffs(diffs)) 753 return diffs 754 755 } 756 757 // configDiff returns the diff of two Task Config objects. If contextual diff is 758 // enabled, all fields will be returned, even if no diff occurred. 759 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 760 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 761 if reflect.DeepEqual(old, new) { 762 return nil 763 } else if len(old) == 0 { 764 diff.Type = DiffTypeAdded 765 } else if len(new) == 0 { 766 diff.Type = DiffTypeDeleted 767 } else { 768 diff.Type = DiffTypeEdited 769 } 770 771 // Diff the primitive fields. 772 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 773 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 774 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 775 return diff 776 } 777 778 // ObjectDiff contains the diff of two generic objects. 779 type ObjectDiff struct { 780 Type DiffType 781 Name string 782 Fields []*FieldDiff 783 Objects []*ObjectDiff 784 } 785 786 func (o *ObjectDiff) GoString() string { 787 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 788 for _, f := range o.Fields { 789 out += fmt.Sprintf("%#v\n", f) 790 } 791 for _, o := range o.Objects { 792 out += fmt.Sprintf("%#v\n", o) 793 } 794 out += "}" 795 return out 796 } 797 798 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 799 if reflect.DeepEqual(o, other) { 800 return false 801 } else if other == nil { 802 return false 803 } else if o == nil { 804 return true 805 } 806 807 if o.Name != other.Name { 808 return o.Name < other.Name 809 } 810 811 if o.Type != other.Type { 812 return o.Type.Less(other.Type) 813 } 814 815 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 816 return lO < lOther 817 } 818 819 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 820 return lO < lOther 821 } 822 823 // Check each field 824 sort.Sort(FieldDiffs(o.Fields)) 825 sort.Sort(FieldDiffs(other.Fields)) 826 827 for i, oV := range o.Fields { 828 if oV.Less(other.Fields[i]) { 829 return true 830 } 831 } 832 833 // Check each object 834 sort.Sort(ObjectDiffs(o.Objects)) 835 sort.Sort(ObjectDiffs(other.Objects)) 836 for i, oV := range o.Objects { 837 if oV.Less(other.Objects[i]) { 838 return true 839 } 840 } 841 842 return false 843 } 844 845 // For sorting ObjectDiffs 846 type ObjectDiffs []*ObjectDiff 847 848 func (o ObjectDiffs) Len() int { return len(o) } 849 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 850 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 851 852 type FieldDiff struct { 853 Type DiffType 854 Name string 855 Old, New string 856 Annotations []string 857 } 858 859 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 860 // returns nil. If contextual diff is enabled, even non-changed fields will be 861 // returned. 862 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 863 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 864 if old == new { 865 if !contextual { 866 return nil 867 } 868 diff.Old, diff.New = old, new 869 return diff 870 } 871 872 if old == "" { 873 diff.Type = DiffTypeAdded 874 diff.New = new 875 } else if new == "" { 876 diff.Type = DiffTypeDeleted 877 diff.Old = old 878 } else { 879 diff.Type = DiffTypeEdited 880 diff.Old = old 881 diff.New = new 882 } 883 return diff 884 } 885 886 func (f *FieldDiff) GoString() string { 887 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 888 if len(f.Annotations) != 0 { 889 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 890 } 891 892 return out 893 } 894 895 func (f *FieldDiff) Less(other *FieldDiff) bool { 896 if reflect.DeepEqual(f, other) { 897 return false 898 } else if other == nil { 899 return false 900 } else if f == nil { 901 return true 902 } 903 904 if f.Name != other.Name { 905 return f.Name < other.Name 906 } else if f.Old != other.Old { 907 return f.Old < other.Old 908 } 909 910 return f.New < other.New 911 } 912 913 // For sorting FieldDiffs 914 type FieldDiffs []*FieldDiff 915 916 func (f FieldDiffs) Len() int { return len(f) } 917 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 918 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 919 920 // fieldDiffs takes a map of field names to their values and returns a set of 921 // field diffs. If contextual diff is enabled, even non-changed fields will be 922 // returned. 923 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 924 var diffs []*FieldDiff 925 visited := make(map[string]struct{}) 926 for k, oldV := range old { 927 visited[k] = struct{}{} 928 newV := new[k] 929 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 930 diffs = append(diffs, diff) 931 } 932 } 933 934 for k, newV := range new { 935 if _, ok := visited[k]; !ok { 936 if diff := fieldDiff("", newV, k, contextual); diff != nil { 937 diffs = append(diffs, diff) 938 } 939 } 940 } 941 942 sort.Sort(FieldDiffs(diffs)) 943 return diffs 944 } 945 946 // stringSetDiff diffs two sets of strings with the given name. 947 func stringSetDiff(old, new []string, name string) *ObjectDiff { 948 oldMap := make(map[string]struct{}, len(old)) 949 newMap := make(map[string]struct{}, len(new)) 950 for _, o := range old { 951 oldMap[o] = struct{}{} 952 } 953 for _, n := range new { 954 newMap[n] = struct{}{} 955 } 956 if reflect.DeepEqual(oldMap, newMap) { 957 return nil 958 } 959 960 diff := &ObjectDiff{Name: name} 961 var added, removed bool 962 for k := range oldMap { 963 if _, ok := newMap[k]; !ok { 964 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, false)) 965 removed = true 966 } 967 } 968 969 for k := range newMap { 970 if _, ok := oldMap[k]; !ok { 971 diff.Fields = append(diff.Fields, fieldDiff("", k, name, false)) 972 added = true 973 } 974 } 975 976 sort.Sort(FieldDiffs(diff.Fields)) 977 978 // Determine the type 979 if added && removed { 980 diff.Type = DiffTypeEdited 981 } else if added { 982 diff.Type = DiffTypeAdded 983 } else { 984 diff.Type = DiffTypeDeleted 985 } 986 987 return diff 988 } 989 990 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 991 // The filter field can be used to exclude fields from the diff. The name is the 992 // name of the objects. If contextual is set, non-changed fields will also be 993 // stored in the object diff. 994 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 995 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 996 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 997 delete(oldPrimitiveFlat, "") 998 delete(newPrimitiveFlat, "") 999 1000 diff := &ObjectDiff{Name: name} 1001 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1002 1003 var added, deleted, edited bool 1004 for _, f := range diff.Fields { 1005 switch f.Type { 1006 case DiffTypeEdited: 1007 edited = true 1008 break 1009 case DiffTypeDeleted: 1010 deleted = true 1011 case DiffTypeAdded: 1012 added = true 1013 } 1014 } 1015 1016 if edited || added && deleted { 1017 diff.Type = DiffTypeEdited 1018 } else if added { 1019 diff.Type = DiffTypeAdded 1020 } else if deleted { 1021 diff.Type = DiffTypeDeleted 1022 } else { 1023 return nil 1024 } 1025 1026 return diff 1027 } 1028 1029 // primitiveObjectSetDiff does a set difference of the old and new sets. The 1030 // filter parameter can be used to filter a set of primitive fields in the 1031 // passed structs. The name corresponds to the name of the passed objects. If 1032 // contextual diff is enabled, objects' primtive fields will be returned even if 1033 // no diff exists. 1034 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 1035 makeSet := func(objects []interface{}) map[string]interface{} { 1036 objMap := make(map[string]interface{}, len(objects)) 1037 for _, obj := range objects { 1038 hash, err := hashstructure.Hash(obj, nil) 1039 if err != nil { 1040 panic(err) 1041 } 1042 objMap[fmt.Sprintf("%d", hash)] = obj 1043 } 1044 1045 return objMap 1046 } 1047 1048 oldSet := makeSet(old) 1049 newSet := makeSet(new) 1050 1051 var diffs []*ObjectDiff 1052 for k, v := range oldSet { 1053 // Deleted 1054 if _, ok := newSet[k]; !ok { 1055 diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual)) 1056 } 1057 } 1058 for k, v := range newSet { 1059 // Added 1060 if _, ok := oldSet[k]; !ok { 1061 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 1062 } 1063 } 1064 1065 sort.Sort(ObjectDiffs(diffs)) 1066 return diffs 1067 } 1068 1069 // interfaceSlice is a helper method that takes a slice of typed elements and 1070 // returns a slice of interface. This method will panic if given a non-slice 1071 // input. 1072 func interfaceSlice(slice interface{}) []interface{} { 1073 s := reflect.ValueOf(slice) 1074 if s.Kind() != reflect.Slice { 1075 panic("InterfaceSlice() given a non-slice type") 1076 } 1077 1078 ret := make([]interface{}, s.Len()) 1079 1080 for i := 0; i < s.Len(); i++ { 1081 ret[i] = s.Index(i).Interface() 1082 } 1083 1084 return ret 1085 }