github.com/uchennaokeke444/nomad@v0.11.8/nomad/structs/diff_test.go (about)

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