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