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