github.com/ThomasObenaus/nomad@v0.11.1/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 // See agent.ApiJobToStructJob Update is a default for TaskGroups 61 diff := &JobDiff{Type: DiffTypeNone} 62 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 63 filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex", 64 "ModifyIndex", "JobModifyIndex", "Update", "SubmitTime"} 65 66 if j == nil && other == nil { 67 return diff, nil 68 } else if j == nil { 69 j = &Job{} 70 diff.Type = DiffTypeAdded 71 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 72 diff.ID = other.ID 73 } else if other == nil { 74 other = &Job{} 75 diff.Type = DiffTypeDeleted 76 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 77 diff.ID = j.ID 78 } else { 79 if j.ID != other.ID { 80 return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID) 81 } 82 83 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 84 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 85 diff.ID = other.ID 86 } 87 88 // Diff the primitive fields. 89 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 90 91 // Datacenters diff 92 if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone { 93 diff.Objects = append(diff.Objects, setDiff) 94 } 95 96 // Constraints diff 97 conDiff := primitiveObjectSetDiff( 98 interfaceSlice(j.Constraints), 99 interfaceSlice(other.Constraints), 100 []string{"str"}, 101 "Constraint", 102 contextual) 103 if conDiff != nil { 104 diff.Objects = append(diff.Objects, conDiff...) 105 } 106 107 // Affinities diff 108 affinitiesDiff := primitiveObjectSetDiff( 109 interfaceSlice(j.Affinities), 110 interfaceSlice(other.Affinities), 111 []string{"str"}, 112 "Affinity", 113 contextual) 114 if affinitiesDiff != nil { 115 diff.Objects = append(diff.Objects, affinitiesDiff...) 116 } 117 118 // Task groups diff 119 tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual) 120 if err != nil { 121 return nil, err 122 } 123 diff.TaskGroups = tgs 124 125 // Periodic diff 126 if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil { 127 diff.Objects = append(diff.Objects, pDiff) 128 } 129 130 // ParameterizedJob diff 131 if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil { 132 diff.Objects = append(diff.Objects, cDiff) 133 } 134 135 // Check to see if there is a diff. We don't use reflect because we are 136 // filtering quite a few fields that will change on each diff. 137 if diff.Type == DiffTypeNone { 138 for _, fd := range diff.Fields { 139 if fd.Type != DiffTypeNone { 140 diff.Type = DiffTypeEdited 141 break 142 } 143 } 144 } 145 146 if diff.Type == DiffTypeNone { 147 for _, od := range diff.Objects { 148 if od.Type != DiffTypeNone { 149 diff.Type = DiffTypeEdited 150 break 151 } 152 } 153 } 154 155 if diff.Type == DiffTypeNone { 156 for _, tg := range diff.TaskGroups { 157 if tg.Type != DiffTypeNone { 158 diff.Type = DiffTypeEdited 159 break 160 } 161 } 162 } 163 164 return diff, nil 165 } 166 167 func (j *JobDiff) GoString() string { 168 out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type) 169 170 for _, f := range j.Fields { 171 out += fmt.Sprintf("%#v\n", f) 172 } 173 174 for _, o := range j.Objects { 175 out += fmt.Sprintf("%#v\n", o) 176 } 177 178 for _, tg := range j.TaskGroups { 179 out += fmt.Sprintf("%#v\n", tg) 180 } 181 182 return out 183 } 184 185 // TaskGroupDiff contains the diff of two task groups. 186 type TaskGroupDiff struct { 187 Type DiffType 188 Name string 189 Fields []*FieldDiff 190 Objects []*ObjectDiff 191 Tasks []*TaskDiff 192 Updates map[string]uint64 193 } 194 195 // Diff returns a diff of two task groups. If contextual diff is enabled, 196 // objects' fields will be stored even if no diff occurred as long as one field 197 // changed. 198 func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) { 199 diff := &TaskGroupDiff{Type: DiffTypeNone} 200 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 201 filter := []string{"Name"} 202 203 if tg == nil && other == nil { 204 return diff, nil 205 } else if tg == nil { 206 tg = &TaskGroup{} 207 diff.Type = DiffTypeAdded 208 diff.Name = other.Name 209 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 210 } else if other == nil { 211 other = &TaskGroup{} 212 diff.Type = DiffTypeDeleted 213 diff.Name = tg.Name 214 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 215 } else { 216 if !reflect.DeepEqual(tg, other) { 217 diff.Type = DiffTypeEdited 218 } 219 if tg.Name != other.Name { 220 return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name) 221 } 222 diff.Name = other.Name 223 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 224 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 225 } 226 227 // ShutdownDelay diff 228 if oldPrimitiveFlat != nil && newPrimitiveFlat != nil { 229 if tg.ShutdownDelay == nil { 230 oldPrimitiveFlat["ShutdownDelay"] = "" 231 } else { 232 oldPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *tg.ShutdownDelay) 233 } 234 if other.ShutdownDelay == nil { 235 newPrimitiveFlat["ShutdownDelay"] = "" 236 } else { 237 newPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *other.ShutdownDelay) 238 } 239 } 240 241 // Diff the primitive fields. 242 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 243 244 // Constraints diff 245 conDiff := primitiveObjectSetDiff( 246 interfaceSlice(tg.Constraints), 247 interfaceSlice(other.Constraints), 248 []string{"str"}, 249 "Constraint", 250 contextual) 251 if conDiff != nil { 252 diff.Objects = append(diff.Objects, conDiff...) 253 } 254 255 // Affinities diff 256 affinitiesDiff := primitiveObjectSetDiff( 257 interfaceSlice(tg.Affinities), 258 interfaceSlice(other.Affinities), 259 []string{"str"}, 260 "Affinity", 261 contextual) 262 if affinitiesDiff != nil { 263 diff.Objects = append(diff.Objects, affinitiesDiff...) 264 } 265 266 // Restart policy diff 267 rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual) 268 if rDiff != nil { 269 diff.Objects = append(diff.Objects, rDiff) 270 } 271 272 // Reschedule policy diff 273 reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual) 274 if reschedDiff != nil { 275 diff.Objects = append(diff.Objects, reschedDiff) 276 } 277 278 // EphemeralDisk diff 279 diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual) 280 if diskDiff != nil { 281 diff.Objects = append(diff.Objects, diskDiff) 282 } 283 284 // Update diff 285 // COMPAT: Remove "Stagger" in 0.7.0. 286 if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil { 287 diff.Objects = append(diff.Objects, uDiff) 288 } 289 290 // Network Resources diff 291 if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil { 292 diff.Objects = append(diff.Objects, nDiffs...) 293 } 294 295 // Services diff 296 if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil { 297 diff.Objects = append(diff.Objects, sDiffs...) 298 } 299 300 // Tasks diff 301 tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual) 302 if err != nil { 303 return nil, err 304 } 305 diff.Tasks = tasks 306 307 return diff, nil 308 } 309 310 func (tg *TaskGroupDiff) GoString() string { 311 out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type) 312 313 if len(tg.Updates) != 0 { 314 out += "Updates {\n" 315 for update, count := range tg.Updates { 316 out += fmt.Sprintf("%d %s\n", count, update) 317 } 318 out += "}\n" 319 } 320 321 for _, f := range tg.Fields { 322 out += fmt.Sprintf("%#v\n", f) 323 } 324 325 for _, o := range tg.Objects { 326 out += fmt.Sprintf("%#v\n", o) 327 } 328 329 for _, t := range tg.Tasks { 330 out += fmt.Sprintf("%#v\n", t) 331 } 332 333 return out 334 } 335 336 // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled, 337 // objects' fields will be stored even if no diff occurred as long as one field 338 // changed. 339 func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) { 340 oldMap := make(map[string]*TaskGroup, len(old)) 341 newMap := make(map[string]*TaskGroup, len(new)) 342 for _, o := range old { 343 oldMap[o.Name] = o 344 } 345 for _, n := range new { 346 newMap[n.Name] = n 347 } 348 349 var diffs []*TaskGroupDiff 350 for name, oldGroup := range oldMap { 351 // Diff the same, deleted and edited 352 diff, err := oldGroup.Diff(newMap[name], contextual) 353 if err != nil { 354 return nil, err 355 } 356 diffs = append(diffs, diff) 357 } 358 359 for name, newGroup := range newMap { 360 // Diff the added 361 if old, ok := oldMap[name]; !ok { 362 diff, err := old.Diff(newGroup, contextual) 363 if err != nil { 364 return nil, err 365 } 366 diffs = append(diffs, diff) 367 } 368 } 369 370 sort.Sort(TaskGroupDiffs(diffs)) 371 return diffs, nil 372 } 373 374 // For sorting TaskGroupDiffs 375 type TaskGroupDiffs []*TaskGroupDiff 376 377 func (tg TaskGroupDiffs) Len() int { return len(tg) } 378 func (tg TaskGroupDiffs) Swap(i, j int) { tg[i], tg[j] = tg[j], tg[i] } 379 func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name } 380 381 // TaskDiff contains the diff of two Tasks 382 type TaskDiff struct { 383 Type DiffType 384 Name string 385 Fields []*FieldDiff 386 Objects []*ObjectDiff 387 Annotations []string 388 } 389 390 // Diff returns a diff of two tasks. If contextual diff is enabled, objects 391 // within the task will contain field information even if unchanged. 392 func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { 393 diff := &TaskDiff{Type: DiffTypeNone} 394 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 395 filter := []string{"Name", "Config"} 396 397 if t == nil && other == nil { 398 return diff, nil 399 } else if t == nil { 400 t = &Task{} 401 diff.Type = DiffTypeAdded 402 diff.Name = other.Name 403 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 404 } else if other == nil { 405 other = &Task{} 406 diff.Type = DiffTypeDeleted 407 diff.Name = t.Name 408 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 409 } else { 410 if !reflect.DeepEqual(t, other) { 411 diff.Type = DiffTypeEdited 412 } 413 if t.Name != other.Name { 414 return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name) 415 } 416 diff.Name = other.Name 417 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 418 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 419 } 420 421 // Diff the primitive fields. 422 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 423 424 // Constraints diff 425 conDiff := primitiveObjectSetDiff( 426 interfaceSlice(t.Constraints), 427 interfaceSlice(other.Constraints), 428 []string{"str"}, 429 "Constraint", 430 contextual) 431 if conDiff != nil { 432 diff.Objects = append(diff.Objects, conDiff...) 433 } 434 435 // Affinities diff 436 affinitiesDiff := primitiveObjectSetDiff( 437 interfaceSlice(t.Affinities), 438 interfaceSlice(other.Affinities), 439 []string{"str"}, 440 "Affinity", 441 contextual) 442 if affinitiesDiff != nil { 443 diff.Objects = append(diff.Objects, affinitiesDiff...) 444 } 445 446 // Config diff 447 if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil { 448 diff.Objects = append(diff.Objects, cDiff) 449 } 450 451 // Resources diff 452 if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil { 453 diff.Objects = append(diff.Objects, rDiff) 454 } 455 456 // LogConfig diff 457 lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual) 458 if lDiff != nil { 459 diff.Objects = append(diff.Objects, lDiff) 460 } 461 462 // Dispatch payload diff 463 dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual) 464 if dDiff != nil { 465 diff.Objects = append(diff.Objects, dDiff) 466 } 467 468 // Artifacts diff 469 diffs := primitiveObjectSetDiff( 470 interfaceSlice(t.Artifacts), 471 interfaceSlice(other.Artifacts), 472 nil, 473 "Artifact", 474 contextual) 475 if diffs != nil { 476 diff.Objects = append(diff.Objects, diffs...) 477 } 478 479 // Services diff 480 if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil { 481 diff.Objects = append(diff.Objects, sDiffs...) 482 } 483 484 // Vault diff 485 vDiff := vaultDiff(t.Vault, other.Vault, contextual) 486 if vDiff != nil { 487 diff.Objects = append(diff.Objects, vDiff) 488 } 489 490 // Template diff 491 tmplDiffs := primitiveObjectSetDiff( 492 interfaceSlice(t.Templates), 493 interfaceSlice(other.Templates), 494 nil, 495 "Template", 496 contextual) 497 if tmplDiffs != nil { 498 diff.Objects = append(diff.Objects, tmplDiffs...) 499 } 500 501 return diff, nil 502 } 503 504 func (t *TaskDiff) GoString() string { 505 var out string 506 if len(t.Annotations) == 0 { 507 out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type) 508 } else { 509 out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ",")) 510 } 511 512 for _, f := range t.Fields { 513 out += fmt.Sprintf("%#v\n", f) 514 } 515 516 for _, o := range t.Objects { 517 out += fmt.Sprintf("%#v\n", o) 518 } 519 520 return out 521 } 522 523 // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged 524 // fields within objects nested in the tasks will be returned. 525 func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) { 526 oldMap := make(map[string]*Task, len(old)) 527 newMap := make(map[string]*Task, len(new)) 528 for _, o := range old { 529 oldMap[o.Name] = o 530 } 531 for _, n := range new { 532 newMap[n.Name] = n 533 } 534 535 var diffs []*TaskDiff 536 for name, oldGroup := range oldMap { 537 // Diff the same, deleted and edited 538 diff, err := oldGroup.Diff(newMap[name], contextual) 539 if err != nil { 540 return nil, err 541 } 542 diffs = append(diffs, diff) 543 } 544 545 for name, newGroup := range newMap { 546 // Diff the added 547 if old, ok := oldMap[name]; !ok { 548 diff, err := old.Diff(newGroup, contextual) 549 if err != nil { 550 return nil, err 551 } 552 diffs = append(diffs, diff) 553 } 554 } 555 556 sort.Sort(TaskDiffs(diffs)) 557 return diffs, nil 558 } 559 560 // For sorting TaskDiffs 561 type TaskDiffs []*TaskDiff 562 563 func (t TaskDiffs) Len() int { return len(t) } 564 func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 565 func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name } 566 567 // serviceDiff returns the diff of two service objects. If contextual diff is 568 // enabled, all fields will be returned, even if no diff occurred. 569 func serviceDiff(old, new *Service, contextual bool) *ObjectDiff { 570 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 571 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 572 573 if reflect.DeepEqual(old, new) { 574 return nil 575 } else if old == nil { 576 old = &Service{} 577 diff.Type = DiffTypeAdded 578 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 579 } else if new == nil { 580 new = &Service{} 581 diff.Type = DiffTypeDeleted 582 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 583 } else { 584 diff.Type = DiffTypeEdited 585 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 586 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 587 } 588 589 // Diff the primitive fields. 590 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 591 592 if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil { 593 diff.Objects = append(diff.Objects, setDiff) 594 } 595 596 // Tag diffs 597 if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil { 598 diff.Objects = append(diff.Objects, setDiff) 599 } 600 601 // Checks diffs 602 if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil { 603 diff.Objects = append(diff.Objects, cDiffs...) 604 } 605 606 // Consul Connect diffs 607 if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil { 608 diff.Objects = append(diff.Objects, conDiffs) 609 } 610 611 return diff 612 } 613 614 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 615 // fields within objects nested in the tasks will be returned. 616 func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff { 617 oldMap := make(map[string]*Service, len(old)) 618 newMap := make(map[string]*Service, len(new)) 619 for _, o := range old { 620 oldMap[o.Name] = o 621 } 622 for _, n := range new { 623 newMap[n.Name] = n 624 } 625 626 var diffs []*ObjectDiff 627 for name, oldService := range oldMap { 628 // Diff the same, deleted and edited 629 if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil { 630 diffs = append(diffs, diff) 631 } 632 } 633 634 for name, newService := range newMap { 635 // Diff the added 636 if old, ok := oldMap[name]; !ok { 637 if diff := serviceDiff(old, newService, contextual); diff != nil { 638 diffs = append(diffs, diff) 639 } 640 } 641 } 642 643 sort.Sort(ObjectDiffs(diffs)) 644 return diffs 645 } 646 647 // serviceCheckDiff returns the diff of two service check objects. If contextual 648 // diff is enabled, all fields will be returned, even if no diff occurred. 649 func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff { 650 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"} 651 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 652 653 if reflect.DeepEqual(old, new) { 654 return nil 655 } else if old == nil { 656 old = &ServiceCheck{} 657 diff.Type = DiffTypeAdded 658 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 659 } else if new == nil { 660 new = &ServiceCheck{} 661 diff.Type = DiffTypeDeleted 662 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 663 } else { 664 diff.Type = DiffTypeEdited 665 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 666 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 667 } 668 669 // Diff the primitive fields. 670 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 671 672 // Diff Header 673 if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil { 674 diff.Objects = append(diff.Objects, headerDiff) 675 } 676 677 // Diff check_restart 678 if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil { 679 diff.Objects = append(diff.Objects, crDiff) 680 } 681 682 return diff 683 } 684 685 // checkHeaderDiff returns the diff of two service check header objects. If 686 // contextual diff is enabled, all fields will be returned, even if no diff 687 // occurred. 688 func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff { 689 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"} 690 var oldFlat, newFlat map[string]string 691 692 if reflect.DeepEqual(old, new) { 693 return nil 694 } else if len(old) == 0 { 695 diff.Type = DiffTypeAdded 696 newFlat = flatmap.Flatten(new, nil, false) 697 } else if len(new) == 0 { 698 diff.Type = DiffTypeDeleted 699 oldFlat = flatmap.Flatten(old, nil, false) 700 } else { 701 diff.Type = DiffTypeEdited 702 oldFlat = flatmap.Flatten(old, nil, false) 703 newFlat = flatmap.Flatten(new, nil, false) 704 } 705 706 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 707 return diff 708 } 709 710 // checkRestartDiff returns the diff of two service check check_restart 711 // objects. If contextual diff is enabled, all fields will be returned, even if 712 // no diff occurred. 713 func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff { 714 diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"} 715 var oldFlat, newFlat map[string]string 716 717 if reflect.DeepEqual(old, new) { 718 return nil 719 } else if old == nil { 720 diff.Type = DiffTypeAdded 721 newFlat = flatmap.Flatten(new, nil, true) 722 diff.Type = DiffTypeAdded 723 } else if new == nil { 724 diff.Type = DiffTypeDeleted 725 oldFlat = flatmap.Flatten(old, nil, true) 726 } else { 727 diff.Type = DiffTypeEdited 728 oldFlat = flatmap.Flatten(old, nil, true) 729 newFlat = flatmap.Flatten(new, nil, true) 730 } 731 732 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 733 return diff 734 } 735 736 // connectDiffs returns the diff of two Consul connect objects. If contextual 737 // diff is enabled, all fields will be returned, even if no diff occurred. 738 func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff { 739 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"} 740 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 741 742 if reflect.DeepEqual(old, new) { 743 return nil 744 } else if old == nil { 745 old = &ConsulConnect{} 746 diff.Type = DiffTypeAdded 747 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 748 } else if new == nil { 749 new = &ConsulConnect{} 750 diff.Type = DiffTypeDeleted 751 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 752 } else { 753 diff.Type = DiffTypeEdited 754 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 755 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 756 } 757 758 // Diff the primitive fields. 759 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 760 761 sidecarSvcDiff := connectSidecarServiceDiff( 762 old.SidecarService, new.SidecarService, contextual) 763 if sidecarSvcDiff != nil { 764 diff.Objects = append(diff.Objects, sidecarSvcDiff) 765 } 766 767 sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual) 768 if sidecarTaskDiff != nil { 769 diff.Objects = append(diff.Objects, sidecarTaskDiff) 770 } 771 772 return diff 773 } 774 775 // connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects. 776 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 777 func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff { 778 diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"} 779 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 780 781 if reflect.DeepEqual(old, new) { 782 return nil 783 } else if old == nil { 784 old = &ConsulSidecarService{} 785 diff.Type = DiffTypeAdded 786 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 787 } else if new == nil { 788 new = &ConsulSidecarService{} 789 diff.Type = DiffTypeDeleted 790 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 791 } else { 792 diff.Type = DiffTypeEdited 793 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 794 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 795 } 796 797 // Diff the primitive fields. 798 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 799 800 consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual) 801 if consulProxyDiff != nil { 802 diff.Objects = append(diff.Objects, consulProxyDiff) 803 } 804 805 return diff 806 } 807 808 // sidecarTaskDiff returns the diff of two Task objects. 809 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 810 func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff { 811 diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"} 812 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 813 814 if reflect.DeepEqual(old, new) { 815 return nil 816 } else if old == nil { 817 old = &SidecarTask{} 818 diff.Type = DiffTypeAdded 819 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 820 } else if new == nil { 821 new = &SidecarTask{} 822 diff.Type = DiffTypeDeleted 823 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 824 } else { 825 diff.Type = DiffTypeEdited 826 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 827 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 828 } 829 830 // Diff the primitive fields. 831 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 832 833 // Config diff 834 if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil { 835 diff.Objects = append(diff.Objects, cDiff) 836 } 837 838 // Resources diff 839 if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil { 840 diff.Objects = append(diff.Objects, rDiff) 841 } 842 843 // LogConfig diff 844 lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual) 845 if lDiff != nil { 846 diff.Objects = append(diff.Objects, lDiff) 847 } 848 849 return diff 850 } 851 852 // consulProxyDiff returns the diff of two ConsulProxy objects. 853 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 854 func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff { 855 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"} 856 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 857 858 if reflect.DeepEqual(old, new) { 859 return nil 860 } else if old == nil { 861 old = &ConsulProxy{} 862 diff.Type = DiffTypeAdded 863 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 864 } else if new == nil { 865 new = &ConsulProxy{} 866 diff.Type = DiffTypeDeleted 867 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 868 } else { 869 diff.Type = DiffTypeEdited 870 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 871 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 872 } 873 874 // Diff the primitive fields. 875 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 876 877 consulUpstreamsDiff := primitiveObjectSetDiff( 878 interfaceSlice(old.Upstreams), 879 interfaceSlice(new.Upstreams), 880 nil, "ConsulUpstreams", contextual) 881 if consulUpstreamsDiff != nil { 882 diff.Objects = append(diff.Objects, consulUpstreamsDiff...) 883 } 884 885 // Config diff 886 if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil { 887 diff.Objects = append(diff.Objects, cDiff) 888 } 889 890 return diff 891 } 892 893 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 894 // enabled, unchanged fields within objects nested in the tasks will be 895 // returned. 896 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 897 oldMap := make(map[string]*ServiceCheck, len(old)) 898 newMap := make(map[string]*ServiceCheck, len(new)) 899 for _, o := range old { 900 oldMap[o.Name] = o 901 } 902 for _, n := range new { 903 newMap[n.Name] = n 904 } 905 906 var diffs []*ObjectDiff 907 for name, oldCheck := range oldMap { 908 // Diff the same, deleted and edited 909 if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil { 910 diffs = append(diffs, diff) 911 } 912 } 913 914 for name, newCheck := range newMap { 915 // Diff the added 916 if old, ok := oldMap[name]; !ok { 917 if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil { 918 diffs = append(diffs, diff) 919 } 920 } 921 } 922 923 sort.Sort(ObjectDiffs(diffs)) 924 return diffs 925 } 926 927 // vaultDiff returns the diff of two vault objects. If contextual diff is 928 // enabled, all fields will be returned, even if no diff occurred. 929 func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff { 930 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"} 931 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 932 933 if reflect.DeepEqual(old, new) { 934 return nil 935 } else if old == nil { 936 old = &Vault{} 937 diff.Type = DiffTypeAdded 938 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 939 } else if new == nil { 940 new = &Vault{} 941 diff.Type = DiffTypeDeleted 942 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 943 } else { 944 diff.Type = DiffTypeEdited 945 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 946 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 947 } 948 949 // Diff the primitive fields. 950 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 951 952 // Policies diffs 953 if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil { 954 diff.Objects = append(diff.Objects, setDiff) 955 } 956 957 return diff 958 } 959 960 // parameterizedJobDiff returns the diff of two parameterized job objects. If 961 // contextual diff is enabled, all fields will be returned, even if no diff 962 // occurred. 963 func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff { 964 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"} 965 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 966 967 if reflect.DeepEqual(old, new) { 968 return nil 969 } else if old == nil { 970 old = &ParameterizedJobConfig{} 971 diff.Type = DiffTypeAdded 972 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 973 } else if new == nil { 974 new = &ParameterizedJobConfig{} 975 diff.Type = DiffTypeDeleted 976 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 977 } else { 978 diff.Type = DiffTypeEdited 979 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 980 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 981 } 982 983 // Diff the primitive fields. 984 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 985 986 // Meta diffs 987 if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil { 988 diff.Objects = append(diff.Objects, optionalDiff) 989 } 990 991 if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil { 992 diff.Objects = append(diff.Objects, requiredDiff) 993 } 994 995 return diff 996 } 997 998 // Diff returns a diff of two resource objects. If contextual diff is enabled, 999 // non-changed fields will still be returned. 1000 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 1001 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 1002 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1003 1004 if reflect.DeepEqual(r, other) { 1005 return nil 1006 } else if r == nil { 1007 r = &Resources{} 1008 diff.Type = DiffTypeAdded 1009 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1010 } else if other == nil { 1011 other = &Resources{} 1012 diff.Type = DiffTypeDeleted 1013 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1014 } else { 1015 diff.Type = DiffTypeEdited 1016 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1017 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1018 } 1019 1020 // Diff the primitive fields. 1021 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1022 1023 // Network Resources diff 1024 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 1025 diff.Objects = append(diff.Objects, nDiffs...) 1026 } 1027 1028 // Requested Devices diff 1029 if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil { 1030 diff.Objects = append(diff.Objects, nDiffs...) 1031 } 1032 1033 return diff 1034 } 1035 1036 // Diff returns a diff of two network resources. If contextual diff is enabled, 1037 // non-changed fields will still be returned. 1038 func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 1039 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 1040 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1041 filter := []string{"Device", "CIDR", "IP"} 1042 1043 if reflect.DeepEqual(r, other) { 1044 return nil 1045 } else if r == nil { 1046 r = &NetworkResource{} 1047 diff.Type = DiffTypeAdded 1048 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 1049 } else if other == nil { 1050 other = &NetworkResource{} 1051 diff.Type = DiffTypeDeleted 1052 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 1053 } else { 1054 diff.Type = DiffTypeEdited 1055 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 1056 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 1057 } 1058 1059 // Diff the primitive fields. 1060 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1061 1062 // Port diffs 1063 resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual) 1064 dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual) 1065 if resPorts != nil { 1066 diff.Objects = append(diff.Objects, resPorts...) 1067 } 1068 if dynPorts != nil { 1069 diff.Objects = append(diff.Objects, dynPorts...) 1070 } 1071 1072 return diff 1073 } 1074 1075 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 1076 // non-changed fields will still be returned. 1077 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 1078 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 1079 objMap := make(map[string]*NetworkResource, len(objects)) 1080 for _, obj := range objects { 1081 hash, err := hashstructure.Hash(obj, nil) 1082 if err != nil { 1083 panic(err) 1084 } 1085 objMap[fmt.Sprintf("%d", hash)] = obj 1086 } 1087 1088 return objMap 1089 } 1090 1091 oldSet := makeSet(old) 1092 newSet := makeSet(new) 1093 1094 var diffs []*ObjectDiff 1095 for k, oldV := range oldSet { 1096 if newV, ok := newSet[k]; !ok { 1097 if diff := oldV.Diff(newV, contextual); diff != nil { 1098 diffs = append(diffs, diff) 1099 } 1100 } 1101 } 1102 for k, newV := range newSet { 1103 if oldV, ok := oldSet[k]; !ok { 1104 if diff := oldV.Diff(newV, contextual); diff != nil { 1105 diffs = append(diffs, diff) 1106 } 1107 } 1108 } 1109 1110 sort.Sort(ObjectDiffs(diffs)) 1111 return diffs 1112 1113 } 1114 1115 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 1116 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 1117 // non-changed fields will still be returned. 1118 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 1119 makeSet := func(ports []Port) map[string]Port { 1120 portMap := make(map[string]Port, len(ports)) 1121 for _, port := range ports { 1122 portMap[port.Label] = port 1123 } 1124 1125 return portMap 1126 } 1127 1128 oldPorts := makeSet(old) 1129 newPorts := makeSet(new) 1130 1131 var filter []string 1132 name := "Static Port" 1133 if dynamic { 1134 filter = []string{"Value"} 1135 name = "Dynamic Port" 1136 } 1137 1138 var diffs []*ObjectDiff 1139 for portLabel, oldPort := range oldPorts { 1140 // Diff the same, deleted and edited 1141 if newPort, ok := newPorts[portLabel]; ok { 1142 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 1143 if diff != nil { 1144 diffs = append(diffs, diff) 1145 } 1146 } else { 1147 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 1148 if diff != nil { 1149 diffs = append(diffs, diff) 1150 } 1151 } 1152 } 1153 for label, newPort := range newPorts { 1154 // Diff the added 1155 if _, ok := oldPorts[label]; !ok { 1156 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 1157 if diff != nil { 1158 diffs = append(diffs, diff) 1159 } 1160 } 1161 } 1162 1163 sort.Sort(ObjectDiffs(diffs)) 1164 return diffs 1165 1166 } 1167 1168 // Diff returns a diff of two requested devices. If contextual diff is enabled, 1169 // non-changed fields will still be returned. 1170 func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff { 1171 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"} 1172 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1173 1174 if reflect.DeepEqual(r, other) { 1175 return nil 1176 } else if r == nil { 1177 diff.Type = DiffTypeAdded 1178 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1179 } else if other == nil { 1180 diff.Type = DiffTypeDeleted 1181 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1182 } else { 1183 diff.Type = DiffTypeEdited 1184 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1185 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1186 } 1187 1188 // Diff the primitive fields. 1189 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1190 1191 return diff 1192 } 1193 1194 // requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled, 1195 // non-changed fields will still be returned. 1196 func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff { 1197 makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice { 1198 deviceMap := make(map[string]*RequestedDevice, len(devices)) 1199 for _, d := range devices { 1200 deviceMap[d.Name] = d 1201 } 1202 1203 return deviceMap 1204 } 1205 1206 oldSet := makeSet(old) 1207 newSet := makeSet(new) 1208 1209 var diffs []*ObjectDiff 1210 for k, oldV := range oldSet { 1211 newV := newSet[k] 1212 if diff := oldV.Diff(newV, contextual); diff != nil { 1213 diffs = append(diffs, diff) 1214 } 1215 } 1216 for k, newV := range newSet { 1217 if oldV, ok := oldSet[k]; !ok { 1218 if diff := oldV.Diff(newV, contextual); diff != nil { 1219 diffs = append(diffs, diff) 1220 } 1221 } 1222 } 1223 1224 sort.Sort(ObjectDiffs(diffs)) 1225 return diffs 1226 1227 } 1228 1229 // configDiff returns the diff of two Task Config objects. If contextual diff is 1230 // enabled, all fields will be returned, even if no diff occurred. 1231 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 1232 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 1233 if reflect.DeepEqual(old, new) { 1234 return nil 1235 } else if len(old) == 0 { 1236 diff.Type = DiffTypeAdded 1237 } else if len(new) == 0 { 1238 diff.Type = DiffTypeDeleted 1239 } else { 1240 diff.Type = DiffTypeEdited 1241 } 1242 1243 // Diff the primitive fields. 1244 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 1245 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 1246 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1247 return diff 1248 } 1249 1250 // ObjectDiff contains the diff of two generic objects. 1251 type ObjectDiff struct { 1252 Type DiffType 1253 Name string 1254 Fields []*FieldDiff 1255 Objects []*ObjectDiff 1256 } 1257 1258 func (o *ObjectDiff) GoString() string { 1259 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 1260 for _, f := range o.Fields { 1261 out += fmt.Sprintf("%#v\n", f) 1262 } 1263 for _, o := range o.Objects { 1264 out += fmt.Sprintf("%#v\n", o) 1265 } 1266 out += "}" 1267 return out 1268 } 1269 1270 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 1271 if reflect.DeepEqual(o, other) { 1272 return false 1273 } else if other == nil { 1274 return false 1275 } else if o == nil { 1276 return true 1277 } 1278 1279 if o.Name != other.Name { 1280 return o.Name < other.Name 1281 } 1282 1283 if o.Type != other.Type { 1284 return o.Type.Less(other.Type) 1285 } 1286 1287 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 1288 return lO < lOther 1289 } 1290 1291 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 1292 return lO < lOther 1293 } 1294 1295 // Check each field 1296 sort.Sort(FieldDiffs(o.Fields)) 1297 sort.Sort(FieldDiffs(other.Fields)) 1298 1299 for i, oV := range o.Fields { 1300 if oV.Less(other.Fields[i]) { 1301 return true 1302 } 1303 } 1304 1305 // Check each object 1306 sort.Sort(ObjectDiffs(o.Objects)) 1307 sort.Sort(ObjectDiffs(other.Objects)) 1308 for i, oV := range o.Objects { 1309 if oV.Less(other.Objects[i]) { 1310 return true 1311 } 1312 } 1313 1314 return false 1315 } 1316 1317 // For sorting ObjectDiffs 1318 type ObjectDiffs []*ObjectDiff 1319 1320 func (o ObjectDiffs) Len() int { return len(o) } 1321 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 1322 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 1323 1324 type FieldDiff struct { 1325 Type DiffType 1326 Name string 1327 Old, New string 1328 Annotations []string 1329 } 1330 1331 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 1332 // returns nil. If contextual diff is enabled, even non-changed fields will be 1333 // returned. 1334 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 1335 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 1336 if old == new { 1337 if !contextual { 1338 return nil 1339 } 1340 diff.Old, diff.New = old, new 1341 return diff 1342 } 1343 1344 if old == "" { 1345 diff.Type = DiffTypeAdded 1346 diff.New = new 1347 } else if new == "" { 1348 diff.Type = DiffTypeDeleted 1349 diff.Old = old 1350 } else { 1351 diff.Type = DiffTypeEdited 1352 diff.Old = old 1353 diff.New = new 1354 } 1355 return diff 1356 } 1357 1358 func (f *FieldDiff) GoString() string { 1359 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 1360 if len(f.Annotations) != 0 { 1361 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 1362 } 1363 1364 return out 1365 } 1366 1367 func (f *FieldDiff) Less(other *FieldDiff) bool { 1368 if reflect.DeepEqual(f, other) { 1369 return false 1370 } else if other == nil { 1371 return false 1372 } else if f == nil { 1373 return true 1374 } 1375 1376 if f.Name != other.Name { 1377 return f.Name < other.Name 1378 } else if f.Old != other.Old { 1379 return f.Old < other.Old 1380 } 1381 1382 return f.New < other.New 1383 } 1384 1385 // For sorting FieldDiffs 1386 type FieldDiffs []*FieldDiff 1387 1388 func (f FieldDiffs) Len() int { return len(f) } 1389 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 1390 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 1391 1392 // fieldDiffs takes a map of field names to their values and returns a set of 1393 // field diffs. If contextual diff is enabled, even non-changed fields will be 1394 // returned. 1395 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 1396 var diffs []*FieldDiff 1397 visited := make(map[string]struct{}) 1398 for k, oldV := range old { 1399 visited[k] = struct{}{} 1400 newV := new[k] 1401 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 1402 diffs = append(diffs, diff) 1403 } 1404 } 1405 1406 for k, newV := range new { 1407 if _, ok := visited[k]; !ok { 1408 if diff := fieldDiff("", newV, k, contextual); diff != nil { 1409 diffs = append(diffs, diff) 1410 } 1411 } 1412 } 1413 1414 sort.Sort(FieldDiffs(diffs)) 1415 return diffs 1416 } 1417 1418 // stringSetDiff diffs two sets of strings with the given name. 1419 func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff { 1420 oldMap := make(map[string]struct{}, len(old)) 1421 newMap := make(map[string]struct{}, len(new)) 1422 for _, o := range old { 1423 oldMap[o] = struct{}{} 1424 } 1425 for _, n := range new { 1426 newMap[n] = struct{}{} 1427 } 1428 if reflect.DeepEqual(oldMap, newMap) && !contextual { 1429 return nil 1430 } 1431 1432 diff := &ObjectDiff{Name: name} 1433 var added, removed bool 1434 for k := range oldMap { 1435 if _, ok := newMap[k]; !ok { 1436 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual)) 1437 removed = true 1438 } else if contextual { 1439 diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual)) 1440 } 1441 } 1442 1443 for k := range newMap { 1444 if _, ok := oldMap[k]; !ok { 1445 diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual)) 1446 added = true 1447 } 1448 } 1449 1450 sort.Sort(FieldDiffs(diff.Fields)) 1451 1452 // Determine the type 1453 if added && removed { 1454 diff.Type = DiffTypeEdited 1455 } else if added { 1456 diff.Type = DiffTypeAdded 1457 } else if removed { 1458 diff.Type = DiffTypeDeleted 1459 } else { 1460 // Diff of an empty set 1461 if len(diff.Fields) == 0 { 1462 return nil 1463 } 1464 1465 diff.Type = DiffTypeNone 1466 } 1467 1468 return diff 1469 } 1470 1471 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 1472 // The filter field can be used to exclude fields from the diff. The name is the 1473 // name of the objects. If contextual is set, non-changed fields will also be 1474 // stored in the object diff. 1475 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 1476 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 1477 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 1478 delete(oldPrimitiveFlat, "") 1479 delete(newPrimitiveFlat, "") 1480 1481 diff := &ObjectDiff{Name: name} 1482 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1483 1484 var added, deleted, edited bool 1485 for _, f := range diff.Fields { 1486 switch f.Type { 1487 case DiffTypeEdited: 1488 edited = true 1489 break 1490 case DiffTypeDeleted: 1491 deleted = true 1492 case DiffTypeAdded: 1493 added = true 1494 } 1495 } 1496 1497 if edited || added && deleted { 1498 diff.Type = DiffTypeEdited 1499 } else if added { 1500 diff.Type = DiffTypeAdded 1501 } else if deleted { 1502 diff.Type = DiffTypeDeleted 1503 } else { 1504 return nil 1505 } 1506 1507 return diff 1508 } 1509 1510 // primitiveObjectSetDiff does a set difference of the old and new sets. The 1511 // filter parameter can be used to filter a set of primitive fields in the 1512 // passed structs. The name corresponds to the name of the passed objects. If 1513 // contextual diff is enabled, objects' primitive fields will be returned even if 1514 // no diff exists. 1515 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 1516 makeSet := func(objects []interface{}) map[string]interface{} { 1517 objMap := make(map[string]interface{}, len(objects)) 1518 for _, obj := range objects { 1519 hash, err := hashstructure.Hash(obj, nil) 1520 if err != nil { 1521 panic(err) 1522 } 1523 objMap[fmt.Sprintf("%d", hash)] = obj 1524 } 1525 1526 return objMap 1527 } 1528 1529 oldSet := makeSet(old) 1530 newSet := makeSet(new) 1531 1532 var diffs []*ObjectDiff 1533 for k, v := range oldSet { 1534 // Deleted 1535 if _, ok := newSet[k]; !ok { 1536 diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual)) 1537 } 1538 } 1539 for k, v := range newSet { 1540 // Added 1541 if _, ok := oldSet[k]; !ok { 1542 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 1543 } 1544 } 1545 1546 sort.Sort(ObjectDiffs(diffs)) 1547 return diffs 1548 } 1549 1550 // interfaceSlice is a helper method that takes a slice of typed elements and 1551 // returns a slice of interface. This method will panic if given a non-slice 1552 // input. 1553 func interfaceSlice(slice interface{}) []interface{} { 1554 s := reflect.ValueOf(slice) 1555 if s.Kind() != reflect.Slice { 1556 panic("InterfaceSlice() given a non-slice type") 1557 } 1558 1559 ret := make([]interface{}, s.Len()) 1560 1561 for i := 0; i < s.Len(); i++ { 1562 ret[i] = s.Index(i).Interface() 1563 } 1564 1565 return ret 1566 }