github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/nomad/structs/diff.go (about)

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