github.com/hernad/nomad@v1.6.112/nomad/structs/diff_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structs
     5  
     6  import (
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hernad/nomad/ci"
    12  	"github.com/hernad/nomad/helper/pointer"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestJobDiff(t *testing.T) {
    17  	ci.Parallel(t)
    18  
    19  	cases := []struct {
    20  		Old, New   *Job
    21  		Expected   *JobDiff
    22  		Error      bool
    23  		Contextual bool
    24  	}{
    25  		{
    26  			Old: nil,
    27  			New: nil,
    28  			Expected: &JobDiff{
    29  				Type: DiffTypeNone,
    30  			},
    31  		},
    32  		{
    33  			// Different IDs
    34  			Old: &Job{
    35  				ID: "foo",
    36  			},
    37  			New: &Job{
    38  				ID: "bar",
    39  			},
    40  			Error: true,
    41  		},
    42  		{
    43  			// Primitive only that is the same
    44  			Old: &Job{
    45  				Region:    "foo",
    46  				ID:        "foo",
    47  				Name:      "foo",
    48  				Type:      "batch",
    49  				Priority:  10,
    50  				AllAtOnce: true,
    51  				Meta: map[string]string{
    52  					"foo": "bar",
    53  				},
    54  			},
    55  			New: &Job{
    56  				Region:    "foo",
    57  				ID:        "foo",
    58  				Name:      "foo",
    59  				Type:      "batch",
    60  				Priority:  10,
    61  				AllAtOnce: true,
    62  				Meta: map[string]string{
    63  					"foo": "bar",
    64  				},
    65  			},
    66  			Expected: &JobDiff{
    67  				Type: DiffTypeNone,
    68  				ID:   "foo",
    69  			},
    70  		},
    71  		{
    72  			// Primitive only that is has diffs
    73  			Old: &Job{
    74  				Region:    "foo",
    75  				ID:        "foo",
    76  				Name:      "foo",
    77  				Type:      "batch",
    78  				Priority:  10,
    79  				AllAtOnce: true,
    80  				Meta: map[string]string{
    81  					"foo": "bar",
    82  				},
    83  			},
    84  			New: &Job{
    85  				Region:    "bar",
    86  				ID:        "foo",
    87  				Name:      "bar",
    88  				Type:      "system",
    89  				Priority:  100,
    90  				AllAtOnce: false,
    91  				Meta: map[string]string{
    92  					"foo": "baz",
    93  				},
    94  			},
    95  			Expected: &JobDiff{
    96  				Type: DiffTypeEdited,
    97  				ID:   "foo",
    98  				Fields: []*FieldDiff{
    99  					{
   100  						Type: DiffTypeEdited,
   101  						Name: "AllAtOnce",
   102  						Old:  "true",
   103  						New:  "false",
   104  					},
   105  					{
   106  						Type: DiffTypeEdited,
   107  						Name: "Meta[foo]",
   108  						Old:  "bar",
   109  						New:  "baz",
   110  					},
   111  					{
   112  						Type: DiffTypeEdited,
   113  						Name: "Name",
   114  						Old:  "foo",
   115  						New:  "bar",
   116  					},
   117  					{
   118  						Type: DiffTypeEdited,
   119  						Name: "Priority",
   120  						Old:  "10",
   121  						New:  "100",
   122  					},
   123  					{
   124  						Type: DiffTypeEdited,
   125  						Name: "Region",
   126  						Old:  "foo",
   127  						New:  "bar",
   128  					},
   129  					{
   130  						Type: DiffTypeEdited,
   131  						Name: "Type",
   132  						Old:  "batch",
   133  						New:  "system",
   134  					},
   135  				},
   136  			},
   137  		},
   138  		{
   139  			// Primitive only deleted job
   140  			Old: &Job{
   141  				Region:    "foo",
   142  				ID:        "foo",
   143  				Name:      "foo",
   144  				Type:      "batch",
   145  				Priority:  10,
   146  				AllAtOnce: true,
   147  				Meta: map[string]string{
   148  					"foo": "bar",
   149  				},
   150  			},
   151  			New: nil,
   152  			Expected: &JobDiff{
   153  				Type: DiffTypeDeleted,
   154  				ID:   "foo",
   155  				Fields: []*FieldDiff{
   156  					{
   157  						Type: DiffTypeDeleted,
   158  						Name: "AllAtOnce",
   159  						Old:  "true",
   160  						New:  "",
   161  					},
   162  					{
   163  						Type: DiffTypeDeleted,
   164  						Name: "Dispatched",
   165  						Old:  "false",
   166  						New:  "",
   167  					},
   168  					{
   169  						Type: DiffTypeDeleted,
   170  						Name: "Meta[foo]",
   171  						Old:  "bar",
   172  						New:  "",
   173  					},
   174  					{
   175  						Type: DiffTypeDeleted,
   176  						Name: "Name",
   177  						Old:  "foo",
   178  						New:  "",
   179  					},
   180  					{
   181  						Type: DiffTypeDeleted,
   182  						Name: "Priority",
   183  						Old:  "10",
   184  						New:  "",
   185  					},
   186  					{
   187  						Type: DiffTypeDeleted,
   188  						Name: "Region",
   189  						Old:  "foo",
   190  						New:  "",
   191  					},
   192  					{
   193  						Type: DiffTypeDeleted,
   194  						Name: "Stop",
   195  						Old:  "false",
   196  						New:  "",
   197  					},
   198  					{
   199  						Type: DiffTypeDeleted,
   200  						Name: "Type",
   201  						Old:  "batch",
   202  						New:  "",
   203  					},
   204  				},
   205  			},
   206  		},
   207  		{
   208  			// Primitive only added job
   209  			Old: nil,
   210  			New: &Job{
   211  				Region:    "foo",
   212  				ID:        "foo",
   213  				Name:      "foo",
   214  				Type:      "batch",
   215  				Priority:  10,
   216  				AllAtOnce: true,
   217  				Meta: map[string]string{
   218  					"foo": "bar",
   219  				},
   220  			},
   221  			Expected: &JobDiff{
   222  				Type: DiffTypeAdded,
   223  				ID:   "foo",
   224  				Fields: []*FieldDiff{
   225  					{
   226  						Type: DiffTypeAdded,
   227  						Name: "AllAtOnce",
   228  						Old:  "",
   229  						New:  "true",
   230  					},
   231  					{
   232  						Type: DiffTypeAdded,
   233  						Name: "Dispatched",
   234  						Old:  "",
   235  						New:  "false",
   236  					},
   237  					{
   238  						Type: DiffTypeAdded,
   239  						Name: "Meta[foo]",
   240  						Old:  "",
   241  						New:  "bar",
   242  					},
   243  					{
   244  						Type: DiffTypeAdded,
   245  						Name: "Name",
   246  						Old:  "",
   247  						New:  "foo",
   248  					},
   249  					{
   250  						Type: DiffTypeAdded,
   251  						Name: "Priority",
   252  						Old:  "",
   253  						New:  "10",
   254  					},
   255  					{
   256  						Type: DiffTypeAdded,
   257  						Name: "Region",
   258  						Old:  "",
   259  						New:  "foo",
   260  					},
   261  					{
   262  						Type: DiffTypeAdded,
   263  						Name: "Stop",
   264  						Old:  "",
   265  						New:  "false",
   266  					},
   267  					{
   268  						Type: DiffTypeAdded,
   269  						Name: "Type",
   270  						Old:  "",
   271  						New:  "batch",
   272  					},
   273  				},
   274  			},
   275  		},
   276  		{
   277  			// Map diff
   278  			Old: &Job{
   279  				Meta: map[string]string{
   280  					"foo": "foo",
   281  					"bar": "bar",
   282  				},
   283  			},
   284  			New: &Job{
   285  				Meta: map[string]string{
   286  					"bar": "bar",
   287  					"baz": "baz",
   288  				},
   289  			},
   290  			Expected: &JobDiff{
   291  				Type: DiffTypeEdited,
   292  				Fields: []*FieldDiff{
   293  					{
   294  						Type: DiffTypeAdded,
   295  						Name: "Meta[baz]",
   296  						Old:  "",
   297  						New:  "baz",
   298  					},
   299  					{
   300  						Type: DiffTypeDeleted,
   301  						Name: "Meta[foo]",
   302  						Old:  "foo",
   303  						New:  "",
   304  					},
   305  				},
   306  			},
   307  		},
   308  		{
   309  			// Datacenter diff both added and removed
   310  			Old: &Job{
   311  				Datacenters: []string{"foo", "bar"},
   312  			},
   313  			New: &Job{
   314  				Datacenters: []string{"baz", "bar"},
   315  			},
   316  			Expected: &JobDiff{
   317  				Type: DiffTypeEdited,
   318  				Objects: []*ObjectDiff{
   319  					{
   320  						Type: DiffTypeEdited,
   321  						Name: "Datacenters",
   322  						Fields: []*FieldDiff{
   323  							{
   324  								Type: DiffTypeAdded,
   325  								Name: "Datacenters",
   326  								Old:  "",
   327  								New:  "baz",
   328  							},
   329  							{
   330  								Type: DiffTypeDeleted,
   331  								Name: "Datacenters",
   332  								Old:  "foo",
   333  								New:  "",
   334  							},
   335  						},
   336  					},
   337  				},
   338  			},
   339  		},
   340  		{
   341  			// Datacenter diff just added
   342  			Old: &Job{
   343  				Datacenters: []string{"foo", "bar"},
   344  			},
   345  			New: &Job{
   346  				Datacenters: []string{"foo", "bar", "baz"},
   347  			},
   348  			Expected: &JobDiff{
   349  				Type: DiffTypeEdited,
   350  				Objects: []*ObjectDiff{
   351  					{
   352  						Type: DiffTypeAdded,
   353  						Name: "Datacenters",
   354  						Fields: []*FieldDiff{
   355  							{
   356  								Type: DiffTypeAdded,
   357  								Name: "Datacenters",
   358  								Old:  "",
   359  								New:  "baz",
   360  							},
   361  						},
   362  					},
   363  				},
   364  			},
   365  		},
   366  		{
   367  			// Datacenter diff just deleted
   368  			Old: &Job{
   369  				Datacenters: []string{"foo", "bar"},
   370  			},
   371  			New: &Job{
   372  				Datacenters: []string{"foo"},
   373  			},
   374  			Expected: &JobDiff{
   375  				Type: DiffTypeEdited,
   376  				Objects: []*ObjectDiff{
   377  					{
   378  						Type: DiffTypeDeleted,
   379  						Name: "Datacenters",
   380  						Fields: []*FieldDiff{
   381  							{
   382  								Type: DiffTypeDeleted,
   383  								Name: "Datacenters",
   384  								Old:  "bar",
   385  								New:  "",
   386  							},
   387  						},
   388  					},
   389  				},
   390  			},
   391  		},
   392  		{
   393  			// Datacenter contextual no change
   394  			Contextual: true,
   395  			Old: &Job{
   396  				Datacenters: []string{"foo", "bar"},
   397  			},
   398  			New: &Job{
   399  				Datacenters: []string{"foo", "bar"},
   400  			},
   401  			Expected: &JobDiff{
   402  				Type: DiffTypeNone,
   403  			},
   404  		},
   405  		{
   406  			// Datacenter contextual
   407  			Contextual: true,
   408  			Old: &Job{
   409  				Datacenters: []string{"foo", "bar"},
   410  			},
   411  			New: &Job{
   412  				Datacenters: []string{"foo", "bar", "baz"},
   413  			},
   414  			Expected: &JobDiff{
   415  				Type: DiffTypeEdited,
   416  				Objects: []*ObjectDiff{
   417  					{
   418  						Type: DiffTypeAdded,
   419  						Name: "Datacenters",
   420  						Fields: []*FieldDiff{
   421  							{
   422  								Type: DiffTypeAdded,
   423  								Name: "Datacenters",
   424  								Old:  "",
   425  								New:  "baz",
   426  							},
   427  							{
   428  								Type: DiffTypeNone,
   429  								Name: "Datacenters",
   430  								Old:  "bar",
   431  								New:  "bar",
   432  							},
   433  							{
   434  								Type: DiffTypeNone,
   435  								Name: "Datacenters",
   436  								Old:  "foo",
   437  								New:  "foo",
   438  							},
   439  						},
   440  					},
   441  				},
   442  			},
   443  		},
   444  
   445  		{
   446  			// NodePool added
   447  			Old: &Job{},
   448  			New: &Job{
   449  				NodePool: "default",
   450  			},
   451  			Expected: &JobDiff{
   452  				Type: DiffTypeEdited,
   453  				Fields: []*FieldDiff{
   454  					{
   455  						Type: DiffTypeAdded,
   456  						Name: "NodePool",
   457  						Old:  "",
   458  						New:  "default",
   459  					},
   460  				},
   461  			},
   462  		},
   463  		{
   464  			// NodePool removed
   465  			Old: &Job{
   466  				NodePool: "default",
   467  			},
   468  			New: &Job{},
   469  			Expected: &JobDiff{
   470  				Type: DiffTypeEdited,
   471  				Fields: []*FieldDiff{
   472  					{
   473  						Type: DiffTypeDeleted,
   474  						Name: "NodePool",
   475  						Old:  "default",
   476  						New:  "",
   477  					},
   478  				},
   479  			},
   480  		},
   481  		{
   482  			// NodePool changed
   483  			Old: &Job{
   484  				NodePool: "default",
   485  			},
   486  			New: &Job{
   487  				NodePool: "foo",
   488  			},
   489  			Expected: &JobDiff{
   490  				Type: DiffTypeEdited,
   491  				Fields: []*FieldDiff{
   492  					{
   493  						Type: DiffTypeEdited,
   494  						Name: "NodePool",
   495  						Old:  "default",
   496  						New:  "foo",
   497  					},
   498  				},
   499  			},
   500  		},
   501  		{
   502  			// NodePool unchanged
   503  			Old: &Job{
   504  				NodePool: "foo",
   505  			},
   506  			New: &Job{
   507  				NodePool: "foo",
   508  			},
   509  			Expected: &JobDiff{
   510  				Type: DiffTypeNone,
   511  			},
   512  		},
   513  
   514  		{
   515  			// Periodic added
   516  			Old: &Job{},
   517  			New: &Job{
   518  				Periodic: &PeriodicConfig{
   519  					Enabled:         false,
   520  					Spec:            "*/15 * * * * *",
   521  					SpecType:        "foo",
   522  					ProhibitOverlap: false,
   523  					TimeZone:        "Europe/Minsk",
   524  				},
   525  			},
   526  			Expected: &JobDiff{
   527  				Type: DiffTypeEdited,
   528  				Objects: []*ObjectDiff{
   529  					{
   530  						Type: DiffTypeAdded,
   531  						Name: "Periodic",
   532  						Fields: []*FieldDiff{
   533  							{
   534  								Type: DiffTypeAdded,
   535  								Name: "Enabled",
   536  								Old:  "",
   537  								New:  "false",
   538  							},
   539  							{
   540  								Type: DiffTypeAdded,
   541  								Name: "ProhibitOverlap",
   542  								Old:  "",
   543  								New:  "false",
   544  							},
   545  							{
   546  								Type: DiffTypeAdded,
   547  								Name: "Spec",
   548  								Old:  "",
   549  								New:  "*/15 * * * * *",
   550  							},
   551  							{
   552  								Type: DiffTypeAdded,
   553  								Name: "SpecType",
   554  								Old:  "",
   555  								New:  "foo",
   556  							},
   557  							{
   558  								Type: DiffTypeAdded,
   559  								Name: "TimeZone",
   560  								Old:  "",
   561  								New:  "Europe/Minsk",
   562  							},
   563  						},
   564  					},
   565  				},
   566  			},
   567  		},
   568  		{
   569  			// Periodic deleted
   570  			Old: &Job{
   571  				Periodic: &PeriodicConfig{
   572  					Enabled:         false,
   573  					Spec:            "*/15 * * * * *",
   574  					SpecType:        "foo",
   575  					ProhibitOverlap: false,
   576  					TimeZone:        "Europe/Minsk",
   577  				},
   578  			},
   579  			New: &Job{},
   580  			Expected: &JobDiff{
   581  				Type: DiffTypeEdited,
   582  				Objects: []*ObjectDiff{
   583  					{
   584  						Type: DiffTypeDeleted,
   585  						Name: "Periodic",
   586  						Fields: []*FieldDiff{
   587  							{
   588  								Type: DiffTypeDeleted,
   589  								Name: "Enabled",
   590  								Old:  "false",
   591  								New:  "",
   592  							},
   593  							{
   594  								Type: DiffTypeDeleted,
   595  								Name: "ProhibitOverlap",
   596  								Old:  "false",
   597  								New:  "",
   598  							},
   599  							{
   600  								Type: DiffTypeDeleted,
   601  								Name: "Spec",
   602  								Old:  "*/15 * * * * *",
   603  								New:  "",
   604  							},
   605  							{
   606  								Type: DiffTypeDeleted,
   607  								Name: "SpecType",
   608  								Old:  "foo",
   609  								New:  "",
   610  							},
   611  							{
   612  								Type: DiffTypeDeleted,
   613  								Name: "TimeZone",
   614  								Old:  "Europe/Minsk",
   615  								New:  "",
   616  							},
   617  						},
   618  					},
   619  				},
   620  			},
   621  		},
   622  		{
   623  			// Periodic edited
   624  			Old: &Job{
   625  				Periodic: &PeriodicConfig{
   626  					Enabled:         false,
   627  					Spec:            "*/15 * * * * *",
   628  					SpecType:        "foo",
   629  					ProhibitOverlap: false,
   630  					TimeZone:        "Europe/Minsk",
   631  				},
   632  			},
   633  			New: &Job{
   634  				Periodic: &PeriodicConfig{
   635  					Enabled:         true,
   636  					Spec:            "* * * * * *",
   637  					SpecType:        "cron",
   638  					ProhibitOverlap: true,
   639  					TimeZone:        "America/Los_Angeles",
   640  				},
   641  			},
   642  			Expected: &JobDiff{
   643  				Type: DiffTypeEdited,
   644  				Objects: []*ObjectDiff{
   645  					{
   646  						Type: DiffTypeEdited,
   647  						Name: "Periodic",
   648  						Fields: []*FieldDiff{
   649  							{
   650  								Type: DiffTypeEdited,
   651  								Name: "Enabled",
   652  								Old:  "false",
   653  								New:  "true",
   654  							},
   655  							{
   656  								Type: DiffTypeEdited,
   657  								Name: "ProhibitOverlap",
   658  								Old:  "false",
   659  								New:  "true",
   660  							},
   661  							{
   662  								Type: DiffTypeEdited,
   663  								Name: "Spec",
   664  								Old:  "*/15 * * * * *",
   665  								New:  "* * * * * *",
   666  							},
   667  							{
   668  								Type: DiffTypeEdited,
   669  								Name: "SpecType",
   670  								Old:  "foo",
   671  								New:  "cron",
   672  							},
   673  							{
   674  								Type: DiffTypeEdited,
   675  								Name: "TimeZone",
   676  								Old:  "Europe/Minsk",
   677  								New:  "America/Los_Angeles",
   678  							},
   679  						},
   680  					},
   681  				},
   682  			},
   683  		},
   684  		{
   685  			// Periodic edited with context
   686  			Contextual: true,
   687  			Old: &Job{
   688  				Periodic: &PeriodicConfig{
   689  					Enabled:         false,
   690  					Spec:            "*/15 * * * * *",
   691  					SpecType:        "foo",
   692  					ProhibitOverlap: false,
   693  					TimeZone:        "Europe/Minsk",
   694  				},
   695  			},
   696  			New: &Job{
   697  				Periodic: &PeriodicConfig{
   698  					Enabled:         true,
   699  					Spec:            "* * * * * *",
   700  					SpecType:        "foo",
   701  					ProhibitOverlap: false,
   702  					TimeZone:        "Europe/Minsk",
   703  				},
   704  			},
   705  			Expected: &JobDiff{
   706  				Type: DiffTypeEdited,
   707  				Objects: []*ObjectDiff{
   708  					{
   709  						Type: DiffTypeEdited,
   710  						Name: "Periodic",
   711  						Fields: []*FieldDiff{
   712  							{
   713  								Type: DiffTypeEdited,
   714  								Name: "Enabled",
   715  								Old:  "false",
   716  								New:  "true",
   717  							},
   718  							{
   719  								Type: DiffTypeNone,
   720  								Name: "ProhibitOverlap",
   721  								Old:  "false",
   722  								New:  "false",
   723  							},
   724  							{
   725  								Type: DiffTypeEdited,
   726  								Name: "Spec",
   727  								Old:  "*/15 * * * * *",
   728  								New:  "* * * * * *",
   729  							},
   730  							{
   731  								Type: DiffTypeNone,
   732  								Name: "SpecType",
   733  								Old:  "foo",
   734  								New:  "foo",
   735  							},
   736  							{
   737  								Type: DiffTypeNone,
   738  								Name: "TimeZone",
   739  								Old:  "Europe/Minsk",
   740  								New:  "Europe/Minsk",
   741  							},
   742  						},
   743  					},
   744  				},
   745  			},
   746  		},
   747  		{
   748  			// Constraints edited
   749  			Old: &Job{
   750  				Constraints: []*Constraint{
   751  					{
   752  						LTarget: "foo",
   753  						RTarget: "foo",
   754  						Operand: "foo",
   755  					},
   756  					{
   757  						LTarget: "bar",
   758  						RTarget: "bar",
   759  						Operand: "bar",
   760  					},
   761  				},
   762  			},
   763  			New: &Job{
   764  				Constraints: []*Constraint{
   765  					{
   766  						LTarget: "foo",
   767  						RTarget: "foo",
   768  						Operand: "foo",
   769  					},
   770  					{
   771  						LTarget: "baz",
   772  						RTarget: "baz",
   773  						Operand: "baz",
   774  					},
   775  				},
   776  			},
   777  			Expected: &JobDiff{
   778  				Type: DiffTypeEdited,
   779  				Objects: []*ObjectDiff{
   780  					{
   781  						Type: DiffTypeAdded,
   782  						Name: "Constraint",
   783  						Fields: []*FieldDiff{
   784  							{
   785  								Type: DiffTypeAdded,
   786  								Name: "LTarget",
   787  								Old:  "",
   788  								New:  "baz",
   789  							},
   790  							{
   791  								Type: DiffTypeAdded,
   792  								Name: "Operand",
   793  								Old:  "",
   794  								New:  "baz",
   795  							},
   796  							{
   797  								Type: DiffTypeAdded,
   798  								Name: "RTarget",
   799  								Old:  "",
   800  								New:  "baz",
   801  							},
   802  						},
   803  					},
   804  					{
   805  						Type: DiffTypeDeleted,
   806  						Name: "Constraint",
   807  						Fields: []*FieldDiff{
   808  							{
   809  								Type: DiffTypeDeleted,
   810  								Name: "LTarget",
   811  								Old:  "bar",
   812  								New:  "",
   813  							},
   814  							{
   815  								Type: DiffTypeDeleted,
   816  								Name: "Operand",
   817  								Old:  "bar",
   818  								New:  "",
   819  							},
   820  							{
   821  								Type: DiffTypeDeleted,
   822  								Name: "RTarget",
   823  								Old:  "bar",
   824  								New:  "",
   825  							},
   826  						},
   827  					},
   828  				},
   829  			},
   830  		},
   831  		{
   832  			// Affinities edited
   833  			Old: &Job{
   834  				Affinities: []*Affinity{
   835  					{
   836  						LTarget: "foo",
   837  						RTarget: "foo",
   838  						Operand: "foo",
   839  						Weight:  20,
   840  					},
   841  					{
   842  						LTarget: "bar",
   843  						RTarget: "bar",
   844  						Operand: "bar",
   845  						Weight:  20,
   846  					},
   847  				},
   848  			},
   849  			New: &Job{
   850  				Affinities: []*Affinity{
   851  					{
   852  						LTarget: "foo",
   853  						RTarget: "foo",
   854  						Operand: "foo",
   855  						Weight:  20,
   856  					},
   857  					{
   858  						LTarget: "baz",
   859  						RTarget: "baz",
   860  						Operand: "baz",
   861  						Weight:  20,
   862  					},
   863  				},
   864  			},
   865  			Expected: &JobDiff{
   866  				Type: DiffTypeEdited,
   867  				Objects: []*ObjectDiff{
   868  					{
   869  						Type: DiffTypeAdded,
   870  						Name: "Affinity",
   871  						Fields: []*FieldDiff{
   872  							{
   873  								Type: DiffTypeAdded,
   874  								Name: "LTarget",
   875  								Old:  "",
   876  								New:  "baz",
   877  							},
   878  							{
   879  								Type: DiffTypeAdded,
   880  								Name: "Operand",
   881  								Old:  "",
   882  								New:  "baz",
   883  							},
   884  							{
   885  								Type: DiffTypeAdded,
   886  								Name: "RTarget",
   887  								Old:  "",
   888  								New:  "baz",
   889  							},
   890  							{
   891  								Type: DiffTypeAdded,
   892  								Name: "Weight",
   893  								Old:  "",
   894  								New:  "20",
   895  							},
   896  						},
   897  					},
   898  					{
   899  						Type: DiffTypeDeleted,
   900  						Name: "Affinity",
   901  						Fields: []*FieldDiff{
   902  							{
   903  								Type: DiffTypeDeleted,
   904  								Name: "LTarget",
   905  								Old:  "bar",
   906  								New:  "",
   907  							},
   908  							{
   909  								Type: DiffTypeDeleted,
   910  								Name: "Operand",
   911  								Old:  "bar",
   912  								New:  "",
   913  							},
   914  							{
   915  								Type: DiffTypeDeleted,
   916  								Name: "RTarget",
   917  								Old:  "bar",
   918  								New:  "",
   919  							},
   920  							{
   921  								Type: DiffTypeDeleted,
   922  								Name: "Weight",
   923  								Old:  "20",
   924  								New:  "",
   925  							},
   926  						},
   927  					},
   928  				},
   929  			},
   930  		},
   931  		{
   932  			// Task groups edited
   933  			Old: &Job{
   934  				TaskGroups: []*TaskGroup{
   935  					{
   936  						Name:  "foo",
   937  						Count: 1,
   938  					},
   939  					{
   940  						Name:  "bar",
   941  						Count: 1,
   942  					},
   943  					{
   944  						Name:  "baz",
   945  						Count: 1,
   946  					},
   947  				},
   948  			},
   949  			New: &Job{
   950  				TaskGroups: []*TaskGroup{
   951  					{
   952  						Name:  "bar",
   953  						Count: 1,
   954  					},
   955  					{
   956  						Name:  "baz",
   957  						Count: 2,
   958  					},
   959  					{
   960  						Name:  "bam",
   961  						Count: 1,
   962  					},
   963  				},
   964  			},
   965  			Expected: &JobDiff{
   966  				Type: DiffTypeEdited,
   967  				TaskGroups: []*TaskGroupDiff{
   968  					{
   969  						Type: DiffTypeAdded,
   970  						Name: "bam",
   971  						Fields: []*FieldDiff{
   972  							{
   973  								Type: DiffTypeAdded,
   974  								Name: "Count",
   975  								Old:  "",
   976  								New:  "1",
   977  							},
   978  						},
   979  					},
   980  					{
   981  						Type: DiffTypeNone,
   982  						Name: "bar",
   983  					},
   984  					{
   985  						Type: DiffTypeEdited,
   986  						Name: "baz",
   987  						Fields: []*FieldDiff{
   988  							{
   989  								Type: DiffTypeEdited,
   990  								Name: "Count",
   991  								Old:  "1",
   992  								New:  "2",
   993  							},
   994  						},
   995  					},
   996  					{
   997  						Type: DiffTypeDeleted,
   998  						Name: "foo",
   999  						Fields: []*FieldDiff{
  1000  							{
  1001  								Type: DiffTypeDeleted,
  1002  								Name: "Count",
  1003  								Old:  "1",
  1004  								New:  "",
  1005  							},
  1006  						},
  1007  					},
  1008  				},
  1009  			},
  1010  		},
  1011  		{
  1012  			// Parameterized Job added
  1013  			Old: &Job{},
  1014  			New: &Job{
  1015  				ParameterizedJob: &ParameterizedJobConfig{
  1016  					Payload:      DispatchPayloadRequired,
  1017  					MetaOptional: []string{"foo"},
  1018  					MetaRequired: []string{"bar"},
  1019  				},
  1020  			},
  1021  			Expected: &JobDiff{
  1022  				Type: DiffTypeEdited,
  1023  				Objects: []*ObjectDiff{
  1024  					{
  1025  						Type: DiffTypeAdded,
  1026  						Name: "ParameterizedJob",
  1027  						Fields: []*FieldDiff{
  1028  							{
  1029  								Type: DiffTypeAdded,
  1030  								Name: "Payload",
  1031  								Old:  "",
  1032  								New:  DispatchPayloadRequired,
  1033  							},
  1034  						},
  1035  						Objects: []*ObjectDiff{
  1036  							{
  1037  								Type: DiffTypeAdded,
  1038  								Name: "MetaOptional",
  1039  								Fields: []*FieldDiff{
  1040  									{
  1041  										Type: DiffTypeAdded,
  1042  										Name: "MetaOptional",
  1043  										Old:  "",
  1044  										New:  "foo",
  1045  									},
  1046  								},
  1047  							},
  1048  							{
  1049  								Type: DiffTypeAdded,
  1050  								Name: "MetaRequired",
  1051  								Fields: []*FieldDiff{
  1052  									{
  1053  										Type: DiffTypeAdded,
  1054  										Name: "MetaRequired",
  1055  										Old:  "",
  1056  										New:  "bar",
  1057  									},
  1058  								},
  1059  							},
  1060  						},
  1061  					},
  1062  				},
  1063  			},
  1064  		},
  1065  		{
  1066  			// Parameterized Job deleted
  1067  			Old: &Job{
  1068  				ParameterizedJob: &ParameterizedJobConfig{
  1069  					Payload:      DispatchPayloadRequired,
  1070  					MetaOptional: []string{"foo"},
  1071  					MetaRequired: []string{"bar"},
  1072  				},
  1073  			},
  1074  			New: &Job{},
  1075  			Expected: &JobDiff{
  1076  				Type: DiffTypeEdited,
  1077  				Objects: []*ObjectDiff{
  1078  					{
  1079  						Type: DiffTypeDeleted,
  1080  						Name: "ParameterizedJob",
  1081  						Fields: []*FieldDiff{
  1082  							{
  1083  								Type: DiffTypeDeleted,
  1084  								Name: "Payload",
  1085  								Old:  DispatchPayloadRequired,
  1086  								New:  "",
  1087  							},
  1088  						},
  1089  						Objects: []*ObjectDiff{
  1090  							{
  1091  								Type: DiffTypeDeleted,
  1092  								Name: "MetaOptional",
  1093  								Fields: []*FieldDiff{
  1094  									{
  1095  										Type: DiffTypeDeleted,
  1096  										Name: "MetaOptional",
  1097  										Old:  "foo",
  1098  										New:  "",
  1099  									},
  1100  								},
  1101  							},
  1102  							{
  1103  								Type: DiffTypeDeleted,
  1104  								Name: "MetaRequired",
  1105  								Fields: []*FieldDiff{
  1106  									{
  1107  										Type: DiffTypeDeleted,
  1108  										Name: "MetaRequired",
  1109  										Old:  "bar",
  1110  										New:  "",
  1111  									},
  1112  								},
  1113  							},
  1114  						},
  1115  					},
  1116  				},
  1117  			},
  1118  		},
  1119  		{
  1120  			// Parameterized Job edited
  1121  			Old: &Job{
  1122  				ParameterizedJob: &ParameterizedJobConfig{
  1123  					Payload:      DispatchPayloadRequired,
  1124  					MetaOptional: []string{"foo"},
  1125  					MetaRequired: []string{"bar"},
  1126  				},
  1127  			},
  1128  			New: &Job{
  1129  				ParameterizedJob: &ParameterizedJobConfig{
  1130  					Payload:      DispatchPayloadOptional,
  1131  					MetaOptional: []string{"bam"},
  1132  					MetaRequired: []string{"bang"},
  1133  				},
  1134  			},
  1135  			Expected: &JobDiff{
  1136  				Type: DiffTypeEdited,
  1137  				Objects: []*ObjectDiff{
  1138  					{
  1139  						Type: DiffTypeEdited,
  1140  						Name: "ParameterizedJob",
  1141  						Fields: []*FieldDiff{
  1142  							{
  1143  								Type: DiffTypeEdited,
  1144  								Name: "Payload",
  1145  								Old:  DispatchPayloadRequired,
  1146  								New:  DispatchPayloadOptional,
  1147  							},
  1148  						},
  1149  						Objects: []*ObjectDiff{
  1150  							{
  1151  								Type: DiffTypeEdited,
  1152  								Name: "MetaOptional",
  1153  								Fields: []*FieldDiff{
  1154  									{
  1155  										Type: DiffTypeAdded,
  1156  										Name: "MetaOptional",
  1157  										Old:  "",
  1158  										New:  "bam",
  1159  									},
  1160  									{
  1161  										Type: DiffTypeDeleted,
  1162  										Name: "MetaOptional",
  1163  										Old:  "foo",
  1164  										New:  "",
  1165  									},
  1166  								},
  1167  							},
  1168  							{
  1169  								Type: DiffTypeEdited,
  1170  								Name: "MetaRequired",
  1171  								Fields: []*FieldDiff{
  1172  									{
  1173  										Type: DiffTypeAdded,
  1174  										Name: "MetaRequired",
  1175  										Old:  "",
  1176  										New:  "bang",
  1177  									},
  1178  									{
  1179  										Type: DiffTypeDeleted,
  1180  										Name: "MetaRequired",
  1181  										Old:  "bar",
  1182  										New:  "",
  1183  									},
  1184  								},
  1185  							},
  1186  						},
  1187  					},
  1188  				},
  1189  			},
  1190  		},
  1191  		{
  1192  			// Parameterized Job edited with context
  1193  			Contextual: true,
  1194  			Old: &Job{
  1195  				ParameterizedJob: &ParameterizedJobConfig{
  1196  					Payload:      DispatchPayloadRequired,
  1197  					MetaOptional: []string{"foo"},
  1198  					MetaRequired: []string{"bar"},
  1199  				},
  1200  			},
  1201  			New: &Job{
  1202  				ParameterizedJob: &ParameterizedJobConfig{
  1203  					Payload:      DispatchPayloadOptional,
  1204  					MetaOptional: []string{"foo"},
  1205  					MetaRequired: []string{"bar"},
  1206  				},
  1207  			},
  1208  			Expected: &JobDiff{
  1209  				Type: DiffTypeEdited,
  1210  				Objects: []*ObjectDiff{
  1211  					{
  1212  						Type: DiffTypeEdited,
  1213  						Name: "ParameterizedJob",
  1214  						Fields: []*FieldDiff{
  1215  							{
  1216  								Type: DiffTypeEdited,
  1217  								Name: "Payload",
  1218  								Old:  DispatchPayloadRequired,
  1219  								New:  DispatchPayloadOptional,
  1220  							},
  1221  						},
  1222  						Objects: []*ObjectDiff{
  1223  							{
  1224  								Type: DiffTypeNone,
  1225  								Name: "MetaOptional",
  1226  								Fields: []*FieldDiff{
  1227  									{
  1228  										Type: DiffTypeNone,
  1229  										Name: "MetaOptional",
  1230  										Old:  "foo",
  1231  										New:  "foo",
  1232  									},
  1233  								},
  1234  							},
  1235  							{
  1236  								Type: DiffTypeNone,
  1237  								Name: "MetaRequired",
  1238  								Fields: []*FieldDiff{
  1239  									{
  1240  										Type: DiffTypeNone,
  1241  										Name: "MetaRequired",
  1242  										Old:  "bar",
  1243  										New:  "bar",
  1244  									},
  1245  								},
  1246  							},
  1247  						},
  1248  					},
  1249  				},
  1250  			},
  1251  		},
  1252  		{
  1253  			// Multiregion: region added
  1254  			Old: &Job{
  1255  				NomadTokenID: "abcdef",
  1256  				Multiregion: &Multiregion{
  1257  					Strategy: &MultiregionStrategy{
  1258  						MaxParallel: 1,
  1259  						OnFailure:   "fail_all",
  1260  					},
  1261  					Regions: []*MultiregionRegion{
  1262  						{
  1263  							Name:        "west",
  1264  							Count:       1,
  1265  							Datacenters: []string{"west-1"},
  1266  							Meta:        map[string]string{"region_code": "W"},
  1267  						},
  1268  					},
  1269  				},
  1270  			},
  1271  
  1272  			New: &Job{
  1273  				NomadTokenID: "12345",
  1274  				Multiregion: &Multiregion{
  1275  					Strategy: &MultiregionStrategy{
  1276  						MaxParallel: 2,
  1277  						OnFailure:   "fail_all",
  1278  					},
  1279  					Regions: []*MultiregionRegion{
  1280  						{
  1281  							Name:        "west",
  1282  							Count:       3,
  1283  							Datacenters: []string{"west-2"},
  1284  							Meta:        map[string]string{"region_code": "W"},
  1285  						},
  1286  						{
  1287  							Name:        "east",
  1288  							Count:       2,
  1289  							Datacenters: []string{"east-1", "east-2"},
  1290  							Meta:        map[string]string{"region_code": "E"},
  1291  						},
  1292  					},
  1293  				},
  1294  			},
  1295  			Expected: &JobDiff{
  1296  				Type: DiffTypeEdited,
  1297  				Objects: []*ObjectDiff{
  1298  					{
  1299  						Type: DiffTypeEdited,
  1300  						Name: "Multiregion",
  1301  						Objects: []*ObjectDiff{
  1302  							{
  1303  								Type: DiffTypeEdited,
  1304  								Name: "Region",
  1305  								Fields: []*FieldDiff{
  1306  									{
  1307  										Type: DiffTypeEdited,
  1308  										Name: "Count",
  1309  										Old:  "1",
  1310  										New:  "3",
  1311  									},
  1312  								},
  1313  								Objects: []*ObjectDiff{
  1314  									{
  1315  										Type: DiffTypeEdited,
  1316  										Name: "Datacenters",
  1317  										Fields: []*FieldDiff{
  1318  											{
  1319  												Type: DiffTypeAdded,
  1320  												Name: "Datacenters",
  1321  												Old:  "",
  1322  												New:  "west-2",
  1323  											},
  1324  											{
  1325  												Type: DiffTypeDeleted,
  1326  												Name: "Datacenters",
  1327  												Old:  "west-1",
  1328  												New:  "",
  1329  											},
  1330  										},
  1331  									},
  1332  								},
  1333  							},
  1334  							{
  1335  								Type: DiffTypeAdded,
  1336  								Name: "Region",
  1337  								Fields: []*FieldDiff{
  1338  									{
  1339  										Type: DiffTypeAdded,
  1340  										Name: "Count",
  1341  										Old:  "",
  1342  										New:  "2",
  1343  									},
  1344  									{
  1345  										Type: DiffTypeAdded,
  1346  										Name: "Meta[region_code]",
  1347  										Old:  "",
  1348  										New:  "E",
  1349  									},
  1350  									{
  1351  										Type: DiffTypeAdded,
  1352  										Name: "Name",
  1353  										Old:  "",
  1354  										New:  "east",
  1355  									},
  1356  								},
  1357  
  1358  								Objects: []*ObjectDiff{
  1359  									{
  1360  										Type: DiffTypeAdded,
  1361  										Name: "Datacenters",
  1362  										Fields: []*FieldDiff{
  1363  											{
  1364  												Type: DiffTypeAdded,
  1365  												Name: "Datacenters",
  1366  												Old:  "",
  1367  												New:  "east-1",
  1368  											},
  1369  											{
  1370  												Type: DiffTypeAdded,
  1371  												Name: "Datacenters",
  1372  												Old:  "",
  1373  												New:  "east-2",
  1374  											},
  1375  										},
  1376  									},
  1377  								},
  1378  							},
  1379  							{
  1380  								Type: DiffTypeEdited,
  1381  								Name: "Strategy",
  1382  								Fields: []*FieldDiff{
  1383  									{
  1384  										Type: DiffTypeEdited,
  1385  										Name: "MaxParallel",
  1386  										Old:  "1",
  1387  										New:  "2",
  1388  									},
  1389  								},
  1390  							},
  1391  						},
  1392  					},
  1393  				},
  1394  			},
  1395  		},
  1396  		{
  1397  			// VaultToken is filtered
  1398  			Old: &Job{
  1399  				ID:         "vault-job",
  1400  				VaultToken: "secret",
  1401  			},
  1402  			New: &Job{
  1403  				ID:         "vault-job",
  1404  				VaultToken: "new-secret",
  1405  			},
  1406  			Expected: &JobDiff{
  1407  				Type: DiffTypeNone,
  1408  				ID:   "vault-job",
  1409  			},
  1410  		},
  1411  	}
  1412  
  1413  	for i, c := range cases {
  1414  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1415  		if c.Error && err == nil {
  1416  			t.Fatalf("case %d: expected errored", i+1)
  1417  		} else if err != nil {
  1418  			if !c.Error {
  1419  				t.Fatalf("case %d: errored %#v", i+1, err)
  1420  			} else {
  1421  				continue
  1422  			}
  1423  		}
  1424  
  1425  		if !reflect.DeepEqual(actual, c.Expected) {
  1426  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1427  				i+1, actual, c.Expected)
  1428  		}
  1429  	}
  1430  }
  1431  
  1432  func TestTaskGroupDiff(t *testing.T) {
  1433  	ci.Parallel(t)
  1434  
  1435  	cases := []struct {
  1436  		TestCase   string
  1437  		Old, New   *TaskGroup
  1438  		Expected   *TaskGroupDiff
  1439  		ExpErr     bool
  1440  		Contextual bool
  1441  	}{
  1442  		{
  1443  			TestCase: "Empty",
  1444  			Old:      nil,
  1445  			New:      nil,
  1446  			Expected: &TaskGroupDiff{
  1447  				Type: DiffTypeNone,
  1448  			},
  1449  		},
  1450  		{
  1451  			TestCase: "Primitive only that has different names",
  1452  			Old: &TaskGroup{
  1453  				Name:  "foo",
  1454  				Count: 10,
  1455  				Meta: map[string]string{
  1456  					"foo": "bar",
  1457  				},
  1458  			},
  1459  			New: &TaskGroup{
  1460  				Name:  "bar",
  1461  				Count: 10,
  1462  				Meta: map[string]string{
  1463  					"foo": "bar",
  1464  				},
  1465  			},
  1466  			ExpErr: true,
  1467  		},
  1468  		{
  1469  			TestCase: "Primitive only that is the same",
  1470  			Old: &TaskGroup{
  1471  				Name:  "foo",
  1472  				Count: 10,
  1473  				Meta: map[string]string{
  1474  					"foo": "bar",
  1475  				},
  1476  			},
  1477  			New: &TaskGroup{
  1478  				Name:  "foo",
  1479  				Count: 10,
  1480  				Meta: map[string]string{
  1481  					"foo": "bar",
  1482  				},
  1483  			},
  1484  			Expected: &TaskGroupDiff{
  1485  				Type: DiffTypeNone,
  1486  				Name: "foo",
  1487  			},
  1488  		},
  1489  		{
  1490  			TestCase: "Primitive only that has diffs",
  1491  			Old: &TaskGroup{
  1492  				Name:  "foo",
  1493  				Count: 10,
  1494  				Meta: map[string]string{
  1495  					"foo": "bar",
  1496  				},
  1497  			},
  1498  			New: &TaskGroup{
  1499  				Name:  "foo",
  1500  				Count: 100,
  1501  				Meta: map[string]string{
  1502  					"foo": "baz",
  1503  				},
  1504  			},
  1505  			Expected: &TaskGroupDiff{
  1506  				Type: DiffTypeEdited,
  1507  				Name: "foo",
  1508  				Fields: []*FieldDiff{
  1509  					{
  1510  						Type: DiffTypeEdited,
  1511  						Name: "Count",
  1512  						Old:  "10",
  1513  						New:  "100",
  1514  					},
  1515  					{
  1516  						Type: DiffTypeEdited,
  1517  						Name: "Meta[foo]",
  1518  						Old:  "bar",
  1519  						New:  "baz",
  1520  					},
  1521  				},
  1522  			},
  1523  		},
  1524  		{
  1525  			TestCase: "Map diff",
  1526  			Old: &TaskGroup{
  1527  				Meta: map[string]string{
  1528  					"foo": "foo",
  1529  					"bar": "bar",
  1530  				},
  1531  			},
  1532  			New: &TaskGroup{
  1533  				Meta: map[string]string{
  1534  					"bar": "bar",
  1535  					"baz": "baz",
  1536  				},
  1537  			},
  1538  			Expected: &TaskGroupDiff{
  1539  				Type: DiffTypeEdited,
  1540  				Fields: []*FieldDiff{
  1541  					{
  1542  						Type: DiffTypeAdded,
  1543  						Name: "Meta[baz]",
  1544  						Old:  "",
  1545  						New:  "baz",
  1546  					},
  1547  					{
  1548  						Type: DiffTypeDeleted,
  1549  						Name: "Meta[foo]",
  1550  						Old:  "foo",
  1551  						New:  "",
  1552  					},
  1553  				},
  1554  			},
  1555  		},
  1556  		{
  1557  			TestCase: "Constraints edited",
  1558  			Old: &TaskGroup{
  1559  				Constraints: []*Constraint{
  1560  					{
  1561  						LTarget: "foo",
  1562  						RTarget: "foo",
  1563  						Operand: "foo",
  1564  					},
  1565  					{
  1566  						LTarget: "bar",
  1567  						RTarget: "bar",
  1568  						Operand: "bar",
  1569  					},
  1570  				},
  1571  			},
  1572  			New: &TaskGroup{
  1573  				Constraints: []*Constraint{
  1574  					{
  1575  						LTarget: "foo",
  1576  						RTarget: "foo",
  1577  						Operand: "foo",
  1578  					},
  1579  					{
  1580  						LTarget: "baz",
  1581  						RTarget: "baz",
  1582  						Operand: "baz",
  1583  					},
  1584  				},
  1585  			},
  1586  			Expected: &TaskGroupDiff{
  1587  				Type: DiffTypeEdited,
  1588  				Objects: []*ObjectDiff{
  1589  					{
  1590  						Type: DiffTypeAdded,
  1591  						Name: "Constraint",
  1592  						Fields: []*FieldDiff{
  1593  							{
  1594  								Type: DiffTypeAdded,
  1595  								Name: "LTarget",
  1596  								Old:  "",
  1597  								New:  "baz",
  1598  							},
  1599  							{
  1600  								Type: DiffTypeAdded,
  1601  								Name: "Operand",
  1602  								Old:  "",
  1603  								New:  "baz",
  1604  							},
  1605  							{
  1606  								Type: DiffTypeAdded,
  1607  								Name: "RTarget",
  1608  								Old:  "",
  1609  								New:  "baz",
  1610  							},
  1611  						},
  1612  					},
  1613  					{
  1614  						Type: DiffTypeDeleted,
  1615  						Name: "Constraint",
  1616  						Fields: []*FieldDiff{
  1617  							{
  1618  								Type: DiffTypeDeleted,
  1619  								Name: "LTarget",
  1620  								Old:  "bar",
  1621  								New:  "",
  1622  							},
  1623  							{
  1624  								Type: DiffTypeDeleted,
  1625  								Name: "Operand",
  1626  								Old:  "bar",
  1627  								New:  "",
  1628  							},
  1629  							{
  1630  								Type: DiffTypeDeleted,
  1631  								Name: "RTarget",
  1632  								Old:  "bar",
  1633  								New:  "",
  1634  							},
  1635  						},
  1636  					},
  1637  				},
  1638  			},
  1639  		},
  1640  		{
  1641  			TestCase: "Affinities edited",
  1642  			Old: &TaskGroup{
  1643  				Affinities: []*Affinity{
  1644  					{
  1645  						LTarget: "foo",
  1646  						RTarget: "foo",
  1647  						Operand: "foo",
  1648  						Weight:  20,
  1649  					},
  1650  					{
  1651  						LTarget: "bar",
  1652  						RTarget: "bar",
  1653  						Operand: "bar",
  1654  						Weight:  20,
  1655  					},
  1656  				},
  1657  			},
  1658  			New: &TaskGroup{
  1659  				Affinities: []*Affinity{
  1660  					{
  1661  						LTarget: "foo",
  1662  						RTarget: "foo",
  1663  						Operand: "foo",
  1664  						Weight:  20,
  1665  					},
  1666  					{
  1667  						LTarget: "baz",
  1668  						RTarget: "baz",
  1669  						Operand: "baz",
  1670  						Weight:  20,
  1671  					},
  1672  				},
  1673  			},
  1674  			Expected: &TaskGroupDiff{
  1675  				Type: DiffTypeEdited,
  1676  				Objects: []*ObjectDiff{
  1677  					{
  1678  						Type: DiffTypeAdded,
  1679  						Name: "Affinity",
  1680  						Fields: []*FieldDiff{
  1681  							{
  1682  								Type: DiffTypeAdded,
  1683  								Name: "LTarget",
  1684  								Old:  "",
  1685  								New:  "baz",
  1686  							},
  1687  							{
  1688  								Type: DiffTypeAdded,
  1689  								Name: "Operand",
  1690  								Old:  "",
  1691  								New:  "baz",
  1692  							},
  1693  							{
  1694  								Type: DiffTypeAdded,
  1695  								Name: "RTarget",
  1696  								Old:  "",
  1697  								New:  "baz",
  1698  							},
  1699  							{
  1700  								Type: DiffTypeAdded,
  1701  								Name: "Weight",
  1702  								Old:  "",
  1703  								New:  "20",
  1704  							},
  1705  						},
  1706  					},
  1707  					{
  1708  						Type: DiffTypeDeleted,
  1709  						Name: "Affinity",
  1710  						Fields: []*FieldDiff{
  1711  							{
  1712  								Type: DiffTypeDeleted,
  1713  								Name: "LTarget",
  1714  								Old:  "bar",
  1715  								New:  "",
  1716  							},
  1717  							{
  1718  								Type: DiffTypeDeleted,
  1719  								Name: "Operand",
  1720  								Old:  "bar",
  1721  								New:  "",
  1722  							},
  1723  							{
  1724  								Type: DiffTypeDeleted,
  1725  								Name: "RTarget",
  1726  								Old:  "bar",
  1727  								New:  "",
  1728  							},
  1729  							{
  1730  								Type: DiffTypeDeleted,
  1731  								Name: "Weight",
  1732  								Old:  "20",
  1733  								New:  "",
  1734  							},
  1735  						},
  1736  					},
  1737  				},
  1738  			},
  1739  		},
  1740  		{
  1741  			TestCase: "Consul added",
  1742  			Old:      &TaskGroup{},
  1743  			New: &TaskGroup{
  1744  				Consul: &Consul{
  1745  					Namespace: "team1",
  1746  				},
  1747  			},
  1748  			Expected: &TaskGroupDiff{
  1749  				Type: DiffTypeEdited,
  1750  				Objects: []*ObjectDiff{
  1751  					{
  1752  						Type: DiffTypeAdded,
  1753  						Name: "Consul",
  1754  						Fields: []*FieldDiff{
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Namespace",
  1758  								Old:  "",
  1759  								New:  "team1",
  1760  							},
  1761  						},
  1762  					},
  1763  				},
  1764  			},
  1765  		},
  1766  		{
  1767  			TestCase: "Consul deleted",
  1768  			Old: &TaskGroup{
  1769  				Consul: &Consul{
  1770  					Namespace: "team1",
  1771  				},
  1772  			},
  1773  			New: &TaskGroup{},
  1774  			Expected: &TaskGroupDiff{
  1775  				Type: DiffTypeEdited,
  1776  				Objects: []*ObjectDiff{
  1777  					{
  1778  						Type: DiffTypeDeleted,
  1779  						Name: "Consul",
  1780  						Fields: []*FieldDiff{
  1781  							{
  1782  								Type: DiffTypeDeleted,
  1783  								Name: "Namespace",
  1784  								Old:  "team1",
  1785  								New:  "",
  1786  							},
  1787  						},
  1788  					},
  1789  				},
  1790  			},
  1791  		},
  1792  		{
  1793  			TestCase: "Consul updated",
  1794  			Old: &TaskGroup{
  1795  				Consul: &Consul{
  1796  					Namespace: "team1",
  1797  				},
  1798  			},
  1799  			New: &TaskGroup{
  1800  				Consul: &Consul{
  1801  					Namespace: "team2",
  1802  				},
  1803  			},
  1804  			Expected: &TaskGroupDiff{
  1805  				Type: DiffTypeEdited,
  1806  				Objects: []*ObjectDiff{
  1807  					{
  1808  						Type: DiffTypeEdited,
  1809  						Name: "Consul",
  1810  						Fields: []*FieldDiff{
  1811  							{
  1812  								Type: DiffTypeEdited,
  1813  								Name: "Namespace",
  1814  								Old:  "team1",
  1815  								New:  "team2",
  1816  							},
  1817  						},
  1818  					},
  1819  				},
  1820  			},
  1821  		},
  1822  		{
  1823  			TestCase: "RestartPolicy added",
  1824  			Old:      &TaskGroup{},
  1825  			New: &TaskGroup{
  1826  				RestartPolicy: &RestartPolicy{
  1827  					Attempts: 1,
  1828  					Interval: 1 * time.Second,
  1829  					Delay:    1 * time.Second,
  1830  					Mode:     "fail",
  1831  				},
  1832  			},
  1833  			Expected: &TaskGroupDiff{
  1834  				Type: DiffTypeEdited,
  1835  				Objects: []*ObjectDiff{
  1836  					{
  1837  						Type: DiffTypeAdded,
  1838  						Name: "RestartPolicy",
  1839  						Fields: []*FieldDiff{
  1840  							{
  1841  								Type: DiffTypeAdded,
  1842  								Name: "Attempts",
  1843  								Old:  "",
  1844  								New:  "1",
  1845  							},
  1846  							{
  1847  								Type: DiffTypeAdded,
  1848  								Name: "Delay",
  1849  								Old:  "",
  1850  								New:  "1000000000",
  1851  							},
  1852  							{
  1853  								Type: DiffTypeAdded,
  1854  								Name: "Interval",
  1855  								Old:  "",
  1856  								New:  "1000000000",
  1857  							},
  1858  							{
  1859  								Type: DiffTypeAdded,
  1860  								Name: "Mode",
  1861  								Old:  "",
  1862  								New:  "fail",
  1863  							},
  1864  						},
  1865  					},
  1866  				},
  1867  			},
  1868  		},
  1869  		{
  1870  			TestCase: "RestartPolicy deleted",
  1871  			Old: &TaskGroup{
  1872  				RestartPolicy: &RestartPolicy{
  1873  					Attempts: 1,
  1874  					Interval: 1 * time.Second,
  1875  					Delay:    1 * time.Second,
  1876  					Mode:     "fail",
  1877  				},
  1878  			},
  1879  			New: &TaskGroup{},
  1880  			Expected: &TaskGroupDiff{
  1881  				Type: DiffTypeEdited,
  1882  				Objects: []*ObjectDiff{
  1883  					{
  1884  						Type: DiffTypeDeleted,
  1885  						Name: "RestartPolicy",
  1886  						Fields: []*FieldDiff{
  1887  							{
  1888  								Type: DiffTypeDeleted,
  1889  								Name: "Attempts",
  1890  								Old:  "1",
  1891  								New:  "",
  1892  							},
  1893  							{
  1894  								Type: DiffTypeDeleted,
  1895  								Name: "Delay",
  1896  								Old:  "1000000000",
  1897  								New:  "",
  1898  							},
  1899  							{
  1900  								Type: DiffTypeDeleted,
  1901  								Name: "Interval",
  1902  								Old:  "1000000000",
  1903  								New:  "",
  1904  							},
  1905  							{
  1906  								Type: DiffTypeDeleted,
  1907  								Name: "Mode",
  1908  								Old:  "fail",
  1909  								New:  "",
  1910  							},
  1911  						},
  1912  					},
  1913  				},
  1914  			},
  1915  		},
  1916  		{
  1917  			TestCase: "RestartPolicy edited",
  1918  			Old: &TaskGroup{
  1919  				RestartPolicy: &RestartPolicy{
  1920  					Attempts: 1,
  1921  					Interval: 1 * time.Second,
  1922  					Delay:    1 * time.Second,
  1923  					Mode:     "fail",
  1924  				},
  1925  			},
  1926  			New: &TaskGroup{
  1927  				RestartPolicy: &RestartPolicy{
  1928  					Attempts: 2,
  1929  					Interval: 2 * time.Second,
  1930  					Delay:    2 * time.Second,
  1931  					Mode:     "delay",
  1932  				},
  1933  			},
  1934  			Expected: &TaskGroupDiff{
  1935  				Type: DiffTypeEdited,
  1936  				Objects: []*ObjectDiff{
  1937  					{
  1938  						Type: DiffTypeEdited,
  1939  						Name: "RestartPolicy",
  1940  						Fields: []*FieldDiff{
  1941  							{
  1942  								Type: DiffTypeEdited,
  1943  								Name: "Attempts",
  1944  								Old:  "1",
  1945  								New:  "2",
  1946  							},
  1947  							{
  1948  								Type: DiffTypeEdited,
  1949  								Name: "Delay",
  1950  								Old:  "1000000000",
  1951  								New:  "2000000000",
  1952  							},
  1953  							{
  1954  								Type: DiffTypeEdited,
  1955  								Name: "Interval",
  1956  								Old:  "1000000000",
  1957  								New:  "2000000000",
  1958  							},
  1959  							{
  1960  								Type: DiffTypeEdited,
  1961  								Name: "Mode",
  1962  								Old:  "fail",
  1963  								New:  "delay",
  1964  							},
  1965  						},
  1966  					},
  1967  				},
  1968  			},
  1969  		},
  1970  		{
  1971  			TestCase:   "RestartPolicy edited with context",
  1972  			Contextual: true,
  1973  			Old: &TaskGroup{
  1974  				RestartPolicy: &RestartPolicy{
  1975  					Attempts: 1,
  1976  					Interval: 1 * time.Second,
  1977  					Delay:    1 * time.Second,
  1978  					Mode:     "fail",
  1979  				},
  1980  			},
  1981  			New: &TaskGroup{
  1982  				RestartPolicy: &RestartPolicy{
  1983  					Attempts: 2,
  1984  					Interval: 2 * time.Second,
  1985  					Delay:    1 * time.Second,
  1986  					Mode:     "fail",
  1987  				},
  1988  			},
  1989  			Expected: &TaskGroupDiff{
  1990  				Type: DiffTypeEdited,
  1991  				Objects: []*ObjectDiff{
  1992  					{
  1993  						Type: DiffTypeEdited,
  1994  						Name: "RestartPolicy",
  1995  						Fields: []*FieldDiff{
  1996  							{
  1997  								Type: DiffTypeEdited,
  1998  								Name: "Attempts",
  1999  								Old:  "1",
  2000  								New:  "2",
  2001  							},
  2002  							{
  2003  								Type: DiffTypeNone,
  2004  								Name: "Delay",
  2005  								Old:  "1000000000",
  2006  								New:  "1000000000",
  2007  							},
  2008  							{
  2009  								Type: DiffTypeEdited,
  2010  								Name: "Interval",
  2011  								Old:  "1000000000",
  2012  								New:  "2000000000",
  2013  							},
  2014  							{
  2015  								Type: DiffTypeNone,
  2016  								Name: "Mode",
  2017  								Old:  "fail",
  2018  								New:  "fail",
  2019  							},
  2020  						},
  2021  					},
  2022  				},
  2023  			},
  2024  		},
  2025  		{
  2026  			TestCase: "ReschedulePolicy added",
  2027  			Old:      &TaskGroup{},
  2028  			New: &TaskGroup{
  2029  				ReschedulePolicy: &ReschedulePolicy{
  2030  					Attempts:      1,
  2031  					Interval:      15 * time.Second,
  2032  					Delay:         5 * time.Second,
  2033  					MaxDelay:      20 * time.Second,
  2034  					DelayFunction: "exponential",
  2035  					Unlimited:     false,
  2036  				},
  2037  			},
  2038  			Expected: &TaskGroupDiff{
  2039  				Type: DiffTypeEdited,
  2040  				Objects: []*ObjectDiff{
  2041  					{
  2042  						Type: DiffTypeAdded,
  2043  						Name: "ReschedulePolicy",
  2044  						Fields: []*FieldDiff{
  2045  							{
  2046  								Type: DiffTypeAdded,
  2047  								Name: "Attempts",
  2048  								Old:  "",
  2049  								New:  "1",
  2050  							},
  2051  							{
  2052  								Type: DiffTypeAdded,
  2053  								Name: "Delay",
  2054  								Old:  "",
  2055  								New:  "5000000000",
  2056  							},
  2057  							{
  2058  								Type: DiffTypeAdded,
  2059  								Name: "DelayFunction",
  2060  								Old:  "",
  2061  								New:  "exponential",
  2062  							},
  2063  							{
  2064  								Type: DiffTypeAdded,
  2065  								Name: "Interval",
  2066  								Old:  "",
  2067  								New:  "15000000000",
  2068  							},
  2069  							{
  2070  								Type: DiffTypeAdded,
  2071  								Name: "MaxDelay",
  2072  								Old:  "",
  2073  								New:  "20000000000",
  2074  							},
  2075  							{
  2076  								Type: DiffTypeAdded,
  2077  								Name: "Unlimited",
  2078  								Old:  "",
  2079  								New:  "false",
  2080  							},
  2081  						},
  2082  					},
  2083  				},
  2084  			},
  2085  		},
  2086  		{
  2087  			TestCase: "ReschedulePolicy deleted",
  2088  			Old: &TaskGroup{
  2089  				ReschedulePolicy: &ReschedulePolicy{
  2090  					Attempts:      1,
  2091  					Interval:      15 * time.Second,
  2092  					Delay:         5 * time.Second,
  2093  					MaxDelay:      20 * time.Second,
  2094  					DelayFunction: "exponential",
  2095  					Unlimited:     false,
  2096  				},
  2097  			},
  2098  			New: &TaskGroup{},
  2099  			Expected: &TaskGroupDiff{
  2100  				Type: DiffTypeEdited,
  2101  				Objects: []*ObjectDiff{
  2102  					{
  2103  						Type: DiffTypeDeleted,
  2104  						Name: "ReschedulePolicy",
  2105  						Fields: []*FieldDiff{
  2106  							{
  2107  								Type: DiffTypeDeleted,
  2108  								Name: "Attempts",
  2109  								Old:  "1",
  2110  								New:  "",
  2111  							},
  2112  							{
  2113  								Type: DiffTypeDeleted,
  2114  								Name: "Delay",
  2115  								Old:  "5000000000",
  2116  								New:  "",
  2117  							},
  2118  							{
  2119  								Type: DiffTypeDeleted,
  2120  								Name: "DelayFunction",
  2121  								Old:  "exponential",
  2122  								New:  "",
  2123  							},
  2124  							{
  2125  								Type: DiffTypeDeleted,
  2126  								Name: "Interval",
  2127  								Old:  "15000000000",
  2128  								New:  "",
  2129  							},
  2130  							{
  2131  								Type: DiffTypeDeleted,
  2132  								Name: "MaxDelay",
  2133  								Old:  "20000000000",
  2134  								New:  "",
  2135  							},
  2136  							{
  2137  								Type: DiffTypeDeleted,
  2138  								Name: "Unlimited",
  2139  								Old:  "false",
  2140  								New:  "",
  2141  							},
  2142  						},
  2143  					},
  2144  				},
  2145  			},
  2146  		},
  2147  		{
  2148  			TestCase: "ReschedulePolicy edited",
  2149  			Old: &TaskGroup{
  2150  				ReschedulePolicy: &ReschedulePolicy{
  2151  					Attempts:      1,
  2152  					Interval:      1 * time.Second,
  2153  					DelayFunction: "exponential",
  2154  					Delay:         20 * time.Second,
  2155  					MaxDelay:      1 * time.Minute,
  2156  					Unlimited:     false,
  2157  				},
  2158  			},
  2159  			New: &TaskGroup{
  2160  				ReschedulePolicy: &ReschedulePolicy{
  2161  					Attempts:      2,
  2162  					Interval:      2 * time.Second,
  2163  					DelayFunction: "constant",
  2164  					Delay:         30 * time.Second,
  2165  					MaxDelay:      1 * time.Minute,
  2166  					Unlimited:     true,
  2167  				},
  2168  			},
  2169  			Expected: &TaskGroupDiff{
  2170  				Type: DiffTypeEdited,
  2171  				Objects: []*ObjectDiff{
  2172  					{
  2173  						Type: DiffTypeEdited,
  2174  						Name: "ReschedulePolicy",
  2175  						Fields: []*FieldDiff{
  2176  							{
  2177  								Type: DiffTypeEdited,
  2178  								Name: "Attempts",
  2179  								Old:  "1",
  2180  								New:  "2",
  2181  							},
  2182  							{
  2183  								Type: DiffTypeEdited,
  2184  								Name: "Delay",
  2185  								Old:  "20000000000",
  2186  								New:  "30000000000",
  2187  							},
  2188  							{
  2189  								Type: DiffTypeEdited,
  2190  								Name: "DelayFunction",
  2191  								Old:  "exponential",
  2192  								New:  "constant",
  2193  							},
  2194  							{
  2195  								Type: DiffTypeEdited,
  2196  								Name: "Interval",
  2197  								Old:  "1000000000",
  2198  								New:  "2000000000",
  2199  							},
  2200  							{
  2201  								Type: DiffTypeEdited,
  2202  								Name: "Unlimited",
  2203  								Old:  "false",
  2204  								New:  "true",
  2205  							},
  2206  						},
  2207  					},
  2208  				},
  2209  			},
  2210  		},
  2211  		{
  2212  			TestCase:   "ReschedulePolicy edited with context",
  2213  			Contextual: true,
  2214  			Old: &TaskGroup{
  2215  				ReschedulePolicy: &ReschedulePolicy{
  2216  					Attempts: 1,
  2217  					Interval: 1 * time.Second,
  2218  				},
  2219  			},
  2220  			New: &TaskGroup{
  2221  				ReschedulePolicy: &ReschedulePolicy{
  2222  					Attempts: 1,
  2223  					Interval: 2 * time.Second,
  2224  				},
  2225  			},
  2226  			Expected: &TaskGroupDiff{
  2227  				Type: DiffTypeEdited,
  2228  				Objects: []*ObjectDiff{
  2229  					{
  2230  						Type: DiffTypeEdited,
  2231  						Name: "ReschedulePolicy",
  2232  						Fields: []*FieldDiff{
  2233  							{
  2234  								Type: DiffTypeNone,
  2235  								Name: "Attempts",
  2236  								Old:  "1",
  2237  								New:  "1",
  2238  							},
  2239  							{
  2240  								Type: DiffTypeNone,
  2241  								Name: "Delay",
  2242  								Old:  "0",
  2243  								New:  "0",
  2244  							},
  2245  							{
  2246  								Type: DiffTypeNone,
  2247  								Name: "DelayFunction",
  2248  								Old:  "",
  2249  								New:  "",
  2250  							},
  2251  							{
  2252  								Type: DiffTypeEdited,
  2253  								Name: "Interval",
  2254  								Old:  "1000000000",
  2255  								New:  "2000000000",
  2256  							},
  2257  							{
  2258  								Type: DiffTypeNone,
  2259  								Name: "MaxDelay",
  2260  								Old:  "0",
  2261  								New:  "0",
  2262  							},
  2263  							{
  2264  								Type: DiffTypeNone,
  2265  								Name: "Unlimited",
  2266  								Old:  "false",
  2267  								New:  "false",
  2268  							},
  2269  						},
  2270  					},
  2271  				},
  2272  			},
  2273  		},
  2274  		{
  2275  			TestCase: "Update strategy deleted",
  2276  			Old: &TaskGroup{
  2277  				Update: &UpdateStrategy{
  2278  					AutoRevert: true,
  2279  				},
  2280  			},
  2281  			New: &TaskGroup{},
  2282  			Expected: &TaskGroupDiff{
  2283  				Type: DiffTypeEdited,
  2284  				Objects: []*ObjectDiff{
  2285  					{
  2286  						Type: DiffTypeDeleted,
  2287  						Name: "Update",
  2288  						Fields: []*FieldDiff{
  2289  							{
  2290  								Type: DiffTypeDeleted,
  2291  								Name: "AutoPromote",
  2292  								Old:  "false",
  2293  								New:  "",
  2294  							},
  2295  							{
  2296  								Type: DiffTypeDeleted,
  2297  								Name: "AutoRevert",
  2298  								Old:  "true",
  2299  								New:  "",
  2300  							},
  2301  							{
  2302  								Type: DiffTypeDeleted,
  2303  								Name: "Canary",
  2304  								Old:  "0",
  2305  								New:  "",
  2306  							},
  2307  							{
  2308  								Type: DiffTypeDeleted,
  2309  								Name: "HealthyDeadline",
  2310  								Old:  "0",
  2311  								New:  "",
  2312  							},
  2313  							{
  2314  								Type: DiffTypeDeleted,
  2315  								Name: "MaxParallel",
  2316  								Old:  "0",
  2317  								New:  "",
  2318  							},
  2319  							{
  2320  								Type: DiffTypeDeleted,
  2321  								Name: "MinHealthyTime",
  2322  								Old:  "0",
  2323  								New:  "",
  2324  							},
  2325  							{
  2326  								Type: DiffTypeDeleted,
  2327  								Name: "ProgressDeadline",
  2328  								Old:  "0",
  2329  								New:  "",
  2330  							},
  2331  						},
  2332  					},
  2333  				},
  2334  			},
  2335  		},
  2336  		{
  2337  			TestCase: "Update strategy added",
  2338  			Old:      &TaskGroup{},
  2339  			New: &TaskGroup{
  2340  				Update: &UpdateStrategy{
  2341  					AutoRevert: true,
  2342  				},
  2343  			},
  2344  			Expected: &TaskGroupDiff{
  2345  				Type: DiffTypeEdited,
  2346  				Objects: []*ObjectDiff{
  2347  					{
  2348  						Type: DiffTypeAdded,
  2349  						Name: "Update",
  2350  						Fields: []*FieldDiff{
  2351  							{
  2352  								Type: DiffTypeAdded,
  2353  								Name: "AutoPromote",
  2354  								Old:  "",
  2355  								New:  "false",
  2356  							},
  2357  							{
  2358  								Type: DiffTypeAdded,
  2359  								Name: "AutoRevert",
  2360  								Old:  "",
  2361  								New:  "true",
  2362  							},
  2363  							{
  2364  								Type: DiffTypeAdded,
  2365  								Name: "Canary",
  2366  								Old:  "",
  2367  								New:  "0",
  2368  							},
  2369  							{
  2370  								Type: DiffTypeAdded,
  2371  								Name: "HealthyDeadline",
  2372  								Old:  "",
  2373  								New:  "0",
  2374  							},
  2375  							{
  2376  								Type: DiffTypeAdded,
  2377  								Name: "MaxParallel",
  2378  								Old:  "",
  2379  								New:  "0",
  2380  							},
  2381  							{
  2382  								Type: DiffTypeAdded,
  2383  								Name: "MinHealthyTime",
  2384  								Old:  "",
  2385  								New:  "0",
  2386  							},
  2387  							{
  2388  								Type: DiffTypeAdded,
  2389  								Name: "ProgressDeadline",
  2390  								Old:  "",
  2391  								New:  "0",
  2392  							},
  2393  						},
  2394  					},
  2395  				},
  2396  			},
  2397  		},
  2398  		{
  2399  			TestCase: "Update strategy edited",
  2400  			Old: &TaskGroup{
  2401  				Update: &UpdateStrategy{
  2402  					MaxParallel:      5,
  2403  					HealthCheck:      "foo",
  2404  					MinHealthyTime:   1 * time.Second,
  2405  					HealthyDeadline:  30 * time.Second,
  2406  					ProgressDeadline: 29 * time.Second,
  2407  					AutoRevert:       true,
  2408  					AutoPromote:      true,
  2409  					Canary:           2,
  2410  				},
  2411  			},
  2412  			New: &TaskGroup{
  2413  				Update: &UpdateStrategy{
  2414  					MaxParallel:      7,
  2415  					HealthCheck:      "bar",
  2416  					MinHealthyTime:   2 * time.Second,
  2417  					HealthyDeadline:  31 * time.Second,
  2418  					ProgressDeadline: 32 * time.Second,
  2419  					AutoRevert:       false,
  2420  					AutoPromote:      false,
  2421  					Canary:           1,
  2422  				},
  2423  			},
  2424  			Expected: &TaskGroupDiff{
  2425  				Type: DiffTypeEdited,
  2426  				Objects: []*ObjectDiff{
  2427  					{
  2428  						Type: DiffTypeEdited,
  2429  						Name: "Update",
  2430  						Fields: []*FieldDiff{
  2431  							{
  2432  								Type: DiffTypeEdited,
  2433  								Name: "AutoPromote",
  2434  								Old:  "true",
  2435  								New:  "false",
  2436  							},
  2437  							{
  2438  								Type: DiffTypeEdited,
  2439  								Name: "AutoRevert",
  2440  								Old:  "true",
  2441  								New:  "false",
  2442  							},
  2443  							{
  2444  								Type: DiffTypeEdited,
  2445  								Name: "Canary",
  2446  								Old:  "2",
  2447  								New:  "1",
  2448  							},
  2449  							{
  2450  								Type: DiffTypeEdited,
  2451  								Name: "HealthCheck",
  2452  								Old:  "foo",
  2453  								New:  "bar",
  2454  							},
  2455  							{
  2456  								Type: DiffTypeEdited,
  2457  								Name: "HealthyDeadline",
  2458  								Old:  "30000000000",
  2459  								New:  "31000000000",
  2460  							},
  2461  							{
  2462  								Type: DiffTypeEdited,
  2463  								Name: "MaxParallel",
  2464  								Old:  "5",
  2465  								New:  "7",
  2466  							},
  2467  							{
  2468  								Type: DiffTypeEdited,
  2469  								Name: "MinHealthyTime",
  2470  								Old:  "1000000000",
  2471  								New:  "2000000000",
  2472  							},
  2473  							{
  2474  								Type: DiffTypeEdited,
  2475  								Name: "ProgressDeadline",
  2476  								Old:  "29000000000",
  2477  								New:  "32000000000",
  2478  							},
  2479  						},
  2480  					},
  2481  				},
  2482  			},
  2483  		},
  2484  		{
  2485  			TestCase:   "Update strategy edited with context",
  2486  			Contextual: true,
  2487  			Old: &TaskGroup{
  2488  				Update: &UpdateStrategy{
  2489  					MaxParallel:      5,
  2490  					HealthCheck:      "foo",
  2491  					MinHealthyTime:   1 * time.Second,
  2492  					HealthyDeadline:  30 * time.Second,
  2493  					ProgressDeadline: 30 * time.Second,
  2494  					AutoRevert:       true,
  2495  					AutoPromote:      true,
  2496  					Canary:           2,
  2497  				},
  2498  			},
  2499  			New: &TaskGroup{
  2500  				Update: &UpdateStrategy{
  2501  					MaxParallel:      7,
  2502  					HealthCheck:      "foo",
  2503  					MinHealthyTime:   1 * time.Second,
  2504  					HealthyDeadline:  30 * time.Second,
  2505  					ProgressDeadline: 30 * time.Second,
  2506  					AutoRevert:       true,
  2507  					AutoPromote:      true,
  2508  					Canary:           2,
  2509  				},
  2510  			},
  2511  			Expected: &TaskGroupDiff{
  2512  				Type: DiffTypeEdited,
  2513  				Objects: []*ObjectDiff{
  2514  					{
  2515  						Type: DiffTypeEdited,
  2516  						Name: "Update",
  2517  						Fields: []*FieldDiff{
  2518  							{
  2519  								Type: DiffTypeNone,
  2520  								Name: "AutoPromote",
  2521  								Old:  "true",
  2522  								New:  "true",
  2523  							},
  2524  							{
  2525  								Type: DiffTypeNone,
  2526  								Name: "AutoRevert",
  2527  								Old:  "true",
  2528  								New:  "true",
  2529  							},
  2530  							{
  2531  								Type: DiffTypeNone,
  2532  								Name: "Canary",
  2533  								Old:  "2",
  2534  								New:  "2",
  2535  							},
  2536  							{
  2537  								Type: DiffTypeNone,
  2538  								Name: "HealthCheck",
  2539  								Old:  "foo",
  2540  								New:  "foo",
  2541  							},
  2542  							{
  2543  								Type: DiffTypeNone,
  2544  								Name: "HealthyDeadline",
  2545  								Old:  "30000000000",
  2546  								New:  "30000000000",
  2547  							},
  2548  							{
  2549  								Type: DiffTypeEdited,
  2550  								Name: "MaxParallel",
  2551  								Old:  "5",
  2552  								New:  "7",
  2553  							},
  2554  							{
  2555  								Type: DiffTypeNone,
  2556  								Name: "MinHealthyTime",
  2557  								Old:  "1000000000",
  2558  								New:  "1000000000",
  2559  							},
  2560  							{
  2561  								Type: DiffTypeNone,
  2562  								Name: "ProgressDeadline",
  2563  								Old:  "30000000000",
  2564  								New:  "30000000000",
  2565  							},
  2566  						},
  2567  					},
  2568  				},
  2569  			},
  2570  		},
  2571  		{
  2572  			TestCase: "EphemeralDisk added",
  2573  			Old:      &TaskGroup{},
  2574  			New: &TaskGroup{
  2575  				EphemeralDisk: &EphemeralDisk{
  2576  					Migrate: true,
  2577  					Sticky:  true,
  2578  					SizeMB:  100,
  2579  				},
  2580  			},
  2581  			Expected: &TaskGroupDiff{
  2582  				Type: DiffTypeEdited,
  2583  				Objects: []*ObjectDiff{
  2584  					{
  2585  						Type: DiffTypeAdded,
  2586  						Name: "EphemeralDisk",
  2587  						Fields: []*FieldDiff{
  2588  							{
  2589  								Type: DiffTypeAdded,
  2590  								Name: "Migrate",
  2591  								Old:  "",
  2592  								New:  "true",
  2593  							},
  2594  							{
  2595  								Type: DiffTypeAdded,
  2596  								Name: "SizeMB",
  2597  								Old:  "",
  2598  								New:  "100",
  2599  							},
  2600  							{
  2601  								Type: DiffTypeAdded,
  2602  								Name: "Sticky",
  2603  								Old:  "",
  2604  								New:  "true",
  2605  							},
  2606  						},
  2607  					},
  2608  				},
  2609  			},
  2610  		},
  2611  		{
  2612  			TestCase: "EphemeralDisk deleted",
  2613  			Old: &TaskGroup{
  2614  				EphemeralDisk: &EphemeralDisk{
  2615  					Migrate: true,
  2616  					Sticky:  true,
  2617  					SizeMB:  100,
  2618  				},
  2619  			},
  2620  			New: &TaskGroup{},
  2621  			Expected: &TaskGroupDiff{
  2622  				Type: DiffTypeEdited,
  2623  				Objects: []*ObjectDiff{
  2624  					{
  2625  						Type: DiffTypeDeleted,
  2626  						Name: "EphemeralDisk",
  2627  						Fields: []*FieldDiff{
  2628  							{
  2629  								Type: DiffTypeDeleted,
  2630  								Name: "Migrate",
  2631  								Old:  "true",
  2632  								New:  "",
  2633  							},
  2634  							{
  2635  								Type: DiffTypeDeleted,
  2636  								Name: "SizeMB",
  2637  								Old:  "100",
  2638  								New:  "",
  2639  							},
  2640  							{
  2641  								Type: DiffTypeDeleted,
  2642  								Name: "Sticky",
  2643  								Old:  "true",
  2644  								New:  "",
  2645  							},
  2646  						},
  2647  					},
  2648  				},
  2649  			},
  2650  		},
  2651  		{
  2652  			TestCase: "EphemeralDisk edited",
  2653  			Old: &TaskGroup{
  2654  				EphemeralDisk: &EphemeralDisk{
  2655  					Migrate: true,
  2656  					Sticky:  true,
  2657  					SizeMB:  150,
  2658  				},
  2659  			},
  2660  			New: &TaskGroup{
  2661  				EphemeralDisk: &EphemeralDisk{
  2662  					Migrate: false,
  2663  					Sticky:  false,
  2664  					SizeMB:  90,
  2665  				},
  2666  			},
  2667  			Expected: &TaskGroupDiff{
  2668  				Type: DiffTypeEdited,
  2669  				Objects: []*ObjectDiff{
  2670  					{
  2671  						Type: DiffTypeEdited,
  2672  						Name: "EphemeralDisk",
  2673  						Fields: []*FieldDiff{
  2674  							{
  2675  								Type: DiffTypeEdited,
  2676  								Name: "Migrate",
  2677  								Old:  "true",
  2678  								New:  "false",
  2679  							},
  2680  							{
  2681  								Type: DiffTypeEdited,
  2682  								Name: "SizeMB",
  2683  								Old:  "150",
  2684  								New:  "90",
  2685  							},
  2686  
  2687  							{
  2688  								Type: DiffTypeEdited,
  2689  								Name: "Sticky",
  2690  								Old:  "true",
  2691  								New:  "false",
  2692  							},
  2693  						},
  2694  					},
  2695  				},
  2696  			},
  2697  		},
  2698  		{
  2699  			TestCase:   "EphemeralDisk edited with context",
  2700  			Contextual: true,
  2701  			Old: &TaskGroup{
  2702  				EphemeralDisk: &EphemeralDisk{
  2703  					Migrate: false,
  2704  					Sticky:  false,
  2705  					SizeMB:  100,
  2706  				},
  2707  			},
  2708  			New: &TaskGroup{
  2709  				EphemeralDisk: &EphemeralDisk{
  2710  					Migrate: true,
  2711  					Sticky:  true,
  2712  					SizeMB:  90,
  2713  				},
  2714  			},
  2715  			Expected: &TaskGroupDiff{
  2716  				Type: DiffTypeEdited,
  2717  				Objects: []*ObjectDiff{
  2718  					{
  2719  						Type: DiffTypeEdited,
  2720  						Name: "EphemeralDisk",
  2721  						Fields: []*FieldDiff{
  2722  							{
  2723  								Type: DiffTypeEdited,
  2724  								Name: "Migrate",
  2725  								Old:  "false",
  2726  								New:  "true",
  2727  							},
  2728  							{
  2729  								Type: DiffTypeEdited,
  2730  								Name: "SizeMB",
  2731  								Old:  "100",
  2732  								New:  "90",
  2733  							},
  2734  							{
  2735  								Type: DiffTypeEdited,
  2736  								Name: "Sticky",
  2737  								Old:  "false",
  2738  								New:  "true",
  2739  							},
  2740  						},
  2741  					},
  2742  				},
  2743  			},
  2744  		},
  2745  		{
  2746  			TestCase:   "TaskGroup Services edited",
  2747  			Contextual: true,
  2748  			Old: &TaskGroup{
  2749  				Services: []*Service{
  2750  					{
  2751  						Name:              "foo",
  2752  						Namespace:         "team1",
  2753  						TaskName:          "task1",
  2754  						EnableTagOverride: false,
  2755  						Checks: []*ServiceCheck{
  2756  							{
  2757  								Name:                   "foo",
  2758  								Type:                   "http",
  2759  								Command:                "foo",
  2760  								Args:                   []string{"foo"},
  2761  								Path:                   "foo",
  2762  								Protocol:               "http",
  2763  								Method:                 "POST",
  2764  								Body:                   "{\"key\": \"value\"}",
  2765  								Expose:                 true,
  2766  								Interval:               1 * time.Second,
  2767  								Timeout:                1 * time.Second,
  2768  								SuccessBeforePassing:   3,
  2769  								FailuresBeforeCritical: 4,
  2770  							},
  2771  						},
  2772  						Connect: &ConsulConnect{
  2773  							Native: false,
  2774  							SidecarTask: &SidecarTask{
  2775  								Name:   "sidecar",
  2776  								Driver: "docker",
  2777  								Env: map[string]string{
  2778  									"FOO": "BAR",
  2779  								},
  2780  								Config: map[string]interface{}{
  2781  									"foo": "baz",
  2782  								},
  2783  							},
  2784  							Gateway: &ConsulGateway{
  2785  								Proxy: &ConsulGatewayProxy{
  2786  									ConnectTimeout:                  pointer.Of(1 * time.Second),
  2787  									EnvoyGatewayBindTaggedAddresses: false,
  2788  									EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
  2789  										"service1": {
  2790  											Address: "10.0.0.1",
  2791  											Port:    2001,
  2792  										},
  2793  									},
  2794  									EnvoyDNSDiscoveryType:     "STRICT_DNS",
  2795  									EnvoyGatewayNoDefaultBind: false,
  2796  									Config: map[string]interface{}{
  2797  										"foo": 1,
  2798  									},
  2799  								},
  2800  								Ingress: &ConsulIngressConfigEntry{
  2801  									TLS: &ConsulGatewayTLSConfig{
  2802  										Enabled: false,
  2803  									},
  2804  									Listeners: []*ConsulIngressListener{{
  2805  										Port:     3001,
  2806  										Protocol: "tcp",
  2807  										Services: []*ConsulIngressService{{
  2808  											Name: "listener1",
  2809  										}},
  2810  									}},
  2811  								},
  2812  								Terminating: &ConsulTerminatingConfigEntry{
  2813  									Services: []*ConsulLinkedService{{
  2814  										Name:     "linked1",
  2815  										CAFile:   "ca1.pem",
  2816  										CertFile: "cert1.pem",
  2817  										KeyFile:  "key1.pem",
  2818  										SNI:      "linked1.consul",
  2819  									}},
  2820  								},
  2821  								Mesh: &ConsulMeshConfigEntry{
  2822  									// nothing
  2823  								},
  2824  							},
  2825  						},
  2826  					},
  2827  				},
  2828  			},
  2829  
  2830  			New: &TaskGroup{
  2831  				Services: []*Service{
  2832  					{
  2833  						Name:              "foo",
  2834  						Namespace:         "team1",
  2835  						TaskName:          "task2",
  2836  						EnableTagOverride: true,
  2837  						Checks: []*ServiceCheck{
  2838  							{
  2839  								Name:     "foo",
  2840  								Type:     "tcp",
  2841  								Command:  "bar",
  2842  								Path:     "bar",
  2843  								Protocol: "tcp",
  2844  								Expose:   false,
  2845  								Interval: 2 * time.Second,
  2846  								Timeout:  2 * time.Second,
  2847  								Header: map[string][]string{
  2848  									"Foo": {"baz"},
  2849  								},
  2850  								SuccessBeforePassing:   5,
  2851  								FailuresBeforeCritical: 6,
  2852  							},
  2853  						},
  2854  						Connect: &ConsulConnect{
  2855  							Native: true,
  2856  							SidecarService: &ConsulSidecarService{
  2857  								Port: "http",
  2858  								Proxy: &ConsulProxy{
  2859  									LocalServiceAddress: "127.0.0.1",
  2860  									LocalServicePort:    8080,
  2861  									Upstreams: []ConsulUpstream{
  2862  										{
  2863  											DestinationName:      "foo",
  2864  											DestinationNamespace: "ns2",
  2865  											LocalBindPort:        8000,
  2866  											Datacenter:           "dc2",
  2867  											LocalBindAddress:     "127.0.0.2",
  2868  											MeshGateway: ConsulMeshGateway{
  2869  												Mode: "remote",
  2870  											},
  2871  										},
  2872  									},
  2873  									Config: map[string]interface{}{
  2874  										"foo": "qux",
  2875  									},
  2876  								},
  2877  							},
  2878  							Gateway: &ConsulGateway{
  2879  								Proxy: &ConsulGatewayProxy{
  2880  									ConnectTimeout:                  pointer.Of(2 * time.Second),
  2881  									EnvoyGatewayBindTaggedAddresses: true,
  2882  									EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
  2883  										"service1": {
  2884  											Address: "10.0.0.2",
  2885  											Port:    2002,
  2886  										},
  2887  									},
  2888  									EnvoyDNSDiscoveryType:     "LOGICAL_DNS",
  2889  									EnvoyGatewayNoDefaultBind: true,
  2890  									Config: map[string]interface{}{
  2891  										"foo": 2,
  2892  									},
  2893  								},
  2894  								Ingress: &ConsulIngressConfigEntry{
  2895  									TLS: &ConsulGatewayTLSConfig{
  2896  										Enabled: true,
  2897  									},
  2898  									Listeners: []*ConsulIngressListener{{
  2899  										Port:     3002,
  2900  										Protocol: "http",
  2901  										Services: []*ConsulIngressService{{
  2902  											Name:  "listener2",
  2903  											Hosts: []string{"127.0.0.1", "127.0.0.1:3002"},
  2904  										}},
  2905  									}},
  2906  								},
  2907  								Terminating: &ConsulTerminatingConfigEntry{
  2908  									Services: []*ConsulLinkedService{{
  2909  										Name:     "linked2",
  2910  										CAFile:   "ca2.pem",
  2911  										CertFile: "cert2.pem",
  2912  										KeyFile:  "key2.pem",
  2913  										SNI:      "linked2.consul",
  2914  									}},
  2915  								},
  2916  								Mesh: &ConsulMeshConfigEntry{
  2917  									// nothing
  2918  								},
  2919  							},
  2920  						},
  2921  					},
  2922  				},
  2923  			},
  2924  
  2925  			Expected: &TaskGroupDiff{
  2926  				Type: DiffTypeEdited,
  2927  				Objects: []*ObjectDiff{
  2928  					{
  2929  						Type: DiffTypeEdited,
  2930  						Name: "Service",
  2931  						Fields: []*FieldDiff{
  2932  							{
  2933  								Type: DiffTypeNone,
  2934  								Name: "Address",
  2935  								Old:  "",
  2936  								New:  "",
  2937  							},
  2938  							{
  2939  								Type: DiffTypeNone,
  2940  								Name: "AddressMode",
  2941  								Old:  "",
  2942  								New:  "",
  2943  							},
  2944  							{
  2945  								Type: DiffTypeEdited,
  2946  								Name: "EnableTagOverride",
  2947  								Old:  "false",
  2948  								New:  "true",
  2949  							},
  2950  							{
  2951  								Type: DiffTypeNone,
  2952  								Name: "Name",
  2953  								Old:  "foo",
  2954  								New:  "foo",
  2955  							},
  2956  							{
  2957  								Type: DiffTypeNone,
  2958  								Name: "Namespace",
  2959  								Old:  "team1",
  2960  								New:  "team1",
  2961  							},
  2962  							{
  2963  								Type: DiffTypeNone,
  2964  								Name: "OnUpdate",
  2965  								Old:  "",
  2966  								New:  "",
  2967  							},
  2968  							{
  2969  								Type: DiffTypeNone,
  2970  								Name: "PortLabel",
  2971  								Old:  "",
  2972  								New:  "",
  2973  							},
  2974  							{
  2975  								Type: DiffTypeNone,
  2976  								Name: "Provider",
  2977  								Old:  "",
  2978  								New:  "",
  2979  							},
  2980  							{
  2981  								Type: DiffTypeEdited,
  2982  								Name: "TaskName",
  2983  								Old:  "task1",
  2984  								New:  "task2",
  2985  							},
  2986  						},
  2987  						Objects: []*ObjectDiff{
  2988  							{
  2989  								Type: DiffTypeEdited,
  2990  								Name: "Check",
  2991  								Fields: []*FieldDiff{
  2992  									{
  2993  										Type: DiffTypeNone,
  2994  										Name: "AddressMode",
  2995  										Old:  "",
  2996  										New:  "",
  2997  									},
  2998  									{
  2999  										Type: DiffTypeDeleted,
  3000  										Name: "Body",
  3001  										Old:  "{\"key\": \"value\"}",
  3002  										New:  "",
  3003  									},
  3004  									{
  3005  										Type: DiffTypeEdited,
  3006  										Name: "Command",
  3007  										Old:  "foo",
  3008  										New:  "bar",
  3009  									},
  3010  									{
  3011  										Type: DiffTypeEdited,
  3012  										Name: "Expose",
  3013  										Old:  "true",
  3014  										New:  "false",
  3015  									},
  3016  									{
  3017  										Type: DiffTypeEdited,
  3018  										Name: "FailuresBeforeCritical",
  3019  										Old:  "4",
  3020  										New:  "6",
  3021  									},
  3022  									{
  3023  										Type: DiffTypeNone,
  3024  										Name: "GRPCService",
  3025  										Old:  "",
  3026  										New:  "",
  3027  									},
  3028  									{
  3029  										Type: DiffTypeNone,
  3030  										Name: "GRPCUseTLS",
  3031  										Old:  "false",
  3032  										New:  "false",
  3033  									},
  3034  									{
  3035  										Type: DiffTypeNone,
  3036  										Name: "InitialStatus",
  3037  										Old:  "",
  3038  										New:  "",
  3039  									},
  3040  									{
  3041  										Type: DiffTypeEdited,
  3042  										Name: "Interval",
  3043  										Old:  "1000000000",
  3044  										New:  "2000000000",
  3045  									},
  3046  									{
  3047  										Type: DiffTypeDeleted,
  3048  										Name: "Method",
  3049  										Old:  "POST",
  3050  										New:  "",
  3051  									},
  3052  									{
  3053  										Type: DiffTypeNone,
  3054  										Name: "Name",
  3055  										Old:  "foo",
  3056  										New:  "foo",
  3057  									},
  3058  									{
  3059  										Type: DiffTypeNone,
  3060  										Name: "OnUpdate",
  3061  										Old:  "",
  3062  										New:  "",
  3063  									},
  3064  									{
  3065  										Type: DiffTypeEdited,
  3066  										Name: "Path",
  3067  										Old:  "foo",
  3068  										New:  "bar",
  3069  									},
  3070  									{
  3071  										Type: DiffTypeNone,
  3072  										Name: "PortLabel",
  3073  										Old:  "",
  3074  										New:  "",
  3075  									},
  3076  									{
  3077  										Type: DiffTypeEdited,
  3078  										Name: "Protocol",
  3079  										Old:  "http",
  3080  										New:  "tcp",
  3081  									},
  3082  									{
  3083  										Type: DiffTypeEdited,
  3084  										Name: "SuccessBeforePassing",
  3085  										Old:  "3",
  3086  										New:  "5",
  3087  									},
  3088  									{
  3089  										Type: DiffTypeNone,
  3090  										Name: "TLSServerName",
  3091  										Old:  "",
  3092  										New:  "",
  3093  									},
  3094  									{
  3095  										Type: DiffTypeNone,
  3096  										Name: "TLSSkipVerify",
  3097  										Old:  "false",
  3098  										New:  "false",
  3099  									},
  3100  									{
  3101  										Type: DiffTypeNone,
  3102  										Name: "TaskName",
  3103  										Old:  "",
  3104  										New:  "",
  3105  									},
  3106  									{
  3107  										Type: DiffTypeEdited,
  3108  										Name: "Timeout",
  3109  										Old:  "1000000000",
  3110  										New:  "2000000000",
  3111  									},
  3112  									{
  3113  										Type: DiffTypeEdited,
  3114  										Name: "Type",
  3115  										Old:  "http",
  3116  										New:  "tcp",
  3117  									},
  3118  								},
  3119  								Objects: []*ObjectDiff{
  3120  									{
  3121  										Type: DiffTypeAdded,
  3122  										Name: "Header",
  3123  										Fields: []*FieldDiff{
  3124  											{
  3125  												Type: DiffTypeAdded,
  3126  												Name: "Foo[0]",
  3127  												Old:  "",
  3128  												New:  "baz",
  3129  											},
  3130  										},
  3131  									},
  3132  								},
  3133  							},
  3134  							{
  3135  								Type: DiffTypeEdited,
  3136  								Name: "ConsulConnect",
  3137  								Fields: []*FieldDiff{
  3138  									{
  3139  										Type: DiffTypeEdited,
  3140  										Name: "Native",
  3141  										Old:  "false",
  3142  										New:  "true",
  3143  									},
  3144  								},
  3145  								Objects: []*ObjectDiff{
  3146  
  3147  									{
  3148  										Type: DiffTypeAdded,
  3149  										Name: "SidecarService",
  3150  										Fields: []*FieldDiff{
  3151  											{
  3152  												Type: DiffTypeAdded,
  3153  												Name: "DisableDefaultTCPCheck",
  3154  												Old:  "",
  3155  												New:  "false",
  3156  											},
  3157  											{
  3158  												Type: DiffTypeAdded,
  3159  												Name: "Port",
  3160  												Old:  "",
  3161  												New:  "http",
  3162  											},
  3163  										},
  3164  										Objects: []*ObjectDiff{
  3165  											{
  3166  												Type: DiffTypeAdded,
  3167  												Name: "ConsulProxy",
  3168  												Fields: []*FieldDiff{
  3169  													{
  3170  														Type: DiffTypeAdded,
  3171  														Name: "LocalServiceAddress",
  3172  														Old:  "",
  3173  														New:  "127.0.0.1",
  3174  													}, {
  3175  														Type: DiffTypeAdded,
  3176  														Name: "LocalServicePort",
  3177  														Old:  "",
  3178  														New:  "8080",
  3179  													},
  3180  												},
  3181  												Objects: []*ObjectDiff{
  3182  													{
  3183  														Type: DiffTypeAdded,
  3184  														Name: "ConsulUpstreams",
  3185  														Fields: []*FieldDiff{
  3186  															{
  3187  																Type: DiffTypeAdded,
  3188  																Name: "Datacenter",
  3189  																Old:  "",
  3190  																New:  "dc2",
  3191  															},
  3192  															{
  3193  																Type: DiffTypeAdded,
  3194  																Name: "DestinationName",
  3195  																Old:  "",
  3196  																New:  "foo",
  3197  															},
  3198  															{
  3199  																Type: DiffTypeAdded,
  3200  																Name: "DestinationNamespace",
  3201  																Old:  "",
  3202  																New:  "ns2",
  3203  															},
  3204  															{
  3205  																Type: DiffTypeAdded,
  3206  																Name: "LocalBindAddress",
  3207  																Old:  "",
  3208  																New:  "127.0.0.2",
  3209  															},
  3210  															{
  3211  																Type: DiffTypeAdded,
  3212  																Name: "LocalBindPort",
  3213  																Old:  "",
  3214  																New:  "8000",
  3215  															},
  3216  														},
  3217  														Objects: []*ObjectDiff{
  3218  															{
  3219  																Type: DiffTypeAdded,
  3220  																Name: "MeshGateway",
  3221  																Fields: []*FieldDiff{
  3222  																	{
  3223  																		Type: DiffTypeAdded,
  3224  																		Name: "Mode",
  3225  																		Old:  "",
  3226  																		New:  "remote",
  3227  																	},
  3228  																},
  3229  															},
  3230  														},
  3231  													},
  3232  													{
  3233  														Type: DiffTypeAdded,
  3234  														Name: "Config",
  3235  														Fields: []*FieldDiff{
  3236  															{
  3237  																Type: DiffTypeAdded,
  3238  																Name: "foo",
  3239  																Old:  "",
  3240  																New:  "qux",
  3241  															},
  3242  														},
  3243  													},
  3244  												},
  3245  											},
  3246  										},
  3247  									},
  3248  
  3249  									{
  3250  										Type: DiffTypeDeleted,
  3251  										Name: "SidecarTask",
  3252  										Fields: []*FieldDiff{
  3253  											{
  3254  												Type: DiffTypeDeleted,
  3255  												Name: "Driver",
  3256  												Old:  "docker",
  3257  												New:  "",
  3258  											},
  3259  											{
  3260  												Type: DiffTypeDeleted,
  3261  												Name: "Env[FOO]",
  3262  												Old:  "BAR",
  3263  												New:  "",
  3264  											},
  3265  											{
  3266  												Type: DiffTypeDeleted,
  3267  												Name: "Name",
  3268  												Old:  "sidecar",
  3269  												New:  "",
  3270  											},
  3271  										},
  3272  										Objects: []*ObjectDiff{
  3273  											{
  3274  												Type: DiffTypeDeleted,
  3275  												Name: "Config",
  3276  												Fields: []*FieldDiff{
  3277  													{
  3278  														Type: DiffTypeDeleted,
  3279  														Name: "foo",
  3280  														Old:  "baz",
  3281  														New:  "",
  3282  													},
  3283  												},
  3284  											},
  3285  										},
  3286  									},
  3287  									{
  3288  										Type: DiffTypeEdited,
  3289  										Name: "Gateway",
  3290  										Objects: []*ObjectDiff{
  3291  											{
  3292  												Type: DiffTypeEdited,
  3293  												Name: "Proxy",
  3294  												Fields: []*FieldDiff{
  3295  													{
  3296  														Type: DiffTypeEdited,
  3297  														Name: "ConnectTimeout",
  3298  														Old:  "1s",
  3299  														New:  "2s",
  3300  													},
  3301  													{
  3302  														Type: DiffTypeEdited,
  3303  														Name: "EnvoyDNSDiscoveryType",
  3304  														Old:  "STRICT_DNS",
  3305  														New:  "LOGICAL_DNS",
  3306  													},
  3307  													{
  3308  														Type: DiffTypeEdited,
  3309  														Name: "EnvoyGatewayBindTaggedAddresses",
  3310  														Old:  "false",
  3311  														New:  "true",
  3312  													},
  3313  													{
  3314  														Type: DiffTypeEdited,
  3315  														Name: "EnvoyGatewayNoDefaultBind",
  3316  														Old:  "false",
  3317  														New:  "true",
  3318  													},
  3319  												},
  3320  												Objects: []*ObjectDiff{
  3321  													{
  3322  														Type: DiffTypeEdited,
  3323  														Name: "EnvoyGatewayBindAddresses",
  3324  														Fields: []*FieldDiff{
  3325  															{
  3326  																Type: DiffTypeEdited,
  3327  																Name: "service1",
  3328  																Old:  "10.0.0.1:2001",
  3329  																New:  "10.0.0.2:2002",
  3330  															},
  3331  														},
  3332  													},
  3333  													{
  3334  														Type: DiffTypeEdited,
  3335  														Name: "Config",
  3336  														Fields: []*FieldDiff{
  3337  															{
  3338  																Type: DiffTypeEdited,
  3339  																Name: "foo",
  3340  																Old:  "1",
  3341  																New:  "2",
  3342  															},
  3343  														},
  3344  													},
  3345  												},
  3346  											},
  3347  											{
  3348  												Type: DiffTypeEdited,
  3349  												Name: "Ingress",
  3350  												Objects: []*ObjectDiff{
  3351  													{
  3352  														Type: DiffTypeEdited,
  3353  														Name: "TLS",
  3354  														Fields: []*FieldDiff{
  3355  															{
  3356  																Type: DiffTypeEdited,
  3357  																Name: "Enabled",
  3358  																Old:  "false",
  3359  																New:  "true",
  3360  															},
  3361  															{
  3362  																Type: DiffTypeNone,
  3363  																Name: "TLSMaxVersion",
  3364  																Old:  "",
  3365  																New:  "",
  3366  															},
  3367  															{
  3368  																Type: DiffTypeNone,
  3369  																Name: "TLSMinVersion",
  3370  																Old:  "",
  3371  																New:  "",
  3372  															},
  3373  														},
  3374  													},
  3375  													{
  3376  														Type: DiffTypeAdded,
  3377  														Name: "Listener",
  3378  														Fields: []*FieldDiff{
  3379  															{
  3380  																Type: DiffTypeAdded,
  3381  																Name: "Port",
  3382  																Old:  "",
  3383  																New:  "3002",
  3384  															},
  3385  															{
  3386  																Type: DiffTypeAdded,
  3387  																Name: "Protocol",
  3388  																Old:  "",
  3389  																New:  "http",
  3390  															},
  3391  														},
  3392  														Objects: []*ObjectDiff{
  3393  															{
  3394  																Type: DiffTypeAdded,
  3395  																Name: "ConsulIngressService",
  3396  																Fields: []*FieldDiff{
  3397  																	{
  3398  																		Type: DiffTypeAdded,
  3399  																		Name: "Name",
  3400  																		Old:  "",
  3401  																		New:  "listener2",
  3402  																	},
  3403  																},
  3404  																Objects: []*ObjectDiff{
  3405  																	{
  3406  																		Type: DiffTypeAdded,
  3407  																		Name: "Hosts",
  3408  																		Fields: []*FieldDiff{
  3409  																			{
  3410  																				Type: DiffTypeAdded,
  3411  																				Name: "Hosts",
  3412  																				Old:  "",
  3413  																				New:  "127.0.0.1",
  3414  																			},
  3415  																			{
  3416  																				Type: DiffTypeAdded,
  3417  																				Name: "Hosts",
  3418  																				Old:  "",
  3419  																				New:  "127.0.0.1:3002",
  3420  																			},
  3421  																		},
  3422  																	},
  3423  																},
  3424  															},
  3425  														},
  3426  													},
  3427  													{
  3428  														Type: DiffTypeDeleted,
  3429  														Name: "Listener",
  3430  														Fields: []*FieldDiff{
  3431  															{
  3432  																Type: DiffTypeDeleted,
  3433  																Name: "Port",
  3434  																Old:  "3001",
  3435  																New:  "",
  3436  															},
  3437  															{
  3438  																Type: DiffTypeDeleted,
  3439  																Name: "Protocol",
  3440  																Old:  "tcp",
  3441  																New:  "",
  3442  															},
  3443  														},
  3444  														Objects: []*ObjectDiff{
  3445  															{
  3446  																Type: DiffTypeDeleted,
  3447  																Name: "ConsulIngressService",
  3448  																Fields: []*FieldDiff{
  3449  																	{
  3450  																		Type: DiffTypeDeleted,
  3451  																		Name: "Name",
  3452  																		Old:  "listener1",
  3453  																		New:  "",
  3454  																	},
  3455  																},
  3456  															},
  3457  														},
  3458  													},
  3459  												},
  3460  											},
  3461  											{
  3462  												Type: DiffTypeEdited,
  3463  												Name: "Terminating",
  3464  												Objects: []*ObjectDiff{
  3465  													{
  3466  														Type: DiffTypeAdded,
  3467  														Name: "Service",
  3468  														Fields: []*FieldDiff{
  3469  															{
  3470  																Type: DiffTypeAdded,
  3471  																Name: "CAFile",
  3472  																Old:  "",
  3473  																New:  "ca2.pem",
  3474  															},
  3475  															{
  3476  																Type: DiffTypeAdded,
  3477  																Name: "CertFile",
  3478  																Old:  "",
  3479  																New:  "cert2.pem",
  3480  															},
  3481  															{
  3482  																Type: DiffTypeAdded,
  3483  																Name: "KeyFile",
  3484  																Old:  "",
  3485  																New:  "key2.pem",
  3486  															},
  3487  															{
  3488  																Type: DiffTypeAdded,
  3489  																Name: "Name",
  3490  																Old:  "",
  3491  																New:  "linked2",
  3492  															},
  3493  															{
  3494  																Type: DiffTypeAdded,
  3495  																Name: "SNI",
  3496  																Old:  "",
  3497  																New:  "linked2.consul",
  3498  															},
  3499  														},
  3500  													},
  3501  													{
  3502  														Type: DiffTypeDeleted,
  3503  														Name: "Service",
  3504  														Fields: []*FieldDiff{
  3505  															{
  3506  																Type: DiffTypeDeleted,
  3507  																Name: "CAFile",
  3508  																Old:  "ca1.pem",
  3509  																New:  "",
  3510  															},
  3511  															{
  3512  																Type: DiffTypeDeleted,
  3513  																Name: "CertFile",
  3514  																Old:  "cert1.pem",
  3515  																New:  "",
  3516  															},
  3517  															{
  3518  																Type: DiffTypeDeleted,
  3519  																Name: "KeyFile",
  3520  																Old:  "key1.pem",
  3521  																New:  "",
  3522  															},
  3523  															{
  3524  																Type: DiffTypeDeleted,
  3525  																Name: "Name",
  3526  																Old:  "linked1",
  3527  																New:  "",
  3528  															},
  3529  															{
  3530  																Type: DiffTypeDeleted,
  3531  																Name: "SNI",
  3532  																Old:  "linked1.consul",
  3533  																New:  "",
  3534  															},
  3535  														},
  3536  													},
  3537  												},
  3538  											},
  3539  										},
  3540  									},
  3541  								},
  3542  							},
  3543  						},
  3544  					},
  3545  				},
  3546  			},
  3547  		},
  3548  		{
  3549  			TestCase:   "TaskGroup Networks edited",
  3550  			Contextual: true,
  3551  			Old: &TaskGroup{
  3552  				Networks: Networks{
  3553  					{
  3554  						Device:   "foo",
  3555  						CIDR:     "foo",
  3556  						IP:       "foo",
  3557  						MBits:    100,
  3558  						Hostname: "foo",
  3559  						ReservedPorts: []Port{
  3560  							{
  3561  								Label: "foo",
  3562  								Value: 80,
  3563  							},
  3564  						},
  3565  					},
  3566  				},
  3567  			},
  3568  			New: &TaskGroup{
  3569  				Networks: Networks{
  3570  					{
  3571  						Device:   "bar",
  3572  						CIDR:     "bar",
  3573  						IP:       "bar",
  3574  						MBits:    200,
  3575  						Hostname: "bar",
  3576  						DynamicPorts: []Port{
  3577  							{
  3578  								Label:       "bar",
  3579  								To:          8081,
  3580  								HostNetwork: "public",
  3581  							},
  3582  						},
  3583  						DNS: &DNSConfig{
  3584  							Servers: []string{"1.1.1.1"},
  3585  						},
  3586  					},
  3587  				},
  3588  			},
  3589  			Expected: &TaskGroupDiff{
  3590  				Type: DiffTypeEdited,
  3591  				Objects: []*ObjectDiff{
  3592  					{
  3593  						Type: DiffTypeAdded,
  3594  						Name: "Network",
  3595  						Fields: []*FieldDiff{
  3596  							{
  3597  								Type: DiffTypeAdded,
  3598  								Name: "Hostname",
  3599  								Old:  "",
  3600  								New:  "bar",
  3601  							},
  3602  							{
  3603  								Type: DiffTypeAdded,
  3604  								Name: "MBits",
  3605  								Old:  "",
  3606  								New:  "200",
  3607  							},
  3608  							{
  3609  								Type: DiffTypeNone,
  3610  								Name: "Mode",
  3611  								Old:  "",
  3612  								New:  "",
  3613  							},
  3614  						},
  3615  						Objects: []*ObjectDiff{
  3616  							{
  3617  								Type: DiffTypeAdded,
  3618  								Name: "Dynamic Port",
  3619  								Fields: []*FieldDiff{
  3620  									{
  3621  										Type: DiffTypeAdded,
  3622  										Name: "HostNetwork",
  3623  										Old:  "",
  3624  										New:  "public",
  3625  									},
  3626  									{
  3627  										Type: DiffTypeAdded,
  3628  										Name: "Label",
  3629  										Old:  "",
  3630  										New:  "bar",
  3631  									},
  3632  									{
  3633  										Type: DiffTypeAdded,
  3634  										Name: "To",
  3635  										Old:  "",
  3636  										New:  "8081",
  3637  									},
  3638  								},
  3639  							},
  3640  							{
  3641  								Type: DiffTypeAdded,
  3642  								Name: "DNS",
  3643  								Fields: []*FieldDiff{
  3644  									{
  3645  										Type: DiffTypeAdded,
  3646  										Name: "Servers",
  3647  										Old:  "",
  3648  										New:  "1.1.1.1",
  3649  									},
  3650  								},
  3651  							},
  3652  						},
  3653  					},
  3654  					{
  3655  						Type: DiffTypeDeleted,
  3656  						Name: "Network",
  3657  						Fields: []*FieldDiff{
  3658  							{
  3659  								Type: DiffTypeDeleted,
  3660  								Name: "Hostname",
  3661  								Old:  "foo",
  3662  								New:  "",
  3663  							},
  3664  							{
  3665  								Type: DiffTypeDeleted,
  3666  								Name: "MBits",
  3667  								Old:  "100",
  3668  								New:  "",
  3669  							},
  3670  							{
  3671  								Type: DiffTypeNone,
  3672  								Name: "Mode",
  3673  								Old:  "",
  3674  								New:  "",
  3675  							},
  3676  						},
  3677  						Objects: []*ObjectDiff{
  3678  							{
  3679  								Type: DiffTypeDeleted,
  3680  								Name: "Static Port",
  3681  								Fields: []*FieldDiff{
  3682  									{
  3683  										Type: DiffTypeNone,
  3684  										Name: "HostNetwork",
  3685  										Old:  "",
  3686  										New:  "",
  3687  									},
  3688  									{
  3689  										Type: DiffTypeDeleted,
  3690  										Name: "Label",
  3691  										Old:  "foo",
  3692  										New:  "",
  3693  									},
  3694  									{
  3695  										Type: DiffTypeDeleted,
  3696  										Name: "To",
  3697  										Old:  "0",
  3698  										New:  "",
  3699  									},
  3700  									{
  3701  										Type: DiffTypeDeleted,
  3702  										Name: "Value",
  3703  										Old:  "80",
  3704  										New:  "",
  3705  									},
  3706  								},
  3707  							},
  3708  						},
  3709  					},
  3710  				},
  3711  			},
  3712  		},
  3713  		{
  3714  			TestCase: "Tasks edited",
  3715  			Old: &TaskGroup{
  3716  				Tasks: []*Task{
  3717  					{
  3718  						Name:   "foo",
  3719  						Driver: "docker",
  3720  					},
  3721  					{
  3722  						Name:   "bar",
  3723  						Driver: "docker",
  3724  					},
  3725  					{
  3726  						Name:          "baz",
  3727  						ShutdownDelay: 1 * time.Second,
  3728  					},
  3729  				},
  3730  			},
  3731  			New: &TaskGroup{
  3732  				Tasks: []*Task{
  3733  					{
  3734  						Name:   "bar",
  3735  						Driver: "docker",
  3736  					},
  3737  					{
  3738  						Name:   "bam",
  3739  						Driver: "docker",
  3740  					},
  3741  					{
  3742  						Name:          "baz",
  3743  						ShutdownDelay: 2 * time.Second,
  3744  					},
  3745  				},
  3746  			},
  3747  			Expected: &TaskGroupDiff{
  3748  				Type: DiffTypeEdited,
  3749  				Tasks: []*TaskDiff{
  3750  					{
  3751  						Type: DiffTypeAdded,
  3752  						Name: "bam",
  3753  						Fields: []*FieldDiff{
  3754  							{
  3755  								Type: DiffTypeAdded,
  3756  								Name: "Driver",
  3757  								Old:  "",
  3758  								New:  "docker",
  3759  							},
  3760  							{
  3761  								Type: DiffTypeAdded,
  3762  								Name: "KillTimeout",
  3763  								Old:  "",
  3764  								New:  "0",
  3765  							},
  3766  							{
  3767  								Type: DiffTypeAdded,
  3768  								Name: "Leader",
  3769  								Old:  "",
  3770  								New:  "false",
  3771  							},
  3772  							{
  3773  								Type: DiffTypeAdded,
  3774  								Name: "ShutdownDelay",
  3775  								Old:  "",
  3776  								New:  "0",
  3777  							},
  3778  						},
  3779  					},
  3780  					{
  3781  						Type: DiffTypeNone,
  3782  						Name: "bar",
  3783  					},
  3784  					{
  3785  						Type: DiffTypeEdited,
  3786  						Name: "baz",
  3787  						Fields: []*FieldDiff{
  3788  							{
  3789  								Type: DiffTypeEdited,
  3790  								Name: "ShutdownDelay",
  3791  								Old:  "1000000000",
  3792  								New:  "2000000000",
  3793  							},
  3794  						},
  3795  					},
  3796  					{
  3797  						Type: DiffTypeDeleted,
  3798  						Name: "foo",
  3799  						Fields: []*FieldDiff{
  3800  							{
  3801  								Type: DiffTypeDeleted,
  3802  								Name: "Driver",
  3803  								Old:  "docker",
  3804  								New:  "",
  3805  							},
  3806  							{
  3807  								Type: DiffTypeDeleted,
  3808  								Name: "KillTimeout",
  3809  								Old:  "0",
  3810  								New:  "",
  3811  							},
  3812  							{
  3813  								Type: DiffTypeDeleted,
  3814  								Name: "Leader",
  3815  								Old:  "false",
  3816  								New:  "",
  3817  							},
  3818  							{
  3819  								Type: DiffTypeDeleted,
  3820  								Name: "ShutdownDelay",
  3821  								Old:  "0",
  3822  								New:  "",
  3823  							},
  3824  						},
  3825  					},
  3826  				},
  3827  			},
  3828  		},
  3829  		{
  3830  			TestCase: "TaskGroup shutdown_delay edited",
  3831  			Old: &TaskGroup{
  3832  				ShutdownDelay: pointer.Of(30 * time.Second),
  3833  			},
  3834  			New: &TaskGroup{
  3835  				ShutdownDelay: pointer.Of(5 * time.Second),
  3836  			},
  3837  			Expected: &TaskGroupDiff{
  3838  				Type: DiffTypeEdited,
  3839  				Fields: []*FieldDiff{
  3840  					{
  3841  						Type: DiffTypeEdited,
  3842  						Name: "ShutdownDelay",
  3843  						Old:  "30000000000",
  3844  						New:  "5000000000",
  3845  					},
  3846  				},
  3847  			},
  3848  		},
  3849  		{
  3850  			TestCase: "TaskGroup shutdown_delay removed",
  3851  			Old: &TaskGroup{
  3852  				ShutdownDelay: pointer.Of(30 * time.Second),
  3853  			},
  3854  			New: &TaskGroup{},
  3855  			Expected: &TaskGroupDiff{
  3856  				Type: DiffTypeEdited,
  3857  				Fields: []*FieldDiff{
  3858  					{
  3859  						Type: DiffTypeDeleted,
  3860  						Name: "ShutdownDelay",
  3861  						Old:  "30000000000",
  3862  						New:  "",
  3863  					},
  3864  				},
  3865  			},
  3866  		},
  3867  		{
  3868  			TestCase: "TaskGroup shutdown_delay added",
  3869  			Old:      &TaskGroup{},
  3870  			New: &TaskGroup{
  3871  				ShutdownDelay: pointer.Of(30 * time.Second),
  3872  			},
  3873  			Expected: &TaskGroupDiff{
  3874  				Type: DiffTypeEdited,
  3875  				Fields: []*FieldDiff{
  3876  					{
  3877  						Type: DiffTypeAdded,
  3878  						Name: "ShutdownDelay",
  3879  						Old:  "",
  3880  						New:  "30000000000",
  3881  					},
  3882  				},
  3883  			},
  3884  		},
  3885  
  3886  		{
  3887  			TestCase: "TaskGroup volumes added",
  3888  			Old:      &TaskGroup{},
  3889  			New: &TaskGroup{
  3890  				Volumes: map[string]*VolumeRequest{
  3891  					"foo": {
  3892  						Name:     "foo",
  3893  						Type:     "host",
  3894  						Source:   "foo-src",
  3895  						ReadOnly: true,
  3896  						PerAlloc: true,
  3897  					},
  3898  				},
  3899  			},
  3900  
  3901  			Expected: &TaskGroupDiff{
  3902  				Type: DiffTypeEdited,
  3903  				Objects: []*ObjectDiff{
  3904  					{
  3905  						Type: DiffTypeAdded,
  3906  						Name: "Volume",
  3907  						Fields: []*FieldDiff{
  3908  							{
  3909  								Type: DiffTypeAdded,
  3910  								Name: "Name",
  3911  								Old:  "",
  3912  								New:  "foo",
  3913  							},
  3914  							{
  3915  								Type: DiffTypeAdded,
  3916  								Name: "PerAlloc",
  3917  								Old:  "",
  3918  								New:  "true",
  3919  							},
  3920  							{
  3921  								Type: DiffTypeAdded,
  3922  								Name: "ReadOnly",
  3923  								Old:  "",
  3924  								New:  "true",
  3925  							},
  3926  							{
  3927  								Type: DiffTypeAdded,
  3928  								Name: "Source",
  3929  								Old:  "",
  3930  								New:  "foo-src",
  3931  							},
  3932  							{
  3933  								Type: DiffTypeAdded,
  3934  								Name: "Type",
  3935  								Old:  "",
  3936  								New:  "host",
  3937  							},
  3938  						},
  3939  					},
  3940  				},
  3941  			},
  3942  		},
  3943  
  3944  		{
  3945  			TestCase: "TaskGroup volumes edited",
  3946  			Old: &TaskGroup{
  3947  				Volumes: map[string]*VolumeRequest{
  3948  					"foo": {
  3949  						Name:     "foo",
  3950  						Type:     "csi",
  3951  						Source:   "foo-src1",
  3952  						ReadOnly: false,
  3953  						MountOptions: &CSIMountOptions{
  3954  							FSType:     "ext4",
  3955  							MountFlags: []string{"relatime", "rw"},
  3956  						},
  3957  					},
  3958  					"bar": {
  3959  						Name:     "bar",
  3960  						Type:     "host",
  3961  						Source:   "bar-src",
  3962  						ReadOnly: true,
  3963  					},
  3964  				},
  3965  			},
  3966  			New: &TaskGroup{
  3967  				Volumes: map[string]*VolumeRequest{
  3968  					"foo": {
  3969  						Name:     "foo",
  3970  						Type:     "csi",
  3971  						Source:   "foo-src2",
  3972  						ReadOnly: true,
  3973  						MountOptions: &CSIMountOptions{
  3974  							FSType:     "ext4",
  3975  							MountFlags: []string{"relatime", "rw", "nosuid"},
  3976  						},
  3977  					},
  3978  					"bar": { // untouched
  3979  						Name:     "bar",
  3980  						Type:     "host",
  3981  						Source:   "bar-src",
  3982  						ReadOnly: true,
  3983  					},
  3984  				},
  3985  			},
  3986  
  3987  			Expected: &TaskGroupDiff{
  3988  				Type: DiffTypeEdited,
  3989  				Objects: []*ObjectDiff{
  3990  					{
  3991  						Type: DiffTypeEdited,
  3992  						Name: "Volume",
  3993  						Fields: []*FieldDiff{
  3994  							{
  3995  								Type: DiffTypeEdited,
  3996  								Name: "ReadOnly",
  3997  								Old:  "false",
  3998  								New:  "true",
  3999  							},
  4000  							{
  4001  								Type: DiffTypeEdited,
  4002  								Name: "Source",
  4003  								Old:  "foo-src1",
  4004  								New:  "foo-src2",
  4005  							},
  4006  						},
  4007  						Objects: []*ObjectDiff{
  4008  							{
  4009  								Type: DiffTypeEdited,
  4010  								Name: "MountOptions",
  4011  								Objects: []*ObjectDiff{
  4012  									{
  4013  										Type: DiffTypeAdded,
  4014  										Name: "MountFlags",
  4015  										Fields: []*FieldDiff{
  4016  											{
  4017  												Type: DiffTypeAdded,
  4018  												Name: "MountFlags",
  4019  												Old:  "",
  4020  												New:  "nosuid",
  4021  											},
  4022  										},
  4023  									},
  4024  								},
  4025  							},
  4026  						},
  4027  					},
  4028  				},
  4029  			},
  4030  		},
  4031  		{
  4032  			TestCase: "MaxClientDisconnect added",
  4033  			Old: &TaskGroup{
  4034  				Name:                "foo",
  4035  				MaxClientDisconnect: nil,
  4036  			},
  4037  			New: &TaskGroup{
  4038  				Name:                "foo",
  4039  				MaxClientDisconnect: pointer.Of(20 * time.Second),
  4040  			},
  4041  			Expected: &TaskGroupDiff{
  4042  				Type: DiffTypeEdited,
  4043  				Name: "foo",
  4044  				Fields: []*FieldDiff{
  4045  					{
  4046  						Type: DiffTypeAdded,
  4047  						Name: "MaxClientDisconnect",
  4048  						Old:  "",
  4049  						New:  "20000000000",
  4050  					},
  4051  				},
  4052  			},
  4053  		},
  4054  		{
  4055  			TestCase: "MaxClientDisconnect updated",
  4056  			Old: &TaskGroup{
  4057  				Name:                "foo",
  4058  				MaxClientDisconnect: pointer.Of(10 * time.Second),
  4059  			},
  4060  			New: &TaskGroup{
  4061  				Name:                "foo",
  4062  				MaxClientDisconnect: pointer.Of(20 * time.Second),
  4063  			},
  4064  			Expected: &TaskGroupDiff{
  4065  				Type: DiffTypeEdited,
  4066  				Name: "foo",
  4067  				Fields: []*FieldDiff{
  4068  					{
  4069  						Type: DiffTypeEdited,
  4070  						Name: "MaxClientDisconnect",
  4071  						Old:  "10000000000",
  4072  						New:  "20000000000",
  4073  					},
  4074  				},
  4075  			},
  4076  		},
  4077  		{
  4078  			TestCase: "MaxClientDisconnect deleted",
  4079  			Old: &TaskGroup{
  4080  				Name:                "foo",
  4081  				MaxClientDisconnect: pointer.Of(10 * time.Second),
  4082  			},
  4083  			New: &TaskGroup{
  4084  				Name:                "foo",
  4085  				MaxClientDisconnect: nil,
  4086  			},
  4087  			Expected: &TaskGroupDiff{
  4088  				Type: DiffTypeEdited,
  4089  				Name: "foo",
  4090  				Fields: []*FieldDiff{
  4091  					{
  4092  						Type: DiffTypeDeleted,
  4093  						Name: "MaxClientDisconnect",
  4094  						Old:  "10000000000",
  4095  						New:  "",
  4096  					},
  4097  				},
  4098  			},
  4099  		},
  4100  	}
  4101  
  4102  	for i, c := range cases {
  4103  		require.NotEmpty(t, c.TestCase, "case #%d needs a name", i+1)
  4104  
  4105  		t.Run(c.TestCase, func(t *testing.T) {
  4106  			result, err := c.Old.Diff(c.New, c.Contextual)
  4107  			switch c.ExpErr {
  4108  			case true:
  4109  				require.Error(t, err, "case %q expected error", c.TestCase)
  4110  			case false:
  4111  				require.NoError(t, err, "case %q expected no error", c.TestCase)
  4112  				require.Equal(t, c.Expected, result)
  4113  			}
  4114  		})
  4115  	}
  4116  }
  4117  
  4118  func TestTaskDiff(t *testing.T) {
  4119  	ci.Parallel(t)
  4120  
  4121  	cases := []struct {
  4122  		Name       string
  4123  		Old, New   *Task
  4124  		Expected   *TaskDiff
  4125  		Error      bool
  4126  		Contextual bool
  4127  	}{
  4128  		{
  4129  			Name: "Empty",
  4130  			Old:  nil,
  4131  			New:  nil,
  4132  			Expected: &TaskDiff{
  4133  				Type: DiffTypeNone,
  4134  			},
  4135  		},
  4136  		{
  4137  			Name: "Primitive only that has different names",
  4138  			Old: &Task{
  4139  				Name: "foo",
  4140  				Meta: map[string]string{
  4141  					"foo": "bar",
  4142  				},
  4143  			},
  4144  			New: &Task{
  4145  				Name: "bar",
  4146  				Meta: map[string]string{
  4147  					"foo": "bar",
  4148  				},
  4149  			},
  4150  			Error: true,
  4151  		},
  4152  		{
  4153  			Name: "Primitive only that is the same",
  4154  			Old: &Task{
  4155  				Name:   "foo",
  4156  				Driver: "exec",
  4157  				User:   "foo",
  4158  				Env: map[string]string{
  4159  					"FOO": "bar",
  4160  				},
  4161  				Meta: map[string]string{
  4162  					"foo": "bar",
  4163  				},
  4164  				KillTimeout: 1 * time.Second,
  4165  				Leader:      true,
  4166  			},
  4167  			New: &Task{
  4168  				Name:   "foo",
  4169  				Driver: "exec",
  4170  				User:   "foo",
  4171  				Env: map[string]string{
  4172  					"FOO": "bar",
  4173  				},
  4174  				Meta: map[string]string{
  4175  					"foo": "bar",
  4176  				},
  4177  				KillTimeout: 1 * time.Second,
  4178  				Leader:      true,
  4179  			},
  4180  			Expected: &TaskDiff{
  4181  				Type: DiffTypeNone,
  4182  				Name: "foo",
  4183  			},
  4184  		},
  4185  		{
  4186  			Name: "Primitive only that has diffs",
  4187  			Old: &Task{
  4188  				Name:   "foo",
  4189  				Driver: "exec",
  4190  				User:   "foo",
  4191  				Env: map[string]string{
  4192  					"FOO": "bar",
  4193  				},
  4194  				Meta: map[string]string{
  4195  					"foo": "bar",
  4196  				},
  4197  				KillTimeout: 1 * time.Second,
  4198  				Leader:      true,
  4199  			},
  4200  			New: &Task{
  4201  				Name:   "foo",
  4202  				Driver: "docker",
  4203  				User:   "bar",
  4204  				Env: map[string]string{
  4205  					"FOO": "baz",
  4206  				},
  4207  				Meta: map[string]string{
  4208  					"foo": "baz",
  4209  				},
  4210  				KillTimeout: 2 * time.Second,
  4211  				Leader:      false,
  4212  			},
  4213  			Expected: &TaskDiff{
  4214  				Type: DiffTypeEdited,
  4215  				Name: "foo",
  4216  				Fields: []*FieldDiff{
  4217  					{
  4218  						Type: DiffTypeEdited,
  4219  						Name: "Driver",
  4220  						Old:  "exec",
  4221  						New:  "docker",
  4222  					},
  4223  					{
  4224  						Type: DiffTypeEdited,
  4225  						Name: "Env[FOO]",
  4226  						Old:  "bar",
  4227  						New:  "baz",
  4228  					},
  4229  					{
  4230  						Type: DiffTypeEdited,
  4231  						Name: "KillTimeout",
  4232  						Old:  "1000000000",
  4233  						New:  "2000000000",
  4234  					},
  4235  					{
  4236  						Type: DiffTypeEdited,
  4237  						Name: "Leader",
  4238  						Old:  "true",
  4239  						New:  "false",
  4240  					},
  4241  					{
  4242  						Type: DiffTypeEdited,
  4243  						Name: "Meta[foo]",
  4244  						Old:  "bar",
  4245  						New:  "baz",
  4246  					},
  4247  					{
  4248  						Type: DiffTypeEdited,
  4249  						Name: "User",
  4250  						Old:  "foo",
  4251  						New:  "bar",
  4252  					},
  4253  				},
  4254  			},
  4255  		},
  4256  		{
  4257  			Name: "Map diff",
  4258  			Old: &Task{
  4259  				Meta: map[string]string{
  4260  					"foo": "foo",
  4261  					"bar": "bar",
  4262  				},
  4263  				Env: map[string]string{
  4264  					"foo": "foo",
  4265  					"bar": "bar",
  4266  				},
  4267  			},
  4268  			New: &Task{
  4269  				Meta: map[string]string{
  4270  					"bar": "bar",
  4271  					"baz": "baz",
  4272  				},
  4273  				Env: map[string]string{
  4274  					"bar": "bar",
  4275  					"baz": "baz",
  4276  				},
  4277  			},
  4278  			Expected: &TaskDiff{
  4279  				Type: DiffTypeEdited,
  4280  				Fields: []*FieldDiff{
  4281  					{
  4282  						Type: DiffTypeAdded,
  4283  						Name: "Env[baz]",
  4284  						Old:  "",
  4285  						New:  "baz",
  4286  					},
  4287  					{
  4288  						Type: DiffTypeDeleted,
  4289  						Name: "Env[foo]",
  4290  						Old:  "foo",
  4291  						New:  "",
  4292  					},
  4293  					{
  4294  						Type: DiffTypeAdded,
  4295  						Name: "Meta[baz]",
  4296  						Old:  "",
  4297  						New:  "baz",
  4298  					},
  4299  					{
  4300  						Type: DiffTypeDeleted,
  4301  						Name: "Meta[foo]",
  4302  						Old:  "foo",
  4303  						New:  "",
  4304  					},
  4305  				},
  4306  			},
  4307  		},
  4308  		{
  4309  			Name: "Constraints edited",
  4310  			Old: &Task{
  4311  				Constraints: []*Constraint{
  4312  					{
  4313  						LTarget: "foo",
  4314  						RTarget: "foo",
  4315  						Operand: "foo",
  4316  					},
  4317  					{
  4318  						LTarget: "bar",
  4319  						RTarget: "bar",
  4320  						Operand: "bar",
  4321  					},
  4322  				},
  4323  			},
  4324  			New: &Task{
  4325  				Constraints: []*Constraint{
  4326  					{
  4327  						LTarget: "foo",
  4328  						RTarget: "foo",
  4329  						Operand: "foo",
  4330  					},
  4331  					{
  4332  						LTarget: "baz",
  4333  						RTarget: "baz",
  4334  						Operand: "baz",
  4335  					},
  4336  				},
  4337  			},
  4338  			Expected: &TaskDiff{
  4339  				Type: DiffTypeEdited,
  4340  				Objects: []*ObjectDiff{
  4341  					{
  4342  						Type: DiffTypeAdded,
  4343  						Name: "Constraint",
  4344  						Fields: []*FieldDiff{
  4345  							{
  4346  								Type: DiffTypeAdded,
  4347  								Name: "LTarget",
  4348  								Old:  "",
  4349  								New:  "baz",
  4350  							},
  4351  							{
  4352  								Type: DiffTypeAdded,
  4353  								Name: "Operand",
  4354  								Old:  "",
  4355  								New:  "baz",
  4356  							},
  4357  							{
  4358  								Type: DiffTypeAdded,
  4359  								Name: "RTarget",
  4360  								Old:  "",
  4361  								New:  "baz",
  4362  							},
  4363  						},
  4364  					},
  4365  					{
  4366  						Type: DiffTypeDeleted,
  4367  						Name: "Constraint",
  4368  						Fields: []*FieldDiff{
  4369  							{
  4370  								Type: DiffTypeDeleted,
  4371  								Name: "LTarget",
  4372  								Old:  "bar",
  4373  								New:  "",
  4374  							},
  4375  							{
  4376  								Type: DiffTypeDeleted,
  4377  								Name: "Operand",
  4378  								Old:  "bar",
  4379  								New:  "",
  4380  							},
  4381  							{
  4382  								Type: DiffTypeDeleted,
  4383  								Name: "RTarget",
  4384  								Old:  "bar",
  4385  								New:  "",
  4386  							},
  4387  						},
  4388  					},
  4389  				},
  4390  			},
  4391  		},
  4392  		{
  4393  			Name: "Affinities edited",
  4394  			Old: &Task{
  4395  				Affinities: []*Affinity{
  4396  					{
  4397  						LTarget: "foo",
  4398  						RTarget: "foo",
  4399  						Operand: "foo",
  4400  						Weight:  20,
  4401  					},
  4402  					{
  4403  						LTarget: "bar",
  4404  						RTarget: "bar",
  4405  						Operand: "bar",
  4406  						Weight:  20,
  4407  					},
  4408  				},
  4409  			},
  4410  			New: &Task{
  4411  				Affinities: []*Affinity{
  4412  					{
  4413  						LTarget: "foo",
  4414  						RTarget: "foo",
  4415  						Operand: "foo",
  4416  						Weight:  20,
  4417  					},
  4418  					{
  4419  						LTarget: "baz",
  4420  						RTarget: "baz",
  4421  						Operand: "baz",
  4422  						Weight:  20,
  4423  					},
  4424  				},
  4425  			},
  4426  			Expected: &TaskDiff{
  4427  				Type: DiffTypeEdited,
  4428  				Objects: []*ObjectDiff{
  4429  					{
  4430  						Type: DiffTypeAdded,
  4431  						Name: "Affinity",
  4432  						Fields: []*FieldDiff{
  4433  							{
  4434  								Type: DiffTypeAdded,
  4435  								Name: "LTarget",
  4436  								Old:  "",
  4437  								New:  "baz",
  4438  							},
  4439  							{
  4440  								Type: DiffTypeAdded,
  4441  								Name: "Operand",
  4442  								Old:  "",
  4443  								New:  "baz",
  4444  							},
  4445  							{
  4446  								Type: DiffTypeAdded,
  4447  								Name: "RTarget",
  4448  								Old:  "",
  4449  								New:  "baz",
  4450  							},
  4451  							{
  4452  								Type: DiffTypeAdded,
  4453  								Name: "Weight",
  4454  								Old:  "",
  4455  								New:  "20",
  4456  							},
  4457  						},
  4458  					},
  4459  					{
  4460  						Type: DiffTypeDeleted,
  4461  						Name: "Affinity",
  4462  						Fields: []*FieldDiff{
  4463  							{
  4464  								Type: DiffTypeDeleted,
  4465  								Name: "LTarget",
  4466  								Old:  "bar",
  4467  								New:  "",
  4468  							},
  4469  							{
  4470  								Type: DiffTypeDeleted,
  4471  								Name: "Operand",
  4472  								Old:  "bar",
  4473  								New:  "",
  4474  							},
  4475  							{
  4476  								Type: DiffTypeDeleted,
  4477  								Name: "RTarget",
  4478  								Old:  "bar",
  4479  								New:  "",
  4480  							},
  4481  							{
  4482  								Type: DiffTypeDeleted,
  4483  								Name: "Weight",
  4484  								Old:  "20",
  4485  								New:  "",
  4486  							},
  4487  						},
  4488  					},
  4489  				},
  4490  			},
  4491  		},
  4492  		{
  4493  			Name: "LogConfig added",
  4494  			Old:  &Task{},
  4495  			New: &Task{
  4496  				LogConfig: &LogConfig{
  4497  					MaxFiles:      1,
  4498  					MaxFileSizeMB: 10,
  4499  					Disabled:      true,
  4500  				},
  4501  			},
  4502  			Expected: &TaskDiff{
  4503  				Type: DiffTypeEdited,
  4504  				Objects: []*ObjectDiff{
  4505  					{
  4506  						Type: DiffTypeAdded,
  4507  						Name: "LogConfig",
  4508  						Fields: []*FieldDiff{
  4509  							{
  4510  								Type: DiffTypeAdded,
  4511  								Name: "Disabled",
  4512  								Old:  "",
  4513  								New:  "true",
  4514  							},
  4515  							{
  4516  								Type: DiffTypeAdded,
  4517  								Name: "MaxFileSizeMB",
  4518  								Old:  "",
  4519  								New:  "10",
  4520  							},
  4521  							{
  4522  								Type: DiffTypeAdded,
  4523  								Name: "MaxFiles",
  4524  								Old:  "",
  4525  								New:  "1",
  4526  							},
  4527  						},
  4528  					},
  4529  				},
  4530  			},
  4531  		},
  4532  		{
  4533  			Name: "LogConfig deleted",
  4534  			Old: &Task{
  4535  				LogConfig: &LogConfig{
  4536  					MaxFiles:      1,
  4537  					MaxFileSizeMB: 10,
  4538  					Disabled:      true,
  4539  				},
  4540  			},
  4541  			New: &Task{},
  4542  			Expected: &TaskDiff{
  4543  				Type: DiffTypeEdited,
  4544  				Objects: []*ObjectDiff{
  4545  					{
  4546  						Type: DiffTypeDeleted,
  4547  						Name: "LogConfig",
  4548  						Fields: []*FieldDiff{
  4549  							{
  4550  								Type: DiffTypeDeleted,
  4551  								Name: "Disabled",
  4552  								Old:  "true",
  4553  								New:  "",
  4554  							},
  4555  							{
  4556  								Type: DiffTypeDeleted,
  4557  								Name: "MaxFileSizeMB",
  4558  								Old:  "10",
  4559  								New:  "",
  4560  							},
  4561  							{
  4562  								Type: DiffTypeDeleted,
  4563  								Name: "MaxFiles",
  4564  								Old:  "1",
  4565  								New:  "",
  4566  							},
  4567  						},
  4568  					},
  4569  				},
  4570  			},
  4571  		},
  4572  		{
  4573  			Name: "LogConfig edited",
  4574  			Old: &Task{
  4575  				LogConfig: &LogConfig{
  4576  					MaxFiles:      1,
  4577  					MaxFileSizeMB: 10,
  4578  					Disabled:      false,
  4579  				},
  4580  			},
  4581  			New: &Task{
  4582  				LogConfig: &LogConfig{
  4583  					MaxFiles:      2,
  4584  					MaxFileSizeMB: 20,
  4585  					Disabled:      true,
  4586  				},
  4587  			},
  4588  			Expected: &TaskDiff{
  4589  				Type: DiffTypeEdited,
  4590  				Objects: []*ObjectDiff{
  4591  					{
  4592  						Type: DiffTypeEdited,
  4593  						Name: "LogConfig",
  4594  						Fields: []*FieldDiff{
  4595  							{
  4596  								Type: DiffTypeEdited,
  4597  								Name: "Disabled",
  4598  								Old:  "false",
  4599  								New:  "true",
  4600  							},
  4601  							{
  4602  								Type: DiffTypeEdited,
  4603  								Name: "MaxFileSizeMB",
  4604  								Old:  "10",
  4605  								New:  "20",
  4606  							},
  4607  							{
  4608  								Type: DiffTypeEdited,
  4609  								Name: "MaxFiles",
  4610  								Old:  "1",
  4611  								New:  "2",
  4612  							},
  4613  						},
  4614  					},
  4615  				},
  4616  			},
  4617  		},
  4618  		{
  4619  			Name:       "LogConfig edited with context",
  4620  			Contextual: true,
  4621  			Old: &Task{
  4622  				LogConfig: &LogConfig{
  4623  					MaxFiles:      1,
  4624  					MaxFileSizeMB: 10,
  4625  					Disabled:      false,
  4626  				},
  4627  			},
  4628  			New: &Task{
  4629  				LogConfig: &LogConfig{
  4630  					MaxFiles:      1,
  4631  					MaxFileSizeMB: 20,
  4632  					Disabled:      true,
  4633  				},
  4634  			},
  4635  			Expected: &TaskDiff{
  4636  				Type: DiffTypeEdited,
  4637  				Objects: []*ObjectDiff{
  4638  					{
  4639  						Type: DiffTypeEdited,
  4640  						Name: "LogConfig",
  4641  						Fields: []*FieldDiff{
  4642  							{
  4643  								Type: DiffTypeEdited,
  4644  								Name: "Disabled",
  4645  								Old:  "false",
  4646  								New:  "true",
  4647  							},
  4648  							{
  4649  								Type: DiffTypeEdited,
  4650  								Name: "MaxFileSizeMB",
  4651  								Old:  "10",
  4652  								New:  "20",
  4653  							},
  4654  							{
  4655  								Type: DiffTypeNone,
  4656  								Name: "MaxFiles",
  4657  								Old:  "1",
  4658  								New:  "1",
  4659  							},
  4660  						},
  4661  					},
  4662  				},
  4663  			},
  4664  		},
  4665  		{
  4666  			Name: "Artifacts edited",
  4667  			Old: &Task{
  4668  				Artifacts: []*TaskArtifact{
  4669  					{
  4670  						GetterSource: "foo",
  4671  						GetterOptions: map[string]string{
  4672  							"foo": "bar",
  4673  						},
  4674  						RelativeDest: "foo",
  4675  					},
  4676  					{
  4677  						GetterSource: "bar",
  4678  						GetterOptions: map[string]string{
  4679  							"bar": "baz",
  4680  						},
  4681  						GetterHeaders: map[string]string{
  4682  							"User": "user1",
  4683  						},
  4684  						GetterMode:   "dir",
  4685  						RelativeDest: "bar",
  4686  					},
  4687  				},
  4688  			},
  4689  			New: &Task{
  4690  				Artifacts: []*TaskArtifact{
  4691  					{
  4692  						GetterSource: "foo/bar",
  4693  						GetterOptions: map[string]string{
  4694  							"foo": "bar",
  4695  						},
  4696  						RelativeDest: "foo",
  4697  					},
  4698  					{
  4699  						GetterSource: "bam",
  4700  						GetterOptions: map[string]string{
  4701  							"bam": "baz",
  4702  						},
  4703  						GetterHeaders: map[string]string{
  4704  							"User":       "user2",
  4705  							"User-Agent": "nomad",
  4706  						},
  4707  						GetterMode:   "file",
  4708  						RelativeDest: "bam",
  4709  					},
  4710  				},
  4711  			},
  4712  			Expected: &TaskDiff{
  4713  				Type: DiffTypeEdited,
  4714  				Objects: []*ObjectDiff{
  4715  					{
  4716  						Type: DiffTypeEdited,
  4717  						Name: "Artifact",
  4718  						Fields: []*FieldDiff{
  4719  							{
  4720  								Type: DiffTypeEdited,
  4721  								Name: "GetterSource",
  4722  								Old:  "foo",
  4723  								New:  "foo/bar",
  4724  							},
  4725  						},
  4726  					},
  4727  					{
  4728  						Type: DiffTypeAdded,
  4729  						Name: "Artifact",
  4730  						Fields: []*FieldDiff{
  4731  							{
  4732  								Type: DiffTypeAdded,
  4733  								Name: "GetterHeaders[User-Agent]",
  4734  								Old:  "",
  4735  								New:  "nomad",
  4736  							},
  4737  							{
  4738  								Type: DiffTypeAdded,
  4739  								Name: "GetterHeaders[User]",
  4740  								Old:  "",
  4741  								New:  "user2",
  4742  							},
  4743  							{
  4744  								Type: DiffTypeAdded,
  4745  								Name: "GetterMode",
  4746  								Old:  "",
  4747  								New:  "file",
  4748  							},
  4749  							{
  4750  								Type: DiffTypeAdded,
  4751  								Name: "GetterOptions[bam]",
  4752  								Old:  "",
  4753  								New:  "baz",
  4754  							},
  4755  							{
  4756  								Type: DiffTypeAdded,
  4757  								Name: "GetterSource",
  4758  								Old:  "",
  4759  								New:  "bam",
  4760  							},
  4761  							{
  4762  								Type: DiffTypeAdded,
  4763  								Name: "RelativeDest",
  4764  								Old:  "",
  4765  								New:  "bam",
  4766  							},
  4767  						},
  4768  					},
  4769  					{
  4770  						Type: DiffTypeDeleted,
  4771  						Name: "Artifact",
  4772  						Fields: []*FieldDiff{
  4773  							{
  4774  								Type: DiffTypeDeleted,
  4775  								Name: "GetterHeaders[User]",
  4776  								Old:  "user1",
  4777  								New:  "",
  4778  							},
  4779  							{
  4780  								Type: DiffTypeDeleted,
  4781  								Name: "GetterMode",
  4782  								Old:  "dir",
  4783  								New:  "",
  4784  							},
  4785  							{
  4786  								Type: DiffTypeDeleted,
  4787  								Name: "GetterOptions[bar]",
  4788  								Old:  "baz",
  4789  								New:  "",
  4790  							},
  4791  							{
  4792  								Type: DiffTypeDeleted,
  4793  								Name: "GetterSource",
  4794  								Old:  "bar",
  4795  								New:  "",
  4796  							},
  4797  							{
  4798  								Type: DiffTypeDeleted,
  4799  								Name: "RelativeDest",
  4800  								Old:  "bar",
  4801  								New:  "",
  4802  							},
  4803  						},
  4804  					},
  4805  				},
  4806  			},
  4807  		},
  4808  		{
  4809  			Name: "Resources edited (no networks)",
  4810  			Old: &Task{
  4811  				Resources: &Resources{
  4812  					CPU:      100,
  4813  					MemoryMB: 100,
  4814  					DiskMB:   100,
  4815  				},
  4816  			},
  4817  			New: &Task{
  4818  				Resources: &Resources{
  4819  					CPU:      200,
  4820  					MemoryMB: 200,
  4821  					DiskMB:   200,
  4822  				},
  4823  			},
  4824  			Expected: &TaskDiff{
  4825  				Type: DiffTypeEdited,
  4826  				Objects: []*ObjectDiff{
  4827  					{
  4828  						Type: DiffTypeEdited,
  4829  						Name: "Resources",
  4830  						Fields: []*FieldDiff{
  4831  							{
  4832  								Type: DiffTypeEdited,
  4833  								Name: "CPU",
  4834  								Old:  "100",
  4835  								New:  "200",
  4836  							},
  4837  							{
  4838  								Type: DiffTypeEdited,
  4839  								Name: "DiskMB",
  4840  								Old:  "100",
  4841  								New:  "200",
  4842  							},
  4843  							{
  4844  								Type: DiffTypeEdited,
  4845  								Name: "MemoryMB",
  4846  								Old:  "100",
  4847  								New:  "200",
  4848  							},
  4849  						},
  4850  					},
  4851  				},
  4852  			},
  4853  		},
  4854  		{
  4855  			Name:       "Resources edited (no networks) with context",
  4856  			Contextual: true,
  4857  			Old: &Task{
  4858  				Resources: &Resources{
  4859  					CPU:      100,
  4860  					MemoryMB: 100,
  4861  					DiskMB:   100,
  4862  				},
  4863  			},
  4864  			New: &Task{
  4865  				Resources: &Resources{
  4866  					CPU:      200,
  4867  					MemoryMB: 100,
  4868  					DiskMB:   200,
  4869  				},
  4870  			},
  4871  			Expected: &TaskDiff{
  4872  				Type: DiffTypeEdited,
  4873  				Objects: []*ObjectDiff{
  4874  					{
  4875  						Type: DiffTypeEdited,
  4876  						Name: "Resources",
  4877  						Fields: []*FieldDiff{
  4878  							{
  4879  								Type: DiffTypeEdited,
  4880  								Name: "CPU",
  4881  								Old:  "100",
  4882  								New:  "200",
  4883  							},
  4884  							{
  4885  								Type: DiffTypeNone,
  4886  								Name: "Cores",
  4887  								Old:  "0",
  4888  								New:  "0",
  4889  							},
  4890  							{
  4891  								Type: DiffTypeEdited,
  4892  								Name: "DiskMB",
  4893  								Old:  "100",
  4894  								New:  "200",
  4895  							},
  4896  							{
  4897  								Type: DiffTypeNone,
  4898  								Name: "IOPS",
  4899  								Old:  "0",
  4900  								New:  "0",
  4901  							},
  4902  							{
  4903  								Type: DiffTypeNone,
  4904  								Name: "MemoryMB",
  4905  								Old:  "100",
  4906  								New:  "100",
  4907  							},
  4908  							{
  4909  								Type: DiffTypeNone,
  4910  								Name: "MemoryMaxMB",
  4911  								Old:  "0",
  4912  								New:  "0",
  4913  							},
  4914  						},
  4915  					},
  4916  				},
  4917  			},
  4918  		},
  4919  		{
  4920  			Name: "Resources edited memory_max",
  4921  			Old: &Task{
  4922  				Resources: &Resources{
  4923  					CPU:         100,
  4924  					MemoryMB:    100,
  4925  					MemoryMaxMB: 200,
  4926  					DiskMB:      100,
  4927  				},
  4928  			},
  4929  			New: &Task{
  4930  				Resources: &Resources{
  4931  					CPU:         100,
  4932  					MemoryMB:    100,
  4933  					MemoryMaxMB: 300,
  4934  					DiskMB:      100,
  4935  				},
  4936  			},
  4937  			Expected: &TaskDiff{
  4938  				Type: DiffTypeEdited,
  4939  				Objects: []*ObjectDiff{
  4940  					{
  4941  						Type: DiffTypeEdited,
  4942  						Name: "Resources",
  4943  						Fields: []*FieldDiff{
  4944  							{
  4945  								Type: DiffTypeEdited,
  4946  								Name: "MemoryMaxMB",
  4947  								Old:  "200",
  4948  								New:  "300",
  4949  							},
  4950  						},
  4951  					},
  4952  				},
  4953  			},
  4954  		},
  4955  		{
  4956  			Name:       "Resources edited memory_max with context",
  4957  			Contextual: true,
  4958  			Old: &Task{
  4959  				Resources: &Resources{
  4960  					CPU:         100,
  4961  					MemoryMB:    100,
  4962  					MemoryMaxMB: 200,
  4963  					DiskMB:      100,
  4964  				},
  4965  			},
  4966  			New: &Task{
  4967  				Resources: &Resources{
  4968  					CPU:         100,
  4969  					MemoryMB:    100,
  4970  					MemoryMaxMB: 300,
  4971  					DiskMB:      100,
  4972  				},
  4973  			},
  4974  			Expected: &TaskDiff{
  4975  				Type: DiffTypeEdited,
  4976  				Objects: []*ObjectDiff{
  4977  					{
  4978  						Type: DiffTypeEdited,
  4979  						Name: "Resources",
  4980  						Fields: []*FieldDiff{
  4981  							{
  4982  								Type: DiffTypeNone,
  4983  								Name: "CPU",
  4984  								Old:  "100",
  4985  								New:  "100",
  4986  							},
  4987  							{
  4988  								Type: DiffTypeNone,
  4989  								Name: "Cores",
  4990  								Old:  "0",
  4991  								New:  "0",
  4992  							},
  4993  							{
  4994  								Type: DiffTypeNone,
  4995  								Name: "DiskMB",
  4996  								Old:  "100",
  4997  								New:  "100",
  4998  							},
  4999  							{
  5000  								Type: DiffTypeNone,
  5001  								Name: "IOPS",
  5002  								Old:  "0",
  5003  								New:  "0",
  5004  							},
  5005  							{
  5006  								Type: DiffTypeNone,
  5007  								Name: "MemoryMB",
  5008  								Old:  "100",
  5009  								New:  "100",
  5010  							},
  5011  							{
  5012  								Type: DiffTypeEdited,
  5013  								Name: "MemoryMaxMB",
  5014  								Old:  "200",
  5015  								New:  "300",
  5016  							},
  5017  						},
  5018  					},
  5019  				},
  5020  			},
  5021  		},
  5022  		{
  5023  			Name: "Network Resources edited",
  5024  			Old: &Task{
  5025  				Resources: &Resources{
  5026  					Networks: []*NetworkResource{
  5027  						{
  5028  							Device: "foo",
  5029  							CIDR:   "foo",
  5030  							IP:     "foo",
  5031  							MBits:  100,
  5032  							ReservedPorts: []Port{
  5033  								{
  5034  									Label: "foo",
  5035  									Value: 80,
  5036  								},
  5037  							},
  5038  							DynamicPorts: []Port{
  5039  								{
  5040  									Label: "bar",
  5041  									To:    8080,
  5042  								},
  5043  							},
  5044  						},
  5045  					},
  5046  				},
  5047  			},
  5048  			New: &Task{
  5049  				Resources: &Resources{
  5050  					Networks: []*NetworkResource{
  5051  						{
  5052  							Device: "bar",
  5053  							CIDR:   "bar",
  5054  							IP:     "bar",
  5055  							MBits:  200,
  5056  							ReservedPorts: []Port{
  5057  								{
  5058  									Label: "foo",
  5059  									Value: 81,
  5060  								},
  5061  							},
  5062  							DynamicPorts: []Port{
  5063  								{
  5064  									Label: "baz",
  5065  									To:    8081,
  5066  								},
  5067  							},
  5068  						},
  5069  					},
  5070  				},
  5071  			},
  5072  			Expected: &TaskDiff{
  5073  				Type: DiffTypeEdited,
  5074  				Objects: []*ObjectDiff{
  5075  					{
  5076  						Type: DiffTypeEdited,
  5077  						Name: "Resources",
  5078  						Objects: []*ObjectDiff{
  5079  							{
  5080  								Type: DiffTypeAdded,
  5081  								Name: "Network",
  5082  								Fields: []*FieldDiff{
  5083  									{
  5084  										Type: DiffTypeAdded,
  5085  										Name: "MBits",
  5086  										Old:  "",
  5087  										New:  "200",
  5088  									},
  5089  								},
  5090  								Objects: []*ObjectDiff{
  5091  									{
  5092  										Type: DiffTypeAdded,
  5093  										Name: "Static Port",
  5094  										Fields: []*FieldDiff{
  5095  											{
  5096  												Type: DiffTypeAdded,
  5097  												Name: "Label",
  5098  												Old:  "",
  5099  												New:  "foo",
  5100  											},
  5101  											{
  5102  												Type: DiffTypeAdded,
  5103  												Name: "To",
  5104  												Old:  "",
  5105  												New:  "0",
  5106  											},
  5107  											{
  5108  												Type: DiffTypeAdded,
  5109  												Name: "Value",
  5110  												Old:  "",
  5111  												New:  "81",
  5112  											},
  5113  										},
  5114  									},
  5115  									{
  5116  										Type: DiffTypeAdded,
  5117  										Name: "Dynamic Port",
  5118  										Fields: []*FieldDiff{
  5119  											{
  5120  												Type: DiffTypeAdded,
  5121  												Name: "Label",
  5122  												Old:  "",
  5123  												New:  "baz",
  5124  											},
  5125  											{
  5126  												Type: DiffTypeAdded,
  5127  												Name: "To",
  5128  												Old:  "",
  5129  												New:  "8081",
  5130  											},
  5131  										},
  5132  									},
  5133  								},
  5134  							},
  5135  							{
  5136  								Type: DiffTypeDeleted,
  5137  								Name: "Network",
  5138  								Fields: []*FieldDiff{
  5139  									{
  5140  										Type: DiffTypeDeleted,
  5141  										Name: "MBits",
  5142  										Old:  "100",
  5143  										New:  "",
  5144  									},
  5145  								},
  5146  								Objects: []*ObjectDiff{
  5147  									{
  5148  										Type: DiffTypeDeleted,
  5149  										Name: "Static Port",
  5150  										Fields: []*FieldDiff{
  5151  											{
  5152  												Type: DiffTypeDeleted,
  5153  												Name: "Label",
  5154  												Old:  "foo",
  5155  												New:  "",
  5156  											},
  5157  											{
  5158  												Type: DiffTypeDeleted,
  5159  												Name: "To",
  5160  												Old:  "0",
  5161  												New:  "",
  5162  											},
  5163  											{
  5164  												Type: DiffTypeDeleted,
  5165  												Name: "Value",
  5166  												Old:  "80",
  5167  												New:  "",
  5168  											},
  5169  										},
  5170  									},
  5171  									{
  5172  										Type: DiffTypeDeleted,
  5173  										Name: "Dynamic Port",
  5174  										Fields: []*FieldDiff{
  5175  											{
  5176  												Type: DiffTypeDeleted,
  5177  												Name: "Label",
  5178  												Old:  "bar",
  5179  												New:  "",
  5180  											},
  5181  											{
  5182  												Type: DiffTypeDeleted,
  5183  												Name: "To",
  5184  												Old:  "8080",
  5185  												New:  "",
  5186  											},
  5187  										},
  5188  									},
  5189  								},
  5190  							},
  5191  						},
  5192  					},
  5193  				},
  5194  			},
  5195  		},
  5196  		{
  5197  			Name: "Device Resources edited",
  5198  			Old: &Task{
  5199  				Resources: &Resources{
  5200  					Devices: []*RequestedDevice{
  5201  						{
  5202  							Name:  "foo",
  5203  							Count: 2,
  5204  						},
  5205  						{
  5206  							Name:  "bar",
  5207  							Count: 2,
  5208  						},
  5209  						{
  5210  							Name:  "baz",
  5211  							Count: 2,
  5212  						},
  5213  					},
  5214  				},
  5215  			},
  5216  			New: &Task{
  5217  				Resources: &Resources{
  5218  					Devices: []*RequestedDevice{
  5219  						{
  5220  							Name:  "foo",
  5221  							Count: 2,
  5222  						},
  5223  						{
  5224  							Name:  "bar",
  5225  							Count: 3,
  5226  						},
  5227  						{
  5228  							Name:  "bam",
  5229  							Count: 2,
  5230  						},
  5231  					},
  5232  				},
  5233  			},
  5234  			Expected: &TaskDiff{
  5235  				Type: DiffTypeEdited,
  5236  				Objects: []*ObjectDiff{
  5237  					{
  5238  						Type: DiffTypeEdited,
  5239  						Name: "Resources",
  5240  						Objects: []*ObjectDiff{
  5241  							{
  5242  								Type: DiffTypeEdited,
  5243  								Name: "Device",
  5244  								Fields: []*FieldDiff{
  5245  									{
  5246  										Type: DiffTypeEdited,
  5247  										Name: "Count",
  5248  										Old:  "2",
  5249  										New:  "3",
  5250  									},
  5251  								},
  5252  							},
  5253  							{
  5254  								Type: DiffTypeAdded,
  5255  								Name: "Device",
  5256  								Fields: []*FieldDiff{
  5257  									{
  5258  										Type: DiffTypeAdded,
  5259  										Name: "Count",
  5260  										Old:  "",
  5261  										New:  "2",
  5262  									},
  5263  									{
  5264  										Type: DiffTypeAdded,
  5265  										Name: "Name",
  5266  										Old:  "",
  5267  										New:  "bam",
  5268  									},
  5269  								},
  5270  							},
  5271  							{
  5272  								Type: DiffTypeDeleted,
  5273  								Name: "Device",
  5274  								Fields: []*FieldDiff{
  5275  									{
  5276  										Type: DiffTypeDeleted,
  5277  										Name: "Count",
  5278  										Old:  "2",
  5279  										New:  "",
  5280  									},
  5281  									{
  5282  										Type: DiffTypeDeleted,
  5283  										Name: "Name",
  5284  										Old:  "baz",
  5285  										New:  "",
  5286  									},
  5287  								},
  5288  							},
  5289  						},
  5290  					},
  5291  				},
  5292  			},
  5293  		},
  5294  		{
  5295  			Name:       "Device Resources edited with context",
  5296  			Contextual: true,
  5297  			Old: &Task{
  5298  				Resources: &Resources{
  5299  					CPU:      100,
  5300  					MemoryMB: 100,
  5301  					DiskMB:   100,
  5302  					Devices: []*RequestedDevice{
  5303  						{
  5304  							Name:  "foo",
  5305  							Count: 2,
  5306  						},
  5307  						{
  5308  							Name:  "bar",
  5309  							Count: 2,
  5310  						},
  5311  						{
  5312  							Name:  "baz",
  5313  							Count: 2,
  5314  						},
  5315  					},
  5316  				},
  5317  			},
  5318  			New: &Task{
  5319  				Resources: &Resources{
  5320  					CPU:      100,
  5321  					MemoryMB: 100,
  5322  					DiskMB:   100,
  5323  					Devices: []*RequestedDevice{
  5324  						{
  5325  							Name:  "foo",
  5326  							Count: 2,
  5327  						},
  5328  						{
  5329  							Name:  "bar",
  5330  							Count: 3,
  5331  						},
  5332  						{
  5333  							Name:  "bam",
  5334  							Count: 2,
  5335  						},
  5336  					},
  5337  				},
  5338  			},
  5339  			Expected: &TaskDiff{
  5340  				Type: DiffTypeEdited,
  5341  				Objects: []*ObjectDiff{
  5342  					{
  5343  						Type: DiffTypeEdited,
  5344  						Name: "Resources",
  5345  						Fields: []*FieldDiff{
  5346  							{
  5347  								Type: DiffTypeNone,
  5348  								Name: "CPU",
  5349  								Old:  "100",
  5350  								New:  "100",
  5351  							},
  5352  							{
  5353  								Type: DiffTypeNone,
  5354  								Name: "Cores",
  5355  								Old:  "0",
  5356  								New:  "0",
  5357  							},
  5358  							{
  5359  								Type: DiffTypeNone,
  5360  								Name: "DiskMB",
  5361  								Old:  "100",
  5362  								New:  "100",
  5363  							},
  5364  							{
  5365  								Type: DiffTypeNone,
  5366  								Name: "IOPS",
  5367  								Old:  "0",
  5368  								New:  "0",
  5369  							},
  5370  							{
  5371  								Type: DiffTypeNone,
  5372  								Name: "MemoryMB",
  5373  								Old:  "100",
  5374  								New:  "100",
  5375  							},
  5376  							{
  5377  								Type: DiffTypeNone,
  5378  								Name: "MemoryMaxMB",
  5379  								Old:  "0",
  5380  								New:  "0",
  5381  							},
  5382  						},
  5383  						Objects: []*ObjectDiff{
  5384  							{
  5385  								Type: DiffTypeEdited,
  5386  								Name: "Device",
  5387  								Fields: []*FieldDiff{
  5388  									{
  5389  										Type: DiffTypeEdited,
  5390  										Name: "Count",
  5391  										Old:  "2",
  5392  										New:  "3",
  5393  									},
  5394  									{
  5395  										Type: DiffTypeNone,
  5396  										Name: "Name",
  5397  										Old:  "bar",
  5398  										New:  "bar",
  5399  									},
  5400  								},
  5401  							},
  5402  							{
  5403  								Type: DiffTypeAdded,
  5404  								Name: "Device",
  5405  								Fields: []*FieldDiff{
  5406  									{
  5407  										Type: DiffTypeAdded,
  5408  										Name: "Count",
  5409  										Old:  "",
  5410  										New:  "2",
  5411  									},
  5412  									{
  5413  										Type: DiffTypeAdded,
  5414  										Name: "Name",
  5415  										Old:  "",
  5416  										New:  "bam",
  5417  									},
  5418  								},
  5419  							},
  5420  							{
  5421  								Type: DiffTypeDeleted,
  5422  								Name: "Device",
  5423  								Fields: []*FieldDiff{
  5424  									{
  5425  										Type: DiffTypeDeleted,
  5426  										Name: "Count",
  5427  										Old:  "2",
  5428  										New:  "",
  5429  									},
  5430  									{
  5431  										Type: DiffTypeDeleted,
  5432  										Name: "Name",
  5433  										Old:  "baz",
  5434  										New:  "",
  5435  									},
  5436  								},
  5437  							},
  5438  						},
  5439  					},
  5440  				},
  5441  			},
  5442  		},
  5443  		{
  5444  			Name: "Config same",
  5445  			Old: &Task{
  5446  				Config: map[string]interface{}{
  5447  					"foo": 1,
  5448  					"bar": "bar",
  5449  					"bam": []string{"a", "b"},
  5450  					"baz": map[string]int{
  5451  						"a": 1,
  5452  						"b": 2,
  5453  					},
  5454  					"boom": &Port{
  5455  						Label: "boom_port",
  5456  					},
  5457  				},
  5458  			},
  5459  			New: &Task{
  5460  				Config: map[string]interface{}{
  5461  					"foo": 1,
  5462  					"bar": "bar",
  5463  					"bam": []string{"a", "b"},
  5464  					"baz": map[string]int{
  5465  						"a": 1,
  5466  						"b": 2,
  5467  					},
  5468  					"boom": &Port{
  5469  						Label: "boom_port",
  5470  					},
  5471  				},
  5472  			},
  5473  			Expected: &TaskDiff{
  5474  				Type: DiffTypeNone,
  5475  			},
  5476  		},
  5477  		{
  5478  			Name: "Config edited",
  5479  			Old: &Task{
  5480  				Config: map[string]interface{}{
  5481  					"foo": 1,
  5482  					"bar": "baz",
  5483  					"bam": []string{"a", "b"},
  5484  					"baz": map[string]int{
  5485  						"a": 1,
  5486  						"b": 2,
  5487  					},
  5488  					"boom": &Port{
  5489  						Label: "boom_port",
  5490  					},
  5491  				},
  5492  			},
  5493  			New: &Task{
  5494  				Config: map[string]interface{}{
  5495  					"foo": 2,
  5496  					"bar": "baz",
  5497  					"bam": []string{"a", "c", "d"},
  5498  					"baz": map[string]int{
  5499  						"b": 3,
  5500  						"c": 4,
  5501  					},
  5502  					"boom": &Port{
  5503  						Label: "boom_port2",
  5504  					},
  5505  				},
  5506  			},
  5507  			Expected: &TaskDiff{
  5508  				Type: DiffTypeEdited,
  5509  				Objects: []*ObjectDiff{
  5510  					{
  5511  						Type: DiffTypeEdited,
  5512  						Name: "Config",
  5513  						Fields: []*FieldDiff{
  5514  							{
  5515  								Type: DiffTypeEdited,
  5516  								Name: "bam[1]",
  5517  								Old:  "b",
  5518  								New:  "c",
  5519  							},
  5520  							{
  5521  								Type: DiffTypeAdded,
  5522  								Name: "bam[2]",
  5523  								Old:  "",
  5524  								New:  "d",
  5525  							},
  5526  							{
  5527  								Type: DiffTypeDeleted,
  5528  								Name: "baz[a]",
  5529  								Old:  "1",
  5530  								New:  "",
  5531  							},
  5532  							{
  5533  								Type: DiffTypeEdited,
  5534  								Name: "baz[b]",
  5535  								Old:  "2",
  5536  								New:  "3",
  5537  							},
  5538  							{
  5539  								Type: DiffTypeAdded,
  5540  								Name: "baz[c]",
  5541  								Old:  "",
  5542  								New:  "4",
  5543  							},
  5544  							{
  5545  								Type: DiffTypeEdited,
  5546  								Name: "boom.Label",
  5547  								Old:  "boom_port",
  5548  								New:  "boom_port2",
  5549  							},
  5550  							{
  5551  								Type: DiffTypeEdited,
  5552  								Name: "foo",
  5553  								Old:  "1",
  5554  								New:  "2",
  5555  							},
  5556  						},
  5557  					},
  5558  				},
  5559  			},
  5560  		},
  5561  		{
  5562  			Name:       "Config edited with context",
  5563  			Contextual: true,
  5564  			Old: &Task{
  5565  				Config: map[string]interface{}{
  5566  					"foo": 1,
  5567  					"bar": "baz",
  5568  					"bam": []string{"a", "b"},
  5569  					"baz": map[string]int{
  5570  						"a": 1,
  5571  						"b": 2,
  5572  					},
  5573  					"boom": &Port{
  5574  						Label: "boom_port",
  5575  					},
  5576  				},
  5577  			},
  5578  			New: &Task{
  5579  				Config: map[string]interface{}{
  5580  					"foo": 2,
  5581  					"bar": "baz",
  5582  					"bam": []string{"a", "c", "d"},
  5583  					"baz": map[string]int{
  5584  						"a": 1,
  5585  						"b": 2,
  5586  					},
  5587  					"boom": &Port{
  5588  						Label: "boom_port",
  5589  					},
  5590  				},
  5591  			},
  5592  			Expected: &TaskDiff{
  5593  				Type: DiffTypeEdited,
  5594  				Objects: []*ObjectDiff{
  5595  					{
  5596  						Type: DiffTypeEdited,
  5597  						Name: "Config",
  5598  						Fields: []*FieldDiff{
  5599  							{
  5600  								Type: DiffTypeNone,
  5601  								Name: "bam[0]",
  5602  								Old:  "a",
  5603  								New:  "a",
  5604  							},
  5605  							{
  5606  								Type: DiffTypeEdited,
  5607  								Name: "bam[1]",
  5608  								Old:  "b",
  5609  								New:  "c",
  5610  							},
  5611  							{
  5612  								Type: DiffTypeAdded,
  5613  								Name: "bam[2]",
  5614  								Old:  "",
  5615  								New:  "d",
  5616  							},
  5617  							{
  5618  								Type: DiffTypeNone,
  5619  								Name: "bar",
  5620  								Old:  "baz",
  5621  								New:  "baz",
  5622  							},
  5623  							{
  5624  								Type: DiffTypeNone,
  5625  								Name: "baz[a]",
  5626  								Old:  "1",
  5627  								New:  "1",
  5628  							},
  5629  							{
  5630  								Type: DiffTypeNone,
  5631  								Name: "baz[b]",
  5632  								Old:  "2",
  5633  								New:  "2",
  5634  							},
  5635  							{
  5636  								Type: DiffTypeNone,
  5637  								Name: "boom.HostNetwork",
  5638  								Old:  "",
  5639  								New:  "",
  5640  							},
  5641  							{
  5642  								Type: DiffTypeNone,
  5643  								Name: "boom.Label",
  5644  								Old:  "boom_port",
  5645  								New:  "boom_port",
  5646  							},
  5647  							{
  5648  								Type: DiffTypeNone,
  5649  								Name: "boom.To",
  5650  								Old:  "0",
  5651  								New:  "0",
  5652  							},
  5653  							{
  5654  								Type: DiffTypeNone,
  5655  								Name: "boom.Value",
  5656  								Old:  "0",
  5657  								New:  "0",
  5658  							},
  5659  							{
  5660  								Type: DiffTypeEdited,
  5661  								Name: "foo",
  5662  								Old:  "1",
  5663  								New:  "2",
  5664  							},
  5665  						},
  5666  					},
  5667  				},
  5668  			},
  5669  		},
  5670  		{
  5671  			Name: "Services edited (no checks)",
  5672  			Old: &Task{
  5673  				Services: []*Service{
  5674  					{
  5675  						Name:      "foo",
  5676  						PortLabel: "foo",
  5677  					},
  5678  					{
  5679  						Name:      "bar",
  5680  						PortLabel: "bar",
  5681  					},
  5682  					{
  5683  						Name:      "baz",
  5684  						PortLabel: "baz",
  5685  					},
  5686  				},
  5687  			},
  5688  			New: &Task{
  5689  				Services: []*Service{
  5690  					{
  5691  						Name:      "bar",
  5692  						PortLabel: "bar",
  5693  					},
  5694  					{
  5695  						Name:      "baz",
  5696  						PortLabel: "baz2",
  5697  					},
  5698  					{
  5699  						Name:      "bam",
  5700  						PortLabel: "bam",
  5701  					},
  5702  				},
  5703  			},
  5704  			Expected: &TaskDiff{
  5705  				Type: DiffTypeEdited,
  5706  				Objects: []*ObjectDiff{
  5707  					{
  5708  						Type: DiffTypeEdited,
  5709  						Name: "Service",
  5710  						Fields: []*FieldDiff{
  5711  							{
  5712  								Type: DiffTypeEdited,
  5713  								Name: "PortLabel",
  5714  								Old:  "baz",
  5715  								New:  "baz2",
  5716  							},
  5717  						},
  5718  					},
  5719  					{
  5720  						Type: DiffTypeAdded,
  5721  						Name: "Service",
  5722  						Fields: []*FieldDiff{
  5723  							{
  5724  								Type: DiffTypeAdded,
  5725  								Name: "EnableTagOverride",
  5726  								Old:  "",
  5727  								New:  "false",
  5728  							},
  5729  							{
  5730  								Type: DiffTypeAdded,
  5731  								Name: "Name",
  5732  								Old:  "",
  5733  								New:  "bam",
  5734  							},
  5735  							{
  5736  								Type: DiffTypeAdded,
  5737  								Name: "PortLabel",
  5738  								Old:  "",
  5739  								New:  "bam",
  5740  							},
  5741  						},
  5742  					},
  5743  					{
  5744  						Type: DiffTypeDeleted,
  5745  						Name: "Service",
  5746  						Fields: []*FieldDiff{
  5747  							{
  5748  								Type: DiffTypeDeleted,
  5749  								Name: "EnableTagOverride",
  5750  								Old:  "false",
  5751  								New:  "",
  5752  							},
  5753  							{
  5754  								Type: DiffTypeDeleted,
  5755  								Name: "Name",
  5756  								Old:  "foo",
  5757  								New:  "",
  5758  							},
  5759  							{
  5760  								Type: DiffTypeDeleted,
  5761  								Name: "PortLabel",
  5762  								Old:  "foo",
  5763  								New:  "",
  5764  							},
  5765  						},
  5766  					},
  5767  				},
  5768  			},
  5769  		},
  5770  		{
  5771  			Name:       "Services edited (no checks) with context",
  5772  			Contextual: true,
  5773  			Old: &Task{
  5774  				Services: []*Service{
  5775  					{
  5776  						Name:      "foo",
  5777  						PortLabel: "foo",
  5778  					},
  5779  				},
  5780  			},
  5781  			New: &Task{
  5782  				Services: []*Service{
  5783  					{
  5784  						Name:        "foo",
  5785  						PortLabel:   "bar",
  5786  						AddressMode: "driver",
  5787  						Address:     "a.example.com",
  5788  						TaskName:    "task1",
  5789  					},
  5790  				},
  5791  			},
  5792  			Expected: &TaskDiff{
  5793  				Type: DiffTypeEdited,
  5794  				Objects: []*ObjectDiff{
  5795  					{
  5796  						Type: DiffTypeEdited,
  5797  						Name: "Service",
  5798  						Fields: []*FieldDiff{
  5799  							{
  5800  								Type: DiffTypeAdded,
  5801  								Name: "Address",
  5802  								Old:  "",
  5803  								New:  "a.example.com",
  5804  							},
  5805  							{
  5806  								Type: DiffTypeAdded,
  5807  								Name: "AddressMode",
  5808  								Old:  "",
  5809  								New:  "driver",
  5810  							},
  5811  							{
  5812  								Type: DiffTypeNone,
  5813  								Name: "EnableTagOverride",
  5814  								Old:  "false",
  5815  								New:  "false",
  5816  							},
  5817  							{
  5818  								Type: DiffTypeNone,
  5819  								Name: "Name",
  5820  								Old:  "foo",
  5821  								New:  "foo",
  5822  							},
  5823  							{
  5824  								Type: DiffTypeNone,
  5825  								Name: "Namespace",
  5826  								Old:  "",
  5827  								New:  "",
  5828  							},
  5829  							{
  5830  								Type: DiffTypeNone,
  5831  								Name: "OnUpdate",
  5832  								Old:  "",
  5833  								New:  "",
  5834  							},
  5835  							{
  5836  								Type: DiffTypeEdited,
  5837  								Name: "PortLabel",
  5838  								Old:  "foo",
  5839  								New:  "bar",
  5840  							},
  5841  							{
  5842  								Type: DiffTypeNone,
  5843  								Name: "Provider",
  5844  							},
  5845  							{
  5846  								Type: DiffTypeAdded,
  5847  								Name: "TaskName",
  5848  								Old:  "",
  5849  								New:  "task1",
  5850  							},
  5851  						},
  5852  					},
  5853  				},
  5854  			},
  5855  		},
  5856  		{
  5857  			Name:       "Service EnableTagOverride edited no context",
  5858  			Contextual: false,
  5859  			Old: &Task{
  5860  				Services: []*Service{{
  5861  					EnableTagOverride: false,
  5862  				}},
  5863  			},
  5864  			New: &Task{
  5865  				Services: []*Service{{
  5866  					EnableTagOverride: true,
  5867  				}},
  5868  			},
  5869  			Expected: &TaskDiff{
  5870  				Type: DiffTypeEdited,
  5871  				Objects: []*ObjectDiff{
  5872  					{
  5873  						Type: DiffTypeEdited,
  5874  						Name: "Service",
  5875  						Fields: []*FieldDiff{
  5876  							{
  5877  								Type: DiffTypeEdited,
  5878  								Name: "EnableTagOverride",
  5879  								Old:  "false",
  5880  								New:  "true",
  5881  							},
  5882  						},
  5883  					},
  5884  				},
  5885  			},
  5886  		},
  5887  		{
  5888  			Name:       "Services tags edited (no checks) with context",
  5889  			Contextual: true,
  5890  			Old: &Task{
  5891  				Services: []*Service{
  5892  					{
  5893  						Tags:       []string{"foo", "bar"},
  5894  						CanaryTags: []string{"foo", "bar"},
  5895  					},
  5896  				},
  5897  			},
  5898  			New: &Task{
  5899  				Services: []*Service{
  5900  					{
  5901  						Tags:       []string{"bar", "bam"},
  5902  						CanaryTags: []string{"bar", "bam"},
  5903  					},
  5904  				},
  5905  			},
  5906  			Expected: &TaskDiff{
  5907  				Type: DiffTypeEdited,
  5908  				Objects: []*ObjectDiff{
  5909  					{
  5910  						Type: DiffTypeEdited,
  5911  						Name: "Service",
  5912  						Objects: []*ObjectDiff{
  5913  							{
  5914  								Type: DiffTypeEdited,
  5915  								Name: "CanaryTags",
  5916  								Fields: []*FieldDiff{
  5917  									{
  5918  										Type: DiffTypeAdded,
  5919  										Name: "CanaryTags",
  5920  										Old:  "",
  5921  										New:  "bam",
  5922  									},
  5923  									{
  5924  										Type: DiffTypeNone,
  5925  										Name: "CanaryTags",
  5926  										Old:  "bar",
  5927  										New:  "bar",
  5928  									},
  5929  									{
  5930  										Type: DiffTypeDeleted,
  5931  										Name: "CanaryTags",
  5932  										Old:  "foo",
  5933  										New:  "",
  5934  									},
  5935  								},
  5936  							},
  5937  							{
  5938  								Type: DiffTypeEdited,
  5939  								Name: "Tags",
  5940  								Fields: []*FieldDiff{
  5941  									{
  5942  										Type: DiffTypeAdded,
  5943  										Name: "Tags",
  5944  										Old:  "",
  5945  										New:  "bam",
  5946  									},
  5947  									{
  5948  										Type: DiffTypeNone,
  5949  										Name: "Tags",
  5950  										Old:  "bar",
  5951  										New:  "bar",
  5952  									},
  5953  									{
  5954  										Type: DiffTypeDeleted,
  5955  										Name: "Tags",
  5956  										Old:  "foo",
  5957  										New:  "",
  5958  									},
  5959  								},
  5960  							},
  5961  						},
  5962  						Fields: []*FieldDiff{
  5963  							{
  5964  								Type: DiffTypeNone,
  5965  								Name: "Address",
  5966  							},
  5967  							{
  5968  								Type: DiffTypeNone,
  5969  								Name: "AddressMode",
  5970  							},
  5971  							{
  5972  								Type: DiffTypeNone,
  5973  								Name: "EnableTagOverride",
  5974  								Old:  "false",
  5975  								New:  "false",
  5976  							},
  5977  							{
  5978  								Type: DiffTypeNone,
  5979  								Name: "Name",
  5980  							},
  5981  							{
  5982  								Type: DiffTypeNone,
  5983  								Name: "Namespace",
  5984  							},
  5985  							{
  5986  								Type: DiffTypeNone,
  5987  								Name: "OnUpdate",
  5988  							},
  5989  							{
  5990  								Type: DiffTypeNone,
  5991  								Name: "PortLabel",
  5992  							},
  5993  							{
  5994  								Type: DiffTypeNone,
  5995  								Name: "Provider",
  5996  							},
  5997  							{
  5998  								Type: DiffTypeNone,
  5999  								Name: "TaskName",
  6000  							},
  6001  						},
  6002  					},
  6003  				},
  6004  			},
  6005  		},
  6006  
  6007  		{
  6008  			Name: "Service with Connect",
  6009  			Old: &Task{
  6010  				Services: []*Service{
  6011  					{
  6012  						Name: "foo",
  6013  					},
  6014  				},
  6015  			},
  6016  			New: &Task{
  6017  				Services: []*Service{
  6018  					{
  6019  						Name: "foo",
  6020  						Connect: &ConsulConnect{
  6021  							SidecarService: &ConsulSidecarService{},
  6022  						},
  6023  					},
  6024  				},
  6025  			},
  6026  			Expected: &TaskDiff{
  6027  				Type: DiffTypeEdited,
  6028  				Objects: []*ObjectDiff{
  6029  					{
  6030  						Type: DiffTypeEdited,
  6031  						Name: "Service",
  6032  						Objects: []*ObjectDiff{
  6033  							{
  6034  								Type: DiffTypeAdded,
  6035  								Name: "ConsulConnect",
  6036  								Fields: []*FieldDiff{
  6037  									{
  6038  										Type: DiffTypeAdded,
  6039  										Name: "Native",
  6040  										Old:  "",
  6041  										New:  "false",
  6042  									},
  6043  								},
  6044  								Objects: []*ObjectDiff{
  6045  									{
  6046  										Type: DiffTypeAdded,
  6047  										Name: "SidecarService",
  6048  										Fields: []*FieldDiff{
  6049  											{
  6050  												Type: DiffTypeAdded,
  6051  												Name: "DisableDefaultTCPCheck",
  6052  												Old:  "",
  6053  												New:  "false",
  6054  											},
  6055  										},
  6056  									},
  6057  								},
  6058  							},
  6059  						},
  6060  					},
  6061  				},
  6062  			},
  6063  		},
  6064  
  6065  		{
  6066  			Name: "Service with Connect Native",
  6067  			Old: &Task{
  6068  				Services: []*Service{
  6069  					{
  6070  						Name: "foo",
  6071  					},
  6072  				},
  6073  			},
  6074  			New: &Task{
  6075  				Services: []*Service{
  6076  					{
  6077  						Name:     "foo",
  6078  						TaskName: "task1",
  6079  						Connect: &ConsulConnect{
  6080  							Native: true,
  6081  						},
  6082  					},
  6083  				},
  6084  			},
  6085  			Expected: &TaskDiff{
  6086  				Type: DiffTypeEdited,
  6087  				Objects: []*ObjectDiff{
  6088  					{
  6089  						Type: DiffTypeEdited,
  6090  						Name: "Service",
  6091  						Fields: []*FieldDiff{
  6092  							{
  6093  								Type: DiffTypeAdded,
  6094  								Name: "TaskName",
  6095  								Old:  "",
  6096  								New:  "task1",
  6097  							},
  6098  						},
  6099  						Objects: []*ObjectDiff{
  6100  							{
  6101  								Type: DiffTypeAdded,
  6102  								Name: "ConsulConnect",
  6103  								Fields: []*FieldDiff{
  6104  									{
  6105  										Type: DiffTypeAdded,
  6106  										Name: "Native",
  6107  										Old:  "",
  6108  										New:  "true",
  6109  									},
  6110  								},
  6111  							},
  6112  						},
  6113  					},
  6114  				},
  6115  			},
  6116  		},
  6117  
  6118  		{
  6119  			Name: "Service Checks edited",
  6120  			Old: &Task{
  6121  				Services: []*Service{
  6122  					{
  6123  						Name: "foo",
  6124  						Checks: []*ServiceCheck{
  6125  							{
  6126  								Name:     "foo",
  6127  								Type:     "http",
  6128  								Command:  "foo",
  6129  								Args:     []string{"foo"},
  6130  								Path:     "foo",
  6131  								Protocol: "http",
  6132  								Interval: 1 * time.Second,
  6133  								Timeout:  1 * time.Second,
  6134  								Header: map[string][]string{
  6135  									"Foo": {"bar"},
  6136  								},
  6137  								SuccessBeforePassing:   1,
  6138  								FailuresBeforeCritical: 1,
  6139  							},
  6140  							{
  6141  								Name:                   "bar",
  6142  								Type:                   "http",
  6143  								Command:                "foo",
  6144  								Args:                   []string{"foo"},
  6145  								Path:                   "foo",
  6146  								Protocol:               "http",
  6147  								Interval:               1 * time.Second,
  6148  								Timeout:                1 * time.Second,
  6149  								SuccessBeforePassing:   7,
  6150  								FailuresBeforeCritical: 7,
  6151  							},
  6152  							{
  6153  								Name:     "baz",
  6154  								Type:     "http",
  6155  								Command:  "foo",
  6156  								Args:     []string{"foo"},
  6157  								Path:     "foo",
  6158  								Protocol: "http",
  6159  								Interval: 1 * time.Second,
  6160  								Timeout:  1 * time.Second,
  6161  							},
  6162  						},
  6163  					},
  6164  				},
  6165  			},
  6166  			New: &Task{
  6167  				Services: []*Service{
  6168  					{
  6169  						Name: "foo",
  6170  						Checks: []*ServiceCheck{
  6171  							{
  6172  								Name:                   "bar",
  6173  								Type:                   "http",
  6174  								Command:                "foo",
  6175  								Args:                   []string{"foo"},
  6176  								Path:                   "foo",
  6177  								Protocol:               "http",
  6178  								Interval:               1 * time.Second,
  6179  								Timeout:                1 * time.Second,
  6180  								SuccessBeforePassing:   7,
  6181  								FailuresBeforeCritical: 7,
  6182  							},
  6183  							{
  6184  								Name:     "baz",
  6185  								Type:     "tcp",
  6186  								Command:  "foo",
  6187  								Args:     []string{"foo"},
  6188  								Path:     "foo",
  6189  								Protocol: "http",
  6190  								Interval: 1 * time.Second,
  6191  								Timeout:  1 * time.Second,
  6192  								Header: map[string][]string{
  6193  									"Eggs": {"spam"},
  6194  								},
  6195  							},
  6196  							{
  6197  								Name:                   "bam",
  6198  								Type:                   "http",
  6199  								Command:                "foo",
  6200  								Args:                   []string{"foo"},
  6201  								Path:                   "foo",
  6202  								Protocol:               "http",
  6203  								Interval:               1 * time.Second,
  6204  								Timeout:                1 * time.Second,
  6205  								SuccessBeforePassing:   2,
  6206  								FailuresBeforeCritical: 2,
  6207  							},
  6208  						},
  6209  					},
  6210  				},
  6211  			},
  6212  			Expected: &TaskDiff{
  6213  				Type: DiffTypeEdited,
  6214  				Objects: []*ObjectDiff{
  6215  					{
  6216  						Type: DiffTypeEdited,
  6217  						Name: "Service",
  6218  						Objects: []*ObjectDiff{
  6219  							{
  6220  								Type: DiffTypeEdited,
  6221  								Name: "Check",
  6222  								Fields: []*FieldDiff{
  6223  									{
  6224  										Type: DiffTypeEdited,
  6225  										Name: "Type",
  6226  										Old:  "http",
  6227  										New:  "tcp",
  6228  									},
  6229  								},
  6230  								Objects: []*ObjectDiff{
  6231  									{
  6232  										Type: DiffTypeAdded,
  6233  										Name: "Header",
  6234  										Fields: []*FieldDiff{
  6235  											{
  6236  												Type: DiffTypeAdded,
  6237  												Name: "Eggs[0]",
  6238  												Old:  "",
  6239  												New:  "spam",
  6240  											},
  6241  										},
  6242  									},
  6243  								},
  6244  							},
  6245  							{
  6246  								Type: DiffTypeAdded,
  6247  								Name: "Check",
  6248  								Fields: []*FieldDiff{
  6249  									{
  6250  										Type: DiffTypeAdded,
  6251  										Name: "Command",
  6252  										Old:  "",
  6253  										New:  "foo",
  6254  									},
  6255  									{
  6256  										Type: DiffTypeAdded,
  6257  										Name: "Expose",
  6258  										Old:  "",
  6259  										New:  "false",
  6260  									},
  6261  									{
  6262  										Type: DiffTypeAdded,
  6263  										Name: "FailuresBeforeCritical",
  6264  										Old:  "",
  6265  										New:  "2",
  6266  									},
  6267  									{
  6268  										Type: DiffTypeAdded,
  6269  										Name: "GRPCUseTLS",
  6270  										Old:  "",
  6271  										New:  "false",
  6272  									},
  6273  									{
  6274  										Type: DiffTypeAdded,
  6275  										Name: "Interval",
  6276  										Old:  "",
  6277  										New:  "1000000000",
  6278  									},
  6279  									{
  6280  										Type: DiffTypeAdded,
  6281  										Name: "Name",
  6282  										Old:  "",
  6283  										New:  "bam",
  6284  									},
  6285  									{
  6286  										Type: DiffTypeAdded,
  6287  										Name: "Path",
  6288  										Old:  "",
  6289  										New:  "foo",
  6290  									},
  6291  									{
  6292  										Type: DiffTypeAdded,
  6293  										Name: "Protocol",
  6294  										Old:  "",
  6295  										New:  "http",
  6296  									},
  6297  									{
  6298  										Type: DiffTypeAdded,
  6299  										Name: "SuccessBeforePassing",
  6300  										Old:  "",
  6301  										New:  "2",
  6302  									},
  6303  									{
  6304  										Type: DiffTypeAdded,
  6305  										Name: "TLSSkipVerify",
  6306  										Old:  "",
  6307  										New:  "false",
  6308  									},
  6309  									{
  6310  										Type: DiffTypeAdded,
  6311  										Name: "Timeout",
  6312  										Old:  "",
  6313  										New:  "1000000000",
  6314  									},
  6315  									{
  6316  										Type: DiffTypeAdded,
  6317  										Name: "Type",
  6318  										Old:  "",
  6319  										New:  "http",
  6320  									},
  6321  								},
  6322  							},
  6323  							{
  6324  								Type: DiffTypeDeleted,
  6325  								Name: "Check",
  6326  								Fields: []*FieldDiff{
  6327  									{
  6328  										Type: DiffTypeDeleted,
  6329  										Name: "Command",
  6330  										Old:  "foo",
  6331  										New:  "",
  6332  									},
  6333  									{
  6334  										Type: DiffTypeDeleted,
  6335  										Name: "Expose",
  6336  										Old:  "false",
  6337  										New:  "",
  6338  									},
  6339  									{
  6340  										Type: DiffTypeDeleted,
  6341  										Name: "FailuresBeforeCritical",
  6342  										Old:  "1",
  6343  										New:  "",
  6344  									},
  6345  									{
  6346  										Type: DiffTypeDeleted,
  6347  										Name: "GRPCUseTLS",
  6348  										Old:  "false",
  6349  										New:  "",
  6350  									},
  6351  									{
  6352  										Type: DiffTypeDeleted,
  6353  										Name: "Interval",
  6354  										Old:  "1000000000",
  6355  										New:  "",
  6356  									},
  6357  									{
  6358  										Type: DiffTypeDeleted,
  6359  										Name: "Name",
  6360  										Old:  "foo",
  6361  										New:  "",
  6362  									},
  6363  									{
  6364  										Type: DiffTypeDeleted,
  6365  										Name: "Path",
  6366  										Old:  "foo",
  6367  										New:  "",
  6368  									},
  6369  									{
  6370  										Type: DiffTypeDeleted,
  6371  										Name: "Protocol",
  6372  										Old:  "http",
  6373  										New:  "",
  6374  									},
  6375  									{
  6376  										Type: DiffTypeDeleted,
  6377  										Name: "SuccessBeforePassing",
  6378  										Old:  "1",
  6379  										New:  "",
  6380  									},
  6381  									{
  6382  										Type: DiffTypeDeleted,
  6383  										Name: "TLSSkipVerify",
  6384  										Old:  "false",
  6385  										New:  "",
  6386  									},
  6387  									{
  6388  										Type: DiffTypeDeleted,
  6389  										Name: "Timeout",
  6390  										Old:  "1000000000",
  6391  										New:  "",
  6392  									},
  6393  									{
  6394  										Type: DiffTypeDeleted,
  6395  										Name: "Type",
  6396  										Old:  "http",
  6397  										New:  "",
  6398  									},
  6399  								},
  6400  								Objects: []*ObjectDiff{
  6401  									{
  6402  										Type: DiffTypeDeleted,
  6403  										Name: "Header",
  6404  										Fields: []*FieldDiff{
  6405  											{
  6406  												Type: DiffTypeDeleted,
  6407  												Name: "Foo[0]",
  6408  												Old:  "bar",
  6409  											},
  6410  										},
  6411  									},
  6412  								},
  6413  							},
  6414  						},
  6415  					},
  6416  				},
  6417  			},
  6418  		},
  6419  		{
  6420  			Name:       "Service Checks edited with context",
  6421  			Contextual: true,
  6422  			Old: &Task{
  6423  				Services: []*Service{
  6424  					{
  6425  						Name: "foo",
  6426  						Checks: []*ServiceCheck{
  6427  							{
  6428  								Name:          "foo",
  6429  								Type:          "http",
  6430  								Command:       "foo",
  6431  								Args:          []string{"foo"},
  6432  								Path:          "foo",
  6433  								Protocol:      "http",
  6434  								Interval:      1 * time.Second,
  6435  								Timeout:       1 * time.Second,
  6436  								InitialStatus: "critical",
  6437  								Header: map[string][]string{
  6438  									"Foo": {"bar"},
  6439  								},
  6440  								SuccessBeforePassing:   4,
  6441  								FailuresBeforeCritical: 5,
  6442  								OnUpdate:               "require_healthy",
  6443  							},
  6444  						},
  6445  					},
  6446  				},
  6447  			},
  6448  			New: &Task{
  6449  				Services: []*Service{
  6450  					{
  6451  						Name: "foo",
  6452  						Checks: []*ServiceCheck{
  6453  							{
  6454  								Name:          "foo",
  6455  								Type:          "tcp",
  6456  								Command:       "foo",
  6457  								Args:          []string{"foo"},
  6458  								Path:          "foo",
  6459  								Protocol:      "http",
  6460  								Interval:      1 * time.Second,
  6461  								Timeout:       1 * time.Second,
  6462  								InitialStatus: "passing",
  6463  								Method:        "POST",
  6464  								Header: map[string][]string{
  6465  									"Foo":  {"bar", "baz"},
  6466  									"Eggs": {"spam"},
  6467  								},
  6468  								SuccessBeforePassing: 4,
  6469  								OnUpdate:             "ignore_warnings",
  6470  							},
  6471  						},
  6472  					},
  6473  				},
  6474  			},
  6475  			Expected: &TaskDiff{
  6476  				Type: DiffTypeEdited,
  6477  				Objects: []*ObjectDiff{
  6478  					{
  6479  						Type: DiffTypeEdited,
  6480  						Name: "Service",
  6481  						Fields: []*FieldDiff{
  6482  							{
  6483  								Type: DiffTypeNone,
  6484  								Name: "Address",
  6485  								Old:  "",
  6486  								New:  "",
  6487  							},
  6488  							{
  6489  								Type: DiffTypeNone,
  6490  								Name: "AddressMode",
  6491  								Old:  "",
  6492  								New:  "",
  6493  							},
  6494  							{
  6495  								Type: DiffTypeNone,
  6496  								Name: "EnableTagOverride",
  6497  								Old:  "false",
  6498  								New:  "false",
  6499  							},
  6500  							{
  6501  								Type: DiffTypeNone,
  6502  								Name: "Name",
  6503  								Old:  "foo",
  6504  								New:  "foo",
  6505  							},
  6506  							{
  6507  								Type: DiffTypeNone,
  6508  								Name: "Namespace",
  6509  								Old:  "",
  6510  								New:  "",
  6511  							},
  6512  							{
  6513  								Type: DiffTypeNone,
  6514  								Name: "OnUpdate",
  6515  							},
  6516  							{
  6517  								Type: DiffTypeNone,
  6518  								Name: "PortLabel",
  6519  								Old:  "",
  6520  								New:  "",
  6521  							},
  6522  							{
  6523  								Type: DiffTypeNone,
  6524  								Name: "Provider",
  6525  							},
  6526  							{
  6527  								Type: DiffTypeNone,
  6528  								Name: "TaskName",
  6529  								Old:  "",
  6530  								New:  "",
  6531  							},
  6532  						},
  6533  						Objects: []*ObjectDiff{
  6534  							{
  6535  								Type: DiffTypeEdited,
  6536  								Name: "Check",
  6537  								Fields: []*FieldDiff{
  6538  									{
  6539  										Type: DiffTypeNone,
  6540  										Name: "AddressMode",
  6541  										Old:  "",
  6542  										New:  "",
  6543  									},
  6544  									{
  6545  										Type: DiffTypeNone,
  6546  										Name: "Body",
  6547  										Old:  "",
  6548  										New:  "",
  6549  									},
  6550  									{
  6551  										Type: DiffTypeNone,
  6552  										Name: "Command",
  6553  										Old:  "foo",
  6554  										New:  "foo",
  6555  									},
  6556  									{
  6557  										Type: DiffTypeNone,
  6558  										Name: "Expose",
  6559  										Old:  "false",
  6560  										New:  "false",
  6561  									},
  6562  									{
  6563  										Type: DiffTypeEdited,
  6564  										Name: "FailuresBeforeCritical",
  6565  										Old:  "5",
  6566  										New:  "0",
  6567  									},
  6568  									{
  6569  										Type: DiffTypeNone,
  6570  										Name: "GRPCService",
  6571  										Old:  "",
  6572  										New:  "",
  6573  									},
  6574  									{
  6575  										Type: DiffTypeNone,
  6576  										Name: "GRPCUseTLS",
  6577  										Old:  "false",
  6578  										New:  "false",
  6579  									},
  6580  									{
  6581  										Type: DiffTypeEdited,
  6582  										Name: "InitialStatus",
  6583  										Old:  "critical",
  6584  										New:  "passing",
  6585  									},
  6586  									{
  6587  										Type: DiffTypeNone,
  6588  										Name: "Interval",
  6589  										Old:  "1000000000",
  6590  										New:  "1000000000",
  6591  									},
  6592  									{
  6593  										Type: DiffTypeAdded,
  6594  										Name: "Method",
  6595  										Old:  "",
  6596  										New:  "POST",
  6597  									},
  6598  									{
  6599  										Type: DiffTypeNone,
  6600  										Name: "Name",
  6601  										Old:  "foo",
  6602  										New:  "foo",
  6603  									},
  6604  									{
  6605  										Type: DiffTypeEdited,
  6606  										Name: "OnUpdate",
  6607  										Old:  "require_healthy",
  6608  										New:  "ignore_warnings",
  6609  									},
  6610  									{
  6611  										Type: DiffTypeNone,
  6612  										Name: "Path",
  6613  										Old:  "foo",
  6614  										New:  "foo",
  6615  									},
  6616  									{
  6617  										Type: DiffTypeNone,
  6618  										Name: "PortLabel",
  6619  										Old:  "",
  6620  										New:  "",
  6621  									},
  6622  									{
  6623  										Type: DiffTypeNone,
  6624  										Name: "Protocol",
  6625  										Old:  "http",
  6626  										New:  "http",
  6627  									},
  6628  									{
  6629  										Type: DiffTypeNone,
  6630  										Name: "SuccessBeforePassing",
  6631  										Old:  "4",
  6632  										New:  "4",
  6633  									},
  6634  									{
  6635  										Type: DiffTypeNone,
  6636  										Name: "TLSServerName",
  6637  										Old:  "",
  6638  										New:  "",
  6639  									},
  6640  									{
  6641  										Type: DiffTypeNone,
  6642  										Name: "TLSSkipVerify",
  6643  										Old:  "false",
  6644  										New:  "false",
  6645  									},
  6646  									{
  6647  										Type: DiffTypeNone,
  6648  										Name: "TaskName",
  6649  										Old:  "",
  6650  										New:  "",
  6651  									},
  6652  									{
  6653  										Type: DiffTypeNone,
  6654  										Name: "Timeout",
  6655  										Old:  "1000000000",
  6656  										New:  "1000000000",
  6657  									},
  6658  									{
  6659  										Type: DiffTypeEdited,
  6660  										Name: "Type",
  6661  										Old:  "http",
  6662  										New:  "tcp",
  6663  									},
  6664  								},
  6665  								Objects: []*ObjectDiff{
  6666  									{
  6667  										Type: DiffTypeEdited,
  6668  										Name: "Header",
  6669  										Fields: []*FieldDiff{
  6670  											{
  6671  												Type: DiffTypeAdded,
  6672  												Name: "Eggs[0]",
  6673  												Old:  "",
  6674  												New:  "spam",
  6675  											},
  6676  											{
  6677  												Type: DiffTypeNone,
  6678  												Name: "Foo[0]",
  6679  												Old:  "bar",
  6680  												New:  "bar",
  6681  											},
  6682  											{
  6683  												Type: DiffTypeAdded,
  6684  												Name: "Foo[1]",
  6685  												Old:  "",
  6686  												New:  "baz",
  6687  											},
  6688  										},
  6689  									},
  6690  								},
  6691  							},
  6692  						},
  6693  					},
  6694  				},
  6695  			},
  6696  		},
  6697  		{
  6698  			Name: "CheckRestart edited",
  6699  			Old: &Task{
  6700  				Services: []*Service{
  6701  					{
  6702  						Name: "foo",
  6703  						Checks: []*ServiceCheck{
  6704  							{
  6705  								Name:     "foo",
  6706  								Type:     "http",
  6707  								Command:  "foo",
  6708  								Args:     []string{"foo"},
  6709  								Path:     "foo",
  6710  								Protocol: "http",
  6711  								Interval: 1 * time.Second,
  6712  								Timeout:  1 * time.Second,
  6713  							},
  6714  							{
  6715  								Name:     "bar",
  6716  								Type:     "http",
  6717  								Command:  "foo",
  6718  								Args:     []string{"foo"},
  6719  								Path:     "foo",
  6720  								Protocol: "http",
  6721  								Interval: 1 * time.Second,
  6722  								Timeout:  1 * time.Second,
  6723  								CheckRestart: &CheckRestart{
  6724  									Limit: 2,
  6725  									Grace: 2 * time.Second,
  6726  								},
  6727  							},
  6728  							{
  6729  								Name:     "baz",
  6730  								Type:     "http",
  6731  								Command:  "foo",
  6732  								Args:     []string{"foo"},
  6733  								Path:     "foo",
  6734  								Protocol: "http",
  6735  								Interval: 1 * time.Second,
  6736  								Timeout:  1 * time.Second,
  6737  								CheckRestart: &CheckRestart{
  6738  									Limit: 3,
  6739  									Grace: 3 * time.Second,
  6740  								},
  6741  							},
  6742  						},
  6743  					},
  6744  				},
  6745  			},
  6746  			New: &Task{
  6747  				Services: []*Service{
  6748  					{
  6749  						Name: "foo",
  6750  						Checks: []*ServiceCheck{
  6751  							{
  6752  								Name:     "foo",
  6753  								Type:     "http",
  6754  								Command:  "foo",
  6755  								Args:     []string{"foo"},
  6756  								Path:     "foo",
  6757  								Protocol: "http",
  6758  								Interval: 1 * time.Second,
  6759  								Timeout:  1 * time.Second,
  6760  								CheckRestart: &CheckRestart{
  6761  									Limit: 1,
  6762  									Grace: 1 * time.Second,
  6763  								},
  6764  							},
  6765  							{
  6766  								Name:     "bar",
  6767  								Type:     "http",
  6768  								Command:  "foo",
  6769  								Args:     []string{"foo"},
  6770  								Path:     "foo",
  6771  								Protocol: "http",
  6772  								Interval: 1 * time.Second,
  6773  								Timeout:  1 * time.Second,
  6774  							},
  6775  							{
  6776  								Name:     "baz",
  6777  								Type:     "http",
  6778  								Command:  "foo",
  6779  								Args:     []string{"foo"},
  6780  								Path:     "foo",
  6781  								Protocol: "http",
  6782  								Interval: 1 * time.Second,
  6783  								Timeout:  1 * time.Second,
  6784  								CheckRestart: &CheckRestart{
  6785  									Limit: 4,
  6786  									Grace: 4 * time.Second,
  6787  								},
  6788  							},
  6789  						},
  6790  					},
  6791  				},
  6792  			},
  6793  			Expected: &TaskDiff{
  6794  				Type: DiffTypeEdited,
  6795  				Objects: []*ObjectDiff{
  6796  					{
  6797  						Type: DiffTypeEdited,
  6798  						Name: "Service",
  6799  						Objects: []*ObjectDiff{
  6800  							{
  6801  								Type: DiffTypeEdited,
  6802  								Name: "Check",
  6803  								Objects: []*ObjectDiff{
  6804  									{
  6805  										Type: DiffTypeEdited,
  6806  										Name: "CheckRestart",
  6807  										Fields: []*FieldDiff{
  6808  											{
  6809  												Type: DiffTypeEdited,
  6810  												Name: "Grace",
  6811  												Old:  "3000000000",
  6812  												New:  "4000000000",
  6813  											},
  6814  											{
  6815  												Type: DiffTypeEdited,
  6816  												Name: "Limit",
  6817  												Old:  "3",
  6818  												New:  "4",
  6819  											},
  6820  										},
  6821  									},
  6822  								},
  6823  							},
  6824  							{
  6825  								Type: DiffTypeEdited,
  6826  								Name: "Check",
  6827  								Objects: []*ObjectDiff{
  6828  									{
  6829  										Type: DiffTypeAdded,
  6830  										Name: "CheckRestart",
  6831  										Fields: []*FieldDiff{
  6832  											{
  6833  												Type: DiffTypeAdded,
  6834  												Name: "Grace",
  6835  												New:  "1000000000",
  6836  											},
  6837  											{
  6838  												Type: DiffTypeAdded,
  6839  												Name: "IgnoreWarnings",
  6840  												New:  "false",
  6841  											},
  6842  											{
  6843  												Type: DiffTypeAdded,
  6844  												Name: "Limit",
  6845  												New:  "1",
  6846  											},
  6847  										},
  6848  									},
  6849  								},
  6850  							},
  6851  							{
  6852  								Type: DiffTypeEdited,
  6853  								Name: "Check",
  6854  								Objects: []*ObjectDiff{
  6855  									{
  6856  										Type: DiffTypeDeleted,
  6857  										Name: "CheckRestart",
  6858  										Fields: []*FieldDiff{
  6859  											{
  6860  												Type: DiffTypeDeleted,
  6861  												Name: "Grace",
  6862  												Old:  "2000000000",
  6863  											},
  6864  											{
  6865  												Type: DiffTypeDeleted,
  6866  												Name: "IgnoreWarnings",
  6867  												Old:  "false",
  6868  											},
  6869  											{
  6870  												Type: DiffTypeDeleted,
  6871  												Name: "Limit",
  6872  												Old:  "2",
  6873  											},
  6874  										},
  6875  									},
  6876  								},
  6877  							},
  6878  						},
  6879  					},
  6880  				},
  6881  			},
  6882  		},
  6883  		{
  6884  			Name: "Vault added",
  6885  			Old:  &Task{},
  6886  			New: &Task{
  6887  				Vault: &Vault{
  6888  					Policies:     []string{"foo", "bar"},
  6889  					Env:          true,
  6890  					DisableFile:  true,
  6891  					ChangeMode:   "signal",
  6892  					ChangeSignal: "SIGUSR1",
  6893  				},
  6894  			},
  6895  			Expected: &TaskDiff{
  6896  				Type: DiffTypeEdited,
  6897  				Objects: []*ObjectDiff{
  6898  					{
  6899  						Type: DiffTypeAdded,
  6900  						Name: "Vault",
  6901  						Fields: []*FieldDiff{
  6902  							{
  6903  								Type: DiffTypeAdded,
  6904  								Name: "ChangeMode",
  6905  								Old:  "",
  6906  								New:  "signal",
  6907  							},
  6908  							{
  6909  								Type: DiffTypeAdded,
  6910  								Name: "ChangeSignal",
  6911  								Old:  "",
  6912  								New:  "SIGUSR1",
  6913  							},
  6914  							{
  6915  								Type: DiffTypeAdded,
  6916  								Name: "DisableFile",
  6917  								Old:  "",
  6918  								New:  "true",
  6919  							},
  6920  							{
  6921  								Type: DiffTypeAdded,
  6922  								Name: "Env",
  6923  								Old:  "",
  6924  								New:  "true",
  6925  							},
  6926  						},
  6927  						Objects: []*ObjectDiff{
  6928  							{
  6929  								Type: DiffTypeAdded,
  6930  								Name: "Policies",
  6931  								Fields: []*FieldDiff{
  6932  									{
  6933  										Type: DiffTypeAdded,
  6934  										Name: "Policies",
  6935  										Old:  "",
  6936  										New:  "bar",
  6937  									},
  6938  									{
  6939  										Type: DiffTypeAdded,
  6940  										Name: "Policies",
  6941  										Old:  "",
  6942  										New:  "foo",
  6943  									},
  6944  								},
  6945  							},
  6946  						},
  6947  					},
  6948  				},
  6949  			},
  6950  		},
  6951  		{
  6952  			Name: "Vault deleted",
  6953  			Old: &Task{
  6954  				Vault: &Vault{
  6955  					Policies:     []string{"foo", "bar"},
  6956  					Env:          true,
  6957  					DisableFile:  true,
  6958  					ChangeMode:   "signal",
  6959  					ChangeSignal: "SIGUSR1",
  6960  				},
  6961  			},
  6962  			New: &Task{},
  6963  			Expected: &TaskDiff{
  6964  				Type: DiffTypeEdited,
  6965  				Objects: []*ObjectDiff{
  6966  					{
  6967  						Type: DiffTypeDeleted,
  6968  						Name: "Vault",
  6969  						Fields: []*FieldDiff{
  6970  							{
  6971  								Type: DiffTypeDeleted,
  6972  								Name: "ChangeMode",
  6973  								Old:  "signal",
  6974  								New:  "",
  6975  							},
  6976  							{
  6977  								Type: DiffTypeDeleted,
  6978  								Name: "ChangeSignal",
  6979  								Old:  "SIGUSR1",
  6980  								New:  "",
  6981  							},
  6982  							{
  6983  								Type: DiffTypeDeleted,
  6984  								Name: "DisableFile",
  6985  								Old:  "true",
  6986  								New:  "",
  6987  							},
  6988  							{
  6989  								Type: DiffTypeDeleted,
  6990  								Name: "Env",
  6991  								Old:  "true",
  6992  								New:  "",
  6993  							},
  6994  						},
  6995  						Objects: []*ObjectDiff{
  6996  							{
  6997  								Type: DiffTypeDeleted,
  6998  								Name: "Policies",
  6999  								Fields: []*FieldDiff{
  7000  									{
  7001  										Type: DiffTypeDeleted,
  7002  										Name: "Policies",
  7003  										Old:  "bar",
  7004  										New:  "",
  7005  									},
  7006  									{
  7007  										Type: DiffTypeDeleted,
  7008  										Name: "Policies",
  7009  										Old:  "foo",
  7010  										New:  "",
  7011  									},
  7012  								},
  7013  							},
  7014  						},
  7015  					},
  7016  				},
  7017  			},
  7018  		},
  7019  		{
  7020  			Name: "Vault edited",
  7021  			Old: &Task{
  7022  				Vault: &Vault{
  7023  					Namespace:    "ns1",
  7024  					Policies:     []string{"foo", "bar"},
  7025  					Env:          true,
  7026  					DisableFile:  true,
  7027  					ChangeMode:   "signal",
  7028  					ChangeSignal: "SIGUSR1",
  7029  				},
  7030  			},
  7031  			New: &Task{
  7032  				Vault: &Vault{
  7033  					Namespace:    "ns2",
  7034  					Policies:     []string{"bar", "baz"},
  7035  					Env:          false,
  7036  					DisableFile:  false,
  7037  					ChangeMode:   "restart",
  7038  					ChangeSignal: "foo",
  7039  				},
  7040  			},
  7041  			Expected: &TaskDiff{
  7042  				Type: DiffTypeEdited,
  7043  				Objects: []*ObjectDiff{
  7044  					{
  7045  						Type: DiffTypeEdited,
  7046  						Name: "Vault",
  7047  						Fields: []*FieldDiff{
  7048  							{
  7049  								Type: DiffTypeEdited,
  7050  								Name: "ChangeMode",
  7051  								Old:  "signal",
  7052  								New:  "restart",
  7053  							},
  7054  							{
  7055  								Type: DiffTypeEdited,
  7056  								Name: "ChangeSignal",
  7057  								Old:  "SIGUSR1",
  7058  								New:  "foo",
  7059  							},
  7060  							{
  7061  								Type: DiffTypeEdited,
  7062  								Name: "DisableFile",
  7063  								Old:  "true",
  7064  								New:  "false",
  7065  							},
  7066  							{
  7067  								Type: DiffTypeEdited,
  7068  								Name: "Env",
  7069  								Old:  "true",
  7070  								New:  "false",
  7071  							},
  7072  							{
  7073  								Type: DiffTypeEdited,
  7074  								Name: "Namespace",
  7075  								Old:  "ns1",
  7076  								New:  "ns2",
  7077  							},
  7078  						},
  7079  						Objects: []*ObjectDiff{
  7080  							{
  7081  								Type: DiffTypeEdited,
  7082  								Name: "Policies",
  7083  								Fields: []*FieldDiff{
  7084  									{
  7085  										Type: DiffTypeAdded,
  7086  										Name: "Policies",
  7087  										Old:  "",
  7088  										New:  "baz",
  7089  									},
  7090  									{
  7091  										Type: DiffTypeDeleted,
  7092  										Name: "Policies",
  7093  										Old:  "foo",
  7094  										New:  "",
  7095  									},
  7096  								},
  7097  							},
  7098  						},
  7099  					},
  7100  				},
  7101  			},
  7102  		},
  7103  		{
  7104  			Name:       "Vault edited with context",
  7105  			Contextual: true,
  7106  			Old: &Task{
  7107  				Vault: &Vault{
  7108  					Namespace:    "ns1",
  7109  					Policies:     []string{"foo", "bar"},
  7110  					Env:          true,
  7111  					DisableFile:  true,
  7112  					ChangeMode:   "signal",
  7113  					ChangeSignal: "SIGUSR1",
  7114  				},
  7115  			},
  7116  			New: &Task{
  7117  				Vault: &Vault{
  7118  					Namespace:    "ns1",
  7119  					Policies:     []string{"bar", "baz"},
  7120  					Env:          true,
  7121  					DisableFile:  true,
  7122  					ChangeMode:   "signal",
  7123  					ChangeSignal: "SIGUSR1",
  7124  				},
  7125  			},
  7126  			Expected: &TaskDiff{
  7127  				Type: DiffTypeEdited,
  7128  				Objects: []*ObjectDiff{
  7129  					{
  7130  						Type: DiffTypeEdited,
  7131  						Name: "Vault",
  7132  						Fields: []*FieldDiff{
  7133  							{
  7134  								Type: DiffTypeNone,
  7135  								Name: "ChangeMode",
  7136  								Old:  "signal",
  7137  								New:  "signal",
  7138  							},
  7139  							{
  7140  								Type: DiffTypeNone,
  7141  								Name: "ChangeSignal",
  7142  								Old:  "SIGUSR1",
  7143  								New:  "SIGUSR1",
  7144  							},
  7145  							{
  7146  								Type: DiffTypeNone,
  7147  								Name: "DisableFile",
  7148  								Old:  "true",
  7149  								New:  "true",
  7150  							},
  7151  							{
  7152  								Type: DiffTypeNone,
  7153  								Name: "Env",
  7154  								Old:  "true",
  7155  								New:  "true",
  7156  							},
  7157  							{
  7158  								Type: DiffTypeNone,
  7159  								Name: "Namespace",
  7160  								Old:  "ns1",
  7161  								New:  "ns1",
  7162  							},
  7163  						},
  7164  						Objects: []*ObjectDiff{
  7165  							{
  7166  								Type: DiffTypeEdited,
  7167  								Name: "Policies",
  7168  								Fields: []*FieldDiff{
  7169  									{
  7170  										Type: DiffTypeAdded,
  7171  										Name: "Policies",
  7172  										Old:  "",
  7173  										New:  "baz",
  7174  									},
  7175  									{
  7176  										Type: DiffTypeNone,
  7177  										Name: "Policies",
  7178  										Old:  "bar",
  7179  										New:  "bar",
  7180  									},
  7181  									{
  7182  										Type: DiffTypeDeleted,
  7183  										Name: "Policies",
  7184  										Old:  "foo",
  7185  										New:  "",
  7186  									},
  7187  								},
  7188  							},
  7189  						},
  7190  					},
  7191  				},
  7192  			},
  7193  		},
  7194  		{
  7195  			Name: "Template edited",
  7196  			Old: &Task{
  7197  				Templates: []*Template{
  7198  					{
  7199  						SourcePath:   "foo",
  7200  						DestPath:     "bar",
  7201  						EmbeddedTmpl: "baz",
  7202  						ChangeMode:   "bam",
  7203  						ChangeSignal: "SIGHUP",
  7204  						ChangeScript: &ChangeScript{
  7205  							Command:     "/bin/foo",
  7206  							Args:        []string{"-debug"},
  7207  							Timeout:     5,
  7208  							FailOnError: false,
  7209  						},
  7210  						Splay: 1,
  7211  						Perms: "0644",
  7212  						Uid:   pointer.Of(1001),
  7213  						Gid:   pointer.Of(21),
  7214  						Wait: &WaitConfig{
  7215  							Min: pointer.Of(5 * time.Second),
  7216  							Max: pointer.Of(5 * time.Second),
  7217  						},
  7218  						ErrMissingKey: false,
  7219  					},
  7220  					{
  7221  						SourcePath:   "foo2",
  7222  						DestPath:     "bar2",
  7223  						EmbeddedTmpl: "baz2",
  7224  						ChangeMode:   "bam2",
  7225  						ChangeSignal: "SIGHUP2",
  7226  						ChangeScript: &ChangeScript{
  7227  							Command:     "/bin/foo2",
  7228  							Args:        []string{"-debugs"},
  7229  							Timeout:     6,
  7230  							FailOnError: false,
  7231  						},
  7232  						Splay:   2,
  7233  						Perms:   "0666",
  7234  						Uid:     pointer.Of(1000),
  7235  						Gid:     pointer.Of(20),
  7236  						Envvars: true,
  7237  					},
  7238  				},
  7239  			},
  7240  			New: &Task{
  7241  				Templates: []*Template{
  7242  					{
  7243  						SourcePath:   "foo",
  7244  						DestPath:     "bar",
  7245  						EmbeddedTmpl: "baz new",
  7246  						ChangeMode:   "bam",
  7247  						ChangeSignal: "SIGHUP",
  7248  						ChangeScript: &ChangeScript{
  7249  							Command:     "/bin/foo",
  7250  							Args:        []string{"-debug"},
  7251  							Timeout:     5,
  7252  							FailOnError: false,
  7253  						},
  7254  						Splay: 1,
  7255  						Perms: "0644",
  7256  						Uid:   pointer.Of(1001),
  7257  						Gid:   pointer.Of(21),
  7258  						Wait: &WaitConfig{
  7259  							Min: pointer.Of(5 * time.Second),
  7260  							Max: pointer.Of(10 * time.Second),
  7261  						},
  7262  						ErrMissingKey: true,
  7263  					},
  7264  					{
  7265  						SourcePath:   "foo3",
  7266  						DestPath:     "bar3",
  7267  						EmbeddedTmpl: "baz3",
  7268  						ChangeMode:   "bam3",
  7269  						ChangeSignal: "SIGHUP3",
  7270  						ChangeScript: &ChangeScript{
  7271  							Command:     "/bin/foo3",
  7272  							Args:        []string{"-debugss"},
  7273  							Timeout:     7,
  7274  							FailOnError: false,
  7275  						},
  7276  						Splay: 3,
  7277  						Perms: "0776",
  7278  						Uid:   pointer.Of(1002),
  7279  						Gid:   pointer.Of(22),
  7280  						Wait: &WaitConfig{
  7281  							Min: pointer.Of(5 * time.Second),
  7282  							Max: pointer.Of(10 * time.Second),
  7283  						},
  7284  						ErrMissingKey: true,
  7285  					},
  7286  				},
  7287  			},
  7288  			Expected: &TaskDiff{
  7289  				Type: DiffTypeEdited,
  7290  				Objects: []*ObjectDiff{
  7291  					{
  7292  						Type: DiffTypeEdited,
  7293  						Name: "Template",
  7294  						Fields: []*FieldDiff{
  7295  							{
  7296  								Type: DiffTypeEdited,
  7297  								Name: "EmbeddedTmpl",
  7298  								Old:  "baz",
  7299  								New:  "baz new",
  7300  							},
  7301  							{
  7302  								Type: DiffTypeEdited,
  7303  								Name: "ErrMissingKey",
  7304  								Old:  "false",
  7305  								New:  "true",
  7306  							},
  7307  						},
  7308  						Objects: []*ObjectDiff{
  7309  							{
  7310  								Type: DiffTypeEdited,
  7311  								Name: "Template",
  7312  								Fields: []*FieldDiff{
  7313  									{
  7314  										Type: DiffTypeEdited,
  7315  										Name: "Max",
  7316  										Old:  "5000000000",
  7317  										New:  "10000000000",
  7318  									},
  7319  								},
  7320  							},
  7321  						},
  7322  					},
  7323  					{
  7324  						Type: DiffTypeAdded,
  7325  						Name: "Template",
  7326  						Fields: []*FieldDiff{
  7327  							{
  7328  								Type: DiffTypeAdded,
  7329  								Name: "ChangeMode",
  7330  								Old:  "",
  7331  								New:  "bam3",
  7332  							},
  7333  							{
  7334  								Type: DiffTypeAdded,
  7335  								Name: "ChangeSignal",
  7336  								Old:  "",
  7337  								New:  "SIGHUP3",
  7338  							},
  7339  							{
  7340  								Type: DiffTypeAdded,
  7341  								Name: "DestPath",
  7342  								Old:  "",
  7343  								New:  "bar3",
  7344  							},
  7345  							{
  7346  								Type: DiffTypeAdded,
  7347  								Name: "EmbeddedTmpl",
  7348  								Old:  "",
  7349  								New:  "baz3",
  7350  							},
  7351  							{
  7352  								Type: DiffTypeAdded,
  7353  								Name: "Envvars",
  7354  								Old:  "",
  7355  								New:  "false",
  7356  							},
  7357  							{
  7358  								Type: DiffTypeAdded,
  7359  								Name: "ErrMissingKey",
  7360  								Old:  "",
  7361  								New:  "true",
  7362  							},
  7363  							{
  7364  								Type: DiffTypeAdded,
  7365  								Name: "Gid",
  7366  								Old:  "",
  7367  								New:  "22",
  7368  							},
  7369  							{
  7370  								Type: DiffTypeAdded,
  7371  								Name: "Perms",
  7372  								Old:  "",
  7373  								New:  "0776",
  7374  							},
  7375  							{
  7376  								Type: DiffTypeAdded,
  7377  								Name: "SourcePath",
  7378  								Old:  "",
  7379  								New:  "foo3",
  7380  							},
  7381  							{
  7382  								Type: DiffTypeAdded,
  7383  								Name: "Splay",
  7384  								Old:  "",
  7385  								New:  "3",
  7386  							},
  7387  							{
  7388  								Type: DiffTypeAdded,
  7389  								Name: "Uid",
  7390  								Old:  "",
  7391  								New:  "1002",
  7392  							},
  7393  							{
  7394  								Type: DiffTypeAdded,
  7395  								Name: "VaultGrace",
  7396  								Old:  "",
  7397  								New:  "0",
  7398  							},
  7399  						},
  7400  						Objects: []*ObjectDiff{
  7401  							{
  7402  								Type: DiffTypeAdded,
  7403  								Name: "Template",
  7404  								Fields: []*FieldDiff{
  7405  									{
  7406  										Type: DiffTypeAdded,
  7407  										Name: "Max",
  7408  										Old:  "",
  7409  										New:  "10000000000",
  7410  									},
  7411  									{
  7412  										Type: DiffTypeAdded,
  7413  										Name: "Min",
  7414  										Old:  "",
  7415  										New:  "5000000000",
  7416  									},
  7417  								},
  7418  							},
  7419  							{
  7420  								Type: DiffTypeAdded,
  7421  								Name: "ChangeScript",
  7422  								Fields: []*FieldDiff{
  7423  									{
  7424  										Type: DiffTypeAdded,
  7425  										Name: "Command",
  7426  										Old:  "",
  7427  										New:  "/bin/foo3",
  7428  									},
  7429  									{
  7430  										Type: DiffTypeAdded,
  7431  										Name: "FailOnError",
  7432  										Old:  "",
  7433  										New:  "false",
  7434  									},
  7435  									{
  7436  										Type: DiffTypeAdded,
  7437  										Name: "Timeout",
  7438  										Old:  "",
  7439  										New:  "7",
  7440  									},
  7441  								},
  7442  								Objects: []*ObjectDiff{
  7443  									{
  7444  										Type: DiffTypeAdded,
  7445  										Name: "Args",
  7446  										Fields: []*FieldDiff{
  7447  											{
  7448  												Type: DiffTypeAdded,
  7449  												Name: "Args",
  7450  												Old:  "",
  7451  												New:  "-debugss",
  7452  											},
  7453  										},
  7454  									},
  7455  								},
  7456  							},
  7457  						},
  7458  					},
  7459  					{
  7460  						Type: DiffTypeDeleted,
  7461  						Name: "Template",
  7462  						Fields: []*FieldDiff{
  7463  							{
  7464  								Type: DiffTypeDeleted,
  7465  								Name: "ChangeMode",
  7466  								Old:  "bam2",
  7467  								New:  "",
  7468  							},
  7469  							{
  7470  								Type: DiffTypeDeleted,
  7471  								Name: "ChangeSignal",
  7472  								Old:  "SIGHUP2",
  7473  								New:  "",
  7474  							},
  7475  							{
  7476  								Type: DiffTypeDeleted,
  7477  								Name: "DestPath",
  7478  								Old:  "bar2",
  7479  								New:  "",
  7480  							},
  7481  							{
  7482  								Type: DiffTypeDeleted,
  7483  								Name: "EmbeddedTmpl",
  7484  								Old:  "baz2",
  7485  								New:  "",
  7486  							},
  7487  							{
  7488  								Type: DiffTypeDeleted,
  7489  								Name: "Envvars",
  7490  								Old:  "true",
  7491  								New:  "",
  7492  							},
  7493  							{
  7494  								Type: DiffTypeDeleted,
  7495  								Name: "ErrMissingKey",
  7496  								Old:  "false",
  7497  								New:  "",
  7498  							},
  7499  							{
  7500  								Type: DiffTypeDeleted,
  7501  								Name: "Gid",
  7502  								Old:  "20",
  7503  								New:  "",
  7504  							},
  7505  							{
  7506  								Type: DiffTypeDeleted,
  7507  								Name: "Perms",
  7508  								Old:  "0666",
  7509  								New:  "",
  7510  							},
  7511  							{
  7512  								Type: DiffTypeDeleted,
  7513  								Name: "SourcePath",
  7514  								Old:  "foo2",
  7515  								New:  "",
  7516  							},
  7517  							{
  7518  								Type: DiffTypeDeleted,
  7519  								Name: "Splay",
  7520  								Old:  "2",
  7521  								New:  "",
  7522  							},
  7523  							{
  7524  								Type: DiffTypeDeleted,
  7525  								Name: "Uid",
  7526  								Old:  "1000",
  7527  								New:  "",
  7528  							},
  7529  							{
  7530  								Type: DiffTypeDeleted,
  7531  								Name: "VaultGrace",
  7532  								Old:  "0",
  7533  								New:  "",
  7534  							},
  7535  						},
  7536  						Objects: []*ObjectDiff{
  7537  							{
  7538  								Type: DiffTypeDeleted,
  7539  								Name: "ChangeScript",
  7540  								Fields: []*FieldDiff{
  7541  									{
  7542  										Type: DiffTypeDeleted,
  7543  										Name: "Command",
  7544  										Old:  "/bin/foo2",
  7545  										New:  "",
  7546  									},
  7547  									{
  7548  										Type: DiffTypeDeleted,
  7549  										Name: "FailOnError",
  7550  										Old:  "false",
  7551  										New:  "",
  7552  									},
  7553  									{
  7554  										Type: DiffTypeDeleted,
  7555  										Name: "Timeout",
  7556  										Old:  "6",
  7557  										New:  "",
  7558  									},
  7559  								},
  7560  								Objects: []*ObjectDiff{
  7561  									{
  7562  										Type: DiffTypeDeleted,
  7563  										Name: "Args",
  7564  										Fields: []*FieldDiff{
  7565  											{
  7566  												Type: DiffTypeDeleted,
  7567  												Name: "Args",
  7568  												Old:  "-debugs",
  7569  												New:  "",
  7570  											},
  7571  										},
  7572  									},
  7573  								},
  7574  							},
  7575  						},
  7576  					},
  7577  				},
  7578  			},
  7579  		},
  7580  		{
  7581  			Name: "DispatchPayload added",
  7582  			Old:  &Task{},
  7583  			New: &Task{
  7584  				DispatchPayload: &DispatchPayloadConfig{
  7585  					File: "foo",
  7586  				},
  7587  			},
  7588  			Expected: &TaskDiff{
  7589  				Type: DiffTypeEdited,
  7590  				Objects: []*ObjectDiff{
  7591  					{
  7592  						Type: DiffTypeAdded,
  7593  						Name: "DispatchPayload",
  7594  						Fields: []*FieldDiff{
  7595  							{
  7596  								Type: DiffTypeAdded,
  7597  								Name: "File",
  7598  								Old:  "",
  7599  								New:  "foo",
  7600  							},
  7601  						},
  7602  					},
  7603  				},
  7604  			},
  7605  		},
  7606  		{
  7607  			Name: "DispatchPayload deleted",
  7608  			Old: &Task{
  7609  				DispatchPayload: &DispatchPayloadConfig{
  7610  					File: "foo",
  7611  				},
  7612  			},
  7613  			New: &Task{},
  7614  			Expected: &TaskDiff{
  7615  				Type: DiffTypeEdited,
  7616  				Objects: []*ObjectDiff{
  7617  					{
  7618  						Type: DiffTypeDeleted,
  7619  						Name: "DispatchPayload",
  7620  						Fields: []*FieldDiff{
  7621  							{
  7622  								Type: DiffTypeDeleted,
  7623  								Name: "File",
  7624  								Old:  "foo",
  7625  								New:  "",
  7626  							},
  7627  						},
  7628  					},
  7629  				},
  7630  			},
  7631  		},
  7632  		{
  7633  			Name: "Dispatch payload edited",
  7634  			Old: &Task{
  7635  				DispatchPayload: &DispatchPayloadConfig{
  7636  					File: "foo",
  7637  				},
  7638  			},
  7639  			New: &Task{
  7640  				DispatchPayload: &DispatchPayloadConfig{
  7641  					File: "bar",
  7642  				},
  7643  			},
  7644  			Expected: &TaskDiff{
  7645  				Type: DiffTypeEdited,
  7646  				Objects: []*ObjectDiff{
  7647  					{
  7648  						Type: DiffTypeEdited,
  7649  						Name: "DispatchPayload",
  7650  						Fields: []*FieldDiff{
  7651  							{
  7652  								Type: DiffTypeEdited,
  7653  								Name: "File",
  7654  								Old:  "foo",
  7655  								New:  "bar",
  7656  							},
  7657  						},
  7658  					},
  7659  				},
  7660  			},
  7661  		},
  7662  		{
  7663  			// Place holder for if more fields are added
  7664  			Name:       "DispatchPayload edited with context",
  7665  			Contextual: true,
  7666  			Old: &Task{
  7667  				DispatchPayload: &DispatchPayloadConfig{
  7668  					File: "foo",
  7669  				},
  7670  			},
  7671  			New: &Task{
  7672  				DispatchPayload: &DispatchPayloadConfig{
  7673  					File: "bar",
  7674  				},
  7675  			},
  7676  			Expected: &TaskDiff{
  7677  				Type: DiffTypeEdited,
  7678  				Objects: []*ObjectDiff{
  7679  					{
  7680  						Type: DiffTypeEdited,
  7681  						Name: "DispatchPayload",
  7682  						Fields: []*FieldDiff{
  7683  							{
  7684  								Type: DiffTypeEdited,
  7685  								Name: "File",
  7686  								Old:  "foo",
  7687  								New:  "bar",
  7688  							},
  7689  						},
  7690  					},
  7691  				},
  7692  			},
  7693  		},
  7694  		{
  7695  			Name: "Identity added",
  7696  			Old:  &Task{},
  7697  			New: &Task{
  7698  				Identity: &WorkloadIdentity{
  7699  					Env: true,
  7700  				},
  7701  			},
  7702  			Expected: &TaskDiff{
  7703  				Type: DiffTypeEdited,
  7704  				Objects: []*ObjectDiff{
  7705  					{
  7706  						Type: DiffTypeAdded,
  7707  						Name: "Identity",
  7708  						Fields: []*FieldDiff{
  7709  							{
  7710  								Type: DiffTypeAdded,
  7711  								Name: "Env",
  7712  								Old:  "",
  7713  								New:  "true",
  7714  							},
  7715  							{
  7716  								Type: DiffTypeAdded,
  7717  								Name: "File",
  7718  								Old:  "",
  7719  								New:  "false",
  7720  							},
  7721  						},
  7722  					},
  7723  				},
  7724  			},
  7725  		},
  7726  		{
  7727  			Name: "Identity removed",
  7728  			Old: &Task{
  7729  				Identity: &WorkloadIdentity{
  7730  					Env: true,
  7731  				},
  7732  			},
  7733  			New: &Task{},
  7734  			Expected: &TaskDiff{
  7735  				Type: DiffTypeEdited,
  7736  				Objects: []*ObjectDiff{
  7737  					{
  7738  						Type: DiffTypeDeleted,
  7739  						Name: "Identity",
  7740  						Fields: []*FieldDiff{
  7741  							{
  7742  								Type: DiffTypeDeleted,
  7743  								Name: "Env",
  7744  								Old:  "true",
  7745  							},
  7746  							{
  7747  								Type: DiffTypeDeleted,
  7748  								Name: "File",
  7749  								Old:  "false",
  7750  							},
  7751  						},
  7752  					},
  7753  				},
  7754  			},
  7755  		},
  7756  		{
  7757  			Name: "Identity modified",
  7758  			Old: &Task{
  7759  				Identity: &WorkloadIdentity{
  7760  					Env: true,
  7761  				},
  7762  			},
  7763  			New: &Task{
  7764  				Identity: &WorkloadIdentity{
  7765  					Env:  true,
  7766  					File: true,
  7767  				},
  7768  			},
  7769  			Expected: &TaskDiff{
  7770  				Type: DiffTypeEdited,
  7771  				Objects: []*ObjectDiff{
  7772  					{
  7773  						Type: DiffTypeEdited,
  7774  						Name: "Identity",
  7775  						Fields: []*FieldDiff{
  7776  							{
  7777  								Type: DiffTypeEdited,
  7778  								Name: "File",
  7779  								Old:  "false",
  7780  								New:  "true",
  7781  							},
  7782  						},
  7783  					},
  7784  				},
  7785  			},
  7786  		},
  7787  	}
  7788  
  7789  	for _, c := range cases {
  7790  		t.Run(c.Name, func(t *testing.T) {
  7791  			actual, err := c.Old.Diff(c.New, c.Contextual)
  7792  			if c.Error {
  7793  				require.Error(t, err)
  7794  			} else {
  7795  				require.NoError(t, err)
  7796  				require.Equal(t, c.Expected, actual)
  7797  			}
  7798  		})
  7799  	}
  7800  }
  7801  
  7802  func TestServicesDiff(t *testing.T) {
  7803  	ci.Parallel(t)
  7804  
  7805  	cases := []struct {
  7806  		Name       string
  7807  		Old, New   []*Service
  7808  		Expected   []*ObjectDiff
  7809  		Contextual bool
  7810  	}{
  7811  		{
  7812  			Name:       "No changes - empty",
  7813  			Contextual: true,
  7814  			Old:        []*Service{},
  7815  			New:        []*Service{},
  7816  			Expected:   nil,
  7817  		},
  7818  		{
  7819  			Name:       "No changes",
  7820  			Contextual: true,
  7821  			Old: []*Service{
  7822  				{
  7823  					Name:      "webapp",
  7824  					PortLabel: "http",
  7825  				},
  7826  			},
  7827  			New: []*Service{
  7828  				{
  7829  					Name:      "webapp",
  7830  					PortLabel: "http",
  7831  				},
  7832  			},
  7833  			Expected: nil,
  7834  		},
  7835  		{
  7836  			Name:       "Detect changes",
  7837  			Contextual: true,
  7838  			Old: []*Service{
  7839  				{
  7840  					Name:              "webapp",
  7841  					PortLabel:         "http",
  7842  					AddressMode:       "host",
  7843  					Address:           "a.example.com",
  7844  					EnableTagOverride: true,
  7845  					Tags:              []string{"prod"},
  7846  					CanaryTags:        []string{"canary"},
  7847  				},
  7848  			},
  7849  			New: []*Service{
  7850  				{
  7851  					Name:              "webapp-2",
  7852  					PortLabel:         "https",
  7853  					AddressMode:       "alloc",
  7854  					Address:           "b.example.com",
  7855  					EnableTagOverride: false,
  7856  					Tags:              []string{"prod", "dev"},
  7857  					CanaryTags:        []string{"qa"},
  7858  				},
  7859  			},
  7860  			Expected: []*ObjectDiff{
  7861  				{
  7862  					Type: DiffTypeEdited,
  7863  					Name: "Service",
  7864  					Fields: []*FieldDiff{
  7865  						{
  7866  							Type: DiffTypeEdited,
  7867  							Name: "Address",
  7868  							Old:  "a.example.com",
  7869  							New:  "b.example.com",
  7870  						},
  7871  						{
  7872  							Type: DiffTypeEdited,
  7873  							Name: "AddressMode",
  7874  							Old:  "host",
  7875  							New:  "alloc",
  7876  						},
  7877  						{
  7878  							Type: DiffTypeEdited,
  7879  							Name: "EnableTagOverride",
  7880  							Old:  "true",
  7881  							New:  "false",
  7882  						},
  7883  						{
  7884  							Type: DiffTypeEdited,
  7885  							Name: "Name",
  7886  							Old:  "webapp",
  7887  							New:  "webapp-2",
  7888  						},
  7889  						{
  7890  							Type: DiffTypeNone,
  7891  							Name: "Namespace",
  7892  						},
  7893  						{
  7894  							Type: DiffTypeNone,
  7895  							Name: "OnUpdate",
  7896  						},
  7897  						{
  7898  							Type: DiffTypeEdited,
  7899  							Name: "PortLabel",
  7900  							Old:  "http",
  7901  							New:  "https",
  7902  						},
  7903  						{
  7904  							Type: DiffTypeNone,
  7905  							Name: "Provider",
  7906  						},
  7907  						{
  7908  							Type: DiffTypeNone,
  7909  							Name: "TaskName",
  7910  						},
  7911  					},
  7912  					Objects: []*ObjectDiff{
  7913  						{
  7914  							Type: DiffTypeEdited,
  7915  							Name: "CanaryTags",
  7916  							Fields: []*FieldDiff{
  7917  								{
  7918  									Type: DiffTypeAdded,
  7919  									Name: "CanaryTags",
  7920  									New:  "qa",
  7921  								},
  7922  								{
  7923  									Type: DiffTypeDeleted,
  7924  									Name: "CanaryTags",
  7925  									Old:  "canary",
  7926  								},
  7927  							},
  7928  						},
  7929  						{
  7930  							Type: DiffTypeAdded,
  7931  							Name: "Tags",
  7932  							Fields: []*FieldDiff{
  7933  								{
  7934  									Type: DiffTypeAdded,
  7935  									Name: "Tags",
  7936  									New:  "dev",
  7937  								},
  7938  								{
  7939  									Type: DiffTypeNone,
  7940  									Name: "Tags",
  7941  									Old:  "prod",
  7942  									New:  "prod",
  7943  								},
  7944  							},
  7945  						},
  7946  					},
  7947  				},
  7948  			},
  7949  		},
  7950  		{
  7951  			Name:       "Service added",
  7952  			Contextual: true,
  7953  			Old:        []*Service{},
  7954  			New: []*Service{
  7955  				{
  7956  					Name:      "webapp",
  7957  					PortLabel: "http",
  7958  				},
  7959  			},
  7960  			Expected: []*ObjectDiff{
  7961  				{
  7962  					Type: DiffTypeAdded,
  7963  					Name: "Service",
  7964  					Fields: []*FieldDiff{
  7965  						{
  7966  							Type: DiffTypeNone,
  7967  							Name: "Address",
  7968  						},
  7969  						{
  7970  							Type: DiffTypeNone,
  7971  							Name: "AddressMode",
  7972  						},
  7973  						{
  7974  							Type: DiffTypeAdded,
  7975  							Name: "EnableTagOverride",
  7976  							New:  "false",
  7977  						},
  7978  						{
  7979  							Type: DiffTypeAdded,
  7980  							Name: "Name",
  7981  							New:  "webapp",
  7982  						},
  7983  						{
  7984  							Type: DiffTypeNone,
  7985  							Name: "Namespace",
  7986  						},
  7987  						{
  7988  							Type: DiffTypeNone,
  7989  							Name: "OnUpdate",
  7990  						},
  7991  						{
  7992  							Type: DiffTypeAdded,
  7993  							Name: "PortLabel",
  7994  							New:  "http",
  7995  						},
  7996  						{
  7997  							Type: DiffTypeNone,
  7998  							Name: "Provider",
  7999  						},
  8000  						{
  8001  							Type: DiffTypeNone,
  8002  							Name: "TaskName",
  8003  						},
  8004  					},
  8005  				},
  8006  			},
  8007  		},
  8008  		{
  8009  			Name:       "Service added with same name",
  8010  			Contextual: true,
  8011  			Old: []*Service{
  8012  				{
  8013  					Name:      "webapp",
  8014  					PortLabel: "http",
  8015  				},
  8016  			},
  8017  			New: []*Service{
  8018  				{
  8019  					Name:      "webapp",
  8020  					PortLabel: "https",
  8021  				},
  8022  				{
  8023  					Name:      "webapp",
  8024  					PortLabel: "http",
  8025  				},
  8026  			},
  8027  			Expected: []*ObjectDiff{
  8028  				{
  8029  					Type: DiffTypeAdded,
  8030  					Name: "Service",
  8031  					Fields: []*FieldDiff{
  8032  						{
  8033  							Type: DiffTypeNone,
  8034  							Name: "Address",
  8035  						},
  8036  						{
  8037  							Type: DiffTypeNone,
  8038  							Name: "AddressMode",
  8039  						},
  8040  						{
  8041  							Type: DiffTypeAdded,
  8042  							Name: "EnableTagOverride",
  8043  							New:  "false",
  8044  						},
  8045  						{
  8046  							Type: DiffTypeAdded,
  8047  							Name: "Name",
  8048  							New:  "webapp",
  8049  						},
  8050  						{
  8051  							Type: DiffTypeNone,
  8052  							Name: "Namespace",
  8053  						},
  8054  						{
  8055  							Type: DiffTypeNone,
  8056  							Name: "OnUpdate",
  8057  						},
  8058  						{
  8059  							Type: DiffTypeAdded,
  8060  							Name: "PortLabel",
  8061  							New:  "https",
  8062  						},
  8063  						{
  8064  							Type: DiffTypeNone,
  8065  							Name: "Provider",
  8066  						},
  8067  						{
  8068  							Type: DiffTypeNone,
  8069  							Name: "TaskName",
  8070  						},
  8071  					},
  8072  				},
  8073  			},
  8074  		},
  8075  		{
  8076  			Name:       "Modify port label of service with same name",
  8077  			Contextual: true,
  8078  			Old: []*Service{
  8079  				{
  8080  					Name:      "webapp",
  8081  					PortLabel: "http",
  8082  				},
  8083  				{
  8084  					Name:      "webapp",
  8085  					PortLabel: "https",
  8086  				},
  8087  			},
  8088  			New: []*Service{
  8089  				{
  8090  					Name:      "webapp",
  8091  					PortLabel: "https-redirect",
  8092  				},
  8093  				{
  8094  					Name:      "webapp",
  8095  					PortLabel: "https",
  8096  				},
  8097  			},
  8098  			Expected: []*ObjectDiff{
  8099  				{
  8100  					Type: DiffTypeEdited,
  8101  					Name: "Service",
  8102  					Fields: []*FieldDiff{
  8103  						{
  8104  							Type: DiffTypeNone,
  8105  							Name: "Address",
  8106  						},
  8107  						{
  8108  							Type: DiffTypeNone,
  8109  							Name: "AddressMode",
  8110  						},
  8111  						{
  8112  							Type: DiffTypeNone,
  8113  							Name: "EnableTagOverride",
  8114  							Old:  "false",
  8115  							New:  "false",
  8116  						},
  8117  						{
  8118  							Type: DiffTypeNone,
  8119  							Name: "Name",
  8120  							Old:  "webapp",
  8121  							New:  "webapp",
  8122  						},
  8123  						{
  8124  							Type: DiffTypeNone,
  8125  							Name: "Namespace",
  8126  						},
  8127  						{
  8128  							Type: DiffTypeNone,
  8129  							Name: "OnUpdate",
  8130  						},
  8131  						{
  8132  							Type: DiffTypeEdited,
  8133  							Name: "PortLabel",
  8134  							Old:  "http",
  8135  							New:  "https-redirect",
  8136  						}, {
  8137  							Type: DiffTypeNone,
  8138  							Name: "Provider",
  8139  						},
  8140  						{
  8141  							Type: DiffTypeNone,
  8142  							Name: "TaskName",
  8143  						},
  8144  					},
  8145  				},
  8146  			},
  8147  		},
  8148  		{
  8149  			Name:       "Modify similar services",
  8150  			Contextual: true,
  8151  			Old: []*Service{
  8152  				{
  8153  					Name:      "webapp",
  8154  					PortLabel: "http",
  8155  					Tags:      []string{"prod"},
  8156  				},
  8157  				{
  8158  					Name:      "webapp",
  8159  					PortLabel: "http",
  8160  					Tags:      []string{"dev"},
  8161  				},
  8162  			},
  8163  			New: []*Service{
  8164  				{
  8165  					Name:      "webapp",
  8166  					PortLabel: "http",
  8167  					Tags:      []string{"prod", "qa"},
  8168  				},
  8169  				{
  8170  					Name:      "webapp",
  8171  					PortLabel: "http",
  8172  					Tags:      []string{"dev"},
  8173  				},
  8174  			},
  8175  			Expected: []*ObjectDiff{
  8176  				{
  8177  					Type: DiffTypeEdited,
  8178  					Name: "Service",
  8179  					Fields: []*FieldDiff{
  8180  						{
  8181  							Type: DiffTypeNone,
  8182  							Name: "Address",
  8183  						},
  8184  						{
  8185  							Type: DiffTypeNone,
  8186  							Name: "AddressMode",
  8187  						},
  8188  						{
  8189  							Type: DiffTypeNone,
  8190  							Name: "EnableTagOverride",
  8191  							Old:  "false",
  8192  							New:  "false",
  8193  						},
  8194  						{
  8195  							Type: DiffTypeNone,
  8196  							Name: "Name",
  8197  							Old:  "webapp",
  8198  							New:  "webapp",
  8199  						},
  8200  						{
  8201  							Type: DiffTypeNone,
  8202  							Name: "Namespace",
  8203  						},
  8204  						{
  8205  							Type: DiffTypeNone,
  8206  							Name: "OnUpdate",
  8207  						},
  8208  						{
  8209  							Type: DiffTypeNone,
  8210  							Name: "PortLabel",
  8211  							Old:  "http",
  8212  							New:  "http",
  8213  						},
  8214  						{
  8215  							Type: DiffTypeNone,
  8216  							Name: "Provider",
  8217  						},
  8218  						{
  8219  							Type: DiffTypeNone,
  8220  							Name: "TaskName",
  8221  						},
  8222  					},
  8223  					Objects: []*ObjectDiff{
  8224  						{
  8225  							Type: DiffTypeAdded,
  8226  							Name: "Tags",
  8227  							Fields: []*FieldDiff{
  8228  								{
  8229  									Type: DiffTypeAdded,
  8230  									Name: "Tags",
  8231  									New:  "qa",
  8232  								},
  8233  								{
  8234  									Type: DiffTypeNone,
  8235  									Name: "Tags",
  8236  									Old:  "prod",
  8237  									New:  "prod",
  8238  								},
  8239  							},
  8240  						},
  8241  					},
  8242  				},
  8243  			},
  8244  		},
  8245  		{
  8246  			Name:       "Service with different provider",
  8247  			Contextual: true,
  8248  			Old: []*Service{
  8249  				{
  8250  					Name:      "webapp",
  8251  					Provider:  "nomad",
  8252  					PortLabel: "http",
  8253  				},
  8254  			},
  8255  			New: []*Service{
  8256  				{
  8257  					Name:      "webapp",
  8258  					Provider:  "consul",
  8259  					PortLabel: "http",
  8260  				},
  8261  			},
  8262  			Expected: []*ObjectDiff{
  8263  				{
  8264  					Type: DiffTypeEdited,
  8265  					Name: "Service",
  8266  					Fields: []*FieldDiff{
  8267  						{
  8268  							Type: DiffTypeNone,
  8269  							Name: "Address",
  8270  						},
  8271  						{
  8272  							Type: DiffTypeNone,
  8273  							Name: "AddressMode",
  8274  						},
  8275  						{
  8276  							Type: DiffTypeNone,
  8277  							Name: "EnableTagOverride",
  8278  							Old:  "false",
  8279  							New:  "false",
  8280  						},
  8281  						{
  8282  							Type: DiffTypeNone,
  8283  							Name: "Name",
  8284  							Old:  "webapp",
  8285  							New:  "webapp",
  8286  						},
  8287  						{
  8288  							Type: DiffTypeNone,
  8289  							Name: "Namespace",
  8290  						},
  8291  						{
  8292  							Type: DiffTypeNone,
  8293  							Name: "OnUpdate",
  8294  						},
  8295  						{
  8296  							Type: DiffTypeNone,
  8297  							Name: "PortLabel",
  8298  							Old:  "http",
  8299  							New:  "http",
  8300  						},
  8301  						{
  8302  							Type: DiffTypeEdited,
  8303  							Name: "Provider",
  8304  							Old:  "nomad",
  8305  							New:  "consul",
  8306  						},
  8307  						{
  8308  							Type: DiffTypeNone,
  8309  							Name: "TaskName",
  8310  						},
  8311  					},
  8312  				},
  8313  			},
  8314  		},
  8315  		{
  8316  			Name:       "Service with different ingress tls",
  8317  			Contextual: false,
  8318  			Old: []*Service{
  8319  				{
  8320  					Name:      "webapp",
  8321  					Provider:  "consul",
  8322  					PortLabel: "http",
  8323  					Connect: &ConsulConnect{
  8324  						Gateway: &ConsulGateway{
  8325  							Ingress: &ConsulIngressConfigEntry{},
  8326  						},
  8327  					},
  8328  				},
  8329  			},
  8330  			New: []*Service{
  8331  				{
  8332  					Name:      "webapp",
  8333  					Provider:  "consul",
  8334  					PortLabel: "http",
  8335  					Connect: &ConsulConnect{
  8336  						Gateway: &ConsulGateway{
  8337  							Ingress: &ConsulIngressConfigEntry{
  8338  								TLS: &ConsulGatewayTLSConfig{
  8339  									Enabled:       true,
  8340  									TLSMinVersion: "TLSv1_2",
  8341  									CipherSuites:  []string{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"},
  8342  								},
  8343  							},
  8344  						},
  8345  					},
  8346  				},
  8347  			},
  8348  			Expected: []*ObjectDiff{
  8349  				{
  8350  					Type: DiffTypeEdited,
  8351  					Name: "Service",
  8352  					Objects: []*ObjectDiff{
  8353  						{
  8354  							Type: DiffTypeEdited,
  8355  							Name: "ConsulConnect",
  8356  							Objects: []*ObjectDiff{
  8357  								{
  8358  									Type: DiffTypeEdited,
  8359  									Name: "Gateway",
  8360  									Objects: []*ObjectDiff{
  8361  										{
  8362  											Type: DiffTypeEdited,
  8363  											Name: "Ingress",
  8364  											Objects: []*ObjectDiff{
  8365  												{
  8366  													Type: DiffTypeAdded,
  8367  													Name: "TLS",
  8368  													Fields: []*FieldDiff{
  8369  														{
  8370  															Type: DiffTypeAdded,
  8371  															Name: "Enabled",
  8372  															New:  "true",
  8373  														},
  8374  														{
  8375  															Type: DiffTypeAdded,
  8376  															Name: "TLSMinVersion",
  8377  															New:  "TLSv1_2",
  8378  														},
  8379  													},
  8380  													Objects: []*ObjectDiff{
  8381  														{
  8382  															Type: DiffTypeAdded,
  8383  															Name: "CipherSuites",
  8384  															Fields: []*FieldDiff{
  8385  																{
  8386  																	Type: DiffTypeAdded,
  8387  																	Name: "CipherSuites",
  8388  																	New:  "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
  8389  																},
  8390  																{
  8391  																	Type: DiffTypeAdded,
  8392  																	Name: "CipherSuites",
  8393  																	New:  "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
  8394  																},
  8395  															},
  8396  														},
  8397  													},
  8398  												},
  8399  											},
  8400  										},
  8401  									},
  8402  								},
  8403  							},
  8404  						},
  8405  					},
  8406  				},
  8407  			},
  8408  		}, {
  8409  			Name:       "SidecarService with different meta",
  8410  			Contextual: false,
  8411  			Old: []*Service{
  8412  				{
  8413  					Name:      "webapp",
  8414  					Provider:  "consul",
  8415  					PortLabel: "http",
  8416  					Connect: &ConsulConnect{
  8417  						SidecarService: &ConsulSidecarService{
  8418  							Port:  "http",
  8419  							Proxy: &ConsulProxy{},
  8420  							Meta: map[string]string{
  8421  								"foo": "qux",
  8422  							},
  8423  						},
  8424  						Gateway: &ConsulGateway{
  8425  							Ingress: &ConsulIngressConfigEntry{},
  8426  						},
  8427  					},
  8428  				},
  8429  			},
  8430  			New: []*Service{
  8431  				{
  8432  					Name:      "webapp",
  8433  					Provider:  "consul",
  8434  					PortLabel: "http",
  8435  					Connect: &ConsulConnect{
  8436  						SidecarService: &ConsulSidecarService{
  8437  							Port:  "http",
  8438  							Proxy: &ConsulProxy{},
  8439  							Meta: map[string]string{
  8440  								"foo":     "var",
  8441  								"testKey": "testValue",
  8442  							},
  8443  						},
  8444  						Gateway: &ConsulGateway{
  8445  							Ingress: &ConsulIngressConfigEntry{},
  8446  						},
  8447  					},
  8448  				},
  8449  			},
  8450  			Expected: []*ObjectDiff{
  8451  				{
  8452  					Type: DiffTypeEdited,
  8453  					Name: "Service",
  8454  					Objects: []*ObjectDiff{
  8455  						{
  8456  							Type:   "Edited",
  8457  							Name:   "ConsulConnect",
  8458  							Fields: nil,
  8459  							Objects: []*ObjectDiff{
  8460  								{
  8461  									Type: "Edited",
  8462  									Name: "SidecarService",
  8463  									Fields: []*FieldDiff{
  8464  										{
  8465  											Type:        "Edited",
  8466  											Name:        "Meta[foo]",
  8467  											Old:         "qux",
  8468  											New:         "var",
  8469  											Annotations: nil,
  8470  										},
  8471  										{
  8472  											Type:        "Added",
  8473  											Name:        "Meta[testKey]",
  8474  											Old:         "",
  8475  											New:         "testValue",
  8476  											Annotations: nil,
  8477  										},
  8478  									},
  8479  									Objects: nil,
  8480  								},
  8481  							},
  8482  						},
  8483  					},
  8484  				},
  8485  			},
  8486  		},
  8487  	}
  8488  
  8489  	for _, c := range cases {
  8490  		t.Run(c.Name, func(t *testing.T) {
  8491  			actual := serviceDiffs(c.Old, c.New, c.Contextual)
  8492  			require.Equal(t, c.Expected, actual)
  8493  		})
  8494  	}
  8495  }