github.com/manicqin/nomad@v0.9.5/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  	// Network Resources diff
   277  	if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil {
   278  		diff.Objects = append(diff.Objects, nDiffs...)
   279  	}
   280  
   281  	// Services diff
   282  	if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil {
   283  		diff.Objects = append(diff.Objects, sDiffs...)
   284  	}
   285  
   286  	// Tasks diff
   287  	tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	diff.Tasks = tasks
   292  
   293  	return diff, nil
   294  }
   295  
   296  func (tg *TaskGroupDiff) GoString() string {
   297  	out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type)
   298  
   299  	if len(tg.Updates) != 0 {
   300  		out += "Updates {\n"
   301  		for update, count := range tg.Updates {
   302  			out += fmt.Sprintf("%d %s\n", count, update)
   303  		}
   304  		out += "}\n"
   305  	}
   306  
   307  	for _, f := range tg.Fields {
   308  		out += fmt.Sprintf("%#v\n", f)
   309  	}
   310  
   311  	for _, o := range tg.Objects {
   312  		out += fmt.Sprintf("%#v\n", o)
   313  	}
   314  
   315  	for _, t := range tg.Tasks {
   316  		out += fmt.Sprintf("%#v\n", t)
   317  	}
   318  
   319  	return out
   320  }
   321  
   322  // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled,
   323  // objects' fields will be stored even if no diff occurred as long as one field
   324  // changed.
   325  func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) {
   326  	oldMap := make(map[string]*TaskGroup, len(old))
   327  	newMap := make(map[string]*TaskGroup, len(new))
   328  	for _, o := range old {
   329  		oldMap[o.Name] = o
   330  	}
   331  	for _, n := range new {
   332  		newMap[n.Name] = n
   333  	}
   334  
   335  	var diffs []*TaskGroupDiff
   336  	for name, oldGroup := range oldMap {
   337  		// Diff the same, deleted and edited
   338  		diff, err := oldGroup.Diff(newMap[name], contextual)
   339  		if err != nil {
   340  			return nil, err
   341  		}
   342  		diffs = append(diffs, diff)
   343  	}
   344  
   345  	for name, newGroup := range newMap {
   346  		// Diff the added
   347  		if old, ok := oldMap[name]; !ok {
   348  			diff, err := old.Diff(newGroup, contextual)
   349  			if err != nil {
   350  				return nil, err
   351  			}
   352  			diffs = append(diffs, diff)
   353  		}
   354  	}
   355  
   356  	sort.Sort(TaskGroupDiffs(diffs))
   357  	return diffs, nil
   358  }
   359  
   360  // For sorting TaskGroupDiffs
   361  type TaskGroupDiffs []*TaskGroupDiff
   362  
   363  func (tg TaskGroupDiffs) Len() int           { return len(tg) }
   364  func (tg TaskGroupDiffs) Swap(i, j int)      { tg[i], tg[j] = tg[j], tg[i] }
   365  func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name }
   366  
   367  // TaskDiff contains the diff of two Tasks
   368  type TaskDiff struct {
   369  	Type        DiffType
   370  	Name        string
   371  	Fields      []*FieldDiff
   372  	Objects     []*ObjectDiff
   373  	Annotations []string
   374  }
   375  
   376  // Diff returns a diff of two tasks. If contextual diff is enabled, objects
   377  // within the task will contain field information even if unchanged.
   378  func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
   379  	diff := &TaskDiff{Type: DiffTypeNone}
   380  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   381  	filter := []string{"Name", "Config"}
   382  
   383  	if t == nil && other == nil {
   384  		return diff, nil
   385  	} else if t == nil {
   386  		t = &Task{}
   387  		diff.Type = DiffTypeAdded
   388  		diff.Name = other.Name
   389  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   390  	} else if other == nil {
   391  		other = &Task{}
   392  		diff.Type = DiffTypeDeleted
   393  		diff.Name = t.Name
   394  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   395  	} else {
   396  		if !reflect.DeepEqual(t, other) {
   397  			diff.Type = DiffTypeEdited
   398  		}
   399  		if t.Name != other.Name {
   400  			return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name)
   401  		}
   402  		diff.Name = other.Name
   403  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   404  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   405  	}
   406  
   407  	// Diff the primitive fields.
   408  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   409  
   410  	// Constraints diff
   411  	conDiff := primitiveObjectSetDiff(
   412  		interfaceSlice(t.Constraints),
   413  		interfaceSlice(other.Constraints),
   414  		[]string{"str"},
   415  		"Constraint",
   416  		contextual)
   417  	if conDiff != nil {
   418  		diff.Objects = append(diff.Objects, conDiff...)
   419  	}
   420  
   421  	// Affinities diff
   422  	affinitiesDiff := primitiveObjectSetDiff(
   423  		interfaceSlice(t.Affinities),
   424  		interfaceSlice(other.Affinities),
   425  		[]string{"str"},
   426  		"Affinity",
   427  		contextual)
   428  	if affinitiesDiff != nil {
   429  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   430  	}
   431  
   432  	// Config diff
   433  	if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil {
   434  		diff.Objects = append(diff.Objects, cDiff)
   435  	}
   436  
   437  	// Resources diff
   438  	if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil {
   439  		diff.Objects = append(diff.Objects, rDiff)
   440  	}
   441  
   442  	// LogConfig diff
   443  	lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual)
   444  	if lDiff != nil {
   445  		diff.Objects = append(diff.Objects, lDiff)
   446  	}
   447  
   448  	// Dispatch payload diff
   449  	dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual)
   450  	if dDiff != nil {
   451  		diff.Objects = append(diff.Objects, dDiff)
   452  	}
   453  
   454  	// Artifacts diff
   455  	diffs := primitiveObjectSetDiff(
   456  		interfaceSlice(t.Artifacts),
   457  		interfaceSlice(other.Artifacts),
   458  		nil,
   459  		"Artifact",
   460  		contextual)
   461  	if diffs != nil {
   462  		diff.Objects = append(diff.Objects, diffs...)
   463  	}
   464  
   465  	// Services diff
   466  	if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil {
   467  		diff.Objects = append(diff.Objects, sDiffs...)
   468  	}
   469  
   470  	// Vault diff
   471  	vDiff := vaultDiff(t.Vault, other.Vault, contextual)
   472  	if vDiff != nil {
   473  		diff.Objects = append(diff.Objects, vDiff)
   474  	}
   475  
   476  	// Template diff
   477  	tmplDiffs := primitiveObjectSetDiff(
   478  		interfaceSlice(t.Templates),
   479  		interfaceSlice(other.Templates),
   480  		nil,
   481  		"Template",
   482  		contextual)
   483  	if tmplDiffs != nil {
   484  		diff.Objects = append(diff.Objects, tmplDiffs...)
   485  	}
   486  
   487  	return diff, nil
   488  }
   489  
   490  func (t *TaskDiff) GoString() string {
   491  	var out string
   492  	if len(t.Annotations) == 0 {
   493  		out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type)
   494  	} else {
   495  		out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ","))
   496  	}
   497  
   498  	for _, f := range t.Fields {
   499  		out += fmt.Sprintf("%#v\n", f)
   500  	}
   501  
   502  	for _, o := range t.Objects {
   503  		out += fmt.Sprintf("%#v\n", o)
   504  	}
   505  
   506  	return out
   507  }
   508  
   509  // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged
   510  // fields within objects nested in the tasks will be returned.
   511  func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) {
   512  	oldMap := make(map[string]*Task, len(old))
   513  	newMap := make(map[string]*Task, len(new))
   514  	for _, o := range old {
   515  		oldMap[o.Name] = o
   516  	}
   517  	for _, n := range new {
   518  		newMap[n.Name] = n
   519  	}
   520  
   521  	var diffs []*TaskDiff
   522  	for name, oldGroup := range oldMap {
   523  		// Diff the same, deleted and edited
   524  		diff, err := oldGroup.Diff(newMap[name], contextual)
   525  		if err != nil {
   526  			return nil, err
   527  		}
   528  		diffs = append(diffs, diff)
   529  	}
   530  
   531  	for name, newGroup := range newMap {
   532  		// Diff the added
   533  		if old, ok := oldMap[name]; !ok {
   534  			diff, err := old.Diff(newGroup, contextual)
   535  			if err != nil {
   536  				return nil, err
   537  			}
   538  			diffs = append(diffs, diff)
   539  		}
   540  	}
   541  
   542  	sort.Sort(TaskDiffs(diffs))
   543  	return diffs, nil
   544  }
   545  
   546  // For sorting TaskDiffs
   547  type TaskDiffs []*TaskDiff
   548  
   549  func (t TaskDiffs) Len() int           { return len(t) }
   550  func (t TaskDiffs) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   551  func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name }
   552  
   553  // serviceDiff returns the diff of two service objects. If contextual diff is
   554  // enabled, all fields will be returned, even if no diff occurred.
   555  func serviceDiff(old, new *Service, contextual bool) *ObjectDiff {
   556  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
   557  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   558  
   559  	if reflect.DeepEqual(old, new) {
   560  		return nil
   561  	} else if old == nil {
   562  		old = &Service{}
   563  		diff.Type = DiffTypeAdded
   564  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   565  	} else if new == nil {
   566  		new = &Service{}
   567  		diff.Type = DiffTypeDeleted
   568  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   569  	} else {
   570  		diff.Type = DiffTypeEdited
   571  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   572  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   573  	}
   574  
   575  	// Diff the primitive fields.
   576  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   577  
   578  	if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil {
   579  		diff.Objects = append(diff.Objects, setDiff)
   580  	}
   581  
   582  	// Tag diffs
   583  	if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil {
   584  		diff.Objects = append(diff.Objects, setDiff)
   585  	}
   586  
   587  	// Checks diffs
   588  	if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil {
   589  		diff.Objects = append(diff.Objects, cDiffs...)
   590  	}
   591  
   592  	// Consul Connect diffs
   593  	if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil {
   594  		diff.Objects = append(diff.Objects, conDiffs)
   595  	}
   596  
   597  	return diff
   598  }
   599  
   600  // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
   601  // fields within objects nested in the tasks will be returned.
   602  func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff {
   603  	oldMap := make(map[string]*Service, len(old))
   604  	newMap := make(map[string]*Service, len(new))
   605  	for _, o := range old {
   606  		oldMap[o.Name] = o
   607  	}
   608  	for _, n := range new {
   609  		newMap[n.Name] = n
   610  	}
   611  
   612  	var diffs []*ObjectDiff
   613  	for name, oldService := range oldMap {
   614  		// Diff the same, deleted and edited
   615  		if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil {
   616  			diffs = append(diffs, diff)
   617  		}
   618  	}
   619  
   620  	for name, newService := range newMap {
   621  		// Diff the added
   622  		if old, ok := oldMap[name]; !ok {
   623  			if diff := serviceDiff(old, newService, contextual); diff != nil {
   624  				diffs = append(diffs, diff)
   625  			}
   626  		}
   627  	}
   628  
   629  	sort.Sort(ObjectDiffs(diffs))
   630  	return diffs
   631  }
   632  
   633  // serviceCheckDiff returns the diff of two service check objects. If contextual
   634  // diff is enabled, all fields will be returned, even if no diff occurred.
   635  func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff {
   636  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"}
   637  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   638  
   639  	if reflect.DeepEqual(old, new) {
   640  		return nil
   641  	} else if old == nil {
   642  		old = &ServiceCheck{}
   643  		diff.Type = DiffTypeAdded
   644  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   645  	} else if new == nil {
   646  		new = &ServiceCheck{}
   647  		diff.Type = DiffTypeDeleted
   648  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   649  	} else {
   650  		diff.Type = DiffTypeEdited
   651  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   652  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   653  	}
   654  
   655  	// Diff the primitive fields.
   656  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   657  
   658  	// Diff Header
   659  	if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil {
   660  		diff.Objects = append(diff.Objects, headerDiff)
   661  	}
   662  
   663  	// Diff check_restart
   664  	if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil {
   665  		diff.Objects = append(diff.Objects, crDiff)
   666  	}
   667  
   668  	return diff
   669  }
   670  
   671  // checkHeaderDiff returns the diff of two service check header objects. If
   672  // contextual diff is enabled, all fields will be returned, even if no diff
   673  // occurred.
   674  func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff {
   675  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"}
   676  	var oldFlat, newFlat map[string]string
   677  
   678  	if reflect.DeepEqual(old, new) {
   679  		return nil
   680  	} else if len(old) == 0 {
   681  		diff.Type = DiffTypeAdded
   682  		newFlat = flatmap.Flatten(new, nil, false)
   683  	} else if len(new) == 0 {
   684  		diff.Type = DiffTypeDeleted
   685  		oldFlat = flatmap.Flatten(old, nil, false)
   686  	} else {
   687  		diff.Type = DiffTypeEdited
   688  		oldFlat = flatmap.Flatten(old, nil, false)
   689  		newFlat = flatmap.Flatten(new, nil, false)
   690  	}
   691  
   692  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   693  	return diff
   694  }
   695  
   696  // checkRestartDiff returns the diff of two service check check_restart
   697  // objects. If contextual diff is enabled, all fields will be returned, even if
   698  // no diff occurred.
   699  func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff {
   700  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"}
   701  	var oldFlat, newFlat map[string]string
   702  
   703  	if reflect.DeepEqual(old, new) {
   704  		return nil
   705  	} else if old == nil {
   706  		diff.Type = DiffTypeAdded
   707  		newFlat = flatmap.Flatten(new, nil, true)
   708  		diff.Type = DiffTypeAdded
   709  	} else if new == nil {
   710  		diff.Type = DiffTypeDeleted
   711  		oldFlat = flatmap.Flatten(old, nil, true)
   712  	} else {
   713  		diff.Type = DiffTypeEdited
   714  		oldFlat = flatmap.Flatten(old, nil, true)
   715  		newFlat = flatmap.Flatten(new, nil, true)
   716  	}
   717  
   718  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   719  	return diff
   720  }
   721  
   722  // connectDiffs returns the diff of two Consul connect objects. If contextual
   723  // diff is enabled, all fields will be returned, even if no diff occurred.
   724  func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff {
   725  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"}
   726  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   727  
   728  	if reflect.DeepEqual(old, new) {
   729  		return nil
   730  	} else if old == nil {
   731  		old = &ConsulConnect{}
   732  		diff.Type = DiffTypeAdded
   733  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   734  	} else if new == nil {
   735  		new = &ConsulConnect{}
   736  		diff.Type = DiffTypeDeleted
   737  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   738  	} else {
   739  		diff.Type = DiffTypeEdited
   740  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   741  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   742  	}
   743  
   744  	// Diff the primitive fields.
   745  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   746  
   747  	sidecarSvcDiff := connectSidecarServiceDiff(
   748  		old.SidecarService, new.SidecarService, contextual)
   749  	if sidecarSvcDiff != nil {
   750  		diff.Objects = append(diff.Objects, sidecarSvcDiff)
   751  	}
   752  
   753  	sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual)
   754  	if sidecarTaskDiff != nil {
   755  		diff.Objects = append(diff.Objects, sidecarTaskDiff)
   756  	}
   757  
   758  	return diff
   759  }
   760  
   761  // connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects.
   762  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
   763  func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff {
   764  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"}
   765  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   766  
   767  	if reflect.DeepEqual(old, new) {
   768  		return nil
   769  	} else if old == nil {
   770  		old = &ConsulSidecarService{}
   771  		diff.Type = DiffTypeAdded
   772  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   773  	} else if new == nil {
   774  		new = &ConsulSidecarService{}
   775  		diff.Type = DiffTypeDeleted
   776  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   777  	} else {
   778  		diff.Type = DiffTypeEdited
   779  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   780  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   781  	}
   782  
   783  	// Diff the primitive fields.
   784  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   785  
   786  	consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual)
   787  	if consulProxyDiff != nil {
   788  		diff.Objects = append(diff.Objects, consulProxyDiff)
   789  	}
   790  
   791  	return diff
   792  }
   793  
   794  // sidecarTaskDiff returns the diff of two Task objects.
   795  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
   796  func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff {
   797  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"}
   798  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   799  
   800  	if reflect.DeepEqual(old, new) {
   801  		return nil
   802  	} else if old == nil {
   803  		old = &SidecarTask{}
   804  		diff.Type = DiffTypeAdded
   805  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   806  	} else if new == nil {
   807  		new = &SidecarTask{}
   808  		diff.Type = DiffTypeDeleted
   809  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   810  	} else {
   811  		diff.Type = DiffTypeEdited
   812  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   813  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   814  	}
   815  
   816  	// Diff the primitive fields.
   817  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   818  
   819  	// Config diff
   820  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
   821  		diff.Objects = append(diff.Objects, cDiff)
   822  	}
   823  
   824  	// Resources diff
   825  	if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil {
   826  		diff.Objects = append(diff.Objects, rDiff)
   827  	}
   828  
   829  	// LogConfig diff
   830  	lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual)
   831  	if lDiff != nil {
   832  		diff.Objects = append(diff.Objects, lDiff)
   833  	}
   834  
   835  	return diff
   836  }
   837  
   838  // consulProxyDiff returns the diff of two ConsulProxy objects.
   839  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
   840  func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff {
   841  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"}
   842  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   843  
   844  	if reflect.DeepEqual(old, new) {
   845  		return nil
   846  	} else if old == nil {
   847  		old = &ConsulProxy{}
   848  		diff.Type = DiffTypeAdded
   849  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   850  	} else if new == nil {
   851  		new = &ConsulProxy{}
   852  		diff.Type = DiffTypeDeleted
   853  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   854  	} else {
   855  		diff.Type = DiffTypeEdited
   856  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   857  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   858  	}
   859  
   860  	// Diff the primitive fields.
   861  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   862  
   863  	consulUpstreamsDiff := primitiveObjectSetDiff(
   864  		interfaceSlice(old.Upstreams),
   865  		interfaceSlice(new.Upstreams),
   866  		nil, "ConsulUpstreams", contextual)
   867  	if consulUpstreamsDiff != nil {
   868  		diff.Objects = append(diff.Objects, consulUpstreamsDiff...)
   869  	}
   870  
   871  	// Config diff
   872  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
   873  		diff.Objects = append(diff.Objects, cDiff)
   874  	}
   875  
   876  	return diff
   877  }
   878  
   879  // serviceCheckDiffs diffs a set of service checks. If contextual diff is
   880  // enabled, unchanged fields within objects nested in the tasks will be
   881  // returned.
   882  func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff {
   883  	oldMap := make(map[string]*ServiceCheck, len(old))
   884  	newMap := make(map[string]*ServiceCheck, len(new))
   885  	for _, o := range old {
   886  		oldMap[o.Name] = o
   887  	}
   888  	for _, n := range new {
   889  		newMap[n.Name] = n
   890  	}
   891  
   892  	var diffs []*ObjectDiff
   893  	for name, oldCheck := range oldMap {
   894  		// Diff the same, deleted and edited
   895  		if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil {
   896  			diffs = append(diffs, diff)
   897  		}
   898  	}
   899  
   900  	for name, newCheck := range newMap {
   901  		// Diff the added
   902  		if old, ok := oldMap[name]; !ok {
   903  			if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil {
   904  				diffs = append(diffs, diff)
   905  			}
   906  		}
   907  	}
   908  
   909  	sort.Sort(ObjectDiffs(diffs))
   910  	return diffs
   911  }
   912  
   913  // vaultDiff returns the diff of two vault objects. If contextual diff is
   914  // enabled, all fields will be returned, even if no diff occurred.
   915  func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff {
   916  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"}
   917  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   918  
   919  	if reflect.DeepEqual(old, new) {
   920  		return nil
   921  	} else if old == nil {
   922  		old = &Vault{}
   923  		diff.Type = DiffTypeAdded
   924  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   925  	} else if new == nil {
   926  		new = &Vault{}
   927  		diff.Type = DiffTypeDeleted
   928  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   929  	} else {
   930  		diff.Type = DiffTypeEdited
   931  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   932  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   933  	}
   934  
   935  	// Diff the primitive fields.
   936  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   937  
   938  	// Policies diffs
   939  	if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil {
   940  		diff.Objects = append(diff.Objects, setDiff)
   941  	}
   942  
   943  	return diff
   944  }
   945  
   946  // parameterizedJobDiff returns the diff of two parameterized job objects. If
   947  // contextual diff is enabled, all fields will be returned, even if no diff
   948  // occurred.
   949  func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff {
   950  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"}
   951  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   952  
   953  	if reflect.DeepEqual(old, new) {
   954  		return nil
   955  	} else if old == nil {
   956  		old = &ParameterizedJobConfig{}
   957  		diff.Type = DiffTypeAdded
   958  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   959  	} else if new == nil {
   960  		new = &ParameterizedJobConfig{}
   961  		diff.Type = DiffTypeDeleted
   962  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   963  	} else {
   964  		diff.Type = DiffTypeEdited
   965  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   966  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   967  	}
   968  
   969  	// Diff the primitive fields.
   970  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   971  
   972  	// Meta diffs
   973  	if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil {
   974  		diff.Objects = append(diff.Objects, optionalDiff)
   975  	}
   976  
   977  	if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil {
   978  		diff.Objects = append(diff.Objects, requiredDiff)
   979  	}
   980  
   981  	return diff
   982  }
   983  
   984  // Diff returns a diff of two resource objects. If contextual diff is enabled,
   985  // non-changed fields will still be returned.
   986  func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff {
   987  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"}
   988  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   989  
   990  	if reflect.DeepEqual(r, other) {
   991  		return nil
   992  	} else if r == nil {
   993  		r = &Resources{}
   994  		diff.Type = DiffTypeAdded
   995  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
   996  	} else if other == nil {
   997  		other = &Resources{}
   998  		diff.Type = DiffTypeDeleted
   999  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1000  	} else {
  1001  		diff.Type = DiffTypeEdited
  1002  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1003  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1004  	}
  1005  
  1006  	// Diff the primitive fields.
  1007  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1008  
  1009  	// Network Resources diff
  1010  	if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil {
  1011  		diff.Objects = append(diff.Objects, nDiffs...)
  1012  	}
  1013  
  1014  	// Requested Devices diff
  1015  	if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil {
  1016  		diff.Objects = append(diff.Objects, nDiffs...)
  1017  	}
  1018  
  1019  	return diff
  1020  }
  1021  
  1022  // Diff returns a diff of two network resources. If contextual diff is enabled,
  1023  // non-changed fields will still be returned.
  1024  func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff {
  1025  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"}
  1026  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1027  	filter := []string{"Device", "CIDR", "IP"}
  1028  
  1029  	if reflect.DeepEqual(r, other) {
  1030  		return nil
  1031  	} else if r == nil {
  1032  		r = &NetworkResource{}
  1033  		diff.Type = DiffTypeAdded
  1034  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  1035  	} else if other == nil {
  1036  		other = &NetworkResource{}
  1037  		diff.Type = DiffTypeDeleted
  1038  		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
  1039  	} else {
  1040  		diff.Type = DiffTypeEdited
  1041  		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
  1042  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  1043  	}
  1044  
  1045  	// Diff the primitive fields.
  1046  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1047  
  1048  	// Port diffs
  1049  	resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual)
  1050  	dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual)
  1051  	if resPorts != nil {
  1052  		diff.Objects = append(diff.Objects, resPorts...)
  1053  	}
  1054  	if dynPorts != nil {
  1055  		diff.Objects = append(diff.Objects, dynPorts...)
  1056  	}
  1057  
  1058  	return diff
  1059  }
  1060  
  1061  // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled,
  1062  // non-changed fields will still be returned.
  1063  func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff {
  1064  	makeSet := func(objects []*NetworkResource) map[string]*NetworkResource {
  1065  		objMap := make(map[string]*NetworkResource, len(objects))
  1066  		for _, obj := range objects {
  1067  			hash, err := hashstructure.Hash(obj, nil)
  1068  			if err != nil {
  1069  				panic(err)
  1070  			}
  1071  			objMap[fmt.Sprintf("%d", hash)] = obj
  1072  		}
  1073  
  1074  		return objMap
  1075  	}
  1076  
  1077  	oldSet := makeSet(old)
  1078  	newSet := makeSet(new)
  1079  
  1080  	var diffs []*ObjectDiff
  1081  	for k, oldV := range oldSet {
  1082  		if newV, ok := newSet[k]; !ok {
  1083  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1084  				diffs = append(diffs, diff)
  1085  			}
  1086  		}
  1087  	}
  1088  	for k, newV := range newSet {
  1089  		if oldV, ok := oldSet[k]; !ok {
  1090  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1091  				diffs = append(diffs, diff)
  1092  			}
  1093  		}
  1094  	}
  1095  
  1096  	sort.Sort(ObjectDiffs(diffs))
  1097  	return diffs
  1098  
  1099  }
  1100  
  1101  // portDiffs returns the diff of two sets of ports. The dynamic flag marks the
  1102  // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled,
  1103  // non-changed fields will still be returned.
  1104  func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff {
  1105  	makeSet := func(ports []Port) map[string]Port {
  1106  		portMap := make(map[string]Port, len(ports))
  1107  		for _, port := range ports {
  1108  			portMap[port.Label] = port
  1109  		}
  1110  
  1111  		return portMap
  1112  	}
  1113  
  1114  	oldPorts := makeSet(old)
  1115  	newPorts := makeSet(new)
  1116  
  1117  	var filter []string
  1118  	name := "Static Port"
  1119  	if dynamic {
  1120  		filter = []string{"Value"}
  1121  		name = "Dynamic Port"
  1122  	}
  1123  
  1124  	var diffs []*ObjectDiff
  1125  	for portLabel, oldPort := range oldPorts {
  1126  		// Diff the same, deleted and edited
  1127  		if newPort, ok := newPorts[portLabel]; ok {
  1128  			diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual)
  1129  			if diff != nil {
  1130  				diffs = append(diffs, diff)
  1131  			}
  1132  		} else {
  1133  			diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual)
  1134  			if diff != nil {
  1135  				diffs = append(diffs, diff)
  1136  			}
  1137  		}
  1138  	}
  1139  	for label, newPort := range newPorts {
  1140  		// Diff the added
  1141  		if _, ok := oldPorts[label]; !ok {
  1142  			diff := primitiveObjectDiff(nil, newPort, filter, name, contextual)
  1143  			if diff != nil {
  1144  				diffs = append(diffs, diff)
  1145  			}
  1146  		}
  1147  	}
  1148  
  1149  	sort.Sort(ObjectDiffs(diffs))
  1150  	return diffs
  1151  
  1152  }
  1153  
  1154  // Diff returns a diff of two requested devices. If contextual diff is enabled,
  1155  // non-changed fields will still be returned.
  1156  func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff {
  1157  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"}
  1158  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1159  
  1160  	if reflect.DeepEqual(r, other) {
  1161  		return nil
  1162  	} else if r == nil {
  1163  		diff.Type = DiffTypeAdded
  1164  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1165  	} else if other == nil {
  1166  		diff.Type = DiffTypeDeleted
  1167  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1168  	} else {
  1169  		diff.Type = DiffTypeEdited
  1170  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1171  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1172  	}
  1173  
  1174  	// Diff the primitive fields.
  1175  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1176  
  1177  	return diff
  1178  }
  1179  
  1180  // requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled,
  1181  // non-changed fields will still be returned.
  1182  func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff {
  1183  	makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice {
  1184  		deviceMap := make(map[string]*RequestedDevice, len(devices))
  1185  		for _, d := range devices {
  1186  			deviceMap[d.Name] = d
  1187  		}
  1188  
  1189  		return deviceMap
  1190  	}
  1191  
  1192  	oldSet := makeSet(old)
  1193  	newSet := makeSet(new)
  1194  
  1195  	var diffs []*ObjectDiff
  1196  	for k, oldV := range oldSet {
  1197  		newV := newSet[k]
  1198  		if diff := oldV.Diff(newV, contextual); diff != nil {
  1199  			diffs = append(diffs, diff)
  1200  		}
  1201  	}
  1202  	for k, newV := range newSet {
  1203  		if oldV, ok := oldSet[k]; !ok {
  1204  			if diff := oldV.Diff(newV, contextual); diff != nil {
  1205  				diffs = append(diffs, diff)
  1206  			}
  1207  		}
  1208  	}
  1209  
  1210  	sort.Sort(ObjectDiffs(diffs))
  1211  	return diffs
  1212  
  1213  }
  1214  
  1215  // configDiff returns the diff of two Task Config objects. If contextual diff is
  1216  // enabled, all fields will be returned, even if no diff occurred.
  1217  func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff {
  1218  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"}
  1219  	if reflect.DeepEqual(old, new) {
  1220  		return nil
  1221  	} else if len(old) == 0 {
  1222  		diff.Type = DiffTypeAdded
  1223  	} else if len(new) == 0 {
  1224  		diff.Type = DiffTypeDeleted
  1225  	} else {
  1226  		diff.Type = DiffTypeEdited
  1227  	}
  1228  
  1229  	// Diff the primitive fields.
  1230  	oldPrimitiveFlat := flatmap.Flatten(old, nil, false)
  1231  	newPrimitiveFlat := flatmap.Flatten(new, nil, false)
  1232  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1233  	return diff
  1234  }
  1235  
  1236  // ObjectDiff contains the diff of two generic objects.
  1237  type ObjectDiff struct {
  1238  	Type    DiffType
  1239  	Name    string
  1240  	Fields  []*FieldDiff
  1241  	Objects []*ObjectDiff
  1242  }
  1243  
  1244  func (o *ObjectDiff) GoString() string {
  1245  	out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type)
  1246  	for _, f := range o.Fields {
  1247  		out += fmt.Sprintf("%#v\n", f)
  1248  	}
  1249  	for _, o := range o.Objects {
  1250  		out += fmt.Sprintf("%#v\n", o)
  1251  	}
  1252  	out += "}"
  1253  	return out
  1254  }
  1255  
  1256  func (o *ObjectDiff) Less(other *ObjectDiff) bool {
  1257  	if reflect.DeepEqual(o, other) {
  1258  		return false
  1259  	} else if other == nil {
  1260  		return false
  1261  	} else if o == nil {
  1262  		return true
  1263  	}
  1264  
  1265  	if o.Name != other.Name {
  1266  		return o.Name < other.Name
  1267  	}
  1268  
  1269  	if o.Type != other.Type {
  1270  		return o.Type.Less(other.Type)
  1271  	}
  1272  
  1273  	if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther {
  1274  		return lO < lOther
  1275  	}
  1276  
  1277  	if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther {
  1278  		return lO < lOther
  1279  	}
  1280  
  1281  	// Check each field
  1282  	sort.Sort(FieldDiffs(o.Fields))
  1283  	sort.Sort(FieldDiffs(other.Fields))
  1284  
  1285  	for i, oV := range o.Fields {
  1286  		if oV.Less(other.Fields[i]) {
  1287  			return true
  1288  		}
  1289  	}
  1290  
  1291  	// Check each object
  1292  	sort.Sort(ObjectDiffs(o.Objects))
  1293  	sort.Sort(ObjectDiffs(other.Objects))
  1294  	for i, oV := range o.Objects {
  1295  		if oV.Less(other.Objects[i]) {
  1296  			return true
  1297  		}
  1298  	}
  1299  
  1300  	return false
  1301  }
  1302  
  1303  // For sorting ObjectDiffs
  1304  type ObjectDiffs []*ObjectDiff
  1305  
  1306  func (o ObjectDiffs) Len() int           { return len(o) }
  1307  func (o ObjectDiffs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
  1308  func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) }
  1309  
  1310  type FieldDiff struct {
  1311  	Type        DiffType
  1312  	Name        string
  1313  	Old, New    string
  1314  	Annotations []string
  1315  }
  1316  
  1317  // fieldDiff returns a FieldDiff if old and new are different otherwise, it
  1318  // returns nil. If contextual diff is enabled, even non-changed fields will be
  1319  // returned.
  1320  func fieldDiff(old, new, name string, contextual bool) *FieldDiff {
  1321  	diff := &FieldDiff{Name: name, Type: DiffTypeNone}
  1322  	if old == new {
  1323  		if !contextual {
  1324  			return nil
  1325  		}
  1326  		diff.Old, diff.New = old, new
  1327  		return diff
  1328  	}
  1329  
  1330  	if old == "" {
  1331  		diff.Type = DiffTypeAdded
  1332  		diff.New = new
  1333  	} else if new == "" {
  1334  		diff.Type = DiffTypeDeleted
  1335  		diff.Old = old
  1336  	} else {
  1337  		diff.Type = DiffTypeEdited
  1338  		diff.Old = old
  1339  		diff.New = new
  1340  	}
  1341  	return diff
  1342  }
  1343  
  1344  func (f *FieldDiff) GoString() string {
  1345  	out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New)
  1346  	if len(f.Annotations) != 0 {
  1347  		out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", "))
  1348  	}
  1349  
  1350  	return out
  1351  }
  1352  
  1353  func (f *FieldDiff) Less(other *FieldDiff) bool {
  1354  	if reflect.DeepEqual(f, other) {
  1355  		return false
  1356  	} else if other == nil {
  1357  		return false
  1358  	} else if f == nil {
  1359  		return true
  1360  	}
  1361  
  1362  	if f.Name != other.Name {
  1363  		return f.Name < other.Name
  1364  	} else if f.Old != other.Old {
  1365  		return f.Old < other.Old
  1366  	}
  1367  
  1368  	return f.New < other.New
  1369  }
  1370  
  1371  // For sorting FieldDiffs
  1372  type FieldDiffs []*FieldDiff
  1373  
  1374  func (f FieldDiffs) Len() int           { return len(f) }
  1375  func (f FieldDiffs) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
  1376  func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) }
  1377  
  1378  // fieldDiffs takes a map of field names to their values and returns a set of
  1379  // field diffs. If contextual diff is enabled, even non-changed fields will be
  1380  // returned.
  1381  func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff {
  1382  	var diffs []*FieldDiff
  1383  	visited := make(map[string]struct{})
  1384  	for k, oldV := range old {
  1385  		visited[k] = struct{}{}
  1386  		newV := new[k]
  1387  		if diff := fieldDiff(oldV, newV, k, contextual); diff != nil {
  1388  			diffs = append(diffs, diff)
  1389  		}
  1390  	}
  1391  
  1392  	for k, newV := range new {
  1393  		if _, ok := visited[k]; !ok {
  1394  			if diff := fieldDiff("", newV, k, contextual); diff != nil {
  1395  				diffs = append(diffs, diff)
  1396  			}
  1397  		}
  1398  	}
  1399  
  1400  	sort.Sort(FieldDiffs(diffs))
  1401  	return diffs
  1402  }
  1403  
  1404  // stringSetDiff diffs two sets of strings with the given name.
  1405  func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff {
  1406  	oldMap := make(map[string]struct{}, len(old))
  1407  	newMap := make(map[string]struct{}, len(new))
  1408  	for _, o := range old {
  1409  		oldMap[o] = struct{}{}
  1410  	}
  1411  	for _, n := range new {
  1412  		newMap[n] = struct{}{}
  1413  	}
  1414  	if reflect.DeepEqual(oldMap, newMap) && !contextual {
  1415  		return nil
  1416  	}
  1417  
  1418  	diff := &ObjectDiff{Name: name}
  1419  	var added, removed bool
  1420  	for k := range oldMap {
  1421  		if _, ok := newMap[k]; !ok {
  1422  			diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual))
  1423  			removed = true
  1424  		} else if contextual {
  1425  			diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual))
  1426  		}
  1427  	}
  1428  
  1429  	for k := range newMap {
  1430  		if _, ok := oldMap[k]; !ok {
  1431  			diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual))
  1432  			added = true
  1433  		}
  1434  	}
  1435  
  1436  	sort.Sort(FieldDiffs(diff.Fields))
  1437  
  1438  	// Determine the type
  1439  	if added && removed {
  1440  		diff.Type = DiffTypeEdited
  1441  	} else if added {
  1442  		diff.Type = DiffTypeAdded
  1443  	} else if removed {
  1444  		diff.Type = DiffTypeDeleted
  1445  	} else {
  1446  		// Diff of an empty set
  1447  		if len(diff.Fields) == 0 {
  1448  			return nil
  1449  		}
  1450  
  1451  		diff.Type = DiffTypeNone
  1452  	}
  1453  
  1454  	return diff
  1455  }
  1456  
  1457  // primitiveObjectDiff returns a diff of the passed objects' primitive fields.
  1458  // The filter field can be used to exclude fields from the diff. The name is the
  1459  // name of the objects. If contextual is set, non-changed fields will also be
  1460  // stored in the object diff.
  1461  func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff {
  1462  	oldPrimitiveFlat := flatmap.Flatten(old, filter, true)
  1463  	newPrimitiveFlat := flatmap.Flatten(new, filter, true)
  1464  	delete(oldPrimitiveFlat, "")
  1465  	delete(newPrimitiveFlat, "")
  1466  
  1467  	diff := &ObjectDiff{Name: name}
  1468  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1469  
  1470  	var added, deleted, edited bool
  1471  	for _, f := range diff.Fields {
  1472  		switch f.Type {
  1473  		case DiffTypeEdited:
  1474  			edited = true
  1475  			break
  1476  		case DiffTypeDeleted:
  1477  			deleted = true
  1478  		case DiffTypeAdded:
  1479  			added = true
  1480  		}
  1481  	}
  1482  
  1483  	if edited || added && deleted {
  1484  		diff.Type = DiffTypeEdited
  1485  	} else if added {
  1486  		diff.Type = DiffTypeAdded
  1487  	} else if deleted {
  1488  		diff.Type = DiffTypeDeleted
  1489  	} else {
  1490  		return nil
  1491  	}
  1492  
  1493  	return diff
  1494  }
  1495  
  1496  // primitiveObjectSetDiff does a set difference of the old and new sets. The
  1497  // filter parameter can be used to filter a set of primitive fields in the
  1498  // passed structs. The name corresponds to the name of the passed objects. If
  1499  // contextual diff is enabled, objects' primitive fields will be returned even if
  1500  // no diff exists.
  1501  func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff {
  1502  	makeSet := func(objects []interface{}) map[string]interface{} {
  1503  		objMap := make(map[string]interface{}, len(objects))
  1504  		for _, obj := range objects {
  1505  			hash, err := hashstructure.Hash(obj, nil)
  1506  			if err != nil {
  1507  				panic(err)
  1508  			}
  1509  			objMap[fmt.Sprintf("%d", hash)] = obj
  1510  		}
  1511  
  1512  		return objMap
  1513  	}
  1514  
  1515  	oldSet := makeSet(old)
  1516  	newSet := makeSet(new)
  1517  
  1518  	var diffs []*ObjectDiff
  1519  	for k, v := range oldSet {
  1520  		// Deleted
  1521  		if _, ok := newSet[k]; !ok {
  1522  			diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual))
  1523  		}
  1524  	}
  1525  	for k, v := range newSet {
  1526  		// Added
  1527  		if _, ok := oldSet[k]; !ok {
  1528  			diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual))
  1529  		}
  1530  	}
  1531  
  1532  	sort.Sort(ObjectDiffs(diffs))
  1533  	return diffs
  1534  }
  1535  
  1536  // interfaceSlice is a helper method that takes a slice of typed elements and
  1537  // returns a slice of interface. This method will panic if given a non-slice
  1538  // input.
  1539  func interfaceSlice(slice interface{}) []interface{} {
  1540  	s := reflect.ValueOf(slice)
  1541  	if s.Kind() != reflect.Slice {
  1542  		panic("InterfaceSlice() given a non-slice type")
  1543  	}
  1544  
  1545  	ret := make([]interface{}, s.Len())
  1546  
  1547  	for i := 0; i < s.Len(); i++ {
  1548  		ret[i] = s.Index(i).Interface()
  1549  	}
  1550  
  1551  	return ret
  1552  }