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