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