github.com/smithx10/nomad@v0.9.1-rc1/nomad/structs/diff.go (about)

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