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