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