github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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 // DiffableWithID defines an object that has a unique and stable value that can 14 // be used as an identifier when generating a diff. 15 type DiffableWithID interface { 16 // DiffID returns the value to use to match entities between the old and 17 // the new input. 18 DiffID() string 19 } 20 21 // DiffType denotes the type of a diff object. 22 type DiffType string 23 24 var ( 25 DiffTypeNone DiffType = "None" 26 DiffTypeAdded DiffType = "Added" 27 DiffTypeDeleted DiffType = "Deleted" 28 DiffTypeEdited DiffType = "Edited" 29 ) 30 31 func (d DiffType) Less(other DiffType) bool { 32 // Edited > Added > Deleted > None 33 // But we do a reverse sort 34 if d == other { 35 return false 36 } 37 38 if d == DiffTypeEdited { 39 return true 40 } else if other == DiffTypeEdited { 41 return false 42 } else if d == DiffTypeAdded { 43 return true 44 } else if other == DiffTypeAdded { 45 return false 46 } else if d == DiffTypeDeleted { 47 return true 48 } else if other == DiffTypeDeleted { 49 return false 50 } 51 52 return true 53 } 54 55 // JobDiff contains the diff of two jobs. 56 type JobDiff struct { 57 Type DiffType 58 ID string 59 Fields []*FieldDiff 60 Objects []*ObjectDiff 61 TaskGroups []*TaskGroupDiff 62 } 63 64 // Diff returns a diff of two jobs and a potential error if the Jobs are not 65 // diffable. If contextual diff is enabled, objects within the job will contain 66 // field information even if unchanged. 67 func (j *Job) Diff(other *Job, contextual bool) (*JobDiff, error) { 68 // See agent.ApiJobToStructJob Update is a default for TaskGroups 69 diff := &JobDiff{Type: DiffTypeNone} 70 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 71 filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex", 72 "ModifyIndex", "JobModifyIndex", "Update", "SubmitTime", "NomadTokenID", "VaultToken"} 73 74 if j == nil && other == nil { 75 return diff, nil 76 } else if j == nil { 77 j = &Job{} 78 diff.Type = DiffTypeAdded 79 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 80 diff.ID = other.ID 81 } else if other == nil { 82 other = &Job{} 83 diff.Type = DiffTypeDeleted 84 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 85 diff.ID = j.ID 86 } else { 87 if j.ID != other.ID { 88 return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID) 89 } 90 91 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 92 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 93 diff.ID = other.ID 94 } 95 96 // Diff the primitive fields. 97 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 98 99 // Datacenters diff 100 if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone { 101 diff.Objects = append(diff.Objects, setDiff) 102 } 103 104 // Constraints diff 105 conDiff := primitiveObjectSetDiff( 106 interfaceSlice(j.Constraints), 107 interfaceSlice(other.Constraints), 108 []string{"str"}, 109 "Constraint", 110 contextual) 111 if conDiff != nil { 112 diff.Objects = append(diff.Objects, conDiff...) 113 } 114 115 // Affinities diff 116 affinitiesDiff := primitiveObjectSetDiff( 117 interfaceSlice(j.Affinities), 118 interfaceSlice(other.Affinities), 119 []string{"str"}, 120 "Affinity", 121 contextual) 122 if affinitiesDiff != nil { 123 diff.Objects = append(diff.Objects, affinitiesDiff...) 124 } 125 126 // Task groups diff 127 tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual) 128 if err != nil { 129 return nil, err 130 } 131 diff.TaskGroups = tgs 132 133 // Periodic diff 134 if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil { 135 diff.Objects = append(diff.Objects, pDiff) 136 } 137 138 // ParameterizedJob diff 139 if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil { 140 diff.Objects = append(diff.Objects, cDiff) 141 } 142 143 // Multiregion diff 144 if mrDiff := multiregionDiff(j.Multiregion, other.Multiregion, contextual); mrDiff != nil { 145 diff.Objects = append(diff.Objects, mrDiff) 146 } 147 148 // Check to see if there is a diff. We don't use reflect because we are 149 // filtering quite a few fields that will change on each diff. 150 if diff.Type == DiffTypeNone { 151 for _, fd := range diff.Fields { 152 if fd.Type != DiffTypeNone { 153 diff.Type = DiffTypeEdited 154 break 155 } 156 } 157 } 158 159 if diff.Type == DiffTypeNone { 160 for _, od := range diff.Objects { 161 if od.Type != DiffTypeNone { 162 diff.Type = DiffTypeEdited 163 break 164 } 165 } 166 } 167 168 if diff.Type == DiffTypeNone { 169 for _, tg := range diff.TaskGroups { 170 if tg.Type != DiffTypeNone { 171 diff.Type = DiffTypeEdited 172 break 173 } 174 } 175 } 176 177 return diff, nil 178 } 179 180 func (j *JobDiff) GoString() string { 181 out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type) 182 183 for _, f := range j.Fields { 184 out += fmt.Sprintf("%#v\n", f) 185 } 186 187 for _, o := range j.Objects { 188 out += fmt.Sprintf("%#v\n", o) 189 } 190 191 for _, tg := range j.TaskGroups { 192 out += fmt.Sprintf("%#v\n", tg) 193 } 194 195 return out 196 } 197 198 // TaskGroupDiff contains the diff of two task groups. 199 type TaskGroupDiff struct { 200 Type DiffType 201 Name string 202 Fields []*FieldDiff 203 Objects []*ObjectDiff 204 Tasks []*TaskDiff 205 Updates map[string]uint64 206 } 207 208 // Diff returns a diff of two task groups. If contextual diff is enabled, 209 // objects' fields will be stored even if no diff occurred as long as one field 210 // changed. 211 func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) { 212 diff := &TaskGroupDiff{Type: DiffTypeNone} 213 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 214 filter := []string{"Name"} 215 216 if tg == nil && other == nil { 217 return diff, nil 218 } else if tg == nil { 219 tg = &TaskGroup{} 220 diff.Type = DiffTypeAdded 221 diff.Name = other.Name 222 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 223 } else if other == nil { 224 other = &TaskGroup{} 225 diff.Type = DiffTypeDeleted 226 diff.Name = tg.Name 227 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 228 } else { 229 if !reflect.DeepEqual(tg, other) { 230 diff.Type = DiffTypeEdited 231 } 232 if tg.Name != other.Name { 233 return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name) 234 } 235 diff.Name = other.Name 236 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 237 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 238 } 239 240 // ShutdownDelay diff 241 if oldPrimitiveFlat != nil && newPrimitiveFlat != nil { 242 if tg.ShutdownDelay == nil { 243 oldPrimitiveFlat["ShutdownDelay"] = "" 244 } else { 245 oldPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *tg.ShutdownDelay) 246 } 247 if other.ShutdownDelay == nil { 248 newPrimitiveFlat["ShutdownDelay"] = "" 249 } else { 250 newPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *other.ShutdownDelay) 251 } 252 } 253 254 // StopAfterClientDisconnect diff 255 if oldPrimitiveFlat != nil && newPrimitiveFlat != nil { 256 if tg.StopAfterClientDisconnect == nil { 257 oldPrimitiveFlat["StopAfterClientDisconnect"] = "" 258 } else { 259 oldPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *tg.StopAfterClientDisconnect) 260 } 261 if other.StopAfterClientDisconnect == nil { 262 newPrimitiveFlat["StopAfterClientDisconnect"] = "" 263 } else { 264 newPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *other.StopAfterClientDisconnect) 265 } 266 } 267 268 // MaxClientDisconnect diff 269 if oldPrimitiveFlat != nil && newPrimitiveFlat != nil { 270 if tg.MaxClientDisconnect == nil { 271 oldPrimitiveFlat["MaxClientDisconnect"] = "" 272 } else { 273 oldPrimitiveFlat["MaxClientDisconnect"] = fmt.Sprintf("%d", *tg.MaxClientDisconnect) 274 } 275 if other.MaxClientDisconnect == nil { 276 newPrimitiveFlat["MaxClientDisconnect"] = "" 277 } else { 278 newPrimitiveFlat["MaxClientDisconnect"] = fmt.Sprintf("%d", *other.MaxClientDisconnect) 279 } 280 } 281 282 // Diff the primitive fields. 283 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 284 285 // Constraints diff 286 conDiff := primitiveObjectSetDiff( 287 interfaceSlice(tg.Constraints), 288 interfaceSlice(other.Constraints), 289 []string{"str"}, 290 "Constraint", 291 contextual) 292 if conDiff != nil { 293 diff.Objects = append(diff.Objects, conDiff...) 294 } 295 296 // Affinities diff 297 affinitiesDiff := primitiveObjectSetDiff( 298 interfaceSlice(tg.Affinities), 299 interfaceSlice(other.Affinities), 300 []string{"str"}, 301 "Affinity", 302 contextual) 303 if affinitiesDiff != nil { 304 diff.Objects = append(diff.Objects, affinitiesDiff...) 305 } 306 307 // Restart policy diff 308 rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual) 309 if rDiff != nil { 310 diff.Objects = append(diff.Objects, rDiff) 311 } 312 313 // Reschedule policy diff 314 reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual) 315 if reschedDiff != nil { 316 diff.Objects = append(diff.Objects, reschedDiff) 317 } 318 319 // EphemeralDisk diff 320 diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual) 321 if diskDiff != nil { 322 diff.Objects = append(diff.Objects, diskDiff) 323 } 324 325 consulDiff := primitiveObjectDiff(tg.Consul, other.Consul, nil, "Consul", contextual) 326 if consulDiff != nil { 327 diff.Objects = append(diff.Objects, consulDiff) 328 } 329 330 // Update diff 331 // COMPAT: Remove "Stagger" in 0.7.0. 332 if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil { 333 diff.Objects = append(diff.Objects, uDiff) 334 } 335 336 // Network Resources diff 337 if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil { 338 diff.Objects = append(diff.Objects, nDiffs...) 339 } 340 341 // Services diff 342 if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil { 343 diff.Objects = append(diff.Objects, sDiffs...) 344 } 345 346 // Volumes diff 347 if vDiffs := volumeDiffs(tg.Volumes, other.Volumes, contextual); vDiffs != nil { 348 diff.Objects = append(diff.Objects, vDiffs...) 349 } 350 351 // Tasks diff 352 tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual) 353 if err != nil { 354 return nil, err 355 } 356 diff.Tasks = tasks 357 358 return diff, nil 359 } 360 361 func (tg *TaskGroupDiff) GoString() string { 362 out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type) 363 364 if len(tg.Updates) != 0 { 365 out += "Updates {\n" 366 for update, count := range tg.Updates { 367 out += fmt.Sprintf("%d %s\n", count, update) 368 } 369 out += "}\n" 370 } 371 372 for _, f := range tg.Fields { 373 out += fmt.Sprintf("%#v\n", f) 374 } 375 376 for _, o := range tg.Objects { 377 out += fmt.Sprintf("%#v\n", o) 378 } 379 380 for _, t := range tg.Tasks { 381 out += fmt.Sprintf("%#v\n", t) 382 } 383 384 return out 385 } 386 387 // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled, 388 // objects' fields will be stored even if no diff occurred as long as one field 389 // changed. 390 func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) { 391 oldMap := make(map[string]*TaskGroup, len(old)) 392 newMap := make(map[string]*TaskGroup, len(new)) 393 for _, o := range old { 394 oldMap[o.Name] = o 395 } 396 for _, n := range new { 397 newMap[n.Name] = n 398 } 399 400 var diffs []*TaskGroupDiff 401 for name, oldGroup := range oldMap { 402 // Diff the same, deleted and edited 403 diff, err := oldGroup.Diff(newMap[name], contextual) 404 if err != nil { 405 return nil, err 406 } 407 diffs = append(diffs, diff) 408 } 409 410 for name, newGroup := range newMap { 411 // Diff the added 412 if old, ok := oldMap[name]; !ok { 413 diff, err := old.Diff(newGroup, contextual) 414 if err != nil { 415 return nil, err 416 } 417 diffs = append(diffs, diff) 418 } 419 } 420 421 sort.Sort(TaskGroupDiffs(diffs)) 422 return diffs, nil 423 } 424 425 // For sorting TaskGroupDiffs 426 type TaskGroupDiffs []*TaskGroupDiff 427 428 func (tg TaskGroupDiffs) Len() int { return len(tg) } 429 func (tg TaskGroupDiffs) Swap(i, j int) { tg[i], tg[j] = tg[j], tg[i] } 430 func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name } 431 432 // TaskDiff contains the diff of two Tasks 433 type TaskDiff struct { 434 Type DiffType 435 Name string 436 Fields []*FieldDiff 437 Objects []*ObjectDiff 438 Annotations []string 439 } 440 441 // Diff returns a diff of two tasks. If contextual diff is enabled, objects 442 // within the task will contain field information even if unchanged. 443 func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { 444 diff := &TaskDiff{Type: DiffTypeNone} 445 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 446 filter := []string{"Name", "Config"} 447 448 if t == nil && other == nil { 449 return diff, nil 450 } else if t == nil { 451 t = &Task{} 452 diff.Type = DiffTypeAdded 453 diff.Name = other.Name 454 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 455 } else if other == nil { 456 other = &Task{} 457 diff.Type = DiffTypeDeleted 458 diff.Name = t.Name 459 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 460 } else { 461 if !reflect.DeepEqual(t, other) { 462 diff.Type = DiffTypeEdited 463 } 464 if t.Name != other.Name { 465 return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name) 466 } 467 diff.Name = other.Name 468 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 469 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 470 } 471 472 // Diff the primitive fields. 473 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 474 475 // Constraints diff 476 conDiff := primitiveObjectSetDiff( 477 interfaceSlice(t.Constraints), 478 interfaceSlice(other.Constraints), 479 []string{"str"}, 480 "Constraint", 481 contextual) 482 if conDiff != nil { 483 diff.Objects = append(diff.Objects, conDiff...) 484 } 485 486 // Affinities diff 487 affinitiesDiff := primitiveObjectSetDiff( 488 interfaceSlice(t.Affinities), 489 interfaceSlice(other.Affinities), 490 []string{"str"}, 491 "Affinity", 492 contextual) 493 if affinitiesDiff != nil { 494 diff.Objects = append(diff.Objects, affinitiesDiff...) 495 } 496 497 // Config diff 498 if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil { 499 diff.Objects = append(diff.Objects, cDiff) 500 } 501 502 // Resources diff 503 if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil { 504 diff.Objects = append(diff.Objects, rDiff) 505 } 506 507 // LogConfig diff 508 lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual) 509 if lDiff != nil { 510 diff.Objects = append(diff.Objects, lDiff) 511 } 512 513 // Dispatch payload diff 514 dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual) 515 if dDiff != nil { 516 diff.Objects = append(diff.Objects, dDiff) 517 } 518 519 // Artifacts diff 520 diffs := primitiveObjectSetDiff( 521 interfaceSlice(t.Artifacts), 522 interfaceSlice(other.Artifacts), 523 nil, 524 "Artifact", 525 contextual) 526 if diffs != nil { 527 diff.Objects = append(diff.Objects, diffs...) 528 } 529 530 // Services diff 531 if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil { 532 diff.Objects = append(diff.Objects, sDiffs...) 533 } 534 535 // Vault diff 536 vDiff := vaultDiff(t.Vault, other.Vault, contextual) 537 if vDiff != nil { 538 diff.Objects = append(diff.Objects, vDiff) 539 } 540 541 // Template diff 542 tmplDiffs := templateDiffs(t.Templates, other.Templates, contextual) 543 if tmplDiffs != nil { 544 diff.Objects = append(diff.Objects, tmplDiffs...) 545 } 546 547 return diff, nil 548 } 549 550 func (t *TaskDiff) GoString() string { 551 var out string 552 if len(t.Annotations) == 0 { 553 out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type) 554 } else { 555 out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ",")) 556 } 557 558 for _, f := range t.Fields { 559 out += fmt.Sprintf("%#v\n", f) 560 } 561 562 for _, o := range t.Objects { 563 out += fmt.Sprintf("%#v\n", o) 564 } 565 566 return out 567 } 568 569 // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged 570 // fields within objects nested in the tasks will be returned. 571 func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) { 572 oldMap := make(map[string]*Task, len(old)) 573 newMap := make(map[string]*Task, len(new)) 574 for _, o := range old { 575 oldMap[o.Name] = o 576 } 577 for _, n := range new { 578 newMap[n.Name] = n 579 } 580 581 var diffs []*TaskDiff 582 for name, oldGroup := range oldMap { 583 // Diff the same, deleted and edited 584 diff, err := oldGroup.Diff(newMap[name], contextual) 585 if err != nil { 586 return nil, err 587 } 588 diffs = append(diffs, diff) 589 } 590 591 for name, newGroup := range newMap { 592 // Diff the added 593 if old, ok := oldMap[name]; !ok { 594 diff, err := old.Diff(newGroup, contextual) 595 if err != nil { 596 return nil, err 597 } 598 diffs = append(diffs, diff) 599 } 600 } 601 602 sort.Sort(TaskDiffs(diffs)) 603 return diffs, nil 604 } 605 606 // For sorting TaskDiffs 607 type TaskDiffs []*TaskDiff 608 609 func (t TaskDiffs) Len() int { return len(t) } 610 func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 611 func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name } 612 613 // serviceDiff returns the diff of two service objects. If contextual diff is 614 // enabled, all fields will be returned, even if no diff occurred. 615 func serviceDiff(old, new *Service, contextual bool) *ObjectDiff { 616 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 617 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 618 619 if reflect.DeepEqual(old, new) { 620 return nil 621 } else if old == nil { 622 old = &Service{} 623 diff.Type = DiffTypeAdded 624 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 625 } else if new == nil { 626 new = &Service{} 627 diff.Type = DiffTypeDeleted 628 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 629 } else { 630 diff.Type = DiffTypeEdited 631 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 632 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 633 } 634 635 // Diff the primitive fields. 636 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 637 638 if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil { 639 diff.Objects = append(diff.Objects, setDiff) 640 } 641 642 // Tag diffs 643 if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil { 644 diff.Objects = append(diff.Objects, setDiff) 645 } 646 647 // Checks diffs 648 if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil { 649 diff.Objects = append(diff.Objects, cDiffs...) 650 } 651 652 // Consul Connect diffs 653 if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil { 654 diff.Objects = append(diff.Objects, conDiffs) 655 } 656 657 return diff 658 } 659 660 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 661 // fields within objects nested in the tasks will be returned. 662 func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff { 663 // Handle trivial case. 664 if len(old) == 1 && len(new) == 1 { 665 if diff := serviceDiff(old[0], new[0], contextual); diff != nil { 666 return []*ObjectDiff{diff} 667 } 668 return nil 669 } 670 671 // For each service we will try to find a corresponding match in the other 672 // service list. 673 // The following lists store the index of the matching service for each 674 // position of the inputs. 675 oldMatches := make([]int, len(old)) 676 newMatches := make([]int, len(new)) 677 678 // Initialize all services as unmatched. 679 for i := range oldMatches { 680 oldMatches[i] = -1 681 } 682 for i := range newMatches { 683 newMatches[i] = -1 684 } 685 686 // Find a match in the new services list for each old service and compute 687 // their diffs. 688 var diffs []*ObjectDiff 689 for oldIndex, oldService := range old { 690 newIndex := findServiceMatch(oldService, oldIndex, new, newMatches) 691 692 // Old services that don't have a match were deleted. 693 if newIndex < 0 { 694 diff := serviceDiff(oldService, nil, contextual) 695 diffs = append(diffs, diff) 696 continue 697 } 698 699 // If A matches B then B matches A. 700 oldMatches[oldIndex] = newIndex 701 newMatches[newIndex] = oldIndex 702 703 newService := new[newIndex] 704 if diff := serviceDiff(oldService, newService, contextual); diff != nil { 705 diffs = append(diffs, diff) 706 } 707 } 708 709 // New services without match were added. 710 for i, m := range newMatches { 711 if m == -1 { 712 diff := serviceDiff(nil, new[i], contextual) 713 diffs = append(diffs, diff) 714 } 715 } 716 717 sort.Sort(ObjectDiffs(diffs)) 718 return diffs 719 } 720 721 // findServiceMatch returns the index of the service in the input services list 722 // that matches the provided input service. 723 func findServiceMatch(service *Service, serviceIndex int, services []*Service, matches []int) int { 724 // minScoreThreshold can be adjusted to generate more (lower value) or 725 // fewer (higher value) matches. 726 // More matches result in more Edited diffs, while fewer matches generate 727 // more Add/Delete diff pairs. 728 minScoreThreshold := 2 729 730 highestScore := 0 731 indexMatch := -1 732 733 for i, s := range services { 734 // Skip service if it's already matched. 735 if matches[i] >= 0 { 736 continue 737 } 738 739 // Finding a perfect match by just looking at the before and after 740 // list of services is impossible since they don't have a stable 741 // identifier that can be used to uniquely identify them. 742 // 743 // Users also have an implicit temporal intuition of which services 744 // match each other when editing their jobspec file. If they move the 745 // 3rd service to the top, they don't expect their job to change. 746 // 747 // This intuition could be made explicit by requiring a user-defined 748 // unique identifier, but this would cause additional work and the 749 // new field would not be intuitive for users to understand how to use 750 // it. 751 // 752 // Using a hash value of the service content will cause any changes to 753 // create a delete/add diff pair. 754 // 755 // There are three main candidates for a service ID: 756 // - name, but they are not unique and can be modified. 757 // - label port, but they have the same problems as name. 758 // - service position within the overall list of services, but if the 759 // service block is moved, it will impact all services that come 760 // after it. 761 // 762 // None of these values are enough on their own, but they are also too 763 // strong when considered all together. 764 // 765 // So we try to score services by their main candidates with a preference 766 // towards name + label over service position. 767 score := 0 768 if i == serviceIndex { 769 score += 1 770 } 771 772 if service.PortLabel == s.PortLabel { 773 score += 2 774 } 775 776 if service.Name == s.Name { 777 score += 3 778 } 779 780 if score > minScoreThreshold && score > highestScore { 781 highestScore = score 782 indexMatch = i 783 } 784 } 785 786 return indexMatch 787 } 788 789 // serviceCheckDiff returns the diff of two service check objects. If contextual 790 // diff is enabled, all fields will be returned, even if no diff occurred. 791 func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff { 792 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"} 793 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 794 795 if reflect.DeepEqual(old, new) { 796 return nil 797 } else if old == nil { 798 old = &ServiceCheck{} 799 diff.Type = DiffTypeAdded 800 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 801 } else if new == nil { 802 new = &ServiceCheck{} 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 // Diff Header 815 if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil { 816 diff.Objects = append(diff.Objects, headerDiff) 817 } 818 819 // Diff check_restart 820 if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil { 821 diff.Objects = append(diff.Objects, crDiff) 822 } 823 824 return diff 825 } 826 827 // checkHeaderDiff returns the diff of two service check header objects. If 828 // contextual diff is enabled, all fields will be returned, even if no diff 829 // occurred. 830 func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff { 831 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"} 832 var oldFlat, newFlat map[string]string 833 834 if reflect.DeepEqual(old, new) { 835 return nil 836 } else if len(old) == 0 { 837 diff.Type = DiffTypeAdded 838 newFlat = flatmap.Flatten(new, nil, false) 839 } else if len(new) == 0 { 840 diff.Type = DiffTypeDeleted 841 oldFlat = flatmap.Flatten(old, nil, false) 842 } else { 843 diff.Type = DiffTypeEdited 844 oldFlat = flatmap.Flatten(old, nil, false) 845 newFlat = flatmap.Flatten(new, nil, false) 846 } 847 848 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 849 return diff 850 } 851 852 // checkRestartDiff returns the diff of two service check check_restart 853 // objects. If contextual diff is enabled, all fields will be returned, even if 854 // no diff occurred. 855 func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff { 856 diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"} 857 var oldFlat, newFlat map[string]string 858 859 if reflect.DeepEqual(old, new) { 860 return nil 861 } else if old == nil { 862 diff.Type = DiffTypeAdded 863 newFlat = flatmap.Flatten(new, nil, true) 864 diff.Type = DiffTypeAdded 865 } else if new == nil { 866 diff.Type = DiffTypeDeleted 867 oldFlat = flatmap.Flatten(old, nil, true) 868 } else { 869 diff.Type = DiffTypeEdited 870 oldFlat = flatmap.Flatten(old, nil, true) 871 newFlat = flatmap.Flatten(new, nil, true) 872 } 873 874 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 875 return diff 876 } 877 878 // connectDiffs returns the diff of two Consul connect objects. If contextual 879 // diff is enabled, all fields will be returned, even if no diff occurred. 880 func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff { 881 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"} 882 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 883 884 if reflect.DeepEqual(old, new) { 885 return nil 886 } else if old == nil { 887 old = &ConsulConnect{} 888 diff.Type = DiffTypeAdded 889 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 890 } else if new == nil { 891 new = &ConsulConnect{} 892 diff.Type = DiffTypeDeleted 893 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 894 } else { 895 diff.Type = DiffTypeEdited 896 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 897 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 898 } 899 900 // Diff the primitive fields. 901 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 902 903 // Diff the object field SidecarService. 904 sidecarSvcDiff := connectSidecarServiceDiff(old.SidecarService, new.SidecarService, contextual) 905 if sidecarSvcDiff != nil { 906 diff.Objects = append(diff.Objects, sidecarSvcDiff) 907 } 908 909 // Diff the object field SidecarTask. 910 sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual) 911 if sidecarTaskDiff != nil { 912 diff.Objects = append(diff.Objects, sidecarTaskDiff) 913 } 914 915 // Diff the object field ConsulGateway. 916 gatewayDiff := connectGatewayDiff(old.Gateway, new.Gateway, contextual) 917 if gatewayDiff != nil { 918 diff.Objects = append(diff.Objects, gatewayDiff) 919 } 920 921 return diff 922 } 923 924 func connectGatewayDiff(prev, next *ConsulGateway, contextual bool) *ObjectDiff { 925 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Gateway"} 926 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 927 928 if reflect.DeepEqual(prev, next) { 929 return nil 930 } else if prev == nil { 931 prev = new(ConsulGateway) 932 diff.Type = DiffTypeAdded 933 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 934 } else if next == nil { 935 next = new(ConsulGateway) 936 diff.Type = DiffTypeDeleted 937 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 938 } else { 939 diff.Type = DiffTypeEdited 940 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 941 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 942 } 943 944 // Diff the primitive fields. 945 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 946 947 // Diff the ConsulGatewayProxy fields. 948 gatewayProxyDiff := connectGatewayProxyDiff(prev.Proxy, next.Proxy, contextual) 949 if gatewayProxyDiff != nil { 950 diff.Objects = append(diff.Objects, gatewayProxyDiff) 951 } 952 953 // Diff the ingress gateway fields. 954 gatewayIngressDiff := connectGatewayIngressDiff(prev.Ingress, next.Ingress, contextual) 955 if gatewayIngressDiff != nil { 956 diff.Objects = append(diff.Objects, gatewayIngressDiff) 957 } 958 959 // Diff the terminating gateway fields. 960 gatewayTerminatingDiff := connectGatewayTerminatingDiff(prev.Terminating, next.Terminating, contextual) 961 if gatewayTerminatingDiff != nil { 962 diff.Objects = append(diff.Objects, gatewayTerminatingDiff) 963 } 964 965 // Diff the mesh gateway fields. 966 gatewayMeshDiff := connectGatewayMeshDiff(prev.Mesh, next.Mesh, contextual) 967 if gatewayMeshDiff != nil { 968 diff.Objects = append(diff.Objects, gatewayMeshDiff) 969 } 970 971 return diff 972 } 973 974 func connectGatewayMeshDiff(prev, next *ConsulMeshConfigEntry, contextual bool) *ObjectDiff { 975 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Mesh"} 976 977 if reflect.DeepEqual(prev, next) { 978 return nil 979 } else if prev == nil { 980 // no fields to further diff 981 diff.Type = DiffTypeAdded 982 } else if next == nil { 983 // no fields to further diff 984 diff.Type = DiffTypeDeleted 985 } else { 986 diff.Type = DiffTypeEdited 987 } 988 989 // Currently no fields in mesh gateways. 990 991 return diff 992 } 993 994 func connectGatewayIngressDiff(prev, next *ConsulIngressConfigEntry, contextual bool) *ObjectDiff { 995 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Ingress"} 996 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 997 998 if reflect.DeepEqual(prev, next) { 999 return nil 1000 } else if prev == nil { 1001 prev = new(ConsulIngressConfigEntry) 1002 diff.Type = DiffTypeAdded 1003 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1004 } else if next == nil { 1005 next = new(ConsulIngressConfigEntry) 1006 diff.Type = DiffTypeDeleted 1007 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1008 } else { 1009 diff.Type = DiffTypeEdited 1010 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1011 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1012 } 1013 1014 // Diff the primitive fields. 1015 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1016 1017 // Diff the ConsulGatewayTLSConfig objects. 1018 tlsConfigDiff := connectGatewayTLSConfigDiff(prev.TLS, next.TLS, contextual) 1019 if tlsConfigDiff != nil { 1020 diff.Objects = append(diff.Objects, tlsConfigDiff) 1021 } 1022 1023 // Diff the Listeners lists. 1024 gatewayIngressListenersDiff := connectGatewayIngressListenersDiff(prev.Listeners, next.Listeners, contextual) 1025 if gatewayIngressListenersDiff != nil { 1026 diff.Objects = append(diff.Objects, gatewayIngressListenersDiff...) 1027 } 1028 1029 return diff 1030 } 1031 1032 func connectGatewayTerminatingDiff(prev, next *ConsulTerminatingConfigEntry, contextual bool) *ObjectDiff { 1033 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Terminating"} 1034 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1035 1036 if reflect.DeepEqual(prev, next) { 1037 return nil 1038 } else if prev == nil { 1039 prev = new(ConsulTerminatingConfigEntry) 1040 diff.Type = DiffTypeAdded 1041 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1042 } else if next == nil { 1043 next = new(ConsulTerminatingConfigEntry) 1044 diff.Type = DiffTypeDeleted 1045 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1046 } else { 1047 diff.Type = DiffTypeEdited 1048 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1049 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1050 } 1051 1052 // Diff the primitive fields. 1053 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1054 1055 // Diff the Services lists. 1056 gatewayLinkedServicesDiff := connectGatewayTerminatingLinkedServicesDiff(prev.Services, next.Services, contextual) 1057 if gatewayLinkedServicesDiff != nil { 1058 diff.Objects = append(diff.Objects, gatewayLinkedServicesDiff...) 1059 } 1060 1061 return diff 1062 } 1063 1064 // connectGatewayTerminatingLinkedServicesDiff diffs are a set of services keyed 1065 // by service name. These objects contain only fields. 1066 func connectGatewayTerminatingLinkedServicesDiff(prev, next []*ConsulLinkedService, contextual bool) []*ObjectDiff { 1067 // create maps, diff the maps, key by linked service name 1068 1069 prevMap := make(map[string]*ConsulLinkedService, len(prev)) 1070 nextMap := make(map[string]*ConsulLinkedService, len(next)) 1071 1072 for _, s := range prev { 1073 prevMap[s.Name] = s 1074 } 1075 for _, s := range next { 1076 nextMap[s.Name] = s 1077 } 1078 1079 var diffs []*ObjectDiff 1080 for k, prevS := range prevMap { 1081 // Diff the same, deleted, and edited 1082 if diff := connectGatewayTerminatingLinkedServiceDiff(prevS, nextMap[k], contextual); diff != nil { 1083 diffs = append(diffs, diff) 1084 } 1085 } 1086 for k, nextS := range nextMap { 1087 // Diff the added 1088 if old, ok := prevMap[k]; !ok { 1089 if diff := connectGatewayTerminatingLinkedServiceDiff(old, nextS, contextual); diff != nil { 1090 diffs = append(diffs, diff) 1091 } 1092 } 1093 } 1094 1095 sort.Sort(ObjectDiffs(diffs)) 1096 return diffs 1097 } 1098 1099 func connectGatewayTerminatingLinkedServiceDiff(prev, next *ConsulLinkedService, contextual bool) *ObjectDiff { 1100 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 1101 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1102 1103 if reflect.DeepEqual(prev, next) { 1104 return nil 1105 } else if prev == nil { 1106 diff.Type = DiffTypeAdded 1107 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1108 } else if next == nil { 1109 diff.Type = DiffTypeDeleted 1110 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1111 } else { 1112 diff.Type = DiffTypeEdited 1113 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1114 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1115 } 1116 1117 // Diff the primitive fields. 1118 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1119 1120 // No objects today. 1121 1122 return diff 1123 } 1124 1125 func connectGatewayTLSConfigDiff(prev, next *ConsulGatewayTLSConfig, contextual bool) *ObjectDiff { 1126 diff := &ObjectDiff{Type: DiffTypeNone, Name: "TLS"} 1127 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1128 1129 if reflect.DeepEqual(prev, next) { 1130 return nil 1131 } else if prev == nil { 1132 prev = &ConsulGatewayTLSConfig{} 1133 diff.Type = DiffTypeAdded 1134 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1135 } else if next == nil { 1136 next = &ConsulGatewayTLSConfig{} 1137 diff.Type = DiffTypeDeleted 1138 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1139 } else { 1140 diff.Type = DiffTypeEdited 1141 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1142 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1143 } 1144 1145 // CipherSuites diffs 1146 if setDiff := stringSetDiff(prev.CipherSuites, next.CipherSuites, "CipherSuites", contextual); setDiff != nil { 1147 diff.Objects = append(diff.Objects, setDiff) 1148 } 1149 1150 // Diff the primitive field. 1151 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1152 1153 return diff 1154 } 1155 1156 // connectGatewayIngressListenersDiff diffs are a set of listeners keyed by "protocol/port", which is 1157 // a nifty workaround having slices instead of maps. Presumably such a key will be unique, because if 1158 // if is not the config entry is not going to work anyway. 1159 func connectGatewayIngressListenersDiff(prev, next []*ConsulIngressListener, contextual bool) []*ObjectDiff { 1160 // create maps, diff the maps, keys are fields, keys are (port+protocol) 1161 1162 key := func(l *ConsulIngressListener) string { 1163 return fmt.Sprintf("%s/%d", l.Protocol, l.Port) 1164 } 1165 1166 prevMap := make(map[string]*ConsulIngressListener, len(prev)) 1167 nextMap := make(map[string]*ConsulIngressListener, len(next)) 1168 1169 for _, l := range prev { 1170 prevMap[key(l)] = l 1171 } 1172 for _, l := range next { 1173 nextMap[key(l)] = l 1174 } 1175 1176 var diffs []*ObjectDiff 1177 for k, prevL := range prevMap { 1178 // Diff the same, deleted, and edited 1179 if diff := connectGatewayIngressListenerDiff(prevL, nextMap[k], contextual); diff != nil { 1180 diffs = append(diffs, diff) 1181 } 1182 } 1183 for k, nextL := range nextMap { 1184 // Diff the added 1185 if old, ok := prevMap[k]; !ok { 1186 if diff := connectGatewayIngressListenerDiff(old, nextL, contextual); diff != nil { 1187 diffs = append(diffs, diff) 1188 } 1189 } 1190 } 1191 1192 sort.Sort(ObjectDiffs(diffs)) 1193 return diffs 1194 } 1195 1196 func connectGatewayIngressListenerDiff(prev, next *ConsulIngressListener, contextual bool) *ObjectDiff { 1197 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Listener"} 1198 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1199 1200 if reflect.DeepEqual(prev, next) { 1201 return nil 1202 } else if prev == nil { 1203 prev = new(ConsulIngressListener) 1204 diff.Type = DiffTypeAdded 1205 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1206 } else if next == nil { 1207 next = new(ConsulIngressListener) 1208 diff.Type = DiffTypeDeleted 1209 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1210 } else { 1211 diff.Type = DiffTypeEdited 1212 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1213 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1214 } 1215 1216 // Diff the primitive fields. 1217 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1218 1219 // Diff the Ingress Service objects. 1220 if diffs := connectGatewayIngressServicesDiff(prev.Services, next.Services, contextual); diffs != nil { 1221 diff.Objects = append(diff.Objects, diffs...) 1222 } 1223 1224 return diff 1225 } 1226 1227 // connectGatewayIngressServicesDiff diffs are a set of ingress services keyed by their service name, which 1228 // is a workaround for having slices instead of maps. Presumably the service name is a unique key, because if 1229 // no the config entry is not going to make sense anyway. 1230 func connectGatewayIngressServicesDiff(prev, next []*ConsulIngressService, contextual bool) []*ObjectDiff { 1231 1232 prevMap := make(map[string]*ConsulIngressService, len(prev)) 1233 nextMap := make(map[string]*ConsulIngressService, len(next)) 1234 1235 for _, s := range prev { 1236 prevMap[s.Name] = s 1237 } 1238 for _, s := range next { 1239 nextMap[s.Name] = s 1240 } 1241 1242 var diffs []*ObjectDiff 1243 for name, oldIS := range prevMap { 1244 // Diff the same, deleted, and edited 1245 if diff := connectGatewayIngressServiceDiff(oldIS, nextMap[name], contextual); diff != nil { 1246 diffs = append(diffs, diff) 1247 } 1248 } 1249 for name, newIS := range nextMap { 1250 // Diff the added 1251 if old, ok := prevMap[name]; !ok { 1252 if diff := connectGatewayIngressServiceDiff(old, newIS, contextual); diff != nil { 1253 diffs = append(diffs, diff) 1254 } 1255 } 1256 } 1257 1258 sort.Sort(ObjectDiffs(diffs)) 1259 return diffs 1260 } 1261 1262 func connectGatewayIngressServiceDiff(prev, next *ConsulIngressService, contextual bool) *ObjectDiff { 1263 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulIngressService"} 1264 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1265 1266 if reflect.DeepEqual(prev, next) { 1267 return nil 1268 } else if prev == nil { 1269 prev = new(ConsulIngressService) 1270 diff.Type = DiffTypeAdded 1271 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1272 } else if next == nil { 1273 next = new(ConsulIngressService) 1274 diff.Type = DiffTypeDeleted 1275 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1276 } else { 1277 diff.Type = DiffTypeEdited 1278 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1279 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1280 } 1281 1282 // Diff the primitive fields. 1283 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1284 1285 // Diff the hosts. 1286 if hDiffs := stringSetDiff(prev.Hosts, next.Hosts, "Hosts", contextual); hDiffs != nil { 1287 diff.Objects = append(diff.Objects, hDiffs) 1288 } 1289 1290 return diff 1291 } 1292 1293 func connectGatewayProxyDiff(prev, next *ConsulGatewayProxy, contextual bool) *ObjectDiff { 1294 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Proxy"} 1295 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1296 1297 if reflect.DeepEqual(prev, next) { 1298 return nil 1299 } else if prev == nil { 1300 prev = new(ConsulGatewayProxy) 1301 diff.Type = DiffTypeAdded 1302 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1303 } else if next == nil { 1304 next = new(ConsulGatewayProxy) 1305 diff.Type = DiffTypeDeleted 1306 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1307 } else { 1308 diff.Type = DiffTypeEdited 1309 oldPrimitiveFlat = flatmap.Flatten(prev, nil, true) 1310 newPrimitiveFlat = flatmap.Flatten(next, nil, true) 1311 } 1312 1313 // Diff the ConnectTimeout field (dur ptr). (i.e. convert to string for comparison) 1314 if oldPrimitiveFlat != nil && newPrimitiveFlat != nil { 1315 if prev.ConnectTimeout == nil { 1316 oldPrimitiveFlat["ConnectTimeout"] = "" 1317 } else { 1318 oldPrimitiveFlat["ConnectTimeout"] = prev.ConnectTimeout.String() 1319 } 1320 if next.ConnectTimeout == nil { 1321 newPrimitiveFlat["ConnectTimeout"] = "" 1322 } else { 1323 newPrimitiveFlat["ConnectTimeout"] = next.ConnectTimeout.String() 1324 } 1325 } 1326 1327 // Diff the primitive fields. 1328 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1329 1330 // Diff the EnvoyGatewayBindAddresses map. 1331 bindAddrsDiff := connectGatewayProxyEnvoyBindAddrsDiff(prev.EnvoyGatewayBindAddresses, next.EnvoyGatewayBindAddresses, contextual) 1332 if bindAddrsDiff != nil { 1333 diff.Objects = append(diff.Objects, bindAddrsDiff) 1334 } 1335 1336 // Diff the opaque Config map. 1337 if cDiff := configDiff(prev.Config, next.Config, contextual); cDiff != nil { 1338 diff.Objects = append(diff.Objects, cDiff) 1339 } 1340 1341 return diff 1342 } 1343 1344 // connectGatewayProxyEnvoyBindAddrsDiff returns the diff of two maps. If contextual 1345 // diff is enabled, all fields will be returned, even if no diff occurred. 1346 func connectGatewayProxyEnvoyBindAddrsDiff(prev, next map[string]*ConsulGatewayBindAddress, contextual bool) *ObjectDiff { 1347 diff := &ObjectDiff{Type: DiffTypeNone, Name: "EnvoyGatewayBindAddresses"} 1348 if reflect.DeepEqual(prev, next) { 1349 return nil 1350 } else if len(prev) == 0 { 1351 diff.Type = DiffTypeAdded 1352 } else if len(next) == 0 { 1353 diff.Type = DiffTypeDeleted 1354 } else { 1355 diff.Type = DiffTypeEdited 1356 } 1357 1358 // convert to string representation 1359 prevMap := make(map[string]string, len(prev)) 1360 nextMap := make(map[string]string, len(next)) 1361 1362 for k, v := range prev { 1363 prevMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port) 1364 } 1365 1366 for k, v := range next { 1367 nextMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port) 1368 } 1369 1370 oldPrimitiveFlat := flatmap.Flatten(prevMap, nil, false) 1371 newPrimitiveFlat := flatmap.Flatten(nextMap, nil, false) 1372 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1373 return diff 1374 } 1375 1376 // connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects. 1377 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 1378 func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff { 1379 diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"} 1380 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1381 1382 if reflect.DeepEqual(old, new) { 1383 return nil 1384 } else if old == nil { 1385 old = &ConsulSidecarService{} 1386 diff.Type = DiffTypeAdded 1387 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1388 } else if new == nil { 1389 new = &ConsulSidecarService{} 1390 diff.Type = DiffTypeDeleted 1391 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1392 } else { 1393 diff.Type = DiffTypeEdited 1394 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1395 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1396 } 1397 1398 // Diff the primitive fields. 1399 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1400 1401 consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual) 1402 if consulProxyDiff != nil { 1403 diff.Objects = append(diff.Objects, consulProxyDiff) 1404 } 1405 1406 return diff 1407 } 1408 1409 // sidecarTaskDiff returns the diff of two Task objects. 1410 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 1411 func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff { 1412 diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"} 1413 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1414 1415 if reflect.DeepEqual(old, new) { 1416 return nil 1417 } else if old == nil { 1418 old = &SidecarTask{} 1419 diff.Type = DiffTypeAdded 1420 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1421 } else if new == nil { 1422 new = &SidecarTask{} 1423 diff.Type = DiffTypeDeleted 1424 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1425 } else { 1426 diff.Type = DiffTypeEdited 1427 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1428 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1429 } 1430 1431 // Diff the primitive fields. 1432 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 1433 1434 // Config diff 1435 if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil { 1436 diff.Objects = append(diff.Objects, cDiff) 1437 } 1438 1439 // Resources diff 1440 if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil { 1441 diff.Objects = append(diff.Objects, rDiff) 1442 } 1443 1444 // LogConfig diff 1445 lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual) 1446 if lDiff != nil { 1447 diff.Objects = append(diff.Objects, lDiff) 1448 } 1449 1450 return diff 1451 } 1452 1453 // consulProxyDiff returns the diff of two ConsulProxy objects. 1454 // If contextual diff is enabled, all fields will be returned, even if no diff occurred. 1455 func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff { 1456 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"} 1457 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1458 1459 if reflect.DeepEqual(old, new) { 1460 return nil 1461 } else if old == nil { 1462 old = &ConsulProxy{} 1463 diff.Type = DiffTypeAdded 1464 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1465 } else if new == nil { 1466 new = &ConsulProxy{} 1467 diff.Type = DiffTypeDeleted 1468 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1469 } else { 1470 diff.Type = DiffTypeEdited 1471 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1472 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1473 } 1474 1475 // diff the primitive fields 1476 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1477 1478 // diff the consul upstream slices 1479 if upDiffs := consulProxyUpstreamsDiff(old.Upstreams, new.Upstreams, contextual); upDiffs != nil { 1480 diff.Objects = append(diff.Objects, upDiffs...) 1481 } 1482 1483 // diff the config blob 1484 if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil { 1485 diff.Objects = append(diff.Objects, cDiff) 1486 } 1487 1488 return diff 1489 } 1490 1491 // consulProxyUpstreamsDiff diffs a set of connect upstreams. If contextual diff is 1492 // enabled, unchanged fields within objects nested in the tasks will be returned. 1493 func consulProxyUpstreamsDiff(old, new []ConsulUpstream, contextual bool) []*ObjectDiff { 1494 oldMap := make(map[string]ConsulUpstream, len(old)) 1495 newMap := make(map[string]ConsulUpstream, len(new)) 1496 1497 idx := func(up ConsulUpstream) string { 1498 return fmt.Sprintf("%s/%s", up.Datacenter, up.DestinationName) 1499 } 1500 1501 for _, o := range old { 1502 oldMap[idx(o)] = o 1503 } 1504 for _, n := range new { 1505 newMap[idx(n)] = n 1506 } 1507 1508 var diffs []*ObjectDiff 1509 for index, oldUpstream := range oldMap { 1510 // Diff the same, deleted, and edited 1511 if diff := consulProxyUpstreamDiff(oldUpstream, newMap[index], contextual); diff != nil { 1512 diffs = append(diffs, diff) 1513 } 1514 } 1515 1516 for index, newUpstream := range newMap { 1517 // diff the added 1518 if oldUpstream, exists := oldMap[index]; !exists { 1519 if diff := consulProxyUpstreamDiff(oldUpstream, newUpstream, contextual); diff != nil { 1520 diffs = append(diffs, diff) 1521 } 1522 } 1523 } 1524 sort.Sort(ObjectDiffs(diffs)) 1525 return diffs 1526 } 1527 1528 func consulProxyUpstreamDiff(prev, next ConsulUpstream, contextual bool) *ObjectDiff { 1529 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulUpstreams"} 1530 var oldPrimFlat, newPrimFlat map[string]string 1531 1532 if reflect.DeepEqual(prev, next) { 1533 return nil 1534 } else if prev.Equal(new(ConsulUpstream)) { 1535 prev = ConsulUpstream{} 1536 diff.Type = DiffTypeAdded 1537 newPrimFlat = flatmap.Flatten(next, nil, true) 1538 } else if next.Equal(new(ConsulUpstream)) { 1539 next = ConsulUpstream{} 1540 diff.Type = DiffTypeDeleted 1541 oldPrimFlat = flatmap.Flatten(prev, nil, true) 1542 } else { 1543 diff.Type = DiffTypeEdited 1544 oldPrimFlat = flatmap.Flatten(prev, nil, true) 1545 newPrimFlat = flatmap.Flatten(next, nil, true) 1546 } 1547 1548 // diff the primitive fields 1549 diff.Fields = fieldDiffs(oldPrimFlat, newPrimFlat, contextual) 1550 1551 // diff the mesh gateway primitive object 1552 if mDiff := primitiveObjectDiff(prev.MeshGateway, next.MeshGateway, nil, "MeshGateway", contextual); mDiff != nil { 1553 diff.Objects = append(diff.Objects, mDiff) 1554 } 1555 1556 return diff 1557 } 1558 1559 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 1560 // enabled, unchanged fields within objects nested in the tasks will be 1561 // returned. 1562 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 1563 oldMap := make(map[string]*ServiceCheck, len(old)) 1564 newMap := make(map[string]*ServiceCheck, len(new)) 1565 for _, o := range old { 1566 oldMap[o.Name] = o 1567 } 1568 for _, n := range new { 1569 newMap[n.Name] = n 1570 } 1571 1572 var diffs []*ObjectDiff 1573 for name, oldCheck := range oldMap { 1574 // Diff the same, deleted and edited 1575 if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil { 1576 diffs = append(diffs, diff) 1577 } 1578 } 1579 1580 for name, newCheck := range newMap { 1581 // Diff the added 1582 if old, ok := oldMap[name]; !ok { 1583 if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil { 1584 diffs = append(diffs, diff) 1585 } 1586 } 1587 } 1588 1589 sort.Sort(ObjectDiffs(diffs)) 1590 return diffs 1591 } 1592 1593 // vaultDiff returns the diff of two vault objects. If contextual diff is 1594 // enabled, all fields will be returned, even if no diff occurred. 1595 func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff { 1596 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"} 1597 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1598 1599 if reflect.DeepEqual(old, new) { 1600 return nil 1601 } else if old == nil { 1602 old = &Vault{} 1603 diff.Type = DiffTypeAdded 1604 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1605 } else if new == nil { 1606 new = &Vault{} 1607 diff.Type = DiffTypeDeleted 1608 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1609 } else { 1610 diff.Type = DiffTypeEdited 1611 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1612 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1613 } 1614 1615 // Diff the primitive fields. 1616 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1617 1618 // Policies diffs 1619 if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil { 1620 diff.Objects = append(diff.Objects, setDiff) 1621 } 1622 1623 return diff 1624 } 1625 1626 // waitConfigDiff returns the diff of two WaitConfig objects. If contextual diff is 1627 // enabled, all fields will be returned, even if no diff occurred. 1628 func waitConfigDiff(old, new *WaitConfig, contextual bool) *ObjectDiff { 1629 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Template"} 1630 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1631 1632 if reflect.DeepEqual(old, new) { 1633 return nil 1634 } else if old == nil { 1635 diff.Type = DiffTypeAdded 1636 newPrimitiveFlat = flatmap.Flatten(new, nil, false) 1637 } else if new == nil { 1638 diff.Type = DiffTypeDeleted 1639 oldPrimitiveFlat = flatmap.Flatten(old, nil, false) 1640 } else { 1641 diff.Type = DiffTypeEdited 1642 oldPrimitiveFlat = flatmap.Flatten(old, nil, false) 1643 newPrimitiveFlat = flatmap.Flatten(new, nil, false) 1644 } 1645 1646 // Diff the primitive fields. 1647 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1648 1649 return diff 1650 } 1651 1652 // changeScriptDiff returns the diff of two ChangeScript objects. If contextual 1653 // diff is enabled, all fields will be returned, even if no diff occurred. 1654 func changeScriptDiff(old, new *ChangeScript, contextual bool) *ObjectDiff { 1655 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ChangeScript"} 1656 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1657 1658 if reflect.DeepEqual(old, new) { 1659 return nil 1660 } else if old == nil { 1661 old = &ChangeScript{} 1662 diff.Type = DiffTypeAdded 1663 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1664 } else if new == nil { 1665 new = &ChangeScript{} 1666 diff.Type = DiffTypeDeleted 1667 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1668 } else { 1669 diff.Type = DiffTypeEdited 1670 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1671 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1672 } 1673 1674 // Diff the primitive fields. 1675 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1676 1677 // Args diffs 1678 if setDiff := stringSetDiff(old.Args, new.Args, "Args", contextual); setDiff != nil { 1679 diff.Objects = append(diff.Objects, setDiff) 1680 } 1681 1682 return diff 1683 } 1684 1685 // templateDiff returns the diff of two Consul Template objects. If contextual diff is 1686 // enabled, all fields will be returned, even if no diff occurred. 1687 func templateDiff(old, new *Template, contextual bool) *ObjectDiff { 1688 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Template"} 1689 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1690 1691 if reflect.DeepEqual(old, new) { 1692 return nil 1693 } else if old == nil { 1694 old = &Template{} 1695 diff.Type = DiffTypeAdded 1696 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1697 } else if new == nil { 1698 new = &Template{} 1699 diff.Type = DiffTypeDeleted 1700 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1701 } else { 1702 diff.Type = DiffTypeEdited 1703 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1704 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1705 } 1706 1707 // Add the pointer primitive fields. 1708 if old != nil { 1709 if old.Uid != nil { 1710 oldPrimitiveFlat["Uid"] = fmt.Sprintf("%v", *old.Uid) 1711 } 1712 if old.Gid != nil { 1713 oldPrimitiveFlat["Gid"] = fmt.Sprintf("%v", *old.Gid) 1714 } 1715 } 1716 if new != nil { 1717 if new.Uid != nil { 1718 newPrimitiveFlat["Uid"] = fmt.Sprintf("%v", *new.Uid) 1719 } 1720 if new.Gid != nil { 1721 newPrimitiveFlat["Gid"] = fmt.Sprintf("%v", *new.Gid) 1722 } 1723 } 1724 1725 // Diff the primitive fields. 1726 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1727 1728 // WaitConfig diffs 1729 if waitDiffs := waitConfigDiff(old.Wait, new.Wait, contextual); waitDiffs != nil { 1730 diff.Objects = append(diff.Objects, waitDiffs) 1731 } 1732 1733 // ChangeScript diffs 1734 if changeScriptDiffs := changeScriptDiff( 1735 old.ChangeScript, new.ChangeScript, contextual, 1736 ); changeScriptDiffs != nil { 1737 diff.Objects = append(diff.Objects, changeScriptDiffs) 1738 } 1739 1740 return diff 1741 } 1742 1743 // templateDiffs returns the diff of two Consul Template slices. If contextual diff is 1744 // enabled, all fields will be returned, even if no diff occurred. 1745 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 1746 // fields within objects nested in the tasks will be returned. 1747 func templateDiffs(old, new []*Template, contextual bool) []*ObjectDiff { 1748 // Handle trivial case. 1749 if len(old) == 1 && len(new) == 1 { 1750 if diff := templateDiff(old[0], new[0], contextual); diff != nil { 1751 return []*ObjectDiff{diff} 1752 } 1753 return nil 1754 } 1755 1756 // For each template we will try to find a corresponding match in the other list. 1757 // The following lists store the index of the matching template for each 1758 // position of the inputs. 1759 oldMatches := make([]int, len(old)) 1760 newMatches := make([]int, len(new)) 1761 1762 // Initialize all templates as unmatched. 1763 for i := range oldMatches { 1764 oldMatches[i] = -1 1765 } 1766 for i := range newMatches { 1767 newMatches[i] = -1 1768 } 1769 1770 // Find a match in the new templates list for each old template and compute 1771 // their diffs. 1772 var diffs []*ObjectDiff 1773 for oldIndex, oldTemplate := range old { 1774 newIndex := findTemplateMatch(oldTemplate, new, newMatches) 1775 1776 // Old templates that don't have a match were deleted. 1777 if newIndex < 0 { 1778 diff := templateDiff(oldTemplate, nil, contextual) 1779 diffs = append(diffs, diff) 1780 continue 1781 } 1782 1783 // If A matches B then B matches A. 1784 oldMatches[oldIndex] = newIndex 1785 newMatches[newIndex] = oldIndex 1786 1787 newTemplate := new[newIndex] 1788 if diff := templateDiff(oldTemplate, newTemplate, contextual); diff != nil { 1789 diffs = append(diffs, diff) 1790 } 1791 } 1792 1793 // New templates without match were added. 1794 for i, m := range newMatches { 1795 if m == -1 { 1796 diff := templateDiff(nil, new[i], contextual) 1797 diffs = append(diffs, diff) 1798 } 1799 } 1800 1801 sort.Sort(ObjectDiffs(diffs)) 1802 return diffs 1803 } 1804 1805 func findTemplateMatch(template *Template, newTemplates []*Template, newTemplateMatches []int) int { 1806 indexMatch := -1 1807 1808 for i, newTemplate := range newTemplates { 1809 // Skip template if it's already matched. 1810 if newTemplateMatches[i] >= 0 { 1811 continue 1812 } 1813 1814 if template.DiffID() == newTemplate.DiffID() { 1815 indexMatch = i 1816 break 1817 } 1818 } 1819 1820 return indexMatch 1821 } 1822 1823 // parameterizedJobDiff returns the diff of two parameterized job objects. If 1824 // contextual diff is enabled, all fields will be returned, even if no diff 1825 // occurred. 1826 func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff { 1827 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"} 1828 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1829 1830 if reflect.DeepEqual(old, new) { 1831 return nil 1832 } else if old == nil { 1833 old = &ParameterizedJobConfig{} 1834 diff.Type = DiffTypeAdded 1835 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1836 } else if new == nil { 1837 new = &ParameterizedJobConfig{} 1838 diff.Type = DiffTypeDeleted 1839 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1840 } else { 1841 diff.Type = DiffTypeEdited 1842 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 1843 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 1844 } 1845 1846 // Diff the primitive fields. 1847 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1848 1849 // Meta diffs 1850 if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil { 1851 diff.Objects = append(diff.Objects, optionalDiff) 1852 } 1853 1854 if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil { 1855 diff.Objects = append(diff.Objects, requiredDiff) 1856 } 1857 1858 return diff 1859 } 1860 1861 func multiregionDiff(old, new *Multiregion, contextual bool) *ObjectDiff { 1862 1863 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Multiregion"} 1864 1865 if reflect.DeepEqual(old, new) { 1866 return nil 1867 } else if old == nil { 1868 old = &Multiregion{} 1869 old.Canonicalize() 1870 diff.Type = DiffTypeAdded 1871 } else if new == nil { 1872 new = &Multiregion{} 1873 diff.Type = DiffTypeDeleted 1874 } else { 1875 diff.Type = DiffTypeEdited 1876 } 1877 1878 // strategy diff 1879 stratDiff := primitiveObjectDiff( 1880 old.Strategy, 1881 new.Strategy, 1882 []string{}, 1883 "Strategy", 1884 contextual) 1885 if stratDiff != nil { 1886 diff.Objects = append(diff.Objects, stratDiff) 1887 } 1888 1889 oldMap := make(map[string]*MultiregionRegion, len(old.Regions)) 1890 newMap := make(map[string]*MultiregionRegion, len(new.Regions)) 1891 for _, o := range old.Regions { 1892 oldMap[o.Name] = o 1893 } 1894 for _, n := range new.Regions { 1895 newMap[n.Name] = n 1896 } 1897 1898 for name, oldRegion := range oldMap { 1899 // Diff the same, deleted and edited 1900 newRegion := newMap[name] 1901 rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual) 1902 if rdiff != nil { 1903 diff.Objects = append(diff.Objects, rdiff) 1904 } 1905 } 1906 1907 for name, newRegion := range newMap { 1908 // Diff the added 1909 if oldRegion, ok := oldMap[name]; !ok { 1910 rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual) 1911 if rdiff != nil { 1912 diff.Objects = append(diff.Objects, rdiff) 1913 } 1914 } 1915 } 1916 sort.Sort(FieldDiffs(diff.Fields)) 1917 sort.Sort(ObjectDiffs(diff.Objects)) 1918 return diff 1919 } 1920 1921 func multiregionRegionDiff(r, other *MultiregionRegion, contextual bool) *ObjectDiff { 1922 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Region"} 1923 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 1924 1925 if reflect.DeepEqual(r, other) { 1926 return nil 1927 } else if r == nil { 1928 r = &MultiregionRegion{} 1929 diff.Type = DiffTypeAdded 1930 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1931 } else if other == nil { 1932 other = &MultiregionRegion{} 1933 diff.Type = DiffTypeDeleted 1934 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1935 } else { 1936 diff.Type = DiffTypeEdited 1937 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 1938 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 1939 } 1940 1941 // Diff the primitive fields. 1942 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1943 1944 // Datacenters diff 1945 setDiff := stringSetDiff(r.Datacenters, other.Datacenters, "Datacenters", contextual) 1946 if setDiff != nil && setDiff.Type != DiffTypeNone { 1947 diff.Objects = append(diff.Objects, setDiff) 1948 } 1949 1950 sort.Sort(ObjectDiffs(diff.Objects)) 1951 sort.Sort(FieldDiffs(diff.Fields)) 1952 1953 var added, deleted, edited bool 1954 Loop: 1955 for _, f := range diff.Fields { 1956 switch f.Type { 1957 case DiffTypeEdited: 1958 edited = true 1959 break Loop 1960 case DiffTypeDeleted: 1961 deleted = true 1962 case DiffTypeAdded: 1963 added = true 1964 } 1965 } 1966 1967 if edited || added && deleted { 1968 diff.Type = DiffTypeEdited 1969 } else if added { 1970 diff.Type = DiffTypeAdded 1971 } else if deleted { 1972 diff.Type = DiffTypeDeleted 1973 } else { 1974 return nil 1975 } 1976 1977 return diff 1978 } 1979 1980 // volumeDiffs returns the diff of a group's volume requests. If contextual 1981 // diff is enabled, all fields will be returned, even if no diff occurred. 1982 func volumeDiffs(oldVR, newVR map[string]*VolumeRequest, contextual bool) []*ObjectDiff { 1983 if reflect.DeepEqual(oldVR, newVR) { 1984 return nil 1985 } 1986 1987 diffs := []*ObjectDiff{} //Type: DiffTypeNone, Name: "Volumes"} 1988 seen := map[string]bool{} 1989 for name, oReq := range oldVR { 1990 nReq := newVR[name] // might be nil, that's ok 1991 seen[name] = true 1992 diff := volumeDiff(oReq, nReq, contextual) 1993 if diff != nil { 1994 diffs = append(diffs, diff) 1995 } 1996 } 1997 for name, nReq := range newVR { 1998 if !seen[name] { 1999 // we know old is nil at this point, or we'd have hit it before 2000 diff := volumeDiff(nil, nReq, contextual) 2001 if diff != nil { 2002 diffs = append(diffs, diff) 2003 } 2004 } 2005 } 2006 return diffs 2007 } 2008 2009 // volumeDiff returns the diff between two volume requests. If contextual diff 2010 // is enabled, all fields will be returned, even if no diff occurred. 2011 func volumeDiff(oldVR, newVR *VolumeRequest, contextual bool) *ObjectDiff { 2012 if reflect.DeepEqual(oldVR, newVR) { 2013 return nil 2014 } 2015 2016 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Volume"} 2017 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2018 2019 if oldVR == nil { 2020 oldVR = &VolumeRequest{} 2021 diff.Type = DiffTypeAdded 2022 newPrimitiveFlat = flatmap.Flatten(newVR, nil, true) 2023 } else if newVR == nil { 2024 newVR = &VolumeRequest{} 2025 diff.Type = DiffTypeDeleted 2026 oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true) 2027 } else { 2028 diff.Type = DiffTypeEdited 2029 oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true) 2030 newPrimitiveFlat = flatmap.Flatten(newVR, nil, true) 2031 } 2032 2033 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2034 2035 mOptsDiff := volumeCSIMountOptionsDiff(oldVR.MountOptions, newVR.MountOptions, contextual) 2036 if mOptsDiff != nil { 2037 diff.Objects = append(diff.Objects, mOptsDiff) 2038 } 2039 2040 return diff 2041 } 2042 2043 // volumeCSIMountOptionsDiff returns the diff between volume mount options. If 2044 // contextual diff is enabled, all fields will be returned, even if no diff 2045 // occurred. 2046 func volumeCSIMountOptionsDiff(oldMO, newMO *CSIMountOptions, contextual bool) *ObjectDiff { 2047 if reflect.DeepEqual(oldMO, newMO) { 2048 return nil 2049 } 2050 2051 diff := &ObjectDiff{Type: DiffTypeNone, Name: "MountOptions"} 2052 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2053 2054 if oldMO == nil && newMO != nil { 2055 oldMO = &CSIMountOptions{} 2056 diff.Type = DiffTypeAdded 2057 newPrimitiveFlat = flatmap.Flatten(newMO, nil, true) 2058 } else if oldMO != nil && newMO == nil { 2059 newMO = &CSIMountOptions{} 2060 diff.Type = DiffTypeDeleted 2061 oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true) 2062 } else { 2063 diff.Type = DiffTypeEdited 2064 oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true) 2065 newPrimitiveFlat = flatmap.Flatten(newMO, nil, true) 2066 } 2067 2068 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2069 2070 setDiff := stringSetDiff(oldMO.MountFlags, newMO.MountFlags, "MountFlags", contextual) 2071 if setDiff != nil { 2072 diff.Objects = append(diff.Objects, setDiff) 2073 } 2074 return diff 2075 } 2076 2077 // Diff returns a diff of two resource objects. If contextual diff is enabled, 2078 // non-changed fields will still be returned. 2079 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 2080 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 2081 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2082 2083 if reflect.DeepEqual(r, other) { 2084 return nil 2085 } else if r == nil { 2086 r = &Resources{} 2087 diff.Type = DiffTypeAdded 2088 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 2089 } else if other == nil { 2090 other = &Resources{} 2091 diff.Type = DiffTypeDeleted 2092 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 2093 } else { 2094 diff.Type = DiffTypeEdited 2095 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 2096 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 2097 } 2098 2099 // Diff the primitive fields. 2100 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2101 2102 // Network Resources diff 2103 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 2104 diff.Objects = append(diff.Objects, nDiffs...) 2105 } 2106 2107 // Requested Devices diff 2108 if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil { 2109 diff.Objects = append(diff.Objects, nDiffs...) 2110 } 2111 2112 return diff 2113 } 2114 2115 // Diff returns a diff of two network resources. If contextual diff is enabled, 2116 // non-changed fields will still be returned. 2117 func (n *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 2118 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 2119 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2120 filter := []string{"Device", "CIDR", "IP"} 2121 2122 if reflect.DeepEqual(n, other) { 2123 return nil 2124 } else if n == nil { 2125 n = &NetworkResource{} 2126 diff.Type = DiffTypeAdded 2127 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 2128 } else if other == nil { 2129 other = &NetworkResource{} 2130 diff.Type = DiffTypeDeleted 2131 oldPrimitiveFlat = flatmap.Flatten(n, filter, true) 2132 } else { 2133 diff.Type = DiffTypeEdited 2134 oldPrimitiveFlat = flatmap.Flatten(n, filter, true) 2135 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 2136 } 2137 2138 // Diff the primitive fields. 2139 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2140 2141 // Port diffs 2142 resPorts := portDiffs(n.ReservedPorts, other.ReservedPorts, false, contextual) 2143 dynPorts := portDiffs(n.DynamicPorts, other.DynamicPorts, true, contextual) 2144 if resPorts != nil { 2145 diff.Objects = append(diff.Objects, resPorts...) 2146 } 2147 if dynPorts != nil { 2148 diff.Objects = append(diff.Objects, dynPorts...) 2149 } 2150 2151 if dnsDiff := n.DNS.Diff(other.DNS, contextual); dnsDiff != nil { 2152 diff.Objects = append(diff.Objects, dnsDiff) 2153 } 2154 2155 return diff 2156 } 2157 2158 // Diff returns a diff of two DNSConfig structs 2159 func (d *DNSConfig) Diff(other *DNSConfig, contextual bool) *ObjectDiff { 2160 if reflect.DeepEqual(d, other) { 2161 return nil 2162 } 2163 2164 flatten := func(conf *DNSConfig) map[string]string { 2165 m := map[string]string{} 2166 if len(conf.Servers) > 0 { 2167 m["Servers"] = strings.Join(conf.Servers, ",") 2168 } 2169 if len(conf.Searches) > 0 { 2170 m["Searches"] = strings.Join(conf.Searches, ",") 2171 } 2172 if len(conf.Options) > 0 { 2173 m["Options"] = strings.Join(conf.Options, ",") 2174 } 2175 return m 2176 } 2177 2178 diff := &ObjectDiff{Type: DiffTypeNone, Name: "DNS"} 2179 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2180 if d == nil { 2181 diff.Type = DiffTypeAdded 2182 newPrimitiveFlat = flatten(other) 2183 } else if other == nil { 2184 diff.Type = DiffTypeDeleted 2185 oldPrimitiveFlat = flatten(d) 2186 } else { 2187 diff.Type = DiffTypeEdited 2188 oldPrimitiveFlat = flatten(d) 2189 newPrimitiveFlat = flatten(other) 2190 } 2191 2192 // Diff the primitive fields. 2193 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2194 2195 return diff 2196 } 2197 2198 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 2199 // non-changed fields will still be returned. 2200 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 2201 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 2202 objMap := make(map[string]*NetworkResource, len(objects)) 2203 for _, obj := range objects { 2204 hash, err := hashstructure.Hash(obj, nil) 2205 if err != nil { 2206 panic(err) 2207 } 2208 objMap[fmt.Sprintf("%d", hash)] = obj 2209 } 2210 2211 return objMap 2212 } 2213 2214 oldSet := makeSet(old) 2215 newSet := makeSet(new) 2216 2217 var diffs []*ObjectDiff 2218 for k, oldV := range oldSet { 2219 if newV, ok := newSet[k]; !ok { 2220 if diff := oldV.Diff(newV, contextual); diff != nil { 2221 diffs = append(diffs, diff) 2222 } 2223 } 2224 } 2225 for k, newV := range newSet { 2226 if oldV, ok := oldSet[k]; !ok { 2227 if diff := oldV.Diff(newV, contextual); diff != nil { 2228 diffs = append(diffs, diff) 2229 } 2230 } 2231 } 2232 2233 sort.Sort(ObjectDiffs(diffs)) 2234 return diffs 2235 2236 } 2237 2238 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 2239 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 2240 // non-changed fields will still be returned. 2241 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 2242 makeSet := func(ports []Port) map[string]Port { 2243 portMap := make(map[string]Port, len(ports)) 2244 for _, port := range ports { 2245 portMap[port.Label] = port 2246 } 2247 2248 return portMap 2249 } 2250 2251 oldPorts := makeSet(old) 2252 newPorts := makeSet(new) 2253 2254 var filter []string 2255 name := "Static Port" 2256 if dynamic { 2257 filter = []string{"Value"} 2258 name = "Dynamic Port" 2259 } 2260 2261 var diffs []*ObjectDiff 2262 for portLabel, oldPort := range oldPorts { 2263 // Diff the same, deleted and edited 2264 if newPort, ok := newPorts[portLabel]; ok { 2265 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 2266 if diff != nil { 2267 diffs = append(diffs, diff) 2268 } 2269 } else { 2270 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 2271 if diff != nil { 2272 diffs = append(diffs, diff) 2273 } 2274 } 2275 } 2276 for label, newPort := range newPorts { 2277 // Diff the added 2278 if _, ok := oldPorts[label]; !ok { 2279 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 2280 if diff != nil { 2281 diffs = append(diffs, diff) 2282 } 2283 } 2284 } 2285 2286 sort.Sort(ObjectDiffs(diffs)) 2287 return diffs 2288 2289 } 2290 2291 // Diff returns a diff of two requested devices. If contextual diff is enabled, 2292 // non-changed fields will still be returned. 2293 func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff { 2294 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"} 2295 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 2296 2297 if reflect.DeepEqual(r, other) { 2298 return nil 2299 } else if r == nil { 2300 diff.Type = DiffTypeAdded 2301 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 2302 } else if other == nil { 2303 diff.Type = DiffTypeDeleted 2304 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 2305 } else { 2306 diff.Type = DiffTypeEdited 2307 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 2308 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 2309 } 2310 2311 // Diff the primitive fields. 2312 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2313 2314 return diff 2315 } 2316 2317 // requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled, 2318 // non-changed fields will still be returned. 2319 func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff { 2320 makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice { 2321 deviceMap := make(map[string]*RequestedDevice, len(devices)) 2322 for _, d := range devices { 2323 deviceMap[d.Name] = d 2324 } 2325 2326 return deviceMap 2327 } 2328 2329 oldSet := makeSet(old) 2330 newSet := makeSet(new) 2331 2332 var diffs []*ObjectDiff 2333 for k, oldV := range oldSet { 2334 newV := newSet[k] 2335 if diff := oldV.Diff(newV, contextual); diff != nil { 2336 diffs = append(diffs, diff) 2337 } 2338 } 2339 for k, newV := range newSet { 2340 if oldV, ok := oldSet[k]; !ok { 2341 if diff := oldV.Diff(newV, contextual); diff != nil { 2342 diffs = append(diffs, diff) 2343 } 2344 } 2345 } 2346 2347 sort.Sort(ObjectDiffs(diffs)) 2348 return diffs 2349 2350 } 2351 2352 // configDiff returns the diff of two Task Config objects. If contextual diff is 2353 // enabled, all fields will be returned, even if no diff occurred. 2354 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 2355 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 2356 if reflect.DeepEqual(old, new) { 2357 return nil 2358 } else if len(old) == 0 { 2359 diff.Type = DiffTypeAdded 2360 } else if len(new) == 0 { 2361 diff.Type = DiffTypeDeleted 2362 } else { 2363 diff.Type = DiffTypeEdited 2364 } 2365 2366 // Diff the primitive fields. 2367 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 2368 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 2369 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2370 return diff 2371 } 2372 2373 // ObjectDiff contains the diff of two generic objects. 2374 type ObjectDiff struct { 2375 Type DiffType 2376 Name string 2377 Fields []*FieldDiff 2378 Objects []*ObjectDiff 2379 } 2380 2381 func (o *ObjectDiff) GoString() string { 2382 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 2383 for _, f := range o.Fields { 2384 out += fmt.Sprintf("%#v\n", f) 2385 } 2386 for _, o := range o.Objects { 2387 out += fmt.Sprintf("%#v\n", o) 2388 } 2389 out += "}" 2390 return out 2391 } 2392 2393 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 2394 if reflect.DeepEqual(o, other) { 2395 return false 2396 } else if other == nil { 2397 return false 2398 } else if o == nil { 2399 return true 2400 } 2401 2402 if o.Name != other.Name { 2403 return o.Name < other.Name 2404 } 2405 2406 if o.Type != other.Type { 2407 return o.Type.Less(other.Type) 2408 } 2409 2410 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 2411 return lO < lOther 2412 } 2413 2414 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 2415 return lO < lOther 2416 } 2417 2418 // Check each field 2419 sort.Sort(FieldDiffs(o.Fields)) 2420 sort.Sort(FieldDiffs(other.Fields)) 2421 2422 for i, oV := range o.Fields { 2423 if oV.Less(other.Fields[i]) { 2424 return true 2425 } 2426 } 2427 2428 // Check each object 2429 sort.Sort(ObjectDiffs(o.Objects)) 2430 sort.Sort(ObjectDiffs(other.Objects)) 2431 for i, oV := range o.Objects { 2432 if oV.Less(other.Objects[i]) { 2433 return true 2434 } 2435 } 2436 2437 return false 2438 } 2439 2440 // For sorting ObjectDiffs 2441 type ObjectDiffs []*ObjectDiff 2442 2443 func (o ObjectDiffs) Len() int { return len(o) } 2444 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 2445 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 2446 2447 type FieldDiff struct { 2448 Type DiffType 2449 Name string 2450 Old, New string 2451 Annotations []string 2452 } 2453 2454 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 2455 // returns nil. If contextual diff is enabled, even non-changed fields will be 2456 // returned. 2457 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 2458 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 2459 if old == new { 2460 if !contextual { 2461 return nil 2462 } 2463 diff.Old, diff.New = old, new 2464 return diff 2465 } 2466 2467 if old == "" { 2468 diff.Type = DiffTypeAdded 2469 diff.New = new 2470 } else if new == "" { 2471 diff.Type = DiffTypeDeleted 2472 diff.Old = old 2473 } else { 2474 diff.Type = DiffTypeEdited 2475 diff.Old = old 2476 diff.New = new 2477 } 2478 return diff 2479 } 2480 2481 func (f *FieldDiff) GoString() string { 2482 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 2483 if len(f.Annotations) != 0 { 2484 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 2485 } 2486 2487 return out 2488 } 2489 2490 func (f *FieldDiff) Less(other *FieldDiff) bool { 2491 if reflect.DeepEqual(f, other) { 2492 return false 2493 } else if other == nil { 2494 return false 2495 } else if f == nil { 2496 return true 2497 } 2498 2499 if f.Name != other.Name { 2500 return f.Name < other.Name 2501 } else if f.Old != other.Old { 2502 return f.Old < other.Old 2503 } 2504 2505 return f.New < other.New 2506 } 2507 2508 // For sorting FieldDiffs 2509 type FieldDiffs []*FieldDiff 2510 2511 func (f FieldDiffs) Len() int { return len(f) } 2512 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 2513 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 2514 2515 // fieldDiffs takes a map of field names to their values and returns a set of 2516 // field diffs. If contextual diff is enabled, even non-changed fields will be 2517 // returned. 2518 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 2519 var diffs []*FieldDiff 2520 visited := make(map[string]struct{}) 2521 for k, oldV := range old { 2522 visited[k] = struct{}{} 2523 newV := new[k] 2524 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 2525 diffs = append(diffs, diff) 2526 } 2527 } 2528 2529 for k, newV := range new { 2530 if _, ok := visited[k]; !ok { 2531 if diff := fieldDiff("", newV, k, contextual); diff != nil { 2532 diffs = append(diffs, diff) 2533 } 2534 } 2535 } 2536 2537 sort.Sort(FieldDiffs(diffs)) 2538 return diffs 2539 } 2540 2541 // stringSetDiff diffs two sets of strings with the given name. 2542 func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff { 2543 oldMap := make(map[string]struct{}, len(old)) 2544 newMap := make(map[string]struct{}, len(new)) 2545 for _, o := range old { 2546 oldMap[o] = struct{}{} 2547 } 2548 for _, n := range new { 2549 newMap[n] = struct{}{} 2550 } 2551 if reflect.DeepEqual(oldMap, newMap) && !contextual { 2552 return nil 2553 } 2554 2555 diff := &ObjectDiff{Name: name} 2556 var added, removed bool 2557 for k := range oldMap { 2558 if _, ok := newMap[k]; !ok { 2559 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual)) 2560 removed = true 2561 } else if contextual { 2562 diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual)) 2563 } 2564 } 2565 2566 for k := range newMap { 2567 if _, ok := oldMap[k]; !ok { 2568 diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual)) 2569 added = true 2570 } 2571 } 2572 2573 sort.Sort(FieldDiffs(diff.Fields)) 2574 2575 // Determine the type 2576 if added && removed { 2577 diff.Type = DiffTypeEdited 2578 } else if added { 2579 diff.Type = DiffTypeAdded 2580 } else if removed { 2581 diff.Type = DiffTypeDeleted 2582 } else { 2583 // Diff of an empty set 2584 if len(diff.Fields) == 0 { 2585 return nil 2586 } 2587 2588 diff.Type = DiffTypeNone 2589 } 2590 2591 return diff 2592 } 2593 2594 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 2595 // The filter field can be used to exclude fields from the diff. The name is the 2596 // name of the objects. If contextual is set, non-changed fields will also be 2597 // stored in the object diff. 2598 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 2599 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 2600 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 2601 delete(oldPrimitiveFlat, "") 2602 delete(newPrimitiveFlat, "") 2603 2604 diff := &ObjectDiff{Name: name} 2605 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 2606 2607 var added, deleted, edited bool 2608 Loop: 2609 for _, f := range diff.Fields { 2610 switch f.Type { 2611 case DiffTypeEdited: 2612 edited = true 2613 break Loop 2614 case DiffTypeDeleted: 2615 deleted = true 2616 case DiffTypeAdded: 2617 added = true 2618 } 2619 } 2620 2621 if edited || added && deleted { 2622 diff.Type = DiffTypeEdited 2623 } else if added { 2624 diff.Type = DiffTypeAdded 2625 } else if deleted { 2626 diff.Type = DiffTypeDeleted 2627 } else { 2628 return nil 2629 } 2630 2631 return diff 2632 } 2633 2634 // primitiveObjectSetDiff does a set difference of the old and new sets. The 2635 // filter parameter can be used to filter a set of primitive fields in the 2636 // passed structs. The name corresponds to the name of the passed objects. If 2637 // contextual diff is enabled, objects' primitive fields will be returned even if 2638 // no diff exists. 2639 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 2640 makeSet := func(objects []interface{}) map[string]interface{} { 2641 objMap := make(map[string]interface{}, len(objects)) 2642 for _, obj := range objects { 2643 var key string 2644 2645 if diffable, ok := obj.(DiffableWithID); ok { 2646 key = diffable.DiffID() 2647 } 2648 2649 if key == "" { 2650 hash, err := hashstructure.Hash(obj, nil) 2651 if err != nil { 2652 panic(err) 2653 } 2654 key = fmt.Sprintf("%d", hash) 2655 } 2656 objMap[key] = obj 2657 } 2658 2659 return objMap 2660 } 2661 2662 oldSet := makeSet(old) 2663 newSet := makeSet(new) 2664 2665 var diffs []*ObjectDiff 2666 for k, oldObj := range oldSet { 2667 newObj := newSet[k] 2668 diff := primitiveObjectDiff(oldObj, newObj, filter, name, contextual) 2669 if diff != nil { 2670 diffs = append(diffs, diff) 2671 } 2672 } 2673 for k, v := range newSet { 2674 // Added 2675 if _, ok := oldSet[k]; !ok { 2676 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 2677 } 2678 } 2679 2680 sort.Sort(ObjectDiffs(diffs)) 2681 return diffs 2682 } 2683 2684 // interfaceSlice is a helper method that takes a slice of typed elements and 2685 // returns a slice of interface. This method will panic if given a non-slice 2686 // input. 2687 func interfaceSlice(slice interface{}) []interface{} { 2688 s := reflect.ValueOf(slice) 2689 if s.Kind() != reflect.Slice { 2690 panic("InterfaceSlice() given a non-slice type") 2691 } 2692 2693 ret := make([]interface{}, s.Len()) 2694 2695 for i := 0; i < s.Len(); i++ { 2696 ret[i] = s.Index(i).Interface() 2697 } 2698 2699 return ret 2700 }