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