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