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