github.com/quite/nomad@v0.8.6/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 // COMPAT: Remove "Update" in 0.7.0. Update pushed down to task groups 61 // in 0.6.0 62 diff := &JobDiff{Type: DiffTypeNone} 63 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 64 filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex", 65 "ModifyIndex", "JobModifyIndex", "Update", "SubmitTime"} 66 67 if j == nil && other == nil { 68 return diff, nil 69 } else if j == nil { 70 j = &Job{} 71 diff.Type = DiffTypeAdded 72 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 73 diff.ID = other.ID 74 } else if other == nil { 75 other = &Job{} 76 diff.Type = DiffTypeDeleted 77 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 78 diff.ID = j.ID 79 } else { 80 if j.ID != other.ID { 81 return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID) 82 } 83 84 oldPrimitiveFlat = flatmap.Flatten(j, filter, true) 85 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 86 diff.ID = other.ID 87 } 88 89 // Diff the primitive fields. 90 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 91 92 // Datacenters diff 93 if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone { 94 diff.Objects = append(diff.Objects, setDiff) 95 } 96 97 // Constraints diff 98 conDiff := primitiveObjectSetDiff( 99 interfaceSlice(j.Constraints), 100 interfaceSlice(other.Constraints), 101 []string{"str"}, 102 "Constraint", 103 contextual) 104 if conDiff != nil { 105 diff.Objects = append(diff.Objects, conDiff...) 106 } 107 108 // Task groups diff 109 tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual) 110 if err != nil { 111 return nil, err 112 } 113 diff.TaskGroups = tgs 114 115 // Periodic diff 116 if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil { 117 diff.Objects = append(diff.Objects, pDiff) 118 } 119 120 // ParameterizedJob diff 121 if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil { 122 diff.Objects = append(diff.Objects, cDiff) 123 } 124 125 // Check to see if there is a diff. We don't use reflect because we are 126 // filtering quite a few fields that will change on each diff. 127 if diff.Type == DiffTypeNone { 128 for _, fd := range diff.Fields { 129 if fd.Type != DiffTypeNone { 130 diff.Type = DiffTypeEdited 131 break 132 } 133 } 134 } 135 136 if diff.Type == DiffTypeNone { 137 for _, od := range diff.Objects { 138 if od.Type != DiffTypeNone { 139 diff.Type = DiffTypeEdited 140 break 141 } 142 } 143 } 144 145 if diff.Type == DiffTypeNone { 146 for _, tg := range diff.TaskGroups { 147 if tg.Type != DiffTypeNone { 148 diff.Type = DiffTypeEdited 149 break 150 } 151 } 152 } 153 154 return diff, nil 155 } 156 157 func (j *JobDiff) GoString() string { 158 out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type) 159 160 for _, f := range j.Fields { 161 out += fmt.Sprintf("%#v\n", f) 162 } 163 164 for _, o := range j.Objects { 165 out += fmt.Sprintf("%#v\n", o) 166 } 167 168 for _, tg := range j.TaskGroups { 169 out += fmt.Sprintf("%#v\n", tg) 170 } 171 172 return out 173 } 174 175 // TaskGroupDiff contains the diff of two task groups. 176 type TaskGroupDiff struct { 177 Type DiffType 178 Name string 179 Fields []*FieldDiff 180 Objects []*ObjectDiff 181 Tasks []*TaskDiff 182 Updates map[string]uint64 183 } 184 185 // Diff returns a diff of two task groups. If contextual diff is enabled, 186 // objects' fields will be stored even if no diff occurred as long as one field 187 // changed. 188 func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) { 189 diff := &TaskGroupDiff{Type: DiffTypeNone} 190 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 191 filter := []string{"Name"} 192 193 if tg == nil && other == nil { 194 return diff, nil 195 } else if tg == nil { 196 tg = &TaskGroup{} 197 diff.Type = DiffTypeAdded 198 diff.Name = other.Name 199 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 200 } else if other == nil { 201 other = &TaskGroup{} 202 diff.Type = DiffTypeDeleted 203 diff.Name = tg.Name 204 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 205 } else { 206 if !reflect.DeepEqual(tg, other) { 207 diff.Type = DiffTypeEdited 208 } 209 if tg.Name != other.Name { 210 return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name) 211 } 212 diff.Name = other.Name 213 oldPrimitiveFlat = flatmap.Flatten(tg, filter, true) 214 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 215 } 216 217 // Diff the primitive fields. 218 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 219 220 // Constraints diff 221 conDiff := primitiveObjectSetDiff( 222 interfaceSlice(tg.Constraints), 223 interfaceSlice(other.Constraints), 224 []string{"str"}, 225 "Constraint", 226 contextual) 227 if conDiff != nil { 228 diff.Objects = append(diff.Objects, conDiff...) 229 } 230 231 // Restart policy diff 232 rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual) 233 if rDiff != nil { 234 diff.Objects = append(diff.Objects, rDiff) 235 } 236 237 // Reschedule policy diff 238 reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual) 239 if reschedDiff != nil { 240 diff.Objects = append(diff.Objects, reschedDiff) 241 } 242 243 // EphemeralDisk diff 244 diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual) 245 if diskDiff != nil { 246 diff.Objects = append(diff.Objects, diskDiff) 247 } 248 249 // Update diff 250 // COMPAT: Remove "Stagger" in 0.7.0. 251 if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil { 252 diff.Objects = append(diff.Objects, uDiff) 253 } 254 255 // Tasks diff 256 tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual) 257 if err != nil { 258 return nil, err 259 } 260 diff.Tasks = tasks 261 262 return diff, nil 263 } 264 265 func (tg *TaskGroupDiff) GoString() string { 266 out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type) 267 268 if len(tg.Updates) != 0 { 269 out += "Updates {\n" 270 for update, count := range tg.Updates { 271 out += fmt.Sprintf("%d %s\n", count, update) 272 } 273 out += "}\n" 274 } 275 276 for _, f := range tg.Fields { 277 out += fmt.Sprintf("%#v\n", f) 278 } 279 280 for _, o := range tg.Objects { 281 out += fmt.Sprintf("%#v\n", o) 282 } 283 284 for _, t := range tg.Tasks { 285 out += fmt.Sprintf("%#v\n", t) 286 } 287 288 return out 289 } 290 291 // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled, 292 // objects' fields will be stored even if no diff occurred as long as one field 293 // changed. 294 func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) { 295 oldMap := make(map[string]*TaskGroup, len(old)) 296 newMap := make(map[string]*TaskGroup, len(new)) 297 for _, o := range old { 298 oldMap[o.Name] = o 299 } 300 for _, n := range new { 301 newMap[n.Name] = n 302 } 303 304 var diffs []*TaskGroupDiff 305 for name, oldGroup := range oldMap { 306 // Diff the same, deleted and edited 307 diff, err := oldGroup.Diff(newMap[name], contextual) 308 if err != nil { 309 return nil, err 310 } 311 diffs = append(diffs, diff) 312 } 313 314 for name, newGroup := range newMap { 315 // Diff the added 316 if old, ok := oldMap[name]; !ok { 317 diff, err := old.Diff(newGroup, contextual) 318 if err != nil { 319 return nil, err 320 } 321 diffs = append(diffs, diff) 322 } 323 } 324 325 sort.Sort(TaskGroupDiffs(diffs)) 326 return diffs, nil 327 } 328 329 // For sorting TaskGroupDiffs 330 type TaskGroupDiffs []*TaskGroupDiff 331 332 func (tg TaskGroupDiffs) Len() int { return len(tg) } 333 func (tg TaskGroupDiffs) Swap(i, j int) { tg[i], tg[j] = tg[j], tg[i] } 334 func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name } 335 336 // TaskDiff contains the diff of two Tasks 337 type TaskDiff struct { 338 Type DiffType 339 Name string 340 Fields []*FieldDiff 341 Objects []*ObjectDiff 342 Annotations []string 343 } 344 345 // Diff returns a diff of two tasks. If contextual diff is enabled, objects 346 // within the task will contain field information even if unchanged. 347 func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { 348 diff := &TaskDiff{Type: DiffTypeNone} 349 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 350 filter := []string{"Name", "Config"} 351 352 if t == nil && other == nil { 353 return diff, nil 354 } else if t == nil { 355 t = &Task{} 356 diff.Type = DiffTypeAdded 357 diff.Name = other.Name 358 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 359 } else if other == nil { 360 other = &Task{} 361 diff.Type = DiffTypeDeleted 362 diff.Name = t.Name 363 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 364 } else { 365 if !reflect.DeepEqual(t, other) { 366 diff.Type = DiffTypeEdited 367 } 368 if t.Name != other.Name { 369 return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name) 370 } 371 diff.Name = other.Name 372 oldPrimitiveFlat = flatmap.Flatten(t, filter, true) 373 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 374 } 375 376 // Diff the primitive fields. 377 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false) 378 379 // Constraints diff 380 conDiff := primitiveObjectSetDiff( 381 interfaceSlice(t.Constraints), 382 interfaceSlice(other.Constraints), 383 []string{"str"}, 384 "Constraint", 385 contextual) 386 if conDiff != nil { 387 diff.Objects = append(diff.Objects, conDiff...) 388 } 389 390 // Config diff 391 if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil { 392 diff.Objects = append(diff.Objects, cDiff) 393 } 394 395 // Resources diff 396 if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil { 397 diff.Objects = append(diff.Objects, rDiff) 398 } 399 400 // LogConfig diff 401 lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual) 402 if lDiff != nil { 403 diff.Objects = append(diff.Objects, lDiff) 404 } 405 406 // Dispatch payload diff 407 dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual) 408 if dDiff != nil { 409 diff.Objects = append(diff.Objects, dDiff) 410 } 411 412 // Artifacts diff 413 diffs := primitiveObjectSetDiff( 414 interfaceSlice(t.Artifacts), 415 interfaceSlice(other.Artifacts), 416 nil, 417 "Artifact", 418 contextual) 419 if diffs != nil { 420 diff.Objects = append(diff.Objects, diffs...) 421 } 422 423 // Services diff 424 if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil { 425 diff.Objects = append(diff.Objects, sDiffs...) 426 } 427 428 // Vault diff 429 vDiff := vaultDiff(t.Vault, other.Vault, contextual) 430 if vDiff != nil { 431 diff.Objects = append(diff.Objects, vDiff) 432 } 433 434 // Template diff 435 tmplDiffs := primitiveObjectSetDiff( 436 interfaceSlice(t.Templates), 437 interfaceSlice(other.Templates), 438 nil, 439 "Template", 440 contextual) 441 if tmplDiffs != nil { 442 diff.Objects = append(diff.Objects, tmplDiffs...) 443 } 444 445 return diff, nil 446 } 447 448 func (t *TaskDiff) GoString() string { 449 var out string 450 if len(t.Annotations) == 0 { 451 out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type) 452 } else { 453 out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ",")) 454 } 455 456 for _, f := range t.Fields { 457 out += fmt.Sprintf("%#v\n", f) 458 } 459 460 for _, o := range t.Objects { 461 out += fmt.Sprintf("%#v\n", o) 462 } 463 464 return out 465 } 466 467 // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged 468 // fields within objects nested in the tasks will be returned. 469 func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) { 470 oldMap := make(map[string]*Task, len(old)) 471 newMap := make(map[string]*Task, len(new)) 472 for _, o := range old { 473 oldMap[o.Name] = o 474 } 475 for _, n := range new { 476 newMap[n.Name] = n 477 } 478 479 var diffs []*TaskDiff 480 for name, oldGroup := range oldMap { 481 // Diff the same, deleted and edited 482 diff, err := oldGroup.Diff(newMap[name], contextual) 483 if err != nil { 484 return nil, err 485 } 486 diffs = append(diffs, diff) 487 } 488 489 for name, newGroup := range newMap { 490 // Diff the added 491 if old, ok := oldMap[name]; !ok { 492 diff, err := old.Diff(newGroup, contextual) 493 if err != nil { 494 return nil, err 495 } 496 diffs = append(diffs, diff) 497 } 498 } 499 500 sort.Sort(TaskDiffs(diffs)) 501 return diffs, nil 502 } 503 504 // For sorting TaskDiffs 505 type TaskDiffs []*TaskDiff 506 507 func (t TaskDiffs) Len() int { return len(t) } 508 func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 509 func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name } 510 511 // serviceDiff returns the diff of two service objects. If contextual diff is 512 // enabled, all fields will be returned, even if no diff occurred. 513 func serviceDiff(old, new *Service, contextual bool) *ObjectDiff { 514 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"} 515 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 516 517 if reflect.DeepEqual(old, new) { 518 return nil 519 } else if old == nil { 520 old = &Service{} 521 diff.Type = DiffTypeAdded 522 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 523 } else if new == nil { 524 new = &Service{} 525 diff.Type = DiffTypeDeleted 526 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 527 } else { 528 diff.Type = DiffTypeEdited 529 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 530 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 531 } 532 533 // Diff the primitive fields. 534 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 535 536 if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil { 537 diff.Objects = append(diff.Objects, setDiff) 538 } 539 540 // Tag diffs 541 if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil { 542 diff.Objects = append(diff.Objects, setDiff) 543 } 544 545 // Checks diffs 546 if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil { 547 diff.Objects = append(diff.Objects, cDiffs...) 548 } 549 550 return diff 551 } 552 553 // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged 554 // fields within objects nested in the tasks will be returned. 555 func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff { 556 oldMap := make(map[string]*Service, len(old)) 557 newMap := make(map[string]*Service, len(new)) 558 for _, o := range old { 559 oldMap[o.Name] = o 560 } 561 for _, n := range new { 562 newMap[n.Name] = n 563 } 564 565 var diffs []*ObjectDiff 566 for name, oldService := range oldMap { 567 // Diff the same, deleted and edited 568 if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil { 569 diffs = append(diffs, diff) 570 } 571 } 572 573 for name, newService := range newMap { 574 // Diff the added 575 if old, ok := oldMap[name]; !ok { 576 if diff := serviceDiff(old, newService, contextual); diff != nil { 577 diffs = append(diffs, diff) 578 } 579 } 580 } 581 582 sort.Sort(ObjectDiffs(diffs)) 583 return diffs 584 } 585 586 // serviceCheckDiff returns the diff of two service check objects. If contextual 587 // diff is enabled, all fields will be returned, even if no diff occurred. 588 func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff { 589 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"} 590 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 591 592 if reflect.DeepEqual(old, new) { 593 return nil 594 } else if old == nil { 595 old = &ServiceCheck{} 596 diff.Type = DiffTypeAdded 597 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 598 } else if new == nil { 599 new = &ServiceCheck{} 600 diff.Type = DiffTypeDeleted 601 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 602 } else { 603 diff.Type = DiffTypeEdited 604 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 605 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 606 } 607 608 // Diff the primitive fields. 609 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 610 611 // Diff Header 612 if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil { 613 diff.Objects = append(diff.Objects, headerDiff) 614 } 615 616 // Diff check_restart 617 if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil { 618 diff.Objects = append(diff.Objects, crDiff) 619 } 620 621 return diff 622 } 623 624 // checkHeaderDiff returns the diff of two service check header objects. If 625 // contextual diff is enabled, all fields will be returned, even if no diff 626 // occurred. 627 func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff { 628 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"} 629 var oldFlat, newFlat map[string]string 630 631 if reflect.DeepEqual(old, new) { 632 return nil 633 } else if len(old) == 0 { 634 diff.Type = DiffTypeAdded 635 newFlat = flatmap.Flatten(new, nil, false) 636 } else if len(new) == 0 { 637 diff.Type = DiffTypeDeleted 638 oldFlat = flatmap.Flatten(old, nil, false) 639 } else { 640 diff.Type = DiffTypeEdited 641 oldFlat = flatmap.Flatten(old, nil, false) 642 newFlat = flatmap.Flatten(new, nil, false) 643 } 644 645 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 646 return diff 647 } 648 649 // checkRestartDiff returns the diff of two service check check_restart 650 // objects. If contextual diff is enabled, all fields will be returned, even if 651 // no diff occurred. 652 func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff { 653 diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"} 654 var oldFlat, newFlat map[string]string 655 656 if reflect.DeepEqual(old, new) { 657 return nil 658 } else if old == nil { 659 diff.Type = DiffTypeAdded 660 newFlat = flatmap.Flatten(new, nil, true) 661 diff.Type = DiffTypeAdded 662 } else if new == nil { 663 diff.Type = DiffTypeDeleted 664 oldFlat = flatmap.Flatten(old, nil, true) 665 } else { 666 diff.Type = DiffTypeEdited 667 oldFlat = flatmap.Flatten(old, nil, true) 668 newFlat = flatmap.Flatten(new, nil, true) 669 } 670 671 diff.Fields = fieldDiffs(oldFlat, newFlat, contextual) 672 return diff 673 } 674 675 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 676 // enabled, unchanged fields within objects nested in the tasks will be 677 // returned. 678 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 679 oldMap := make(map[string]*ServiceCheck, len(old)) 680 newMap := make(map[string]*ServiceCheck, len(new)) 681 for _, o := range old { 682 oldMap[o.Name] = o 683 } 684 for _, n := range new { 685 newMap[n.Name] = n 686 } 687 688 var diffs []*ObjectDiff 689 for name, oldCheck := range oldMap { 690 // Diff the same, deleted and edited 691 if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil { 692 diffs = append(diffs, diff) 693 } 694 } 695 696 for name, newCheck := range newMap { 697 // Diff the added 698 if old, ok := oldMap[name]; !ok { 699 if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil { 700 diffs = append(diffs, diff) 701 } 702 } 703 } 704 705 sort.Sort(ObjectDiffs(diffs)) 706 return diffs 707 } 708 709 // vaultDiff returns the diff of two vault objects. If contextual diff is 710 // enabled, all fields will be returned, even if no diff occurred. 711 func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff { 712 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"} 713 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 714 715 if reflect.DeepEqual(old, new) { 716 return nil 717 } else if old == nil { 718 old = &Vault{} 719 diff.Type = DiffTypeAdded 720 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 721 } else if new == nil { 722 new = &Vault{} 723 diff.Type = DiffTypeDeleted 724 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 725 } else { 726 diff.Type = DiffTypeEdited 727 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 728 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 729 } 730 731 // Diff the primitive fields. 732 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 733 734 // Policies diffs 735 if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil { 736 diff.Objects = append(diff.Objects, setDiff) 737 } 738 739 return diff 740 } 741 742 // parameterizedJobDiff returns the diff of two parameterized job objects. If 743 // contextual diff is enabled, all fields will be returned, even if no diff 744 // occurred. 745 func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff { 746 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"} 747 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 748 749 if reflect.DeepEqual(old, new) { 750 return nil 751 } else if old == nil { 752 old = &ParameterizedJobConfig{} 753 diff.Type = DiffTypeAdded 754 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 755 } else if new == nil { 756 new = &ParameterizedJobConfig{} 757 diff.Type = DiffTypeDeleted 758 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 759 } else { 760 diff.Type = DiffTypeEdited 761 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 762 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 763 } 764 765 // Diff the primitive fields. 766 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 767 768 // Meta diffs 769 if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil { 770 diff.Objects = append(diff.Objects, optionalDiff) 771 } 772 773 if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil { 774 diff.Objects = append(diff.Objects, requiredDiff) 775 } 776 777 return diff 778 } 779 780 // Diff returns a diff of two resource objects. If contextual diff is enabled, 781 // non-changed fields will still be returned. 782 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 783 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 784 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 785 786 if reflect.DeepEqual(r, other) { 787 return nil 788 } else if r == nil { 789 r = &Resources{} 790 diff.Type = DiffTypeAdded 791 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 792 } else if other == nil { 793 other = &Resources{} 794 diff.Type = DiffTypeDeleted 795 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 796 } else { 797 diff.Type = DiffTypeEdited 798 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 799 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 800 } 801 802 // Diff the primitive fields. 803 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 804 805 // Network Resources diff 806 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 807 diff.Objects = append(diff.Objects, nDiffs...) 808 } 809 810 return diff 811 } 812 813 // Diff returns a diff of two network resources. If contextual diff is enabled, 814 // non-changed fields will still be returned. 815 func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 816 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 817 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 818 filter := []string{"Device", "CIDR", "IP"} 819 820 if reflect.DeepEqual(r, other) { 821 return nil 822 } else if r == nil { 823 r = &NetworkResource{} 824 diff.Type = DiffTypeAdded 825 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 826 } else if other == nil { 827 other = &NetworkResource{} 828 diff.Type = DiffTypeDeleted 829 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 830 } else { 831 diff.Type = DiffTypeEdited 832 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 833 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 834 } 835 836 // Diff the primitive fields. 837 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 838 839 // Port diffs 840 resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual) 841 dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual) 842 if resPorts != nil { 843 diff.Objects = append(diff.Objects, resPorts...) 844 } 845 if dynPorts != nil { 846 diff.Objects = append(diff.Objects, dynPorts...) 847 } 848 849 return diff 850 } 851 852 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 853 // non-changed fields will still be returned. 854 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 855 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 856 objMap := make(map[string]*NetworkResource, len(objects)) 857 for _, obj := range objects { 858 hash, err := hashstructure.Hash(obj, nil) 859 if err != nil { 860 panic(err) 861 } 862 objMap[fmt.Sprintf("%d", hash)] = obj 863 } 864 865 return objMap 866 } 867 868 oldSet := makeSet(old) 869 newSet := makeSet(new) 870 871 var diffs []*ObjectDiff 872 for k, oldV := range oldSet { 873 if newV, ok := newSet[k]; !ok { 874 if diff := oldV.Diff(newV, contextual); diff != nil { 875 diffs = append(diffs, diff) 876 } 877 } 878 } 879 for k, newV := range newSet { 880 if oldV, ok := oldSet[k]; !ok { 881 if diff := oldV.Diff(newV, contextual); diff != nil { 882 diffs = append(diffs, diff) 883 } 884 } 885 } 886 887 sort.Sort(ObjectDiffs(diffs)) 888 return diffs 889 890 } 891 892 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 893 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 894 // non-changed fields will still be returned. 895 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 896 makeSet := func(ports []Port) map[string]Port { 897 portMap := make(map[string]Port, len(ports)) 898 for _, port := range ports { 899 portMap[port.Label] = port 900 } 901 902 return portMap 903 } 904 905 oldPorts := makeSet(old) 906 newPorts := makeSet(new) 907 908 var filter []string 909 name := "Static Port" 910 if dynamic { 911 filter = []string{"Value"} 912 name = "Dynamic Port" 913 } 914 915 var diffs []*ObjectDiff 916 for portLabel, oldPort := range oldPorts { 917 // Diff the same, deleted and edited 918 if newPort, ok := newPorts[portLabel]; ok { 919 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 920 if diff != nil { 921 diffs = append(diffs, diff) 922 } 923 } else { 924 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 925 if diff != nil { 926 diffs = append(diffs, diff) 927 } 928 } 929 } 930 for label, newPort := range newPorts { 931 // Diff the added 932 if _, ok := oldPorts[label]; !ok { 933 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 934 if diff != nil { 935 diffs = append(diffs, diff) 936 } 937 } 938 } 939 940 sort.Sort(ObjectDiffs(diffs)) 941 return diffs 942 943 } 944 945 // configDiff returns the diff of two Task Config objects. If contextual diff is 946 // enabled, all fields will be returned, even if no diff occurred. 947 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 948 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 949 if reflect.DeepEqual(old, new) { 950 return nil 951 } else if len(old) == 0 { 952 diff.Type = DiffTypeAdded 953 } else if len(new) == 0 { 954 diff.Type = DiffTypeDeleted 955 } else { 956 diff.Type = DiffTypeEdited 957 } 958 959 // Diff the primitive fields. 960 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 961 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 962 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 963 return diff 964 } 965 966 // ObjectDiff contains the diff of two generic objects. 967 type ObjectDiff struct { 968 Type DiffType 969 Name string 970 Fields []*FieldDiff 971 Objects []*ObjectDiff 972 } 973 974 func (o *ObjectDiff) GoString() string { 975 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 976 for _, f := range o.Fields { 977 out += fmt.Sprintf("%#v\n", f) 978 } 979 for _, o := range o.Objects { 980 out += fmt.Sprintf("%#v\n", o) 981 } 982 out += "}" 983 return out 984 } 985 986 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 987 if reflect.DeepEqual(o, other) { 988 return false 989 } else if other == nil { 990 return false 991 } else if o == nil { 992 return true 993 } 994 995 if o.Name != other.Name { 996 return o.Name < other.Name 997 } 998 999 if o.Type != other.Type { 1000 return o.Type.Less(other.Type) 1001 } 1002 1003 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 1004 return lO < lOther 1005 } 1006 1007 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 1008 return lO < lOther 1009 } 1010 1011 // Check each field 1012 sort.Sort(FieldDiffs(o.Fields)) 1013 sort.Sort(FieldDiffs(other.Fields)) 1014 1015 for i, oV := range o.Fields { 1016 if oV.Less(other.Fields[i]) { 1017 return true 1018 } 1019 } 1020 1021 // Check each object 1022 sort.Sort(ObjectDiffs(o.Objects)) 1023 sort.Sort(ObjectDiffs(other.Objects)) 1024 for i, oV := range o.Objects { 1025 if oV.Less(other.Objects[i]) { 1026 return true 1027 } 1028 } 1029 1030 return false 1031 } 1032 1033 // For sorting ObjectDiffs 1034 type ObjectDiffs []*ObjectDiff 1035 1036 func (o ObjectDiffs) Len() int { return len(o) } 1037 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 1038 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 1039 1040 type FieldDiff struct { 1041 Type DiffType 1042 Name string 1043 Old, New string 1044 Annotations []string 1045 } 1046 1047 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 1048 // returns nil. If contextual diff is enabled, even non-changed fields will be 1049 // returned. 1050 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 1051 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 1052 if old == new { 1053 if !contextual { 1054 return nil 1055 } 1056 diff.Old, diff.New = old, new 1057 return diff 1058 } 1059 1060 if old == "" { 1061 diff.Type = DiffTypeAdded 1062 diff.New = new 1063 } else if new == "" { 1064 diff.Type = DiffTypeDeleted 1065 diff.Old = old 1066 } else { 1067 diff.Type = DiffTypeEdited 1068 diff.Old = old 1069 diff.New = new 1070 } 1071 return diff 1072 } 1073 1074 func (f *FieldDiff) GoString() string { 1075 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 1076 if len(f.Annotations) != 0 { 1077 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 1078 } 1079 1080 return out 1081 } 1082 1083 func (f *FieldDiff) Less(other *FieldDiff) bool { 1084 if reflect.DeepEqual(f, other) { 1085 return false 1086 } else if other == nil { 1087 return false 1088 } else if f == nil { 1089 return true 1090 } 1091 1092 if f.Name != other.Name { 1093 return f.Name < other.Name 1094 } else if f.Old != other.Old { 1095 return f.Old < other.Old 1096 } 1097 1098 return f.New < other.New 1099 } 1100 1101 // For sorting FieldDiffs 1102 type FieldDiffs []*FieldDiff 1103 1104 func (f FieldDiffs) Len() int { return len(f) } 1105 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 1106 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 1107 1108 // fieldDiffs takes a map of field names to their values and returns a set of 1109 // field diffs. If contextual diff is enabled, even non-changed fields will be 1110 // returned. 1111 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 1112 var diffs []*FieldDiff 1113 visited := make(map[string]struct{}) 1114 for k, oldV := range old { 1115 visited[k] = struct{}{} 1116 newV := new[k] 1117 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 1118 diffs = append(diffs, diff) 1119 } 1120 } 1121 1122 for k, newV := range new { 1123 if _, ok := visited[k]; !ok { 1124 if diff := fieldDiff("", newV, k, contextual); diff != nil { 1125 diffs = append(diffs, diff) 1126 } 1127 } 1128 } 1129 1130 sort.Sort(FieldDiffs(diffs)) 1131 return diffs 1132 } 1133 1134 // stringSetDiff diffs two sets of strings with the given name. 1135 func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff { 1136 oldMap := make(map[string]struct{}, len(old)) 1137 newMap := make(map[string]struct{}, len(new)) 1138 for _, o := range old { 1139 oldMap[o] = struct{}{} 1140 } 1141 for _, n := range new { 1142 newMap[n] = struct{}{} 1143 } 1144 if reflect.DeepEqual(oldMap, newMap) && !contextual { 1145 return nil 1146 } 1147 1148 diff := &ObjectDiff{Name: name} 1149 var added, removed bool 1150 for k := range oldMap { 1151 if _, ok := newMap[k]; !ok { 1152 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual)) 1153 removed = true 1154 } else if contextual { 1155 diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual)) 1156 } 1157 } 1158 1159 for k := range newMap { 1160 if _, ok := oldMap[k]; !ok { 1161 diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual)) 1162 added = true 1163 } 1164 } 1165 1166 sort.Sort(FieldDiffs(diff.Fields)) 1167 1168 // Determine the type 1169 if added && removed { 1170 diff.Type = DiffTypeEdited 1171 } else if added { 1172 diff.Type = DiffTypeAdded 1173 } else if removed { 1174 diff.Type = DiffTypeDeleted 1175 } else { 1176 // Diff of an empty set 1177 if len(diff.Fields) == 0 { 1178 return nil 1179 } 1180 1181 diff.Type = DiffTypeNone 1182 } 1183 1184 return diff 1185 } 1186 1187 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 1188 // The filter field can be used to exclude fields from the diff. The name is the 1189 // name of the objects. If contextual is set, non-changed fields will also be 1190 // stored in the object diff. 1191 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 1192 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 1193 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 1194 delete(oldPrimitiveFlat, "") 1195 delete(newPrimitiveFlat, "") 1196 1197 diff := &ObjectDiff{Name: name} 1198 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1199 1200 var added, deleted, edited bool 1201 for _, f := range diff.Fields { 1202 switch f.Type { 1203 case DiffTypeEdited: 1204 edited = true 1205 break 1206 case DiffTypeDeleted: 1207 deleted = true 1208 case DiffTypeAdded: 1209 added = true 1210 } 1211 } 1212 1213 if edited || added && deleted { 1214 diff.Type = DiffTypeEdited 1215 } else if added { 1216 diff.Type = DiffTypeAdded 1217 } else if deleted { 1218 diff.Type = DiffTypeDeleted 1219 } else { 1220 return nil 1221 } 1222 1223 return diff 1224 } 1225 1226 // primitiveObjectSetDiff does a set difference of the old and new sets. The 1227 // filter parameter can be used to filter a set of primitive fields in the 1228 // passed structs. The name corresponds to the name of the passed objects. If 1229 // contextual diff is enabled, objects' primitive fields will be returned even if 1230 // no diff exists. 1231 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 1232 makeSet := func(objects []interface{}) map[string]interface{} { 1233 objMap := make(map[string]interface{}, len(objects)) 1234 for _, obj := range objects { 1235 hash, err := hashstructure.Hash(obj, nil) 1236 if err != nil { 1237 panic(err) 1238 } 1239 objMap[fmt.Sprintf("%d", hash)] = obj 1240 } 1241 1242 return objMap 1243 } 1244 1245 oldSet := makeSet(old) 1246 newSet := makeSet(new) 1247 1248 var diffs []*ObjectDiff 1249 for k, v := range oldSet { 1250 // Deleted 1251 if _, ok := newSet[k]; !ok { 1252 diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual)) 1253 } 1254 } 1255 for k, v := range newSet { 1256 // Added 1257 if _, ok := oldSet[k]; !ok { 1258 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 1259 } 1260 } 1261 1262 sort.Sort(ObjectDiffs(diffs)) 1263 return diffs 1264 } 1265 1266 // interfaceSlice is a helper method that takes a slice of typed elements and 1267 // returns a slice of interface. This method will panic if given a non-slice 1268 // input. 1269 func interfaceSlice(slice interface{}) []interface{} { 1270 s := reflect.ValueOf(slice) 1271 if s.Kind() != reflect.Slice { 1272 panic("InterfaceSlice() given a non-slice type") 1273 } 1274 1275 ret := make([]interface{}, s.Len()) 1276 1277 for i := 0; i < s.Len(); i++ { 1278 ret[i] = s.Index(i).Interface() 1279 } 1280 1281 return ret 1282 }