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

     1  package structs
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestJobDiff(t *testing.T) {
    10  	cases := []struct {
    11  		Old, New   *Job
    12  		Expected   *JobDiff
    13  		Error      bool
    14  		Contextual bool
    15  	}{
    16  		{
    17  			Old: nil,
    18  			New: nil,
    19  			Expected: &JobDiff{
    20  				Type: DiffTypeNone,
    21  			},
    22  		},
    23  		{
    24  			// Different IDs
    25  			Old: &Job{
    26  				ID: "foo",
    27  			},
    28  			New: &Job{
    29  				ID: "bar",
    30  			},
    31  			Error: true,
    32  		},
    33  		{
    34  			// Primitive only that is the same
    35  			Old: &Job{
    36  				Region:    "foo",
    37  				ID:        "foo",
    38  				Name:      "foo",
    39  				Type:      "batch",
    40  				Priority:  10,
    41  				AllAtOnce: true,
    42  				Meta: map[string]string{
    43  					"foo": "bar",
    44  				},
    45  			},
    46  			New: &Job{
    47  				Region:    "foo",
    48  				ID:        "foo",
    49  				Name:      "foo",
    50  				Type:      "batch",
    51  				Priority:  10,
    52  				AllAtOnce: true,
    53  				Meta: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Expected: &JobDiff{
    58  				Type: DiffTypeNone,
    59  				ID:   "foo",
    60  			},
    61  		},
    62  		{
    63  			// Primitive only that is has diffs
    64  			Old: &Job{
    65  				Region:    "foo",
    66  				ID:        "foo",
    67  				Name:      "foo",
    68  				Type:      "batch",
    69  				Priority:  10,
    70  				AllAtOnce: true,
    71  				Meta: map[string]string{
    72  					"foo": "bar",
    73  				},
    74  			},
    75  			New: &Job{
    76  				Region:    "bar",
    77  				ID:        "foo",
    78  				Name:      "bar",
    79  				Type:      "system",
    80  				Priority:  100,
    81  				AllAtOnce: false,
    82  				Meta: map[string]string{
    83  					"foo": "baz",
    84  				},
    85  			},
    86  			Expected: &JobDiff{
    87  				Type: DiffTypeEdited,
    88  				ID:   "foo",
    89  				Fields: []*FieldDiff{
    90  					{
    91  						Type: DiffTypeEdited,
    92  						Name: "AllAtOnce",
    93  						Old:  "true",
    94  						New:  "false",
    95  					},
    96  					{
    97  						Type: DiffTypeEdited,
    98  						Name: "Meta[foo]",
    99  						Old:  "bar",
   100  						New:  "baz",
   101  					},
   102  					{
   103  						Type: DiffTypeEdited,
   104  						Name: "Name",
   105  						Old:  "foo",
   106  						New:  "bar",
   107  					},
   108  					{
   109  						Type: DiffTypeEdited,
   110  						Name: "Priority",
   111  						Old:  "10",
   112  						New:  "100",
   113  					},
   114  					{
   115  						Type: DiffTypeEdited,
   116  						Name: "Region",
   117  						Old:  "foo",
   118  						New:  "bar",
   119  					},
   120  					{
   121  						Type: DiffTypeEdited,
   122  						Name: "Type",
   123  						Old:  "batch",
   124  						New:  "system",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			// Primitive only deleted job
   131  			Old: &Job{
   132  				Region:    "foo",
   133  				ID:        "foo",
   134  				Name:      "foo",
   135  				Type:      "batch",
   136  				Priority:  10,
   137  				AllAtOnce: true,
   138  				Meta: map[string]string{
   139  					"foo": "bar",
   140  				},
   141  			},
   142  			New: nil,
   143  			Expected: &JobDiff{
   144  				Type: DiffTypeDeleted,
   145  				ID:   "foo",
   146  				Fields: []*FieldDiff{
   147  					{
   148  						Type: DiffTypeDeleted,
   149  						Name: "AllAtOnce",
   150  						Old:  "true",
   151  						New:  "",
   152  					},
   153  					{
   154  						Type: DiffTypeDeleted,
   155  						Name: "Dispatched",
   156  						Old:  "false",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Meta[foo]",
   162  						Old:  "bar",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Name",
   168  						Old:  "foo",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Priority",
   174  						Old:  "10",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Region",
   180  						Old:  "foo",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Stop",
   186  						Old:  "false",
   187  						New:  "",
   188  					},
   189  					{
   190  						Type: DiffTypeDeleted,
   191  						Name: "Type",
   192  						Old:  "batch",
   193  						New:  "",
   194  					},
   195  				},
   196  			},
   197  		},
   198  		{
   199  			// Primitive only added job
   200  			Old: nil,
   201  			New: &Job{
   202  				Region:    "foo",
   203  				ID:        "foo",
   204  				Name:      "foo",
   205  				Type:      "batch",
   206  				Priority:  10,
   207  				AllAtOnce: true,
   208  				Meta: map[string]string{
   209  					"foo": "bar",
   210  				},
   211  			},
   212  			Expected: &JobDiff{
   213  				Type: DiffTypeAdded,
   214  				ID:   "foo",
   215  				Fields: []*FieldDiff{
   216  					{
   217  						Type: DiffTypeAdded,
   218  						Name: "AllAtOnce",
   219  						Old:  "",
   220  						New:  "true",
   221  					},
   222  					{
   223  						Type: DiffTypeAdded,
   224  						Name: "Dispatched",
   225  						Old:  "",
   226  						New:  "false",
   227  					},
   228  					{
   229  						Type: DiffTypeAdded,
   230  						Name: "Meta[foo]",
   231  						Old:  "",
   232  						New:  "bar",
   233  					},
   234  					{
   235  						Type: DiffTypeAdded,
   236  						Name: "Name",
   237  						Old:  "",
   238  						New:  "foo",
   239  					},
   240  					{
   241  						Type: DiffTypeAdded,
   242  						Name: "Priority",
   243  						Old:  "",
   244  						New:  "10",
   245  					},
   246  					{
   247  						Type: DiffTypeAdded,
   248  						Name: "Region",
   249  						Old:  "",
   250  						New:  "foo",
   251  					},
   252  					{
   253  						Type: DiffTypeAdded,
   254  						Name: "Stop",
   255  						Old:  "",
   256  						New:  "false",
   257  					},
   258  					{
   259  						Type: DiffTypeAdded,
   260  						Name: "Type",
   261  						Old:  "",
   262  						New:  "batch",
   263  					},
   264  				},
   265  			},
   266  		},
   267  		{
   268  			// Map diff
   269  			Old: &Job{
   270  				Meta: map[string]string{
   271  					"foo": "foo",
   272  					"bar": "bar",
   273  				},
   274  			},
   275  			New: &Job{
   276  				Meta: map[string]string{
   277  					"bar": "bar",
   278  					"baz": "baz",
   279  				},
   280  			},
   281  			Expected: &JobDiff{
   282  				Type: DiffTypeEdited,
   283  				Fields: []*FieldDiff{
   284  					{
   285  						Type: DiffTypeAdded,
   286  						Name: "Meta[baz]",
   287  						Old:  "",
   288  						New:  "baz",
   289  					},
   290  					{
   291  						Type: DiffTypeDeleted,
   292  						Name: "Meta[foo]",
   293  						Old:  "foo",
   294  						New:  "",
   295  					},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			// Datacenter diff both added and removed
   301  			Old: &Job{
   302  				Datacenters: []string{"foo", "bar"},
   303  			},
   304  			New: &Job{
   305  				Datacenters: []string{"baz", "bar"},
   306  			},
   307  			Expected: &JobDiff{
   308  				Type: DiffTypeEdited,
   309  				Objects: []*ObjectDiff{
   310  					{
   311  						Type: DiffTypeEdited,
   312  						Name: "Datacenters",
   313  						Fields: []*FieldDiff{
   314  							{
   315  								Type: DiffTypeAdded,
   316  								Name: "Datacenters",
   317  								Old:  "",
   318  								New:  "baz",
   319  							},
   320  							{
   321  								Type: DiffTypeDeleted,
   322  								Name: "Datacenters",
   323  								Old:  "foo",
   324  								New:  "",
   325  							},
   326  						},
   327  					},
   328  				},
   329  			},
   330  		},
   331  		{
   332  			// Datacenter diff just added
   333  			Old: &Job{
   334  				Datacenters: []string{"foo", "bar"},
   335  			},
   336  			New: &Job{
   337  				Datacenters: []string{"foo", "bar", "baz"},
   338  			},
   339  			Expected: &JobDiff{
   340  				Type: DiffTypeEdited,
   341  				Objects: []*ObjectDiff{
   342  					{
   343  						Type: DiffTypeAdded,
   344  						Name: "Datacenters",
   345  						Fields: []*FieldDiff{
   346  							{
   347  								Type: DiffTypeAdded,
   348  								Name: "Datacenters",
   349  								Old:  "",
   350  								New:  "baz",
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			// Datacenter diff just deleted
   359  			Old: &Job{
   360  				Datacenters: []string{"foo", "bar"},
   361  			},
   362  			New: &Job{
   363  				Datacenters: []string{"foo"},
   364  			},
   365  			Expected: &JobDiff{
   366  				Type: DiffTypeEdited,
   367  				Objects: []*ObjectDiff{
   368  					{
   369  						Type: DiffTypeDeleted,
   370  						Name: "Datacenters",
   371  						Fields: []*FieldDiff{
   372  							{
   373  								Type: DiffTypeDeleted,
   374  								Name: "Datacenters",
   375  								Old:  "bar",
   376  								New:  "",
   377  							},
   378  						},
   379  					},
   380  				},
   381  			},
   382  		},
   383  		{
   384  			// Datacenter contextual no change
   385  			Contextual: true,
   386  			Old: &Job{
   387  				Datacenters: []string{"foo", "bar"},
   388  			},
   389  			New: &Job{
   390  				Datacenters: []string{"foo", "bar"},
   391  			},
   392  			Expected: &JobDiff{
   393  				Type: DiffTypeNone,
   394  			},
   395  		},
   396  		{
   397  			// Datacenter contextual
   398  			Contextual: true,
   399  			Old: &Job{
   400  				Datacenters: []string{"foo", "bar"},
   401  			},
   402  			New: &Job{
   403  				Datacenters: []string{"foo", "bar", "baz"},
   404  			},
   405  			Expected: &JobDiff{
   406  				Type: DiffTypeEdited,
   407  				Objects: []*ObjectDiff{
   408  					{
   409  						Type: DiffTypeAdded,
   410  						Name: "Datacenters",
   411  						Fields: []*FieldDiff{
   412  							{
   413  								Type: DiffTypeAdded,
   414  								Name: "Datacenters",
   415  								Old:  "",
   416  								New:  "baz",
   417  							},
   418  							{
   419  								Type: DiffTypeNone,
   420  								Name: "Datacenters",
   421  								Old:  "bar",
   422  								New:  "bar",
   423  							},
   424  							{
   425  								Type: DiffTypeNone,
   426  								Name: "Datacenters",
   427  								Old:  "foo",
   428  								New:  "foo",
   429  							},
   430  						},
   431  					},
   432  				},
   433  			},
   434  		},
   435  		{
   436  			// Periodic added
   437  			Old: &Job{},
   438  			New: &Job{
   439  				Periodic: &PeriodicConfig{
   440  					Enabled:         false,
   441  					Spec:            "*/15 * * * * *",
   442  					SpecType:        "foo",
   443  					ProhibitOverlap: false,
   444  					TimeZone:        "Europe/Minsk",
   445  				},
   446  			},
   447  			Expected: &JobDiff{
   448  				Type: DiffTypeEdited,
   449  				Objects: []*ObjectDiff{
   450  					{
   451  						Type: DiffTypeAdded,
   452  						Name: "Periodic",
   453  						Fields: []*FieldDiff{
   454  							{
   455  								Type: DiffTypeAdded,
   456  								Name: "Enabled",
   457  								Old:  "",
   458  								New:  "false",
   459  							},
   460  							{
   461  								Type: DiffTypeAdded,
   462  								Name: "ProhibitOverlap",
   463  								Old:  "",
   464  								New:  "false",
   465  							},
   466  							{
   467  								Type: DiffTypeAdded,
   468  								Name: "Spec",
   469  								Old:  "",
   470  								New:  "*/15 * * * * *",
   471  							},
   472  							{
   473  								Type: DiffTypeAdded,
   474  								Name: "SpecType",
   475  								Old:  "",
   476  								New:  "foo",
   477  							},
   478  							{
   479  								Type: DiffTypeAdded,
   480  								Name: "TimeZone",
   481  								Old:  "",
   482  								New:  "Europe/Minsk",
   483  							},
   484  						},
   485  					},
   486  				},
   487  			},
   488  		},
   489  		{
   490  			// Periodic deleted
   491  			Old: &Job{
   492  				Periodic: &PeriodicConfig{
   493  					Enabled:         false,
   494  					Spec:            "*/15 * * * * *",
   495  					SpecType:        "foo",
   496  					ProhibitOverlap: false,
   497  					TimeZone:        "Europe/Minsk",
   498  				},
   499  			},
   500  			New: &Job{},
   501  			Expected: &JobDiff{
   502  				Type: DiffTypeEdited,
   503  				Objects: []*ObjectDiff{
   504  					{
   505  						Type: DiffTypeDeleted,
   506  						Name: "Periodic",
   507  						Fields: []*FieldDiff{
   508  							{
   509  								Type: DiffTypeDeleted,
   510  								Name: "Enabled",
   511  								Old:  "false",
   512  								New:  "",
   513  							},
   514  							{
   515  								Type: DiffTypeDeleted,
   516  								Name: "ProhibitOverlap",
   517  								Old:  "false",
   518  								New:  "",
   519  							},
   520  							{
   521  								Type: DiffTypeDeleted,
   522  								Name: "Spec",
   523  								Old:  "*/15 * * * * *",
   524  								New:  "",
   525  							},
   526  							{
   527  								Type: DiffTypeDeleted,
   528  								Name: "SpecType",
   529  								Old:  "foo",
   530  								New:  "",
   531  							},
   532  							{
   533  								Type: DiffTypeDeleted,
   534  								Name: "TimeZone",
   535  								Old:  "Europe/Minsk",
   536  								New:  "",
   537  							},
   538  						},
   539  					},
   540  				},
   541  			},
   542  		},
   543  		{
   544  			// Periodic edited
   545  			Old: &Job{
   546  				Periodic: &PeriodicConfig{
   547  					Enabled:         false,
   548  					Spec:            "*/15 * * * * *",
   549  					SpecType:        "foo",
   550  					ProhibitOverlap: false,
   551  					TimeZone:        "Europe/Minsk",
   552  				},
   553  			},
   554  			New: &Job{
   555  				Periodic: &PeriodicConfig{
   556  					Enabled:         true,
   557  					Spec:            "* * * * * *",
   558  					SpecType:        "cron",
   559  					ProhibitOverlap: true,
   560  					TimeZone:        "America/Los_Angeles",
   561  				},
   562  			},
   563  			Expected: &JobDiff{
   564  				Type: DiffTypeEdited,
   565  				Objects: []*ObjectDiff{
   566  					{
   567  						Type: DiffTypeEdited,
   568  						Name: "Periodic",
   569  						Fields: []*FieldDiff{
   570  							{
   571  								Type: DiffTypeEdited,
   572  								Name: "Enabled",
   573  								Old:  "false",
   574  								New:  "true",
   575  							},
   576  							{
   577  								Type: DiffTypeEdited,
   578  								Name: "ProhibitOverlap",
   579  								Old:  "false",
   580  								New:  "true",
   581  							},
   582  							{
   583  								Type: DiffTypeEdited,
   584  								Name: "Spec",
   585  								Old:  "*/15 * * * * *",
   586  								New:  "* * * * * *",
   587  							},
   588  							{
   589  								Type: DiffTypeEdited,
   590  								Name: "SpecType",
   591  								Old:  "foo",
   592  								New:  "cron",
   593  							},
   594  							{
   595  								Type: DiffTypeEdited,
   596  								Name: "TimeZone",
   597  								Old:  "Europe/Minsk",
   598  								New:  "America/Los_Angeles",
   599  							},
   600  						},
   601  					},
   602  				},
   603  			},
   604  		},
   605  		{
   606  			// Periodic edited with context
   607  			Contextual: true,
   608  			Old: &Job{
   609  				Periodic: &PeriodicConfig{
   610  					Enabled:         false,
   611  					Spec:            "*/15 * * * * *",
   612  					SpecType:        "foo",
   613  					ProhibitOverlap: false,
   614  					TimeZone:        "Europe/Minsk",
   615  				},
   616  			},
   617  			New: &Job{
   618  				Periodic: &PeriodicConfig{
   619  					Enabled:         true,
   620  					Spec:            "* * * * * *",
   621  					SpecType:        "foo",
   622  					ProhibitOverlap: false,
   623  					TimeZone:        "Europe/Minsk",
   624  				},
   625  			},
   626  			Expected: &JobDiff{
   627  				Type: DiffTypeEdited,
   628  				Objects: []*ObjectDiff{
   629  					{
   630  						Type: DiffTypeEdited,
   631  						Name: "Periodic",
   632  						Fields: []*FieldDiff{
   633  							{
   634  								Type: DiffTypeEdited,
   635  								Name: "Enabled",
   636  								Old:  "false",
   637  								New:  "true",
   638  							},
   639  							{
   640  								Type: DiffTypeNone,
   641  								Name: "ProhibitOverlap",
   642  								Old:  "false",
   643  								New:  "false",
   644  							},
   645  							{
   646  								Type: DiffTypeEdited,
   647  								Name: "Spec",
   648  								Old:  "*/15 * * * * *",
   649  								New:  "* * * * * *",
   650  							},
   651  							{
   652  								Type: DiffTypeNone,
   653  								Name: "SpecType",
   654  								Old:  "foo",
   655  								New:  "foo",
   656  							},
   657  							{
   658  								Type: DiffTypeNone,
   659  								Name: "TimeZone",
   660  								Old:  "Europe/Minsk",
   661  								New:  "Europe/Minsk",
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  		},
   668  		{
   669  			// Constraints edited
   670  			Old: &Job{
   671  				Constraints: []*Constraint{
   672  					{
   673  						LTarget: "foo",
   674  						RTarget: "foo",
   675  						Operand: "foo",
   676  						str:     "foo",
   677  					},
   678  					{
   679  						LTarget: "bar",
   680  						RTarget: "bar",
   681  						Operand: "bar",
   682  						str:     "bar",
   683  					},
   684  				},
   685  			},
   686  			New: &Job{
   687  				Constraints: []*Constraint{
   688  					{
   689  						LTarget: "foo",
   690  						RTarget: "foo",
   691  						Operand: "foo",
   692  						str:     "foo",
   693  					},
   694  					{
   695  						LTarget: "baz",
   696  						RTarget: "baz",
   697  						Operand: "baz",
   698  						str:     "baz",
   699  					},
   700  				},
   701  			},
   702  			Expected: &JobDiff{
   703  				Type: DiffTypeEdited,
   704  				Objects: []*ObjectDiff{
   705  					{
   706  						Type: DiffTypeAdded,
   707  						Name: "Constraint",
   708  						Fields: []*FieldDiff{
   709  							{
   710  								Type: DiffTypeAdded,
   711  								Name: "LTarget",
   712  								Old:  "",
   713  								New:  "baz",
   714  							},
   715  							{
   716  								Type: DiffTypeAdded,
   717  								Name: "Operand",
   718  								Old:  "",
   719  								New:  "baz",
   720  							},
   721  							{
   722  								Type: DiffTypeAdded,
   723  								Name: "RTarget",
   724  								Old:  "",
   725  								New:  "baz",
   726  							},
   727  						},
   728  					},
   729  					{
   730  						Type: DiffTypeDeleted,
   731  						Name: "Constraint",
   732  						Fields: []*FieldDiff{
   733  							{
   734  								Type: DiffTypeDeleted,
   735  								Name: "LTarget",
   736  								Old:  "bar",
   737  								New:  "",
   738  							},
   739  							{
   740  								Type: DiffTypeDeleted,
   741  								Name: "Operand",
   742  								Old:  "bar",
   743  								New:  "",
   744  							},
   745  							{
   746  								Type: DiffTypeDeleted,
   747  								Name: "RTarget",
   748  								Old:  "bar",
   749  								New:  "",
   750  							},
   751  						},
   752  					},
   753  				},
   754  			},
   755  		},
   756  		{
   757  			// Affinities edited
   758  			Old: &Job{
   759  				Affinities: []*Affinity{
   760  					{
   761  						LTarget: "foo",
   762  						RTarget: "foo",
   763  						Operand: "foo",
   764  						Weight:  20,
   765  						str:     "foo",
   766  					},
   767  					{
   768  						LTarget: "bar",
   769  						RTarget: "bar",
   770  						Operand: "bar",
   771  						Weight:  20,
   772  						str:     "bar",
   773  					},
   774  				},
   775  			},
   776  			New: &Job{
   777  				Affinities: []*Affinity{
   778  					{
   779  						LTarget: "foo",
   780  						RTarget: "foo",
   781  						Operand: "foo",
   782  						Weight:  20,
   783  						str:     "foo",
   784  					},
   785  					{
   786  						LTarget: "baz",
   787  						RTarget: "baz",
   788  						Operand: "baz",
   789  						Weight:  20,
   790  						str:     "baz",
   791  					},
   792  				},
   793  			},
   794  			Expected: &JobDiff{
   795  				Type: DiffTypeEdited,
   796  				Objects: []*ObjectDiff{
   797  					{
   798  						Type: DiffTypeAdded,
   799  						Name: "Affinity",
   800  						Fields: []*FieldDiff{
   801  							{
   802  								Type: DiffTypeAdded,
   803  								Name: "LTarget",
   804  								Old:  "",
   805  								New:  "baz",
   806  							},
   807  							{
   808  								Type: DiffTypeAdded,
   809  								Name: "Operand",
   810  								Old:  "",
   811  								New:  "baz",
   812  							},
   813  							{
   814  								Type: DiffTypeAdded,
   815  								Name: "RTarget",
   816  								Old:  "",
   817  								New:  "baz",
   818  							},
   819  							{
   820  								Type: DiffTypeAdded,
   821  								Name: "Weight",
   822  								Old:  "",
   823  								New:  "20",
   824  							},
   825  						},
   826  					},
   827  					{
   828  						Type: DiffTypeDeleted,
   829  						Name: "Affinity",
   830  						Fields: []*FieldDiff{
   831  							{
   832  								Type: DiffTypeDeleted,
   833  								Name: "LTarget",
   834  								Old:  "bar",
   835  								New:  "",
   836  							},
   837  							{
   838  								Type: DiffTypeDeleted,
   839  								Name: "Operand",
   840  								Old:  "bar",
   841  								New:  "",
   842  							},
   843  							{
   844  								Type: DiffTypeDeleted,
   845  								Name: "RTarget",
   846  								Old:  "bar",
   847  								New:  "",
   848  							},
   849  							{
   850  								Type: DiffTypeDeleted,
   851  								Name: "Weight",
   852  								Old:  "20",
   853  								New:  "",
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  		},
   860  		{
   861  			// Task groups edited
   862  			Old: &Job{
   863  				TaskGroups: []*TaskGroup{
   864  					{
   865  						Name:  "foo",
   866  						Count: 1,
   867  					},
   868  					{
   869  						Name:  "bar",
   870  						Count: 1,
   871  					},
   872  					{
   873  						Name:  "baz",
   874  						Count: 1,
   875  					},
   876  				},
   877  			},
   878  			New: &Job{
   879  				TaskGroups: []*TaskGroup{
   880  					{
   881  						Name:  "bar",
   882  						Count: 1,
   883  					},
   884  					{
   885  						Name:  "baz",
   886  						Count: 2,
   887  					},
   888  					{
   889  						Name:  "bam",
   890  						Count: 1,
   891  					},
   892  				},
   893  			},
   894  			Expected: &JobDiff{
   895  				Type: DiffTypeEdited,
   896  				TaskGroups: []*TaskGroupDiff{
   897  					{
   898  						Type: DiffTypeAdded,
   899  						Name: "bam",
   900  						Fields: []*FieldDiff{
   901  							{
   902  								Type: DiffTypeAdded,
   903  								Name: "Count",
   904  								Old:  "",
   905  								New:  "1",
   906  							},
   907  						},
   908  					},
   909  					{
   910  						Type: DiffTypeNone,
   911  						Name: "bar",
   912  					},
   913  					{
   914  						Type: DiffTypeEdited,
   915  						Name: "baz",
   916  						Fields: []*FieldDiff{
   917  							{
   918  								Type: DiffTypeEdited,
   919  								Name: "Count",
   920  								Old:  "1",
   921  								New:  "2",
   922  							},
   923  						},
   924  					},
   925  					{
   926  						Type: DiffTypeDeleted,
   927  						Name: "foo",
   928  						Fields: []*FieldDiff{
   929  							{
   930  								Type: DiffTypeDeleted,
   931  								Name: "Count",
   932  								Old:  "1",
   933  								New:  "",
   934  							},
   935  						},
   936  					},
   937  				},
   938  			},
   939  		},
   940  		{
   941  			// Parameterized Job added
   942  			Old: &Job{},
   943  			New: &Job{
   944  				ParameterizedJob: &ParameterizedJobConfig{
   945  					Payload:      DispatchPayloadRequired,
   946  					MetaOptional: []string{"foo"},
   947  					MetaRequired: []string{"bar"},
   948  				},
   949  			},
   950  			Expected: &JobDiff{
   951  				Type: DiffTypeEdited,
   952  				Objects: []*ObjectDiff{
   953  					{
   954  						Type: DiffTypeAdded,
   955  						Name: "ParameterizedJob",
   956  						Fields: []*FieldDiff{
   957  							{
   958  								Type: DiffTypeAdded,
   959  								Name: "Payload",
   960  								Old:  "",
   961  								New:  DispatchPayloadRequired,
   962  							},
   963  						},
   964  						Objects: []*ObjectDiff{
   965  							{
   966  								Type: DiffTypeAdded,
   967  								Name: "MetaOptional",
   968  								Fields: []*FieldDiff{
   969  									{
   970  										Type: DiffTypeAdded,
   971  										Name: "MetaOptional",
   972  										Old:  "",
   973  										New:  "foo",
   974  									},
   975  								},
   976  							},
   977  							{
   978  								Type: DiffTypeAdded,
   979  								Name: "MetaRequired",
   980  								Fields: []*FieldDiff{
   981  									{
   982  										Type: DiffTypeAdded,
   983  										Name: "MetaRequired",
   984  										Old:  "",
   985  										New:  "bar",
   986  									},
   987  								},
   988  							},
   989  						},
   990  					},
   991  				},
   992  			},
   993  		},
   994  		{
   995  			// Parameterized Job deleted
   996  			Old: &Job{
   997  				ParameterizedJob: &ParameterizedJobConfig{
   998  					Payload:      DispatchPayloadRequired,
   999  					MetaOptional: []string{"foo"},
  1000  					MetaRequired: []string{"bar"},
  1001  				},
  1002  			},
  1003  			New: &Job{},
  1004  			Expected: &JobDiff{
  1005  				Type: DiffTypeEdited,
  1006  				Objects: []*ObjectDiff{
  1007  					{
  1008  						Type: DiffTypeDeleted,
  1009  						Name: "ParameterizedJob",
  1010  						Fields: []*FieldDiff{
  1011  							{
  1012  								Type: DiffTypeDeleted,
  1013  								Name: "Payload",
  1014  								Old:  DispatchPayloadRequired,
  1015  								New:  "",
  1016  							},
  1017  						},
  1018  						Objects: []*ObjectDiff{
  1019  							{
  1020  								Type: DiffTypeDeleted,
  1021  								Name: "MetaOptional",
  1022  								Fields: []*FieldDiff{
  1023  									{
  1024  										Type: DiffTypeDeleted,
  1025  										Name: "MetaOptional",
  1026  										Old:  "foo",
  1027  										New:  "",
  1028  									},
  1029  								},
  1030  							},
  1031  							{
  1032  								Type: DiffTypeDeleted,
  1033  								Name: "MetaRequired",
  1034  								Fields: []*FieldDiff{
  1035  									{
  1036  										Type: DiffTypeDeleted,
  1037  										Name: "MetaRequired",
  1038  										Old:  "bar",
  1039  										New:  "",
  1040  									},
  1041  								},
  1042  							},
  1043  						},
  1044  					},
  1045  				},
  1046  			},
  1047  		},
  1048  		{
  1049  			// Parameterized Job edited
  1050  			Old: &Job{
  1051  				ParameterizedJob: &ParameterizedJobConfig{
  1052  					Payload:      DispatchPayloadRequired,
  1053  					MetaOptional: []string{"foo"},
  1054  					MetaRequired: []string{"bar"},
  1055  				},
  1056  			},
  1057  			New: &Job{
  1058  				ParameterizedJob: &ParameterizedJobConfig{
  1059  					Payload:      DispatchPayloadOptional,
  1060  					MetaOptional: []string{"bam"},
  1061  					MetaRequired: []string{"bang"},
  1062  				},
  1063  			},
  1064  			Expected: &JobDiff{
  1065  				Type: DiffTypeEdited,
  1066  				Objects: []*ObjectDiff{
  1067  					{
  1068  						Type: DiffTypeEdited,
  1069  						Name: "ParameterizedJob",
  1070  						Fields: []*FieldDiff{
  1071  							{
  1072  								Type: DiffTypeEdited,
  1073  								Name: "Payload",
  1074  								Old:  DispatchPayloadRequired,
  1075  								New:  DispatchPayloadOptional,
  1076  							},
  1077  						},
  1078  						Objects: []*ObjectDiff{
  1079  							{
  1080  								Type: DiffTypeEdited,
  1081  								Name: "MetaOptional",
  1082  								Fields: []*FieldDiff{
  1083  									{
  1084  										Type: DiffTypeAdded,
  1085  										Name: "MetaOptional",
  1086  										Old:  "",
  1087  										New:  "bam",
  1088  									},
  1089  									{
  1090  										Type: DiffTypeDeleted,
  1091  										Name: "MetaOptional",
  1092  										Old:  "foo",
  1093  										New:  "",
  1094  									},
  1095  								},
  1096  							},
  1097  							{
  1098  								Type: DiffTypeEdited,
  1099  								Name: "MetaRequired",
  1100  								Fields: []*FieldDiff{
  1101  									{
  1102  										Type: DiffTypeAdded,
  1103  										Name: "MetaRequired",
  1104  										Old:  "",
  1105  										New:  "bang",
  1106  									},
  1107  									{
  1108  										Type: DiffTypeDeleted,
  1109  										Name: "MetaRequired",
  1110  										Old:  "bar",
  1111  										New:  "",
  1112  									},
  1113  								},
  1114  							},
  1115  						},
  1116  					},
  1117  				},
  1118  			},
  1119  		},
  1120  		{
  1121  			// Parameterized Job edited with context
  1122  			Contextual: true,
  1123  			Old: &Job{
  1124  				ParameterizedJob: &ParameterizedJobConfig{
  1125  					Payload:      DispatchPayloadRequired,
  1126  					MetaOptional: []string{"foo"},
  1127  					MetaRequired: []string{"bar"},
  1128  				},
  1129  			},
  1130  			New: &Job{
  1131  				ParameterizedJob: &ParameterizedJobConfig{
  1132  					Payload:      DispatchPayloadOptional,
  1133  					MetaOptional: []string{"foo"},
  1134  					MetaRequired: []string{"bar"},
  1135  				},
  1136  			},
  1137  			Expected: &JobDiff{
  1138  				Type: DiffTypeEdited,
  1139  				Objects: []*ObjectDiff{
  1140  					{
  1141  						Type: DiffTypeEdited,
  1142  						Name: "ParameterizedJob",
  1143  						Fields: []*FieldDiff{
  1144  							{
  1145  								Type: DiffTypeEdited,
  1146  								Name: "Payload",
  1147  								Old:  DispatchPayloadRequired,
  1148  								New:  DispatchPayloadOptional,
  1149  							},
  1150  						},
  1151  						Objects: []*ObjectDiff{
  1152  							{
  1153  								Type: DiffTypeNone,
  1154  								Name: "MetaOptional",
  1155  								Fields: []*FieldDiff{
  1156  									{
  1157  										Type: DiffTypeNone,
  1158  										Name: "MetaOptional",
  1159  										Old:  "foo",
  1160  										New:  "foo",
  1161  									},
  1162  								},
  1163  							},
  1164  							{
  1165  								Type: DiffTypeNone,
  1166  								Name: "MetaRequired",
  1167  								Fields: []*FieldDiff{
  1168  									{
  1169  										Type: DiffTypeNone,
  1170  										Name: "MetaRequired",
  1171  										Old:  "bar",
  1172  										New:  "bar",
  1173  									},
  1174  								},
  1175  							},
  1176  						},
  1177  					},
  1178  				},
  1179  			},
  1180  		},
  1181  	}
  1182  
  1183  	for i, c := range cases {
  1184  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1185  		if c.Error && err == nil {
  1186  			t.Fatalf("case %d: expected errored", i+1)
  1187  		} else if err != nil {
  1188  			if !c.Error {
  1189  				t.Fatalf("case %d: errored %#v", i+1, err)
  1190  			} else {
  1191  				continue
  1192  			}
  1193  		}
  1194  
  1195  		if !reflect.DeepEqual(actual, c.Expected) {
  1196  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1197  				i+1, actual, c.Expected)
  1198  		}
  1199  	}
  1200  }
  1201  
  1202  func TestTaskGroupDiff(t *testing.T) {
  1203  	cases := []struct {
  1204  		Old, New   *TaskGroup
  1205  		Expected   *TaskGroupDiff
  1206  		Error      bool
  1207  		Contextual bool
  1208  	}{
  1209  		{
  1210  			Old: nil,
  1211  			New: nil,
  1212  			Expected: &TaskGroupDiff{
  1213  				Type: DiffTypeNone,
  1214  			},
  1215  		},
  1216  		{
  1217  			// Primitive only that has different names
  1218  			Old: &TaskGroup{
  1219  				Name:  "foo",
  1220  				Count: 10,
  1221  				Meta: map[string]string{
  1222  					"foo": "bar",
  1223  				},
  1224  			},
  1225  			New: &TaskGroup{
  1226  				Name:  "bar",
  1227  				Count: 10,
  1228  				Meta: map[string]string{
  1229  					"foo": "bar",
  1230  				},
  1231  			},
  1232  			Error: true,
  1233  		},
  1234  		{
  1235  			// Primitive only that is the same
  1236  			Old: &TaskGroup{
  1237  				Name:  "foo",
  1238  				Count: 10,
  1239  				Meta: map[string]string{
  1240  					"foo": "bar",
  1241  				},
  1242  			},
  1243  			New: &TaskGroup{
  1244  				Name:  "foo",
  1245  				Count: 10,
  1246  				Meta: map[string]string{
  1247  					"foo": "bar",
  1248  				},
  1249  			},
  1250  			Expected: &TaskGroupDiff{
  1251  				Type: DiffTypeNone,
  1252  				Name: "foo",
  1253  			},
  1254  		},
  1255  		{
  1256  			// Primitive only that has diffs
  1257  			Old: &TaskGroup{
  1258  				Name:  "foo",
  1259  				Count: 10,
  1260  				Meta: map[string]string{
  1261  					"foo": "bar",
  1262  				},
  1263  			},
  1264  			New: &TaskGroup{
  1265  				Name:  "foo",
  1266  				Count: 100,
  1267  				Meta: map[string]string{
  1268  					"foo": "baz",
  1269  				},
  1270  			},
  1271  			Expected: &TaskGroupDiff{
  1272  				Type: DiffTypeEdited,
  1273  				Name: "foo",
  1274  				Fields: []*FieldDiff{
  1275  					{
  1276  						Type: DiffTypeEdited,
  1277  						Name: "Count",
  1278  						Old:  "10",
  1279  						New:  "100",
  1280  					},
  1281  					{
  1282  						Type: DiffTypeEdited,
  1283  						Name: "Meta[foo]",
  1284  						Old:  "bar",
  1285  						New:  "baz",
  1286  					},
  1287  				},
  1288  			},
  1289  		},
  1290  		{
  1291  			// Map diff
  1292  			Old: &TaskGroup{
  1293  				Meta: map[string]string{
  1294  					"foo": "foo",
  1295  					"bar": "bar",
  1296  				},
  1297  			},
  1298  			New: &TaskGroup{
  1299  				Meta: map[string]string{
  1300  					"bar": "bar",
  1301  					"baz": "baz",
  1302  				},
  1303  			},
  1304  			Expected: &TaskGroupDiff{
  1305  				Type: DiffTypeEdited,
  1306  				Fields: []*FieldDiff{
  1307  					{
  1308  						Type: DiffTypeAdded,
  1309  						Name: "Meta[baz]",
  1310  						Old:  "",
  1311  						New:  "baz",
  1312  					},
  1313  					{
  1314  						Type: DiffTypeDeleted,
  1315  						Name: "Meta[foo]",
  1316  						Old:  "foo",
  1317  						New:  "",
  1318  					},
  1319  				},
  1320  			},
  1321  		},
  1322  		{
  1323  			// Constraints edited
  1324  			Old: &TaskGroup{
  1325  				Constraints: []*Constraint{
  1326  					{
  1327  						LTarget: "foo",
  1328  						RTarget: "foo",
  1329  						Operand: "foo",
  1330  						str:     "foo",
  1331  					},
  1332  					{
  1333  						LTarget: "bar",
  1334  						RTarget: "bar",
  1335  						Operand: "bar",
  1336  						str:     "bar",
  1337  					},
  1338  				},
  1339  			},
  1340  			New: &TaskGroup{
  1341  				Constraints: []*Constraint{
  1342  					{
  1343  						LTarget: "foo",
  1344  						RTarget: "foo",
  1345  						Operand: "foo",
  1346  						str:     "foo",
  1347  					},
  1348  					{
  1349  						LTarget: "baz",
  1350  						RTarget: "baz",
  1351  						Operand: "baz",
  1352  						str:     "baz",
  1353  					},
  1354  				},
  1355  			},
  1356  			Expected: &TaskGroupDiff{
  1357  				Type: DiffTypeEdited,
  1358  				Objects: []*ObjectDiff{
  1359  					{
  1360  						Type: DiffTypeAdded,
  1361  						Name: "Constraint",
  1362  						Fields: []*FieldDiff{
  1363  							{
  1364  								Type: DiffTypeAdded,
  1365  								Name: "LTarget",
  1366  								Old:  "",
  1367  								New:  "baz",
  1368  							},
  1369  							{
  1370  								Type: DiffTypeAdded,
  1371  								Name: "Operand",
  1372  								Old:  "",
  1373  								New:  "baz",
  1374  							},
  1375  							{
  1376  								Type: DiffTypeAdded,
  1377  								Name: "RTarget",
  1378  								Old:  "",
  1379  								New:  "baz",
  1380  							},
  1381  						},
  1382  					},
  1383  					{
  1384  						Type: DiffTypeDeleted,
  1385  						Name: "Constraint",
  1386  						Fields: []*FieldDiff{
  1387  							{
  1388  								Type: DiffTypeDeleted,
  1389  								Name: "LTarget",
  1390  								Old:  "bar",
  1391  								New:  "",
  1392  							},
  1393  							{
  1394  								Type: DiffTypeDeleted,
  1395  								Name: "Operand",
  1396  								Old:  "bar",
  1397  								New:  "",
  1398  							},
  1399  							{
  1400  								Type: DiffTypeDeleted,
  1401  								Name: "RTarget",
  1402  								Old:  "bar",
  1403  								New:  "",
  1404  							},
  1405  						},
  1406  					},
  1407  				},
  1408  			},
  1409  		},
  1410  		{
  1411  			// Affinities edited
  1412  			Old: &TaskGroup{
  1413  				Affinities: []*Affinity{
  1414  					{
  1415  						LTarget: "foo",
  1416  						RTarget: "foo",
  1417  						Operand: "foo",
  1418  						Weight:  20,
  1419  						str:     "foo",
  1420  					},
  1421  					{
  1422  						LTarget: "bar",
  1423  						RTarget: "bar",
  1424  						Operand: "bar",
  1425  						Weight:  20,
  1426  						str:     "bar",
  1427  					},
  1428  				},
  1429  			},
  1430  			New: &TaskGroup{
  1431  				Affinities: []*Affinity{
  1432  					{
  1433  						LTarget: "foo",
  1434  						RTarget: "foo",
  1435  						Operand: "foo",
  1436  						Weight:  20,
  1437  						str:     "foo",
  1438  					},
  1439  					{
  1440  						LTarget: "baz",
  1441  						RTarget: "baz",
  1442  						Operand: "baz",
  1443  						Weight:  20,
  1444  						str:     "baz",
  1445  					},
  1446  				},
  1447  			},
  1448  			Expected: &TaskGroupDiff{
  1449  				Type: DiffTypeEdited,
  1450  				Objects: []*ObjectDiff{
  1451  					{
  1452  						Type: DiffTypeAdded,
  1453  						Name: "Affinity",
  1454  						Fields: []*FieldDiff{
  1455  							{
  1456  								Type: DiffTypeAdded,
  1457  								Name: "LTarget",
  1458  								Old:  "",
  1459  								New:  "baz",
  1460  							},
  1461  							{
  1462  								Type: DiffTypeAdded,
  1463  								Name: "Operand",
  1464  								Old:  "",
  1465  								New:  "baz",
  1466  							},
  1467  							{
  1468  								Type: DiffTypeAdded,
  1469  								Name: "RTarget",
  1470  								Old:  "",
  1471  								New:  "baz",
  1472  							},
  1473  							{
  1474  								Type: DiffTypeAdded,
  1475  								Name: "Weight",
  1476  								Old:  "",
  1477  								New:  "20",
  1478  							},
  1479  						},
  1480  					},
  1481  					{
  1482  						Type: DiffTypeDeleted,
  1483  						Name: "Affinity",
  1484  						Fields: []*FieldDiff{
  1485  							{
  1486  								Type: DiffTypeDeleted,
  1487  								Name: "LTarget",
  1488  								Old:  "bar",
  1489  								New:  "",
  1490  							},
  1491  							{
  1492  								Type: DiffTypeDeleted,
  1493  								Name: "Operand",
  1494  								Old:  "bar",
  1495  								New:  "",
  1496  							},
  1497  							{
  1498  								Type: DiffTypeDeleted,
  1499  								Name: "RTarget",
  1500  								Old:  "bar",
  1501  								New:  "",
  1502  							},
  1503  							{
  1504  								Type: DiffTypeDeleted,
  1505  								Name: "Weight",
  1506  								Old:  "20",
  1507  								New:  "",
  1508  							},
  1509  						},
  1510  					},
  1511  				},
  1512  			},
  1513  		},
  1514  		{
  1515  			// RestartPolicy added
  1516  			Old: &TaskGroup{},
  1517  			New: &TaskGroup{
  1518  				RestartPolicy: &RestartPolicy{
  1519  					Attempts: 1,
  1520  					Interval: 1 * time.Second,
  1521  					Delay:    1 * time.Second,
  1522  					Mode:     "fail",
  1523  				},
  1524  			},
  1525  			Expected: &TaskGroupDiff{
  1526  				Type: DiffTypeEdited,
  1527  				Objects: []*ObjectDiff{
  1528  					{
  1529  						Type: DiffTypeAdded,
  1530  						Name: "RestartPolicy",
  1531  						Fields: []*FieldDiff{
  1532  							{
  1533  								Type: DiffTypeAdded,
  1534  								Name: "Attempts",
  1535  								Old:  "",
  1536  								New:  "1",
  1537  							},
  1538  							{
  1539  								Type: DiffTypeAdded,
  1540  								Name: "Delay",
  1541  								Old:  "",
  1542  								New:  "1000000000",
  1543  							},
  1544  							{
  1545  								Type: DiffTypeAdded,
  1546  								Name: "Interval",
  1547  								Old:  "",
  1548  								New:  "1000000000",
  1549  							},
  1550  							{
  1551  								Type: DiffTypeAdded,
  1552  								Name: "Mode",
  1553  								Old:  "",
  1554  								New:  "fail",
  1555  							},
  1556  						},
  1557  					},
  1558  				},
  1559  			},
  1560  		},
  1561  		{
  1562  			// RestartPolicy deleted
  1563  			Old: &TaskGroup{
  1564  				RestartPolicy: &RestartPolicy{
  1565  					Attempts: 1,
  1566  					Interval: 1 * time.Second,
  1567  					Delay:    1 * time.Second,
  1568  					Mode:     "fail",
  1569  				},
  1570  			},
  1571  			New: &TaskGroup{},
  1572  			Expected: &TaskGroupDiff{
  1573  				Type: DiffTypeEdited,
  1574  				Objects: []*ObjectDiff{
  1575  					{
  1576  						Type: DiffTypeDeleted,
  1577  						Name: "RestartPolicy",
  1578  						Fields: []*FieldDiff{
  1579  							{
  1580  								Type: DiffTypeDeleted,
  1581  								Name: "Attempts",
  1582  								Old:  "1",
  1583  								New:  "",
  1584  							},
  1585  							{
  1586  								Type: DiffTypeDeleted,
  1587  								Name: "Delay",
  1588  								Old:  "1000000000",
  1589  								New:  "",
  1590  							},
  1591  							{
  1592  								Type: DiffTypeDeleted,
  1593  								Name: "Interval",
  1594  								Old:  "1000000000",
  1595  								New:  "",
  1596  							},
  1597  							{
  1598  								Type: DiffTypeDeleted,
  1599  								Name: "Mode",
  1600  								Old:  "fail",
  1601  								New:  "",
  1602  							},
  1603  						},
  1604  					},
  1605  				},
  1606  			},
  1607  		},
  1608  		{
  1609  			// RestartPolicy edited
  1610  			Old: &TaskGroup{
  1611  				RestartPolicy: &RestartPolicy{
  1612  					Attempts: 1,
  1613  					Interval: 1 * time.Second,
  1614  					Delay:    1 * time.Second,
  1615  					Mode:     "fail",
  1616  				},
  1617  			},
  1618  			New: &TaskGroup{
  1619  				RestartPolicy: &RestartPolicy{
  1620  					Attempts: 2,
  1621  					Interval: 2 * time.Second,
  1622  					Delay:    2 * time.Second,
  1623  					Mode:     "delay",
  1624  				},
  1625  			},
  1626  			Expected: &TaskGroupDiff{
  1627  				Type: DiffTypeEdited,
  1628  				Objects: []*ObjectDiff{
  1629  					{
  1630  						Type: DiffTypeEdited,
  1631  						Name: "RestartPolicy",
  1632  						Fields: []*FieldDiff{
  1633  							{
  1634  								Type: DiffTypeEdited,
  1635  								Name: "Attempts",
  1636  								Old:  "1",
  1637  								New:  "2",
  1638  							},
  1639  							{
  1640  								Type: DiffTypeEdited,
  1641  								Name: "Delay",
  1642  								Old:  "1000000000",
  1643  								New:  "2000000000",
  1644  							},
  1645  							{
  1646  								Type: DiffTypeEdited,
  1647  								Name: "Interval",
  1648  								Old:  "1000000000",
  1649  								New:  "2000000000",
  1650  							},
  1651  							{
  1652  								Type: DiffTypeEdited,
  1653  								Name: "Mode",
  1654  								Old:  "fail",
  1655  								New:  "delay",
  1656  							},
  1657  						},
  1658  					},
  1659  				},
  1660  			},
  1661  		},
  1662  		{
  1663  			// RestartPolicy edited with context
  1664  			Contextual: true,
  1665  			Old: &TaskGroup{
  1666  				RestartPolicy: &RestartPolicy{
  1667  					Attempts: 1,
  1668  					Interval: 1 * time.Second,
  1669  					Delay:    1 * time.Second,
  1670  					Mode:     "fail",
  1671  				},
  1672  			},
  1673  			New: &TaskGroup{
  1674  				RestartPolicy: &RestartPolicy{
  1675  					Attempts: 2,
  1676  					Interval: 2 * time.Second,
  1677  					Delay:    1 * time.Second,
  1678  					Mode:     "fail",
  1679  				},
  1680  			},
  1681  			Expected: &TaskGroupDiff{
  1682  				Type: DiffTypeEdited,
  1683  				Objects: []*ObjectDiff{
  1684  					{
  1685  						Type: DiffTypeEdited,
  1686  						Name: "RestartPolicy",
  1687  						Fields: []*FieldDiff{
  1688  							{
  1689  								Type: DiffTypeEdited,
  1690  								Name: "Attempts",
  1691  								Old:  "1",
  1692  								New:  "2",
  1693  							},
  1694  							{
  1695  								Type: DiffTypeNone,
  1696  								Name: "Delay",
  1697  								Old:  "1000000000",
  1698  								New:  "1000000000",
  1699  							},
  1700  							{
  1701  								Type: DiffTypeEdited,
  1702  								Name: "Interval",
  1703  								Old:  "1000000000",
  1704  								New:  "2000000000",
  1705  							},
  1706  							{
  1707  								Type: DiffTypeNone,
  1708  								Name: "Mode",
  1709  								Old:  "fail",
  1710  								New:  "fail",
  1711  							},
  1712  						},
  1713  					},
  1714  				},
  1715  			},
  1716  		},
  1717  		{
  1718  			// ReschedulePolicy added
  1719  			Old: &TaskGroup{},
  1720  			New: &TaskGroup{
  1721  				ReschedulePolicy: &ReschedulePolicy{
  1722  					Attempts:      1,
  1723  					Interval:      15 * time.Second,
  1724  					Delay:         5 * time.Second,
  1725  					MaxDelay:      20 * time.Second,
  1726  					DelayFunction: "exponential",
  1727  					Unlimited:     false,
  1728  				},
  1729  			},
  1730  			Expected: &TaskGroupDiff{
  1731  				Type: DiffTypeEdited,
  1732  				Objects: []*ObjectDiff{
  1733  					{
  1734  						Type: DiffTypeAdded,
  1735  						Name: "ReschedulePolicy",
  1736  						Fields: []*FieldDiff{
  1737  							{
  1738  								Type: DiffTypeAdded,
  1739  								Name: "Attempts",
  1740  								Old:  "",
  1741  								New:  "1",
  1742  							},
  1743  							{
  1744  								Type: DiffTypeAdded,
  1745  								Name: "Delay",
  1746  								Old:  "",
  1747  								New:  "5000000000",
  1748  							},
  1749  							{
  1750  								Type: DiffTypeAdded,
  1751  								Name: "DelayFunction",
  1752  								Old:  "",
  1753  								New:  "exponential",
  1754  							},
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Interval",
  1758  								Old:  "",
  1759  								New:  "15000000000",
  1760  							},
  1761  							{
  1762  								Type: DiffTypeAdded,
  1763  								Name: "MaxDelay",
  1764  								Old:  "",
  1765  								New:  "20000000000",
  1766  							},
  1767  							{
  1768  								Type: DiffTypeAdded,
  1769  								Name: "Unlimited",
  1770  								Old:  "",
  1771  								New:  "false",
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			// ReschedulePolicy deleted
  1780  			Old: &TaskGroup{
  1781  				ReschedulePolicy: &ReschedulePolicy{
  1782  					Attempts:      1,
  1783  					Interval:      15 * time.Second,
  1784  					Delay:         5 * time.Second,
  1785  					MaxDelay:      20 * time.Second,
  1786  					DelayFunction: "exponential",
  1787  					Unlimited:     false,
  1788  				},
  1789  			},
  1790  			New: &TaskGroup{},
  1791  			Expected: &TaskGroupDiff{
  1792  				Type: DiffTypeEdited,
  1793  				Objects: []*ObjectDiff{
  1794  					{
  1795  						Type: DiffTypeDeleted,
  1796  						Name: "ReschedulePolicy",
  1797  						Fields: []*FieldDiff{
  1798  							{
  1799  								Type: DiffTypeDeleted,
  1800  								Name: "Attempts",
  1801  								Old:  "1",
  1802  								New:  "",
  1803  							},
  1804  							{
  1805  								Type: DiffTypeDeleted,
  1806  								Name: "Delay",
  1807  								Old:  "5000000000",
  1808  								New:  "",
  1809  							},
  1810  							{
  1811  								Type: DiffTypeDeleted,
  1812  								Name: "DelayFunction",
  1813  								Old:  "exponential",
  1814  								New:  "",
  1815  							},
  1816  							{
  1817  								Type: DiffTypeDeleted,
  1818  								Name: "Interval",
  1819  								Old:  "15000000000",
  1820  								New:  "",
  1821  							},
  1822  							{
  1823  								Type: DiffTypeDeleted,
  1824  								Name: "MaxDelay",
  1825  								Old:  "20000000000",
  1826  								New:  "",
  1827  							},
  1828  							{
  1829  								Type: DiffTypeDeleted,
  1830  								Name: "Unlimited",
  1831  								Old:  "false",
  1832  								New:  "",
  1833  							},
  1834  						},
  1835  					},
  1836  				},
  1837  			},
  1838  		},
  1839  		{
  1840  			// ReschedulePolicy edited
  1841  			Old: &TaskGroup{
  1842  				ReschedulePolicy: &ReschedulePolicy{
  1843  					Attempts:      1,
  1844  					Interval:      1 * time.Second,
  1845  					DelayFunction: "exponential",
  1846  					Delay:         20 * time.Second,
  1847  					MaxDelay:      1 * time.Minute,
  1848  					Unlimited:     false,
  1849  				},
  1850  			},
  1851  			New: &TaskGroup{
  1852  				ReschedulePolicy: &ReschedulePolicy{
  1853  					Attempts:      2,
  1854  					Interval:      2 * time.Second,
  1855  					DelayFunction: "constant",
  1856  					Delay:         30 * time.Second,
  1857  					MaxDelay:      1 * time.Minute,
  1858  					Unlimited:     true,
  1859  				},
  1860  			},
  1861  			Expected: &TaskGroupDiff{
  1862  				Type: DiffTypeEdited,
  1863  				Objects: []*ObjectDiff{
  1864  					{
  1865  						Type: DiffTypeEdited,
  1866  						Name: "ReschedulePolicy",
  1867  						Fields: []*FieldDiff{
  1868  							{
  1869  								Type: DiffTypeEdited,
  1870  								Name: "Attempts",
  1871  								Old:  "1",
  1872  								New:  "2",
  1873  							},
  1874  							{
  1875  								Type: DiffTypeEdited,
  1876  								Name: "Delay",
  1877  								Old:  "20000000000",
  1878  								New:  "30000000000",
  1879  							},
  1880  							{
  1881  								Type: DiffTypeEdited,
  1882  								Name: "DelayFunction",
  1883  								Old:  "exponential",
  1884  								New:  "constant",
  1885  							},
  1886  							{
  1887  								Type: DiffTypeEdited,
  1888  								Name: "Interval",
  1889  								Old:  "1000000000",
  1890  								New:  "2000000000",
  1891  							},
  1892  							{
  1893  								Type: DiffTypeEdited,
  1894  								Name: "Unlimited",
  1895  								Old:  "false",
  1896  								New:  "true",
  1897  							},
  1898  						},
  1899  					},
  1900  				},
  1901  			},
  1902  		}, {
  1903  			// ReschedulePolicy edited with context
  1904  			Contextual: true,
  1905  			Old: &TaskGroup{
  1906  				ReschedulePolicy: &ReschedulePolicy{
  1907  					Attempts: 1,
  1908  					Interval: 1 * time.Second,
  1909  				},
  1910  			},
  1911  			New: &TaskGroup{
  1912  				ReschedulePolicy: &ReschedulePolicy{
  1913  					Attempts: 1,
  1914  					Interval: 2 * time.Second,
  1915  				},
  1916  			},
  1917  			Expected: &TaskGroupDiff{
  1918  				Type: DiffTypeEdited,
  1919  				Objects: []*ObjectDiff{
  1920  					{
  1921  						Type: DiffTypeEdited,
  1922  						Name: "ReschedulePolicy",
  1923  						Fields: []*FieldDiff{
  1924  							{
  1925  								Type: DiffTypeNone,
  1926  								Name: "Attempts",
  1927  								Old:  "1",
  1928  								New:  "1",
  1929  							},
  1930  							{
  1931  								Type: DiffTypeNone,
  1932  								Name: "Delay",
  1933  								Old:  "0",
  1934  								New:  "0",
  1935  							},
  1936  							{
  1937  								Type: DiffTypeNone,
  1938  								Name: "DelayFunction",
  1939  								Old:  "",
  1940  								New:  "",
  1941  							},
  1942  							{
  1943  								Type: DiffTypeEdited,
  1944  								Name: "Interval",
  1945  								Old:  "1000000000",
  1946  								New:  "2000000000",
  1947  							},
  1948  							{
  1949  								Type: DiffTypeNone,
  1950  								Name: "MaxDelay",
  1951  								Old:  "0",
  1952  								New:  "0",
  1953  							},
  1954  							{
  1955  								Type: DiffTypeNone,
  1956  								Name: "Unlimited",
  1957  								Old:  "false",
  1958  								New:  "false",
  1959  							},
  1960  						},
  1961  					},
  1962  				},
  1963  			},
  1964  		},
  1965  		{
  1966  			// Update strategy deleted
  1967  			Old: &TaskGroup{
  1968  				Update: &UpdateStrategy{
  1969  					AutoRevert: true,
  1970  				},
  1971  			},
  1972  			New: &TaskGroup{},
  1973  			Expected: &TaskGroupDiff{
  1974  				Type: DiffTypeEdited,
  1975  				Objects: []*ObjectDiff{
  1976  					{
  1977  						Type: DiffTypeDeleted,
  1978  						Name: "Update",
  1979  						Fields: []*FieldDiff{
  1980  							{
  1981  								Type: DiffTypeDeleted,
  1982  								Name: "AutoRevert",
  1983  								Old:  "true",
  1984  								New:  "",
  1985  							},
  1986  							{
  1987  								Type: DiffTypeDeleted,
  1988  								Name: "Canary",
  1989  								Old:  "0",
  1990  								New:  "",
  1991  							},
  1992  							{
  1993  								Type: DiffTypeDeleted,
  1994  								Name: "HealthyDeadline",
  1995  								Old:  "0",
  1996  								New:  "",
  1997  							},
  1998  							{
  1999  								Type: DiffTypeDeleted,
  2000  								Name: "MaxParallel",
  2001  								Old:  "0",
  2002  								New:  "",
  2003  							},
  2004  							{
  2005  								Type: DiffTypeDeleted,
  2006  								Name: "MinHealthyTime",
  2007  								Old:  "0",
  2008  								New:  "",
  2009  							},
  2010  							{
  2011  								Type: DiffTypeDeleted,
  2012  								Name: "ProgressDeadline",
  2013  								Old:  "0",
  2014  								New:  "",
  2015  							},
  2016  						},
  2017  					},
  2018  				},
  2019  			},
  2020  		},
  2021  		{
  2022  			// Update strategy added
  2023  			Old: &TaskGroup{},
  2024  			New: &TaskGroup{
  2025  				Update: &UpdateStrategy{
  2026  					AutoRevert: true,
  2027  				},
  2028  			},
  2029  			Expected: &TaskGroupDiff{
  2030  				Type: DiffTypeEdited,
  2031  				Objects: []*ObjectDiff{
  2032  					{
  2033  						Type: DiffTypeAdded,
  2034  						Name: "Update",
  2035  						Fields: []*FieldDiff{
  2036  							{
  2037  								Type: DiffTypeAdded,
  2038  								Name: "AutoRevert",
  2039  								Old:  "",
  2040  								New:  "true",
  2041  							},
  2042  							{
  2043  								Type: DiffTypeAdded,
  2044  								Name: "Canary",
  2045  								Old:  "",
  2046  								New:  "0",
  2047  							},
  2048  							{
  2049  								Type: DiffTypeAdded,
  2050  								Name: "HealthyDeadline",
  2051  								Old:  "",
  2052  								New:  "0",
  2053  							},
  2054  							{
  2055  								Type: DiffTypeAdded,
  2056  								Name: "MaxParallel",
  2057  								Old:  "",
  2058  								New:  "0",
  2059  							},
  2060  							{
  2061  								Type: DiffTypeAdded,
  2062  								Name: "MinHealthyTime",
  2063  								Old:  "",
  2064  								New:  "0",
  2065  							},
  2066  							{
  2067  								Type: DiffTypeAdded,
  2068  								Name: "ProgressDeadline",
  2069  								Old:  "",
  2070  								New:  "0",
  2071  							},
  2072  						},
  2073  					},
  2074  				},
  2075  			},
  2076  		},
  2077  		{
  2078  			// Update strategy edited
  2079  			Old: &TaskGroup{
  2080  				Update: &UpdateStrategy{
  2081  					MaxParallel:      5,
  2082  					HealthCheck:      "foo",
  2083  					MinHealthyTime:   1 * time.Second,
  2084  					HealthyDeadline:  30 * time.Second,
  2085  					ProgressDeadline: 29 * time.Second,
  2086  					AutoRevert:       true,
  2087  					Canary:           2,
  2088  				},
  2089  			},
  2090  			New: &TaskGroup{
  2091  				Update: &UpdateStrategy{
  2092  					MaxParallel:      7,
  2093  					HealthCheck:      "bar",
  2094  					MinHealthyTime:   2 * time.Second,
  2095  					HealthyDeadline:  31 * time.Second,
  2096  					ProgressDeadline: 32 * time.Second,
  2097  					AutoRevert:       false,
  2098  					Canary:           1,
  2099  				},
  2100  			},
  2101  			Expected: &TaskGroupDiff{
  2102  				Type: DiffTypeEdited,
  2103  				Objects: []*ObjectDiff{
  2104  					{
  2105  						Type: DiffTypeEdited,
  2106  						Name: "Update",
  2107  						Fields: []*FieldDiff{
  2108  							{
  2109  								Type: DiffTypeEdited,
  2110  								Name: "AutoRevert",
  2111  								Old:  "true",
  2112  								New:  "false",
  2113  							},
  2114  							{
  2115  								Type: DiffTypeEdited,
  2116  								Name: "Canary",
  2117  								Old:  "2",
  2118  								New:  "1",
  2119  							},
  2120  							{
  2121  								Type: DiffTypeEdited,
  2122  								Name: "HealthCheck",
  2123  								Old:  "foo",
  2124  								New:  "bar",
  2125  							},
  2126  							{
  2127  								Type: DiffTypeEdited,
  2128  								Name: "HealthyDeadline",
  2129  								Old:  "30000000000",
  2130  								New:  "31000000000",
  2131  							},
  2132  							{
  2133  								Type: DiffTypeEdited,
  2134  								Name: "MaxParallel",
  2135  								Old:  "5",
  2136  								New:  "7",
  2137  							},
  2138  							{
  2139  								Type: DiffTypeEdited,
  2140  								Name: "MinHealthyTime",
  2141  								Old:  "1000000000",
  2142  								New:  "2000000000",
  2143  							},
  2144  							{
  2145  								Type: DiffTypeEdited,
  2146  								Name: "ProgressDeadline",
  2147  								Old:  "29000000000",
  2148  								New:  "32000000000",
  2149  							},
  2150  						},
  2151  					},
  2152  				},
  2153  			},
  2154  		},
  2155  		{
  2156  			// Update strategy edited with context
  2157  			Contextual: true,
  2158  			Old: &TaskGroup{
  2159  				Update: &UpdateStrategy{
  2160  					MaxParallel:      5,
  2161  					HealthCheck:      "foo",
  2162  					MinHealthyTime:   1 * time.Second,
  2163  					HealthyDeadline:  30 * time.Second,
  2164  					ProgressDeadline: 30 * time.Second,
  2165  					AutoRevert:       true,
  2166  					Canary:           2,
  2167  				},
  2168  			},
  2169  			New: &TaskGroup{
  2170  				Update: &UpdateStrategy{
  2171  					MaxParallel:      7,
  2172  					HealthCheck:      "foo",
  2173  					MinHealthyTime:   1 * time.Second,
  2174  					HealthyDeadline:  30 * time.Second,
  2175  					ProgressDeadline: 30 * time.Second,
  2176  					AutoRevert:       true,
  2177  					Canary:           2,
  2178  				},
  2179  			},
  2180  			Expected: &TaskGroupDiff{
  2181  				Type: DiffTypeEdited,
  2182  				Objects: []*ObjectDiff{
  2183  					{
  2184  						Type: DiffTypeEdited,
  2185  						Name: "Update",
  2186  						Fields: []*FieldDiff{
  2187  							{
  2188  								Type: DiffTypeNone,
  2189  								Name: "AutoRevert",
  2190  								Old:  "true",
  2191  								New:  "true",
  2192  							},
  2193  							{
  2194  								Type: DiffTypeNone,
  2195  								Name: "Canary",
  2196  								Old:  "2",
  2197  								New:  "2",
  2198  							},
  2199  							{
  2200  								Type: DiffTypeNone,
  2201  								Name: "HealthCheck",
  2202  								Old:  "foo",
  2203  								New:  "foo",
  2204  							},
  2205  							{
  2206  								Type: DiffTypeNone,
  2207  								Name: "HealthyDeadline",
  2208  								Old:  "30000000000",
  2209  								New:  "30000000000",
  2210  							},
  2211  							{
  2212  								Type: DiffTypeEdited,
  2213  								Name: "MaxParallel",
  2214  								Old:  "5",
  2215  								New:  "7",
  2216  							},
  2217  							{
  2218  								Type: DiffTypeNone,
  2219  								Name: "MinHealthyTime",
  2220  								Old:  "1000000000",
  2221  								New:  "1000000000",
  2222  							},
  2223  							{
  2224  								Type: DiffTypeNone,
  2225  								Name: "ProgressDeadline",
  2226  								Old:  "30000000000",
  2227  								New:  "30000000000",
  2228  							},
  2229  						},
  2230  					},
  2231  				},
  2232  			},
  2233  		},
  2234  		{
  2235  			// EphemeralDisk added
  2236  			Old: &TaskGroup{},
  2237  			New: &TaskGroup{
  2238  				EphemeralDisk: &EphemeralDisk{
  2239  					Migrate: true,
  2240  					Sticky:  true,
  2241  					SizeMB:  100,
  2242  				},
  2243  			},
  2244  			Expected: &TaskGroupDiff{
  2245  				Type: DiffTypeEdited,
  2246  				Objects: []*ObjectDiff{
  2247  					{
  2248  						Type: DiffTypeAdded,
  2249  						Name: "EphemeralDisk",
  2250  						Fields: []*FieldDiff{
  2251  							{
  2252  								Type: DiffTypeAdded,
  2253  								Name: "Migrate",
  2254  								Old:  "",
  2255  								New:  "true",
  2256  							},
  2257  							{
  2258  								Type: DiffTypeAdded,
  2259  								Name: "SizeMB",
  2260  								Old:  "",
  2261  								New:  "100",
  2262  							},
  2263  							{
  2264  								Type: DiffTypeAdded,
  2265  								Name: "Sticky",
  2266  								Old:  "",
  2267  								New:  "true",
  2268  							},
  2269  						},
  2270  					},
  2271  				},
  2272  			},
  2273  		},
  2274  		{
  2275  			// EphemeralDisk deleted
  2276  			Old: &TaskGroup{
  2277  				EphemeralDisk: &EphemeralDisk{
  2278  					Migrate: true,
  2279  					Sticky:  true,
  2280  					SizeMB:  100,
  2281  				},
  2282  			},
  2283  			New: &TaskGroup{},
  2284  			Expected: &TaskGroupDiff{
  2285  				Type: DiffTypeEdited,
  2286  				Objects: []*ObjectDiff{
  2287  					{
  2288  						Type: DiffTypeDeleted,
  2289  						Name: "EphemeralDisk",
  2290  						Fields: []*FieldDiff{
  2291  							{
  2292  								Type: DiffTypeDeleted,
  2293  								Name: "Migrate",
  2294  								Old:  "true",
  2295  								New:  "",
  2296  							},
  2297  							{
  2298  								Type: DiffTypeDeleted,
  2299  								Name: "SizeMB",
  2300  								Old:  "100",
  2301  								New:  "",
  2302  							},
  2303  							{
  2304  								Type: DiffTypeDeleted,
  2305  								Name: "Sticky",
  2306  								Old:  "true",
  2307  								New:  "",
  2308  							},
  2309  						},
  2310  					},
  2311  				},
  2312  			},
  2313  		},
  2314  		{
  2315  			// EphemeralDisk edited
  2316  			Old: &TaskGroup{
  2317  				EphemeralDisk: &EphemeralDisk{
  2318  					Migrate: true,
  2319  					Sticky:  true,
  2320  					SizeMB:  150,
  2321  				},
  2322  			},
  2323  			New: &TaskGroup{
  2324  				EphemeralDisk: &EphemeralDisk{
  2325  					Migrate: false,
  2326  					Sticky:  false,
  2327  					SizeMB:  90,
  2328  				},
  2329  			},
  2330  			Expected: &TaskGroupDiff{
  2331  				Type: DiffTypeEdited,
  2332  				Objects: []*ObjectDiff{
  2333  					{
  2334  						Type: DiffTypeEdited,
  2335  						Name: "EphemeralDisk",
  2336  						Fields: []*FieldDiff{
  2337  							{
  2338  								Type: DiffTypeEdited,
  2339  								Name: "Migrate",
  2340  								Old:  "true",
  2341  								New:  "false",
  2342  							},
  2343  							{
  2344  								Type: DiffTypeEdited,
  2345  								Name: "SizeMB",
  2346  								Old:  "150",
  2347  								New:  "90",
  2348  							},
  2349  
  2350  							{
  2351  								Type: DiffTypeEdited,
  2352  								Name: "Sticky",
  2353  								Old:  "true",
  2354  								New:  "false",
  2355  							},
  2356  						},
  2357  					},
  2358  				},
  2359  			},
  2360  		},
  2361  		{
  2362  			// EphemeralDisk edited with context
  2363  			Contextual: true,
  2364  			Old: &TaskGroup{
  2365  				EphemeralDisk: &EphemeralDisk{
  2366  					Migrate: false,
  2367  					Sticky:  false,
  2368  					SizeMB:  100,
  2369  				},
  2370  			},
  2371  			New: &TaskGroup{
  2372  				EphemeralDisk: &EphemeralDisk{
  2373  					Migrate: true,
  2374  					Sticky:  true,
  2375  					SizeMB:  90,
  2376  				},
  2377  			},
  2378  			Expected: &TaskGroupDiff{
  2379  				Type: DiffTypeEdited,
  2380  				Objects: []*ObjectDiff{
  2381  					{
  2382  						Type: DiffTypeEdited,
  2383  						Name: "EphemeralDisk",
  2384  						Fields: []*FieldDiff{
  2385  							{
  2386  								Type: DiffTypeEdited,
  2387  								Name: "Migrate",
  2388  								Old:  "false",
  2389  								New:  "true",
  2390  							},
  2391  							{
  2392  								Type: DiffTypeEdited,
  2393  								Name: "SizeMB",
  2394  								Old:  "100",
  2395  								New:  "90",
  2396  							},
  2397  							{
  2398  								Type: DiffTypeEdited,
  2399  								Name: "Sticky",
  2400  								Old:  "false",
  2401  								New:  "true",
  2402  							},
  2403  						},
  2404  					},
  2405  				},
  2406  			},
  2407  		},
  2408  		{
  2409  			// Tasks edited
  2410  			Old: &TaskGroup{
  2411  				Tasks: []*Task{
  2412  					{
  2413  						Name:   "foo",
  2414  						Driver: "docker",
  2415  					},
  2416  					{
  2417  						Name:   "bar",
  2418  						Driver: "docker",
  2419  					},
  2420  					{
  2421  						Name:          "baz",
  2422  						ShutdownDelay: 1 * time.Second,
  2423  					},
  2424  				},
  2425  			},
  2426  			New: &TaskGroup{
  2427  				Tasks: []*Task{
  2428  					{
  2429  						Name:   "bar",
  2430  						Driver: "docker",
  2431  					},
  2432  					{
  2433  						Name:   "bam",
  2434  						Driver: "docker",
  2435  					},
  2436  					{
  2437  						Name:          "baz",
  2438  						ShutdownDelay: 2 * time.Second,
  2439  					},
  2440  				},
  2441  			},
  2442  			Expected: &TaskGroupDiff{
  2443  				Type: DiffTypeEdited,
  2444  				Tasks: []*TaskDiff{
  2445  					{
  2446  						Type: DiffTypeAdded,
  2447  						Name: "bam",
  2448  						Fields: []*FieldDiff{
  2449  							{
  2450  								Type: DiffTypeAdded,
  2451  								Name: "Driver",
  2452  								Old:  "",
  2453  								New:  "docker",
  2454  							},
  2455  							{
  2456  								Type: DiffTypeAdded,
  2457  								Name: "KillTimeout",
  2458  								Old:  "",
  2459  								New:  "0",
  2460  							},
  2461  							{
  2462  								Type: DiffTypeAdded,
  2463  								Name: "Leader",
  2464  								Old:  "",
  2465  								New:  "false",
  2466  							},
  2467  							{
  2468  								Type: DiffTypeAdded,
  2469  								Name: "ShutdownDelay",
  2470  								Old:  "",
  2471  								New:  "0",
  2472  							},
  2473  						},
  2474  					},
  2475  					{
  2476  						Type: DiffTypeNone,
  2477  						Name: "bar",
  2478  					},
  2479  					{
  2480  						Type: DiffTypeEdited,
  2481  						Name: "baz",
  2482  						Fields: []*FieldDiff{
  2483  							{
  2484  								Type: DiffTypeEdited,
  2485  								Name: "ShutdownDelay",
  2486  								Old:  "1000000000",
  2487  								New:  "2000000000",
  2488  							},
  2489  						},
  2490  					},
  2491  					{
  2492  						Type: DiffTypeDeleted,
  2493  						Name: "foo",
  2494  						Fields: []*FieldDiff{
  2495  							{
  2496  								Type: DiffTypeDeleted,
  2497  								Name: "Driver",
  2498  								Old:  "docker",
  2499  								New:  "",
  2500  							},
  2501  							{
  2502  								Type: DiffTypeDeleted,
  2503  								Name: "KillTimeout",
  2504  								Old:  "0",
  2505  								New:  "",
  2506  							},
  2507  							{
  2508  								Type: DiffTypeDeleted,
  2509  								Name: "Leader",
  2510  								Old:  "false",
  2511  								New:  "",
  2512  							},
  2513  							{
  2514  								Type: DiffTypeDeleted,
  2515  								Name: "ShutdownDelay",
  2516  								Old:  "0",
  2517  								New:  "",
  2518  							},
  2519  						},
  2520  					},
  2521  				},
  2522  			},
  2523  		},
  2524  	}
  2525  
  2526  	for i, c := range cases {
  2527  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2528  		if c.Error && err == nil {
  2529  			t.Fatalf("case %d: expected errored", i+1)
  2530  		} else if err != nil {
  2531  			if !c.Error {
  2532  				t.Fatalf("case %d: errored %#v", i+1, err)
  2533  			} else {
  2534  				continue
  2535  			}
  2536  		}
  2537  
  2538  		if !reflect.DeepEqual(actual, c.Expected) {
  2539  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2540  				i+1, actual, c.Expected)
  2541  		}
  2542  	}
  2543  }
  2544  
  2545  func TestTaskDiff(t *testing.T) {
  2546  	cases := []struct {
  2547  		Name       string
  2548  		Old, New   *Task
  2549  		Expected   *TaskDiff
  2550  		Error      bool
  2551  		Contextual bool
  2552  	}{
  2553  		{
  2554  			Name: "Empty",
  2555  			Old:  nil,
  2556  			New:  nil,
  2557  			Expected: &TaskDiff{
  2558  				Type: DiffTypeNone,
  2559  			},
  2560  		},
  2561  		{
  2562  			Name: "Primitive only that has different names",
  2563  			Old: &Task{
  2564  				Name: "foo",
  2565  				Meta: map[string]string{
  2566  					"foo": "bar",
  2567  				},
  2568  			},
  2569  			New: &Task{
  2570  				Name: "bar",
  2571  				Meta: map[string]string{
  2572  					"foo": "bar",
  2573  				},
  2574  			},
  2575  			Error: true,
  2576  		},
  2577  		{
  2578  			Name: "Primitive only that is the same",
  2579  			Old: &Task{
  2580  				Name:   "foo",
  2581  				Driver: "exec",
  2582  				User:   "foo",
  2583  				Env: map[string]string{
  2584  					"FOO": "bar",
  2585  				},
  2586  				Meta: map[string]string{
  2587  					"foo": "bar",
  2588  				},
  2589  				KillTimeout: 1 * time.Second,
  2590  				Leader:      true,
  2591  			},
  2592  			New: &Task{
  2593  				Name:   "foo",
  2594  				Driver: "exec",
  2595  				User:   "foo",
  2596  				Env: map[string]string{
  2597  					"FOO": "bar",
  2598  				},
  2599  				Meta: map[string]string{
  2600  					"foo": "bar",
  2601  				},
  2602  				KillTimeout: 1 * time.Second,
  2603  				Leader:      true,
  2604  			},
  2605  			Expected: &TaskDiff{
  2606  				Type: DiffTypeNone,
  2607  				Name: "foo",
  2608  			},
  2609  		},
  2610  		{
  2611  			Name: "Primitive only that has diffs",
  2612  			Old: &Task{
  2613  				Name:   "foo",
  2614  				Driver: "exec",
  2615  				User:   "foo",
  2616  				Env: map[string]string{
  2617  					"FOO": "bar",
  2618  				},
  2619  				Meta: map[string]string{
  2620  					"foo": "bar",
  2621  				},
  2622  				KillTimeout: 1 * time.Second,
  2623  				Leader:      true,
  2624  			},
  2625  			New: &Task{
  2626  				Name:   "foo",
  2627  				Driver: "docker",
  2628  				User:   "bar",
  2629  				Env: map[string]string{
  2630  					"FOO": "baz",
  2631  				},
  2632  				Meta: map[string]string{
  2633  					"foo": "baz",
  2634  				},
  2635  				KillTimeout: 2 * time.Second,
  2636  				Leader:      false,
  2637  			},
  2638  			Expected: &TaskDiff{
  2639  				Type: DiffTypeEdited,
  2640  				Name: "foo",
  2641  				Fields: []*FieldDiff{
  2642  					{
  2643  						Type: DiffTypeEdited,
  2644  						Name: "Driver",
  2645  						Old:  "exec",
  2646  						New:  "docker",
  2647  					},
  2648  					{
  2649  						Type: DiffTypeEdited,
  2650  						Name: "Env[FOO]",
  2651  						Old:  "bar",
  2652  						New:  "baz",
  2653  					},
  2654  					{
  2655  						Type: DiffTypeEdited,
  2656  						Name: "KillTimeout",
  2657  						Old:  "1000000000",
  2658  						New:  "2000000000",
  2659  					},
  2660  					{
  2661  						Type: DiffTypeEdited,
  2662  						Name: "Leader",
  2663  						Old:  "true",
  2664  						New:  "false",
  2665  					},
  2666  					{
  2667  						Type: DiffTypeEdited,
  2668  						Name: "Meta[foo]",
  2669  						Old:  "bar",
  2670  						New:  "baz",
  2671  					},
  2672  					{
  2673  						Type: DiffTypeEdited,
  2674  						Name: "User",
  2675  						Old:  "foo",
  2676  						New:  "bar",
  2677  					},
  2678  				},
  2679  			},
  2680  		},
  2681  		{
  2682  			Name: "Map diff",
  2683  			Old: &Task{
  2684  				Meta: map[string]string{
  2685  					"foo": "foo",
  2686  					"bar": "bar",
  2687  				},
  2688  				Env: map[string]string{
  2689  					"foo": "foo",
  2690  					"bar": "bar",
  2691  				},
  2692  			},
  2693  			New: &Task{
  2694  				Meta: map[string]string{
  2695  					"bar": "bar",
  2696  					"baz": "baz",
  2697  				},
  2698  				Env: map[string]string{
  2699  					"bar": "bar",
  2700  					"baz": "baz",
  2701  				},
  2702  			},
  2703  			Expected: &TaskDiff{
  2704  				Type: DiffTypeEdited,
  2705  				Fields: []*FieldDiff{
  2706  					{
  2707  						Type: DiffTypeAdded,
  2708  						Name: "Env[baz]",
  2709  						Old:  "",
  2710  						New:  "baz",
  2711  					},
  2712  					{
  2713  						Type: DiffTypeDeleted,
  2714  						Name: "Env[foo]",
  2715  						Old:  "foo",
  2716  						New:  "",
  2717  					},
  2718  					{
  2719  						Type: DiffTypeAdded,
  2720  						Name: "Meta[baz]",
  2721  						Old:  "",
  2722  						New:  "baz",
  2723  					},
  2724  					{
  2725  						Type: DiffTypeDeleted,
  2726  						Name: "Meta[foo]",
  2727  						Old:  "foo",
  2728  						New:  "",
  2729  					},
  2730  				},
  2731  			},
  2732  		},
  2733  		{
  2734  			Name: "Constraints edited",
  2735  			Old: &Task{
  2736  				Constraints: []*Constraint{
  2737  					{
  2738  						LTarget: "foo",
  2739  						RTarget: "foo",
  2740  						Operand: "foo",
  2741  						str:     "foo",
  2742  					},
  2743  					{
  2744  						LTarget: "bar",
  2745  						RTarget: "bar",
  2746  						Operand: "bar",
  2747  						str:     "bar",
  2748  					},
  2749  				},
  2750  			},
  2751  			New: &Task{
  2752  				Constraints: []*Constraint{
  2753  					{
  2754  						LTarget: "foo",
  2755  						RTarget: "foo",
  2756  						Operand: "foo",
  2757  						str:     "foo",
  2758  					},
  2759  					{
  2760  						LTarget: "baz",
  2761  						RTarget: "baz",
  2762  						Operand: "baz",
  2763  						str:     "baz",
  2764  					},
  2765  				},
  2766  			},
  2767  			Expected: &TaskDiff{
  2768  				Type: DiffTypeEdited,
  2769  				Objects: []*ObjectDiff{
  2770  					{
  2771  						Type: DiffTypeAdded,
  2772  						Name: "Constraint",
  2773  						Fields: []*FieldDiff{
  2774  							{
  2775  								Type: DiffTypeAdded,
  2776  								Name: "LTarget",
  2777  								Old:  "",
  2778  								New:  "baz",
  2779  							},
  2780  							{
  2781  								Type: DiffTypeAdded,
  2782  								Name: "Operand",
  2783  								Old:  "",
  2784  								New:  "baz",
  2785  							},
  2786  							{
  2787  								Type: DiffTypeAdded,
  2788  								Name: "RTarget",
  2789  								Old:  "",
  2790  								New:  "baz",
  2791  							},
  2792  						},
  2793  					},
  2794  					{
  2795  						Type: DiffTypeDeleted,
  2796  						Name: "Constraint",
  2797  						Fields: []*FieldDiff{
  2798  							{
  2799  								Type: DiffTypeDeleted,
  2800  								Name: "LTarget",
  2801  								Old:  "bar",
  2802  								New:  "",
  2803  							},
  2804  							{
  2805  								Type: DiffTypeDeleted,
  2806  								Name: "Operand",
  2807  								Old:  "bar",
  2808  								New:  "",
  2809  							},
  2810  							{
  2811  								Type: DiffTypeDeleted,
  2812  								Name: "RTarget",
  2813  								Old:  "bar",
  2814  								New:  "",
  2815  							},
  2816  						},
  2817  					},
  2818  				},
  2819  			},
  2820  		},
  2821  		{
  2822  			Name: "Affinities edited",
  2823  			Old: &Task{
  2824  				Affinities: []*Affinity{
  2825  					{
  2826  						LTarget: "foo",
  2827  						RTarget: "foo",
  2828  						Operand: "foo",
  2829  						Weight:  20,
  2830  						str:     "foo",
  2831  					},
  2832  					{
  2833  						LTarget: "bar",
  2834  						RTarget: "bar",
  2835  						Operand: "bar",
  2836  						Weight:  20,
  2837  						str:     "bar",
  2838  					},
  2839  				},
  2840  			},
  2841  			New: &Task{
  2842  				Affinities: []*Affinity{
  2843  					{
  2844  						LTarget: "foo",
  2845  						RTarget: "foo",
  2846  						Operand: "foo",
  2847  						Weight:  20,
  2848  						str:     "foo",
  2849  					},
  2850  					{
  2851  						LTarget: "baz",
  2852  						RTarget: "baz",
  2853  						Operand: "baz",
  2854  						Weight:  20,
  2855  						str:     "baz",
  2856  					},
  2857  				},
  2858  			},
  2859  			Expected: &TaskDiff{
  2860  				Type: DiffTypeEdited,
  2861  				Objects: []*ObjectDiff{
  2862  					{
  2863  						Type: DiffTypeAdded,
  2864  						Name: "Affinity",
  2865  						Fields: []*FieldDiff{
  2866  							{
  2867  								Type: DiffTypeAdded,
  2868  								Name: "LTarget",
  2869  								Old:  "",
  2870  								New:  "baz",
  2871  							},
  2872  							{
  2873  								Type: DiffTypeAdded,
  2874  								Name: "Operand",
  2875  								Old:  "",
  2876  								New:  "baz",
  2877  							},
  2878  							{
  2879  								Type: DiffTypeAdded,
  2880  								Name: "RTarget",
  2881  								Old:  "",
  2882  								New:  "baz",
  2883  							},
  2884  							{
  2885  								Type: DiffTypeAdded,
  2886  								Name: "Weight",
  2887  								Old:  "",
  2888  								New:  "20",
  2889  							},
  2890  						},
  2891  					},
  2892  					{
  2893  						Type: DiffTypeDeleted,
  2894  						Name: "Affinity",
  2895  						Fields: []*FieldDiff{
  2896  							{
  2897  								Type: DiffTypeDeleted,
  2898  								Name: "LTarget",
  2899  								Old:  "bar",
  2900  								New:  "",
  2901  							},
  2902  							{
  2903  								Type: DiffTypeDeleted,
  2904  								Name: "Operand",
  2905  								Old:  "bar",
  2906  								New:  "",
  2907  							},
  2908  							{
  2909  								Type: DiffTypeDeleted,
  2910  								Name: "RTarget",
  2911  								Old:  "bar",
  2912  								New:  "",
  2913  							},
  2914  							{
  2915  								Type: DiffTypeDeleted,
  2916  								Name: "Weight",
  2917  								Old:  "20",
  2918  								New:  "",
  2919  							},
  2920  						},
  2921  					},
  2922  				},
  2923  			},
  2924  		},
  2925  		{
  2926  			Name: "LogConfig added",
  2927  			Old:  &Task{},
  2928  			New: &Task{
  2929  				LogConfig: &LogConfig{
  2930  					MaxFiles:      1,
  2931  					MaxFileSizeMB: 10,
  2932  				},
  2933  			},
  2934  			Expected: &TaskDiff{
  2935  				Type: DiffTypeEdited,
  2936  				Objects: []*ObjectDiff{
  2937  					{
  2938  						Type: DiffTypeAdded,
  2939  						Name: "LogConfig",
  2940  						Fields: []*FieldDiff{
  2941  							{
  2942  								Type: DiffTypeAdded,
  2943  								Name: "MaxFileSizeMB",
  2944  								Old:  "",
  2945  								New:  "10",
  2946  							},
  2947  							{
  2948  								Type: DiffTypeAdded,
  2949  								Name: "MaxFiles",
  2950  								Old:  "",
  2951  								New:  "1",
  2952  							},
  2953  						},
  2954  					},
  2955  				},
  2956  			},
  2957  		},
  2958  		{
  2959  			Name: "LogConfig deleted",
  2960  			Old: &Task{
  2961  				LogConfig: &LogConfig{
  2962  					MaxFiles:      1,
  2963  					MaxFileSizeMB: 10,
  2964  				},
  2965  			},
  2966  			New: &Task{},
  2967  			Expected: &TaskDiff{
  2968  				Type: DiffTypeEdited,
  2969  				Objects: []*ObjectDiff{
  2970  					{
  2971  						Type: DiffTypeDeleted,
  2972  						Name: "LogConfig",
  2973  						Fields: []*FieldDiff{
  2974  							{
  2975  								Type: DiffTypeDeleted,
  2976  								Name: "MaxFileSizeMB",
  2977  								Old:  "10",
  2978  								New:  "",
  2979  							},
  2980  							{
  2981  								Type: DiffTypeDeleted,
  2982  								Name: "MaxFiles",
  2983  								Old:  "1",
  2984  								New:  "",
  2985  							},
  2986  						},
  2987  					},
  2988  				},
  2989  			},
  2990  		},
  2991  		{
  2992  			Name: "LogConfig edited",
  2993  			Old: &Task{
  2994  				LogConfig: &LogConfig{
  2995  					MaxFiles:      1,
  2996  					MaxFileSizeMB: 10,
  2997  				},
  2998  			},
  2999  			New: &Task{
  3000  				LogConfig: &LogConfig{
  3001  					MaxFiles:      2,
  3002  					MaxFileSizeMB: 20,
  3003  				},
  3004  			},
  3005  			Expected: &TaskDiff{
  3006  				Type: DiffTypeEdited,
  3007  				Objects: []*ObjectDiff{
  3008  					{
  3009  						Type: DiffTypeEdited,
  3010  						Name: "LogConfig",
  3011  						Fields: []*FieldDiff{
  3012  							{
  3013  								Type: DiffTypeEdited,
  3014  								Name: "MaxFileSizeMB",
  3015  								Old:  "10",
  3016  								New:  "20",
  3017  							},
  3018  							{
  3019  								Type: DiffTypeEdited,
  3020  								Name: "MaxFiles",
  3021  								Old:  "1",
  3022  								New:  "2",
  3023  							},
  3024  						},
  3025  					},
  3026  				},
  3027  			},
  3028  		},
  3029  		{
  3030  			Name:       "LogConfig edited with context",
  3031  			Contextual: true,
  3032  			Old: &Task{
  3033  				LogConfig: &LogConfig{
  3034  					MaxFiles:      1,
  3035  					MaxFileSizeMB: 10,
  3036  				},
  3037  			},
  3038  			New: &Task{
  3039  				LogConfig: &LogConfig{
  3040  					MaxFiles:      1,
  3041  					MaxFileSizeMB: 20,
  3042  				},
  3043  			},
  3044  			Expected: &TaskDiff{
  3045  				Type: DiffTypeEdited,
  3046  				Objects: []*ObjectDiff{
  3047  					{
  3048  						Type: DiffTypeEdited,
  3049  						Name: "LogConfig",
  3050  						Fields: []*FieldDiff{
  3051  							{
  3052  								Type: DiffTypeEdited,
  3053  								Name: "MaxFileSizeMB",
  3054  								Old:  "10",
  3055  								New:  "20",
  3056  							},
  3057  							{
  3058  								Type: DiffTypeNone,
  3059  								Name: "MaxFiles",
  3060  								Old:  "1",
  3061  								New:  "1",
  3062  							},
  3063  						},
  3064  					},
  3065  				},
  3066  			},
  3067  		},
  3068  		{
  3069  			Name: "Artifacts edited",
  3070  			Old: &Task{
  3071  				Artifacts: []*TaskArtifact{
  3072  					{
  3073  						GetterSource: "foo",
  3074  						GetterOptions: map[string]string{
  3075  							"foo": "bar",
  3076  						},
  3077  						RelativeDest: "foo",
  3078  					},
  3079  					{
  3080  						GetterSource: "bar",
  3081  						GetterOptions: map[string]string{
  3082  							"bar": "baz",
  3083  						},
  3084  						GetterMode:   "dir",
  3085  						RelativeDest: "bar",
  3086  					},
  3087  				},
  3088  			},
  3089  			New: &Task{
  3090  				Artifacts: []*TaskArtifact{
  3091  					{
  3092  						GetterSource: "foo",
  3093  						GetterOptions: map[string]string{
  3094  							"foo": "bar",
  3095  						},
  3096  						RelativeDest: "foo",
  3097  					},
  3098  					{
  3099  						GetterSource: "bam",
  3100  						GetterOptions: map[string]string{
  3101  							"bam": "baz",
  3102  						},
  3103  						GetterMode:   "file",
  3104  						RelativeDest: "bam",
  3105  					},
  3106  				},
  3107  			},
  3108  			Expected: &TaskDiff{
  3109  				Type: DiffTypeEdited,
  3110  				Objects: []*ObjectDiff{
  3111  					{
  3112  						Type: DiffTypeAdded,
  3113  						Name: "Artifact",
  3114  						Fields: []*FieldDiff{
  3115  							{
  3116  								Type: DiffTypeAdded,
  3117  								Name: "GetterMode",
  3118  								Old:  "",
  3119  								New:  "file",
  3120  							},
  3121  							{
  3122  								Type: DiffTypeAdded,
  3123  								Name: "GetterOptions[bam]",
  3124  								Old:  "",
  3125  								New:  "baz",
  3126  							},
  3127  							{
  3128  								Type: DiffTypeAdded,
  3129  								Name: "GetterSource",
  3130  								Old:  "",
  3131  								New:  "bam",
  3132  							},
  3133  							{
  3134  								Type: DiffTypeAdded,
  3135  								Name: "RelativeDest",
  3136  								Old:  "",
  3137  								New:  "bam",
  3138  							},
  3139  						},
  3140  					},
  3141  					{
  3142  						Type: DiffTypeDeleted,
  3143  						Name: "Artifact",
  3144  						Fields: []*FieldDiff{
  3145  							{
  3146  								Type: DiffTypeDeleted,
  3147  								Name: "GetterMode",
  3148  								Old:  "dir",
  3149  								New:  "",
  3150  							},
  3151  							{
  3152  								Type: DiffTypeDeleted,
  3153  								Name: "GetterOptions[bar]",
  3154  								Old:  "baz",
  3155  								New:  "",
  3156  							},
  3157  							{
  3158  								Type: DiffTypeDeleted,
  3159  								Name: "GetterSource",
  3160  								Old:  "bar",
  3161  								New:  "",
  3162  							},
  3163  							{
  3164  								Type: DiffTypeDeleted,
  3165  								Name: "RelativeDest",
  3166  								Old:  "bar",
  3167  								New:  "",
  3168  							},
  3169  						},
  3170  					},
  3171  				},
  3172  			},
  3173  		},
  3174  		{
  3175  			Name: "Resources edited (no networks)",
  3176  			Old: &Task{
  3177  				Resources: &Resources{
  3178  					CPU:      100,
  3179  					MemoryMB: 100,
  3180  					DiskMB:   100,
  3181  				},
  3182  			},
  3183  			New: &Task{
  3184  				Resources: &Resources{
  3185  					CPU:      200,
  3186  					MemoryMB: 200,
  3187  					DiskMB:   200,
  3188  				},
  3189  			},
  3190  			Expected: &TaskDiff{
  3191  				Type: DiffTypeEdited,
  3192  				Objects: []*ObjectDiff{
  3193  					{
  3194  						Type: DiffTypeEdited,
  3195  						Name: "Resources",
  3196  						Fields: []*FieldDiff{
  3197  							{
  3198  								Type: DiffTypeEdited,
  3199  								Name: "CPU",
  3200  								Old:  "100",
  3201  								New:  "200",
  3202  							},
  3203  							{
  3204  								Type: DiffTypeEdited,
  3205  								Name: "DiskMB",
  3206  								Old:  "100",
  3207  								New:  "200",
  3208  							},
  3209  							{
  3210  								Type: DiffTypeEdited,
  3211  								Name: "MemoryMB",
  3212  								Old:  "100",
  3213  								New:  "200",
  3214  							},
  3215  						},
  3216  					},
  3217  				},
  3218  			},
  3219  		},
  3220  		{
  3221  			Name:       "Resources edited (no networks) with context",
  3222  			Contextual: true,
  3223  			Old: &Task{
  3224  				Resources: &Resources{
  3225  					CPU:      100,
  3226  					MemoryMB: 100,
  3227  					DiskMB:   100,
  3228  				},
  3229  			},
  3230  			New: &Task{
  3231  				Resources: &Resources{
  3232  					CPU:      200,
  3233  					MemoryMB: 100,
  3234  					DiskMB:   200,
  3235  				},
  3236  			},
  3237  			Expected: &TaskDiff{
  3238  				Type: DiffTypeEdited,
  3239  				Objects: []*ObjectDiff{
  3240  					{
  3241  						Type: DiffTypeEdited,
  3242  						Name: "Resources",
  3243  						Fields: []*FieldDiff{
  3244  							{
  3245  								Type: DiffTypeEdited,
  3246  								Name: "CPU",
  3247  								Old:  "100",
  3248  								New:  "200",
  3249  							},
  3250  							{
  3251  								Type: DiffTypeEdited,
  3252  								Name: "DiskMB",
  3253  								Old:  "100",
  3254  								New:  "200",
  3255  							},
  3256  							{
  3257  								Type: DiffTypeNone,
  3258  								Name: "IOPS",
  3259  								Old:  "0",
  3260  								New:  "0",
  3261  							},
  3262  							{
  3263  								Type: DiffTypeNone,
  3264  								Name: "MemoryMB",
  3265  								Old:  "100",
  3266  								New:  "100",
  3267  							},
  3268  						},
  3269  					},
  3270  				},
  3271  			},
  3272  		},
  3273  		{
  3274  			Name: "Network Resources edited",
  3275  			Old: &Task{
  3276  				Resources: &Resources{
  3277  					Networks: []*NetworkResource{
  3278  						{
  3279  							Device: "foo",
  3280  							CIDR:   "foo",
  3281  							IP:     "foo",
  3282  							MBits:  100,
  3283  							ReservedPorts: []Port{
  3284  								{
  3285  									Label: "foo",
  3286  									Value: 80,
  3287  								},
  3288  							},
  3289  							DynamicPorts: []Port{
  3290  								{
  3291  									Label: "bar",
  3292  								},
  3293  							},
  3294  						},
  3295  					},
  3296  				},
  3297  			},
  3298  			New: &Task{
  3299  				Resources: &Resources{
  3300  					Networks: []*NetworkResource{
  3301  						{
  3302  							Device: "bar",
  3303  							CIDR:   "bar",
  3304  							IP:     "bar",
  3305  							MBits:  200,
  3306  							ReservedPorts: []Port{
  3307  								{
  3308  									Label: "foo",
  3309  									Value: 81,
  3310  								},
  3311  							},
  3312  							DynamicPorts: []Port{
  3313  								{
  3314  									Label: "baz",
  3315  								},
  3316  							},
  3317  						},
  3318  					},
  3319  				},
  3320  			},
  3321  			Expected: &TaskDiff{
  3322  				Type: DiffTypeEdited,
  3323  				Objects: []*ObjectDiff{
  3324  					{
  3325  						Type: DiffTypeEdited,
  3326  						Name: "Resources",
  3327  						Objects: []*ObjectDiff{
  3328  							{
  3329  								Type: DiffTypeAdded,
  3330  								Name: "Network",
  3331  								Fields: []*FieldDiff{
  3332  									{
  3333  										Type: DiffTypeAdded,
  3334  										Name: "MBits",
  3335  										Old:  "",
  3336  										New:  "200",
  3337  									},
  3338  								},
  3339  								Objects: []*ObjectDiff{
  3340  									{
  3341  										Type: DiffTypeAdded,
  3342  										Name: "Static Port",
  3343  										Fields: []*FieldDiff{
  3344  											{
  3345  												Type: DiffTypeAdded,
  3346  												Name: "Label",
  3347  												Old:  "",
  3348  												New:  "foo",
  3349  											},
  3350  											{
  3351  												Type: DiffTypeAdded,
  3352  												Name: "Value",
  3353  												Old:  "",
  3354  												New:  "81",
  3355  											},
  3356  										},
  3357  									},
  3358  									{
  3359  										Type: DiffTypeAdded,
  3360  										Name: "Dynamic Port",
  3361  										Fields: []*FieldDiff{
  3362  											{
  3363  												Type: DiffTypeAdded,
  3364  												Name: "Label",
  3365  												Old:  "",
  3366  												New:  "baz",
  3367  											},
  3368  										},
  3369  									},
  3370  								},
  3371  							},
  3372  							{
  3373  								Type: DiffTypeDeleted,
  3374  								Name: "Network",
  3375  								Fields: []*FieldDiff{
  3376  									{
  3377  										Type: DiffTypeDeleted,
  3378  										Name: "MBits",
  3379  										Old:  "100",
  3380  										New:  "",
  3381  									},
  3382  								},
  3383  								Objects: []*ObjectDiff{
  3384  									{
  3385  										Type: DiffTypeDeleted,
  3386  										Name: "Static Port",
  3387  										Fields: []*FieldDiff{
  3388  											{
  3389  												Type: DiffTypeDeleted,
  3390  												Name: "Label",
  3391  												Old:  "foo",
  3392  												New:  "",
  3393  											},
  3394  											{
  3395  												Type: DiffTypeDeleted,
  3396  												Name: "Value",
  3397  												Old:  "80",
  3398  												New:  "",
  3399  											},
  3400  										},
  3401  									},
  3402  									{
  3403  										Type: DiffTypeDeleted,
  3404  										Name: "Dynamic Port",
  3405  										Fields: []*FieldDiff{
  3406  											{
  3407  												Type: DiffTypeDeleted,
  3408  												Name: "Label",
  3409  												Old:  "bar",
  3410  												New:  "",
  3411  											},
  3412  										},
  3413  									},
  3414  								},
  3415  							},
  3416  						},
  3417  					},
  3418  				},
  3419  			},
  3420  		},
  3421  		{
  3422  			Name: "Device Resources edited",
  3423  			Old: &Task{
  3424  				Resources: &Resources{
  3425  					Devices: []*RequestedDevice{
  3426  						{
  3427  							Name:  "foo",
  3428  							Count: 2,
  3429  						},
  3430  						{
  3431  							Name:  "bar",
  3432  							Count: 2,
  3433  						},
  3434  						{
  3435  							Name:  "baz",
  3436  							Count: 2,
  3437  						},
  3438  					},
  3439  				},
  3440  			},
  3441  			New: &Task{
  3442  				Resources: &Resources{
  3443  					Devices: []*RequestedDevice{
  3444  						{
  3445  							Name:  "foo",
  3446  							Count: 2,
  3447  						},
  3448  						{
  3449  							Name:  "bar",
  3450  							Count: 3,
  3451  						},
  3452  						{
  3453  							Name:  "bam",
  3454  							Count: 2,
  3455  						},
  3456  					},
  3457  				},
  3458  			},
  3459  			Expected: &TaskDiff{
  3460  				Type: DiffTypeEdited,
  3461  				Objects: []*ObjectDiff{
  3462  					{
  3463  						Type: DiffTypeEdited,
  3464  						Name: "Resources",
  3465  						Objects: []*ObjectDiff{
  3466  							{
  3467  								Type: DiffTypeEdited,
  3468  								Name: "Device",
  3469  								Fields: []*FieldDiff{
  3470  									{
  3471  										Type: DiffTypeEdited,
  3472  										Name: "Count",
  3473  										Old:  "2",
  3474  										New:  "3",
  3475  									},
  3476  								},
  3477  							},
  3478  							{
  3479  								Type: DiffTypeAdded,
  3480  								Name: "Device",
  3481  								Fields: []*FieldDiff{
  3482  									{
  3483  										Type: DiffTypeAdded,
  3484  										Name: "Count",
  3485  										Old:  "",
  3486  										New:  "2",
  3487  									},
  3488  									{
  3489  										Type: DiffTypeAdded,
  3490  										Name: "Name",
  3491  										Old:  "",
  3492  										New:  "bam",
  3493  									},
  3494  								},
  3495  							},
  3496  							{
  3497  								Type: DiffTypeDeleted,
  3498  								Name: "Device",
  3499  								Fields: []*FieldDiff{
  3500  									{
  3501  										Type: DiffTypeDeleted,
  3502  										Name: "Count",
  3503  										Old:  "2",
  3504  										New:  "",
  3505  									},
  3506  									{
  3507  										Type: DiffTypeDeleted,
  3508  										Name: "Name",
  3509  										Old:  "baz",
  3510  										New:  "",
  3511  									},
  3512  								},
  3513  							},
  3514  						},
  3515  					},
  3516  				},
  3517  			},
  3518  		},
  3519  		{
  3520  			Name:       "Device Resources edited with context",
  3521  			Contextual: true,
  3522  			Old: &Task{
  3523  				Resources: &Resources{
  3524  					CPU:      100,
  3525  					MemoryMB: 100,
  3526  					DiskMB:   100,
  3527  					Devices: []*RequestedDevice{
  3528  						{
  3529  							Name:  "foo",
  3530  							Count: 2,
  3531  						},
  3532  						{
  3533  							Name:  "bar",
  3534  							Count: 2,
  3535  						},
  3536  						{
  3537  							Name:  "baz",
  3538  							Count: 2,
  3539  						},
  3540  					},
  3541  				},
  3542  			},
  3543  			New: &Task{
  3544  				Resources: &Resources{
  3545  					CPU:      100,
  3546  					MemoryMB: 100,
  3547  					DiskMB:   100,
  3548  					Devices: []*RequestedDevice{
  3549  						{
  3550  							Name:  "foo",
  3551  							Count: 2,
  3552  						},
  3553  						{
  3554  							Name:  "bar",
  3555  							Count: 3,
  3556  						},
  3557  						{
  3558  							Name:  "bam",
  3559  							Count: 2,
  3560  						},
  3561  					},
  3562  				},
  3563  			},
  3564  			Expected: &TaskDiff{
  3565  				Type: DiffTypeEdited,
  3566  				Objects: []*ObjectDiff{
  3567  					{
  3568  						Type: DiffTypeEdited,
  3569  						Name: "Resources",
  3570  						Fields: []*FieldDiff{
  3571  							{
  3572  								Type: DiffTypeNone,
  3573  								Name: "CPU",
  3574  								Old:  "100",
  3575  								New:  "100",
  3576  							},
  3577  							{
  3578  								Type: DiffTypeNone,
  3579  								Name: "DiskMB",
  3580  								Old:  "100",
  3581  								New:  "100",
  3582  							},
  3583  							{
  3584  								Type: DiffTypeNone,
  3585  								Name: "IOPS",
  3586  								Old:  "0",
  3587  								New:  "0",
  3588  							},
  3589  							{
  3590  								Type: DiffTypeNone,
  3591  								Name: "MemoryMB",
  3592  								Old:  "100",
  3593  								New:  "100",
  3594  							},
  3595  						},
  3596  						Objects: []*ObjectDiff{
  3597  							{
  3598  								Type: DiffTypeEdited,
  3599  								Name: "Device",
  3600  								Fields: []*FieldDiff{
  3601  									{
  3602  										Type: DiffTypeEdited,
  3603  										Name: "Count",
  3604  										Old:  "2",
  3605  										New:  "3",
  3606  									},
  3607  									{
  3608  										Type: DiffTypeNone,
  3609  										Name: "Name",
  3610  										Old:  "bar",
  3611  										New:  "bar",
  3612  									},
  3613  								},
  3614  							},
  3615  							{
  3616  								Type: DiffTypeAdded,
  3617  								Name: "Device",
  3618  								Fields: []*FieldDiff{
  3619  									{
  3620  										Type: DiffTypeAdded,
  3621  										Name: "Count",
  3622  										Old:  "",
  3623  										New:  "2",
  3624  									},
  3625  									{
  3626  										Type: DiffTypeAdded,
  3627  										Name: "Name",
  3628  										Old:  "",
  3629  										New:  "bam",
  3630  									},
  3631  								},
  3632  							},
  3633  							{
  3634  								Type: DiffTypeDeleted,
  3635  								Name: "Device",
  3636  								Fields: []*FieldDiff{
  3637  									{
  3638  										Type: DiffTypeDeleted,
  3639  										Name: "Count",
  3640  										Old:  "2",
  3641  										New:  "",
  3642  									},
  3643  									{
  3644  										Type: DiffTypeDeleted,
  3645  										Name: "Name",
  3646  										Old:  "baz",
  3647  										New:  "",
  3648  									},
  3649  								},
  3650  							},
  3651  						},
  3652  					},
  3653  				},
  3654  			},
  3655  		},
  3656  		{
  3657  			Name: "Config same",
  3658  			Old: &Task{
  3659  				Config: map[string]interface{}{
  3660  					"foo": 1,
  3661  					"bar": "bar",
  3662  					"bam": []string{"a", "b"},
  3663  					"baz": map[string]int{
  3664  						"a": 1,
  3665  						"b": 2,
  3666  					},
  3667  					"boom": &Port{
  3668  						Label: "boom_port",
  3669  					},
  3670  				},
  3671  			},
  3672  			New: &Task{
  3673  				Config: map[string]interface{}{
  3674  					"foo": 1,
  3675  					"bar": "bar",
  3676  					"bam": []string{"a", "b"},
  3677  					"baz": map[string]int{
  3678  						"a": 1,
  3679  						"b": 2,
  3680  					},
  3681  					"boom": &Port{
  3682  						Label: "boom_port",
  3683  					},
  3684  				},
  3685  			},
  3686  			Expected: &TaskDiff{
  3687  				Type: DiffTypeNone,
  3688  			},
  3689  		},
  3690  		{
  3691  			Name: "Config edited",
  3692  			Old: &Task{
  3693  				Config: map[string]interface{}{
  3694  					"foo": 1,
  3695  					"bar": "baz",
  3696  					"bam": []string{"a", "b"},
  3697  					"baz": map[string]int{
  3698  						"a": 1,
  3699  						"b": 2,
  3700  					},
  3701  					"boom": &Port{
  3702  						Label: "boom_port",
  3703  					},
  3704  				},
  3705  			},
  3706  			New: &Task{
  3707  				Config: map[string]interface{}{
  3708  					"foo": 2,
  3709  					"bar": "baz",
  3710  					"bam": []string{"a", "c", "d"},
  3711  					"baz": map[string]int{
  3712  						"b": 3,
  3713  						"c": 4,
  3714  					},
  3715  					"boom": &Port{
  3716  						Label: "boom_port2",
  3717  					},
  3718  				},
  3719  			},
  3720  			Expected: &TaskDiff{
  3721  				Type: DiffTypeEdited,
  3722  				Objects: []*ObjectDiff{
  3723  					{
  3724  						Type: DiffTypeEdited,
  3725  						Name: "Config",
  3726  						Fields: []*FieldDiff{
  3727  							{
  3728  								Type: DiffTypeEdited,
  3729  								Name: "bam[1]",
  3730  								Old:  "b",
  3731  								New:  "c",
  3732  							},
  3733  							{
  3734  								Type: DiffTypeAdded,
  3735  								Name: "bam[2]",
  3736  								Old:  "",
  3737  								New:  "d",
  3738  							},
  3739  							{
  3740  								Type: DiffTypeDeleted,
  3741  								Name: "baz[a]",
  3742  								Old:  "1",
  3743  								New:  "",
  3744  							},
  3745  							{
  3746  								Type: DiffTypeEdited,
  3747  								Name: "baz[b]",
  3748  								Old:  "2",
  3749  								New:  "3",
  3750  							},
  3751  							{
  3752  								Type: DiffTypeAdded,
  3753  								Name: "baz[c]",
  3754  								Old:  "",
  3755  								New:  "4",
  3756  							},
  3757  							{
  3758  								Type: DiffTypeEdited,
  3759  								Name: "boom.Label",
  3760  								Old:  "boom_port",
  3761  								New:  "boom_port2",
  3762  							},
  3763  							{
  3764  								Type: DiffTypeEdited,
  3765  								Name: "foo",
  3766  								Old:  "1",
  3767  								New:  "2",
  3768  							},
  3769  						},
  3770  					},
  3771  				},
  3772  			},
  3773  		},
  3774  		{
  3775  			Name:       "Config edited with context",
  3776  			Contextual: true,
  3777  			Old: &Task{
  3778  				Config: map[string]interface{}{
  3779  					"foo": 1,
  3780  					"bar": "baz",
  3781  					"bam": []string{"a", "b"},
  3782  					"baz": map[string]int{
  3783  						"a": 1,
  3784  						"b": 2,
  3785  					},
  3786  					"boom": &Port{
  3787  						Label: "boom_port",
  3788  					},
  3789  				},
  3790  			},
  3791  			New: &Task{
  3792  				Config: map[string]interface{}{
  3793  					"foo": 2,
  3794  					"bar": "baz",
  3795  					"bam": []string{"a", "c", "d"},
  3796  					"baz": map[string]int{
  3797  						"a": 1,
  3798  						"b": 2,
  3799  					},
  3800  					"boom": &Port{
  3801  						Label: "boom_port",
  3802  					},
  3803  				},
  3804  			},
  3805  			Expected: &TaskDiff{
  3806  				Type: DiffTypeEdited,
  3807  				Objects: []*ObjectDiff{
  3808  					{
  3809  						Type: DiffTypeEdited,
  3810  						Name: "Config",
  3811  						Fields: []*FieldDiff{
  3812  							{
  3813  								Type: DiffTypeNone,
  3814  								Name: "bam[0]",
  3815  								Old:  "a",
  3816  								New:  "a",
  3817  							},
  3818  							{
  3819  								Type: DiffTypeEdited,
  3820  								Name: "bam[1]",
  3821  								Old:  "b",
  3822  								New:  "c",
  3823  							},
  3824  							{
  3825  								Type: DiffTypeAdded,
  3826  								Name: "bam[2]",
  3827  								Old:  "",
  3828  								New:  "d",
  3829  							},
  3830  							{
  3831  								Type: DiffTypeNone,
  3832  								Name: "bar",
  3833  								Old:  "baz",
  3834  								New:  "baz",
  3835  							},
  3836  							{
  3837  								Type: DiffTypeNone,
  3838  								Name: "baz[a]",
  3839  								Old:  "1",
  3840  								New:  "1",
  3841  							},
  3842  							{
  3843  								Type: DiffTypeNone,
  3844  								Name: "baz[b]",
  3845  								Old:  "2",
  3846  								New:  "2",
  3847  							},
  3848  							{
  3849  								Type: DiffTypeNone,
  3850  								Name: "boom.Label",
  3851  								Old:  "boom_port",
  3852  								New:  "boom_port",
  3853  							},
  3854  							{
  3855  								Type: DiffTypeNone,
  3856  								Name: "boom.Value",
  3857  								Old:  "0",
  3858  								New:  "0",
  3859  							},
  3860  							{
  3861  								Type: DiffTypeEdited,
  3862  								Name: "foo",
  3863  								Old:  "1",
  3864  								New:  "2",
  3865  							},
  3866  						},
  3867  					},
  3868  				},
  3869  			},
  3870  		},
  3871  		{
  3872  			Name: "Services edited (no checks)",
  3873  			Old: &Task{
  3874  				Services: []*Service{
  3875  					{
  3876  						Name:      "foo",
  3877  						PortLabel: "foo",
  3878  					},
  3879  					{
  3880  						Name:      "bar",
  3881  						PortLabel: "bar",
  3882  					},
  3883  					{
  3884  						Name:      "baz",
  3885  						PortLabel: "baz",
  3886  					},
  3887  				},
  3888  			},
  3889  			New: &Task{
  3890  				Services: []*Service{
  3891  					{
  3892  						Name:      "bar",
  3893  						PortLabel: "bar",
  3894  					},
  3895  					{
  3896  						Name:      "baz",
  3897  						PortLabel: "baz2",
  3898  					},
  3899  					{
  3900  						Name:      "bam",
  3901  						PortLabel: "bam",
  3902  					},
  3903  				},
  3904  			},
  3905  			Expected: &TaskDiff{
  3906  				Type: DiffTypeEdited,
  3907  				Objects: []*ObjectDiff{
  3908  					{
  3909  						Type: DiffTypeEdited,
  3910  						Name: "Service",
  3911  						Fields: []*FieldDiff{
  3912  							{
  3913  								Type: DiffTypeEdited,
  3914  								Name: "PortLabel",
  3915  								Old:  "baz",
  3916  								New:  "baz2",
  3917  							},
  3918  						},
  3919  					},
  3920  					{
  3921  						Type: DiffTypeAdded,
  3922  						Name: "Service",
  3923  						Fields: []*FieldDiff{
  3924  							{
  3925  								Type: DiffTypeAdded,
  3926  								Name: "Name",
  3927  								Old:  "",
  3928  								New:  "bam",
  3929  							},
  3930  							{
  3931  								Type: DiffTypeAdded,
  3932  								Name: "PortLabel",
  3933  								Old:  "",
  3934  								New:  "bam",
  3935  							},
  3936  						},
  3937  					},
  3938  					{
  3939  						Type: DiffTypeDeleted,
  3940  						Name: "Service",
  3941  						Fields: []*FieldDiff{
  3942  							{
  3943  								Type: DiffTypeDeleted,
  3944  								Name: "Name",
  3945  								Old:  "foo",
  3946  								New:  "",
  3947  							},
  3948  							{
  3949  								Type: DiffTypeDeleted,
  3950  								Name: "PortLabel",
  3951  								Old:  "foo",
  3952  								New:  "",
  3953  							},
  3954  						},
  3955  					},
  3956  				},
  3957  			},
  3958  		},
  3959  		{
  3960  			Name:       "Services edited (no checks) with context",
  3961  			Contextual: true,
  3962  			Old: &Task{
  3963  				Services: []*Service{
  3964  					{
  3965  						Name:      "foo",
  3966  						PortLabel: "foo",
  3967  					},
  3968  				},
  3969  			},
  3970  			New: &Task{
  3971  				Services: []*Service{
  3972  					{
  3973  						Name:        "foo",
  3974  						PortLabel:   "bar",
  3975  						AddressMode: "driver",
  3976  					},
  3977  				},
  3978  			},
  3979  			Expected: &TaskDiff{
  3980  				Type: DiffTypeEdited,
  3981  				Objects: []*ObjectDiff{
  3982  					{
  3983  						Type: DiffTypeEdited,
  3984  						Name: "Service",
  3985  						Fields: []*FieldDiff{
  3986  							{
  3987  								Type: DiffTypeAdded,
  3988  								Name: "AddressMode",
  3989  								New:  "driver",
  3990  							},
  3991  							{
  3992  								Type: DiffTypeNone,
  3993  								Name: "Name",
  3994  								Old:  "foo",
  3995  								New:  "foo",
  3996  							},
  3997  							{
  3998  								Type: DiffTypeEdited,
  3999  								Name: "PortLabel",
  4000  								Old:  "foo",
  4001  								New:  "bar",
  4002  							},
  4003  						},
  4004  					},
  4005  				},
  4006  			},
  4007  		},
  4008  		{
  4009  			Name:       "Services tags edited (no checks) with context",
  4010  			Contextual: true,
  4011  			Old: &Task{
  4012  				Services: []*Service{
  4013  					{
  4014  						Tags:       []string{"foo", "bar"},
  4015  						CanaryTags: []string{"foo", "bar"},
  4016  					},
  4017  				},
  4018  			},
  4019  			New: &Task{
  4020  				Services: []*Service{
  4021  					{
  4022  						Tags:       []string{"bar", "bam"},
  4023  						CanaryTags: []string{"bar", "bam"},
  4024  					},
  4025  				},
  4026  			},
  4027  			Expected: &TaskDiff{
  4028  				Type: DiffTypeEdited,
  4029  				Objects: []*ObjectDiff{
  4030  					{
  4031  						Type: DiffTypeEdited,
  4032  						Name: "Service",
  4033  						Objects: []*ObjectDiff{
  4034  							{
  4035  								Type: DiffTypeEdited,
  4036  								Name: "CanaryTags",
  4037  								Fields: []*FieldDiff{
  4038  									{
  4039  										Type: DiffTypeAdded,
  4040  										Name: "CanaryTags",
  4041  										Old:  "",
  4042  										New:  "bam",
  4043  									},
  4044  									{
  4045  										Type: DiffTypeNone,
  4046  										Name: "CanaryTags",
  4047  										Old:  "bar",
  4048  										New:  "bar",
  4049  									},
  4050  									{
  4051  										Type: DiffTypeDeleted,
  4052  										Name: "CanaryTags",
  4053  										Old:  "foo",
  4054  										New:  "",
  4055  									},
  4056  								},
  4057  							},
  4058  							{
  4059  								Type: DiffTypeEdited,
  4060  								Name: "Tags",
  4061  								Fields: []*FieldDiff{
  4062  									{
  4063  										Type: DiffTypeAdded,
  4064  										Name: "Tags",
  4065  										Old:  "",
  4066  										New:  "bam",
  4067  									},
  4068  									{
  4069  										Type: DiffTypeNone,
  4070  										Name: "Tags",
  4071  										Old:  "bar",
  4072  										New:  "bar",
  4073  									},
  4074  									{
  4075  										Type: DiffTypeDeleted,
  4076  										Name: "Tags",
  4077  										Old:  "foo",
  4078  										New:  "",
  4079  									},
  4080  								},
  4081  							},
  4082  						},
  4083  						Fields: []*FieldDiff{
  4084  							{
  4085  								Type: DiffTypeNone,
  4086  								Name: "AddressMode",
  4087  							},
  4088  							{
  4089  								Type: DiffTypeNone,
  4090  								Name: "Name",
  4091  							},
  4092  							{
  4093  								Type: DiffTypeNone,
  4094  								Name: "PortLabel",
  4095  							},
  4096  						},
  4097  					},
  4098  				},
  4099  			},
  4100  		},
  4101  		{
  4102  			Name: "Service Checks edited",
  4103  			Old: &Task{
  4104  				Services: []*Service{
  4105  					{
  4106  						Name: "foo",
  4107  						Checks: []*ServiceCheck{
  4108  							{
  4109  								Name:     "foo",
  4110  								Type:     "http",
  4111  								Command:  "foo",
  4112  								Args:     []string{"foo"},
  4113  								Path:     "foo",
  4114  								Protocol: "http",
  4115  								Interval: 1 * time.Second,
  4116  								Timeout:  1 * time.Second,
  4117  								Header: map[string][]string{
  4118  									"Foo": {"bar"},
  4119  								},
  4120  							},
  4121  							{
  4122  								Name:     "bar",
  4123  								Type:     "http",
  4124  								Command:  "foo",
  4125  								Args:     []string{"foo"},
  4126  								Path:     "foo",
  4127  								Protocol: "http",
  4128  								Interval: 1 * time.Second,
  4129  								Timeout:  1 * time.Second,
  4130  							},
  4131  							{
  4132  								Name:     "baz",
  4133  								Type:     "http",
  4134  								Command:  "foo",
  4135  								Args:     []string{"foo"},
  4136  								Path:     "foo",
  4137  								Protocol: "http",
  4138  								Interval: 1 * time.Second,
  4139  								Timeout:  1 * time.Second,
  4140  							},
  4141  						},
  4142  					},
  4143  				},
  4144  			},
  4145  			New: &Task{
  4146  				Services: []*Service{
  4147  					{
  4148  						Name: "foo",
  4149  						Checks: []*ServiceCheck{
  4150  							{
  4151  								Name:     "bar",
  4152  								Type:     "http",
  4153  								Command:  "foo",
  4154  								Args:     []string{"foo"},
  4155  								Path:     "foo",
  4156  								Protocol: "http",
  4157  								Interval: 1 * time.Second,
  4158  								Timeout:  1 * time.Second,
  4159  							},
  4160  							{
  4161  								Name:     "baz",
  4162  								Type:     "tcp",
  4163  								Command:  "foo",
  4164  								Args:     []string{"foo"},
  4165  								Path:     "foo",
  4166  								Protocol: "http",
  4167  								Interval: 1 * time.Second,
  4168  								Timeout:  1 * time.Second,
  4169  								Header: map[string][]string{
  4170  									"Eggs": {"spam"},
  4171  								},
  4172  							},
  4173  							{
  4174  								Name:     "bam",
  4175  								Type:     "http",
  4176  								Command:  "foo",
  4177  								Args:     []string{"foo"},
  4178  								Path:     "foo",
  4179  								Protocol: "http",
  4180  								Interval: 1 * time.Second,
  4181  								Timeout:  1 * time.Second,
  4182  							},
  4183  						},
  4184  					},
  4185  				},
  4186  			},
  4187  			Expected: &TaskDiff{
  4188  				Type: DiffTypeEdited,
  4189  				Objects: []*ObjectDiff{
  4190  					{
  4191  						Type: DiffTypeEdited,
  4192  						Name: "Service",
  4193  						Objects: []*ObjectDiff{
  4194  							{
  4195  								Type: DiffTypeEdited,
  4196  								Name: "Check",
  4197  								Fields: []*FieldDiff{
  4198  									{
  4199  										Type: DiffTypeEdited,
  4200  										Name: "Type",
  4201  										Old:  "http",
  4202  										New:  "tcp",
  4203  									},
  4204  								},
  4205  								Objects: []*ObjectDiff{
  4206  									{
  4207  										Type: DiffTypeAdded,
  4208  										Name: "Header",
  4209  										Fields: []*FieldDiff{
  4210  											{
  4211  												Type: DiffTypeAdded,
  4212  												Name: "Eggs[0]",
  4213  												Old:  "",
  4214  												New:  "spam",
  4215  											},
  4216  										},
  4217  									},
  4218  								},
  4219  							},
  4220  							{
  4221  								Type: DiffTypeAdded,
  4222  								Name: "Check",
  4223  								Fields: []*FieldDiff{
  4224  									{
  4225  										Type: DiffTypeAdded,
  4226  										Name: "Command",
  4227  										Old:  "",
  4228  										New:  "foo",
  4229  									},
  4230  									{
  4231  										Type: DiffTypeAdded,
  4232  										Name: "GRPCUseTLS",
  4233  										Old:  "",
  4234  										New:  "false",
  4235  									},
  4236  									{
  4237  										Type: DiffTypeAdded,
  4238  										Name: "Interval",
  4239  										Old:  "",
  4240  										New:  "1000000000",
  4241  									},
  4242  									{
  4243  										Type: DiffTypeAdded,
  4244  										Name: "Name",
  4245  										Old:  "",
  4246  										New:  "bam",
  4247  									},
  4248  									{
  4249  										Type: DiffTypeAdded,
  4250  										Name: "Path",
  4251  										Old:  "",
  4252  										New:  "foo",
  4253  									},
  4254  									{
  4255  										Type: DiffTypeAdded,
  4256  										Name: "Protocol",
  4257  										Old:  "",
  4258  										New:  "http",
  4259  									},
  4260  									{
  4261  										Type: DiffTypeAdded,
  4262  										Name: "TLSSkipVerify",
  4263  										Old:  "",
  4264  										New:  "false",
  4265  									},
  4266  									{
  4267  										Type: DiffTypeAdded,
  4268  										Name: "Timeout",
  4269  										Old:  "",
  4270  										New:  "1000000000",
  4271  									},
  4272  									{
  4273  										Type: DiffTypeAdded,
  4274  										Name: "Type",
  4275  										Old:  "",
  4276  										New:  "http",
  4277  									},
  4278  								},
  4279  							},
  4280  							{
  4281  								Type: DiffTypeDeleted,
  4282  								Name: "Check",
  4283  								Fields: []*FieldDiff{
  4284  									{
  4285  										Type: DiffTypeDeleted,
  4286  										Name: "Command",
  4287  										Old:  "foo",
  4288  										New:  "",
  4289  									},
  4290  									{
  4291  										Type: DiffTypeDeleted,
  4292  										Name: "GRPCUseTLS",
  4293  										Old:  "false",
  4294  										New:  "",
  4295  									},
  4296  									{
  4297  										Type: DiffTypeDeleted,
  4298  										Name: "Interval",
  4299  										Old:  "1000000000",
  4300  										New:  "",
  4301  									},
  4302  									{
  4303  										Type: DiffTypeDeleted,
  4304  										Name: "Name",
  4305  										Old:  "foo",
  4306  										New:  "",
  4307  									},
  4308  									{
  4309  										Type: DiffTypeDeleted,
  4310  										Name: "Path",
  4311  										Old:  "foo",
  4312  										New:  "",
  4313  									},
  4314  									{
  4315  										Type: DiffTypeDeleted,
  4316  										Name: "Protocol",
  4317  										Old:  "http",
  4318  										New:  "",
  4319  									},
  4320  									{
  4321  										Type: DiffTypeDeleted,
  4322  										Name: "TLSSkipVerify",
  4323  										Old:  "false",
  4324  										New:  "",
  4325  									},
  4326  									{
  4327  										Type: DiffTypeDeleted,
  4328  										Name: "Timeout",
  4329  										Old:  "1000000000",
  4330  										New:  "",
  4331  									},
  4332  									{
  4333  										Type: DiffTypeDeleted,
  4334  										Name: "Type",
  4335  										Old:  "http",
  4336  										New:  "",
  4337  									},
  4338  								},
  4339  								Objects: []*ObjectDiff{
  4340  									{
  4341  										Type: DiffTypeDeleted,
  4342  										Name: "Header",
  4343  										Fields: []*FieldDiff{
  4344  											{
  4345  												Type: DiffTypeDeleted,
  4346  												Name: "Foo[0]",
  4347  												Old:  "bar",
  4348  											},
  4349  										},
  4350  									},
  4351  								},
  4352  							},
  4353  						},
  4354  					},
  4355  				},
  4356  			},
  4357  		},
  4358  		{
  4359  			Name:       "Service Checks edited with context",
  4360  			Contextual: true,
  4361  			Old: &Task{
  4362  				Services: []*Service{
  4363  					{
  4364  						Name: "foo",
  4365  						Checks: []*ServiceCheck{
  4366  							{
  4367  								Name:          "foo",
  4368  								Type:          "http",
  4369  								Command:       "foo",
  4370  								Args:          []string{"foo"},
  4371  								Path:          "foo",
  4372  								Protocol:      "http",
  4373  								Interval:      1 * time.Second,
  4374  								Timeout:       1 * time.Second,
  4375  								InitialStatus: "critical",
  4376  								Header: map[string][]string{
  4377  									"Foo": {"bar"},
  4378  								},
  4379  							},
  4380  						},
  4381  					},
  4382  				},
  4383  			},
  4384  			New: &Task{
  4385  				Services: []*Service{
  4386  					{
  4387  						Name: "foo",
  4388  						Checks: []*ServiceCheck{
  4389  							{
  4390  								Name:          "foo",
  4391  								Type:          "tcp",
  4392  								Command:       "foo",
  4393  								Args:          []string{"foo"},
  4394  								Path:          "foo",
  4395  								Protocol:      "http",
  4396  								Interval:      1 * time.Second,
  4397  								Timeout:       1 * time.Second,
  4398  								InitialStatus: "passing",
  4399  								Method:        "POST",
  4400  								Header: map[string][]string{
  4401  									"Foo":  {"bar", "baz"},
  4402  									"Eggs": {"spam"},
  4403  								},
  4404  							},
  4405  						},
  4406  					},
  4407  				},
  4408  			},
  4409  			Expected: &TaskDiff{
  4410  				Type: DiffTypeEdited,
  4411  				Objects: []*ObjectDiff{
  4412  					{
  4413  						Type: DiffTypeEdited,
  4414  						Name: "Service",
  4415  						Fields: []*FieldDiff{
  4416  							{
  4417  								Type: DiffTypeNone,
  4418  								Name: "AddressMode",
  4419  								Old:  "",
  4420  								New:  "",
  4421  							},
  4422  							{
  4423  								Type: DiffTypeNone,
  4424  								Name: "Name",
  4425  								Old:  "foo",
  4426  								New:  "foo",
  4427  							},
  4428  							{
  4429  								Type: DiffTypeNone,
  4430  								Name: "PortLabel",
  4431  								Old:  "",
  4432  								New:  "",
  4433  							},
  4434  						},
  4435  						Objects: []*ObjectDiff{
  4436  							{
  4437  								Type: DiffTypeEdited,
  4438  								Name: "Check",
  4439  								Fields: []*FieldDiff{
  4440  									{
  4441  										Type: DiffTypeNone,
  4442  										Name: "AddressMode",
  4443  										Old:  "",
  4444  										New:  "",
  4445  									},
  4446  									{
  4447  										Type: DiffTypeNone,
  4448  										Name: "Command",
  4449  										Old:  "foo",
  4450  										New:  "foo",
  4451  									},
  4452  									{
  4453  										Type: DiffTypeNone,
  4454  										Name: "GRPCService",
  4455  										Old:  "",
  4456  										New:  "",
  4457  									},
  4458  									{
  4459  										Type: DiffTypeNone,
  4460  										Name: "GRPCUseTLS",
  4461  										Old:  "false",
  4462  										New:  "false",
  4463  									},
  4464  									{
  4465  										Type: DiffTypeEdited,
  4466  										Name: "InitialStatus",
  4467  										Old:  "critical",
  4468  										New:  "passing",
  4469  									},
  4470  									{
  4471  										Type: DiffTypeNone,
  4472  										Name: "Interval",
  4473  										Old:  "1000000000",
  4474  										New:  "1000000000",
  4475  									},
  4476  									{
  4477  										Type: DiffTypeAdded,
  4478  										Name: "Method",
  4479  										Old:  "",
  4480  										New:  "POST",
  4481  									},
  4482  									{
  4483  										Type: DiffTypeNone,
  4484  										Name: "Name",
  4485  										Old:  "foo",
  4486  										New:  "foo",
  4487  									},
  4488  									{
  4489  										Type: DiffTypeNone,
  4490  										Name: "Path",
  4491  										Old:  "foo",
  4492  										New:  "foo",
  4493  									},
  4494  									{
  4495  										Type: DiffTypeNone,
  4496  										Name: "PortLabel",
  4497  										Old:  "",
  4498  										New:  "",
  4499  									},
  4500  									{
  4501  										Type: DiffTypeNone,
  4502  										Name: "Protocol",
  4503  										Old:  "http",
  4504  										New:  "http",
  4505  									},
  4506  									{
  4507  										Type: DiffTypeNone,
  4508  										Name: "TLSSkipVerify",
  4509  										Old:  "false",
  4510  										New:  "false",
  4511  									},
  4512  									{
  4513  										Type: DiffTypeNone,
  4514  										Name: "Timeout",
  4515  										Old:  "1000000000",
  4516  										New:  "1000000000",
  4517  									},
  4518  									{
  4519  										Type: DiffTypeEdited,
  4520  										Name: "Type",
  4521  										Old:  "http",
  4522  										New:  "tcp",
  4523  									},
  4524  								},
  4525  								Objects: []*ObjectDiff{
  4526  									{
  4527  										Type: DiffTypeEdited,
  4528  										Name: "Header",
  4529  										Fields: []*FieldDiff{
  4530  											{
  4531  												Type: DiffTypeAdded,
  4532  												Name: "Eggs[0]",
  4533  												Old:  "",
  4534  												New:  "spam",
  4535  											},
  4536  											{
  4537  												Type: DiffTypeNone,
  4538  												Name: "Foo[0]",
  4539  												Old:  "bar",
  4540  												New:  "bar",
  4541  											},
  4542  											{
  4543  												Type: DiffTypeAdded,
  4544  												Name: "Foo[1]",
  4545  												Old:  "",
  4546  												New:  "baz",
  4547  											},
  4548  										},
  4549  									},
  4550  								},
  4551  							},
  4552  						},
  4553  					},
  4554  				},
  4555  			},
  4556  		},
  4557  		{
  4558  			Name: "CheckRestart edited",
  4559  			Old: &Task{
  4560  				Services: []*Service{
  4561  					{
  4562  						Name: "foo",
  4563  						Checks: []*ServiceCheck{
  4564  							{
  4565  								Name:     "foo",
  4566  								Type:     "http",
  4567  								Command:  "foo",
  4568  								Args:     []string{"foo"},
  4569  								Path:     "foo",
  4570  								Protocol: "http",
  4571  								Interval: 1 * time.Second,
  4572  								Timeout:  1 * time.Second,
  4573  							},
  4574  							{
  4575  								Name:     "bar",
  4576  								Type:     "http",
  4577  								Command:  "foo",
  4578  								Args:     []string{"foo"},
  4579  								Path:     "foo",
  4580  								Protocol: "http",
  4581  								Interval: 1 * time.Second,
  4582  								Timeout:  1 * time.Second,
  4583  								CheckRestart: &CheckRestart{
  4584  									Limit: 2,
  4585  									Grace: 2 * time.Second,
  4586  								},
  4587  							},
  4588  							{
  4589  								Name:     "baz",
  4590  								Type:     "http",
  4591  								Command:  "foo",
  4592  								Args:     []string{"foo"},
  4593  								Path:     "foo",
  4594  								Protocol: "http",
  4595  								Interval: 1 * time.Second,
  4596  								Timeout:  1 * time.Second,
  4597  								CheckRestart: &CheckRestart{
  4598  									Limit: 3,
  4599  									Grace: 3 * time.Second,
  4600  								},
  4601  							},
  4602  						},
  4603  					},
  4604  				},
  4605  			},
  4606  			New: &Task{
  4607  				Services: []*Service{
  4608  					{
  4609  						Name: "foo",
  4610  						Checks: []*ServiceCheck{
  4611  							{
  4612  								Name:     "foo",
  4613  								Type:     "http",
  4614  								Command:  "foo",
  4615  								Args:     []string{"foo"},
  4616  								Path:     "foo",
  4617  								Protocol: "http",
  4618  								Interval: 1 * time.Second,
  4619  								Timeout:  1 * time.Second,
  4620  								CheckRestart: &CheckRestart{
  4621  									Limit: 1,
  4622  									Grace: 1 * time.Second,
  4623  								},
  4624  							},
  4625  							{
  4626  								Name:     "bar",
  4627  								Type:     "http",
  4628  								Command:  "foo",
  4629  								Args:     []string{"foo"},
  4630  								Path:     "foo",
  4631  								Protocol: "http",
  4632  								Interval: 1 * time.Second,
  4633  								Timeout:  1 * time.Second,
  4634  							},
  4635  							{
  4636  								Name:     "baz",
  4637  								Type:     "http",
  4638  								Command:  "foo",
  4639  								Args:     []string{"foo"},
  4640  								Path:     "foo",
  4641  								Protocol: "http",
  4642  								Interval: 1 * time.Second,
  4643  								Timeout:  1 * time.Second,
  4644  								CheckRestart: &CheckRestart{
  4645  									Limit: 4,
  4646  									Grace: 4 * time.Second,
  4647  								},
  4648  							},
  4649  						},
  4650  					},
  4651  				},
  4652  			},
  4653  			Expected: &TaskDiff{
  4654  				Type: DiffTypeEdited,
  4655  				Objects: []*ObjectDiff{
  4656  					{
  4657  						Type: DiffTypeEdited,
  4658  						Name: "Service",
  4659  						Objects: []*ObjectDiff{
  4660  							{
  4661  								Type: DiffTypeEdited,
  4662  								Name: "Check",
  4663  								Objects: []*ObjectDiff{
  4664  									{
  4665  										Type: DiffTypeEdited,
  4666  										Name: "CheckRestart",
  4667  										Fields: []*FieldDiff{
  4668  											{
  4669  												Type: DiffTypeEdited,
  4670  												Name: "Grace",
  4671  												Old:  "3000000000",
  4672  												New:  "4000000000",
  4673  											},
  4674  											{
  4675  												Type: DiffTypeEdited,
  4676  												Name: "Limit",
  4677  												Old:  "3",
  4678  												New:  "4",
  4679  											},
  4680  										},
  4681  									},
  4682  								},
  4683  							},
  4684  							{
  4685  								Type: DiffTypeEdited,
  4686  								Name: "Check",
  4687  								Objects: []*ObjectDiff{
  4688  									{
  4689  										Type: DiffTypeAdded,
  4690  										Name: "CheckRestart",
  4691  										Fields: []*FieldDiff{
  4692  											{
  4693  												Type: DiffTypeAdded,
  4694  												Name: "Grace",
  4695  												New:  "1000000000",
  4696  											},
  4697  											{
  4698  												Type: DiffTypeAdded,
  4699  												Name: "IgnoreWarnings",
  4700  												New:  "false",
  4701  											},
  4702  											{
  4703  												Type: DiffTypeAdded,
  4704  												Name: "Limit",
  4705  												New:  "1",
  4706  											},
  4707  										},
  4708  									},
  4709  								},
  4710  							},
  4711  							{
  4712  								Type: DiffTypeEdited,
  4713  								Name: "Check",
  4714  								Objects: []*ObjectDiff{
  4715  									{
  4716  										Type: DiffTypeDeleted,
  4717  										Name: "CheckRestart",
  4718  										Fields: []*FieldDiff{
  4719  											{
  4720  												Type: DiffTypeDeleted,
  4721  												Name: "Grace",
  4722  												Old:  "2000000000",
  4723  											},
  4724  											{
  4725  												Type: DiffTypeDeleted,
  4726  												Name: "IgnoreWarnings",
  4727  												Old:  "false",
  4728  											},
  4729  											{
  4730  												Type: DiffTypeDeleted,
  4731  												Name: "Limit",
  4732  												Old:  "2",
  4733  											},
  4734  										},
  4735  									},
  4736  								},
  4737  							},
  4738  						},
  4739  					},
  4740  				},
  4741  			},
  4742  		},
  4743  		{
  4744  			Name: "Vault added",
  4745  			Old:  &Task{},
  4746  			New: &Task{
  4747  				Vault: &Vault{
  4748  					Policies:     []string{"foo", "bar"},
  4749  					Env:          true,
  4750  					ChangeMode:   "signal",
  4751  					ChangeSignal: "SIGUSR1",
  4752  				},
  4753  			},
  4754  			Expected: &TaskDiff{
  4755  				Type: DiffTypeEdited,
  4756  				Objects: []*ObjectDiff{
  4757  					{
  4758  						Type: DiffTypeAdded,
  4759  						Name: "Vault",
  4760  						Fields: []*FieldDiff{
  4761  							{
  4762  								Type: DiffTypeAdded,
  4763  								Name: "ChangeMode",
  4764  								Old:  "",
  4765  								New:  "signal",
  4766  							},
  4767  							{
  4768  								Type: DiffTypeAdded,
  4769  								Name: "ChangeSignal",
  4770  								Old:  "",
  4771  								New:  "SIGUSR1",
  4772  							},
  4773  							{
  4774  								Type: DiffTypeAdded,
  4775  								Name: "Env",
  4776  								Old:  "",
  4777  								New:  "true",
  4778  							},
  4779  						},
  4780  						Objects: []*ObjectDiff{
  4781  							{
  4782  								Type: DiffTypeAdded,
  4783  								Name: "Policies",
  4784  								Fields: []*FieldDiff{
  4785  									{
  4786  										Type: DiffTypeAdded,
  4787  										Name: "Policies",
  4788  										Old:  "",
  4789  										New:  "bar",
  4790  									},
  4791  									{
  4792  										Type: DiffTypeAdded,
  4793  										Name: "Policies",
  4794  										Old:  "",
  4795  										New:  "foo",
  4796  									},
  4797  								},
  4798  							},
  4799  						},
  4800  					},
  4801  				},
  4802  			},
  4803  		},
  4804  		{
  4805  			Name: "Vault deleted",
  4806  			Old: &Task{
  4807  				Vault: &Vault{
  4808  					Policies:     []string{"foo", "bar"},
  4809  					Env:          true,
  4810  					ChangeMode:   "signal",
  4811  					ChangeSignal: "SIGUSR1",
  4812  				},
  4813  			},
  4814  			New: &Task{},
  4815  			Expected: &TaskDiff{
  4816  				Type: DiffTypeEdited,
  4817  				Objects: []*ObjectDiff{
  4818  					{
  4819  						Type: DiffTypeDeleted,
  4820  						Name: "Vault",
  4821  						Fields: []*FieldDiff{
  4822  							{
  4823  								Type: DiffTypeDeleted,
  4824  								Name: "ChangeMode",
  4825  								Old:  "signal",
  4826  								New:  "",
  4827  							},
  4828  							{
  4829  								Type: DiffTypeDeleted,
  4830  								Name: "ChangeSignal",
  4831  								Old:  "SIGUSR1",
  4832  								New:  "",
  4833  							},
  4834  							{
  4835  								Type: DiffTypeDeleted,
  4836  								Name: "Env",
  4837  								Old:  "true",
  4838  								New:  "",
  4839  							},
  4840  						},
  4841  						Objects: []*ObjectDiff{
  4842  							{
  4843  								Type: DiffTypeDeleted,
  4844  								Name: "Policies",
  4845  								Fields: []*FieldDiff{
  4846  									{
  4847  										Type: DiffTypeDeleted,
  4848  										Name: "Policies",
  4849  										Old:  "bar",
  4850  										New:  "",
  4851  									},
  4852  									{
  4853  										Type: DiffTypeDeleted,
  4854  										Name: "Policies",
  4855  										Old:  "foo",
  4856  										New:  "",
  4857  									},
  4858  								},
  4859  							},
  4860  						},
  4861  					},
  4862  				},
  4863  			},
  4864  		},
  4865  		{
  4866  			Name: "Vault edited",
  4867  			Old: &Task{
  4868  				Vault: &Vault{
  4869  					Policies:     []string{"foo", "bar"},
  4870  					Env:          true,
  4871  					ChangeMode:   "signal",
  4872  					ChangeSignal: "SIGUSR1",
  4873  				},
  4874  			},
  4875  			New: &Task{
  4876  				Vault: &Vault{
  4877  					Policies:     []string{"bar", "baz"},
  4878  					Env:          false,
  4879  					ChangeMode:   "restart",
  4880  					ChangeSignal: "foo",
  4881  				},
  4882  			},
  4883  			Expected: &TaskDiff{
  4884  				Type: DiffTypeEdited,
  4885  				Objects: []*ObjectDiff{
  4886  					{
  4887  						Type: DiffTypeEdited,
  4888  						Name: "Vault",
  4889  						Fields: []*FieldDiff{
  4890  							{
  4891  								Type: DiffTypeEdited,
  4892  								Name: "ChangeMode",
  4893  								Old:  "signal",
  4894  								New:  "restart",
  4895  							},
  4896  							{
  4897  								Type: DiffTypeEdited,
  4898  								Name: "ChangeSignal",
  4899  								Old:  "SIGUSR1",
  4900  								New:  "foo",
  4901  							},
  4902  							{
  4903  								Type: DiffTypeEdited,
  4904  								Name: "Env",
  4905  								Old:  "true",
  4906  								New:  "false",
  4907  							},
  4908  						},
  4909  						Objects: []*ObjectDiff{
  4910  							{
  4911  								Type: DiffTypeEdited,
  4912  								Name: "Policies",
  4913  								Fields: []*FieldDiff{
  4914  									{
  4915  										Type: DiffTypeAdded,
  4916  										Name: "Policies",
  4917  										Old:  "",
  4918  										New:  "baz",
  4919  									},
  4920  									{
  4921  										Type: DiffTypeDeleted,
  4922  										Name: "Policies",
  4923  										Old:  "foo",
  4924  										New:  "",
  4925  									},
  4926  								},
  4927  							},
  4928  						},
  4929  					},
  4930  				},
  4931  			},
  4932  		},
  4933  		{
  4934  			Name:       "Vault edited with context",
  4935  			Contextual: true,
  4936  			Old: &Task{
  4937  				Vault: &Vault{
  4938  					Policies:     []string{"foo", "bar"},
  4939  					Env:          true,
  4940  					ChangeMode:   "signal",
  4941  					ChangeSignal: "SIGUSR1",
  4942  				},
  4943  			},
  4944  			New: &Task{
  4945  				Vault: &Vault{
  4946  					Policies:     []string{"bar", "baz"},
  4947  					Env:          true,
  4948  					ChangeMode:   "signal",
  4949  					ChangeSignal: "SIGUSR1",
  4950  				},
  4951  			},
  4952  			Expected: &TaskDiff{
  4953  				Type: DiffTypeEdited,
  4954  				Objects: []*ObjectDiff{
  4955  					{
  4956  						Type: DiffTypeEdited,
  4957  						Name: "Vault",
  4958  						Fields: []*FieldDiff{
  4959  							{
  4960  								Type: DiffTypeNone,
  4961  								Name: "ChangeMode",
  4962  								Old:  "signal",
  4963  								New:  "signal",
  4964  							},
  4965  							{
  4966  								Type: DiffTypeNone,
  4967  								Name: "ChangeSignal",
  4968  								Old:  "SIGUSR1",
  4969  								New:  "SIGUSR1",
  4970  							},
  4971  							{
  4972  								Type: DiffTypeNone,
  4973  								Name: "Env",
  4974  								Old:  "true",
  4975  								New:  "true",
  4976  							},
  4977  						},
  4978  						Objects: []*ObjectDiff{
  4979  							{
  4980  								Type: DiffTypeEdited,
  4981  								Name: "Policies",
  4982  								Fields: []*FieldDiff{
  4983  									{
  4984  										Type: DiffTypeAdded,
  4985  										Name: "Policies",
  4986  										Old:  "",
  4987  										New:  "baz",
  4988  									},
  4989  									{
  4990  										Type: DiffTypeNone,
  4991  										Name: "Policies",
  4992  										Old:  "bar",
  4993  										New:  "bar",
  4994  									},
  4995  									{
  4996  										Type: DiffTypeDeleted,
  4997  										Name: "Policies",
  4998  										Old:  "foo",
  4999  										New:  "",
  5000  									},
  5001  								},
  5002  							},
  5003  						},
  5004  					},
  5005  				},
  5006  			},
  5007  		},
  5008  		{
  5009  			Name: "Template edited",
  5010  			Old: &Task{
  5011  				Templates: []*Template{
  5012  					{
  5013  						SourcePath:   "foo",
  5014  						DestPath:     "bar",
  5015  						EmbeddedTmpl: "baz",
  5016  						ChangeMode:   "bam",
  5017  						ChangeSignal: "SIGHUP",
  5018  						Splay:        1,
  5019  						Perms:        "0644",
  5020  						VaultGrace:   3 * time.Second,
  5021  					},
  5022  					{
  5023  						SourcePath:   "foo2",
  5024  						DestPath:     "bar2",
  5025  						EmbeddedTmpl: "baz2",
  5026  						ChangeMode:   "bam2",
  5027  						ChangeSignal: "SIGHUP2",
  5028  						Splay:        2,
  5029  						Perms:        "0666",
  5030  						Envvars:      true,
  5031  						VaultGrace:   5 * time.Second,
  5032  					},
  5033  				},
  5034  			},
  5035  			New: &Task{
  5036  				Templates: []*Template{
  5037  					{
  5038  						SourcePath:   "foo",
  5039  						DestPath:     "bar",
  5040  						EmbeddedTmpl: "baz",
  5041  						ChangeMode:   "bam",
  5042  						ChangeSignal: "SIGHUP",
  5043  						Splay:        1,
  5044  						Perms:        "0644",
  5045  						VaultGrace:   3 * time.Second,
  5046  					},
  5047  					{
  5048  						SourcePath:   "foo3",
  5049  						DestPath:     "bar3",
  5050  						EmbeddedTmpl: "baz3",
  5051  						ChangeMode:   "bam3",
  5052  						ChangeSignal: "SIGHUP3",
  5053  						Splay:        3,
  5054  						Perms:        "0776",
  5055  						VaultGrace:   10 * time.Second,
  5056  					},
  5057  				},
  5058  			},
  5059  			Expected: &TaskDiff{
  5060  				Type: DiffTypeEdited,
  5061  				Objects: []*ObjectDiff{
  5062  					{
  5063  						Type: DiffTypeAdded,
  5064  						Name: "Template",
  5065  						Fields: []*FieldDiff{
  5066  							{
  5067  								Type: DiffTypeAdded,
  5068  								Name: "ChangeMode",
  5069  								Old:  "",
  5070  								New:  "bam3",
  5071  							},
  5072  							{
  5073  								Type: DiffTypeAdded,
  5074  								Name: "ChangeSignal",
  5075  								Old:  "",
  5076  								New:  "SIGHUP3",
  5077  							},
  5078  							{
  5079  								Type: DiffTypeAdded,
  5080  								Name: "DestPath",
  5081  								Old:  "",
  5082  								New:  "bar3",
  5083  							},
  5084  							{
  5085  								Type: DiffTypeAdded,
  5086  								Name: "EmbeddedTmpl",
  5087  								Old:  "",
  5088  								New:  "baz3",
  5089  							},
  5090  							{
  5091  								Type: DiffTypeAdded,
  5092  								Name: "Envvars",
  5093  								Old:  "",
  5094  								New:  "false",
  5095  							},
  5096  							{
  5097  								Type: DiffTypeAdded,
  5098  								Name: "Perms",
  5099  								Old:  "",
  5100  								New:  "0776",
  5101  							},
  5102  							{
  5103  								Type: DiffTypeAdded,
  5104  								Name: "SourcePath",
  5105  								Old:  "",
  5106  								New:  "foo3",
  5107  							},
  5108  							{
  5109  								Type: DiffTypeAdded,
  5110  								Name: "Splay",
  5111  								Old:  "",
  5112  								New:  "3",
  5113  							},
  5114  							{
  5115  								Type: DiffTypeAdded,
  5116  								Name: "VaultGrace",
  5117  								Old:  "",
  5118  								New:  "10000000000",
  5119  							},
  5120  						},
  5121  					},
  5122  					{
  5123  						Type: DiffTypeDeleted,
  5124  						Name: "Template",
  5125  						Fields: []*FieldDiff{
  5126  							{
  5127  								Type: DiffTypeDeleted,
  5128  								Name: "ChangeMode",
  5129  								Old:  "bam2",
  5130  								New:  "",
  5131  							},
  5132  							{
  5133  								Type: DiffTypeDeleted,
  5134  								Name: "ChangeSignal",
  5135  								Old:  "SIGHUP2",
  5136  								New:  "",
  5137  							},
  5138  							{
  5139  								Type: DiffTypeDeleted,
  5140  								Name: "DestPath",
  5141  								Old:  "bar2",
  5142  								New:  "",
  5143  							},
  5144  							{
  5145  								Type: DiffTypeDeleted,
  5146  								Name: "EmbeddedTmpl",
  5147  								Old:  "baz2",
  5148  								New:  "",
  5149  							},
  5150  							{
  5151  								Type: DiffTypeDeleted,
  5152  								Name: "Envvars",
  5153  								Old:  "true",
  5154  								New:  "",
  5155  							},
  5156  							{
  5157  								Type: DiffTypeDeleted,
  5158  								Name: "Perms",
  5159  								Old:  "0666",
  5160  								New:  "",
  5161  							},
  5162  							{
  5163  								Type: DiffTypeDeleted,
  5164  								Name: "SourcePath",
  5165  								Old:  "foo2",
  5166  								New:  "",
  5167  							},
  5168  							{
  5169  								Type: DiffTypeDeleted,
  5170  								Name: "Splay",
  5171  								Old:  "2",
  5172  								New:  "",
  5173  							},
  5174  							{
  5175  								Type: DiffTypeDeleted,
  5176  								Name: "VaultGrace",
  5177  								Old:  "5000000000",
  5178  								New:  "",
  5179  							},
  5180  						},
  5181  					},
  5182  				},
  5183  			},
  5184  		},
  5185  		{
  5186  			Name: "DispatchPayload added",
  5187  			Old:  &Task{},
  5188  			New: &Task{
  5189  				DispatchPayload: &DispatchPayloadConfig{
  5190  					File: "foo",
  5191  				},
  5192  			},
  5193  			Expected: &TaskDiff{
  5194  				Type: DiffTypeEdited,
  5195  				Objects: []*ObjectDiff{
  5196  					{
  5197  						Type: DiffTypeAdded,
  5198  						Name: "DispatchPayload",
  5199  						Fields: []*FieldDiff{
  5200  							{
  5201  								Type: DiffTypeAdded,
  5202  								Name: "File",
  5203  								Old:  "",
  5204  								New:  "foo",
  5205  							},
  5206  						},
  5207  					},
  5208  				},
  5209  			},
  5210  		},
  5211  		{
  5212  			Name: "DispatchPayload deleted",
  5213  			Old: &Task{
  5214  				DispatchPayload: &DispatchPayloadConfig{
  5215  					File: "foo",
  5216  				},
  5217  			},
  5218  			New: &Task{},
  5219  			Expected: &TaskDiff{
  5220  				Type: DiffTypeEdited,
  5221  				Objects: []*ObjectDiff{
  5222  					{
  5223  						Type: DiffTypeDeleted,
  5224  						Name: "DispatchPayload",
  5225  						Fields: []*FieldDiff{
  5226  							{
  5227  								Type: DiffTypeDeleted,
  5228  								Name: "File",
  5229  								Old:  "foo",
  5230  								New:  "",
  5231  							},
  5232  						},
  5233  					},
  5234  				},
  5235  			},
  5236  		},
  5237  		{
  5238  			Name: "Dispatch payload edited",
  5239  			Old: &Task{
  5240  				DispatchPayload: &DispatchPayloadConfig{
  5241  					File: "foo",
  5242  				},
  5243  			},
  5244  			New: &Task{
  5245  				DispatchPayload: &DispatchPayloadConfig{
  5246  					File: "bar",
  5247  				},
  5248  			},
  5249  			Expected: &TaskDiff{
  5250  				Type: DiffTypeEdited,
  5251  				Objects: []*ObjectDiff{
  5252  					{
  5253  						Type: DiffTypeEdited,
  5254  						Name: "DispatchPayload",
  5255  						Fields: []*FieldDiff{
  5256  							{
  5257  								Type: DiffTypeEdited,
  5258  								Name: "File",
  5259  								Old:  "foo",
  5260  								New:  "bar",
  5261  							},
  5262  						},
  5263  					},
  5264  				},
  5265  			},
  5266  		},
  5267  		{
  5268  			// Place holder for if more fields are added
  5269  			Name:       "DispatchPayload edited with context",
  5270  			Contextual: true,
  5271  			Old: &Task{
  5272  				DispatchPayload: &DispatchPayloadConfig{
  5273  					File: "foo",
  5274  				},
  5275  			},
  5276  			New: &Task{
  5277  				DispatchPayload: &DispatchPayloadConfig{
  5278  					File: "bar",
  5279  				},
  5280  			},
  5281  			Expected: &TaskDiff{
  5282  				Type: DiffTypeEdited,
  5283  				Objects: []*ObjectDiff{
  5284  					{
  5285  						Type: DiffTypeEdited,
  5286  						Name: "DispatchPayload",
  5287  						Fields: []*FieldDiff{
  5288  							{
  5289  								Type: DiffTypeEdited,
  5290  								Name: "File",
  5291  								Old:  "foo",
  5292  								New:  "bar",
  5293  							},
  5294  						},
  5295  					},
  5296  				},
  5297  			},
  5298  		},
  5299  	}
  5300  
  5301  	for i, c := range cases {
  5302  		t.Run(c.Name, func(t *testing.T) {
  5303  			actual, err := c.Old.Diff(c.New, c.Contextual)
  5304  			if c.Error && err == nil {
  5305  				t.Fatalf("case %d: expected errored", i+1)
  5306  			} else if err != nil {
  5307  				if !c.Error {
  5308  					t.Fatalf("case %d: errored %#v", i+1, err)
  5309  				} else {
  5310  					return
  5311  				}
  5312  			}
  5313  
  5314  			if !reflect.DeepEqual(actual, c.Expected) {
  5315  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  5316  					i+1, actual, c.Expected)
  5317  			}
  5318  		})
  5319  	}
  5320  }