github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/diff.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/nomad/helper/flatmap"
    10  	"github.com/mitchellh/hashstructure"
    11  )
    12  
    13  // DiffType denotes the type of a diff object.
    14  type DiffType string
    15  
    16  var (
    17  	DiffTypeNone    DiffType = "None"
    18  	DiffTypeAdded   DiffType = "Added"
    19  	DiffTypeDeleted DiffType = "Deleted"
    20  	DiffTypeEdited  DiffType = "Edited"
    21  )
    22  
    23  func (d DiffType) Less(other DiffType) bool {
    24  	// Edited > Added > Deleted > None
    25  	// But we do a reverse sort
    26  	if d == other {
    27  		return false
    28  	}
    29  
    30  	if d == DiffTypeEdited {
    31  		return true
    32  	} else if other == DiffTypeEdited {
    33  		return false
    34  	} else if d == DiffTypeAdded {
    35  		return true
    36  	} else if other == DiffTypeAdded {
    37  		return false
    38  	} else if d == DiffTypeDeleted {
    39  		return true
    40  	} else if other == DiffTypeDeleted {
    41  		return false
    42  	}
    43  
    44  	return true
    45  }
    46  
    47  // JobDiff contains the diff of two jobs.
    48  type JobDiff struct {
    49  	Type       DiffType
    50  	ID         string
    51  	Fields     []*FieldDiff
    52  	Objects    []*ObjectDiff
    53  	TaskGroups []*TaskGroupDiff
    54  }
    55  
    56  // Diff returns a diff of two jobs and a potential error if the Jobs are not
    57  // diffable. If contextual diff is enabled, objects within the job will contain
    58  // field information even if unchanged.
    59  func (j *Job) Diff(other *Job, contextual bool) (*JobDiff, error) {
    60  	// See agent.ApiJobToStructJob Update is a default for TaskGroups
    61  	diff := &JobDiff{Type: DiffTypeNone}
    62  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
    63  	filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex",
    64  		"ModifyIndex", "JobModifyIndex", "Update", "SubmitTime", "NomadTokenID"}
    65  
    66  	if j == nil && other == nil {
    67  		return diff, nil
    68  	} else if j == nil {
    69  		j = &Job{}
    70  		diff.Type = DiffTypeAdded
    71  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
    72  		diff.ID = other.ID
    73  	} else if other == nil {
    74  		other = &Job{}
    75  		diff.Type = DiffTypeDeleted
    76  		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
    77  		diff.ID = j.ID
    78  	} else {
    79  		if j.ID != other.ID {
    80  			return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID)
    81  		}
    82  
    83  		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
    84  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
    85  		diff.ID = other.ID
    86  	}
    87  
    88  	// Diff the primitive fields.
    89  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
    90  
    91  	// Datacenters diff
    92  	if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone {
    93  		diff.Objects = append(diff.Objects, setDiff)
    94  	}
    95  
    96  	// Constraints diff
    97  	conDiff := primitiveObjectSetDiff(
    98  		interfaceSlice(j.Constraints),
    99  		interfaceSlice(other.Constraints),
   100  		[]string{"str"},
   101  		"Constraint",
   102  		contextual)
   103  	if conDiff != nil {
   104  		diff.Objects = append(diff.Objects, conDiff...)
   105  	}
   106  
   107  	// Affinities diff
   108  	affinitiesDiff := primitiveObjectSetDiff(
   109  		interfaceSlice(j.Affinities),
   110  		interfaceSlice(other.Affinities),
   111  		[]string{"str"},
   112  		"Affinity",
   113  		contextual)
   114  	if affinitiesDiff != nil {
   115  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   116  	}
   117  
   118  	// Task groups diff
   119  	tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	diff.TaskGroups = tgs
   124  
   125  	// Periodic diff
   126  	if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil {
   127  		diff.Objects = append(diff.Objects, pDiff)
   128  	}
   129  
   130  	// ParameterizedJob diff
   131  	if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil {
   132  		diff.Objects = append(diff.Objects, cDiff)
   133  	}
   134  
   135  	// Multiregion diff
   136  	if mrDiff := multiregionDiff(j.Multiregion, other.Multiregion, contextual); mrDiff != nil {
   137  		diff.Objects = append(diff.Objects, mrDiff)
   138  	}
   139  
   140  	// Check to see if there is a diff. We don't use reflect because we are
   141  	// filtering quite a few fields that will change on each diff.
   142  	if diff.Type == DiffTypeNone {
   143  		for _, fd := range diff.Fields {
   144  			if fd.Type != DiffTypeNone {
   145  				diff.Type = DiffTypeEdited
   146  				break
   147  			}
   148  		}
   149  	}
   150  
   151  	if diff.Type == DiffTypeNone {
   152  		for _, od := range diff.Objects {
   153  			if od.Type != DiffTypeNone {
   154  				diff.Type = DiffTypeEdited
   155  				break
   156  			}
   157  		}
   158  	}
   159  
   160  	if diff.Type == DiffTypeNone {
   161  		for _, tg := range diff.TaskGroups {
   162  			if tg.Type != DiffTypeNone {
   163  				diff.Type = DiffTypeEdited
   164  				break
   165  			}
   166  		}
   167  	}
   168  
   169  	return diff, nil
   170  }
   171  
   172  func (j *JobDiff) GoString() string {
   173  	out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type)
   174  
   175  	for _, f := range j.Fields {
   176  		out += fmt.Sprintf("%#v\n", f)
   177  	}
   178  
   179  	for _, o := range j.Objects {
   180  		out += fmt.Sprintf("%#v\n", o)
   181  	}
   182  
   183  	for _, tg := range j.TaskGroups {
   184  		out += fmt.Sprintf("%#v\n", tg)
   185  	}
   186  
   187  	return out
   188  }
   189  
   190  // TaskGroupDiff contains the diff of two task groups.
   191  type TaskGroupDiff struct {
   192  	Type    DiffType
   193  	Name    string
   194  	Fields  []*FieldDiff
   195  	Objects []*ObjectDiff
   196  	Tasks   []*TaskDiff
   197  	Updates map[string]uint64
   198  }
   199  
   200  // Diff returns a diff of two task groups. If contextual diff is enabled,
   201  // objects' fields will be stored even if no diff occurred as long as one field
   202  // changed.
   203  func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) {
   204  	diff := &TaskGroupDiff{Type: DiffTypeNone}
   205  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   206  	filter := []string{"Name"}
   207  
   208  	if tg == nil && other == nil {
   209  		return diff, nil
   210  	} else if tg == nil {
   211  		tg = &TaskGroup{}
   212  		diff.Type = DiffTypeAdded
   213  		diff.Name = other.Name
   214  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   215  	} else if other == nil {
   216  		other = &TaskGroup{}
   217  		diff.Type = DiffTypeDeleted
   218  		diff.Name = tg.Name
   219  		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
   220  	} else {
   221  		if !reflect.DeepEqual(tg, other) {
   222  			diff.Type = DiffTypeEdited
   223  		}
   224  		if tg.Name != other.Name {
   225  			return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name)
   226  		}
   227  		diff.Name = other.Name
   228  		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
   229  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   230  	}
   231  
   232  	// ShutdownDelay diff
   233  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
   234  		if tg.ShutdownDelay == nil {
   235  			oldPrimitiveFlat["ShutdownDelay"] = ""
   236  		} else {
   237  			oldPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *tg.ShutdownDelay)
   238  		}
   239  		if other.ShutdownDelay == nil {
   240  			newPrimitiveFlat["ShutdownDelay"] = ""
   241  		} else {
   242  			newPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *other.ShutdownDelay)
   243  		}
   244  	}
   245  
   246  	// StopAfterClientDisconnect diff
   247  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
   248  		if tg.StopAfterClientDisconnect == nil {
   249  			oldPrimitiveFlat["StopAfterClientDisconnect"] = ""
   250  		} else {
   251  			oldPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *tg.StopAfterClientDisconnect)
   252  		}
   253  		if other.StopAfterClientDisconnect == nil {
   254  			newPrimitiveFlat["StopAfterClientDisconnect"] = ""
   255  		} else {
   256  			newPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *other.StopAfterClientDisconnect)
   257  		}
   258  	}
   259  
   260  	// Diff the primitive fields.
   261  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   262  
   263  	// Constraints diff
   264  	conDiff := primitiveObjectSetDiff(
   265  		interfaceSlice(tg.Constraints),
   266  		interfaceSlice(other.Constraints),
   267  		[]string{"str"},
   268  		"Constraint",
   269  		contextual)
   270  	if conDiff != nil {
   271  		diff.Objects = append(diff.Objects, conDiff...)
   272  	}
   273  
   274  	// Affinities diff
   275  	affinitiesDiff := primitiveObjectSetDiff(
   276  		interfaceSlice(tg.Affinities),
   277  		interfaceSlice(other.Affinities),
   278  		[]string{"str"},
   279  		"Affinity",
   280  		contextual)
   281  	if affinitiesDiff != nil {
   282  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   283  	}
   284  
   285  	// Restart policy diff
   286  	rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual)
   287  	if rDiff != nil {
   288  		diff.Objects = append(diff.Objects, rDiff)
   289  	}
   290  
   291  	// Reschedule policy diff
   292  	reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual)
   293  	if reschedDiff != nil {
   294  		diff.Objects = append(diff.Objects, reschedDiff)
   295  	}
   296  
   297  	// EphemeralDisk diff
   298  	diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual)
   299  	if diskDiff != nil {
   300  		diff.Objects = append(diff.Objects, diskDiff)
   301  	}
   302  
   303  	// Update diff
   304  	// COMPAT: Remove "Stagger" in 0.7.0.
   305  	if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil {
   306  		diff.Objects = append(diff.Objects, uDiff)
   307  	}
   308  
   309  	// Network Resources diff
   310  	if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil {
   311  		diff.Objects = append(diff.Objects, nDiffs...)
   312  	}
   313  
   314  	// Services diff
   315  	if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil {
   316  		diff.Objects = append(diff.Objects, sDiffs...)
   317  	}
   318  
   319  	// Volumes diff
   320  	if vDiffs := volumeDiffs(tg.Volumes, other.Volumes, contextual); vDiffs != nil {
   321  		diff.Objects = append(diff.Objects, vDiffs...)
   322  	}
   323  
   324  	// Tasks diff
   325  	tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	diff.Tasks = tasks
   330  
   331  	return diff, nil
   332  }
   333  
   334  func (tg *TaskGroupDiff) GoString() string {
   335  	out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type)
   336  
   337  	if len(tg.Updates) != 0 {
   338  		out += "Updates {\n"
   339  		for update, count := range tg.Updates {
   340  			out += fmt.Sprintf("%d %s\n", count, update)
   341  		}
   342  		out += "}\n"
   343  	}
   344  
   345  	for _, f := range tg.Fields {
   346  		out += fmt.Sprintf("%#v\n", f)
   347  	}
   348  
   349  	for _, o := range tg.Objects {
   350  		out += fmt.Sprintf("%#v\n", o)
   351  	}
   352  
   353  	for _, t := range tg.Tasks {
   354  		out += fmt.Sprintf("%#v\n", t)
   355  	}
   356  
   357  	return out
   358  }
   359  
   360  // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled,
   361  // objects' fields will be stored even if no diff occurred as long as one field
   362  // changed.
   363  func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) {
   364  	oldMap := make(map[string]*TaskGroup, len(old))
   365  	newMap := make(map[string]*TaskGroup, len(new))
   366  	for _, o := range old {
   367  		oldMap[o.Name] = o
   368  	}
   369  	for _, n := range new {
   370  		newMap[n.Name] = n
   371  	}
   372  
   373  	var diffs []*TaskGroupDiff
   374  	for name, oldGroup := range oldMap {
   375  		// Diff the same, deleted and edited
   376  		diff, err := oldGroup.Diff(newMap[name], contextual)
   377  		if err != nil {
   378  			return nil, err
   379  		}
   380  		diffs = append(diffs, diff)
   381  	}
   382  
   383  	for name, newGroup := range newMap {
   384  		// Diff the added
   385  		if old, ok := oldMap[name]; !ok {
   386  			diff, err := old.Diff(newGroup, contextual)
   387  			if err != nil {
   388  				return nil, err
   389  			}
   390  			diffs = append(diffs, diff)
   391  		}
   392  	}
   393  
   394  	sort.Sort(TaskGroupDiffs(diffs))
   395  	return diffs, nil
   396  }
   397  
   398  // For sorting TaskGroupDiffs
   399  type TaskGroupDiffs []*TaskGroupDiff
   400  
   401  func (tg TaskGroupDiffs) Len() int           { return len(tg) }
   402  func (tg TaskGroupDiffs) Swap(i, j int)      { tg[i], tg[j] = tg[j], tg[i] }
   403  func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name }
   404  
   405  // TaskDiff contains the diff of two Tasks
   406  type TaskDiff struct {
   407  	Type        DiffType
   408  	Name        string
   409  	Fields      []*FieldDiff
   410  	Objects     []*ObjectDiff
   411  	Annotations []string
   412  }
   413  
   414  // Diff returns a diff of two tasks. If contextual diff is enabled, objects
   415  // within the task will contain field information even if unchanged.
   416  func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
   417  	diff := &TaskDiff{Type: DiffTypeNone}
   418  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   419  	filter := []string{"Name", "Config"}
   420  
   421  	if t == nil && other == nil {
   422  		return diff, nil
   423  	} else if t == nil {
   424  		t = &Task{}
   425  		diff.Type = DiffTypeAdded
   426  		diff.Name = other.Name
   427  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   428  	} else if other == nil {
   429  		other = &Task{}
   430  		diff.Type = DiffTypeDeleted
   431  		diff.Name = t.Name
   432  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   433  	} else {
   434  		if !reflect.DeepEqual(t, other) {
   435  			diff.Type = DiffTypeEdited
   436  		}
   437  		if t.Name != other.Name {
   438  			return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name)
   439  		}
   440  		diff.Name = other.Name
   441  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   442  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   443  	}
   444  
   445  	// Diff the primitive fields.
   446  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   447  
   448  	// Constraints diff
   449  	conDiff := primitiveObjectSetDiff(
   450  		interfaceSlice(t.Constraints),
   451  		interfaceSlice(other.Constraints),
   452  		[]string{"str"},
   453  		"Constraint",
   454  		contextual)
   455  	if conDiff != nil {
   456  		diff.Objects = append(diff.Objects, conDiff...)
   457  	}
   458  
   459  	// Affinities diff
   460  	affinitiesDiff := primitiveObjectSetDiff(
   461  		interfaceSlice(t.Affinities),
   462  		interfaceSlice(other.Affinities),
   463  		[]string{"str"},
   464  		"Affinity",
   465  		contextual)
   466  	if affinitiesDiff != nil {
   467  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   468  	}
   469  
   470  	// Config diff
   471  	if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil {
   472  		diff.Objects = append(diff.Objects, cDiff)
   473  	}
   474  
   475  	// Resources diff
   476  	if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil {
   477  		diff.Objects = append(diff.Objects, rDiff)
   478  	}
   479  
   480  	// LogConfig diff
   481  	lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual)
   482  	if lDiff != nil {
   483  		diff.Objects = append(diff.Objects, lDiff)
   484  	}
   485  
   486  	// Dispatch payload diff
   487  	dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual)
   488  	if dDiff != nil {
   489  		diff.Objects = append(diff.Objects, dDiff)
   490  	}
   491  
   492  	// Artifacts diff
   493  	diffs := primitiveObjectSetDiff(
   494  		interfaceSlice(t.Artifacts),
   495  		interfaceSlice(other.Artifacts),
   496  		nil,
   497  		"Artifact",
   498  		contextual)
   499  	if diffs != nil {
   500  		diff.Objects = append(diff.Objects, diffs...)
   501  	}
   502  
   503  	// Services diff
   504  	if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil {
   505  		diff.Objects = append(diff.Objects, sDiffs...)
   506  	}
   507  
   508  	// Vault diff
   509  	vDiff := vaultDiff(t.Vault, other.Vault, contextual)
   510  	if vDiff != nil {
   511  		diff.Objects = append(diff.Objects, vDiff)
   512  	}
   513  
   514  	// Template diff
   515  	tmplDiffs := primitiveObjectSetDiff(
   516  		interfaceSlice(t.Templates),
   517  		interfaceSlice(other.Templates),
   518  		nil,
   519  		"Template",
   520  		contextual)
   521  	if tmplDiffs != nil {
   522  		diff.Objects = append(diff.Objects, tmplDiffs...)
   523  	}
   524  
   525  	return diff, nil
   526  }
   527  
   528  func (t *TaskDiff) GoString() string {
   529  	var out string
   530  	if len(t.Annotations) == 0 {
   531  		out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type)
   532  	} else {
   533  		out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ","))
   534  	}
   535  
   536  	for _, f := range t.Fields {
   537  		out += fmt.Sprintf("%#v\n", f)
   538  	}
   539  
   540  	for _, o := range t.Objects {
   541  		out += fmt.Sprintf("%#v\n", o)
   542  	}
   543  
   544  	return out
   545  }
   546  
   547  // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged
   548  // fields within objects nested in the tasks will be returned.
   549  func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) {
   550  	oldMap := make(map[string]*Task, len(old))
   551  	newMap := make(map[string]*Task, len(new))
   552  	for _, o := range old {
   553  		oldMap[o.Name] = o
   554  	}
   555  	for _, n := range new {
   556  		newMap[n.Name] = n
   557  	}
   558  
   559  	var diffs []*TaskDiff
   560  	for name, oldGroup := range oldMap {
   561  		// Diff the same, deleted and edited
   562  		diff, err := oldGroup.Diff(newMap[name], contextual)
   563  		if err != nil {
   564  			return nil, err
   565  		}
   566  		diffs = append(diffs, diff)
   567  	}
   568  
   569  	for name, newGroup := range newMap {
   570  		// Diff the added
   571  		if old, ok := oldMap[name]; !ok {
   572  			diff, err := old.Diff(newGroup, contextual)
   573  			if err != nil {
   574  				return nil, err
   575  			}
   576  			diffs = append(diffs, diff)
   577  		}
   578  	}
   579  
   580  	sort.Sort(TaskDiffs(diffs))
   581  	return diffs, nil
   582  }
   583  
   584  // For sorting TaskDiffs
   585  type TaskDiffs []*TaskDiff
   586  
   587  func (t TaskDiffs) Len() int           { return len(t) }
   588  func (t TaskDiffs) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   589  func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name }
   590  
   591  // serviceDiff returns the diff of two service objects. If contextual diff is
   592  // enabled, all fields will be returned, even if no diff occurred.
   593  func serviceDiff(old, new *Service, contextual bool) *ObjectDiff {
   594  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
   595  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   596  
   597  	if reflect.DeepEqual(old, new) {
   598  		return nil
   599  	} else if old == nil {
   600  		old = &Service{}
   601  		diff.Type = DiffTypeAdded
   602  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   603  	} else if new == nil {
   604  		new = &Service{}
   605  		diff.Type = DiffTypeDeleted
   606  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   607  	} else {
   608  		diff.Type = DiffTypeEdited
   609  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   610  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   611  	}
   612  
   613  	// Diff the primitive fields.
   614  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   615  
   616  	if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil {
   617  		diff.Objects = append(diff.Objects, setDiff)
   618  	}
   619  
   620  	// Tag diffs
   621  	if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil {
   622  		diff.Objects = append(diff.Objects, setDiff)
   623  	}
   624  
   625  	// Checks diffs
   626  	if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil {
   627  		diff.Objects = append(diff.Objects, cDiffs...)
   628  	}
   629  
   630  	// Consul Connect diffs
   631  	if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil {
   632  		diff.Objects = append(diff.Objects, conDiffs)
   633  	}
   634  
   635  	return diff
   636  }
   637  
   638  // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
   639  // fields within objects nested in the tasks will be returned.
   640  func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff {
   641  	oldMap := make(map[string]*Service, len(old))
   642  	newMap := make(map[string]*Service, len(new))
   643  	for _, o := range old {
   644  		oldMap[o.Name] = o
   645  	}
   646  	for _, n := range new {
   647  		newMap[n.Name] = n
   648  	}
   649  
   650  	var diffs []*ObjectDiff
   651  	for name, oldService := range oldMap {
   652  		// Diff the same, deleted and edited
   653  		if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil {
   654  			diffs = append(diffs, diff)
   655  		}
   656  	}
   657  
   658  	for name, newService := range newMap {
   659  		// Diff the added
   660  		if old, ok := oldMap[name]; !ok {
   661  			if diff := serviceDiff(old, newService, contextual); diff != nil {
   662  				diffs = append(diffs, diff)
   663  			}
   664  		}
   665  	}
   666  
   667  	sort.Sort(ObjectDiffs(diffs))
   668  	return diffs
   669  }
   670  
   671  // serviceCheckDiff returns the diff of two service check objects. If contextual
   672  // diff is enabled, all fields will be returned, even if no diff occurred.
   673  func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff {
   674  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"}
   675  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   676  
   677  	if reflect.DeepEqual(old, new) {
   678  		return nil
   679  	} else if old == nil {
   680  		old = &ServiceCheck{}
   681  		diff.Type = DiffTypeAdded
   682  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   683  	} else if new == nil {
   684  		new = &ServiceCheck{}
   685  		diff.Type = DiffTypeDeleted
   686  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   687  	} else {
   688  		diff.Type = DiffTypeEdited
   689  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   690  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   691  	}
   692  
   693  	// Diff the primitive fields.
   694  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   695  
   696  	// Diff Header
   697  	if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil {
   698  		diff.Objects = append(diff.Objects, headerDiff)
   699  	}
   700  
   701  	// Diff check_restart
   702  	if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil {
   703  		diff.Objects = append(diff.Objects, crDiff)
   704  	}
   705  
   706  	return diff
   707  }
   708  
   709  // checkHeaderDiff returns the diff of two service check header objects. If
   710  // contextual diff is enabled, all fields will be returned, even if no diff
   711  // occurred.
   712  func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff {
   713  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"}
   714  	var oldFlat, newFlat map[string]string
   715  
   716  	if reflect.DeepEqual(old, new) {
   717  		return nil
   718  	} else if len(old) == 0 {
   719  		diff.Type = DiffTypeAdded
   720  		newFlat = flatmap.Flatten(new, nil, false)
   721  	} else if len(new) == 0 {
   722  		diff.Type = DiffTypeDeleted
   723  		oldFlat = flatmap.Flatten(old, nil, false)
   724  	} else {
   725  		diff.Type = DiffTypeEdited
   726  		oldFlat = flatmap.Flatten(old, nil, false)
   727  		newFlat = flatmap.Flatten(new, nil, false)
   728  	}
   729  
   730  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   731  	return diff
   732  }
   733  
   734  // checkRestartDiff returns the diff of two service check check_restart
   735  // objects. If contextual diff is enabled, all fields will be returned, even if
   736  // no diff occurred.
   737  func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff {
   738  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"}
   739  	var oldFlat, newFlat map[string]string
   740  
   741  	if reflect.DeepEqual(old, new) {
   742  		return nil
   743  	} else if old == nil {
   744  		diff.Type = DiffTypeAdded
   745  		newFlat = flatmap.Flatten(new, nil, true)
   746  		diff.Type = DiffTypeAdded
   747  	} else if new == nil {
   748  		diff.Type = DiffTypeDeleted
   749  		oldFlat = flatmap.Flatten(old, nil, true)
   750  	} else {
   751  		diff.Type = DiffTypeEdited
   752  		oldFlat = flatmap.Flatten(old, nil, true)
   753  		newFlat = flatmap.Flatten(new, nil, true)
   754  	}
   755  
   756  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   757  	return diff
   758  }
   759  
   760  // connectDiffs returns the diff of two Consul connect objects. If contextual
   761  // diff is enabled, all fields will be returned, even if no diff occurred.
   762  func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff {
   763  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"}
   764  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   765  
   766  	if reflect.DeepEqual(old, new) {
   767  		return nil
   768  	} else if old == nil {
   769  		old = &ConsulConnect{}
   770  		diff.Type = DiffTypeAdded
   771  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   772  	} else if new == nil {
   773  		new = &ConsulConnect{}
   774  		diff.Type = DiffTypeDeleted
   775  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   776  	} else {
   777  		diff.Type = DiffTypeEdited
   778  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   779  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   780  	}
   781  
   782  	// Diff the primitive fields.
   783  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   784  
   785  	// Diff the object field SidecarService.
   786  	sidecarSvcDiff := connectSidecarServiceDiff(old.SidecarService, new.SidecarService, contextual)
   787  	if sidecarSvcDiff != nil {
   788  		diff.Objects = append(diff.Objects, sidecarSvcDiff)
   789  	}
   790  
   791  	// Diff the object field SidecarTask.
   792  	sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual)
   793  	if sidecarTaskDiff != nil {
   794  		diff.Objects = append(diff.Objects, sidecarTaskDiff)
   795  	}
   796  
   797  	// Diff the object field ConsulGateway.
   798  	gatewayDiff := connectGatewayDiff(old.Gateway, new.Gateway, contextual)
   799  	if gatewayDiff != nil {
   800  		diff.Objects = append(diff.Objects, gatewayDiff)
   801  	}
   802  
   803  	return diff
   804  }
   805  
   806  func connectGatewayDiff(prev, next *ConsulGateway, contextual bool) *ObjectDiff {
   807  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Gateway"}
   808  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   809  
   810  	if reflect.DeepEqual(prev, next) {
   811  		return nil
   812  	} else if prev == nil {
   813  		prev = new(ConsulGateway)
   814  		diff.Type = DiffTypeAdded
   815  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   816  	} else if next == nil {
   817  		next = new(ConsulGateway)
   818  		diff.Type = DiffTypeDeleted
   819  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   820  	} else {
   821  		diff.Type = DiffTypeEdited
   822  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   823  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   824  	}
   825  
   826  	// Diff the primitive fields.
   827  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   828  
   829  	// Diff the ConsulGatewayProxy fields.
   830  	gatewayProxyDiff := connectGatewayProxyDiff(prev.Proxy, next.Proxy, contextual)
   831  	if gatewayProxyDiff != nil {
   832  		diff.Objects = append(diff.Objects, gatewayProxyDiff)
   833  	}
   834  
   835  	// Diff the ingress gateway fields.
   836  	gatewayIngressDiff := connectGatewayIngressDiff(prev.Ingress, next.Ingress, contextual)
   837  	if gatewayIngressDiff != nil {
   838  		diff.Objects = append(diff.Objects, gatewayIngressDiff)
   839  	}
   840  
   841  	//  Diff the terminating gateway fields.
   842  	gatewayTerminatingDiff := connectGatewayTerminatingDiff(prev.Terminating, next.Terminating, contextual)
   843  	if gatewayTerminatingDiff != nil {
   844  		diff.Objects = append(diff.Objects, gatewayTerminatingDiff)
   845  	}
   846  
   847  	return diff
   848  }
   849  
   850  func connectGatewayIngressDiff(prev, next *ConsulIngressConfigEntry, contextual bool) *ObjectDiff {
   851  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Ingress"}
   852  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   853  
   854  	if reflect.DeepEqual(prev, next) {
   855  		return nil
   856  	} else if prev == nil {
   857  		prev = new(ConsulIngressConfigEntry)
   858  		diff.Type = DiffTypeAdded
   859  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   860  	} else if next == nil {
   861  		next = new(ConsulIngressConfigEntry)
   862  		diff.Type = DiffTypeDeleted
   863  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   864  	} else {
   865  		diff.Type = DiffTypeEdited
   866  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   867  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   868  	}
   869  
   870  	// Diff the primitive fields.
   871  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   872  
   873  	// Diff the ConsulGatewayTLSConfig objects.
   874  	tlsConfigDiff := connectGatewayTLSConfigDiff(prev.TLS, next.TLS, contextual)
   875  	if tlsConfigDiff != nil {
   876  		diff.Objects = append(diff.Objects, tlsConfigDiff)
   877  	}
   878  
   879  	// Diff the Listeners lists.
   880  	gatewayIngressListenersDiff := connectGatewayIngressListenersDiff(prev.Listeners, next.Listeners, contextual)
   881  	if gatewayIngressListenersDiff != nil {
   882  		diff.Objects = append(diff.Objects, gatewayIngressListenersDiff...)
   883  	}
   884  
   885  	return diff
   886  }
   887  
   888  func connectGatewayTerminatingDiff(prev, next *ConsulTerminatingConfigEntry, contextual bool) *ObjectDiff {
   889  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Terminating"}
   890  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   891  
   892  	if reflect.DeepEqual(prev, next) {
   893  		return nil
   894  	} else if prev == nil {
   895  		prev = new(ConsulTerminatingConfigEntry)
   896  		diff.Type = DiffTypeAdded
   897  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   898  	} else if next == nil {
   899  		next = new(ConsulTerminatingConfigEntry)
   900  		diff.Type = DiffTypeDeleted
   901  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   902  	} else {
   903  		diff.Type = DiffTypeEdited
   904  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   905  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   906  	}
   907  
   908  	// Diff the primitive fields.
   909  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   910  
   911  	// Diff the Services lists.
   912  	gatewayLinkedServicesDiff := connectGatewayTerminatingLinkedServicesDiff(prev.Services, next.Services, contextual)
   913  	if gatewayLinkedServicesDiff != nil {
   914  		diff.Objects = append(diff.Objects, gatewayLinkedServicesDiff...)
   915  	}
   916  
   917  	return diff
   918  }
   919  
   920  // connectGatewayTerminatingLinkedServicesDiff diffs are a set of services keyed
   921  // by service name. These objects contain only fields.
   922  func connectGatewayTerminatingLinkedServicesDiff(prev, next []*ConsulLinkedService, contextual bool) []*ObjectDiff {
   923  	// create maps, diff the maps, key by linked service name
   924  
   925  	prevMap := make(map[string]*ConsulLinkedService, len(prev))
   926  	nextMap := make(map[string]*ConsulLinkedService, len(next))
   927  
   928  	for _, s := range prev {
   929  		prevMap[s.Name] = s
   930  	}
   931  	for _, s := range next {
   932  		nextMap[s.Name] = s
   933  	}
   934  
   935  	var diffs []*ObjectDiff
   936  	for k, prevS := range prevMap {
   937  		// Diff the same, deleted, and edited
   938  		if diff := connectGatewayTerminatingLinkedServiceDiff(prevS, nextMap[k], contextual); diff != nil {
   939  			diffs = append(diffs, diff)
   940  		}
   941  	}
   942  	for k, nextS := range nextMap {
   943  		// Diff the added
   944  		if old, ok := prevMap[k]; !ok {
   945  			if diff := connectGatewayTerminatingLinkedServiceDiff(old, nextS, contextual); diff != nil {
   946  				diffs = append(diffs, diff)
   947  			}
   948  		}
   949  	}
   950  
   951  	sort.Sort(ObjectDiffs(diffs))
   952  	return diffs
   953  }
   954  
   955  func connectGatewayTerminatingLinkedServiceDiff(prev, next *ConsulLinkedService, contextual bool) *ObjectDiff {
   956  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
   957  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   958  
   959  	if reflect.DeepEqual(prev, next) {
   960  		return nil
   961  	} else if prev == nil {
   962  		diff.Type = DiffTypeAdded
   963  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   964  	} else if next == nil {
   965  		diff.Type = DiffTypeDeleted
   966  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   967  	} else {
   968  		diff.Type = DiffTypeEdited
   969  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   970  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   971  	}
   972  
   973  	// Diff the primitive fields.
   974  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   975  
   976  	// No objects today.
   977  
   978  	return diff
   979  }
   980  
   981  func connectGatewayTLSConfigDiff(prev, next *ConsulGatewayTLSConfig, contextual bool) *ObjectDiff {
   982  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "TLS"}
   983  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   984  
   985  	if reflect.DeepEqual(prev, next) {
   986  		return nil
   987  	} else if prev == nil {
   988  		diff.Type = DiffTypeAdded
   989  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   990  	} else if next == nil {
   991  		diff.Type = DiffTypeDeleted
   992  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   993  	} else {
   994  		diff.Type = DiffTypeEdited
   995  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   996  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   997  	}
   998  
   999  	// Diff the primitive field.
  1000  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1001  
  1002  	return diff
  1003  }
  1004  
  1005  // connectGatewayIngressListenersDiff diffs are a set of listeners keyed by "protocol/port", which is
  1006  // a nifty workaround having slices instead of maps. Presumably such a key will be unique, because if
  1007  // if is not the config entry is not going to work anyway.
  1008  func connectGatewayIngressListenersDiff(prev, next []*ConsulIngressListener, contextual bool) []*ObjectDiff {
  1009  	//  create maps, diff the maps, keys are fields, keys are (port+protocol)
  1010  
  1011  	key := func(l *ConsulIngressListener) string {
  1012  		return fmt.Sprintf("%s/%d", l.Protocol, l.Port)
  1013  	}
  1014  
  1015  	prevMap := make(map[string]*ConsulIngressListener, len(prev))
  1016  	nextMap := make(map[string]*ConsulIngressListener, len(next))
  1017  
  1018  	for _, l := range prev {
  1019  		prevMap[key(l)] = l
  1020  	}
  1021  	for _, l := range next {
  1022  		nextMap[key(l)] = l
  1023  	}
  1024  
  1025  	var diffs []*ObjectDiff
  1026  	for k, prevL := range prevMap {
  1027  		// Diff the same, deleted, and edited
  1028  		if diff := connectGatewayIngressListenerDiff(prevL, nextMap[k], contextual); diff != nil {
  1029  			diffs = append(diffs, diff)
  1030  		}
  1031  	}
  1032  	for k, nextL := range nextMap {
  1033  		// Diff the added
  1034  		if old, ok := prevMap[k]; !ok {
  1035  			if diff := connectGatewayIngressListenerDiff(old, nextL, contextual); diff != nil {
  1036  				diffs = append(diffs, diff)
  1037  			}
  1038  		}
  1039  	}
  1040  
  1041  	sort.Sort(ObjectDiffs(diffs))
  1042  	return diffs
  1043  }
  1044  
  1045  func connectGatewayIngressListenerDiff(prev, next *ConsulIngressListener, contextual bool) *ObjectDiff {
  1046  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Listener"}
  1047  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1048  
  1049  	if reflect.DeepEqual(prev, next) {
  1050  		return nil
  1051  	} else if prev == nil {
  1052  		prev = new(ConsulIngressListener)
  1053  		diff.Type = DiffTypeAdded
  1054  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1055  	} else if next == nil {
  1056  		next = new(ConsulIngressListener)
  1057  		diff.Type = DiffTypeDeleted
  1058  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1059  	} else {
  1060  		diff.Type = DiffTypeEdited
  1061  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1062  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1063  	}
  1064  
  1065  	// Diff the primitive fields.
  1066  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1067  
  1068  	// Diff the Ingress Service objects.
  1069  	if diffs := connectGatewayIngressServicesDiff(prev.Services, next.Services, contextual); diffs != nil {
  1070  		diff.Objects = append(diff.Objects, diffs...)
  1071  	}
  1072  
  1073  	return diff
  1074  }
  1075  
  1076  // connectGatewayIngressServicesDiff diffs are a set of ingress services keyed by their service name, which
  1077  // is a workaround for having slices instead of maps. Presumably the service name is a unique key, because if
  1078  // no the config entry is not going to make sense anyway.
  1079  func connectGatewayIngressServicesDiff(prev, next []*ConsulIngressService, contextual bool) []*ObjectDiff {
  1080  
  1081  	prevMap := make(map[string]*ConsulIngressService, len(prev))
  1082  	nextMap := make(map[string]*ConsulIngressService, len(next))
  1083  
  1084  	for _, s := range prev {
  1085  		prevMap[s.Name] = s
  1086  	}
  1087  	for _, s := range next {
  1088  		nextMap[s.Name] = s
  1089  	}
  1090  
  1091  	var diffs []*ObjectDiff
  1092  	for name, oldIS := range prevMap {
  1093  		// Diff the same, deleted, and edited
  1094  		if diff := connectGatewayIngressServiceDiff(oldIS, nextMap[name], contextual); diff != nil {
  1095  			diffs = append(diffs, diff)
  1096  		}
  1097  	}
  1098  	for name, newIS := range nextMap {
  1099  		// Diff the added
  1100  		if old, ok := prevMap[name]; !ok {
  1101  			if diff := connectGatewayIngressServiceDiff(old, newIS, contextual); diff != nil {
  1102  				diffs = append(diffs, diff)
  1103  			}
  1104  		}
  1105  	}
  1106  
  1107  	sort.Sort(ObjectDiffs(diffs))
  1108  	return diffs
  1109  }
  1110  
  1111  func connectGatewayIngressServiceDiff(prev, next *ConsulIngressService, contextual bool) *ObjectDiff {
  1112  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulIngressService"}
  1113  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1114  
  1115  	if reflect.DeepEqual(prev, next) {
  1116  		return nil
  1117  	} else if prev == nil {
  1118  		prev = new(ConsulIngressService)
  1119  		diff.Type = DiffTypeAdded
  1120  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1121  	} else if next == nil {
  1122  		next = new(ConsulIngressService)
  1123  		diff.Type = DiffTypeDeleted
  1124  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1125  	} else {
  1126  		diff.Type = DiffTypeEdited
  1127  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1128  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1129  	}
  1130  
  1131  	// Diff the primitive fields.
  1132  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1133  
  1134  	// Diff the hosts.
  1135  	if hDiffs := stringSetDiff(prev.Hosts, next.Hosts, "Hosts", contextual); hDiffs != nil {
  1136  		diff.Objects = append(diff.Objects, hDiffs)
  1137  	}
  1138  
  1139  	return diff
  1140  }
  1141  
  1142  func connectGatewayProxyDiff(prev, next *ConsulGatewayProxy, contextual bool) *ObjectDiff {
  1143  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Proxy"}
  1144  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1145  
  1146  	if reflect.DeepEqual(prev, next) {
  1147  		return nil
  1148  	} else if prev == nil {
  1149  		prev = new(ConsulGatewayProxy)
  1150  		diff.Type = DiffTypeAdded
  1151  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1152  	} else if next == nil {
  1153  		next = new(ConsulGatewayProxy)
  1154  		diff.Type = DiffTypeDeleted
  1155  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1156  	} else {
  1157  		diff.Type = DiffTypeEdited
  1158  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1159  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1160  	}
  1161  
  1162  	// Diff the ConnectTimeout field (dur ptr). (i.e. convert to string for comparison)
  1163  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
  1164  		if prev.ConnectTimeout == nil {
  1165  			oldPrimitiveFlat["ConnectTimeout"] = ""
  1166  		} else {
  1167  			oldPrimitiveFlat["ConnectTimeout"] = prev.ConnectTimeout.String()
  1168  		}
  1169  		if next.ConnectTimeout == nil {
  1170  			newPrimitiveFlat["ConnectTimeout"] = ""
  1171  		} else {
  1172  			newPrimitiveFlat["ConnectTimeout"] = next.ConnectTimeout.String()
  1173  		}
  1174  	}
  1175  
  1176  	// Diff the primitive fields.
  1177  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1178  
  1179  	// Diff the EnvoyGatewayBindAddresses map.
  1180  	bindAddrsDiff := connectGatewayProxyEnvoyBindAddrsDiff(prev.EnvoyGatewayBindAddresses, next.EnvoyGatewayBindAddresses, contextual)
  1181  	if bindAddrsDiff != nil {
  1182  		diff.Objects = append(diff.Objects, bindAddrsDiff)
  1183  	}
  1184  
  1185  	// Diff the opaque Config map.
  1186  	if cDiff := configDiff(prev.Config, next.Config, contextual); cDiff != nil {
  1187  		diff.Objects = append(diff.Objects, cDiff)
  1188  	}
  1189  
  1190  	return diff
  1191  }
  1192  
  1193  // connectGatewayProxyEnvoyBindAddrsDiff returns the diff of two maps. If contextual
  1194  // diff is enabled, all fields will be returned, even if no diff occurred.
  1195  func connectGatewayProxyEnvoyBindAddrsDiff(prev, next map[string]*ConsulGatewayBindAddress, contextual bool) *ObjectDiff {
  1196  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "EnvoyGatewayBindAddresses"}
  1197  	if reflect.DeepEqual(prev, next) {
  1198  		return nil
  1199  	} else if len(prev) == 0 {
  1200  		diff.Type = DiffTypeAdded
  1201  	} else if len(next) == 0 {
  1202  		diff.Type = DiffTypeDeleted
  1203  	} else {
  1204  		diff.Type = DiffTypeEdited
  1205  	}
  1206  
  1207  	// convert to string representation
  1208  	prevMap := make(map[string]string, len(prev))
  1209  	nextMap := make(map[string]string, len(next))
  1210  
  1211  	for k, v := range prev {
  1212  		prevMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port)
  1213  	}
  1214  
  1215  	for k, v := range next {
  1216  		nextMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port)
  1217  	}
  1218  
  1219  	oldPrimitiveFlat := flatmap.Flatten(prevMap, nil, false)
  1220  	newPrimitiveFlat := flatmap.Flatten(nextMap, nil, false)
  1221  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1222  	return diff
  1223  }
  1224  
  1225  // connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects.
  1226  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1227  func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff {
  1228  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"}
  1229  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1230  
  1231  	if reflect.DeepEqual(old, new) {
  1232  		return nil
  1233  	} else if old == nil {
  1234  		old = &ConsulSidecarService{}
  1235  		diff.Type = DiffTypeAdded
  1236  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1237  	} else if new == nil {
  1238  		new = &ConsulSidecarService{}
  1239  		diff.Type = DiffTypeDeleted
  1240  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1241  	} else {
  1242  		diff.Type = DiffTypeEdited
  1243  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1244  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1245  	}
  1246  
  1247  	// Diff the primitive fields.
  1248  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1249  
  1250  	consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual)
  1251  	if consulProxyDiff != nil {
  1252  		diff.Objects = append(diff.Objects, consulProxyDiff)
  1253  	}
  1254  
  1255  	return diff
  1256  }
  1257  
  1258  // sidecarTaskDiff returns the diff of two Task objects.
  1259  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1260  func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff {
  1261  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"}
  1262  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1263  
  1264  	if reflect.DeepEqual(old, new) {
  1265  		return nil
  1266  	} else if old == nil {
  1267  		old = &SidecarTask{}
  1268  		diff.Type = DiffTypeAdded
  1269  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1270  	} else if new == nil {
  1271  		new = &SidecarTask{}
  1272  		diff.Type = DiffTypeDeleted
  1273  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1274  	} else {
  1275  		diff.Type = DiffTypeEdited
  1276  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1277  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1278  	}
  1279  
  1280  	// Diff the primitive fields.
  1281  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
  1282  
  1283  	// Config diff
  1284  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
  1285  		diff.Objects = append(diff.Objects, cDiff)
  1286  	}
  1287  
  1288  	// Resources diff
  1289  	if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil {
  1290  		diff.Objects = append(diff.Objects, rDiff)
  1291  	}
  1292  
  1293  	// LogConfig diff
  1294  	lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual)
  1295  	if lDiff != nil {
  1296  		diff.Objects = append(diff.Objects, lDiff)
  1297  	}
  1298  
  1299  	return diff
  1300  }
  1301  
  1302  // consulProxyDiff returns the diff of two ConsulProxy objects.
  1303  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1304  func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff {
  1305  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"}
  1306  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1307  
  1308  	if reflect.DeepEqual(old, new) {
  1309  		return nil
  1310  	} else if old == nil {
  1311  		old = &ConsulProxy{}
  1312  		diff.Type = DiffTypeAdded
  1313  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1314  	} else if new == nil {
  1315  		new = &ConsulProxy{}
  1316  		diff.Type = DiffTypeDeleted
  1317  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1318  	} else {
  1319  		diff.Type = DiffTypeEdited
  1320  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1321  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1322  	}
  1323  
  1324  	// Diff the primitive fields.
  1325  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1326  
  1327  	consulUpstreamsDiff := primitiveObjectSetDiff(
  1328  		interfaceSlice(old.Upstreams),
  1329  		interfaceSlice(new.Upstreams),
  1330  		nil, "ConsulUpstreams", contextual)
  1331  	if consulUpstreamsDiff != nil {
  1332  		diff.Objects = append(diff.Objects, consulUpstreamsDiff...)
  1333  	}
  1334  
  1335  	// Config diff
  1336  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
  1337  		diff.Objects = append(diff.Objects, cDiff)
  1338  	}
  1339  
  1340  	return diff
  1341  }
  1342  
  1343  // serviceCheckDiffs diffs a set of service checks. If contextual diff is
  1344  // enabled, unchanged fields within objects nested in the tasks will be
  1345  // returned.
  1346  func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff {
  1347  	oldMap := make(map[string]*ServiceCheck, len(old))
  1348  	newMap := make(map[string]*ServiceCheck, len(new))
  1349  	for _, o := range old {
  1350  		oldMap[o.Name] = o
  1351  	}
  1352  	for _, n := range new {
  1353  		newMap[n.Name] = n
  1354  	}
  1355  
  1356  	var diffs []*ObjectDiff
  1357  	for name, oldCheck := range oldMap {
  1358  		// Diff the same, deleted and edited
  1359  		if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil {
  1360  			diffs = append(diffs, diff)
  1361  		}
  1362  	}
  1363  
  1364  	for name, newCheck := range newMap {
  1365  		// Diff the added
  1366  		if old, ok := oldMap[name]; !ok {
  1367  			if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil {
  1368  				diffs = append(diffs, diff)
  1369  			}
  1370  		}
  1371  	}
  1372  
  1373  	sort.Sort(ObjectDiffs(diffs))
  1374  	return diffs
  1375  }
  1376  
  1377  // vaultDiff returns the diff of two vault objects. If contextual diff is
  1378  // enabled, all fields will be returned, even if no diff occurred.
  1379  func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff {
  1380  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"}
  1381  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1382  
  1383  	if reflect.DeepEqual(old, new) {
  1384  		return nil
  1385  	} else if old == nil {
  1386  		old = &Vault{}
  1387  		diff.Type = DiffTypeAdded
  1388  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1389  	} else if new == nil {
  1390  		new = &Vault{}
  1391  		diff.Type = DiffTypeDeleted
  1392  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1393  	} else {
  1394  		diff.Type = DiffTypeEdited
  1395  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1396  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1397  	}
  1398  
  1399  	// Diff the primitive fields.
  1400  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1401  
  1402  	// Policies diffs
  1403  	if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil {
  1404  		diff.Objects = append(diff.Objects, setDiff)
  1405  	}
  1406  
  1407  	return diff
  1408  }
  1409  
  1410  // parameterizedJobDiff returns the diff of two parameterized job objects. If
  1411  // contextual diff is enabled, all fields will be returned, even if no diff
  1412  // occurred.
  1413  func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff {
  1414  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"}
  1415  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1416  
  1417  	if reflect.DeepEqual(old, new) {
  1418  		return nil
  1419  	} else if old == nil {
  1420  		old = &ParameterizedJobConfig{}
  1421  		diff.Type = DiffTypeAdded
  1422  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1423  	} else if new == nil {
  1424  		new = &ParameterizedJobConfig{}
  1425  		diff.Type = DiffTypeDeleted
  1426  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1427  	} else {
  1428  		diff.Type = DiffTypeEdited
  1429  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1430  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1431  	}
  1432  
  1433  	// Diff the primitive fields.
  1434  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1435  
  1436  	// Meta diffs
  1437  	if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil {
  1438  		diff.Objects = append(diff.Objects, optionalDiff)
  1439  	}
  1440  
  1441  	if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil {
  1442  		diff.Objects = append(diff.Objects, requiredDiff)
  1443  	}
  1444  
  1445  	return diff
  1446  }
  1447  
  1448  func multiregionDiff(old, new *Multiregion, contextual bool) *ObjectDiff {
  1449  
  1450  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Multiregion"}
  1451  
  1452  	if reflect.DeepEqual(old, new) {
  1453  		return nil
  1454  	} else if old == nil {
  1455  		old = &Multiregion{}
  1456  		old.Canonicalize()
  1457  		diff.Type = DiffTypeAdded
  1458  	} else if new == nil {
  1459  		new = &Multiregion{}
  1460  		diff.Type = DiffTypeDeleted
  1461  	} else {
  1462  		diff.Type = DiffTypeEdited
  1463  	}
  1464  
  1465  	// strategy diff
  1466  	stratDiff := primitiveObjectDiff(
  1467  		old.Strategy,
  1468  		new.Strategy,
  1469  		[]string{},
  1470  		"Strategy",
  1471  		contextual)
  1472  	if stratDiff != nil {
  1473  		diff.Objects = append(diff.Objects, stratDiff)
  1474  	}
  1475  
  1476  	oldMap := make(map[string]*MultiregionRegion, len(old.Regions))
  1477  	newMap := make(map[string]*MultiregionRegion, len(new.Regions))
  1478  	for _, o := range old.Regions {
  1479  		oldMap[o.Name] = o
  1480  	}
  1481  	for _, n := range new.Regions {
  1482  		newMap[n.Name] = n
  1483  	}
  1484  
  1485  	for name, oldRegion := range oldMap {
  1486  		// Diff the same, deleted and edited
  1487  		newRegion := newMap[name]
  1488  		rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual)
  1489  		if rdiff != nil {
  1490  			diff.Objects = append(diff.Objects, rdiff)
  1491  		}
  1492  	}
  1493  
  1494  	for name, newRegion := range newMap {
  1495  		// Diff the added
  1496  		if oldRegion, ok := oldMap[name]; !ok {
  1497  			rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual)
  1498  			if rdiff != nil {
  1499  				diff.Objects = append(diff.Objects, rdiff)
  1500  			}
  1501  		}
  1502  	}
  1503  	sort.Sort(FieldDiffs(diff.Fields))
  1504  	sort.Sort(ObjectDiffs(diff.Objects))
  1505  	return diff
  1506  }
  1507  
  1508  func multiregionRegionDiff(r, other *MultiregionRegion, contextual bool) *ObjectDiff {
  1509  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Region"}
  1510  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1511  
  1512  	if reflect.DeepEqual(r, other) {
  1513  		return nil
  1514  	} else if r == nil {
  1515  		r = &MultiregionRegion{}
  1516  		diff.Type = DiffTypeAdded
  1517  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1518  	} else if other == nil {
  1519  		other = &MultiregionRegion{}
  1520  		diff.Type = DiffTypeDeleted
  1521  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1522  	} else {
  1523  		diff.Type = DiffTypeEdited
  1524  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1525  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1526  	}
  1527  
  1528  	// Diff the primitive fields.
  1529  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1530  
  1531  	// Datacenters diff
  1532  	setDiff := stringSetDiff(r.Datacenters, other.Datacenters, "Datacenters", contextual)
  1533  	if setDiff != nil && setDiff.Type != DiffTypeNone {
  1534  		diff.Objects = append(diff.Objects, setDiff)
  1535  	}
  1536  
  1537  	sort.Sort(ObjectDiffs(diff.Objects))
  1538  	sort.Sort(FieldDiffs(diff.Fields))
  1539  
  1540  	var added, deleted, edited bool
  1541  Loop:
  1542  	for _, f := range diff.Fields {
  1543  		switch f.Type {
  1544  		case DiffTypeEdited:
  1545  			edited = true
  1546  			break Loop
  1547  		case DiffTypeDeleted:
  1548  			deleted = true
  1549  		case DiffTypeAdded:
  1550  			added = true
  1551  		}
  1552  	}
  1553  
  1554  	if edited || added && deleted {
  1555  		diff.Type = DiffTypeEdited
  1556  	} else if added {
  1557  		diff.Type = DiffTypeAdded
  1558  	} else if deleted {
  1559  		diff.Type = DiffTypeDeleted
  1560  	} else {
  1561  		return nil
  1562  	}
  1563  
  1564  	return diff
  1565  }
  1566  
  1567  // volumeDiffs returns the diff of a group's volume requests. If contextual
  1568  // diff is enabled, all fields will be returned, even if no diff occurred.
  1569  func volumeDiffs(oldVR, newVR map[string]*VolumeRequest, contextual bool) []*ObjectDiff {
  1570  	if reflect.DeepEqual(oldVR, newVR) {
  1571  		return nil
  1572  	}
  1573  
  1574  	diffs := []*ObjectDiff{} //Type: DiffTypeNone, Name: "Volumes"}
  1575  	seen := map[string]bool{}
  1576  	for name, oReq := range oldVR {
  1577  		nReq := newVR[name] // might be nil, that's ok
  1578  		seen[name] = true
  1579  		diff := volumeDiff(oReq, nReq, contextual)
  1580  		if diff != nil {
  1581  			diffs = append(diffs, diff)
  1582  		}
  1583  	}
  1584  	for name, nReq := range newVR {
  1585  		if !seen[name] {
  1586  			// we know old is nil at this point, or we'd have hit it before
  1587  			diff := volumeDiff(nil, nReq, contextual)
  1588  			if diff != nil {
  1589  				diffs = append(diffs, diff)
  1590  			}
  1591  		}
  1592  	}
  1593  	return diffs
  1594  }
  1595  
  1596  // volumeDiff returns the diff between two volume requests. If contextual diff
  1597  // is enabled, all fields will be returned, even if no diff occurred.
  1598  func volumeDiff(oldVR, newVR *VolumeRequest, contextual bool) *ObjectDiff {
  1599  	if reflect.DeepEqual(oldVR, newVR) {
  1600  		return nil
  1601  	}
  1602  
  1603  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Volume"}
  1604  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1605  
  1606  	if oldVR == nil {
  1607  		oldVR = &VolumeRequest{}
  1608  		diff.Type = DiffTypeAdded
  1609  		newPrimitiveFlat = flatmap.Flatten(newVR, nil, true)
  1610  	} else if newVR == nil {
  1611  		newVR = &VolumeRequest{}
  1612  		diff.Type = DiffTypeDeleted
  1613  		oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true)
  1614  	} else {
  1615  		diff.Type = DiffTypeEdited
  1616  		oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true)
  1617  		newPrimitiveFlat = flatmap.Flatten(newVR, nil, true)
  1618  	}
  1619  
  1620  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1621  
  1622  	mOptsDiff := volumeCSIMountOptionsDiff(oldVR.MountOptions, newVR.MountOptions, contextual)
  1623  	if mOptsDiff != nil {
  1624  		diff.Objects = append(diff.Objects, mOptsDiff)
  1625  	}
  1626  
  1627  	return diff
  1628  }
  1629  
  1630  // volumeCSIMountOptionsDiff returns the diff between volume mount options. If
  1631  // contextual diff is enabled, all fields will be returned, even if no diff
  1632  // occurred.
  1633  func volumeCSIMountOptionsDiff(oldMO, newMO *CSIMountOptions, contextual bool) *ObjectDiff {
  1634  	if reflect.DeepEqual(oldMO, newMO) {
  1635  		return nil
  1636  	}
  1637  
  1638  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "MountOptions"}
  1639  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1640  
  1641  	if oldMO == nil && newMO != nil {
  1642  		oldMO = &CSIMountOptions{}
  1643  		diff.Type = DiffTypeAdded
  1644  		newPrimitiveFlat = flatmap.Flatten(newMO, nil, true)
  1645  	} else if oldMO == nil && newMO != nil {
  1646  		newMO = &CSIMountOptions{}
  1647  		diff.Type = DiffTypeDeleted
  1648  		oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true)
  1649  	} else {
  1650  		diff.Type = DiffTypeEdited
  1651  		oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true)
  1652  		newPrimitiveFlat = flatmap.Flatten(newMO, nil, true)
  1653  	}
  1654  
  1655  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1656  
  1657  	setDiff := stringSetDiff(oldMO.MountFlags, newMO.MountFlags, "MountFlags", contextual)
  1658  	if setDiff != nil {
  1659  		diff.Objects = append(diff.Objects, setDiff)
  1660  	}
  1661  	return diff
  1662  }
  1663  
  1664  // Diff returns a diff of two resource objects. If contextual diff is enabled,
  1665  // non-changed fields will still be returned.
  1666  func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff {
  1667  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"}
  1668  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1669  
  1670  	if reflect.DeepEqual(r, other) {
  1671  		return nil
  1672  	} else if r == nil {
  1673  		r = &Resources{}
  1674  		diff.Type = DiffTypeAdded
  1675  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1676  	} else if other == nil {
  1677  		other = &Resources{}
  1678  		diff.Type = DiffTypeDeleted
  1679  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1680  	} else {
  1681  		diff.Type = DiffTypeEdited
  1682  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1683  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1684  	}
  1685  
  1686  	// Diff the primitive fields.
  1687  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1688  
  1689  	// Network Resources diff
  1690  	if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil {
  1691  		diff.Objects = append(diff.Objects, nDiffs...)
  1692  	}
  1693  
  1694  	// Requested Devices diff
  1695  	if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil {
  1696  		diff.Objects = append(diff.Objects, nDiffs...)
  1697  	}
  1698  
  1699  	return diff
  1700  }
  1701  
  1702  // Diff returns a diff of two network resources. If contextual diff is enabled,
  1703  // non-changed fields will still be returned.
  1704  func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff {
  1705  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"}
  1706  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1707  	filter := []string{"Device", "CIDR", "IP"}
  1708  
  1709  	if reflect.DeepEqual(r, other) {
  1710  		return nil
  1711  	} else if r == nil {
  1712  		r = &NetworkResource{}
  1713  		diff.Type = DiffTypeAdded
  1714  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  1715  	} else if other == nil {
  1716  		other = &NetworkResource{}
  1717  		diff.Type = DiffTypeDeleted
  1718  		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
  1719  	} else {
  1720  		diff.Type = DiffTypeEdited
  1721  		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
  1722  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  1723  	}
  1724  
  1725  	// Diff the primitive fields.
  1726  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1727  
  1728  	// Port diffs
  1729  	resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual)
  1730  	dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual)
  1731  	if resPorts != nil {
  1732  		diff.Objects = append(diff.Objects, resPorts...)
  1733  	}
  1734  	if dynPorts != nil {
  1735  		diff.Objects = append(diff.Objects, dynPorts...)
  1736  	}
  1737  
  1738  	if dnsDiff := r.DNS.Diff(other.DNS, contextual); dnsDiff != nil {
  1739  		diff.Objects = append(diff.Objects, dnsDiff)
  1740  	}
  1741  
  1742  	return diff
  1743  }
  1744  
  1745  // Diff returns a diff of two DNSConfig structs
  1746  func (c *DNSConfig) Diff(other *DNSConfig, contextual bool) *ObjectDiff {
  1747  	if reflect.DeepEqual(c, other) {
  1748  		return nil
  1749  	}
  1750  
  1751  	flatten := func(conf *DNSConfig) map[string]string {
  1752  		m := map[string]string{}
  1753  		if len(conf.Servers) > 0 {
  1754  			m["Servers"] = strings.Join(conf.Servers, ",")
  1755  		}
  1756  		if len(conf.Searches) > 0 {
  1757  			m["Searches"] = strings.Join(conf.Searches, ",")
  1758  		}
  1759  		if len(conf.Options) > 0 {
  1760  			m["Options"] = strings.Join(conf.Options, ",")
  1761  		}
  1762  		return m
  1763  	}
  1764  
  1765  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "DNS"}
  1766  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1767  	if c == nil {
  1768  		diff.Type = DiffTypeAdded
  1769  		newPrimitiveFlat = flatten(other)
  1770  	} else if other == nil {
  1771  		diff.Type = DiffTypeDeleted
  1772  		oldPrimitiveFlat = flatten(c)
  1773  	} else {
  1774  		diff.Type = DiffTypeEdited
  1775  		oldPrimitiveFlat = flatten(c)
  1776  		newPrimitiveFlat = flatten(other)
  1777  	}
  1778  
  1779  	// Diff the primitive fields.
  1780  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1781  
  1782  	return diff
  1783  }
  1784  
  1785  // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled,
  1786  // non-changed fields will still be returned.
  1787  func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff {
  1788  	makeSet := func(objects []*NetworkResource) map[string]*NetworkResource {
  1789  		objMap := make(map[string]*NetworkResource, len(objects))
  1790  		for _, obj := range objects {
  1791  			hash, err := hashstructure.Hash(obj, nil)
  1792  			if err != nil {
  1793  				panic(err)
  1794  			}
  1795  			objMap[fmt.Sprintf("%d", hash)] = obj
  1796  		}
  1797  
  1798  		return objMap
  1799  	}
  1800  
  1801  	oldSet := makeSet(old)
  1802  	newSet := makeSet(new)
  1803  
  1804  	var diffs []*ObjectDiff
  1805  	for k, oldV := range oldSet {
  1806  		if newV, ok := newSet[k]; !ok {
  1807  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1808  				diffs = append(diffs, diff)
  1809  			}
  1810  		}
  1811  	}
  1812  	for k, newV := range newSet {
  1813  		if oldV, ok := oldSet[k]; !ok {
  1814  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1815  				diffs = append(diffs, diff)
  1816  			}
  1817  		}
  1818  	}
  1819  
  1820  	sort.Sort(ObjectDiffs(diffs))
  1821  	return diffs
  1822  
  1823  }
  1824  
  1825  // portDiffs returns the diff of two sets of ports. The dynamic flag marks the
  1826  // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled,
  1827  // non-changed fields will still be returned.
  1828  func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff {
  1829  	makeSet := func(ports []Port) map[string]Port {
  1830  		portMap := make(map[string]Port, len(ports))
  1831  		for _, port := range ports {
  1832  			portMap[port.Label] = port
  1833  		}
  1834  
  1835  		return portMap
  1836  	}
  1837  
  1838  	oldPorts := makeSet(old)
  1839  	newPorts := makeSet(new)
  1840  
  1841  	var filter []string
  1842  	name := "Static Port"
  1843  	if dynamic {
  1844  		filter = []string{"Value"}
  1845  		name = "Dynamic Port"
  1846  	}
  1847  
  1848  	var diffs []*ObjectDiff
  1849  	for portLabel, oldPort := range oldPorts {
  1850  		// Diff the same, deleted and edited
  1851  		if newPort, ok := newPorts[portLabel]; ok {
  1852  			diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual)
  1853  			if diff != nil {
  1854  				diffs = append(diffs, diff)
  1855  			}
  1856  		} else {
  1857  			diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual)
  1858  			if diff != nil {
  1859  				diffs = append(diffs, diff)
  1860  			}
  1861  		}
  1862  	}
  1863  	for label, newPort := range newPorts {
  1864  		// Diff the added
  1865  		if _, ok := oldPorts[label]; !ok {
  1866  			diff := primitiveObjectDiff(nil, newPort, filter, name, contextual)
  1867  			if diff != nil {
  1868  				diffs = append(diffs, diff)
  1869  			}
  1870  		}
  1871  	}
  1872  
  1873  	sort.Sort(ObjectDiffs(diffs))
  1874  	return diffs
  1875  
  1876  }
  1877  
  1878  // Diff returns a diff of two requested devices. If contextual diff is enabled,
  1879  // non-changed fields will still be returned.
  1880  func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff {
  1881  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"}
  1882  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1883  
  1884  	if reflect.DeepEqual(r, other) {
  1885  		return nil
  1886  	} else if r == nil {
  1887  		diff.Type = DiffTypeAdded
  1888  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1889  	} else if other == nil {
  1890  		diff.Type = DiffTypeDeleted
  1891  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1892  	} else {
  1893  		diff.Type = DiffTypeEdited
  1894  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1895  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1896  	}
  1897  
  1898  	// Diff the primitive fields.
  1899  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1900  
  1901  	return diff
  1902  }
  1903  
  1904  // requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled,
  1905  // non-changed fields will still be returned.
  1906  func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff {
  1907  	makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice {
  1908  		deviceMap := make(map[string]*RequestedDevice, len(devices))
  1909  		for _, d := range devices {
  1910  			deviceMap[d.Name] = d
  1911  		}
  1912  
  1913  		return deviceMap
  1914  	}
  1915  
  1916  	oldSet := makeSet(old)
  1917  	newSet := makeSet(new)
  1918  
  1919  	var diffs []*ObjectDiff
  1920  	for k, oldV := range oldSet {
  1921  		newV := newSet[k]
  1922  		if diff := oldV.Diff(newV, contextual); diff != nil {
  1923  			diffs = append(diffs, diff)
  1924  		}
  1925  	}
  1926  	for k, newV := range newSet {
  1927  		if oldV, ok := oldSet[k]; !ok {
  1928  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1929  				diffs = append(diffs, diff)
  1930  			}
  1931  		}
  1932  	}
  1933  
  1934  	sort.Sort(ObjectDiffs(diffs))
  1935  	return diffs
  1936  
  1937  }
  1938  
  1939  // configDiff returns the diff of two Task Config objects. If contextual diff is
  1940  // enabled, all fields will be returned, even if no diff occurred.
  1941  func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff {
  1942  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"}
  1943  	if reflect.DeepEqual(old, new) {
  1944  		return nil
  1945  	} else if len(old) == 0 {
  1946  		diff.Type = DiffTypeAdded
  1947  	} else if len(new) == 0 {
  1948  		diff.Type = DiffTypeDeleted
  1949  	} else {
  1950  		diff.Type = DiffTypeEdited
  1951  	}
  1952  
  1953  	// Diff the primitive fields.
  1954  	oldPrimitiveFlat := flatmap.Flatten(old, nil, false)
  1955  	newPrimitiveFlat := flatmap.Flatten(new, nil, false)
  1956  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1957  	return diff
  1958  }
  1959  
  1960  // ObjectDiff contains the diff of two generic objects.
  1961  type ObjectDiff struct {
  1962  	Type    DiffType
  1963  	Name    string
  1964  	Fields  []*FieldDiff
  1965  	Objects []*ObjectDiff
  1966  }
  1967  
  1968  func (o *ObjectDiff) GoString() string {
  1969  	out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type)
  1970  	for _, f := range o.Fields {
  1971  		out += fmt.Sprintf("%#v\n", f)
  1972  	}
  1973  	for _, o := range o.Objects {
  1974  		out += fmt.Sprintf("%#v\n", o)
  1975  	}
  1976  	out += "}"
  1977  	return out
  1978  }
  1979  
  1980  func (o *ObjectDiff) Less(other *ObjectDiff) bool {
  1981  	if reflect.DeepEqual(o, other) {
  1982  		return false
  1983  	} else if other == nil {
  1984  		return false
  1985  	} else if o == nil {
  1986  		return true
  1987  	}
  1988  
  1989  	if o.Name != other.Name {
  1990  		return o.Name < other.Name
  1991  	}
  1992  
  1993  	if o.Type != other.Type {
  1994  		return o.Type.Less(other.Type)
  1995  	}
  1996  
  1997  	if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther {
  1998  		return lO < lOther
  1999  	}
  2000  
  2001  	if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther {
  2002  		return lO < lOther
  2003  	}
  2004  
  2005  	// Check each field
  2006  	sort.Sort(FieldDiffs(o.Fields))
  2007  	sort.Sort(FieldDiffs(other.Fields))
  2008  
  2009  	for i, oV := range o.Fields {
  2010  		if oV.Less(other.Fields[i]) {
  2011  			return true
  2012  		}
  2013  	}
  2014  
  2015  	// Check each object
  2016  	sort.Sort(ObjectDiffs(o.Objects))
  2017  	sort.Sort(ObjectDiffs(other.Objects))
  2018  	for i, oV := range o.Objects {
  2019  		if oV.Less(other.Objects[i]) {
  2020  			return true
  2021  		}
  2022  	}
  2023  
  2024  	return false
  2025  }
  2026  
  2027  // For sorting ObjectDiffs
  2028  type ObjectDiffs []*ObjectDiff
  2029  
  2030  func (o ObjectDiffs) Len() int           { return len(o) }
  2031  func (o ObjectDiffs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
  2032  func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) }
  2033  
  2034  type FieldDiff struct {
  2035  	Type        DiffType
  2036  	Name        string
  2037  	Old, New    string
  2038  	Annotations []string
  2039  }
  2040  
  2041  // fieldDiff returns a FieldDiff if old and new are different otherwise, it
  2042  // returns nil. If contextual diff is enabled, even non-changed fields will be
  2043  // returned.
  2044  func fieldDiff(old, new, name string, contextual bool) *FieldDiff {
  2045  	diff := &FieldDiff{Name: name, Type: DiffTypeNone}
  2046  	if old == new {
  2047  		if !contextual {
  2048  			return nil
  2049  		}
  2050  		diff.Old, diff.New = old, new
  2051  		return diff
  2052  	}
  2053  
  2054  	if old == "" {
  2055  		diff.Type = DiffTypeAdded
  2056  		diff.New = new
  2057  	} else if new == "" {
  2058  		diff.Type = DiffTypeDeleted
  2059  		diff.Old = old
  2060  	} else {
  2061  		diff.Type = DiffTypeEdited
  2062  		diff.Old = old
  2063  		diff.New = new
  2064  	}
  2065  	return diff
  2066  }
  2067  
  2068  func (f *FieldDiff) GoString() string {
  2069  	out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New)
  2070  	if len(f.Annotations) != 0 {
  2071  		out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", "))
  2072  	}
  2073  
  2074  	return out
  2075  }
  2076  
  2077  func (f *FieldDiff) Less(other *FieldDiff) bool {
  2078  	if reflect.DeepEqual(f, other) {
  2079  		return false
  2080  	} else if other == nil {
  2081  		return false
  2082  	} else if f == nil {
  2083  		return true
  2084  	}
  2085  
  2086  	if f.Name != other.Name {
  2087  		return f.Name < other.Name
  2088  	} else if f.Old != other.Old {
  2089  		return f.Old < other.Old
  2090  	}
  2091  
  2092  	return f.New < other.New
  2093  }
  2094  
  2095  // For sorting FieldDiffs
  2096  type FieldDiffs []*FieldDiff
  2097  
  2098  func (f FieldDiffs) Len() int           { return len(f) }
  2099  func (f FieldDiffs) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
  2100  func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) }
  2101  
  2102  // fieldDiffs takes a map of field names to their values and returns a set of
  2103  // field diffs. If contextual diff is enabled, even non-changed fields will be
  2104  // returned.
  2105  func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff {
  2106  	var diffs []*FieldDiff
  2107  	visited := make(map[string]struct{})
  2108  	for k, oldV := range old {
  2109  		visited[k] = struct{}{}
  2110  		newV := new[k]
  2111  		if diff := fieldDiff(oldV, newV, k, contextual); diff != nil {
  2112  			diffs = append(diffs, diff)
  2113  		}
  2114  	}
  2115  
  2116  	for k, newV := range new {
  2117  		if _, ok := visited[k]; !ok {
  2118  			if diff := fieldDiff("", newV, k, contextual); diff != nil {
  2119  				diffs = append(diffs, diff)
  2120  			}
  2121  		}
  2122  	}
  2123  
  2124  	sort.Sort(FieldDiffs(diffs))
  2125  	return diffs
  2126  }
  2127  
  2128  // stringSetDiff diffs two sets of strings with the given name.
  2129  func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff {
  2130  	oldMap := make(map[string]struct{}, len(old))
  2131  	newMap := make(map[string]struct{}, len(new))
  2132  	for _, o := range old {
  2133  		oldMap[o] = struct{}{}
  2134  	}
  2135  	for _, n := range new {
  2136  		newMap[n] = struct{}{}
  2137  	}
  2138  	if reflect.DeepEqual(oldMap, newMap) && !contextual {
  2139  		return nil
  2140  	}
  2141  
  2142  	diff := &ObjectDiff{Name: name}
  2143  	var added, removed bool
  2144  	for k := range oldMap {
  2145  		if _, ok := newMap[k]; !ok {
  2146  			diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual))
  2147  			removed = true
  2148  		} else if contextual {
  2149  			diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual))
  2150  		}
  2151  	}
  2152  
  2153  	for k := range newMap {
  2154  		if _, ok := oldMap[k]; !ok {
  2155  			diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual))
  2156  			added = true
  2157  		}
  2158  	}
  2159  
  2160  	sort.Sort(FieldDiffs(diff.Fields))
  2161  
  2162  	// Determine the type
  2163  	if added && removed {
  2164  		diff.Type = DiffTypeEdited
  2165  	} else if added {
  2166  		diff.Type = DiffTypeAdded
  2167  	} else if removed {
  2168  		diff.Type = DiffTypeDeleted
  2169  	} else {
  2170  		// Diff of an empty set
  2171  		if len(diff.Fields) == 0 {
  2172  			return nil
  2173  		}
  2174  
  2175  		diff.Type = DiffTypeNone
  2176  	}
  2177  
  2178  	return diff
  2179  }
  2180  
  2181  // primitiveObjectDiff returns a diff of the passed objects' primitive fields.
  2182  // The filter field can be used to exclude fields from the diff. The name is the
  2183  // name of the objects. If contextual is set, non-changed fields will also be
  2184  // stored in the object diff.
  2185  func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff {
  2186  	oldPrimitiveFlat := flatmap.Flatten(old, filter, true)
  2187  	newPrimitiveFlat := flatmap.Flatten(new, filter, true)
  2188  	delete(oldPrimitiveFlat, "")
  2189  	delete(newPrimitiveFlat, "")
  2190  
  2191  	diff := &ObjectDiff{Name: name}
  2192  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2193  
  2194  	var added, deleted, edited bool
  2195  Loop:
  2196  	for _, f := range diff.Fields {
  2197  		switch f.Type {
  2198  		case DiffTypeEdited:
  2199  			edited = true
  2200  			break Loop
  2201  		case DiffTypeDeleted:
  2202  			deleted = true
  2203  		case DiffTypeAdded:
  2204  			added = true
  2205  		}
  2206  	}
  2207  
  2208  	if edited || added && deleted {
  2209  		diff.Type = DiffTypeEdited
  2210  	} else if added {
  2211  		diff.Type = DiffTypeAdded
  2212  	} else if deleted {
  2213  		diff.Type = DiffTypeDeleted
  2214  	} else {
  2215  		return nil
  2216  	}
  2217  
  2218  	return diff
  2219  }
  2220  
  2221  // primitiveObjectSetDiff does a set difference of the old and new sets. The
  2222  // filter parameter can be used to filter a set of primitive fields in the
  2223  // passed structs. The name corresponds to the name of the passed objects. If
  2224  // contextual diff is enabled, objects' primitive fields will be returned even if
  2225  // no diff exists.
  2226  func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff {
  2227  	makeSet := func(objects []interface{}) map[string]interface{} {
  2228  		objMap := make(map[string]interface{}, len(objects))
  2229  		for _, obj := range objects {
  2230  			hash, err := hashstructure.Hash(obj, nil)
  2231  			if err != nil {
  2232  				panic(err)
  2233  			}
  2234  			objMap[fmt.Sprintf("%d", hash)] = obj
  2235  		}
  2236  
  2237  		return objMap
  2238  	}
  2239  
  2240  	oldSet := makeSet(old)
  2241  	newSet := makeSet(new)
  2242  
  2243  	var diffs []*ObjectDiff
  2244  	for k, v := range oldSet {
  2245  		// Deleted
  2246  		if _, ok := newSet[k]; !ok {
  2247  			diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual))
  2248  		}
  2249  	}
  2250  	for k, v := range newSet {
  2251  		// Added
  2252  		if _, ok := oldSet[k]; !ok {
  2253  			diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual))
  2254  		}
  2255  	}
  2256  
  2257  	sort.Sort(ObjectDiffs(diffs))
  2258  	return diffs
  2259  }
  2260  
  2261  // interfaceSlice is a helper method that takes a slice of typed elements and
  2262  // returns a slice of interface. This method will panic if given a non-slice
  2263  // input.
  2264  func interfaceSlice(slice interface{}) []interface{} {
  2265  	s := reflect.ValueOf(slice)
  2266  	if s.Kind() != reflect.Slice {
  2267  		panic("InterfaceSlice() given a non-slice type")
  2268  	}
  2269  
  2270  	ret := make([]interface{}, s.Len())
  2271  
  2272  	for i := 0; i < s.Len(); i++ {
  2273  		ret[i] = s.Index(i).Interface()
  2274  	}
  2275  
  2276  	return ret
  2277  }