github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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  // DiffableWithID defines an object that has a unique and stable value that can
    14  // be used as an identifier when generating a diff.
    15  type DiffableWithID interface {
    16  	// DiffID returns the value to use to match entities between the old and
    17  	// the new input.
    18  	DiffID() string
    19  }
    20  
    21  // DiffType denotes the type of a diff object.
    22  type DiffType string
    23  
    24  var (
    25  	DiffTypeNone    DiffType = "None"
    26  	DiffTypeAdded   DiffType = "Added"
    27  	DiffTypeDeleted DiffType = "Deleted"
    28  	DiffTypeEdited  DiffType = "Edited"
    29  )
    30  
    31  func (d DiffType) Less(other DiffType) bool {
    32  	// Edited > Added > Deleted > None
    33  	// But we do a reverse sort
    34  	if d == other {
    35  		return false
    36  	}
    37  
    38  	if d == DiffTypeEdited {
    39  		return true
    40  	} else if other == DiffTypeEdited {
    41  		return false
    42  	} else if d == DiffTypeAdded {
    43  		return true
    44  	} else if other == DiffTypeAdded {
    45  		return false
    46  	} else if d == DiffTypeDeleted {
    47  		return true
    48  	} else if other == DiffTypeDeleted {
    49  		return false
    50  	}
    51  
    52  	return true
    53  }
    54  
    55  // JobDiff contains the diff of two jobs.
    56  type JobDiff struct {
    57  	Type       DiffType
    58  	ID         string
    59  	Fields     []*FieldDiff
    60  	Objects    []*ObjectDiff
    61  	TaskGroups []*TaskGroupDiff
    62  }
    63  
    64  // Diff returns a diff of two jobs and a potential error if the Jobs are not
    65  // diffable. If contextual diff is enabled, objects within the job will contain
    66  // field information even if unchanged.
    67  func (j *Job) Diff(other *Job, contextual bool) (*JobDiff, error) {
    68  	// See agent.ApiJobToStructJob Update is a default for TaskGroups
    69  	diff := &JobDiff{Type: DiffTypeNone}
    70  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
    71  	filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex",
    72  		"ModifyIndex", "JobModifyIndex", "Update", "SubmitTime", "NomadTokenID", "VaultToken"}
    73  
    74  	if j == nil && other == nil {
    75  		return diff, nil
    76  	} else if j == nil {
    77  		j = &Job{}
    78  		diff.Type = DiffTypeAdded
    79  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
    80  		diff.ID = other.ID
    81  	} else if other == nil {
    82  		other = &Job{}
    83  		diff.Type = DiffTypeDeleted
    84  		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
    85  		diff.ID = j.ID
    86  	} else {
    87  		if j.ID != other.ID {
    88  			return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID)
    89  		}
    90  
    91  		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
    92  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
    93  		diff.ID = other.ID
    94  	}
    95  
    96  	// Diff the primitive fields.
    97  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
    98  
    99  	// Datacenters diff
   100  	if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone {
   101  		diff.Objects = append(diff.Objects, setDiff)
   102  	}
   103  
   104  	// Constraints diff
   105  	conDiff := primitiveObjectSetDiff(
   106  		interfaceSlice(j.Constraints),
   107  		interfaceSlice(other.Constraints),
   108  		[]string{"str"},
   109  		"Constraint",
   110  		contextual)
   111  	if conDiff != nil {
   112  		diff.Objects = append(diff.Objects, conDiff...)
   113  	}
   114  
   115  	// Affinities diff
   116  	affinitiesDiff := primitiveObjectSetDiff(
   117  		interfaceSlice(j.Affinities),
   118  		interfaceSlice(other.Affinities),
   119  		[]string{"str"},
   120  		"Affinity",
   121  		contextual)
   122  	if affinitiesDiff != nil {
   123  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   124  	}
   125  
   126  	// Task groups diff
   127  	tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	diff.TaskGroups = tgs
   132  
   133  	// Periodic diff
   134  	if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil {
   135  		diff.Objects = append(diff.Objects, pDiff)
   136  	}
   137  
   138  	// ParameterizedJob diff
   139  	if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil {
   140  		diff.Objects = append(diff.Objects, cDiff)
   141  	}
   142  
   143  	// Multiregion diff
   144  	if mrDiff := multiregionDiff(j.Multiregion, other.Multiregion, contextual); mrDiff != nil {
   145  		diff.Objects = append(diff.Objects, mrDiff)
   146  	}
   147  
   148  	// Check to see if there is a diff. We don't use reflect because we are
   149  	// filtering quite a few fields that will change on each diff.
   150  	if diff.Type == DiffTypeNone {
   151  		for _, fd := range diff.Fields {
   152  			if fd.Type != DiffTypeNone {
   153  				diff.Type = DiffTypeEdited
   154  				break
   155  			}
   156  		}
   157  	}
   158  
   159  	if diff.Type == DiffTypeNone {
   160  		for _, od := range diff.Objects {
   161  			if od.Type != DiffTypeNone {
   162  				diff.Type = DiffTypeEdited
   163  				break
   164  			}
   165  		}
   166  	}
   167  
   168  	if diff.Type == DiffTypeNone {
   169  		for _, tg := range diff.TaskGroups {
   170  			if tg.Type != DiffTypeNone {
   171  				diff.Type = DiffTypeEdited
   172  				break
   173  			}
   174  		}
   175  	}
   176  
   177  	return diff, nil
   178  }
   179  
   180  func (j *JobDiff) GoString() string {
   181  	out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type)
   182  
   183  	for _, f := range j.Fields {
   184  		out += fmt.Sprintf("%#v\n", f)
   185  	}
   186  
   187  	for _, o := range j.Objects {
   188  		out += fmt.Sprintf("%#v\n", o)
   189  	}
   190  
   191  	for _, tg := range j.TaskGroups {
   192  		out += fmt.Sprintf("%#v\n", tg)
   193  	}
   194  
   195  	return out
   196  }
   197  
   198  // TaskGroupDiff contains the diff of two task groups.
   199  type TaskGroupDiff struct {
   200  	Type    DiffType
   201  	Name    string
   202  	Fields  []*FieldDiff
   203  	Objects []*ObjectDiff
   204  	Tasks   []*TaskDiff
   205  	Updates map[string]uint64
   206  }
   207  
   208  // Diff returns a diff of two task groups. If contextual diff is enabled,
   209  // objects' fields will be stored even if no diff occurred as long as one field
   210  // changed.
   211  func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) {
   212  	diff := &TaskGroupDiff{Type: DiffTypeNone}
   213  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   214  	filter := []string{"Name"}
   215  
   216  	if tg == nil && other == nil {
   217  		return diff, nil
   218  	} else if tg == nil {
   219  		tg = &TaskGroup{}
   220  		diff.Type = DiffTypeAdded
   221  		diff.Name = other.Name
   222  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   223  	} else if other == nil {
   224  		other = &TaskGroup{}
   225  		diff.Type = DiffTypeDeleted
   226  		diff.Name = tg.Name
   227  		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
   228  	} else {
   229  		if !reflect.DeepEqual(tg, other) {
   230  			diff.Type = DiffTypeEdited
   231  		}
   232  		if tg.Name != other.Name {
   233  			return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name)
   234  		}
   235  		diff.Name = other.Name
   236  		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
   237  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   238  	}
   239  
   240  	// ShutdownDelay diff
   241  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
   242  		if tg.ShutdownDelay == nil {
   243  			oldPrimitiveFlat["ShutdownDelay"] = ""
   244  		} else {
   245  			oldPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *tg.ShutdownDelay)
   246  		}
   247  		if other.ShutdownDelay == nil {
   248  			newPrimitiveFlat["ShutdownDelay"] = ""
   249  		} else {
   250  			newPrimitiveFlat["ShutdownDelay"] = fmt.Sprintf("%d", *other.ShutdownDelay)
   251  		}
   252  	}
   253  
   254  	// StopAfterClientDisconnect diff
   255  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
   256  		if tg.StopAfterClientDisconnect == nil {
   257  			oldPrimitiveFlat["StopAfterClientDisconnect"] = ""
   258  		} else {
   259  			oldPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *tg.StopAfterClientDisconnect)
   260  		}
   261  		if other.StopAfterClientDisconnect == nil {
   262  			newPrimitiveFlat["StopAfterClientDisconnect"] = ""
   263  		} else {
   264  			newPrimitiveFlat["StopAfterClientDisconnect"] = fmt.Sprintf("%d", *other.StopAfterClientDisconnect)
   265  		}
   266  	}
   267  
   268  	// MaxClientDisconnect diff
   269  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
   270  		if tg.MaxClientDisconnect == nil {
   271  			oldPrimitiveFlat["MaxClientDisconnect"] = ""
   272  		} else {
   273  			oldPrimitiveFlat["MaxClientDisconnect"] = fmt.Sprintf("%d", *tg.MaxClientDisconnect)
   274  		}
   275  		if other.MaxClientDisconnect == nil {
   276  			newPrimitiveFlat["MaxClientDisconnect"] = ""
   277  		} else {
   278  			newPrimitiveFlat["MaxClientDisconnect"] = fmt.Sprintf("%d", *other.MaxClientDisconnect)
   279  		}
   280  	}
   281  
   282  	// Diff the primitive fields.
   283  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   284  
   285  	// Constraints diff
   286  	conDiff := primitiveObjectSetDiff(
   287  		interfaceSlice(tg.Constraints),
   288  		interfaceSlice(other.Constraints),
   289  		[]string{"str"},
   290  		"Constraint",
   291  		contextual)
   292  	if conDiff != nil {
   293  		diff.Objects = append(diff.Objects, conDiff...)
   294  	}
   295  
   296  	// Affinities diff
   297  	affinitiesDiff := primitiveObjectSetDiff(
   298  		interfaceSlice(tg.Affinities),
   299  		interfaceSlice(other.Affinities),
   300  		[]string{"str"},
   301  		"Affinity",
   302  		contextual)
   303  	if affinitiesDiff != nil {
   304  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   305  	}
   306  
   307  	// Restart policy diff
   308  	rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual)
   309  	if rDiff != nil {
   310  		diff.Objects = append(diff.Objects, rDiff)
   311  	}
   312  
   313  	// Reschedule policy diff
   314  	reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual)
   315  	if reschedDiff != nil {
   316  		diff.Objects = append(diff.Objects, reschedDiff)
   317  	}
   318  
   319  	// EphemeralDisk diff
   320  	diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual)
   321  	if diskDiff != nil {
   322  		diff.Objects = append(diff.Objects, diskDiff)
   323  	}
   324  
   325  	consulDiff := primitiveObjectDiff(tg.Consul, other.Consul, nil, "Consul", contextual)
   326  	if consulDiff != nil {
   327  		diff.Objects = append(diff.Objects, consulDiff)
   328  	}
   329  
   330  	// Update diff
   331  	// COMPAT: Remove "Stagger" in 0.7.0.
   332  	if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil {
   333  		diff.Objects = append(diff.Objects, uDiff)
   334  	}
   335  
   336  	// Network Resources diff
   337  	if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil {
   338  		diff.Objects = append(diff.Objects, nDiffs...)
   339  	}
   340  
   341  	// Services diff
   342  	if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil {
   343  		diff.Objects = append(diff.Objects, sDiffs...)
   344  	}
   345  
   346  	// Volumes diff
   347  	if vDiffs := volumeDiffs(tg.Volumes, other.Volumes, contextual); vDiffs != nil {
   348  		diff.Objects = append(diff.Objects, vDiffs...)
   349  	}
   350  
   351  	// Tasks diff
   352  	tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  	diff.Tasks = tasks
   357  
   358  	return diff, nil
   359  }
   360  
   361  func (tg *TaskGroupDiff) GoString() string {
   362  	out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type)
   363  
   364  	if len(tg.Updates) != 0 {
   365  		out += "Updates {\n"
   366  		for update, count := range tg.Updates {
   367  			out += fmt.Sprintf("%d %s\n", count, update)
   368  		}
   369  		out += "}\n"
   370  	}
   371  
   372  	for _, f := range tg.Fields {
   373  		out += fmt.Sprintf("%#v\n", f)
   374  	}
   375  
   376  	for _, o := range tg.Objects {
   377  		out += fmt.Sprintf("%#v\n", o)
   378  	}
   379  
   380  	for _, t := range tg.Tasks {
   381  		out += fmt.Sprintf("%#v\n", t)
   382  	}
   383  
   384  	return out
   385  }
   386  
   387  // TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled,
   388  // objects' fields will be stored even if no diff occurred as long as one field
   389  // changed.
   390  func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) {
   391  	oldMap := make(map[string]*TaskGroup, len(old))
   392  	newMap := make(map[string]*TaskGroup, len(new))
   393  	for _, o := range old {
   394  		oldMap[o.Name] = o
   395  	}
   396  	for _, n := range new {
   397  		newMap[n.Name] = n
   398  	}
   399  
   400  	var diffs []*TaskGroupDiff
   401  	for name, oldGroup := range oldMap {
   402  		// Diff the same, deleted and edited
   403  		diff, err := oldGroup.Diff(newMap[name], contextual)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  		diffs = append(diffs, diff)
   408  	}
   409  
   410  	for name, newGroup := range newMap {
   411  		// Diff the added
   412  		if old, ok := oldMap[name]; !ok {
   413  			diff, err := old.Diff(newGroup, contextual)
   414  			if err != nil {
   415  				return nil, err
   416  			}
   417  			diffs = append(diffs, diff)
   418  		}
   419  	}
   420  
   421  	sort.Sort(TaskGroupDiffs(diffs))
   422  	return diffs, nil
   423  }
   424  
   425  // For sorting TaskGroupDiffs
   426  type TaskGroupDiffs []*TaskGroupDiff
   427  
   428  func (tg TaskGroupDiffs) Len() int           { return len(tg) }
   429  func (tg TaskGroupDiffs) Swap(i, j int)      { tg[i], tg[j] = tg[j], tg[i] }
   430  func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name }
   431  
   432  // TaskDiff contains the diff of two Tasks
   433  type TaskDiff struct {
   434  	Type        DiffType
   435  	Name        string
   436  	Fields      []*FieldDiff
   437  	Objects     []*ObjectDiff
   438  	Annotations []string
   439  }
   440  
   441  // Diff returns a diff of two tasks. If contextual diff is enabled, objects
   442  // within the task will contain field information even if unchanged.
   443  func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
   444  	diff := &TaskDiff{Type: DiffTypeNone}
   445  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   446  	filter := []string{"Name", "Config"}
   447  
   448  	if t == nil && other == nil {
   449  		return diff, nil
   450  	} else if t == nil {
   451  		t = &Task{}
   452  		diff.Type = DiffTypeAdded
   453  		diff.Name = other.Name
   454  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   455  	} else if other == nil {
   456  		other = &Task{}
   457  		diff.Type = DiffTypeDeleted
   458  		diff.Name = t.Name
   459  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   460  	} else {
   461  		if !reflect.DeepEqual(t, other) {
   462  			diff.Type = DiffTypeEdited
   463  		}
   464  		if t.Name != other.Name {
   465  			return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name)
   466  		}
   467  		diff.Name = other.Name
   468  		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
   469  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
   470  	}
   471  
   472  	// Diff the primitive fields.
   473  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
   474  
   475  	// Constraints diff
   476  	conDiff := primitiveObjectSetDiff(
   477  		interfaceSlice(t.Constraints),
   478  		interfaceSlice(other.Constraints),
   479  		[]string{"str"},
   480  		"Constraint",
   481  		contextual)
   482  	if conDiff != nil {
   483  		diff.Objects = append(diff.Objects, conDiff...)
   484  	}
   485  
   486  	// Affinities diff
   487  	affinitiesDiff := primitiveObjectSetDiff(
   488  		interfaceSlice(t.Affinities),
   489  		interfaceSlice(other.Affinities),
   490  		[]string{"str"},
   491  		"Affinity",
   492  		contextual)
   493  	if affinitiesDiff != nil {
   494  		diff.Objects = append(diff.Objects, affinitiesDiff...)
   495  	}
   496  
   497  	// Config diff
   498  	if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil {
   499  		diff.Objects = append(diff.Objects, cDiff)
   500  	}
   501  
   502  	// Resources diff
   503  	if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil {
   504  		diff.Objects = append(diff.Objects, rDiff)
   505  	}
   506  
   507  	// LogConfig diff
   508  	lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual)
   509  	if lDiff != nil {
   510  		diff.Objects = append(diff.Objects, lDiff)
   511  	}
   512  
   513  	// Dispatch payload diff
   514  	dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual)
   515  	if dDiff != nil {
   516  		diff.Objects = append(diff.Objects, dDiff)
   517  	}
   518  
   519  	// Artifacts diff
   520  	diffs := primitiveObjectSetDiff(
   521  		interfaceSlice(t.Artifacts),
   522  		interfaceSlice(other.Artifacts),
   523  		nil,
   524  		"Artifact",
   525  		contextual)
   526  	if diffs != nil {
   527  		diff.Objects = append(diff.Objects, diffs...)
   528  	}
   529  
   530  	// Services diff
   531  	if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil {
   532  		diff.Objects = append(diff.Objects, sDiffs...)
   533  	}
   534  
   535  	// Vault diff
   536  	vDiff := vaultDiff(t.Vault, other.Vault, contextual)
   537  	if vDiff != nil {
   538  		diff.Objects = append(diff.Objects, vDiff)
   539  	}
   540  
   541  	// Template diff
   542  	tmplDiffs := templateDiffs(t.Templates, other.Templates, contextual)
   543  	if tmplDiffs != nil {
   544  		diff.Objects = append(diff.Objects, tmplDiffs...)
   545  	}
   546  
   547  	return diff, nil
   548  }
   549  
   550  func (t *TaskDiff) GoString() string {
   551  	var out string
   552  	if len(t.Annotations) == 0 {
   553  		out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type)
   554  	} else {
   555  		out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ","))
   556  	}
   557  
   558  	for _, f := range t.Fields {
   559  		out += fmt.Sprintf("%#v\n", f)
   560  	}
   561  
   562  	for _, o := range t.Objects {
   563  		out += fmt.Sprintf("%#v\n", o)
   564  	}
   565  
   566  	return out
   567  }
   568  
   569  // taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged
   570  // fields within objects nested in the tasks will be returned.
   571  func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) {
   572  	oldMap := make(map[string]*Task, len(old))
   573  	newMap := make(map[string]*Task, len(new))
   574  	for _, o := range old {
   575  		oldMap[o.Name] = o
   576  	}
   577  	for _, n := range new {
   578  		newMap[n.Name] = n
   579  	}
   580  
   581  	var diffs []*TaskDiff
   582  	for name, oldGroup := range oldMap {
   583  		// Diff the same, deleted and edited
   584  		diff, err := oldGroup.Diff(newMap[name], contextual)
   585  		if err != nil {
   586  			return nil, err
   587  		}
   588  		diffs = append(diffs, diff)
   589  	}
   590  
   591  	for name, newGroup := range newMap {
   592  		// Diff the added
   593  		if old, ok := oldMap[name]; !ok {
   594  			diff, err := old.Diff(newGroup, contextual)
   595  			if err != nil {
   596  				return nil, err
   597  			}
   598  			diffs = append(diffs, diff)
   599  		}
   600  	}
   601  
   602  	sort.Sort(TaskDiffs(diffs))
   603  	return diffs, nil
   604  }
   605  
   606  // For sorting TaskDiffs
   607  type TaskDiffs []*TaskDiff
   608  
   609  func (t TaskDiffs) Len() int           { return len(t) }
   610  func (t TaskDiffs) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   611  func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name }
   612  
   613  // serviceDiff returns the diff of two service objects. If contextual diff is
   614  // enabled, all fields will be returned, even if no diff occurred.
   615  func serviceDiff(old, new *Service, contextual bool) *ObjectDiff {
   616  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
   617  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   618  
   619  	if reflect.DeepEqual(old, new) {
   620  		return nil
   621  	} else if old == nil {
   622  		old = &Service{}
   623  		diff.Type = DiffTypeAdded
   624  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   625  	} else if new == nil {
   626  		new = &Service{}
   627  		diff.Type = DiffTypeDeleted
   628  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   629  	} else {
   630  		diff.Type = DiffTypeEdited
   631  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   632  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   633  	}
   634  
   635  	// Diff the primitive fields.
   636  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   637  
   638  	if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil {
   639  		diff.Objects = append(diff.Objects, setDiff)
   640  	}
   641  
   642  	// Tag diffs
   643  	if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil {
   644  		diff.Objects = append(diff.Objects, setDiff)
   645  	}
   646  
   647  	// Checks diffs
   648  	if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil {
   649  		diff.Objects = append(diff.Objects, cDiffs...)
   650  	}
   651  
   652  	// Consul Connect diffs
   653  	if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil {
   654  		diff.Objects = append(diff.Objects, conDiffs)
   655  	}
   656  
   657  	return diff
   658  }
   659  
   660  // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
   661  // fields within objects nested in the tasks will be returned.
   662  func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff {
   663  	// Handle trivial case.
   664  	if len(old) == 1 && len(new) == 1 {
   665  		if diff := serviceDiff(old[0], new[0], contextual); diff != nil {
   666  			return []*ObjectDiff{diff}
   667  		}
   668  		return nil
   669  	}
   670  
   671  	// For each service we will try to find a corresponding match in the other
   672  	// service list.
   673  	// The following lists store the index of the matching service for each
   674  	// position of the inputs.
   675  	oldMatches := make([]int, len(old))
   676  	newMatches := make([]int, len(new))
   677  
   678  	// Initialize all services as unmatched.
   679  	for i := range oldMatches {
   680  		oldMatches[i] = -1
   681  	}
   682  	for i := range newMatches {
   683  		newMatches[i] = -1
   684  	}
   685  
   686  	// Find a match in the new services list for each old service and compute
   687  	// their diffs.
   688  	var diffs []*ObjectDiff
   689  	for oldIndex, oldService := range old {
   690  		newIndex := findServiceMatch(oldService, oldIndex, new, newMatches)
   691  
   692  		// Old services that don't have a match were deleted.
   693  		if newIndex < 0 {
   694  			diff := serviceDiff(oldService, nil, contextual)
   695  			diffs = append(diffs, diff)
   696  			continue
   697  		}
   698  
   699  		// If A matches B then B matches A.
   700  		oldMatches[oldIndex] = newIndex
   701  		newMatches[newIndex] = oldIndex
   702  
   703  		newService := new[newIndex]
   704  		if diff := serviceDiff(oldService, newService, contextual); diff != nil {
   705  			diffs = append(diffs, diff)
   706  		}
   707  	}
   708  
   709  	// New services without match were added.
   710  	for i, m := range newMatches {
   711  		if m == -1 {
   712  			diff := serviceDiff(nil, new[i], contextual)
   713  			diffs = append(diffs, diff)
   714  		}
   715  	}
   716  
   717  	sort.Sort(ObjectDiffs(diffs))
   718  	return diffs
   719  }
   720  
   721  // findServiceMatch returns the index of the service in the input services list
   722  // that matches the provided input service.
   723  func findServiceMatch(service *Service, serviceIndex int, services []*Service, matches []int) int {
   724  	// minScoreThreshold can be adjusted to generate more (lower value) or
   725  	// fewer (higher value) matches.
   726  	// More matches result in more Edited diffs, while fewer matches generate
   727  	// more Add/Delete diff pairs.
   728  	minScoreThreshold := 2
   729  
   730  	highestScore := 0
   731  	indexMatch := -1
   732  
   733  	for i, s := range services {
   734  		// Skip service if it's already matched.
   735  		if matches[i] >= 0 {
   736  			continue
   737  		}
   738  
   739  		// Finding a perfect match by just looking at the before and after
   740  		// list of services is impossible since they don't have a stable
   741  		// identifier that can be used to uniquely identify them.
   742  		//
   743  		// Users also have an implicit temporal intuition of which services
   744  		// match each other when editing their jobspec file. If they move the
   745  		// 3rd service to the top, they don't expect their job to change.
   746  		//
   747  		// This intuition could be made explicit by requiring a user-defined
   748  		// unique identifier, but this would cause additional work and the
   749  		// new field would not be intuitive for users to understand how to use
   750  		// it.
   751  		//
   752  		// Using a hash value of the service content will cause any changes to
   753  		// create a delete/add diff pair.
   754  		//
   755  		// There are three main candidates for a service ID:
   756  		//   - name, but they are not unique and can be modified.
   757  		//   - label port, but they have the same problems as name.
   758  		//   - service position within the overall list of services, but if the
   759  		//     service block is moved, it will impact all services that come
   760  		//     after it.
   761  		//
   762  		// None of these values are enough on their own, but they are also too
   763  		// strong when considered all together.
   764  		//
   765  		// So we try to score services by their main candidates with a preference
   766  		// towards name + label over service position.
   767  		score := 0
   768  		if i == serviceIndex {
   769  			score += 1
   770  		}
   771  
   772  		if service.PortLabel == s.PortLabel {
   773  			score += 2
   774  		}
   775  
   776  		if service.Name == s.Name {
   777  			score += 3
   778  		}
   779  
   780  		if score > minScoreThreshold && score > highestScore {
   781  			highestScore = score
   782  			indexMatch = i
   783  		}
   784  	}
   785  
   786  	return indexMatch
   787  }
   788  
   789  // serviceCheckDiff returns the diff of two service check objects. If contextual
   790  // diff is enabled, all fields will be returned, even if no diff occurred.
   791  func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff {
   792  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"}
   793  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   794  
   795  	if reflect.DeepEqual(old, new) {
   796  		return nil
   797  	} else if old == nil {
   798  		old = &ServiceCheck{}
   799  		diff.Type = DiffTypeAdded
   800  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   801  	} else if new == nil {
   802  		new = &ServiceCheck{}
   803  		diff.Type = DiffTypeDeleted
   804  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   805  	} else {
   806  		diff.Type = DiffTypeEdited
   807  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   808  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   809  	}
   810  
   811  	// Diff the primitive fields.
   812  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   813  
   814  	// Diff Header
   815  	if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil {
   816  		diff.Objects = append(diff.Objects, headerDiff)
   817  	}
   818  
   819  	// Diff check_restart
   820  	if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil {
   821  		diff.Objects = append(diff.Objects, crDiff)
   822  	}
   823  
   824  	return diff
   825  }
   826  
   827  // checkHeaderDiff returns the diff of two service check header objects. If
   828  // contextual diff is enabled, all fields will be returned, even if no diff
   829  // occurred.
   830  func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff {
   831  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"}
   832  	var oldFlat, newFlat map[string]string
   833  
   834  	if reflect.DeepEqual(old, new) {
   835  		return nil
   836  	} else if len(old) == 0 {
   837  		diff.Type = DiffTypeAdded
   838  		newFlat = flatmap.Flatten(new, nil, false)
   839  	} else if len(new) == 0 {
   840  		diff.Type = DiffTypeDeleted
   841  		oldFlat = flatmap.Flatten(old, nil, false)
   842  	} else {
   843  		diff.Type = DiffTypeEdited
   844  		oldFlat = flatmap.Flatten(old, nil, false)
   845  		newFlat = flatmap.Flatten(new, nil, false)
   846  	}
   847  
   848  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   849  	return diff
   850  }
   851  
   852  // checkRestartDiff returns the diff of two service check check_restart
   853  // objects. If contextual diff is enabled, all fields will be returned, even if
   854  // no diff occurred.
   855  func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff {
   856  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"}
   857  	var oldFlat, newFlat map[string]string
   858  
   859  	if reflect.DeepEqual(old, new) {
   860  		return nil
   861  	} else if old == nil {
   862  		diff.Type = DiffTypeAdded
   863  		newFlat = flatmap.Flatten(new, nil, true)
   864  		diff.Type = DiffTypeAdded
   865  	} else if new == nil {
   866  		diff.Type = DiffTypeDeleted
   867  		oldFlat = flatmap.Flatten(old, nil, true)
   868  	} else {
   869  		diff.Type = DiffTypeEdited
   870  		oldFlat = flatmap.Flatten(old, nil, true)
   871  		newFlat = flatmap.Flatten(new, nil, true)
   872  	}
   873  
   874  	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
   875  	return diff
   876  }
   877  
   878  // connectDiffs returns the diff of two Consul connect objects. If contextual
   879  // diff is enabled, all fields will be returned, even if no diff occurred.
   880  func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff {
   881  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"}
   882  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   883  
   884  	if reflect.DeepEqual(old, new) {
   885  		return nil
   886  	} else if old == nil {
   887  		old = &ConsulConnect{}
   888  		diff.Type = DiffTypeAdded
   889  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   890  	} else if new == nil {
   891  		new = &ConsulConnect{}
   892  		diff.Type = DiffTypeDeleted
   893  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   894  	} else {
   895  		diff.Type = DiffTypeEdited
   896  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
   897  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
   898  	}
   899  
   900  	// Diff the primitive fields.
   901  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   902  
   903  	// Diff the object field SidecarService.
   904  	sidecarSvcDiff := connectSidecarServiceDiff(old.SidecarService, new.SidecarService, contextual)
   905  	if sidecarSvcDiff != nil {
   906  		diff.Objects = append(diff.Objects, sidecarSvcDiff)
   907  	}
   908  
   909  	// Diff the object field SidecarTask.
   910  	sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual)
   911  	if sidecarTaskDiff != nil {
   912  		diff.Objects = append(diff.Objects, sidecarTaskDiff)
   913  	}
   914  
   915  	// Diff the object field ConsulGateway.
   916  	gatewayDiff := connectGatewayDiff(old.Gateway, new.Gateway, contextual)
   917  	if gatewayDiff != nil {
   918  		diff.Objects = append(diff.Objects, gatewayDiff)
   919  	}
   920  
   921  	return diff
   922  }
   923  
   924  func connectGatewayDiff(prev, next *ConsulGateway, contextual bool) *ObjectDiff {
   925  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Gateway"}
   926  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   927  
   928  	if reflect.DeepEqual(prev, next) {
   929  		return nil
   930  	} else if prev == nil {
   931  		prev = new(ConsulGateway)
   932  		diff.Type = DiffTypeAdded
   933  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   934  	} else if next == nil {
   935  		next = new(ConsulGateway)
   936  		diff.Type = DiffTypeDeleted
   937  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   938  	} else {
   939  		diff.Type = DiffTypeEdited
   940  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
   941  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
   942  	}
   943  
   944  	// Diff the primitive fields.
   945  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
   946  
   947  	// Diff the ConsulGatewayProxy fields.
   948  	gatewayProxyDiff := connectGatewayProxyDiff(prev.Proxy, next.Proxy, contextual)
   949  	if gatewayProxyDiff != nil {
   950  		diff.Objects = append(diff.Objects, gatewayProxyDiff)
   951  	}
   952  
   953  	// Diff the ingress gateway fields.
   954  	gatewayIngressDiff := connectGatewayIngressDiff(prev.Ingress, next.Ingress, contextual)
   955  	if gatewayIngressDiff != nil {
   956  		diff.Objects = append(diff.Objects, gatewayIngressDiff)
   957  	}
   958  
   959  	//  Diff the terminating gateway fields.
   960  	gatewayTerminatingDiff := connectGatewayTerminatingDiff(prev.Terminating, next.Terminating, contextual)
   961  	if gatewayTerminatingDiff != nil {
   962  		diff.Objects = append(diff.Objects, gatewayTerminatingDiff)
   963  	}
   964  
   965  	// Diff the mesh gateway fields.
   966  	gatewayMeshDiff := connectGatewayMeshDiff(prev.Mesh, next.Mesh, contextual)
   967  	if gatewayMeshDiff != nil {
   968  		diff.Objects = append(diff.Objects, gatewayMeshDiff)
   969  	}
   970  
   971  	return diff
   972  }
   973  
   974  func connectGatewayMeshDiff(prev, next *ConsulMeshConfigEntry, contextual bool) *ObjectDiff {
   975  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Mesh"}
   976  
   977  	if reflect.DeepEqual(prev, next) {
   978  		return nil
   979  	} else if prev == nil {
   980  		// no fields to further diff
   981  		diff.Type = DiffTypeAdded
   982  	} else if next == nil {
   983  		// no fields to further diff
   984  		diff.Type = DiffTypeDeleted
   985  	} else {
   986  		diff.Type = DiffTypeEdited
   987  	}
   988  
   989  	// Currently no fields in mesh gateways.
   990  
   991  	return diff
   992  }
   993  
   994  func connectGatewayIngressDiff(prev, next *ConsulIngressConfigEntry, contextual bool) *ObjectDiff {
   995  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Ingress"}
   996  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
   997  
   998  	if reflect.DeepEqual(prev, next) {
   999  		return nil
  1000  	} else if prev == nil {
  1001  		prev = new(ConsulIngressConfigEntry)
  1002  		diff.Type = DiffTypeAdded
  1003  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1004  	} else if next == nil {
  1005  		next = new(ConsulIngressConfigEntry)
  1006  		diff.Type = DiffTypeDeleted
  1007  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1008  	} else {
  1009  		diff.Type = DiffTypeEdited
  1010  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1011  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1012  	}
  1013  
  1014  	// Diff the primitive fields.
  1015  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1016  
  1017  	// Diff the ConsulGatewayTLSConfig objects.
  1018  	tlsConfigDiff := connectGatewayTLSConfigDiff(prev.TLS, next.TLS, contextual)
  1019  	if tlsConfigDiff != nil {
  1020  		diff.Objects = append(diff.Objects, tlsConfigDiff)
  1021  	}
  1022  
  1023  	// Diff the Listeners lists.
  1024  	gatewayIngressListenersDiff := connectGatewayIngressListenersDiff(prev.Listeners, next.Listeners, contextual)
  1025  	if gatewayIngressListenersDiff != nil {
  1026  		diff.Objects = append(diff.Objects, gatewayIngressListenersDiff...)
  1027  	}
  1028  
  1029  	return diff
  1030  }
  1031  
  1032  func connectGatewayTerminatingDiff(prev, next *ConsulTerminatingConfigEntry, contextual bool) *ObjectDiff {
  1033  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Terminating"}
  1034  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1035  
  1036  	if reflect.DeepEqual(prev, next) {
  1037  		return nil
  1038  	} else if prev == nil {
  1039  		prev = new(ConsulTerminatingConfigEntry)
  1040  		diff.Type = DiffTypeAdded
  1041  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1042  	} else if next == nil {
  1043  		next = new(ConsulTerminatingConfigEntry)
  1044  		diff.Type = DiffTypeDeleted
  1045  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1046  	} else {
  1047  		diff.Type = DiffTypeEdited
  1048  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1049  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1050  	}
  1051  
  1052  	// Diff the primitive fields.
  1053  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1054  
  1055  	// Diff the Services lists.
  1056  	gatewayLinkedServicesDiff := connectGatewayTerminatingLinkedServicesDiff(prev.Services, next.Services, contextual)
  1057  	if gatewayLinkedServicesDiff != nil {
  1058  		diff.Objects = append(diff.Objects, gatewayLinkedServicesDiff...)
  1059  	}
  1060  
  1061  	return diff
  1062  }
  1063  
  1064  // connectGatewayTerminatingLinkedServicesDiff diffs are a set of services keyed
  1065  // by service name. These objects contain only fields.
  1066  func connectGatewayTerminatingLinkedServicesDiff(prev, next []*ConsulLinkedService, contextual bool) []*ObjectDiff {
  1067  	// create maps, diff the maps, key by linked service name
  1068  
  1069  	prevMap := make(map[string]*ConsulLinkedService, len(prev))
  1070  	nextMap := make(map[string]*ConsulLinkedService, len(next))
  1071  
  1072  	for _, s := range prev {
  1073  		prevMap[s.Name] = s
  1074  	}
  1075  	for _, s := range next {
  1076  		nextMap[s.Name] = s
  1077  	}
  1078  
  1079  	var diffs []*ObjectDiff
  1080  	for k, prevS := range prevMap {
  1081  		// Diff the same, deleted, and edited
  1082  		if diff := connectGatewayTerminatingLinkedServiceDiff(prevS, nextMap[k], contextual); diff != nil {
  1083  			diffs = append(diffs, diff)
  1084  		}
  1085  	}
  1086  	for k, nextS := range nextMap {
  1087  		// Diff the added
  1088  		if old, ok := prevMap[k]; !ok {
  1089  			if diff := connectGatewayTerminatingLinkedServiceDiff(old, nextS, contextual); diff != nil {
  1090  				diffs = append(diffs, diff)
  1091  			}
  1092  		}
  1093  	}
  1094  
  1095  	sort.Sort(ObjectDiffs(diffs))
  1096  	return diffs
  1097  }
  1098  
  1099  func connectGatewayTerminatingLinkedServiceDiff(prev, next *ConsulLinkedService, contextual bool) *ObjectDiff {
  1100  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
  1101  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1102  
  1103  	if reflect.DeepEqual(prev, next) {
  1104  		return nil
  1105  	} else if prev == nil {
  1106  		diff.Type = DiffTypeAdded
  1107  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1108  	} else if next == nil {
  1109  		diff.Type = DiffTypeDeleted
  1110  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1111  	} else {
  1112  		diff.Type = DiffTypeEdited
  1113  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1114  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1115  	}
  1116  
  1117  	// Diff the primitive fields.
  1118  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1119  
  1120  	// No objects today.
  1121  
  1122  	return diff
  1123  }
  1124  
  1125  func connectGatewayTLSConfigDiff(prev, next *ConsulGatewayTLSConfig, contextual bool) *ObjectDiff {
  1126  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "TLS"}
  1127  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1128  
  1129  	if reflect.DeepEqual(prev, next) {
  1130  		return nil
  1131  	} else if prev == nil {
  1132  		prev = &ConsulGatewayTLSConfig{}
  1133  		diff.Type = DiffTypeAdded
  1134  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1135  	} else if next == nil {
  1136  		next = &ConsulGatewayTLSConfig{}
  1137  		diff.Type = DiffTypeDeleted
  1138  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1139  	} else {
  1140  		diff.Type = DiffTypeEdited
  1141  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1142  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1143  	}
  1144  
  1145  	// CipherSuites diffs
  1146  	if setDiff := stringSetDiff(prev.CipherSuites, next.CipherSuites, "CipherSuites", contextual); setDiff != nil {
  1147  		diff.Objects = append(diff.Objects, setDiff)
  1148  	}
  1149  
  1150  	// Diff the primitive field.
  1151  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1152  
  1153  	return diff
  1154  }
  1155  
  1156  // connectGatewayIngressListenersDiff diffs are a set of listeners keyed by "protocol/port", which is
  1157  // a nifty workaround having slices instead of maps. Presumably such a key will be unique, because if
  1158  // if is not the config entry is not going to work anyway.
  1159  func connectGatewayIngressListenersDiff(prev, next []*ConsulIngressListener, contextual bool) []*ObjectDiff {
  1160  	//  create maps, diff the maps, keys are fields, keys are (port+protocol)
  1161  
  1162  	key := func(l *ConsulIngressListener) string {
  1163  		return fmt.Sprintf("%s/%d", l.Protocol, l.Port)
  1164  	}
  1165  
  1166  	prevMap := make(map[string]*ConsulIngressListener, len(prev))
  1167  	nextMap := make(map[string]*ConsulIngressListener, len(next))
  1168  
  1169  	for _, l := range prev {
  1170  		prevMap[key(l)] = l
  1171  	}
  1172  	for _, l := range next {
  1173  		nextMap[key(l)] = l
  1174  	}
  1175  
  1176  	var diffs []*ObjectDiff
  1177  	for k, prevL := range prevMap {
  1178  		// Diff the same, deleted, and edited
  1179  		if diff := connectGatewayIngressListenerDiff(prevL, nextMap[k], contextual); diff != nil {
  1180  			diffs = append(diffs, diff)
  1181  		}
  1182  	}
  1183  	for k, nextL := range nextMap {
  1184  		// Diff the added
  1185  		if old, ok := prevMap[k]; !ok {
  1186  			if diff := connectGatewayIngressListenerDiff(old, nextL, contextual); diff != nil {
  1187  				diffs = append(diffs, diff)
  1188  			}
  1189  		}
  1190  	}
  1191  
  1192  	sort.Sort(ObjectDiffs(diffs))
  1193  	return diffs
  1194  }
  1195  
  1196  func connectGatewayIngressListenerDiff(prev, next *ConsulIngressListener, contextual bool) *ObjectDiff {
  1197  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Listener"}
  1198  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1199  
  1200  	if reflect.DeepEqual(prev, next) {
  1201  		return nil
  1202  	} else if prev == nil {
  1203  		prev = new(ConsulIngressListener)
  1204  		diff.Type = DiffTypeAdded
  1205  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1206  	} else if next == nil {
  1207  		next = new(ConsulIngressListener)
  1208  		diff.Type = DiffTypeDeleted
  1209  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1210  	} else {
  1211  		diff.Type = DiffTypeEdited
  1212  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1213  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1214  	}
  1215  
  1216  	// Diff the primitive fields.
  1217  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1218  
  1219  	// Diff the Ingress Service objects.
  1220  	if diffs := connectGatewayIngressServicesDiff(prev.Services, next.Services, contextual); diffs != nil {
  1221  		diff.Objects = append(diff.Objects, diffs...)
  1222  	}
  1223  
  1224  	return diff
  1225  }
  1226  
  1227  // connectGatewayIngressServicesDiff diffs are a set of ingress services keyed by their service name, which
  1228  // is a workaround for having slices instead of maps. Presumably the service name is a unique key, because if
  1229  // no the config entry is not going to make sense anyway.
  1230  func connectGatewayIngressServicesDiff(prev, next []*ConsulIngressService, contextual bool) []*ObjectDiff {
  1231  
  1232  	prevMap := make(map[string]*ConsulIngressService, len(prev))
  1233  	nextMap := make(map[string]*ConsulIngressService, len(next))
  1234  
  1235  	for _, s := range prev {
  1236  		prevMap[s.Name] = s
  1237  	}
  1238  	for _, s := range next {
  1239  		nextMap[s.Name] = s
  1240  	}
  1241  
  1242  	var diffs []*ObjectDiff
  1243  	for name, oldIS := range prevMap {
  1244  		// Diff the same, deleted, and edited
  1245  		if diff := connectGatewayIngressServiceDiff(oldIS, nextMap[name], contextual); diff != nil {
  1246  			diffs = append(diffs, diff)
  1247  		}
  1248  	}
  1249  	for name, newIS := range nextMap {
  1250  		// Diff the added
  1251  		if old, ok := prevMap[name]; !ok {
  1252  			if diff := connectGatewayIngressServiceDiff(old, newIS, contextual); diff != nil {
  1253  				diffs = append(diffs, diff)
  1254  			}
  1255  		}
  1256  	}
  1257  
  1258  	sort.Sort(ObjectDiffs(diffs))
  1259  	return diffs
  1260  }
  1261  
  1262  func connectGatewayIngressServiceDiff(prev, next *ConsulIngressService, contextual bool) *ObjectDiff {
  1263  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulIngressService"}
  1264  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1265  
  1266  	if reflect.DeepEqual(prev, next) {
  1267  		return nil
  1268  	} else if prev == nil {
  1269  		prev = new(ConsulIngressService)
  1270  		diff.Type = DiffTypeAdded
  1271  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1272  	} else if next == nil {
  1273  		next = new(ConsulIngressService)
  1274  		diff.Type = DiffTypeDeleted
  1275  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1276  	} else {
  1277  		diff.Type = DiffTypeEdited
  1278  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1279  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1280  	}
  1281  
  1282  	// Diff the primitive fields.
  1283  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1284  
  1285  	// Diff the hosts.
  1286  	if hDiffs := stringSetDiff(prev.Hosts, next.Hosts, "Hosts", contextual); hDiffs != nil {
  1287  		diff.Objects = append(diff.Objects, hDiffs)
  1288  	}
  1289  
  1290  	return diff
  1291  }
  1292  
  1293  func connectGatewayProxyDiff(prev, next *ConsulGatewayProxy, contextual bool) *ObjectDiff {
  1294  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Proxy"}
  1295  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1296  
  1297  	if reflect.DeepEqual(prev, next) {
  1298  		return nil
  1299  	} else if prev == nil {
  1300  		prev = new(ConsulGatewayProxy)
  1301  		diff.Type = DiffTypeAdded
  1302  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1303  	} else if next == nil {
  1304  		next = new(ConsulGatewayProxy)
  1305  		diff.Type = DiffTypeDeleted
  1306  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1307  	} else {
  1308  		diff.Type = DiffTypeEdited
  1309  		oldPrimitiveFlat = flatmap.Flatten(prev, nil, true)
  1310  		newPrimitiveFlat = flatmap.Flatten(next, nil, true)
  1311  	}
  1312  
  1313  	// Diff the ConnectTimeout field (dur ptr). (i.e. convert to string for comparison)
  1314  	if oldPrimitiveFlat != nil && newPrimitiveFlat != nil {
  1315  		if prev.ConnectTimeout == nil {
  1316  			oldPrimitiveFlat["ConnectTimeout"] = ""
  1317  		} else {
  1318  			oldPrimitiveFlat["ConnectTimeout"] = prev.ConnectTimeout.String()
  1319  		}
  1320  		if next.ConnectTimeout == nil {
  1321  			newPrimitiveFlat["ConnectTimeout"] = ""
  1322  		} else {
  1323  			newPrimitiveFlat["ConnectTimeout"] = next.ConnectTimeout.String()
  1324  		}
  1325  	}
  1326  
  1327  	// Diff the primitive fields.
  1328  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1329  
  1330  	// Diff the EnvoyGatewayBindAddresses map.
  1331  	bindAddrsDiff := connectGatewayProxyEnvoyBindAddrsDiff(prev.EnvoyGatewayBindAddresses, next.EnvoyGatewayBindAddresses, contextual)
  1332  	if bindAddrsDiff != nil {
  1333  		diff.Objects = append(diff.Objects, bindAddrsDiff)
  1334  	}
  1335  
  1336  	// Diff the opaque Config map.
  1337  	if cDiff := configDiff(prev.Config, next.Config, contextual); cDiff != nil {
  1338  		diff.Objects = append(diff.Objects, cDiff)
  1339  	}
  1340  
  1341  	return diff
  1342  }
  1343  
  1344  // connectGatewayProxyEnvoyBindAddrsDiff returns the diff of two maps. If contextual
  1345  // diff is enabled, all fields will be returned, even if no diff occurred.
  1346  func connectGatewayProxyEnvoyBindAddrsDiff(prev, next map[string]*ConsulGatewayBindAddress, contextual bool) *ObjectDiff {
  1347  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "EnvoyGatewayBindAddresses"}
  1348  	if reflect.DeepEqual(prev, next) {
  1349  		return nil
  1350  	} else if len(prev) == 0 {
  1351  		diff.Type = DiffTypeAdded
  1352  	} else if len(next) == 0 {
  1353  		diff.Type = DiffTypeDeleted
  1354  	} else {
  1355  		diff.Type = DiffTypeEdited
  1356  	}
  1357  
  1358  	// convert to string representation
  1359  	prevMap := make(map[string]string, len(prev))
  1360  	nextMap := make(map[string]string, len(next))
  1361  
  1362  	for k, v := range prev {
  1363  		prevMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port)
  1364  	}
  1365  
  1366  	for k, v := range next {
  1367  		nextMap[k] = fmt.Sprintf("%s:%d", v.Address, v.Port)
  1368  	}
  1369  
  1370  	oldPrimitiveFlat := flatmap.Flatten(prevMap, nil, false)
  1371  	newPrimitiveFlat := flatmap.Flatten(nextMap, nil, false)
  1372  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1373  	return diff
  1374  }
  1375  
  1376  // connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects.
  1377  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1378  func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff {
  1379  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"}
  1380  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1381  
  1382  	if reflect.DeepEqual(old, new) {
  1383  		return nil
  1384  	} else if old == nil {
  1385  		old = &ConsulSidecarService{}
  1386  		diff.Type = DiffTypeAdded
  1387  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1388  	} else if new == nil {
  1389  		new = &ConsulSidecarService{}
  1390  		diff.Type = DiffTypeDeleted
  1391  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1392  	} else {
  1393  		diff.Type = DiffTypeEdited
  1394  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1395  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1396  	}
  1397  
  1398  	// Diff the primitive fields.
  1399  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1400  
  1401  	consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual)
  1402  	if consulProxyDiff != nil {
  1403  		diff.Objects = append(diff.Objects, consulProxyDiff)
  1404  	}
  1405  
  1406  	return diff
  1407  }
  1408  
  1409  // sidecarTaskDiff returns the diff of two Task objects.
  1410  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1411  func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff {
  1412  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"}
  1413  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1414  
  1415  	if reflect.DeepEqual(old, new) {
  1416  		return nil
  1417  	} else if old == nil {
  1418  		old = &SidecarTask{}
  1419  		diff.Type = DiffTypeAdded
  1420  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1421  	} else if new == nil {
  1422  		new = &SidecarTask{}
  1423  		diff.Type = DiffTypeDeleted
  1424  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1425  	} else {
  1426  		diff.Type = DiffTypeEdited
  1427  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1428  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1429  	}
  1430  
  1431  	// Diff the primitive fields.
  1432  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
  1433  
  1434  	// Config diff
  1435  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
  1436  		diff.Objects = append(diff.Objects, cDiff)
  1437  	}
  1438  
  1439  	// Resources diff
  1440  	if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil {
  1441  		diff.Objects = append(diff.Objects, rDiff)
  1442  	}
  1443  
  1444  	// LogConfig diff
  1445  	lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual)
  1446  	if lDiff != nil {
  1447  		diff.Objects = append(diff.Objects, lDiff)
  1448  	}
  1449  
  1450  	return diff
  1451  }
  1452  
  1453  // consulProxyDiff returns the diff of two ConsulProxy objects.
  1454  // If contextual diff is enabled, all fields will be returned, even if no diff occurred.
  1455  func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff {
  1456  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"}
  1457  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1458  
  1459  	if reflect.DeepEqual(old, new) {
  1460  		return nil
  1461  	} else if old == nil {
  1462  		old = &ConsulProxy{}
  1463  		diff.Type = DiffTypeAdded
  1464  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1465  	} else if new == nil {
  1466  		new = &ConsulProxy{}
  1467  		diff.Type = DiffTypeDeleted
  1468  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1469  	} else {
  1470  		diff.Type = DiffTypeEdited
  1471  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1472  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1473  	}
  1474  
  1475  	// diff the primitive fields
  1476  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1477  
  1478  	// diff the consul upstream slices
  1479  	if upDiffs := consulProxyUpstreamsDiff(old.Upstreams, new.Upstreams, contextual); upDiffs != nil {
  1480  		diff.Objects = append(diff.Objects, upDiffs...)
  1481  	}
  1482  
  1483  	// diff the config blob
  1484  	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
  1485  		diff.Objects = append(diff.Objects, cDiff)
  1486  	}
  1487  
  1488  	return diff
  1489  }
  1490  
  1491  // consulProxyUpstreamsDiff diffs a set of connect upstreams. If contextual diff is
  1492  // enabled, unchanged fields within objects nested in the tasks will be returned.
  1493  func consulProxyUpstreamsDiff(old, new []ConsulUpstream, contextual bool) []*ObjectDiff {
  1494  	oldMap := make(map[string]ConsulUpstream, len(old))
  1495  	newMap := make(map[string]ConsulUpstream, len(new))
  1496  
  1497  	idx := func(up ConsulUpstream) string {
  1498  		return fmt.Sprintf("%s/%s", up.Datacenter, up.DestinationName)
  1499  	}
  1500  
  1501  	for _, o := range old {
  1502  		oldMap[idx(o)] = o
  1503  	}
  1504  	for _, n := range new {
  1505  		newMap[idx(n)] = n
  1506  	}
  1507  
  1508  	var diffs []*ObjectDiff
  1509  	for index, oldUpstream := range oldMap {
  1510  		// Diff the same, deleted, and edited
  1511  		if diff := consulProxyUpstreamDiff(oldUpstream, newMap[index], contextual); diff != nil {
  1512  			diffs = append(diffs, diff)
  1513  		}
  1514  	}
  1515  
  1516  	for index, newUpstream := range newMap {
  1517  		// diff the added
  1518  		if oldUpstream, exists := oldMap[index]; !exists {
  1519  			if diff := consulProxyUpstreamDiff(oldUpstream, newUpstream, contextual); diff != nil {
  1520  				diffs = append(diffs, diff)
  1521  			}
  1522  		}
  1523  	}
  1524  	sort.Sort(ObjectDiffs(diffs))
  1525  	return diffs
  1526  }
  1527  
  1528  func consulProxyUpstreamDiff(prev, next ConsulUpstream, contextual bool) *ObjectDiff {
  1529  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulUpstreams"}
  1530  	var oldPrimFlat, newPrimFlat map[string]string
  1531  
  1532  	if reflect.DeepEqual(prev, next) {
  1533  		return nil
  1534  	} else if prev.Equal(new(ConsulUpstream)) {
  1535  		prev = ConsulUpstream{}
  1536  		diff.Type = DiffTypeAdded
  1537  		newPrimFlat = flatmap.Flatten(next, nil, true)
  1538  	} else if next.Equal(new(ConsulUpstream)) {
  1539  		next = ConsulUpstream{}
  1540  		diff.Type = DiffTypeDeleted
  1541  		oldPrimFlat = flatmap.Flatten(prev, nil, true)
  1542  	} else {
  1543  		diff.Type = DiffTypeEdited
  1544  		oldPrimFlat = flatmap.Flatten(prev, nil, true)
  1545  		newPrimFlat = flatmap.Flatten(next, nil, true)
  1546  	}
  1547  
  1548  	// diff the primitive fields
  1549  	diff.Fields = fieldDiffs(oldPrimFlat, newPrimFlat, contextual)
  1550  
  1551  	// diff the mesh gateway primitive object
  1552  	if mDiff := primitiveObjectDiff(prev.MeshGateway, next.MeshGateway, nil, "MeshGateway", contextual); mDiff != nil {
  1553  		diff.Objects = append(diff.Objects, mDiff)
  1554  	}
  1555  
  1556  	return diff
  1557  }
  1558  
  1559  // serviceCheckDiffs diffs a set of service checks. If contextual diff is
  1560  // enabled, unchanged fields within objects nested in the tasks will be
  1561  // returned.
  1562  func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff {
  1563  	oldMap := make(map[string]*ServiceCheck, len(old))
  1564  	newMap := make(map[string]*ServiceCheck, len(new))
  1565  	for _, o := range old {
  1566  		oldMap[o.Name] = o
  1567  	}
  1568  	for _, n := range new {
  1569  		newMap[n.Name] = n
  1570  	}
  1571  
  1572  	var diffs []*ObjectDiff
  1573  	for name, oldCheck := range oldMap {
  1574  		// Diff the same, deleted and edited
  1575  		if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil {
  1576  			diffs = append(diffs, diff)
  1577  		}
  1578  	}
  1579  
  1580  	for name, newCheck := range newMap {
  1581  		// Diff the added
  1582  		if old, ok := oldMap[name]; !ok {
  1583  			if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil {
  1584  				diffs = append(diffs, diff)
  1585  			}
  1586  		}
  1587  	}
  1588  
  1589  	sort.Sort(ObjectDiffs(diffs))
  1590  	return diffs
  1591  }
  1592  
  1593  // vaultDiff returns the diff of two vault objects. If contextual diff is
  1594  // enabled, all fields will be returned, even if no diff occurred.
  1595  func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff {
  1596  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"}
  1597  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1598  
  1599  	if reflect.DeepEqual(old, new) {
  1600  		return nil
  1601  	} else if old == nil {
  1602  		old = &Vault{}
  1603  		diff.Type = DiffTypeAdded
  1604  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1605  	} else if new == nil {
  1606  		new = &Vault{}
  1607  		diff.Type = DiffTypeDeleted
  1608  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1609  	} else {
  1610  		diff.Type = DiffTypeEdited
  1611  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1612  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1613  	}
  1614  
  1615  	// Diff the primitive fields.
  1616  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1617  
  1618  	// Policies diffs
  1619  	if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil {
  1620  		diff.Objects = append(diff.Objects, setDiff)
  1621  	}
  1622  
  1623  	return diff
  1624  }
  1625  
  1626  // waitConfigDiff returns the diff of two WaitConfig objects. If contextual diff is
  1627  // enabled, all fields will be returned, even if no diff occurred.
  1628  func waitConfigDiff(old, new *WaitConfig, contextual bool) *ObjectDiff {
  1629  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Template"}
  1630  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1631  
  1632  	if reflect.DeepEqual(old, new) {
  1633  		return nil
  1634  	} else if old == nil {
  1635  		diff.Type = DiffTypeAdded
  1636  		newPrimitiveFlat = flatmap.Flatten(new, nil, false)
  1637  	} else if new == nil {
  1638  		diff.Type = DiffTypeDeleted
  1639  		oldPrimitiveFlat = flatmap.Flatten(old, nil, false)
  1640  	} else {
  1641  		diff.Type = DiffTypeEdited
  1642  		oldPrimitiveFlat = flatmap.Flatten(old, nil, false)
  1643  		newPrimitiveFlat = flatmap.Flatten(new, nil, false)
  1644  	}
  1645  
  1646  	// Diff the primitive fields.
  1647  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1648  
  1649  	return diff
  1650  }
  1651  
  1652  // changeScriptDiff returns the diff of two ChangeScript objects. If contextual
  1653  // diff is enabled, all fields will be returned, even if no diff occurred.
  1654  func changeScriptDiff(old, new *ChangeScript, contextual bool) *ObjectDiff {
  1655  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ChangeScript"}
  1656  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1657  
  1658  	if reflect.DeepEqual(old, new) {
  1659  		return nil
  1660  	} else if old == nil {
  1661  		old = &ChangeScript{}
  1662  		diff.Type = DiffTypeAdded
  1663  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1664  	} else if new == nil {
  1665  		new = &ChangeScript{}
  1666  		diff.Type = DiffTypeDeleted
  1667  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1668  	} else {
  1669  		diff.Type = DiffTypeEdited
  1670  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1671  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1672  	}
  1673  
  1674  	// Diff the primitive fields.
  1675  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1676  
  1677  	// Args diffs
  1678  	if setDiff := stringSetDiff(old.Args, new.Args, "Args", contextual); setDiff != nil {
  1679  		diff.Objects = append(diff.Objects, setDiff)
  1680  	}
  1681  
  1682  	return diff
  1683  }
  1684  
  1685  // templateDiff returns the diff of two Consul Template objects. If contextual diff is
  1686  // enabled, all fields will be returned, even if no diff occurred.
  1687  func templateDiff(old, new *Template, contextual bool) *ObjectDiff {
  1688  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Template"}
  1689  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1690  
  1691  	if reflect.DeepEqual(old, new) {
  1692  		return nil
  1693  	} else if old == nil {
  1694  		old = &Template{}
  1695  		diff.Type = DiffTypeAdded
  1696  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1697  	} else if new == nil {
  1698  		new = &Template{}
  1699  		diff.Type = DiffTypeDeleted
  1700  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1701  	} else {
  1702  		diff.Type = DiffTypeEdited
  1703  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1704  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1705  	}
  1706  
  1707  	// Add the pointer primitive fields.
  1708  	if old != nil {
  1709  		if old.Uid != nil {
  1710  			oldPrimitiveFlat["Uid"] = fmt.Sprintf("%v", *old.Uid)
  1711  		}
  1712  		if old.Gid != nil {
  1713  			oldPrimitiveFlat["Gid"] = fmt.Sprintf("%v", *old.Gid)
  1714  		}
  1715  	}
  1716  	if new != nil {
  1717  		if new.Uid != nil {
  1718  			newPrimitiveFlat["Uid"] = fmt.Sprintf("%v", *new.Uid)
  1719  		}
  1720  		if new.Gid != nil {
  1721  			newPrimitiveFlat["Gid"] = fmt.Sprintf("%v", *new.Gid)
  1722  		}
  1723  	}
  1724  
  1725  	// Diff the primitive fields.
  1726  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1727  
  1728  	// WaitConfig diffs
  1729  	if waitDiffs := waitConfigDiff(old.Wait, new.Wait, contextual); waitDiffs != nil {
  1730  		diff.Objects = append(diff.Objects, waitDiffs)
  1731  	}
  1732  
  1733  	// ChangeScript diffs
  1734  	if changeScriptDiffs := changeScriptDiff(
  1735  		old.ChangeScript, new.ChangeScript, contextual,
  1736  	); changeScriptDiffs != nil {
  1737  		diff.Objects = append(diff.Objects, changeScriptDiffs)
  1738  	}
  1739  
  1740  	return diff
  1741  }
  1742  
  1743  // templateDiffs returns the diff of two Consul Template slices. If contextual diff is
  1744  // enabled, all fields will be returned, even if no diff occurred.
  1745  // serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
  1746  // fields within objects nested in the tasks will be returned.
  1747  func templateDiffs(old, new []*Template, contextual bool) []*ObjectDiff {
  1748  	// Handle trivial case.
  1749  	if len(old) == 1 && len(new) == 1 {
  1750  		if diff := templateDiff(old[0], new[0], contextual); diff != nil {
  1751  			return []*ObjectDiff{diff}
  1752  		}
  1753  		return nil
  1754  	}
  1755  
  1756  	// For each template we will try to find a corresponding match in the other list.
  1757  	// The following lists store the index of the matching template for each
  1758  	// position of the inputs.
  1759  	oldMatches := make([]int, len(old))
  1760  	newMatches := make([]int, len(new))
  1761  
  1762  	// Initialize all templates as unmatched.
  1763  	for i := range oldMatches {
  1764  		oldMatches[i] = -1
  1765  	}
  1766  	for i := range newMatches {
  1767  		newMatches[i] = -1
  1768  	}
  1769  
  1770  	// Find a match in the new templates list for each old template and compute
  1771  	// their diffs.
  1772  	var diffs []*ObjectDiff
  1773  	for oldIndex, oldTemplate := range old {
  1774  		newIndex := findTemplateMatch(oldTemplate, new, newMatches)
  1775  
  1776  		// Old templates that don't have a match were deleted.
  1777  		if newIndex < 0 {
  1778  			diff := templateDiff(oldTemplate, nil, contextual)
  1779  			diffs = append(diffs, diff)
  1780  			continue
  1781  		}
  1782  
  1783  		// If A matches B then B matches A.
  1784  		oldMatches[oldIndex] = newIndex
  1785  		newMatches[newIndex] = oldIndex
  1786  
  1787  		newTemplate := new[newIndex]
  1788  		if diff := templateDiff(oldTemplate, newTemplate, contextual); diff != nil {
  1789  			diffs = append(diffs, diff)
  1790  		}
  1791  	}
  1792  
  1793  	// New templates without match were added.
  1794  	for i, m := range newMatches {
  1795  		if m == -1 {
  1796  			diff := templateDiff(nil, new[i], contextual)
  1797  			diffs = append(diffs, diff)
  1798  		}
  1799  	}
  1800  
  1801  	sort.Sort(ObjectDiffs(diffs))
  1802  	return diffs
  1803  }
  1804  
  1805  func findTemplateMatch(template *Template, newTemplates []*Template, newTemplateMatches []int) int {
  1806  	indexMatch := -1
  1807  
  1808  	for i, newTemplate := range newTemplates {
  1809  		// Skip template if it's already matched.
  1810  		if newTemplateMatches[i] >= 0 {
  1811  			continue
  1812  		}
  1813  
  1814  		if template.DiffID() == newTemplate.DiffID() {
  1815  			indexMatch = i
  1816  			break
  1817  		}
  1818  	}
  1819  
  1820  	return indexMatch
  1821  }
  1822  
  1823  // parameterizedJobDiff returns the diff of two parameterized job objects. If
  1824  // contextual diff is enabled, all fields will be returned, even if no diff
  1825  // occurred.
  1826  func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff {
  1827  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"}
  1828  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1829  
  1830  	if reflect.DeepEqual(old, new) {
  1831  		return nil
  1832  	} else if old == nil {
  1833  		old = &ParameterizedJobConfig{}
  1834  		diff.Type = DiffTypeAdded
  1835  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1836  	} else if new == nil {
  1837  		new = &ParameterizedJobConfig{}
  1838  		diff.Type = DiffTypeDeleted
  1839  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1840  	} else {
  1841  		diff.Type = DiffTypeEdited
  1842  		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
  1843  		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
  1844  	}
  1845  
  1846  	// Diff the primitive fields.
  1847  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1848  
  1849  	// Meta diffs
  1850  	if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil {
  1851  		diff.Objects = append(diff.Objects, optionalDiff)
  1852  	}
  1853  
  1854  	if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil {
  1855  		diff.Objects = append(diff.Objects, requiredDiff)
  1856  	}
  1857  
  1858  	return diff
  1859  }
  1860  
  1861  func multiregionDiff(old, new *Multiregion, contextual bool) *ObjectDiff {
  1862  
  1863  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Multiregion"}
  1864  
  1865  	if reflect.DeepEqual(old, new) {
  1866  		return nil
  1867  	} else if old == nil {
  1868  		old = &Multiregion{}
  1869  		old.Canonicalize()
  1870  		diff.Type = DiffTypeAdded
  1871  	} else if new == nil {
  1872  		new = &Multiregion{}
  1873  		diff.Type = DiffTypeDeleted
  1874  	} else {
  1875  		diff.Type = DiffTypeEdited
  1876  	}
  1877  
  1878  	// strategy diff
  1879  	stratDiff := primitiveObjectDiff(
  1880  		old.Strategy,
  1881  		new.Strategy,
  1882  		[]string{},
  1883  		"Strategy",
  1884  		contextual)
  1885  	if stratDiff != nil {
  1886  		diff.Objects = append(diff.Objects, stratDiff)
  1887  	}
  1888  
  1889  	oldMap := make(map[string]*MultiregionRegion, len(old.Regions))
  1890  	newMap := make(map[string]*MultiregionRegion, len(new.Regions))
  1891  	for _, o := range old.Regions {
  1892  		oldMap[o.Name] = o
  1893  	}
  1894  	for _, n := range new.Regions {
  1895  		newMap[n.Name] = n
  1896  	}
  1897  
  1898  	for name, oldRegion := range oldMap {
  1899  		// Diff the same, deleted and edited
  1900  		newRegion := newMap[name]
  1901  		rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual)
  1902  		if rdiff != nil {
  1903  			diff.Objects = append(diff.Objects, rdiff)
  1904  		}
  1905  	}
  1906  
  1907  	for name, newRegion := range newMap {
  1908  		// Diff the added
  1909  		if oldRegion, ok := oldMap[name]; !ok {
  1910  			rdiff := multiregionRegionDiff(oldRegion, newRegion, contextual)
  1911  			if rdiff != nil {
  1912  				diff.Objects = append(diff.Objects, rdiff)
  1913  			}
  1914  		}
  1915  	}
  1916  	sort.Sort(FieldDiffs(diff.Fields))
  1917  	sort.Sort(ObjectDiffs(diff.Objects))
  1918  	return diff
  1919  }
  1920  
  1921  func multiregionRegionDiff(r, other *MultiregionRegion, contextual bool) *ObjectDiff {
  1922  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Region"}
  1923  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  1924  
  1925  	if reflect.DeepEqual(r, other) {
  1926  		return nil
  1927  	} else if r == nil {
  1928  		r = &MultiregionRegion{}
  1929  		diff.Type = DiffTypeAdded
  1930  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1931  	} else if other == nil {
  1932  		other = &MultiregionRegion{}
  1933  		diff.Type = DiffTypeDeleted
  1934  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1935  	} else {
  1936  		diff.Type = DiffTypeEdited
  1937  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  1938  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  1939  	}
  1940  
  1941  	// Diff the primitive fields.
  1942  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  1943  
  1944  	// Datacenters diff
  1945  	setDiff := stringSetDiff(r.Datacenters, other.Datacenters, "Datacenters", contextual)
  1946  	if setDiff != nil && setDiff.Type != DiffTypeNone {
  1947  		diff.Objects = append(diff.Objects, setDiff)
  1948  	}
  1949  
  1950  	sort.Sort(ObjectDiffs(diff.Objects))
  1951  	sort.Sort(FieldDiffs(diff.Fields))
  1952  
  1953  	var added, deleted, edited bool
  1954  Loop:
  1955  	for _, f := range diff.Fields {
  1956  		switch f.Type {
  1957  		case DiffTypeEdited:
  1958  			edited = true
  1959  			break Loop
  1960  		case DiffTypeDeleted:
  1961  			deleted = true
  1962  		case DiffTypeAdded:
  1963  			added = true
  1964  		}
  1965  	}
  1966  
  1967  	if edited || added && deleted {
  1968  		diff.Type = DiffTypeEdited
  1969  	} else if added {
  1970  		diff.Type = DiffTypeAdded
  1971  	} else if deleted {
  1972  		diff.Type = DiffTypeDeleted
  1973  	} else {
  1974  		return nil
  1975  	}
  1976  
  1977  	return diff
  1978  }
  1979  
  1980  // volumeDiffs returns the diff of a group's volume requests. If contextual
  1981  // diff is enabled, all fields will be returned, even if no diff occurred.
  1982  func volumeDiffs(oldVR, newVR map[string]*VolumeRequest, contextual bool) []*ObjectDiff {
  1983  	if reflect.DeepEqual(oldVR, newVR) {
  1984  		return nil
  1985  	}
  1986  
  1987  	diffs := []*ObjectDiff{} //Type: DiffTypeNone, Name: "Volumes"}
  1988  	seen := map[string]bool{}
  1989  	for name, oReq := range oldVR {
  1990  		nReq := newVR[name] // might be nil, that's ok
  1991  		seen[name] = true
  1992  		diff := volumeDiff(oReq, nReq, contextual)
  1993  		if diff != nil {
  1994  			diffs = append(diffs, diff)
  1995  		}
  1996  	}
  1997  	for name, nReq := range newVR {
  1998  		if !seen[name] {
  1999  			// we know old is nil at this point, or we'd have hit it before
  2000  			diff := volumeDiff(nil, nReq, contextual)
  2001  			if diff != nil {
  2002  				diffs = append(diffs, diff)
  2003  			}
  2004  		}
  2005  	}
  2006  	return diffs
  2007  }
  2008  
  2009  // volumeDiff returns the diff between two volume requests. If contextual diff
  2010  // is enabled, all fields will be returned, even if no diff occurred.
  2011  func volumeDiff(oldVR, newVR *VolumeRequest, contextual bool) *ObjectDiff {
  2012  	if reflect.DeepEqual(oldVR, newVR) {
  2013  		return nil
  2014  	}
  2015  
  2016  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Volume"}
  2017  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2018  
  2019  	if oldVR == nil {
  2020  		oldVR = &VolumeRequest{}
  2021  		diff.Type = DiffTypeAdded
  2022  		newPrimitiveFlat = flatmap.Flatten(newVR, nil, true)
  2023  	} else if newVR == nil {
  2024  		newVR = &VolumeRequest{}
  2025  		diff.Type = DiffTypeDeleted
  2026  		oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true)
  2027  	} else {
  2028  		diff.Type = DiffTypeEdited
  2029  		oldPrimitiveFlat = flatmap.Flatten(oldVR, nil, true)
  2030  		newPrimitiveFlat = flatmap.Flatten(newVR, nil, true)
  2031  	}
  2032  
  2033  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2034  
  2035  	mOptsDiff := volumeCSIMountOptionsDiff(oldVR.MountOptions, newVR.MountOptions, contextual)
  2036  	if mOptsDiff != nil {
  2037  		diff.Objects = append(diff.Objects, mOptsDiff)
  2038  	}
  2039  
  2040  	return diff
  2041  }
  2042  
  2043  // volumeCSIMountOptionsDiff returns the diff between volume mount options. If
  2044  // contextual diff is enabled, all fields will be returned, even if no diff
  2045  // occurred.
  2046  func volumeCSIMountOptionsDiff(oldMO, newMO *CSIMountOptions, contextual bool) *ObjectDiff {
  2047  	if reflect.DeepEqual(oldMO, newMO) {
  2048  		return nil
  2049  	}
  2050  
  2051  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "MountOptions"}
  2052  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2053  
  2054  	if oldMO == nil && newMO != nil {
  2055  		oldMO = &CSIMountOptions{}
  2056  		diff.Type = DiffTypeAdded
  2057  		newPrimitiveFlat = flatmap.Flatten(newMO, nil, true)
  2058  	} else if oldMO != nil && newMO == nil {
  2059  		newMO = &CSIMountOptions{}
  2060  		diff.Type = DiffTypeDeleted
  2061  		oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true)
  2062  	} else {
  2063  		diff.Type = DiffTypeEdited
  2064  		oldPrimitiveFlat = flatmap.Flatten(oldMO, nil, true)
  2065  		newPrimitiveFlat = flatmap.Flatten(newMO, nil, true)
  2066  	}
  2067  
  2068  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2069  
  2070  	setDiff := stringSetDiff(oldMO.MountFlags, newMO.MountFlags, "MountFlags", contextual)
  2071  	if setDiff != nil {
  2072  		diff.Objects = append(diff.Objects, setDiff)
  2073  	}
  2074  	return diff
  2075  }
  2076  
  2077  // Diff returns a diff of two resource objects. If contextual diff is enabled,
  2078  // non-changed fields will still be returned.
  2079  func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff {
  2080  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"}
  2081  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2082  
  2083  	if reflect.DeepEqual(r, other) {
  2084  		return nil
  2085  	} else if r == nil {
  2086  		r = &Resources{}
  2087  		diff.Type = DiffTypeAdded
  2088  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  2089  	} else if other == nil {
  2090  		other = &Resources{}
  2091  		diff.Type = DiffTypeDeleted
  2092  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  2093  	} else {
  2094  		diff.Type = DiffTypeEdited
  2095  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  2096  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  2097  	}
  2098  
  2099  	// Diff the primitive fields.
  2100  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2101  
  2102  	// Network Resources diff
  2103  	if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil {
  2104  		diff.Objects = append(diff.Objects, nDiffs...)
  2105  	}
  2106  
  2107  	// Requested Devices diff
  2108  	if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil {
  2109  		diff.Objects = append(diff.Objects, nDiffs...)
  2110  	}
  2111  
  2112  	return diff
  2113  }
  2114  
  2115  // Diff returns a diff of two network resources. If contextual diff is enabled,
  2116  // non-changed fields will still be returned.
  2117  func (n *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff {
  2118  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"}
  2119  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2120  	filter := []string{"Device", "CIDR", "IP"}
  2121  
  2122  	if reflect.DeepEqual(n, other) {
  2123  		return nil
  2124  	} else if n == nil {
  2125  		n = &NetworkResource{}
  2126  		diff.Type = DiffTypeAdded
  2127  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  2128  	} else if other == nil {
  2129  		other = &NetworkResource{}
  2130  		diff.Type = DiffTypeDeleted
  2131  		oldPrimitiveFlat = flatmap.Flatten(n, filter, true)
  2132  	} else {
  2133  		diff.Type = DiffTypeEdited
  2134  		oldPrimitiveFlat = flatmap.Flatten(n, filter, true)
  2135  		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
  2136  	}
  2137  
  2138  	// Diff the primitive fields.
  2139  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2140  
  2141  	// Port diffs
  2142  	resPorts := portDiffs(n.ReservedPorts, other.ReservedPorts, false, contextual)
  2143  	dynPorts := portDiffs(n.DynamicPorts, other.DynamicPorts, true, contextual)
  2144  	if resPorts != nil {
  2145  		diff.Objects = append(diff.Objects, resPorts...)
  2146  	}
  2147  	if dynPorts != nil {
  2148  		diff.Objects = append(diff.Objects, dynPorts...)
  2149  	}
  2150  
  2151  	if dnsDiff := n.DNS.Diff(other.DNS, contextual); dnsDiff != nil {
  2152  		diff.Objects = append(diff.Objects, dnsDiff)
  2153  	}
  2154  
  2155  	return diff
  2156  }
  2157  
  2158  // Diff returns a diff of two DNSConfig structs
  2159  func (d *DNSConfig) Diff(other *DNSConfig, contextual bool) *ObjectDiff {
  2160  	if reflect.DeepEqual(d, other) {
  2161  		return nil
  2162  	}
  2163  
  2164  	flatten := func(conf *DNSConfig) map[string]string {
  2165  		m := map[string]string{}
  2166  		if len(conf.Servers) > 0 {
  2167  			m["Servers"] = strings.Join(conf.Servers, ",")
  2168  		}
  2169  		if len(conf.Searches) > 0 {
  2170  			m["Searches"] = strings.Join(conf.Searches, ",")
  2171  		}
  2172  		if len(conf.Options) > 0 {
  2173  			m["Options"] = strings.Join(conf.Options, ",")
  2174  		}
  2175  		return m
  2176  	}
  2177  
  2178  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "DNS"}
  2179  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2180  	if d == nil {
  2181  		diff.Type = DiffTypeAdded
  2182  		newPrimitiveFlat = flatten(other)
  2183  	} else if other == nil {
  2184  		diff.Type = DiffTypeDeleted
  2185  		oldPrimitiveFlat = flatten(d)
  2186  	} else {
  2187  		diff.Type = DiffTypeEdited
  2188  		oldPrimitiveFlat = flatten(d)
  2189  		newPrimitiveFlat = flatten(other)
  2190  	}
  2191  
  2192  	// Diff the primitive fields.
  2193  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2194  
  2195  	return diff
  2196  }
  2197  
  2198  // networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled,
  2199  // non-changed fields will still be returned.
  2200  func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff {
  2201  	makeSet := func(objects []*NetworkResource) map[string]*NetworkResource {
  2202  		objMap := make(map[string]*NetworkResource, len(objects))
  2203  		for _, obj := range objects {
  2204  			hash, err := hashstructure.Hash(obj, nil)
  2205  			if err != nil {
  2206  				panic(err)
  2207  			}
  2208  			objMap[fmt.Sprintf("%d", hash)] = obj
  2209  		}
  2210  
  2211  		return objMap
  2212  	}
  2213  
  2214  	oldSet := makeSet(old)
  2215  	newSet := makeSet(new)
  2216  
  2217  	var diffs []*ObjectDiff
  2218  	for k, oldV := range oldSet {
  2219  		if newV, ok := newSet[k]; !ok {
  2220  			if diff := oldV.Diff(newV, contextual); diff != nil {
  2221  				diffs = append(diffs, diff)
  2222  			}
  2223  		}
  2224  	}
  2225  	for k, newV := range newSet {
  2226  		if oldV, ok := oldSet[k]; !ok {
  2227  			if diff := oldV.Diff(newV, contextual); diff != nil {
  2228  				diffs = append(diffs, diff)
  2229  			}
  2230  		}
  2231  	}
  2232  
  2233  	sort.Sort(ObjectDiffs(diffs))
  2234  	return diffs
  2235  
  2236  }
  2237  
  2238  // portDiffs returns the diff of two sets of ports. The dynamic flag marks the
  2239  // set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled,
  2240  // non-changed fields will still be returned.
  2241  func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff {
  2242  	makeSet := func(ports []Port) map[string]Port {
  2243  		portMap := make(map[string]Port, len(ports))
  2244  		for _, port := range ports {
  2245  			portMap[port.Label] = port
  2246  		}
  2247  
  2248  		return portMap
  2249  	}
  2250  
  2251  	oldPorts := makeSet(old)
  2252  	newPorts := makeSet(new)
  2253  
  2254  	var filter []string
  2255  	name := "Static Port"
  2256  	if dynamic {
  2257  		filter = []string{"Value"}
  2258  		name = "Dynamic Port"
  2259  	}
  2260  
  2261  	var diffs []*ObjectDiff
  2262  	for portLabel, oldPort := range oldPorts {
  2263  		// Diff the same, deleted and edited
  2264  		if newPort, ok := newPorts[portLabel]; ok {
  2265  			diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual)
  2266  			if diff != nil {
  2267  				diffs = append(diffs, diff)
  2268  			}
  2269  		} else {
  2270  			diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual)
  2271  			if diff != nil {
  2272  				diffs = append(diffs, diff)
  2273  			}
  2274  		}
  2275  	}
  2276  	for label, newPort := range newPorts {
  2277  		// Diff the added
  2278  		if _, ok := oldPorts[label]; !ok {
  2279  			diff := primitiveObjectDiff(nil, newPort, filter, name, contextual)
  2280  			if diff != nil {
  2281  				diffs = append(diffs, diff)
  2282  			}
  2283  		}
  2284  	}
  2285  
  2286  	sort.Sort(ObjectDiffs(diffs))
  2287  	return diffs
  2288  
  2289  }
  2290  
  2291  // Diff returns a diff of two requested devices. If contextual diff is enabled,
  2292  // non-changed fields will still be returned.
  2293  func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff {
  2294  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"}
  2295  	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
  2296  
  2297  	if reflect.DeepEqual(r, other) {
  2298  		return nil
  2299  	} else if r == nil {
  2300  		diff.Type = DiffTypeAdded
  2301  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  2302  	} else if other == nil {
  2303  		diff.Type = DiffTypeDeleted
  2304  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  2305  	} else {
  2306  		diff.Type = DiffTypeEdited
  2307  		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
  2308  		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
  2309  	}
  2310  
  2311  	// Diff the primitive fields.
  2312  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2313  
  2314  	return diff
  2315  }
  2316  
  2317  // requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled,
  2318  // non-changed fields will still be returned.
  2319  func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff {
  2320  	makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice {
  2321  		deviceMap := make(map[string]*RequestedDevice, len(devices))
  2322  		for _, d := range devices {
  2323  			deviceMap[d.Name] = d
  2324  		}
  2325  
  2326  		return deviceMap
  2327  	}
  2328  
  2329  	oldSet := makeSet(old)
  2330  	newSet := makeSet(new)
  2331  
  2332  	var diffs []*ObjectDiff
  2333  	for k, oldV := range oldSet {
  2334  		newV := newSet[k]
  2335  		if diff := oldV.Diff(newV, contextual); diff != nil {
  2336  			diffs = append(diffs, diff)
  2337  		}
  2338  	}
  2339  	for k, newV := range newSet {
  2340  		if oldV, ok := oldSet[k]; !ok {
  2341  			if diff := oldV.Diff(newV, contextual); diff != nil {
  2342  				diffs = append(diffs, diff)
  2343  			}
  2344  		}
  2345  	}
  2346  
  2347  	sort.Sort(ObjectDiffs(diffs))
  2348  	return diffs
  2349  
  2350  }
  2351  
  2352  // configDiff returns the diff of two Task Config objects. If contextual diff is
  2353  // enabled, all fields will be returned, even if no diff occurred.
  2354  func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff {
  2355  	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"}
  2356  	if reflect.DeepEqual(old, new) {
  2357  		return nil
  2358  	} else if len(old) == 0 {
  2359  		diff.Type = DiffTypeAdded
  2360  	} else if len(new) == 0 {
  2361  		diff.Type = DiffTypeDeleted
  2362  	} else {
  2363  		diff.Type = DiffTypeEdited
  2364  	}
  2365  
  2366  	// Diff the primitive fields.
  2367  	oldPrimitiveFlat := flatmap.Flatten(old, nil, false)
  2368  	newPrimitiveFlat := flatmap.Flatten(new, nil, false)
  2369  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2370  	return diff
  2371  }
  2372  
  2373  // ObjectDiff contains the diff of two generic objects.
  2374  type ObjectDiff struct {
  2375  	Type    DiffType
  2376  	Name    string
  2377  	Fields  []*FieldDiff
  2378  	Objects []*ObjectDiff
  2379  }
  2380  
  2381  func (o *ObjectDiff) GoString() string {
  2382  	out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type)
  2383  	for _, f := range o.Fields {
  2384  		out += fmt.Sprintf("%#v\n", f)
  2385  	}
  2386  	for _, o := range o.Objects {
  2387  		out += fmt.Sprintf("%#v\n", o)
  2388  	}
  2389  	out += "}"
  2390  	return out
  2391  }
  2392  
  2393  func (o *ObjectDiff) Less(other *ObjectDiff) bool {
  2394  	if reflect.DeepEqual(o, other) {
  2395  		return false
  2396  	} else if other == nil {
  2397  		return false
  2398  	} else if o == nil {
  2399  		return true
  2400  	}
  2401  
  2402  	if o.Name != other.Name {
  2403  		return o.Name < other.Name
  2404  	}
  2405  
  2406  	if o.Type != other.Type {
  2407  		return o.Type.Less(other.Type)
  2408  	}
  2409  
  2410  	if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther {
  2411  		return lO < lOther
  2412  	}
  2413  
  2414  	if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther {
  2415  		return lO < lOther
  2416  	}
  2417  
  2418  	// Check each field
  2419  	sort.Sort(FieldDiffs(o.Fields))
  2420  	sort.Sort(FieldDiffs(other.Fields))
  2421  
  2422  	for i, oV := range o.Fields {
  2423  		if oV.Less(other.Fields[i]) {
  2424  			return true
  2425  		}
  2426  	}
  2427  
  2428  	// Check each object
  2429  	sort.Sort(ObjectDiffs(o.Objects))
  2430  	sort.Sort(ObjectDiffs(other.Objects))
  2431  	for i, oV := range o.Objects {
  2432  		if oV.Less(other.Objects[i]) {
  2433  			return true
  2434  		}
  2435  	}
  2436  
  2437  	return false
  2438  }
  2439  
  2440  // For sorting ObjectDiffs
  2441  type ObjectDiffs []*ObjectDiff
  2442  
  2443  func (o ObjectDiffs) Len() int           { return len(o) }
  2444  func (o ObjectDiffs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
  2445  func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) }
  2446  
  2447  type FieldDiff struct {
  2448  	Type        DiffType
  2449  	Name        string
  2450  	Old, New    string
  2451  	Annotations []string
  2452  }
  2453  
  2454  // fieldDiff returns a FieldDiff if old and new are different otherwise, it
  2455  // returns nil. If contextual diff is enabled, even non-changed fields will be
  2456  // returned.
  2457  func fieldDiff(old, new, name string, contextual bool) *FieldDiff {
  2458  	diff := &FieldDiff{Name: name, Type: DiffTypeNone}
  2459  	if old == new {
  2460  		if !contextual {
  2461  			return nil
  2462  		}
  2463  		diff.Old, diff.New = old, new
  2464  		return diff
  2465  	}
  2466  
  2467  	if old == "" {
  2468  		diff.Type = DiffTypeAdded
  2469  		diff.New = new
  2470  	} else if new == "" {
  2471  		diff.Type = DiffTypeDeleted
  2472  		diff.Old = old
  2473  	} else {
  2474  		diff.Type = DiffTypeEdited
  2475  		diff.Old = old
  2476  		diff.New = new
  2477  	}
  2478  	return diff
  2479  }
  2480  
  2481  func (f *FieldDiff) GoString() string {
  2482  	out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New)
  2483  	if len(f.Annotations) != 0 {
  2484  		out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", "))
  2485  	}
  2486  
  2487  	return out
  2488  }
  2489  
  2490  func (f *FieldDiff) Less(other *FieldDiff) bool {
  2491  	if reflect.DeepEqual(f, other) {
  2492  		return false
  2493  	} else if other == nil {
  2494  		return false
  2495  	} else if f == nil {
  2496  		return true
  2497  	}
  2498  
  2499  	if f.Name != other.Name {
  2500  		return f.Name < other.Name
  2501  	} else if f.Old != other.Old {
  2502  		return f.Old < other.Old
  2503  	}
  2504  
  2505  	return f.New < other.New
  2506  }
  2507  
  2508  // For sorting FieldDiffs
  2509  type FieldDiffs []*FieldDiff
  2510  
  2511  func (f FieldDiffs) Len() int           { return len(f) }
  2512  func (f FieldDiffs) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
  2513  func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) }
  2514  
  2515  // fieldDiffs takes a map of field names to their values and returns a set of
  2516  // field diffs. If contextual diff is enabled, even non-changed fields will be
  2517  // returned.
  2518  func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff {
  2519  	var diffs []*FieldDiff
  2520  	visited := make(map[string]struct{})
  2521  	for k, oldV := range old {
  2522  		visited[k] = struct{}{}
  2523  		newV := new[k]
  2524  		if diff := fieldDiff(oldV, newV, k, contextual); diff != nil {
  2525  			diffs = append(diffs, diff)
  2526  		}
  2527  	}
  2528  
  2529  	for k, newV := range new {
  2530  		if _, ok := visited[k]; !ok {
  2531  			if diff := fieldDiff("", newV, k, contextual); diff != nil {
  2532  				diffs = append(diffs, diff)
  2533  			}
  2534  		}
  2535  	}
  2536  
  2537  	sort.Sort(FieldDiffs(diffs))
  2538  	return diffs
  2539  }
  2540  
  2541  // stringSetDiff diffs two sets of strings with the given name.
  2542  func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff {
  2543  	oldMap := make(map[string]struct{}, len(old))
  2544  	newMap := make(map[string]struct{}, len(new))
  2545  	for _, o := range old {
  2546  		oldMap[o] = struct{}{}
  2547  	}
  2548  	for _, n := range new {
  2549  		newMap[n] = struct{}{}
  2550  	}
  2551  	if reflect.DeepEqual(oldMap, newMap) && !contextual {
  2552  		return nil
  2553  	}
  2554  
  2555  	diff := &ObjectDiff{Name: name}
  2556  	var added, removed bool
  2557  	for k := range oldMap {
  2558  		if _, ok := newMap[k]; !ok {
  2559  			diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual))
  2560  			removed = true
  2561  		} else if contextual {
  2562  			diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual))
  2563  		}
  2564  	}
  2565  
  2566  	for k := range newMap {
  2567  		if _, ok := oldMap[k]; !ok {
  2568  			diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual))
  2569  			added = true
  2570  		}
  2571  	}
  2572  
  2573  	sort.Sort(FieldDiffs(diff.Fields))
  2574  
  2575  	// Determine the type
  2576  	if added && removed {
  2577  		diff.Type = DiffTypeEdited
  2578  	} else if added {
  2579  		diff.Type = DiffTypeAdded
  2580  	} else if removed {
  2581  		diff.Type = DiffTypeDeleted
  2582  	} else {
  2583  		// Diff of an empty set
  2584  		if len(diff.Fields) == 0 {
  2585  			return nil
  2586  		}
  2587  
  2588  		diff.Type = DiffTypeNone
  2589  	}
  2590  
  2591  	return diff
  2592  }
  2593  
  2594  // primitiveObjectDiff returns a diff of the passed objects' primitive fields.
  2595  // The filter field can be used to exclude fields from the diff. The name is the
  2596  // name of the objects. If contextual is set, non-changed fields will also be
  2597  // stored in the object diff.
  2598  func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff {
  2599  	oldPrimitiveFlat := flatmap.Flatten(old, filter, true)
  2600  	newPrimitiveFlat := flatmap.Flatten(new, filter, true)
  2601  	delete(oldPrimitiveFlat, "")
  2602  	delete(newPrimitiveFlat, "")
  2603  
  2604  	diff := &ObjectDiff{Name: name}
  2605  	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
  2606  
  2607  	var added, deleted, edited bool
  2608  Loop:
  2609  	for _, f := range diff.Fields {
  2610  		switch f.Type {
  2611  		case DiffTypeEdited:
  2612  			edited = true
  2613  			break Loop
  2614  		case DiffTypeDeleted:
  2615  			deleted = true
  2616  		case DiffTypeAdded:
  2617  			added = true
  2618  		}
  2619  	}
  2620  
  2621  	if edited || added && deleted {
  2622  		diff.Type = DiffTypeEdited
  2623  	} else if added {
  2624  		diff.Type = DiffTypeAdded
  2625  	} else if deleted {
  2626  		diff.Type = DiffTypeDeleted
  2627  	} else {
  2628  		return nil
  2629  	}
  2630  
  2631  	return diff
  2632  }
  2633  
  2634  // primitiveObjectSetDiff does a set difference of the old and new sets. The
  2635  // filter parameter can be used to filter a set of primitive fields in the
  2636  // passed structs. The name corresponds to the name of the passed objects. If
  2637  // contextual diff is enabled, objects' primitive fields will be returned even if
  2638  // no diff exists.
  2639  func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff {
  2640  	makeSet := func(objects []interface{}) map[string]interface{} {
  2641  		objMap := make(map[string]interface{}, len(objects))
  2642  		for _, obj := range objects {
  2643  			var key string
  2644  
  2645  			if diffable, ok := obj.(DiffableWithID); ok {
  2646  				key = diffable.DiffID()
  2647  			}
  2648  
  2649  			if key == "" {
  2650  				hash, err := hashstructure.Hash(obj, nil)
  2651  				if err != nil {
  2652  					panic(err)
  2653  				}
  2654  				key = fmt.Sprintf("%d", hash)
  2655  			}
  2656  			objMap[key] = obj
  2657  		}
  2658  
  2659  		return objMap
  2660  	}
  2661  
  2662  	oldSet := makeSet(old)
  2663  	newSet := makeSet(new)
  2664  
  2665  	var diffs []*ObjectDiff
  2666  	for k, oldObj := range oldSet {
  2667  		newObj := newSet[k]
  2668  		diff := primitiveObjectDiff(oldObj, newObj, filter, name, contextual)
  2669  		if diff != nil {
  2670  			diffs = append(diffs, diff)
  2671  		}
  2672  	}
  2673  	for k, v := range newSet {
  2674  		// Added
  2675  		if _, ok := oldSet[k]; !ok {
  2676  			diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual))
  2677  		}
  2678  	}
  2679  
  2680  	sort.Sort(ObjectDiffs(diffs))
  2681  	return diffs
  2682  }
  2683  
  2684  // interfaceSlice is a helper method that takes a slice of typed elements and
  2685  // returns a slice of interface. This method will panic if given a non-slice
  2686  // input.
  2687  func interfaceSlice(slice interface{}) []interface{} {
  2688  	s := reflect.ValueOf(slice)
  2689  	if s.Kind() != reflect.Slice {
  2690  		panic("InterfaceSlice() given a non-slice type")
  2691  	}
  2692  
  2693  	ret := make([]interface{}, s.Len())
  2694  
  2695  	for i := 0; i < s.Len(); i++ {
  2696  		ret[i] = s.Index(i).Interface()
  2697  	}
  2698  
  2699  	return ret
  2700  }