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