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