github.com/quite/nomad@v0.8.6/nomad/structs/diff.go (about)

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