github.com/quite/nomad@v0.8.6/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  			// Task groups edited
   758  			Old: &Job{
   759  				TaskGroups: []*TaskGroup{
   760  					{
   761  						Name:  "foo",
   762  						Count: 1,
   763  					},
   764  					{
   765  						Name:  "bar",
   766  						Count: 1,
   767  					},
   768  					{
   769  						Name:  "baz",
   770  						Count: 1,
   771  					},
   772  				},
   773  			},
   774  			New: &Job{
   775  				TaskGroups: []*TaskGroup{
   776  					{
   777  						Name:  "bar",
   778  						Count: 1,
   779  					},
   780  					{
   781  						Name:  "baz",
   782  						Count: 2,
   783  					},
   784  					{
   785  						Name:  "bam",
   786  						Count: 1,
   787  					},
   788  				},
   789  			},
   790  			Expected: &JobDiff{
   791  				Type: DiffTypeEdited,
   792  				TaskGroups: []*TaskGroupDiff{
   793  					{
   794  						Type: DiffTypeAdded,
   795  						Name: "bam",
   796  						Fields: []*FieldDiff{
   797  							{
   798  								Type: DiffTypeAdded,
   799  								Name: "Count",
   800  								Old:  "",
   801  								New:  "1",
   802  							},
   803  						},
   804  					},
   805  					{
   806  						Type: DiffTypeNone,
   807  						Name: "bar",
   808  					},
   809  					{
   810  						Type: DiffTypeEdited,
   811  						Name: "baz",
   812  						Fields: []*FieldDiff{
   813  							{
   814  								Type: DiffTypeEdited,
   815  								Name: "Count",
   816  								Old:  "1",
   817  								New:  "2",
   818  							},
   819  						},
   820  					},
   821  					{
   822  						Type: DiffTypeDeleted,
   823  						Name: "foo",
   824  						Fields: []*FieldDiff{
   825  							{
   826  								Type: DiffTypeDeleted,
   827  								Name: "Count",
   828  								Old:  "1",
   829  								New:  "",
   830  							},
   831  						},
   832  					},
   833  				},
   834  			},
   835  		},
   836  		{
   837  			// Parameterized Job added
   838  			Old: &Job{},
   839  			New: &Job{
   840  				ParameterizedJob: &ParameterizedJobConfig{
   841  					Payload:      DispatchPayloadRequired,
   842  					MetaOptional: []string{"foo"},
   843  					MetaRequired: []string{"bar"},
   844  				},
   845  			},
   846  			Expected: &JobDiff{
   847  				Type: DiffTypeEdited,
   848  				Objects: []*ObjectDiff{
   849  					{
   850  						Type: DiffTypeAdded,
   851  						Name: "ParameterizedJob",
   852  						Fields: []*FieldDiff{
   853  							{
   854  								Type: DiffTypeAdded,
   855  								Name: "Payload",
   856  								Old:  "",
   857  								New:  DispatchPayloadRequired,
   858  							},
   859  						},
   860  						Objects: []*ObjectDiff{
   861  							{
   862  								Type: DiffTypeAdded,
   863  								Name: "MetaOptional",
   864  								Fields: []*FieldDiff{
   865  									{
   866  										Type: DiffTypeAdded,
   867  										Name: "MetaOptional",
   868  										Old:  "",
   869  										New:  "foo",
   870  									},
   871  								},
   872  							},
   873  							{
   874  								Type: DiffTypeAdded,
   875  								Name: "MetaRequired",
   876  								Fields: []*FieldDiff{
   877  									{
   878  										Type: DiffTypeAdded,
   879  										Name: "MetaRequired",
   880  										Old:  "",
   881  										New:  "bar",
   882  									},
   883  								},
   884  							},
   885  						},
   886  					},
   887  				},
   888  			},
   889  		},
   890  		{
   891  			// Parameterized Job deleted
   892  			Old: &Job{
   893  				ParameterizedJob: &ParameterizedJobConfig{
   894  					Payload:      DispatchPayloadRequired,
   895  					MetaOptional: []string{"foo"},
   896  					MetaRequired: []string{"bar"},
   897  				},
   898  			},
   899  			New: &Job{},
   900  			Expected: &JobDiff{
   901  				Type: DiffTypeEdited,
   902  				Objects: []*ObjectDiff{
   903  					{
   904  						Type: DiffTypeDeleted,
   905  						Name: "ParameterizedJob",
   906  						Fields: []*FieldDiff{
   907  							{
   908  								Type: DiffTypeDeleted,
   909  								Name: "Payload",
   910  								Old:  DispatchPayloadRequired,
   911  								New:  "",
   912  							},
   913  						},
   914  						Objects: []*ObjectDiff{
   915  							{
   916  								Type: DiffTypeDeleted,
   917  								Name: "MetaOptional",
   918  								Fields: []*FieldDiff{
   919  									{
   920  										Type: DiffTypeDeleted,
   921  										Name: "MetaOptional",
   922  										Old:  "foo",
   923  										New:  "",
   924  									},
   925  								},
   926  							},
   927  							{
   928  								Type: DiffTypeDeleted,
   929  								Name: "MetaRequired",
   930  								Fields: []*FieldDiff{
   931  									{
   932  										Type: DiffTypeDeleted,
   933  										Name: "MetaRequired",
   934  										Old:  "bar",
   935  										New:  "",
   936  									},
   937  								},
   938  							},
   939  						},
   940  					},
   941  				},
   942  			},
   943  		},
   944  		{
   945  			// Parameterized Job edited
   946  			Old: &Job{
   947  				ParameterizedJob: &ParameterizedJobConfig{
   948  					Payload:      DispatchPayloadRequired,
   949  					MetaOptional: []string{"foo"},
   950  					MetaRequired: []string{"bar"},
   951  				},
   952  			},
   953  			New: &Job{
   954  				ParameterizedJob: &ParameterizedJobConfig{
   955  					Payload:      DispatchPayloadOptional,
   956  					MetaOptional: []string{"bam"},
   957  					MetaRequired: []string{"bang"},
   958  				},
   959  			},
   960  			Expected: &JobDiff{
   961  				Type: DiffTypeEdited,
   962  				Objects: []*ObjectDiff{
   963  					{
   964  						Type: DiffTypeEdited,
   965  						Name: "ParameterizedJob",
   966  						Fields: []*FieldDiff{
   967  							{
   968  								Type: DiffTypeEdited,
   969  								Name: "Payload",
   970  								Old:  DispatchPayloadRequired,
   971  								New:  DispatchPayloadOptional,
   972  							},
   973  						},
   974  						Objects: []*ObjectDiff{
   975  							{
   976  								Type: DiffTypeEdited,
   977  								Name: "MetaOptional",
   978  								Fields: []*FieldDiff{
   979  									{
   980  										Type: DiffTypeAdded,
   981  										Name: "MetaOptional",
   982  										Old:  "",
   983  										New:  "bam",
   984  									},
   985  									{
   986  										Type: DiffTypeDeleted,
   987  										Name: "MetaOptional",
   988  										Old:  "foo",
   989  										New:  "",
   990  									},
   991  								},
   992  							},
   993  							{
   994  								Type: DiffTypeEdited,
   995  								Name: "MetaRequired",
   996  								Fields: []*FieldDiff{
   997  									{
   998  										Type: DiffTypeAdded,
   999  										Name: "MetaRequired",
  1000  										Old:  "",
  1001  										New:  "bang",
  1002  									},
  1003  									{
  1004  										Type: DiffTypeDeleted,
  1005  										Name: "MetaRequired",
  1006  										Old:  "bar",
  1007  										New:  "",
  1008  									},
  1009  								},
  1010  							},
  1011  						},
  1012  					},
  1013  				},
  1014  			},
  1015  		},
  1016  		{
  1017  			// Parameterized Job edited with context
  1018  			Contextual: true,
  1019  			Old: &Job{
  1020  				ParameterizedJob: &ParameterizedJobConfig{
  1021  					Payload:      DispatchPayloadRequired,
  1022  					MetaOptional: []string{"foo"},
  1023  					MetaRequired: []string{"bar"},
  1024  				},
  1025  			},
  1026  			New: &Job{
  1027  				ParameterizedJob: &ParameterizedJobConfig{
  1028  					Payload:      DispatchPayloadOptional,
  1029  					MetaOptional: []string{"foo"},
  1030  					MetaRequired: []string{"bar"},
  1031  				},
  1032  			},
  1033  			Expected: &JobDiff{
  1034  				Type: DiffTypeEdited,
  1035  				Objects: []*ObjectDiff{
  1036  					{
  1037  						Type: DiffTypeEdited,
  1038  						Name: "ParameterizedJob",
  1039  						Fields: []*FieldDiff{
  1040  							{
  1041  								Type: DiffTypeEdited,
  1042  								Name: "Payload",
  1043  								Old:  DispatchPayloadRequired,
  1044  								New:  DispatchPayloadOptional,
  1045  							},
  1046  						},
  1047  						Objects: []*ObjectDiff{
  1048  							{
  1049  								Type: DiffTypeNone,
  1050  								Name: "MetaOptional",
  1051  								Fields: []*FieldDiff{
  1052  									{
  1053  										Type: DiffTypeNone,
  1054  										Name: "MetaOptional",
  1055  										Old:  "foo",
  1056  										New:  "foo",
  1057  									},
  1058  								},
  1059  							},
  1060  							{
  1061  								Type: DiffTypeNone,
  1062  								Name: "MetaRequired",
  1063  								Fields: []*FieldDiff{
  1064  									{
  1065  										Type: DiffTypeNone,
  1066  										Name: "MetaRequired",
  1067  										Old:  "bar",
  1068  										New:  "bar",
  1069  									},
  1070  								},
  1071  							},
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  		},
  1077  	}
  1078  
  1079  	for i, c := range cases {
  1080  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1081  		if c.Error && err == nil {
  1082  			t.Fatalf("case %d: expected errored", i+1)
  1083  		} else if err != nil {
  1084  			if !c.Error {
  1085  				t.Fatalf("case %d: errored %#v", i+1, err)
  1086  			} else {
  1087  				continue
  1088  			}
  1089  		}
  1090  
  1091  		if !reflect.DeepEqual(actual, c.Expected) {
  1092  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1093  				i+1, actual, c.Expected)
  1094  		}
  1095  	}
  1096  }
  1097  
  1098  func TestTaskGroupDiff(t *testing.T) {
  1099  	cases := []struct {
  1100  		Old, New   *TaskGroup
  1101  		Expected   *TaskGroupDiff
  1102  		Error      bool
  1103  		Contextual bool
  1104  	}{
  1105  		{
  1106  			Old: nil,
  1107  			New: nil,
  1108  			Expected: &TaskGroupDiff{
  1109  				Type: DiffTypeNone,
  1110  			},
  1111  		},
  1112  		{
  1113  			// Primitive only that has different names
  1114  			Old: &TaskGroup{
  1115  				Name:  "foo",
  1116  				Count: 10,
  1117  				Meta: map[string]string{
  1118  					"foo": "bar",
  1119  				},
  1120  			},
  1121  			New: &TaskGroup{
  1122  				Name:  "bar",
  1123  				Count: 10,
  1124  				Meta: map[string]string{
  1125  					"foo": "bar",
  1126  				},
  1127  			},
  1128  			Error: true,
  1129  		},
  1130  		{
  1131  			// Primitive only that is the same
  1132  			Old: &TaskGroup{
  1133  				Name:  "foo",
  1134  				Count: 10,
  1135  				Meta: map[string]string{
  1136  					"foo": "bar",
  1137  				},
  1138  			},
  1139  			New: &TaskGroup{
  1140  				Name:  "foo",
  1141  				Count: 10,
  1142  				Meta: map[string]string{
  1143  					"foo": "bar",
  1144  				},
  1145  			},
  1146  			Expected: &TaskGroupDiff{
  1147  				Type: DiffTypeNone,
  1148  				Name: "foo",
  1149  			},
  1150  		},
  1151  		{
  1152  			// Primitive only that has diffs
  1153  			Old: &TaskGroup{
  1154  				Name:  "foo",
  1155  				Count: 10,
  1156  				Meta: map[string]string{
  1157  					"foo": "bar",
  1158  				},
  1159  			},
  1160  			New: &TaskGroup{
  1161  				Name:  "foo",
  1162  				Count: 100,
  1163  				Meta: map[string]string{
  1164  					"foo": "baz",
  1165  				},
  1166  			},
  1167  			Expected: &TaskGroupDiff{
  1168  				Type: DiffTypeEdited,
  1169  				Name: "foo",
  1170  				Fields: []*FieldDiff{
  1171  					{
  1172  						Type: DiffTypeEdited,
  1173  						Name: "Count",
  1174  						Old:  "10",
  1175  						New:  "100",
  1176  					},
  1177  					{
  1178  						Type: DiffTypeEdited,
  1179  						Name: "Meta[foo]",
  1180  						Old:  "bar",
  1181  						New:  "baz",
  1182  					},
  1183  				},
  1184  			},
  1185  		},
  1186  		{
  1187  			// Map diff
  1188  			Old: &TaskGroup{
  1189  				Meta: map[string]string{
  1190  					"foo": "foo",
  1191  					"bar": "bar",
  1192  				},
  1193  			},
  1194  			New: &TaskGroup{
  1195  				Meta: map[string]string{
  1196  					"bar": "bar",
  1197  					"baz": "baz",
  1198  				},
  1199  			},
  1200  			Expected: &TaskGroupDiff{
  1201  				Type: DiffTypeEdited,
  1202  				Fields: []*FieldDiff{
  1203  					{
  1204  						Type: DiffTypeAdded,
  1205  						Name: "Meta[baz]",
  1206  						Old:  "",
  1207  						New:  "baz",
  1208  					},
  1209  					{
  1210  						Type: DiffTypeDeleted,
  1211  						Name: "Meta[foo]",
  1212  						Old:  "foo",
  1213  						New:  "",
  1214  					},
  1215  				},
  1216  			},
  1217  		},
  1218  		{
  1219  			// Constraints edited
  1220  			Old: &TaskGroup{
  1221  				Constraints: []*Constraint{
  1222  					{
  1223  						LTarget: "foo",
  1224  						RTarget: "foo",
  1225  						Operand: "foo",
  1226  						str:     "foo",
  1227  					},
  1228  					{
  1229  						LTarget: "bar",
  1230  						RTarget: "bar",
  1231  						Operand: "bar",
  1232  						str:     "bar",
  1233  					},
  1234  				},
  1235  			},
  1236  			New: &TaskGroup{
  1237  				Constraints: []*Constraint{
  1238  					{
  1239  						LTarget: "foo",
  1240  						RTarget: "foo",
  1241  						Operand: "foo",
  1242  						str:     "foo",
  1243  					},
  1244  					{
  1245  						LTarget: "baz",
  1246  						RTarget: "baz",
  1247  						Operand: "baz",
  1248  						str:     "baz",
  1249  					},
  1250  				},
  1251  			},
  1252  			Expected: &TaskGroupDiff{
  1253  				Type: DiffTypeEdited,
  1254  				Objects: []*ObjectDiff{
  1255  					{
  1256  						Type: DiffTypeAdded,
  1257  						Name: "Constraint",
  1258  						Fields: []*FieldDiff{
  1259  							{
  1260  								Type: DiffTypeAdded,
  1261  								Name: "LTarget",
  1262  								Old:  "",
  1263  								New:  "baz",
  1264  							},
  1265  							{
  1266  								Type: DiffTypeAdded,
  1267  								Name: "Operand",
  1268  								Old:  "",
  1269  								New:  "baz",
  1270  							},
  1271  							{
  1272  								Type: DiffTypeAdded,
  1273  								Name: "RTarget",
  1274  								Old:  "",
  1275  								New:  "baz",
  1276  							},
  1277  						},
  1278  					},
  1279  					{
  1280  						Type: DiffTypeDeleted,
  1281  						Name: "Constraint",
  1282  						Fields: []*FieldDiff{
  1283  							{
  1284  								Type: DiffTypeDeleted,
  1285  								Name: "LTarget",
  1286  								Old:  "bar",
  1287  								New:  "",
  1288  							},
  1289  							{
  1290  								Type: DiffTypeDeleted,
  1291  								Name: "Operand",
  1292  								Old:  "bar",
  1293  								New:  "",
  1294  							},
  1295  							{
  1296  								Type: DiffTypeDeleted,
  1297  								Name: "RTarget",
  1298  								Old:  "bar",
  1299  								New:  "",
  1300  							},
  1301  						},
  1302  					},
  1303  				},
  1304  			},
  1305  		},
  1306  		{
  1307  			// RestartPolicy added
  1308  			Old: &TaskGroup{},
  1309  			New: &TaskGroup{
  1310  				RestartPolicy: &RestartPolicy{
  1311  					Attempts: 1,
  1312  					Interval: 1 * time.Second,
  1313  					Delay:    1 * time.Second,
  1314  					Mode:     "fail",
  1315  				},
  1316  			},
  1317  			Expected: &TaskGroupDiff{
  1318  				Type: DiffTypeEdited,
  1319  				Objects: []*ObjectDiff{
  1320  					{
  1321  						Type: DiffTypeAdded,
  1322  						Name: "RestartPolicy",
  1323  						Fields: []*FieldDiff{
  1324  							{
  1325  								Type: DiffTypeAdded,
  1326  								Name: "Attempts",
  1327  								Old:  "",
  1328  								New:  "1",
  1329  							},
  1330  							{
  1331  								Type: DiffTypeAdded,
  1332  								Name: "Delay",
  1333  								Old:  "",
  1334  								New:  "1000000000",
  1335  							},
  1336  							{
  1337  								Type: DiffTypeAdded,
  1338  								Name: "Interval",
  1339  								Old:  "",
  1340  								New:  "1000000000",
  1341  							},
  1342  							{
  1343  								Type: DiffTypeAdded,
  1344  								Name: "Mode",
  1345  								Old:  "",
  1346  								New:  "fail",
  1347  							},
  1348  						},
  1349  					},
  1350  				},
  1351  			},
  1352  		},
  1353  		{
  1354  			// RestartPolicy deleted
  1355  			Old: &TaskGroup{
  1356  				RestartPolicy: &RestartPolicy{
  1357  					Attempts: 1,
  1358  					Interval: 1 * time.Second,
  1359  					Delay:    1 * time.Second,
  1360  					Mode:     "fail",
  1361  				},
  1362  			},
  1363  			New: &TaskGroup{},
  1364  			Expected: &TaskGroupDiff{
  1365  				Type: DiffTypeEdited,
  1366  				Objects: []*ObjectDiff{
  1367  					{
  1368  						Type: DiffTypeDeleted,
  1369  						Name: "RestartPolicy",
  1370  						Fields: []*FieldDiff{
  1371  							{
  1372  								Type: DiffTypeDeleted,
  1373  								Name: "Attempts",
  1374  								Old:  "1",
  1375  								New:  "",
  1376  							},
  1377  							{
  1378  								Type: DiffTypeDeleted,
  1379  								Name: "Delay",
  1380  								Old:  "1000000000",
  1381  								New:  "",
  1382  							},
  1383  							{
  1384  								Type: DiffTypeDeleted,
  1385  								Name: "Interval",
  1386  								Old:  "1000000000",
  1387  								New:  "",
  1388  							},
  1389  							{
  1390  								Type: DiffTypeDeleted,
  1391  								Name: "Mode",
  1392  								Old:  "fail",
  1393  								New:  "",
  1394  							},
  1395  						},
  1396  					},
  1397  				},
  1398  			},
  1399  		},
  1400  		{
  1401  			// RestartPolicy edited
  1402  			Old: &TaskGroup{
  1403  				RestartPolicy: &RestartPolicy{
  1404  					Attempts: 1,
  1405  					Interval: 1 * time.Second,
  1406  					Delay:    1 * time.Second,
  1407  					Mode:     "fail",
  1408  				},
  1409  			},
  1410  			New: &TaskGroup{
  1411  				RestartPolicy: &RestartPolicy{
  1412  					Attempts: 2,
  1413  					Interval: 2 * time.Second,
  1414  					Delay:    2 * time.Second,
  1415  					Mode:     "delay",
  1416  				},
  1417  			},
  1418  			Expected: &TaskGroupDiff{
  1419  				Type: DiffTypeEdited,
  1420  				Objects: []*ObjectDiff{
  1421  					{
  1422  						Type: DiffTypeEdited,
  1423  						Name: "RestartPolicy",
  1424  						Fields: []*FieldDiff{
  1425  							{
  1426  								Type: DiffTypeEdited,
  1427  								Name: "Attempts",
  1428  								Old:  "1",
  1429  								New:  "2",
  1430  							},
  1431  							{
  1432  								Type: DiffTypeEdited,
  1433  								Name: "Delay",
  1434  								Old:  "1000000000",
  1435  								New:  "2000000000",
  1436  							},
  1437  							{
  1438  								Type: DiffTypeEdited,
  1439  								Name: "Interval",
  1440  								Old:  "1000000000",
  1441  								New:  "2000000000",
  1442  							},
  1443  							{
  1444  								Type: DiffTypeEdited,
  1445  								Name: "Mode",
  1446  								Old:  "fail",
  1447  								New:  "delay",
  1448  							},
  1449  						},
  1450  					},
  1451  				},
  1452  			},
  1453  		},
  1454  		{
  1455  			// RestartPolicy edited with context
  1456  			Contextual: true,
  1457  			Old: &TaskGroup{
  1458  				RestartPolicy: &RestartPolicy{
  1459  					Attempts: 1,
  1460  					Interval: 1 * time.Second,
  1461  					Delay:    1 * time.Second,
  1462  					Mode:     "fail",
  1463  				},
  1464  			},
  1465  			New: &TaskGroup{
  1466  				RestartPolicy: &RestartPolicy{
  1467  					Attempts: 2,
  1468  					Interval: 2 * time.Second,
  1469  					Delay:    1 * time.Second,
  1470  					Mode:     "fail",
  1471  				},
  1472  			},
  1473  			Expected: &TaskGroupDiff{
  1474  				Type: DiffTypeEdited,
  1475  				Objects: []*ObjectDiff{
  1476  					{
  1477  						Type: DiffTypeEdited,
  1478  						Name: "RestartPolicy",
  1479  						Fields: []*FieldDiff{
  1480  							{
  1481  								Type: DiffTypeEdited,
  1482  								Name: "Attempts",
  1483  								Old:  "1",
  1484  								New:  "2",
  1485  							},
  1486  							{
  1487  								Type: DiffTypeNone,
  1488  								Name: "Delay",
  1489  								Old:  "1000000000",
  1490  								New:  "1000000000",
  1491  							},
  1492  							{
  1493  								Type: DiffTypeEdited,
  1494  								Name: "Interval",
  1495  								Old:  "1000000000",
  1496  								New:  "2000000000",
  1497  							},
  1498  							{
  1499  								Type: DiffTypeNone,
  1500  								Name: "Mode",
  1501  								Old:  "fail",
  1502  								New:  "fail",
  1503  							},
  1504  						},
  1505  					},
  1506  				},
  1507  			},
  1508  		},
  1509  		{
  1510  			// ReschedulePolicy added
  1511  			Old: &TaskGroup{},
  1512  			New: &TaskGroup{
  1513  				ReschedulePolicy: &ReschedulePolicy{
  1514  					Attempts:      1,
  1515  					Interval:      15 * time.Second,
  1516  					Delay:         5 * time.Second,
  1517  					MaxDelay:      20 * time.Second,
  1518  					DelayFunction: "exponential",
  1519  					Unlimited:     false,
  1520  				},
  1521  			},
  1522  			Expected: &TaskGroupDiff{
  1523  				Type: DiffTypeEdited,
  1524  				Objects: []*ObjectDiff{
  1525  					{
  1526  						Type: DiffTypeAdded,
  1527  						Name: "ReschedulePolicy",
  1528  						Fields: []*FieldDiff{
  1529  							{
  1530  								Type: DiffTypeAdded,
  1531  								Name: "Attempts",
  1532  								Old:  "",
  1533  								New:  "1",
  1534  							},
  1535  							{
  1536  								Type: DiffTypeAdded,
  1537  								Name: "Delay",
  1538  								Old:  "",
  1539  								New:  "5000000000",
  1540  							},
  1541  							{
  1542  								Type: DiffTypeAdded,
  1543  								Name: "DelayFunction",
  1544  								Old:  "",
  1545  								New:  "exponential",
  1546  							},
  1547  							{
  1548  								Type: DiffTypeAdded,
  1549  								Name: "Interval",
  1550  								Old:  "",
  1551  								New:  "15000000000",
  1552  							},
  1553  							{
  1554  								Type: DiffTypeAdded,
  1555  								Name: "MaxDelay",
  1556  								Old:  "",
  1557  								New:  "20000000000",
  1558  							},
  1559  							{
  1560  								Type: DiffTypeAdded,
  1561  								Name: "Unlimited",
  1562  								Old:  "",
  1563  								New:  "false",
  1564  							},
  1565  						},
  1566  					},
  1567  				},
  1568  			},
  1569  		},
  1570  		{
  1571  			// ReschedulePolicy deleted
  1572  			Old: &TaskGroup{
  1573  				ReschedulePolicy: &ReschedulePolicy{
  1574  					Attempts:      1,
  1575  					Interval:      15 * time.Second,
  1576  					Delay:         5 * time.Second,
  1577  					MaxDelay:      20 * time.Second,
  1578  					DelayFunction: "exponential",
  1579  					Unlimited:     false,
  1580  				},
  1581  			},
  1582  			New: &TaskGroup{},
  1583  			Expected: &TaskGroupDiff{
  1584  				Type: DiffTypeEdited,
  1585  				Objects: []*ObjectDiff{
  1586  					{
  1587  						Type: DiffTypeDeleted,
  1588  						Name: "ReschedulePolicy",
  1589  						Fields: []*FieldDiff{
  1590  							{
  1591  								Type: DiffTypeDeleted,
  1592  								Name: "Attempts",
  1593  								Old:  "1",
  1594  								New:  "",
  1595  							},
  1596  							{
  1597  								Type: DiffTypeDeleted,
  1598  								Name: "Delay",
  1599  								Old:  "5000000000",
  1600  								New:  "",
  1601  							},
  1602  							{
  1603  								Type: DiffTypeDeleted,
  1604  								Name: "DelayFunction",
  1605  								Old:  "exponential",
  1606  								New:  "",
  1607  							},
  1608  							{
  1609  								Type: DiffTypeDeleted,
  1610  								Name: "Interval",
  1611  								Old:  "15000000000",
  1612  								New:  "",
  1613  							},
  1614  							{
  1615  								Type: DiffTypeDeleted,
  1616  								Name: "MaxDelay",
  1617  								Old:  "20000000000",
  1618  								New:  "",
  1619  							},
  1620  							{
  1621  								Type: DiffTypeDeleted,
  1622  								Name: "Unlimited",
  1623  								Old:  "false",
  1624  								New:  "",
  1625  							},
  1626  						},
  1627  					},
  1628  				},
  1629  			},
  1630  		},
  1631  		{
  1632  			// ReschedulePolicy edited
  1633  			Old: &TaskGroup{
  1634  				ReschedulePolicy: &ReschedulePolicy{
  1635  					Attempts:      1,
  1636  					Interval:      1 * time.Second,
  1637  					DelayFunction: "exponential",
  1638  					Delay:         20 * time.Second,
  1639  					MaxDelay:      1 * time.Minute,
  1640  					Unlimited:     false,
  1641  				},
  1642  			},
  1643  			New: &TaskGroup{
  1644  				ReschedulePolicy: &ReschedulePolicy{
  1645  					Attempts:      2,
  1646  					Interval:      2 * time.Second,
  1647  					DelayFunction: "constant",
  1648  					Delay:         30 * time.Second,
  1649  					MaxDelay:      1 * time.Minute,
  1650  					Unlimited:     true,
  1651  				},
  1652  			},
  1653  			Expected: &TaskGroupDiff{
  1654  				Type: DiffTypeEdited,
  1655  				Objects: []*ObjectDiff{
  1656  					{
  1657  						Type: DiffTypeEdited,
  1658  						Name: "ReschedulePolicy",
  1659  						Fields: []*FieldDiff{
  1660  							{
  1661  								Type: DiffTypeEdited,
  1662  								Name: "Attempts",
  1663  								Old:  "1",
  1664  								New:  "2",
  1665  							},
  1666  							{
  1667  								Type: DiffTypeEdited,
  1668  								Name: "Delay",
  1669  								Old:  "20000000000",
  1670  								New:  "30000000000",
  1671  							},
  1672  							{
  1673  								Type: DiffTypeEdited,
  1674  								Name: "DelayFunction",
  1675  								Old:  "exponential",
  1676  								New:  "constant",
  1677  							},
  1678  							{
  1679  								Type: DiffTypeEdited,
  1680  								Name: "Interval",
  1681  								Old:  "1000000000",
  1682  								New:  "2000000000",
  1683  							},
  1684  							{
  1685  								Type: DiffTypeEdited,
  1686  								Name: "Unlimited",
  1687  								Old:  "false",
  1688  								New:  "true",
  1689  							},
  1690  						},
  1691  					},
  1692  				},
  1693  			},
  1694  		}, {
  1695  			// ReschedulePolicy edited with context
  1696  			Contextual: true,
  1697  			Old: &TaskGroup{
  1698  				ReschedulePolicy: &ReschedulePolicy{
  1699  					Attempts: 1,
  1700  					Interval: 1 * time.Second,
  1701  				},
  1702  			},
  1703  			New: &TaskGroup{
  1704  				ReschedulePolicy: &ReschedulePolicy{
  1705  					Attempts: 1,
  1706  					Interval: 2 * time.Second,
  1707  				},
  1708  			},
  1709  			Expected: &TaskGroupDiff{
  1710  				Type: DiffTypeEdited,
  1711  				Objects: []*ObjectDiff{
  1712  					{
  1713  						Type: DiffTypeEdited,
  1714  						Name: "ReschedulePolicy",
  1715  						Fields: []*FieldDiff{
  1716  							{
  1717  								Type: DiffTypeNone,
  1718  								Name: "Attempts",
  1719  								Old:  "1",
  1720  								New:  "1",
  1721  							},
  1722  							{
  1723  								Type: DiffTypeNone,
  1724  								Name: "Delay",
  1725  								Old:  "0",
  1726  								New:  "0",
  1727  							},
  1728  							{
  1729  								Type: DiffTypeNone,
  1730  								Name: "DelayFunction",
  1731  								Old:  "",
  1732  								New:  "",
  1733  							},
  1734  							{
  1735  								Type: DiffTypeEdited,
  1736  								Name: "Interval",
  1737  								Old:  "1000000000",
  1738  								New:  "2000000000",
  1739  							},
  1740  							{
  1741  								Type: DiffTypeNone,
  1742  								Name: "MaxDelay",
  1743  								Old:  "0",
  1744  								New:  "0",
  1745  							},
  1746  							{
  1747  								Type: DiffTypeNone,
  1748  								Name: "Unlimited",
  1749  								Old:  "false",
  1750  								New:  "false",
  1751  							},
  1752  						},
  1753  					},
  1754  				},
  1755  			},
  1756  		},
  1757  		{
  1758  			// Update strategy deleted
  1759  			Old: &TaskGroup{
  1760  				Update: &UpdateStrategy{
  1761  					AutoRevert: true,
  1762  				},
  1763  			},
  1764  			New: &TaskGroup{},
  1765  			Expected: &TaskGroupDiff{
  1766  				Type: DiffTypeEdited,
  1767  				Objects: []*ObjectDiff{
  1768  					{
  1769  						Type: DiffTypeDeleted,
  1770  						Name: "Update",
  1771  						Fields: []*FieldDiff{
  1772  							{
  1773  								Type: DiffTypeDeleted,
  1774  								Name: "AutoRevert",
  1775  								Old:  "true",
  1776  								New:  "",
  1777  							},
  1778  							{
  1779  								Type: DiffTypeDeleted,
  1780  								Name: "Canary",
  1781  								Old:  "0",
  1782  								New:  "",
  1783  							},
  1784  							{
  1785  								Type: DiffTypeDeleted,
  1786  								Name: "HealthyDeadline",
  1787  								Old:  "0",
  1788  								New:  "",
  1789  							},
  1790  							{
  1791  								Type: DiffTypeDeleted,
  1792  								Name: "MaxParallel",
  1793  								Old:  "0",
  1794  								New:  "",
  1795  							},
  1796  							{
  1797  								Type: DiffTypeDeleted,
  1798  								Name: "MinHealthyTime",
  1799  								Old:  "0",
  1800  								New:  "",
  1801  							},
  1802  							{
  1803  								Type: DiffTypeDeleted,
  1804  								Name: "ProgressDeadline",
  1805  								Old:  "0",
  1806  								New:  "",
  1807  							},
  1808  						},
  1809  					},
  1810  				},
  1811  			},
  1812  		},
  1813  		{
  1814  			// Update strategy added
  1815  			Old: &TaskGroup{},
  1816  			New: &TaskGroup{
  1817  				Update: &UpdateStrategy{
  1818  					AutoRevert: true,
  1819  				},
  1820  			},
  1821  			Expected: &TaskGroupDiff{
  1822  				Type: DiffTypeEdited,
  1823  				Objects: []*ObjectDiff{
  1824  					{
  1825  						Type: DiffTypeAdded,
  1826  						Name: "Update",
  1827  						Fields: []*FieldDiff{
  1828  							{
  1829  								Type: DiffTypeAdded,
  1830  								Name: "AutoRevert",
  1831  								Old:  "",
  1832  								New:  "true",
  1833  							},
  1834  							{
  1835  								Type: DiffTypeAdded,
  1836  								Name: "Canary",
  1837  								Old:  "",
  1838  								New:  "0",
  1839  							},
  1840  							{
  1841  								Type: DiffTypeAdded,
  1842  								Name: "HealthyDeadline",
  1843  								Old:  "",
  1844  								New:  "0",
  1845  							},
  1846  							{
  1847  								Type: DiffTypeAdded,
  1848  								Name: "MaxParallel",
  1849  								Old:  "",
  1850  								New:  "0",
  1851  							},
  1852  							{
  1853  								Type: DiffTypeAdded,
  1854  								Name: "MinHealthyTime",
  1855  								Old:  "",
  1856  								New:  "0",
  1857  							},
  1858  							{
  1859  								Type: DiffTypeAdded,
  1860  								Name: "ProgressDeadline",
  1861  								Old:  "",
  1862  								New:  "0",
  1863  							},
  1864  						},
  1865  					},
  1866  				},
  1867  			},
  1868  		},
  1869  		{
  1870  			// Update strategy edited
  1871  			Old: &TaskGroup{
  1872  				Update: &UpdateStrategy{
  1873  					MaxParallel:      5,
  1874  					HealthCheck:      "foo",
  1875  					MinHealthyTime:   1 * time.Second,
  1876  					HealthyDeadline:  30 * time.Second,
  1877  					ProgressDeadline: 29 * time.Second,
  1878  					AutoRevert:       true,
  1879  					Canary:           2,
  1880  				},
  1881  			},
  1882  			New: &TaskGroup{
  1883  				Update: &UpdateStrategy{
  1884  					MaxParallel:      7,
  1885  					HealthCheck:      "bar",
  1886  					MinHealthyTime:   2 * time.Second,
  1887  					HealthyDeadline:  31 * time.Second,
  1888  					ProgressDeadline: 32 * time.Second,
  1889  					AutoRevert:       false,
  1890  					Canary:           1,
  1891  				},
  1892  			},
  1893  			Expected: &TaskGroupDiff{
  1894  				Type: DiffTypeEdited,
  1895  				Objects: []*ObjectDiff{
  1896  					{
  1897  						Type: DiffTypeEdited,
  1898  						Name: "Update",
  1899  						Fields: []*FieldDiff{
  1900  							{
  1901  								Type: DiffTypeEdited,
  1902  								Name: "AutoRevert",
  1903  								Old:  "true",
  1904  								New:  "false",
  1905  							},
  1906  							{
  1907  								Type: DiffTypeEdited,
  1908  								Name: "Canary",
  1909  								Old:  "2",
  1910  								New:  "1",
  1911  							},
  1912  							{
  1913  								Type: DiffTypeEdited,
  1914  								Name: "HealthCheck",
  1915  								Old:  "foo",
  1916  								New:  "bar",
  1917  							},
  1918  							{
  1919  								Type: DiffTypeEdited,
  1920  								Name: "HealthyDeadline",
  1921  								Old:  "30000000000",
  1922  								New:  "31000000000",
  1923  							},
  1924  							{
  1925  								Type: DiffTypeEdited,
  1926  								Name: "MaxParallel",
  1927  								Old:  "5",
  1928  								New:  "7",
  1929  							},
  1930  							{
  1931  								Type: DiffTypeEdited,
  1932  								Name: "MinHealthyTime",
  1933  								Old:  "1000000000",
  1934  								New:  "2000000000",
  1935  							},
  1936  							{
  1937  								Type: DiffTypeEdited,
  1938  								Name: "ProgressDeadline",
  1939  								Old:  "29000000000",
  1940  								New:  "32000000000",
  1941  							},
  1942  						},
  1943  					},
  1944  				},
  1945  			},
  1946  		},
  1947  		{
  1948  			// Update strategy edited with context
  1949  			Contextual: true,
  1950  			Old: &TaskGroup{
  1951  				Update: &UpdateStrategy{
  1952  					MaxParallel:      5,
  1953  					HealthCheck:      "foo",
  1954  					MinHealthyTime:   1 * time.Second,
  1955  					HealthyDeadline:  30 * time.Second,
  1956  					ProgressDeadline: 30 * time.Second,
  1957  					AutoRevert:       true,
  1958  					Canary:           2,
  1959  				},
  1960  			},
  1961  			New: &TaskGroup{
  1962  				Update: &UpdateStrategy{
  1963  					MaxParallel:      7,
  1964  					HealthCheck:      "foo",
  1965  					MinHealthyTime:   1 * time.Second,
  1966  					HealthyDeadline:  30 * time.Second,
  1967  					ProgressDeadline: 30 * time.Second,
  1968  					AutoRevert:       true,
  1969  					Canary:           2,
  1970  				},
  1971  			},
  1972  			Expected: &TaskGroupDiff{
  1973  				Type: DiffTypeEdited,
  1974  				Objects: []*ObjectDiff{
  1975  					{
  1976  						Type: DiffTypeEdited,
  1977  						Name: "Update",
  1978  						Fields: []*FieldDiff{
  1979  							{
  1980  								Type: DiffTypeNone,
  1981  								Name: "AutoRevert",
  1982  								Old:  "true",
  1983  								New:  "true",
  1984  							},
  1985  							{
  1986  								Type: DiffTypeNone,
  1987  								Name: "Canary",
  1988  								Old:  "2",
  1989  								New:  "2",
  1990  							},
  1991  							{
  1992  								Type: DiffTypeNone,
  1993  								Name: "HealthCheck",
  1994  								Old:  "foo",
  1995  								New:  "foo",
  1996  							},
  1997  							{
  1998  								Type: DiffTypeNone,
  1999  								Name: "HealthyDeadline",
  2000  								Old:  "30000000000",
  2001  								New:  "30000000000",
  2002  							},
  2003  							{
  2004  								Type: DiffTypeEdited,
  2005  								Name: "MaxParallel",
  2006  								Old:  "5",
  2007  								New:  "7",
  2008  							},
  2009  							{
  2010  								Type: DiffTypeNone,
  2011  								Name: "MinHealthyTime",
  2012  								Old:  "1000000000",
  2013  								New:  "1000000000",
  2014  							},
  2015  							{
  2016  								Type: DiffTypeNone,
  2017  								Name: "ProgressDeadline",
  2018  								Old:  "30000000000",
  2019  								New:  "30000000000",
  2020  							},
  2021  						},
  2022  					},
  2023  				},
  2024  			},
  2025  		},
  2026  		{
  2027  			// EphemeralDisk added
  2028  			Old: &TaskGroup{},
  2029  			New: &TaskGroup{
  2030  				EphemeralDisk: &EphemeralDisk{
  2031  					Migrate: true,
  2032  					Sticky:  true,
  2033  					SizeMB:  100,
  2034  				},
  2035  			},
  2036  			Expected: &TaskGroupDiff{
  2037  				Type: DiffTypeEdited,
  2038  				Objects: []*ObjectDiff{
  2039  					{
  2040  						Type: DiffTypeAdded,
  2041  						Name: "EphemeralDisk",
  2042  						Fields: []*FieldDiff{
  2043  							{
  2044  								Type: DiffTypeAdded,
  2045  								Name: "Migrate",
  2046  								Old:  "",
  2047  								New:  "true",
  2048  							},
  2049  							{
  2050  								Type: DiffTypeAdded,
  2051  								Name: "SizeMB",
  2052  								Old:  "",
  2053  								New:  "100",
  2054  							},
  2055  							{
  2056  								Type: DiffTypeAdded,
  2057  								Name: "Sticky",
  2058  								Old:  "",
  2059  								New:  "true",
  2060  							},
  2061  						},
  2062  					},
  2063  				},
  2064  			},
  2065  		},
  2066  		{
  2067  			// EphemeralDisk deleted
  2068  			Old: &TaskGroup{
  2069  				EphemeralDisk: &EphemeralDisk{
  2070  					Migrate: true,
  2071  					Sticky:  true,
  2072  					SizeMB:  100,
  2073  				},
  2074  			},
  2075  			New: &TaskGroup{},
  2076  			Expected: &TaskGroupDiff{
  2077  				Type: DiffTypeEdited,
  2078  				Objects: []*ObjectDiff{
  2079  					{
  2080  						Type: DiffTypeDeleted,
  2081  						Name: "EphemeralDisk",
  2082  						Fields: []*FieldDiff{
  2083  							{
  2084  								Type: DiffTypeDeleted,
  2085  								Name: "Migrate",
  2086  								Old:  "true",
  2087  								New:  "",
  2088  							},
  2089  							{
  2090  								Type: DiffTypeDeleted,
  2091  								Name: "SizeMB",
  2092  								Old:  "100",
  2093  								New:  "",
  2094  							},
  2095  							{
  2096  								Type: DiffTypeDeleted,
  2097  								Name: "Sticky",
  2098  								Old:  "true",
  2099  								New:  "",
  2100  							},
  2101  						},
  2102  					},
  2103  				},
  2104  			},
  2105  		},
  2106  		{
  2107  			// EphemeralDisk edited
  2108  			Old: &TaskGroup{
  2109  				EphemeralDisk: &EphemeralDisk{
  2110  					Migrate: true,
  2111  					Sticky:  true,
  2112  					SizeMB:  150,
  2113  				},
  2114  			},
  2115  			New: &TaskGroup{
  2116  				EphemeralDisk: &EphemeralDisk{
  2117  					Migrate: false,
  2118  					Sticky:  false,
  2119  					SizeMB:  90,
  2120  				},
  2121  			},
  2122  			Expected: &TaskGroupDiff{
  2123  				Type: DiffTypeEdited,
  2124  				Objects: []*ObjectDiff{
  2125  					{
  2126  						Type: DiffTypeEdited,
  2127  						Name: "EphemeralDisk",
  2128  						Fields: []*FieldDiff{
  2129  							{
  2130  								Type: DiffTypeEdited,
  2131  								Name: "Migrate",
  2132  								Old:  "true",
  2133  								New:  "false",
  2134  							},
  2135  							{
  2136  								Type: DiffTypeEdited,
  2137  								Name: "SizeMB",
  2138  								Old:  "150",
  2139  								New:  "90",
  2140  							},
  2141  
  2142  							{
  2143  								Type: DiffTypeEdited,
  2144  								Name: "Sticky",
  2145  								Old:  "true",
  2146  								New:  "false",
  2147  							},
  2148  						},
  2149  					},
  2150  				},
  2151  			},
  2152  		},
  2153  		{
  2154  			// EphemeralDisk edited with context
  2155  			Contextual: true,
  2156  			Old: &TaskGroup{
  2157  				EphemeralDisk: &EphemeralDisk{
  2158  					Migrate: false,
  2159  					Sticky:  false,
  2160  					SizeMB:  100,
  2161  				},
  2162  			},
  2163  			New: &TaskGroup{
  2164  				EphemeralDisk: &EphemeralDisk{
  2165  					Migrate: true,
  2166  					Sticky:  true,
  2167  					SizeMB:  90,
  2168  				},
  2169  			},
  2170  			Expected: &TaskGroupDiff{
  2171  				Type: DiffTypeEdited,
  2172  				Objects: []*ObjectDiff{
  2173  					{
  2174  						Type: DiffTypeEdited,
  2175  						Name: "EphemeralDisk",
  2176  						Fields: []*FieldDiff{
  2177  							{
  2178  								Type: DiffTypeEdited,
  2179  								Name: "Migrate",
  2180  								Old:  "false",
  2181  								New:  "true",
  2182  							},
  2183  							{
  2184  								Type: DiffTypeEdited,
  2185  								Name: "SizeMB",
  2186  								Old:  "100",
  2187  								New:  "90",
  2188  							},
  2189  							{
  2190  								Type: DiffTypeEdited,
  2191  								Name: "Sticky",
  2192  								Old:  "false",
  2193  								New:  "true",
  2194  							},
  2195  						},
  2196  					},
  2197  				},
  2198  			},
  2199  		},
  2200  		{
  2201  			// Tasks edited
  2202  			Old: &TaskGroup{
  2203  				Tasks: []*Task{
  2204  					{
  2205  						Name:   "foo",
  2206  						Driver: "docker",
  2207  					},
  2208  					{
  2209  						Name:   "bar",
  2210  						Driver: "docker",
  2211  					},
  2212  					{
  2213  						Name:          "baz",
  2214  						ShutdownDelay: 1 * time.Second,
  2215  					},
  2216  				},
  2217  			},
  2218  			New: &TaskGroup{
  2219  				Tasks: []*Task{
  2220  					{
  2221  						Name:   "bar",
  2222  						Driver: "docker",
  2223  					},
  2224  					{
  2225  						Name:   "bam",
  2226  						Driver: "docker",
  2227  					},
  2228  					{
  2229  						Name:          "baz",
  2230  						ShutdownDelay: 2 * time.Second,
  2231  					},
  2232  				},
  2233  			},
  2234  			Expected: &TaskGroupDiff{
  2235  				Type: DiffTypeEdited,
  2236  				Tasks: []*TaskDiff{
  2237  					{
  2238  						Type: DiffTypeAdded,
  2239  						Name: "bam",
  2240  						Fields: []*FieldDiff{
  2241  							{
  2242  								Type: DiffTypeAdded,
  2243  								Name: "Driver",
  2244  								Old:  "",
  2245  								New:  "docker",
  2246  							},
  2247  							{
  2248  								Type: DiffTypeAdded,
  2249  								Name: "KillTimeout",
  2250  								Old:  "",
  2251  								New:  "0",
  2252  							},
  2253  							{
  2254  								Type: DiffTypeAdded,
  2255  								Name: "Leader",
  2256  								Old:  "",
  2257  								New:  "false",
  2258  							},
  2259  							{
  2260  								Type: DiffTypeAdded,
  2261  								Name: "ShutdownDelay",
  2262  								Old:  "",
  2263  								New:  "0",
  2264  							},
  2265  						},
  2266  					},
  2267  					{
  2268  						Type: DiffTypeNone,
  2269  						Name: "bar",
  2270  					},
  2271  					{
  2272  						Type: DiffTypeEdited,
  2273  						Name: "baz",
  2274  						Fields: []*FieldDiff{
  2275  							{
  2276  								Type: DiffTypeEdited,
  2277  								Name: "ShutdownDelay",
  2278  								Old:  "1000000000",
  2279  								New:  "2000000000",
  2280  							},
  2281  						},
  2282  					},
  2283  					{
  2284  						Type: DiffTypeDeleted,
  2285  						Name: "foo",
  2286  						Fields: []*FieldDiff{
  2287  							{
  2288  								Type: DiffTypeDeleted,
  2289  								Name: "Driver",
  2290  								Old:  "docker",
  2291  								New:  "",
  2292  							},
  2293  							{
  2294  								Type: DiffTypeDeleted,
  2295  								Name: "KillTimeout",
  2296  								Old:  "0",
  2297  								New:  "",
  2298  							},
  2299  							{
  2300  								Type: DiffTypeDeleted,
  2301  								Name: "Leader",
  2302  								Old:  "false",
  2303  								New:  "",
  2304  							},
  2305  							{
  2306  								Type: DiffTypeDeleted,
  2307  								Name: "ShutdownDelay",
  2308  								Old:  "0",
  2309  								New:  "",
  2310  							},
  2311  						},
  2312  					},
  2313  				},
  2314  			},
  2315  		},
  2316  	}
  2317  
  2318  	for i, c := range cases {
  2319  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2320  		if c.Error && err == nil {
  2321  			t.Fatalf("case %d: expected errored", i+1)
  2322  		} else if err != nil {
  2323  			if !c.Error {
  2324  				t.Fatalf("case %d: errored %#v", i+1, err)
  2325  			} else {
  2326  				continue
  2327  			}
  2328  		}
  2329  
  2330  		if !reflect.DeepEqual(actual, c.Expected) {
  2331  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2332  				i+1, actual, c.Expected)
  2333  		}
  2334  	}
  2335  }
  2336  
  2337  func TestTaskDiff(t *testing.T) {
  2338  	cases := []struct {
  2339  		Name       string
  2340  		Old, New   *Task
  2341  		Expected   *TaskDiff
  2342  		Error      bool
  2343  		Contextual bool
  2344  	}{
  2345  		{
  2346  			Name: "Empty",
  2347  			Old:  nil,
  2348  			New:  nil,
  2349  			Expected: &TaskDiff{
  2350  				Type: DiffTypeNone,
  2351  			},
  2352  		},
  2353  		{
  2354  			Name: "Primitive only that has different names",
  2355  			Old: &Task{
  2356  				Name: "foo",
  2357  				Meta: map[string]string{
  2358  					"foo": "bar",
  2359  				},
  2360  			},
  2361  			New: &Task{
  2362  				Name: "bar",
  2363  				Meta: map[string]string{
  2364  					"foo": "bar",
  2365  				},
  2366  			},
  2367  			Error: true,
  2368  		},
  2369  		{
  2370  			Name: "Primitive only that is the same",
  2371  			Old: &Task{
  2372  				Name:   "foo",
  2373  				Driver: "exec",
  2374  				User:   "foo",
  2375  				Env: map[string]string{
  2376  					"FOO": "bar",
  2377  				},
  2378  				Meta: map[string]string{
  2379  					"foo": "bar",
  2380  				},
  2381  				KillTimeout: 1 * time.Second,
  2382  				Leader:      true,
  2383  			},
  2384  			New: &Task{
  2385  				Name:   "foo",
  2386  				Driver: "exec",
  2387  				User:   "foo",
  2388  				Env: map[string]string{
  2389  					"FOO": "bar",
  2390  				},
  2391  				Meta: map[string]string{
  2392  					"foo": "bar",
  2393  				},
  2394  				KillTimeout: 1 * time.Second,
  2395  				Leader:      true,
  2396  			},
  2397  			Expected: &TaskDiff{
  2398  				Type: DiffTypeNone,
  2399  				Name: "foo",
  2400  			},
  2401  		},
  2402  		{
  2403  			Name: "Primitive only that has diffs",
  2404  			Old: &Task{
  2405  				Name:   "foo",
  2406  				Driver: "exec",
  2407  				User:   "foo",
  2408  				Env: map[string]string{
  2409  					"FOO": "bar",
  2410  				},
  2411  				Meta: map[string]string{
  2412  					"foo": "bar",
  2413  				},
  2414  				KillTimeout: 1 * time.Second,
  2415  				Leader:      true,
  2416  			},
  2417  			New: &Task{
  2418  				Name:   "foo",
  2419  				Driver: "docker",
  2420  				User:   "bar",
  2421  				Env: map[string]string{
  2422  					"FOO": "baz",
  2423  				},
  2424  				Meta: map[string]string{
  2425  					"foo": "baz",
  2426  				},
  2427  				KillTimeout: 2 * time.Second,
  2428  				Leader:      false,
  2429  			},
  2430  			Expected: &TaskDiff{
  2431  				Type: DiffTypeEdited,
  2432  				Name: "foo",
  2433  				Fields: []*FieldDiff{
  2434  					{
  2435  						Type: DiffTypeEdited,
  2436  						Name: "Driver",
  2437  						Old:  "exec",
  2438  						New:  "docker",
  2439  					},
  2440  					{
  2441  						Type: DiffTypeEdited,
  2442  						Name: "Env[FOO]",
  2443  						Old:  "bar",
  2444  						New:  "baz",
  2445  					},
  2446  					{
  2447  						Type: DiffTypeEdited,
  2448  						Name: "KillTimeout",
  2449  						Old:  "1000000000",
  2450  						New:  "2000000000",
  2451  					},
  2452  					{
  2453  						Type: DiffTypeEdited,
  2454  						Name: "Leader",
  2455  						Old:  "true",
  2456  						New:  "false",
  2457  					},
  2458  					{
  2459  						Type: DiffTypeEdited,
  2460  						Name: "Meta[foo]",
  2461  						Old:  "bar",
  2462  						New:  "baz",
  2463  					},
  2464  					{
  2465  						Type: DiffTypeEdited,
  2466  						Name: "User",
  2467  						Old:  "foo",
  2468  						New:  "bar",
  2469  					},
  2470  				},
  2471  			},
  2472  		},
  2473  		{
  2474  			Name: "Map diff",
  2475  			Old: &Task{
  2476  				Meta: map[string]string{
  2477  					"foo": "foo",
  2478  					"bar": "bar",
  2479  				},
  2480  				Env: map[string]string{
  2481  					"foo": "foo",
  2482  					"bar": "bar",
  2483  				},
  2484  			},
  2485  			New: &Task{
  2486  				Meta: map[string]string{
  2487  					"bar": "bar",
  2488  					"baz": "baz",
  2489  				},
  2490  				Env: map[string]string{
  2491  					"bar": "bar",
  2492  					"baz": "baz",
  2493  				},
  2494  			},
  2495  			Expected: &TaskDiff{
  2496  				Type: DiffTypeEdited,
  2497  				Fields: []*FieldDiff{
  2498  					{
  2499  						Type: DiffTypeAdded,
  2500  						Name: "Env[baz]",
  2501  						Old:  "",
  2502  						New:  "baz",
  2503  					},
  2504  					{
  2505  						Type: DiffTypeDeleted,
  2506  						Name: "Env[foo]",
  2507  						Old:  "foo",
  2508  						New:  "",
  2509  					},
  2510  					{
  2511  						Type: DiffTypeAdded,
  2512  						Name: "Meta[baz]",
  2513  						Old:  "",
  2514  						New:  "baz",
  2515  					},
  2516  					{
  2517  						Type: DiffTypeDeleted,
  2518  						Name: "Meta[foo]",
  2519  						Old:  "foo",
  2520  						New:  "",
  2521  					},
  2522  				},
  2523  			},
  2524  		},
  2525  		{
  2526  			Name: "Constraints edited",
  2527  			Old: &Task{
  2528  				Constraints: []*Constraint{
  2529  					{
  2530  						LTarget: "foo",
  2531  						RTarget: "foo",
  2532  						Operand: "foo",
  2533  						str:     "foo",
  2534  					},
  2535  					{
  2536  						LTarget: "bar",
  2537  						RTarget: "bar",
  2538  						Operand: "bar",
  2539  						str:     "bar",
  2540  					},
  2541  				},
  2542  			},
  2543  			New: &Task{
  2544  				Constraints: []*Constraint{
  2545  					{
  2546  						LTarget: "foo",
  2547  						RTarget: "foo",
  2548  						Operand: "foo",
  2549  						str:     "foo",
  2550  					},
  2551  					{
  2552  						LTarget: "baz",
  2553  						RTarget: "baz",
  2554  						Operand: "baz",
  2555  						str:     "baz",
  2556  					},
  2557  				},
  2558  			},
  2559  			Expected: &TaskDiff{
  2560  				Type: DiffTypeEdited,
  2561  				Objects: []*ObjectDiff{
  2562  					{
  2563  						Type: DiffTypeAdded,
  2564  						Name: "Constraint",
  2565  						Fields: []*FieldDiff{
  2566  							{
  2567  								Type: DiffTypeAdded,
  2568  								Name: "LTarget",
  2569  								Old:  "",
  2570  								New:  "baz",
  2571  							},
  2572  							{
  2573  								Type: DiffTypeAdded,
  2574  								Name: "Operand",
  2575  								Old:  "",
  2576  								New:  "baz",
  2577  							},
  2578  							{
  2579  								Type: DiffTypeAdded,
  2580  								Name: "RTarget",
  2581  								Old:  "",
  2582  								New:  "baz",
  2583  							},
  2584  						},
  2585  					},
  2586  					{
  2587  						Type: DiffTypeDeleted,
  2588  						Name: "Constraint",
  2589  						Fields: []*FieldDiff{
  2590  							{
  2591  								Type: DiffTypeDeleted,
  2592  								Name: "LTarget",
  2593  								Old:  "bar",
  2594  								New:  "",
  2595  							},
  2596  							{
  2597  								Type: DiffTypeDeleted,
  2598  								Name: "Operand",
  2599  								Old:  "bar",
  2600  								New:  "",
  2601  							},
  2602  							{
  2603  								Type: DiffTypeDeleted,
  2604  								Name: "RTarget",
  2605  								Old:  "bar",
  2606  								New:  "",
  2607  							},
  2608  						},
  2609  					},
  2610  				},
  2611  			},
  2612  		},
  2613  		{
  2614  			Name: "LogConfig added",
  2615  			Old:  &Task{},
  2616  			New: &Task{
  2617  				LogConfig: &LogConfig{
  2618  					MaxFiles:      1,
  2619  					MaxFileSizeMB: 10,
  2620  				},
  2621  			},
  2622  			Expected: &TaskDiff{
  2623  				Type: DiffTypeEdited,
  2624  				Objects: []*ObjectDiff{
  2625  					{
  2626  						Type: DiffTypeAdded,
  2627  						Name: "LogConfig",
  2628  						Fields: []*FieldDiff{
  2629  							{
  2630  								Type: DiffTypeAdded,
  2631  								Name: "MaxFileSizeMB",
  2632  								Old:  "",
  2633  								New:  "10",
  2634  							},
  2635  							{
  2636  								Type: DiffTypeAdded,
  2637  								Name: "MaxFiles",
  2638  								Old:  "",
  2639  								New:  "1",
  2640  							},
  2641  						},
  2642  					},
  2643  				},
  2644  			},
  2645  		},
  2646  		{
  2647  			Name: "LogConfig deleted",
  2648  			Old: &Task{
  2649  				LogConfig: &LogConfig{
  2650  					MaxFiles:      1,
  2651  					MaxFileSizeMB: 10,
  2652  				},
  2653  			},
  2654  			New: &Task{},
  2655  			Expected: &TaskDiff{
  2656  				Type: DiffTypeEdited,
  2657  				Objects: []*ObjectDiff{
  2658  					{
  2659  						Type: DiffTypeDeleted,
  2660  						Name: "LogConfig",
  2661  						Fields: []*FieldDiff{
  2662  							{
  2663  								Type: DiffTypeDeleted,
  2664  								Name: "MaxFileSizeMB",
  2665  								Old:  "10",
  2666  								New:  "",
  2667  							},
  2668  							{
  2669  								Type: DiffTypeDeleted,
  2670  								Name: "MaxFiles",
  2671  								Old:  "1",
  2672  								New:  "",
  2673  							},
  2674  						},
  2675  					},
  2676  				},
  2677  			},
  2678  		},
  2679  		{
  2680  			Name: "LogConfig edited",
  2681  			Old: &Task{
  2682  				LogConfig: &LogConfig{
  2683  					MaxFiles:      1,
  2684  					MaxFileSizeMB: 10,
  2685  				},
  2686  			},
  2687  			New: &Task{
  2688  				LogConfig: &LogConfig{
  2689  					MaxFiles:      2,
  2690  					MaxFileSizeMB: 20,
  2691  				},
  2692  			},
  2693  			Expected: &TaskDiff{
  2694  				Type: DiffTypeEdited,
  2695  				Objects: []*ObjectDiff{
  2696  					{
  2697  						Type: DiffTypeEdited,
  2698  						Name: "LogConfig",
  2699  						Fields: []*FieldDiff{
  2700  							{
  2701  								Type: DiffTypeEdited,
  2702  								Name: "MaxFileSizeMB",
  2703  								Old:  "10",
  2704  								New:  "20",
  2705  							},
  2706  							{
  2707  								Type: DiffTypeEdited,
  2708  								Name: "MaxFiles",
  2709  								Old:  "1",
  2710  								New:  "2",
  2711  							},
  2712  						},
  2713  					},
  2714  				},
  2715  			},
  2716  		},
  2717  		{
  2718  			Name:       "LogConfig edited with context",
  2719  			Contextual: true,
  2720  			Old: &Task{
  2721  				LogConfig: &LogConfig{
  2722  					MaxFiles:      1,
  2723  					MaxFileSizeMB: 10,
  2724  				},
  2725  			},
  2726  			New: &Task{
  2727  				LogConfig: &LogConfig{
  2728  					MaxFiles:      1,
  2729  					MaxFileSizeMB: 20,
  2730  				},
  2731  			},
  2732  			Expected: &TaskDiff{
  2733  				Type: DiffTypeEdited,
  2734  				Objects: []*ObjectDiff{
  2735  					{
  2736  						Type: DiffTypeEdited,
  2737  						Name: "LogConfig",
  2738  						Fields: []*FieldDiff{
  2739  							{
  2740  								Type: DiffTypeEdited,
  2741  								Name: "MaxFileSizeMB",
  2742  								Old:  "10",
  2743  								New:  "20",
  2744  							},
  2745  							{
  2746  								Type: DiffTypeNone,
  2747  								Name: "MaxFiles",
  2748  								Old:  "1",
  2749  								New:  "1",
  2750  							},
  2751  						},
  2752  					},
  2753  				},
  2754  			},
  2755  		},
  2756  		{
  2757  			Name: "Artifacts edited",
  2758  			Old: &Task{
  2759  				Artifacts: []*TaskArtifact{
  2760  					{
  2761  						GetterSource: "foo",
  2762  						GetterOptions: map[string]string{
  2763  							"foo": "bar",
  2764  						},
  2765  						RelativeDest: "foo",
  2766  					},
  2767  					{
  2768  						GetterSource: "bar",
  2769  						GetterOptions: map[string]string{
  2770  							"bar": "baz",
  2771  						},
  2772  						GetterMode:   "dir",
  2773  						RelativeDest: "bar",
  2774  					},
  2775  				},
  2776  			},
  2777  			New: &Task{
  2778  				Artifacts: []*TaskArtifact{
  2779  					{
  2780  						GetterSource: "foo",
  2781  						GetterOptions: map[string]string{
  2782  							"foo": "bar",
  2783  						},
  2784  						RelativeDest: "foo",
  2785  					},
  2786  					{
  2787  						GetterSource: "bam",
  2788  						GetterOptions: map[string]string{
  2789  							"bam": "baz",
  2790  						},
  2791  						GetterMode:   "file",
  2792  						RelativeDest: "bam",
  2793  					},
  2794  				},
  2795  			},
  2796  			Expected: &TaskDiff{
  2797  				Type: DiffTypeEdited,
  2798  				Objects: []*ObjectDiff{
  2799  					{
  2800  						Type: DiffTypeAdded,
  2801  						Name: "Artifact",
  2802  						Fields: []*FieldDiff{
  2803  							{
  2804  								Type: DiffTypeAdded,
  2805  								Name: "GetterMode",
  2806  								Old:  "",
  2807  								New:  "file",
  2808  							},
  2809  							{
  2810  								Type: DiffTypeAdded,
  2811  								Name: "GetterOptions[bam]",
  2812  								Old:  "",
  2813  								New:  "baz",
  2814  							},
  2815  							{
  2816  								Type: DiffTypeAdded,
  2817  								Name: "GetterSource",
  2818  								Old:  "",
  2819  								New:  "bam",
  2820  							},
  2821  							{
  2822  								Type: DiffTypeAdded,
  2823  								Name: "RelativeDest",
  2824  								Old:  "",
  2825  								New:  "bam",
  2826  							},
  2827  						},
  2828  					},
  2829  					{
  2830  						Type: DiffTypeDeleted,
  2831  						Name: "Artifact",
  2832  						Fields: []*FieldDiff{
  2833  							{
  2834  								Type: DiffTypeDeleted,
  2835  								Name: "GetterMode",
  2836  								Old:  "dir",
  2837  								New:  "",
  2838  							},
  2839  							{
  2840  								Type: DiffTypeDeleted,
  2841  								Name: "GetterOptions[bar]",
  2842  								Old:  "baz",
  2843  								New:  "",
  2844  							},
  2845  							{
  2846  								Type: DiffTypeDeleted,
  2847  								Name: "GetterSource",
  2848  								Old:  "bar",
  2849  								New:  "",
  2850  							},
  2851  							{
  2852  								Type: DiffTypeDeleted,
  2853  								Name: "RelativeDest",
  2854  								Old:  "bar",
  2855  								New:  "",
  2856  							},
  2857  						},
  2858  					},
  2859  				},
  2860  			},
  2861  		},
  2862  		{
  2863  			Name: "Resources edited (no networks)",
  2864  			Old: &Task{
  2865  				Resources: &Resources{
  2866  					CPU:      100,
  2867  					MemoryMB: 100,
  2868  					DiskMB:   100,
  2869  					IOPS:     100,
  2870  				},
  2871  			},
  2872  			New: &Task{
  2873  				Resources: &Resources{
  2874  					CPU:      200,
  2875  					MemoryMB: 200,
  2876  					DiskMB:   200,
  2877  					IOPS:     200,
  2878  				},
  2879  			},
  2880  			Expected: &TaskDiff{
  2881  				Type: DiffTypeEdited,
  2882  				Objects: []*ObjectDiff{
  2883  					{
  2884  						Type: DiffTypeEdited,
  2885  						Name: "Resources",
  2886  						Fields: []*FieldDiff{
  2887  							{
  2888  								Type: DiffTypeEdited,
  2889  								Name: "CPU",
  2890  								Old:  "100",
  2891  								New:  "200",
  2892  							},
  2893  							{
  2894  								Type: DiffTypeEdited,
  2895  								Name: "DiskMB",
  2896  								Old:  "100",
  2897  								New:  "200",
  2898  							},
  2899  							{
  2900  								Type: DiffTypeEdited,
  2901  								Name: "IOPS",
  2902  								Old:  "100",
  2903  								New:  "200",
  2904  							},
  2905  							{
  2906  								Type: DiffTypeEdited,
  2907  								Name: "MemoryMB",
  2908  								Old:  "100",
  2909  								New:  "200",
  2910  							},
  2911  						},
  2912  					},
  2913  				},
  2914  			},
  2915  		},
  2916  		{
  2917  			Name:       "Resources edited (no networks) with context",
  2918  			Contextual: true,
  2919  			Old: &Task{
  2920  				Resources: &Resources{
  2921  					CPU:      100,
  2922  					MemoryMB: 100,
  2923  					DiskMB:   100,
  2924  					IOPS:     100,
  2925  				},
  2926  			},
  2927  			New: &Task{
  2928  				Resources: &Resources{
  2929  					CPU:      200,
  2930  					MemoryMB: 100,
  2931  					DiskMB:   200,
  2932  					IOPS:     100,
  2933  				},
  2934  			},
  2935  			Expected: &TaskDiff{
  2936  				Type: DiffTypeEdited,
  2937  				Objects: []*ObjectDiff{
  2938  					{
  2939  						Type: DiffTypeEdited,
  2940  						Name: "Resources",
  2941  						Fields: []*FieldDiff{
  2942  							{
  2943  								Type: DiffTypeEdited,
  2944  								Name: "CPU",
  2945  								Old:  "100",
  2946  								New:  "200",
  2947  							},
  2948  							{
  2949  								Type: DiffTypeEdited,
  2950  								Name: "DiskMB",
  2951  								Old:  "100",
  2952  								New:  "200",
  2953  							},
  2954  							{
  2955  								Type: DiffTypeNone,
  2956  								Name: "IOPS",
  2957  								Old:  "100",
  2958  								New:  "100",
  2959  							},
  2960  							{
  2961  								Type: DiffTypeNone,
  2962  								Name: "MemoryMB",
  2963  								Old:  "100",
  2964  								New:  "100",
  2965  							},
  2966  						},
  2967  					},
  2968  				},
  2969  			},
  2970  		},
  2971  		{
  2972  			Name: "Network Resources edited",
  2973  			Old: &Task{
  2974  				Resources: &Resources{
  2975  					Networks: []*NetworkResource{
  2976  						{
  2977  							Device: "foo",
  2978  							CIDR:   "foo",
  2979  							IP:     "foo",
  2980  							MBits:  100,
  2981  							ReservedPorts: []Port{
  2982  								{
  2983  									Label: "foo",
  2984  									Value: 80,
  2985  								},
  2986  							},
  2987  							DynamicPorts: []Port{
  2988  								{
  2989  									Label: "bar",
  2990  								},
  2991  							},
  2992  						},
  2993  					},
  2994  				},
  2995  			},
  2996  			New: &Task{
  2997  				Resources: &Resources{
  2998  					Networks: []*NetworkResource{
  2999  						{
  3000  							Device: "bar",
  3001  							CIDR:   "bar",
  3002  							IP:     "bar",
  3003  							MBits:  200,
  3004  							ReservedPorts: []Port{
  3005  								{
  3006  									Label: "foo",
  3007  									Value: 81,
  3008  								},
  3009  							},
  3010  							DynamicPorts: []Port{
  3011  								{
  3012  									Label: "baz",
  3013  								},
  3014  							},
  3015  						},
  3016  					},
  3017  				},
  3018  			},
  3019  			Expected: &TaskDiff{
  3020  				Type: DiffTypeEdited,
  3021  				Objects: []*ObjectDiff{
  3022  					{
  3023  						Type: DiffTypeEdited,
  3024  						Name: "Resources",
  3025  						Objects: []*ObjectDiff{
  3026  							{
  3027  								Type: DiffTypeAdded,
  3028  								Name: "Network",
  3029  								Fields: []*FieldDiff{
  3030  									{
  3031  										Type: DiffTypeAdded,
  3032  										Name: "MBits",
  3033  										Old:  "",
  3034  										New:  "200",
  3035  									},
  3036  								},
  3037  								Objects: []*ObjectDiff{
  3038  									{
  3039  										Type: DiffTypeAdded,
  3040  										Name: "Static Port",
  3041  										Fields: []*FieldDiff{
  3042  											{
  3043  												Type: DiffTypeAdded,
  3044  												Name: "Label",
  3045  												Old:  "",
  3046  												New:  "foo",
  3047  											},
  3048  											{
  3049  												Type: DiffTypeAdded,
  3050  												Name: "Value",
  3051  												Old:  "",
  3052  												New:  "81",
  3053  											},
  3054  										},
  3055  									},
  3056  									{
  3057  										Type: DiffTypeAdded,
  3058  										Name: "Dynamic Port",
  3059  										Fields: []*FieldDiff{
  3060  											{
  3061  												Type: DiffTypeAdded,
  3062  												Name: "Label",
  3063  												Old:  "",
  3064  												New:  "baz",
  3065  											},
  3066  										},
  3067  									},
  3068  								},
  3069  							},
  3070  							{
  3071  								Type: DiffTypeDeleted,
  3072  								Name: "Network",
  3073  								Fields: []*FieldDiff{
  3074  									{
  3075  										Type: DiffTypeDeleted,
  3076  										Name: "MBits",
  3077  										Old:  "100",
  3078  										New:  "",
  3079  									},
  3080  								},
  3081  								Objects: []*ObjectDiff{
  3082  									{
  3083  										Type: DiffTypeDeleted,
  3084  										Name: "Static Port",
  3085  										Fields: []*FieldDiff{
  3086  											{
  3087  												Type: DiffTypeDeleted,
  3088  												Name: "Label",
  3089  												Old:  "foo",
  3090  												New:  "",
  3091  											},
  3092  											{
  3093  												Type: DiffTypeDeleted,
  3094  												Name: "Value",
  3095  												Old:  "80",
  3096  												New:  "",
  3097  											},
  3098  										},
  3099  									},
  3100  									{
  3101  										Type: DiffTypeDeleted,
  3102  										Name: "Dynamic Port",
  3103  										Fields: []*FieldDiff{
  3104  											{
  3105  												Type: DiffTypeDeleted,
  3106  												Name: "Label",
  3107  												Old:  "bar",
  3108  												New:  "",
  3109  											},
  3110  										},
  3111  									},
  3112  								},
  3113  							},
  3114  						},
  3115  					},
  3116  				},
  3117  			},
  3118  		},
  3119  		{
  3120  			Name: "Config same",
  3121  			Old: &Task{
  3122  				Config: map[string]interface{}{
  3123  					"foo": 1,
  3124  					"bar": "bar",
  3125  					"bam": []string{"a", "b"},
  3126  					"baz": map[string]int{
  3127  						"a": 1,
  3128  						"b": 2,
  3129  					},
  3130  					"boom": &Port{
  3131  						Label: "boom_port",
  3132  					},
  3133  				},
  3134  			},
  3135  			New: &Task{
  3136  				Config: map[string]interface{}{
  3137  					"foo": 1,
  3138  					"bar": "bar",
  3139  					"bam": []string{"a", "b"},
  3140  					"baz": map[string]int{
  3141  						"a": 1,
  3142  						"b": 2,
  3143  					},
  3144  					"boom": &Port{
  3145  						Label: "boom_port",
  3146  					},
  3147  				},
  3148  			},
  3149  			Expected: &TaskDiff{
  3150  				Type: DiffTypeNone,
  3151  			},
  3152  		},
  3153  		{
  3154  			Name: "Config edited",
  3155  			Old: &Task{
  3156  				Config: map[string]interface{}{
  3157  					"foo": 1,
  3158  					"bar": "baz",
  3159  					"bam": []string{"a", "b"},
  3160  					"baz": map[string]int{
  3161  						"a": 1,
  3162  						"b": 2,
  3163  					},
  3164  					"boom": &Port{
  3165  						Label: "boom_port",
  3166  					},
  3167  				},
  3168  			},
  3169  			New: &Task{
  3170  				Config: map[string]interface{}{
  3171  					"foo": 2,
  3172  					"bar": "baz",
  3173  					"bam": []string{"a", "c", "d"},
  3174  					"baz": map[string]int{
  3175  						"b": 3,
  3176  						"c": 4,
  3177  					},
  3178  					"boom": &Port{
  3179  						Label: "boom_port2",
  3180  					},
  3181  				},
  3182  			},
  3183  			Expected: &TaskDiff{
  3184  				Type: DiffTypeEdited,
  3185  				Objects: []*ObjectDiff{
  3186  					{
  3187  						Type: DiffTypeEdited,
  3188  						Name: "Config",
  3189  						Fields: []*FieldDiff{
  3190  							{
  3191  								Type: DiffTypeEdited,
  3192  								Name: "bam[1]",
  3193  								Old:  "b",
  3194  								New:  "c",
  3195  							},
  3196  							{
  3197  								Type: DiffTypeAdded,
  3198  								Name: "bam[2]",
  3199  								Old:  "",
  3200  								New:  "d",
  3201  							},
  3202  							{
  3203  								Type: DiffTypeDeleted,
  3204  								Name: "baz[a]",
  3205  								Old:  "1",
  3206  								New:  "",
  3207  							},
  3208  							{
  3209  								Type: DiffTypeEdited,
  3210  								Name: "baz[b]",
  3211  								Old:  "2",
  3212  								New:  "3",
  3213  							},
  3214  							{
  3215  								Type: DiffTypeAdded,
  3216  								Name: "baz[c]",
  3217  								Old:  "",
  3218  								New:  "4",
  3219  							},
  3220  							{
  3221  								Type: DiffTypeEdited,
  3222  								Name: "boom.Label",
  3223  								Old:  "boom_port",
  3224  								New:  "boom_port2",
  3225  							},
  3226  							{
  3227  								Type: DiffTypeEdited,
  3228  								Name: "foo",
  3229  								Old:  "1",
  3230  								New:  "2",
  3231  							},
  3232  						},
  3233  					},
  3234  				},
  3235  			},
  3236  		},
  3237  		{
  3238  			Name:       "Config edited with context",
  3239  			Contextual: true,
  3240  			Old: &Task{
  3241  				Config: map[string]interface{}{
  3242  					"foo": 1,
  3243  					"bar": "baz",
  3244  					"bam": []string{"a", "b"},
  3245  					"baz": map[string]int{
  3246  						"a": 1,
  3247  						"b": 2,
  3248  					},
  3249  					"boom": &Port{
  3250  						Label: "boom_port",
  3251  					},
  3252  				},
  3253  			},
  3254  			New: &Task{
  3255  				Config: map[string]interface{}{
  3256  					"foo": 2,
  3257  					"bar": "baz",
  3258  					"bam": []string{"a", "c", "d"},
  3259  					"baz": map[string]int{
  3260  						"a": 1,
  3261  						"b": 2,
  3262  					},
  3263  					"boom": &Port{
  3264  						Label: "boom_port",
  3265  					},
  3266  				},
  3267  			},
  3268  			Expected: &TaskDiff{
  3269  				Type: DiffTypeEdited,
  3270  				Objects: []*ObjectDiff{
  3271  					{
  3272  						Type: DiffTypeEdited,
  3273  						Name: "Config",
  3274  						Fields: []*FieldDiff{
  3275  							{
  3276  								Type: DiffTypeNone,
  3277  								Name: "bam[0]",
  3278  								Old:  "a",
  3279  								New:  "a",
  3280  							},
  3281  							{
  3282  								Type: DiffTypeEdited,
  3283  								Name: "bam[1]",
  3284  								Old:  "b",
  3285  								New:  "c",
  3286  							},
  3287  							{
  3288  								Type: DiffTypeAdded,
  3289  								Name: "bam[2]",
  3290  								Old:  "",
  3291  								New:  "d",
  3292  							},
  3293  							{
  3294  								Type: DiffTypeNone,
  3295  								Name: "bar",
  3296  								Old:  "baz",
  3297  								New:  "baz",
  3298  							},
  3299  							{
  3300  								Type: DiffTypeNone,
  3301  								Name: "baz[a]",
  3302  								Old:  "1",
  3303  								New:  "1",
  3304  							},
  3305  							{
  3306  								Type: DiffTypeNone,
  3307  								Name: "baz[b]",
  3308  								Old:  "2",
  3309  								New:  "2",
  3310  							},
  3311  							{
  3312  								Type: DiffTypeNone,
  3313  								Name: "boom.Label",
  3314  								Old:  "boom_port",
  3315  								New:  "boom_port",
  3316  							},
  3317  							{
  3318  								Type: DiffTypeNone,
  3319  								Name: "boom.Value",
  3320  								Old:  "0",
  3321  								New:  "0",
  3322  							},
  3323  							{
  3324  								Type: DiffTypeEdited,
  3325  								Name: "foo",
  3326  								Old:  "1",
  3327  								New:  "2",
  3328  							},
  3329  						},
  3330  					},
  3331  				},
  3332  			},
  3333  		},
  3334  		{
  3335  			Name: "Services edited (no checks)",
  3336  			Old: &Task{
  3337  				Services: []*Service{
  3338  					{
  3339  						Name:      "foo",
  3340  						PortLabel: "foo",
  3341  					},
  3342  					{
  3343  						Name:      "bar",
  3344  						PortLabel: "bar",
  3345  					},
  3346  					{
  3347  						Name:      "baz",
  3348  						PortLabel: "baz",
  3349  					},
  3350  				},
  3351  			},
  3352  			New: &Task{
  3353  				Services: []*Service{
  3354  					{
  3355  						Name:      "bar",
  3356  						PortLabel: "bar",
  3357  					},
  3358  					{
  3359  						Name:      "baz",
  3360  						PortLabel: "baz2",
  3361  					},
  3362  					{
  3363  						Name:      "bam",
  3364  						PortLabel: "bam",
  3365  					},
  3366  				},
  3367  			},
  3368  			Expected: &TaskDiff{
  3369  				Type: DiffTypeEdited,
  3370  				Objects: []*ObjectDiff{
  3371  					{
  3372  						Type: DiffTypeEdited,
  3373  						Name: "Service",
  3374  						Fields: []*FieldDiff{
  3375  							{
  3376  								Type: DiffTypeEdited,
  3377  								Name: "PortLabel",
  3378  								Old:  "baz",
  3379  								New:  "baz2",
  3380  							},
  3381  						},
  3382  					},
  3383  					{
  3384  						Type: DiffTypeAdded,
  3385  						Name: "Service",
  3386  						Fields: []*FieldDiff{
  3387  							{
  3388  								Type: DiffTypeAdded,
  3389  								Name: "Name",
  3390  								Old:  "",
  3391  								New:  "bam",
  3392  							},
  3393  							{
  3394  								Type: DiffTypeAdded,
  3395  								Name: "PortLabel",
  3396  								Old:  "",
  3397  								New:  "bam",
  3398  							},
  3399  						},
  3400  					},
  3401  					{
  3402  						Type: DiffTypeDeleted,
  3403  						Name: "Service",
  3404  						Fields: []*FieldDiff{
  3405  							{
  3406  								Type: DiffTypeDeleted,
  3407  								Name: "Name",
  3408  								Old:  "foo",
  3409  								New:  "",
  3410  							},
  3411  							{
  3412  								Type: DiffTypeDeleted,
  3413  								Name: "PortLabel",
  3414  								Old:  "foo",
  3415  								New:  "",
  3416  							},
  3417  						},
  3418  					},
  3419  				},
  3420  			},
  3421  		},
  3422  		{
  3423  			Name:       "Services edited (no checks) with context",
  3424  			Contextual: true,
  3425  			Old: &Task{
  3426  				Services: []*Service{
  3427  					{
  3428  						Name:      "foo",
  3429  						PortLabel: "foo",
  3430  					},
  3431  				},
  3432  			},
  3433  			New: &Task{
  3434  				Services: []*Service{
  3435  					{
  3436  						Name:        "foo",
  3437  						PortLabel:   "bar",
  3438  						AddressMode: "driver",
  3439  					},
  3440  				},
  3441  			},
  3442  			Expected: &TaskDiff{
  3443  				Type: DiffTypeEdited,
  3444  				Objects: []*ObjectDiff{
  3445  					{
  3446  						Type: DiffTypeEdited,
  3447  						Name: "Service",
  3448  						Fields: []*FieldDiff{
  3449  							{
  3450  								Type: DiffTypeAdded,
  3451  								Name: "AddressMode",
  3452  								New:  "driver",
  3453  							},
  3454  							{
  3455  								Type: DiffTypeNone,
  3456  								Name: "Name",
  3457  								Old:  "foo",
  3458  								New:  "foo",
  3459  							},
  3460  							{
  3461  								Type: DiffTypeEdited,
  3462  								Name: "PortLabel",
  3463  								Old:  "foo",
  3464  								New:  "bar",
  3465  							},
  3466  						},
  3467  					},
  3468  				},
  3469  			},
  3470  		},
  3471  		{
  3472  			Name:       "Services tags edited (no checks) with context",
  3473  			Contextual: true,
  3474  			Old: &Task{
  3475  				Services: []*Service{
  3476  					{
  3477  						Tags:       []string{"foo", "bar"},
  3478  						CanaryTags: []string{"foo", "bar"},
  3479  					},
  3480  				},
  3481  			},
  3482  			New: &Task{
  3483  				Services: []*Service{
  3484  					{
  3485  						Tags:       []string{"bar", "bam"},
  3486  						CanaryTags: []string{"bar", "bam"},
  3487  					},
  3488  				},
  3489  			},
  3490  			Expected: &TaskDiff{
  3491  				Type: DiffTypeEdited,
  3492  				Objects: []*ObjectDiff{
  3493  					{
  3494  						Type: DiffTypeEdited,
  3495  						Name: "Service",
  3496  						Objects: []*ObjectDiff{
  3497  							{
  3498  								Type: DiffTypeEdited,
  3499  								Name: "CanaryTags",
  3500  								Fields: []*FieldDiff{
  3501  									{
  3502  										Type: DiffTypeAdded,
  3503  										Name: "CanaryTags",
  3504  										Old:  "",
  3505  										New:  "bam",
  3506  									},
  3507  									{
  3508  										Type: DiffTypeNone,
  3509  										Name: "CanaryTags",
  3510  										Old:  "bar",
  3511  										New:  "bar",
  3512  									},
  3513  									{
  3514  										Type: DiffTypeDeleted,
  3515  										Name: "CanaryTags",
  3516  										Old:  "foo",
  3517  										New:  "",
  3518  									},
  3519  								},
  3520  							},
  3521  							{
  3522  								Type: DiffTypeEdited,
  3523  								Name: "Tags",
  3524  								Fields: []*FieldDiff{
  3525  									{
  3526  										Type: DiffTypeAdded,
  3527  										Name: "Tags",
  3528  										Old:  "",
  3529  										New:  "bam",
  3530  									},
  3531  									{
  3532  										Type: DiffTypeNone,
  3533  										Name: "Tags",
  3534  										Old:  "bar",
  3535  										New:  "bar",
  3536  									},
  3537  									{
  3538  										Type: DiffTypeDeleted,
  3539  										Name: "Tags",
  3540  										Old:  "foo",
  3541  										New:  "",
  3542  									},
  3543  								},
  3544  							},
  3545  						},
  3546  						Fields: []*FieldDiff{
  3547  							{
  3548  								Type: DiffTypeNone,
  3549  								Name: "AddressMode",
  3550  							},
  3551  							{
  3552  								Type: DiffTypeNone,
  3553  								Name: "Name",
  3554  							},
  3555  							{
  3556  								Type: DiffTypeNone,
  3557  								Name: "PortLabel",
  3558  							},
  3559  						},
  3560  					},
  3561  				},
  3562  			},
  3563  		},
  3564  		{
  3565  			Name: "Service Checks edited",
  3566  			Old: &Task{
  3567  				Services: []*Service{
  3568  					{
  3569  						Name: "foo",
  3570  						Checks: []*ServiceCheck{
  3571  							{
  3572  								Name:     "foo",
  3573  								Type:     "http",
  3574  								Command:  "foo",
  3575  								Args:     []string{"foo"},
  3576  								Path:     "foo",
  3577  								Protocol: "http",
  3578  								Interval: 1 * time.Second,
  3579  								Timeout:  1 * time.Second,
  3580  								Header: map[string][]string{
  3581  									"Foo": {"bar"},
  3582  								},
  3583  							},
  3584  							{
  3585  								Name:     "bar",
  3586  								Type:     "http",
  3587  								Command:  "foo",
  3588  								Args:     []string{"foo"},
  3589  								Path:     "foo",
  3590  								Protocol: "http",
  3591  								Interval: 1 * time.Second,
  3592  								Timeout:  1 * time.Second,
  3593  							},
  3594  							{
  3595  								Name:     "baz",
  3596  								Type:     "http",
  3597  								Command:  "foo",
  3598  								Args:     []string{"foo"},
  3599  								Path:     "foo",
  3600  								Protocol: "http",
  3601  								Interval: 1 * time.Second,
  3602  								Timeout:  1 * time.Second,
  3603  							},
  3604  						},
  3605  					},
  3606  				},
  3607  			},
  3608  			New: &Task{
  3609  				Services: []*Service{
  3610  					{
  3611  						Name: "foo",
  3612  						Checks: []*ServiceCheck{
  3613  							{
  3614  								Name:     "bar",
  3615  								Type:     "http",
  3616  								Command:  "foo",
  3617  								Args:     []string{"foo"},
  3618  								Path:     "foo",
  3619  								Protocol: "http",
  3620  								Interval: 1 * time.Second,
  3621  								Timeout:  1 * time.Second,
  3622  							},
  3623  							{
  3624  								Name:     "baz",
  3625  								Type:     "tcp",
  3626  								Command:  "foo",
  3627  								Args:     []string{"foo"},
  3628  								Path:     "foo",
  3629  								Protocol: "http",
  3630  								Interval: 1 * time.Second,
  3631  								Timeout:  1 * time.Second,
  3632  								Header: map[string][]string{
  3633  									"Eggs": {"spam"},
  3634  								},
  3635  							},
  3636  							{
  3637  								Name:     "bam",
  3638  								Type:     "http",
  3639  								Command:  "foo",
  3640  								Args:     []string{"foo"},
  3641  								Path:     "foo",
  3642  								Protocol: "http",
  3643  								Interval: 1 * time.Second,
  3644  								Timeout:  1 * time.Second,
  3645  							},
  3646  						},
  3647  					},
  3648  				},
  3649  			},
  3650  			Expected: &TaskDiff{
  3651  				Type: DiffTypeEdited,
  3652  				Objects: []*ObjectDiff{
  3653  					{
  3654  						Type: DiffTypeEdited,
  3655  						Name: "Service",
  3656  						Objects: []*ObjectDiff{
  3657  							{
  3658  								Type: DiffTypeEdited,
  3659  								Name: "Check",
  3660  								Fields: []*FieldDiff{
  3661  									{
  3662  										Type: DiffTypeEdited,
  3663  										Name: "Type",
  3664  										Old:  "http",
  3665  										New:  "tcp",
  3666  									},
  3667  								},
  3668  								Objects: []*ObjectDiff{
  3669  									{
  3670  										Type: DiffTypeAdded,
  3671  										Name: "Header",
  3672  										Fields: []*FieldDiff{
  3673  											{
  3674  												Type: DiffTypeAdded,
  3675  												Name: "Eggs[0]",
  3676  												Old:  "",
  3677  												New:  "spam",
  3678  											},
  3679  										},
  3680  									},
  3681  								},
  3682  							},
  3683  							{
  3684  								Type: DiffTypeAdded,
  3685  								Name: "Check",
  3686  								Fields: []*FieldDiff{
  3687  									{
  3688  										Type: DiffTypeAdded,
  3689  										Name: "Command",
  3690  										Old:  "",
  3691  										New:  "foo",
  3692  									},
  3693  									{
  3694  										Type: DiffTypeAdded,
  3695  										Name: "GRPCUseTLS",
  3696  										Old:  "",
  3697  										New:  "false",
  3698  									},
  3699  									{
  3700  										Type: DiffTypeAdded,
  3701  										Name: "Interval",
  3702  										Old:  "",
  3703  										New:  "1000000000",
  3704  									},
  3705  									{
  3706  										Type: DiffTypeAdded,
  3707  										Name: "Name",
  3708  										Old:  "",
  3709  										New:  "bam",
  3710  									},
  3711  									{
  3712  										Type: DiffTypeAdded,
  3713  										Name: "Path",
  3714  										Old:  "",
  3715  										New:  "foo",
  3716  									},
  3717  									{
  3718  										Type: DiffTypeAdded,
  3719  										Name: "Protocol",
  3720  										Old:  "",
  3721  										New:  "http",
  3722  									},
  3723  									{
  3724  										Type: DiffTypeAdded,
  3725  										Name: "TLSSkipVerify",
  3726  										Old:  "",
  3727  										New:  "false",
  3728  									},
  3729  									{
  3730  										Type: DiffTypeAdded,
  3731  										Name: "Timeout",
  3732  										Old:  "",
  3733  										New:  "1000000000",
  3734  									},
  3735  									{
  3736  										Type: DiffTypeAdded,
  3737  										Name: "Type",
  3738  										Old:  "",
  3739  										New:  "http",
  3740  									},
  3741  								},
  3742  							},
  3743  							{
  3744  								Type: DiffTypeDeleted,
  3745  								Name: "Check",
  3746  								Fields: []*FieldDiff{
  3747  									{
  3748  										Type: DiffTypeDeleted,
  3749  										Name: "Command",
  3750  										Old:  "foo",
  3751  										New:  "",
  3752  									},
  3753  									{
  3754  										Type: DiffTypeDeleted,
  3755  										Name: "GRPCUseTLS",
  3756  										Old:  "false",
  3757  										New:  "",
  3758  									},
  3759  									{
  3760  										Type: DiffTypeDeleted,
  3761  										Name: "Interval",
  3762  										Old:  "1000000000",
  3763  										New:  "",
  3764  									},
  3765  									{
  3766  										Type: DiffTypeDeleted,
  3767  										Name: "Name",
  3768  										Old:  "foo",
  3769  										New:  "",
  3770  									},
  3771  									{
  3772  										Type: DiffTypeDeleted,
  3773  										Name: "Path",
  3774  										Old:  "foo",
  3775  										New:  "",
  3776  									},
  3777  									{
  3778  										Type: DiffTypeDeleted,
  3779  										Name: "Protocol",
  3780  										Old:  "http",
  3781  										New:  "",
  3782  									},
  3783  									{
  3784  										Type: DiffTypeDeleted,
  3785  										Name: "TLSSkipVerify",
  3786  										Old:  "false",
  3787  										New:  "",
  3788  									},
  3789  									{
  3790  										Type: DiffTypeDeleted,
  3791  										Name: "Timeout",
  3792  										Old:  "1000000000",
  3793  										New:  "",
  3794  									},
  3795  									{
  3796  										Type: DiffTypeDeleted,
  3797  										Name: "Type",
  3798  										Old:  "http",
  3799  										New:  "",
  3800  									},
  3801  								},
  3802  								Objects: []*ObjectDiff{
  3803  									{
  3804  										Type: DiffTypeDeleted,
  3805  										Name: "Header",
  3806  										Fields: []*FieldDiff{
  3807  											{
  3808  												Type: DiffTypeDeleted,
  3809  												Name: "Foo[0]",
  3810  												Old:  "bar",
  3811  											},
  3812  										},
  3813  									},
  3814  								},
  3815  							},
  3816  						},
  3817  					},
  3818  				},
  3819  			},
  3820  		},
  3821  		{
  3822  			Name:       "Service Checks edited with context",
  3823  			Contextual: true,
  3824  			Old: &Task{
  3825  				Services: []*Service{
  3826  					{
  3827  						Name: "foo",
  3828  						Checks: []*ServiceCheck{
  3829  							{
  3830  								Name:          "foo",
  3831  								Type:          "http",
  3832  								Command:       "foo",
  3833  								Args:          []string{"foo"},
  3834  								Path:          "foo",
  3835  								Protocol:      "http",
  3836  								Interval:      1 * time.Second,
  3837  								Timeout:       1 * time.Second,
  3838  								InitialStatus: "critical",
  3839  								Header: map[string][]string{
  3840  									"Foo": {"bar"},
  3841  								},
  3842  							},
  3843  						},
  3844  					},
  3845  				},
  3846  			},
  3847  			New: &Task{
  3848  				Services: []*Service{
  3849  					{
  3850  						Name: "foo",
  3851  						Checks: []*ServiceCheck{
  3852  							{
  3853  								Name:          "foo",
  3854  								Type:          "tcp",
  3855  								Command:       "foo",
  3856  								Args:          []string{"foo"},
  3857  								Path:          "foo",
  3858  								Protocol:      "http",
  3859  								Interval:      1 * time.Second,
  3860  								Timeout:       1 * time.Second,
  3861  								InitialStatus: "passing",
  3862  								Method:        "POST",
  3863  								Header: map[string][]string{
  3864  									"Foo":  {"bar", "baz"},
  3865  									"Eggs": {"spam"},
  3866  								},
  3867  							},
  3868  						},
  3869  					},
  3870  				},
  3871  			},
  3872  			Expected: &TaskDiff{
  3873  				Type: DiffTypeEdited,
  3874  				Objects: []*ObjectDiff{
  3875  					{
  3876  						Type: DiffTypeEdited,
  3877  						Name: "Service",
  3878  						Fields: []*FieldDiff{
  3879  							{
  3880  								Type: DiffTypeNone,
  3881  								Name: "AddressMode",
  3882  								Old:  "",
  3883  								New:  "",
  3884  							},
  3885  							{
  3886  								Type: DiffTypeNone,
  3887  								Name: "Name",
  3888  								Old:  "foo",
  3889  								New:  "foo",
  3890  							},
  3891  							{
  3892  								Type: DiffTypeNone,
  3893  								Name: "PortLabel",
  3894  								Old:  "",
  3895  								New:  "",
  3896  							},
  3897  						},
  3898  						Objects: []*ObjectDiff{
  3899  							{
  3900  								Type: DiffTypeEdited,
  3901  								Name: "Check",
  3902  								Fields: []*FieldDiff{
  3903  									{
  3904  										Type: DiffTypeNone,
  3905  										Name: "AddressMode",
  3906  										Old:  "",
  3907  										New:  "",
  3908  									},
  3909  									{
  3910  										Type: DiffTypeNone,
  3911  										Name: "Command",
  3912  										Old:  "foo",
  3913  										New:  "foo",
  3914  									},
  3915  									{
  3916  										Type: DiffTypeNone,
  3917  										Name: "GRPCService",
  3918  										Old:  "",
  3919  										New:  "",
  3920  									},
  3921  									{
  3922  										Type: DiffTypeNone,
  3923  										Name: "GRPCUseTLS",
  3924  										Old:  "false",
  3925  										New:  "false",
  3926  									},
  3927  									{
  3928  										Type: DiffTypeEdited,
  3929  										Name: "InitialStatus",
  3930  										Old:  "critical",
  3931  										New:  "passing",
  3932  									},
  3933  									{
  3934  										Type: DiffTypeNone,
  3935  										Name: "Interval",
  3936  										Old:  "1000000000",
  3937  										New:  "1000000000",
  3938  									},
  3939  									{
  3940  										Type: DiffTypeAdded,
  3941  										Name: "Method",
  3942  										Old:  "",
  3943  										New:  "POST",
  3944  									},
  3945  									{
  3946  										Type: DiffTypeNone,
  3947  										Name: "Name",
  3948  										Old:  "foo",
  3949  										New:  "foo",
  3950  									},
  3951  									{
  3952  										Type: DiffTypeNone,
  3953  										Name: "Path",
  3954  										Old:  "foo",
  3955  										New:  "foo",
  3956  									},
  3957  									{
  3958  										Type: DiffTypeNone,
  3959  										Name: "PortLabel",
  3960  										Old:  "",
  3961  										New:  "",
  3962  									},
  3963  									{
  3964  										Type: DiffTypeNone,
  3965  										Name: "Protocol",
  3966  										Old:  "http",
  3967  										New:  "http",
  3968  									},
  3969  									{
  3970  										Type: DiffTypeNone,
  3971  										Name: "TLSSkipVerify",
  3972  										Old:  "false",
  3973  										New:  "false",
  3974  									},
  3975  									{
  3976  										Type: DiffTypeNone,
  3977  										Name: "Timeout",
  3978  										Old:  "1000000000",
  3979  										New:  "1000000000",
  3980  									},
  3981  									{
  3982  										Type: DiffTypeEdited,
  3983  										Name: "Type",
  3984  										Old:  "http",
  3985  										New:  "tcp",
  3986  									},
  3987  								},
  3988  								Objects: []*ObjectDiff{
  3989  									{
  3990  										Type: DiffTypeEdited,
  3991  										Name: "Header",
  3992  										Fields: []*FieldDiff{
  3993  											{
  3994  												Type: DiffTypeAdded,
  3995  												Name: "Eggs[0]",
  3996  												Old:  "",
  3997  												New:  "spam",
  3998  											},
  3999  											{
  4000  												Type: DiffTypeNone,
  4001  												Name: "Foo[0]",
  4002  												Old:  "bar",
  4003  												New:  "bar",
  4004  											},
  4005  											{
  4006  												Type: DiffTypeAdded,
  4007  												Name: "Foo[1]",
  4008  												Old:  "",
  4009  												New:  "baz",
  4010  											},
  4011  										},
  4012  									},
  4013  								},
  4014  							},
  4015  						},
  4016  					},
  4017  				},
  4018  			},
  4019  		},
  4020  		{
  4021  			Name: "CheckRestart edited",
  4022  			Old: &Task{
  4023  				Services: []*Service{
  4024  					{
  4025  						Name: "foo",
  4026  						Checks: []*ServiceCheck{
  4027  							{
  4028  								Name:     "foo",
  4029  								Type:     "http",
  4030  								Command:  "foo",
  4031  								Args:     []string{"foo"},
  4032  								Path:     "foo",
  4033  								Protocol: "http",
  4034  								Interval: 1 * time.Second,
  4035  								Timeout:  1 * time.Second,
  4036  							},
  4037  							{
  4038  								Name:     "bar",
  4039  								Type:     "http",
  4040  								Command:  "foo",
  4041  								Args:     []string{"foo"},
  4042  								Path:     "foo",
  4043  								Protocol: "http",
  4044  								Interval: 1 * time.Second,
  4045  								Timeout:  1 * time.Second,
  4046  								CheckRestart: &CheckRestart{
  4047  									Limit: 2,
  4048  									Grace: 2 * time.Second,
  4049  								},
  4050  							},
  4051  							{
  4052  								Name:     "baz",
  4053  								Type:     "http",
  4054  								Command:  "foo",
  4055  								Args:     []string{"foo"},
  4056  								Path:     "foo",
  4057  								Protocol: "http",
  4058  								Interval: 1 * time.Second,
  4059  								Timeout:  1 * time.Second,
  4060  								CheckRestart: &CheckRestart{
  4061  									Limit: 3,
  4062  									Grace: 3 * time.Second,
  4063  								},
  4064  							},
  4065  						},
  4066  					},
  4067  				},
  4068  			},
  4069  			New: &Task{
  4070  				Services: []*Service{
  4071  					{
  4072  						Name: "foo",
  4073  						Checks: []*ServiceCheck{
  4074  							{
  4075  								Name:     "foo",
  4076  								Type:     "http",
  4077  								Command:  "foo",
  4078  								Args:     []string{"foo"},
  4079  								Path:     "foo",
  4080  								Protocol: "http",
  4081  								Interval: 1 * time.Second,
  4082  								Timeout:  1 * time.Second,
  4083  								CheckRestart: &CheckRestart{
  4084  									Limit: 1,
  4085  									Grace: 1 * time.Second,
  4086  								},
  4087  							},
  4088  							{
  4089  								Name:     "bar",
  4090  								Type:     "http",
  4091  								Command:  "foo",
  4092  								Args:     []string{"foo"},
  4093  								Path:     "foo",
  4094  								Protocol: "http",
  4095  								Interval: 1 * time.Second,
  4096  								Timeout:  1 * time.Second,
  4097  							},
  4098  							{
  4099  								Name:     "baz",
  4100  								Type:     "http",
  4101  								Command:  "foo",
  4102  								Args:     []string{"foo"},
  4103  								Path:     "foo",
  4104  								Protocol: "http",
  4105  								Interval: 1 * time.Second,
  4106  								Timeout:  1 * time.Second,
  4107  								CheckRestart: &CheckRestart{
  4108  									Limit: 4,
  4109  									Grace: 4 * time.Second,
  4110  								},
  4111  							},
  4112  						},
  4113  					},
  4114  				},
  4115  			},
  4116  			Expected: &TaskDiff{
  4117  				Type: DiffTypeEdited,
  4118  				Objects: []*ObjectDiff{
  4119  					{
  4120  						Type: DiffTypeEdited,
  4121  						Name: "Service",
  4122  						Objects: []*ObjectDiff{
  4123  							{
  4124  								Type: DiffTypeEdited,
  4125  								Name: "Check",
  4126  								Objects: []*ObjectDiff{
  4127  									{
  4128  										Type: DiffTypeEdited,
  4129  										Name: "CheckRestart",
  4130  										Fields: []*FieldDiff{
  4131  											{
  4132  												Type: DiffTypeEdited,
  4133  												Name: "Grace",
  4134  												Old:  "3000000000",
  4135  												New:  "4000000000",
  4136  											},
  4137  											{
  4138  												Type: DiffTypeEdited,
  4139  												Name: "Limit",
  4140  												Old:  "3",
  4141  												New:  "4",
  4142  											},
  4143  										},
  4144  									},
  4145  								},
  4146  							},
  4147  							{
  4148  								Type: DiffTypeEdited,
  4149  								Name: "Check",
  4150  								Objects: []*ObjectDiff{
  4151  									{
  4152  										Type: DiffTypeAdded,
  4153  										Name: "CheckRestart",
  4154  										Fields: []*FieldDiff{
  4155  											{
  4156  												Type: DiffTypeAdded,
  4157  												Name: "Grace",
  4158  												New:  "1000000000",
  4159  											},
  4160  											{
  4161  												Type: DiffTypeAdded,
  4162  												Name: "IgnoreWarnings",
  4163  												New:  "false",
  4164  											},
  4165  											{
  4166  												Type: DiffTypeAdded,
  4167  												Name: "Limit",
  4168  												New:  "1",
  4169  											},
  4170  										},
  4171  									},
  4172  								},
  4173  							},
  4174  							{
  4175  								Type: DiffTypeEdited,
  4176  								Name: "Check",
  4177  								Objects: []*ObjectDiff{
  4178  									{
  4179  										Type: DiffTypeDeleted,
  4180  										Name: "CheckRestart",
  4181  										Fields: []*FieldDiff{
  4182  											{
  4183  												Type: DiffTypeDeleted,
  4184  												Name: "Grace",
  4185  												Old:  "2000000000",
  4186  											},
  4187  											{
  4188  												Type: DiffTypeDeleted,
  4189  												Name: "IgnoreWarnings",
  4190  												Old:  "false",
  4191  											},
  4192  											{
  4193  												Type: DiffTypeDeleted,
  4194  												Name: "Limit",
  4195  												Old:  "2",
  4196  											},
  4197  										},
  4198  									},
  4199  								},
  4200  							},
  4201  						},
  4202  					},
  4203  				},
  4204  			},
  4205  		},
  4206  		{
  4207  			Name: "Vault added",
  4208  			Old:  &Task{},
  4209  			New: &Task{
  4210  				Vault: &Vault{
  4211  					Policies:     []string{"foo", "bar"},
  4212  					Env:          true,
  4213  					ChangeMode:   "signal",
  4214  					ChangeSignal: "SIGUSR1",
  4215  				},
  4216  			},
  4217  			Expected: &TaskDiff{
  4218  				Type: DiffTypeEdited,
  4219  				Objects: []*ObjectDiff{
  4220  					{
  4221  						Type: DiffTypeAdded,
  4222  						Name: "Vault",
  4223  						Fields: []*FieldDiff{
  4224  							{
  4225  								Type: DiffTypeAdded,
  4226  								Name: "ChangeMode",
  4227  								Old:  "",
  4228  								New:  "signal",
  4229  							},
  4230  							{
  4231  								Type: DiffTypeAdded,
  4232  								Name: "ChangeSignal",
  4233  								Old:  "",
  4234  								New:  "SIGUSR1",
  4235  							},
  4236  							{
  4237  								Type: DiffTypeAdded,
  4238  								Name: "Env",
  4239  								Old:  "",
  4240  								New:  "true",
  4241  							},
  4242  						},
  4243  						Objects: []*ObjectDiff{
  4244  							{
  4245  								Type: DiffTypeAdded,
  4246  								Name: "Policies",
  4247  								Fields: []*FieldDiff{
  4248  									{
  4249  										Type: DiffTypeAdded,
  4250  										Name: "Policies",
  4251  										Old:  "",
  4252  										New:  "bar",
  4253  									},
  4254  									{
  4255  										Type: DiffTypeAdded,
  4256  										Name: "Policies",
  4257  										Old:  "",
  4258  										New:  "foo",
  4259  									},
  4260  								},
  4261  							},
  4262  						},
  4263  					},
  4264  				},
  4265  			},
  4266  		},
  4267  		{
  4268  			Name: "Vault deleted",
  4269  			Old: &Task{
  4270  				Vault: &Vault{
  4271  					Policies:     []string{"foo", "bar"},
  4272  					Env:          true,
  4273  					ChangeMode:   "signal",
  4274  					ChangeSignal: "SIGUSR1",
  4275  				},
  4276  			},
  4277  			New: &Task{},
  4278  			Expected: &TaskDiff{
  4279  				Type: DiffTypeEdited,
  4280  				Objects: []*ObjectDiff{
  4281  					{
  4282  						Type: DiffTypeDeleted,
  4283  						Name: "Vault",
  4284  						Fields: []*FieldDiff{
  4285  							{
  4286  								Type: DiffTypeDeleted,
  4287  								Name: "ChangeMode",
  4288  								Old:  "signal",
  4289  								New:  "",
  4290  							},
  4291  							{
  4292  								Type: DiffTypeDeleted,
  4293  								Name: "ChangeSignal",
  4294  								Old:  "SIGUSR1",
  4295  								New:  "",
  4296  							},
  4297  							{
  4298  								Type: DiffTypeDeleted,
  4299  								Name: "Env",
  4300  								Old:  "true",
  4301  								New:  "",
  4302  							},
  4303  						},
  4304  						Objects: []*ObjectDiff{
  4305  							{
  4306  								Type: DiffTypeDeleted,
  4307  								Name: "Policies",
  4308  								Fields: []*FieldDiff{
  4309  									{
  4310  										Type: DiffTypeDeleted,
  4311  										Name: "Policies",
  4312  										Old:  "bar",
  4313  										New:  "",
  4314  									},
  4315  									{
  4316  										Type: DiffTypeDeleted,
  4317  										Name: "Policies",
  4318  										Old:  "foo",
  4319  										New:  "",
  4320  									},
  4321  								},
  4322  							},
  4323  						},
  4324  					},
  4325  				},
  4326  			},
  4327  		},
  4328  		{
  4329  			Name: "Vault edited",
  4330  			Old: &Task{
  4331  				Vault: &Vault{
  4332  					Policies:     []string{"foo", "bar"},
  4333  					Env:          true,
  4334  					ChangeMode:   "signal",
  4335  					ChangeSignal: "SIGUSR1",
  4336  				},
  4337  			},
  4338  			New: &Task{
  4339  				Vault: &Vault{
  4340  					Policies:     []string{"bar", "baz"},
  4341  					Env:          false,
  4342  					ChangeMode:   "restart",
  4343  					ChangeSignal: "foo",
  4344  				},
  4345  			},
  4346  			Expected: &TaskDiff{
  4347  				Type: DiffTypeEdited,
  4348  				Objects: []*ObjectDiff{
  4349  					{
  4350  						Type: DiffTypeEdited,
  4351  						Name: "Vault",
  4352  						Fields: []*FieldDiff{
  4353  							{
  4354  								Type: DiffTypeEdited,
  4355  								Name: "ChangeMode",
  4356  								Old:  "signal",
  4357  								New:  "restart",
  4358  							},
  4359  							{
  4360  								Type: DiffTypeEdited,
  4361  								Name: "ChangeSignal",
  4362  								Old:  "SIGUSR1",
  4363  								New:  "foo",
  4364  							},
  4365  							{
  4366  								Type: DiffTypeEdited,
  4367  								Name: "Env",
  4368  								Old:  "true",
  4369  								New:  "false",
  4370  							},
  4371  						},
  4372  						Objects: []*ObjectDiff{
  4373  							{
  4374  								Type: DiffTypeEdited,
  4375  								Name: "Policies",
  4376  								Fields: []*FieldDiff{
  4377  									{
  4378  										Type: DiffTypeAdded,
  4379  										Name: "Policies",
  4380  										Old:  "",
  4381  										New:  "baz",
  4382  									},
  4383  									{
  4384  										Type: DiffTypeDeleted,
  4385  										Name: "Policies",
  4386  										Old:  "foo",
  4387  										New:  "",
  4388  									},
  4389  								},
  4390  							},
  4391  						},
  4392  					},
  4393  				},
  4394  			},
  4395  		},
  4396  		{
  4397  			Name:       "Vault edited with context",
  4398  			Contextual: true,
  4399  			Old: &Task{
  4400  				Vault: &Vault{
  4401  					Policies:     []string{"foo", "bar"},
  4402  					Env:          true,
  4403  					ChangeMode:   "signal",
  4404  					ChangeSignal: "SIGUSR1",
  4405  				},
  4406  			},
  4407  			New: &Task{
  4408  				Vault: &Vault{
  4409  					Policies:     []string{"bar", "baz"},
  4410  					Env:          true,
  4411  					ChangeMode:   "signal",
  4412  					ChangeSignal: "SIGUSR1",
  4413  				},
  4414  			},
  4415  			Expected: &TaskDiff{
  4416  				Type: DiffTypeEdited,
  4417  				Objects: []*ObjectDiff{
  4418  					{
  4419  						Type: DiffTypeEdited,
  4420  						Name: "Vault",
  4421  						Fields: []*FieldDiff{
  4422  							{
  4423  								Type: DiffTypeNone,
  4424  								Name: "ChangeMode",
  4425  								Old:  "signal",
  4426  								New:  "signal",
  4427  							},
  4428  							{
  4429  								Type: DiffTypeNone,
  4430  								Name: "ChangeSignal",
  4431  								Old:  "SIGUSR1",
  4432  								New:  "SIGUSR1",
  4433  							},
  4434  							{
  4435  								Type: DiffTypeNone,
  4436  								Name: "Env",
  4437  								Old:  "true",
  4438  								New:  "true",
  4439  							},
  4440  						},
  4441  						Objects: []*ObjectDiff{
  4442  							{
  4443  								Type: DiffTypeEdited,
  4444  								Name: "Policies",
  4445  								Fields: []*FieldDiff{
  4446  									{
  4447  										Type: DiffTypeAdded,
  4448  										Name: "Policies",
  4449  										Old:  "",
  4450  										New:  "baz",
  4451  									},
  4452  									{
  4453  										Type: DiffTypeNone,
  4454  										Name: "Policies",
  4455  										Old:  "bar",
  4456  										New:  "bar",
  4457  									},
  4458  									{
  4459  										Type: DiffTypeDeleted,
  4460  										Name: "Policies",
  4461  										Old:  "foo",
  4462  										New:  "",
  4463  									},
  4464  								},
  4465  							},
  4466  						},
  4467  					},
  4468  				},
  4469  			},
  4470  		},
  4471  		{
  4472  			Name: "Template edited",
  4473  			Old: &Task{
  4474  				Templates: []*Template{
  4475  					{
  4476  						SourcePath:   "foo",
  4477  						DestPath:     "bar",
  4478  						EmbeddedTmpl: "baz",
  4479  						ChangeMode:   "bam",
  4480  						ChangeSignal: "SIGHUP",
  4481  						Splay:        1,
  4482  						Perms:        "0644",
  4483  						VaultGrace:   3 * time.Second,
  4484  					},
  4485  					{
  4486  						SourcePath:   "foo2",
  4487  						DestPath:     "bar2",
  4488  						EmbeddedTmpl: "baz2",
  4489  						ChangeMode:   "bam2",
  4490  						ChangeSignal: "SIGHUP2",
  4491  						Splay:        2,
  4492  						Perms:        "0666",
  4493  						Envvars:      true,
  4494  						VaultGrace:   5 * time.Second,
  4495  					},
  4496  				},
  4497  			},
  4498  			New: &Task{
  4499  				Templates: []*Template{
  4500  					{
  4501  						SourcePath:   "foo",
  4502  						DestPath:     "bar",
  4503  						EmbeddedTmpl: "baz",
  4504  						ChangeMode:   "bam",
  4505  						ChangeSignal: "SIGHUP",
  4506  						Splay:        1,
  4507  						Perms:        "0644",
  4508  						VaultGrace:   3 * time.Second,
  4509  					},
  4510  					{
  4511  						SourcePath:   "foo3",
  4512  						DestPath:     "bar3",
  4513  						EmbeddedTmpl: "baz3",
  4514  						ChangeMode:   "bam3",
  4515  						ChangeSignal: "SIGHUP3",
  4516  						Splay:        3,
  4517  						Perms:        "0776",
  4518  						VaultGrace:   10 * time.Second,
  4519  					},
  4520  				},
  4521  			},
  4522  			Expected: &TaskDiff{
  4523  				Type: DiffTypeEdited,
  4524  				Objects: []*ObjectDiff{
  4525  					{
  4526  						Type: DiffTypeAdded,
  4527  						Name: "Template",
  4528  						Fields: []*FieldDiff{
  4529  							{
  4530  								Type: DiffTypeAdded,
  4531  								Name: "ChangeMode",
  4532  								Old:  "",
  4533  								New:  "bam3",
  4534  							},
  4535  							{
  4536  								Type: DiffTypeAdded,
  4537  								Name: "ChangeSignal",
  4538  								Old:  "",
  4539  								New:  "SIGHUP3",
  4540  							},
  4541  							{
  4542  								Type: DiffTypeAdded,
  4543  								Name: "DestPath",
  4544  								Old:  "",
  4545  								New:  "bar3",
  4546  							},
  4547  							{
  4548  								Type: DiffTypeAdded,
  4549  								Name: "EmbeddedTmpl",
  4550  								Old:  "",
  4551  								New:  "baz3",
  4552  							},
  4553  							{
  4554  								Type: DiffTypeAdded,
  4555  								Name: "Envvars",
  4556  								Old:  "",
  4557  								New:  "false",
  4558  							},
  4559  							{
  4560  								Type: DiffTypeAdded,
  4561  								Name: "Perms",
  4562  								Old:  "",
  4563  								New:  "0776",
  4564  							},
  4565  							{
  4566  								Type: DiffTypeAdded,
  4567  								Name: "SourcePath",
  4568  								Old:  "",
  4569  								New:  "foo3",
  4570  							},
  4571  							{
  4572  								Type: DiffTypeAdded,
  4573  								Name: "Splay",
  4574  								Old:  "",
  4575  								New:  "3",
  4576  							},
  4577  							{
  4578  								Type: DiffTypeAdded,
  4579  								Name: "VaultGrace",
  4580  								Old:  "",
  4581  								New:  "10000000000",
  4582  							},
  4583  						},
  4584  					},
  4585  					{
  4586  						Type: DiffTypeDeleted,
  4587  						Name: "Template",
  4588  						Fields: []*FieldDiff{
  4589  							{
  4590  								Type: DiffTypeDeleted,
  4591  								Name: "ChangeMode",
  4592  								Old:  "bam2",
  4593  								New:  "",
  4594  							},
  4595  							{
  4596  								Type: DiffTypeDeleted,
  4597  								Name: "ChangeSignal",
  4598  								Old:  "SIGHUP2",
  4599  								New:  "",
  4600  							},
  4601  							{
  4602  								Type: DiffTypeDeleted,
  4603  								Name: "DestPath",
  4604  								Old:  "bar2",
  4605  								New:  "",
  4606  							},
  4607  							{
  4608  								Type: DiffTypeDeleted,
  4609  								Name: "EmbeddedTmpl",
  4610  								Old:  "baz2",
  4611  								New:  "",
  4612  							},
  4613  							{
  4614  								Type: DiffTypeDeleted,
  4615  								Name: "Envvars",
  4616  								Old:  "true",
  4617  								New:  "",
  4618  							},
  4619  							{
  4620  								Type: DiffTypeDeleted,
  4621  								Name: "Perms",
  4622  								Old:  "0666",
  4623  								New:  "",
  4624  							},
  4625  							{
  4626  								Type: DiffTypeDeleted,
  4627  								Name: "SourcePath",
  4628  								Old:  "foo2",
  4629  								New:  "",
  4630  							},
  4631  							{
  4632  								Type: DiffTypeDeleted,
  4633  								Name: "Splay",
  4634  								Old:  "2",
  4635  								New:  "",
  4636  							},
  4637  							{
  4638  								Type: DiffTypeDeleted,
  4639  								Name: "VaultGrace",
  4640  								Old:  "5000000000",
  4641  								New:  "",
  4642  							},
  4643  						},
  4644  					},
  4645  				},
  4646  			},
  4647  		},
  4648  		{
  4649  			Name: "DispatchPayload added",
  4650  			Old:  &Task{},
  4651  			New: &Task{
  4652  				DispatchPayload: &DispatchPayloadConfig{
  4653  					File: "foo",
  4654  				},
  4655  			},
  4656  			Expected: &TaskDiff{
  4657  				Type: DiffTypeEdited,
  4658  				Objects: []*ObjectDiff{
  4659  					{
  4660  						Type: DiffTypeAdded,
  4661  						Name: "DispatchPayload",
  4662  						Fields: []*FieldDiff{
  4663  							{
  4664  								Type: DiffTypeAdded,
  4665  								Name: "File",
  4666  								Old:  "",
  4667  								New:  "foo",
  4668  							},
  4669  						},
  4670  					},
  4671  				},
  4672  			},
  4673  		},
  4674  		{
  4675  			Name: "DispatchPayload deleted",
  4676  			Old: &Task{
  4677  				DispatchPayload: &DispatchPayloadConfig{
  4678  					File: "foo",
  4679  				},
  4680  			},
  4681  			New: &Task{},
  4682  			Expected: &TaskDiff{
  4683  				Type: DiffTypeEdited,
  4684  				Objects: []*ObjectDiff{
  4685  					{
  4686  						Type: DiffTypeDeleted,
  4687  						Name: "DispatchPayload",
  4688  						Fields: []*FieldDiff{
  4689  							{
  4690  								Type: DiffTypeDeleted,
  4691  								Name: "File",
  4692  								Old:  "foo",
  4693  								New:  "",
  4694  							},
  4695  						},
  4696  					},
  4697  				},
  4698  			},
  4699  		},
  4700  		{
  4701  			Name: "Dispatch payload edited",
  4702  			Old: &Task{
  4703  				DispatchPayload: &DispatchPayloadConfig{
  4704  					File: "foo",
  4705  				},
  4706  			},
  4707  			New: &Task{
  4708  				DispatchPayload: &DispatchPayloadConfig{
  4709  					File: "bar",
  4710  				},
  4711  			},
  4712  			Expected: &TaskDiff{
  4713  				Type: DiffTypeEdited,
  4714  				Objects: []*ObjectDiff{
  4715  					{
  4716  						Type: DiffTypeEdited,
  4717  						Name: "DispatchPayload",
  4718  						Fields: []*FieldDiff{
  4719  							{
  4720  								Type: DiffTypeEdited,
  4721  								Name: "File",
  4722  								Old:  "foo",
  4723  								New:  "bar",
  4724  							},
  4725  						},
  4726  					},
  4727  				},
  4728  			},
  4729  		},
  4730  		{
  4731  			// Place holder for if more fields are added
  4732  			Name:       "DispatchPayload edited with context",
  4733  			Contextual: true,
  4734  			Old: &Task{
  4735  				DispatchPayload: &DispatchPayloadConfig{
  4736  					File: "foo",
  4737  				},
  4738  			},
  4739  			New: &Task{
  4740  				DispatchPayload: &DispatchPayloadConfig{
  4741  					File: "bar",
  4742  				},
  4743  			},
  4744  			Expected: &TaskDiff{
  4745  				Type: DiffTypeEdited,
  4746  				Objects: []*ObjectDiff{
  4747  					{
  4748  						Type: DiffTypeEdited,
  4749  						Name: "DispatchPayload",
  4750  						Fields: []*FieldDiff{
  4751  							{
  4752  								Type: DiffTypeEdited,
  4753  								Name: "File",
  4754  								Old:  "foo",
  4755  								New:  "bar",
  4756  							},
  4757  						},
  4758  					},
  4759  				},
  4760  			},
  4761  		},
  4762  	}
  4763  
  4764  	for i, c := range cases {
  4765  		t.Run(c.Name, func(t *testing.T) {
  4766  			actual, err := c.Old.Diff(c.New, c.Contextual)
  4767  			if c.Error && err == nil {
  4768  				t.Fatalf("case %d: expected errored", i+1)
  4769  			} else if err != nil {
  4770  				if !c.Error {
  4771  					t.Fatalf("case %d: errored %#v", i+1, err)
  4772  				} else {
  4773  					return
  4774  				}
  4775  			}
  4776  
  4777  			if !reflect.DeepEqual(actual, c.Expected) {
  4778  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  4779  					i+1, actual, c.Expected)
  4780  			}
  4781  		})
  4782  	}
  4783  }