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