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