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