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