github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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 return diff 596 } 597 598 // serviceCheckDiffs diffs a set of service checks. If contextual diff is 599 // enabled, unchanged fields within objects nested in the tasks will be 600 // returned. 601 func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff { 602 oldMap := make(map[string]*ServiceCheck, len(old)) 603 newMap := make(map[string]*ServiceCheck, len(new)) 604 for _, o := range old { 605 oldMap[o.Name] = o 606 } 607 for _, n := range new { 608 newMap[n.Name] = n 609 } 610 611 var diffs []*ObjectDiff 612 for name, oldService := range oldMap { 613 // Diff the same, deleted and edited 614 if diff := serviceCheckDiff(oldService, newMap[name], contextual); diff != nil { 615 diffs = append(diffs, diff) 616 } 617 } 618 619 for name, newService := range newMap { 620 // Diff the added 621 if old, ok := oldMap[name]; !ok { 622 if diff := serviceCheckDiff(old, newService, contextual); diff != nil { 623 diffs = append(diffs, diff) 624 } 625 } 626 } 627 628 sort.Sort(ObjectDiffs(diffs)) 629 return diffs 630 } 631 632 // vaultDiff returns the diff of two vault objects. If contextual diff is 633 // enabled, all fields will be returned, even if no diff occurred. 634 func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff { 635 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"} 636 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 637 638 if reflect.DeepEqual(old, new) { 639 return nil 640 } else if old == nil { 641 old = &Vault{} 642 diff.Type = DiffTypeAdded 643 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 644 } else if new == nil { 645 new = &Vault{} 646 diff.Type = DiffTypeDeleted 647 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 648 } else { 649 diff.Type = DiffTypeEdited 650 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 651 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 652 } 653 654 // Diff the primitive fields. 655 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 656 657 // Policies diffs 658 if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil { 659 diff.Objects = append(diff.Objects, setDiff) 660 } 661 662 return diff 663 } 664 665 // parameterizedJobDiff returns the diff of two parameterized job objects. If 666 // contextual diff is enabled, all fields will be returned, even if no diff 667 // occurred. 668 func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff { 669 diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"} 670 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 671 672 if reflect.DeepEqual(old, new) { 673 return nil 674 } else if old == nil { 675 old = &ParameterizedJobConfig{} 676 diff.Type = DiffTypeAdded 677 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 678 } else if new == nil { 679 new = &ParameterizedJobConfig{} 680 diff.Type = DiffTypeDeleted 681 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 682 } else { 683 diff.Type = DiffTypeEdited 684 oldPrimitiveFlat = flatmap.Flatten(old, nil, true) 685 newPrimitiveFlat = flatmap.Flatten(new, nil, true) 686 } 687 688 // Diff the primitive fields. 689 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 690 691 // Meta diffs 692 if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil { 693 diff.Objects = append(diff.Objects, optionalDiff) 694 } 695 696 if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil { 697 diff.Objects = append(diff.Objects, requiredDiff) 698 } 699 700 return diff 701 } 702 703 // Diff returns a diff of two resource objects. If contextual diff is enabled, 704 // non-changed fields will still be returned. 705 func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { 706 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"} 707 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 708 709 if reflect.DeepEqual(r, other) { 710 return nil 711 } else if r == nil { 712 r = &Resources{} 713 diff.Type = DiffTypeAdded 714 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 715 } else if other == nil { 716 other = &Resources{} 717 diff.Type = DiffTypeDeleted 718 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 719 } else { 720 diff.Type = DiffTypeEdited 721 oldPrimitiveFlat = flatmap.Flatten(r, nil, true) 722 newPrimitiveFlat = flatmap.Flatten(other, nil, true) 723 } 724 725 // Diff the primitive fields. 726 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 727 728 // Network Resources diff 729 if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil { 730 diff.Objects = append(diff.Objects, nDiffs...) 731 } 732 733 return diff 734 } 735 736 // Diff returns a diff of two network resources. If contextual diff is enabled, 737 // non-changed fields will still be returned. 738 func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff { 739 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"} 740 var oldPrimitiveFlat, newPrimitiveFlat map[string]string 741 filter := []string{"Device", "CIDR", "IP"} 742 743 if reflect.DeepEqual(r, other) { 744 return nil 745 } else if r == nil { 746 r = &NetworkResource{} 747 diff.Type = DiffTypeAdded 748 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 749 } else if other == nil { 750 other = &NetworkResource{} 751 diff.Type = DiffTypeDeleted 752 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 753 } else { 754 diff.Type = DiffTypeEdited 755 oldPrimitiveFlat = flatmap.Flatten(r, filter, true) 756 newPrimitiveFlat = flatmap.Flatten(other, filter, true) 757 } 758 759 // Diff the primitive fields. 760 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 761 762 // Port diffs 763 resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual) 764 dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual) 765 if resPorts != nil { 766 diff.Objects = append(diff.Objects, resPorts...) 767 } 768 if dynPorts != nil { 769 diff.Objects = append(diff.Objects, dynPorts...) 770 } 771 772 return diff 773 } 774 775 // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled, 776 // non-changed fields will still be returned. 777 func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff { 778 makeSet := func(objects []*NetworkResource) map[string]*NetworkResource { 779 objMap := make(map[string]*NetworkResource, len(objects)) 780 for _, obj := range objects { 781 hash, err := hashstructure.Hash(obj, nil) 782 if err != nil { 783 panic(err) 784 } 785 objMap[fmt.Sprintf("%d", hash)] = obj 786 } 787 788 return objMap 789 } 790 791 oldSet := makeSet(old) 792 newSet := makeSet(new) 793 794 var diffs []*ObjectDiff 795 for k, oldV := range oldSet { 796 if newV, ok := newSet[k]; !ok { 797 if diff := oldV.Diff(newV, contextual); diff != nil { 798 diffs = append(diffs, diff) 799 } 800 } 801 } 802 for k, newV := range newSet { 803 if oldV, ok := oldSet[k]; !ok { 804 if diff := oldV.Diff(newV, contextual); diff != nil { 805 diffs = append(diffs, diff) 806 } 807 } 808 } 809 810 sort.Sort(ObjectDiffs(diffs)) 811 return diffs 812 813 } 814 815 // portDiffs returns the diff of two sets of ports. The dynamic flag marks the 816 // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled, 817 // non-changed fields will still be returned. 818 func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { 819 makeSet := func(ports []Port) map[string]Port { 820 portMap := make(map[string]Port, len(ports)) 821 for _, port := range ports { 822 portMap[port.Label] = port 823 } 824 825 return portMap 826 } 827 828 oldPorts := makeSet(old) 829 newPorts := makeSet(new) 830 831 var filter []string 832 name := "Static Port" 833 if dynamic { 834 filter = []string{"Value"} 835 name = "Dynamic Port" 836 } 837 838 var diffs []*ObjectDiff 839 for portLabel, oldPort := range oldPorts { 840 // Diff the same, deleted and edited 841 if newPort, ok := newPorts[portLabel]; ok { 842 diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual) 843 if diff != nil { 844 diffs = append(diffs, diff) 845 } 846 } else { 847 diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual) 848 if diff != nil { 849 diffs = append(diffs, diff) 850 } 851 } 852 } 853 for label, newPort := range newPorts { 854 // Diff the added 855 if _, ok := oldPorts[label]; !ok { 856 diff := primitiveObjectDiff(nil, newPort, filter, name, contextual) 857 if diff != nil { 858 diffs = append(diffs, diff) 859 } 860 } 861 } 862 863 sort.Sort(ObjectDiffs(diffs)) 864 return diffs 865 866 } 867 868 // configDiff returns the diff of two Task Config objects. If contextual diff is 869 // enabled, all fields will be returned, even if no diff occurred. 870 func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { 871 diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"} 872 if reflect.DeepEqual(old, new) { 873 return nil 874 } else if len(old) == 0 { 875 diff.Type = DiffTypeAdded 876 } else if len(new) == 0 { 877 diff.Type = DiffTypeDeleted 878 } else { 879 diff.Type = DiffTypeEdited 880 } 881 882 // Diff the primitive fields. 883 oldPrimitiveFlat := flatmap.Flatten(old, nil, false) 884 newPrimitiveFlat := flatmap.Flatten(new, nil, false) 885 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 886 return diff 887 } 888 889 // ObjectDiff contains the diff of two generic objects. 890 type ObjectDiff struct { 891 Type DiffType 892 Name string 893 Fields []*FieldDiff 894 Objects []*ObjectDiff 895 } 896 897 func (o *ObjectDiff) GoString() string { 898 out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type) 899 for _, f := range o.Fields { 900 out += fmt.Sprintf("%#v\n", f) 901 } 902 for _, o := range o.Objects { 903 out += fmt.Sprintf("%#v\n", o) 904 } 905 out += "}" 906 return out 907 } 908 909 func (o *ObjectDiff) Less(other *ObjectDiff) bool { 910 if reflect.DeepEqual(o, other) { 911 return false 912 } else if other == nil { 913 return false 914 } else if o == nil { 915 return true 916 } 917 918 if o.Name != other.Name { 919 return o.Name < other.Name 920 } 921 922 if o.Type != other.Type { 923 return o.Type.Less(other.Type) 924 } 925 926 if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther { 927 return lO < lOther 928 } 929 930 if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther { 931 return lO < lOther 932 } 933 934 // Check each field 935 sort.Sort(FieldDiffs(o.Fields)) 936 sort.Sort(FieldDiffs(other.Fields)) 937 938 for i, oV := range o.Fields { 939 if oV.Less(other.Fields[i]) { 940 return true 941 } 942 } 943 944 // Check each object 945 sort.Sort(ObjectDiffs(o.Objects)) 946 sort.Sort(ObjectDiffs(other.Objects)) 947 for i, oV := range o.Objects { 948 if oV.Less(other.Objects[i]) { 949 return true 950 } 951 } 952 953 return false 954 } 955 956 // For sorting ObjectDiffs 957 type ObjectDiffs []*ObjectDiff 958 959 func (o ObjectDiffs) Len() int { return len(o) } 960 func (o ObjectDiffs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 961 func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) } 962 963 type FieldDiff struct { 964 Type DiffType 965 Name string 966 Old, New string 967 Annotations []string 968 } 969 970 // fieldDiff returns a FieldDiff if old and new are different otherwise, it 971 // returns nil. If contextual diff is enabled, even non-changed fields will be 972 // returned. 973 func fieldDiff(old, new, name string, contextual bool) *FieldDiff { 974 diff := &FieldDiff{Name: name, Type: DiffTypeNone} 975 if old == new { 976 if !contextual { 977 return nil 978 } 979 diff.Old, diff.New = old, new 980 return diff 981 } 982 983 if old == "" { 984 diff.Type = DiffTypeAdded 985 diff.New = new 986 } else if new == "" { 987 diff.Type = DiffTypeDeleted 988 diff.Old = old 989 } else { 990 diff.Type = DiffTypeEdited 991 diff.Old = old 992 diff.New = new 993 } 994 return diff 995 } 996 997 func (f *FieldDiff) GoString() string { 998 out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New) 999 if len(f.Annotations) != 0 { 1000 out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", ")) 1001 } 1002 1003 return out 1004 } 1005 1006 func (f *FieldDiff) Less(other *FieldDiff) bool { 1007 if reflect.DeepEqual(f, other) { 1008 return false 1009 } else if other == nil { 1010 return false 1011 } else if f == nil { 1012 return true 1013 } 1014 1015 if f.Name != other.Name { 1016 return f.Name < other.Name 1017 } else if f.Old != other.Old { 1018 return f.Old < other.Old 1019 } 1020 1021 return f.New < other.New 1022 } 1023 1024 // For sorting FieldDiffs 1025 type FieldDiffs []*FieldDiff 1026 1027 func (f FieldDiffs) Len() int { return len(f) } 1028 func (f FieldDiffs) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 1029 func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) } 1030 1031 // fieldDiffs takes a map of field names to their values and returns a set of 1032 // field diffs. If contextual diff is enabled, even non-changed fields will be 1033 // returned. 1034 func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff { 1035 var diffs []*FieldDiff 1036 visited := make(map[string]struct{}) 1037 for k, oldV := range old { 1038 visited[k] = struct{}{} 1039 newV := new[k] 1040 if diff := fieldDiff(oldV, newV, k, contextual); diff != nil { 1041 diffs = append(diffs, diff) 1042 } 1043 } 1044 1045 for k, newV := range new { 1046 if _, ok := visited[k]; !ok { 1047 if diff := fieldDiff("", newV, k, contextual); diff != nil { 1048 diffs = append(diffs, diff) 1049 } 1050 } 1051 } 1052 1053 sort.Sort(FieldDiffs(diffs)) 1054 return diffs 1055 } 1056 1057 // stringSetDiff diffs two sets of strings with the given name. 1058 func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff { 1059 oldMap := make(map[string]struct{}, len(old)) 1060 newMap := make(map[string]struct{}, len(new)) 1061 for _, o := range old { 1062 oldMap[o] = struct{}{} 1063 } 1064 for _, n := range new { 1065 newMap[n] = struct{}{} 1066 } 1067 if reflect.DeepEqual(oldMap, newMap) && !contextual { 1068 return nil 1069 } 1070 1071 diff := &ObjectDiff{Name: name} 1072 var added, removed bool 1073 for k := range oldMap { 1074 if _, ok := newMap[k]; !ok { 1075 diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual)) 1076 removed = true 1077 } else if contextual { 1078 diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual)) 1079 } 1080 } 1081 1082 for k := range newMap { 1083 if _, ok := oldMap[k]; !ok { 1084 diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual)) 1085 added = true 1086 } 1087 } 1088 1089 sort.Sort(FieldDiffs(diff.Fields)) 1090 1091 // Determine the type 1092 if added && removed { 1093 diff.Type = DiffTypeEdited 1094 } else if added { 1095 diff.Type = DiffTypeAdded 1096 } else if removed { 1097 diff.Type = DiffTypeDeleted 1098 } else { 1099 // Diff of an empty set 1100 if len(diff.Fields) == 0 { 1101 return nil 1102 } 1103 1104 diff.Type = DiffTypeNone 1105 } 1106 1107 return diff 1108 } 1109 1110 // primitiveObjectDiff returns a diff of the passed objects' primitive fields. 1111 // The filter field can be used to exclude fields from the diff. The name is the 1112 // name of the objects. If contextual is set, non-changed fields will also be 1113 // stored in the object diff. 1114 func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff { 1115 oldPrimitiveFlat := flatmap.Flatten(old, filter, true) 1116 newPrimitiveFlat := flatmap.Flatten(new, filter, true) 1117 delete(oldPrimitiveFlat, "") 1118 delete(newPrimitiveFlat, "") 1119 1120 diff := &ObjectDiff{Name: name} 1121 diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) 1122 1123 var added, deleted, edited bool 1124 for _, f := range diff.Fields { 1125 switch f.Type { 1126 case DiffTypeEdited: 1127 edited = true 1128 break 1129 case DiffTypeDeleted: 1130 deleted = true 1131 case DiffTypeAdded: 1132 added = true 1133 } 1134 } 1135 1136 if edited || added && deleted { 1137 diff.Type = DiffTypeEdited 1138 } else if added { 1139 diff.Type = DiffTypeAdded 1140 } else if deleted { 1141 diff.Type = DiffTypeDeleted 1142 } else { 1143 return nil 1144 } 1145 1146 return diff 1147 } 1148 1149 // primitiveObjectSetDiff does a set difference of the old and new sets. The 1150 // filter parameter can be used to filter a set of primitive fields in the 1151 // passed structs. The name corresponds to the name of the passed objects. If 1152 // contextual diff is enabled, objects' primtive fields will be returned even if 1153 // no diff exists. 1154 func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff { 1155 makeSet := func(objects []interface{}) map[string]interface{} { 1156 objMap := make(map[string]interface{}, len(objects)) 1157 for _, obj := range objects { 1158 hash, err := hashstructure.Hash(obj, nil) 1159 if err != nil { 1160 panic(err) 1161 } 1162 objMap[fmt.Sprintf("%d", hash)] = obj 1163 } 1164 1165 return objMap 1166 } 1167 1168 oldSet := makeSet(old) 1169 newSet := makeSet(new) 1170 1171 var diffs []*ObjectDiff 1172 for k, v := range oldSet { 1173 // Deleted 1174 if _, ok := newSet[k]; !ok { 1175 diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual)) 1176 } 1177 } 1178 for k, v := range newSet { 1179 // Added 1180 if _, ok := oldSet[k]; !ok { 1181 diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual)) 1182 } 1183 } 1184 1185 sort.Sort(ObjectDiffs(diffs)) 1186 return diffs 1187 } 1188 1189 // interfaceSlice is a helper method that takes a slice of typed elements and 1190 // returns a slice of interface. This method will panic if given a non-slice 1191 // input. 1192 func interfaceSlice(slice interface{}) []interface{} { 1193 s := reflect.ValueOf(slice) 1194 if s.Kind() != reflect.Slice { 1195 panic("InterfaceSlice() given a non-slice type") 1196 } 1197 1198 ret := make([]interface{}, s.Len()) 1199 1200 for i := 0; i < s.Len(); i++ { 1201 ret[i] = s.Index(i).Interface() 1202 } 1203 1204 return ret 1205 }