github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/nomad/structs/diff_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestJobDiff(t *testing.T) {
    10  	cases := []struct {
    11  		Old, New   *Job
    12  		Expected   *JobDiff
    13  		Error      bool
    14  		Contextual bool
    15  	}{
    16  		{
    17  			Old: nil,
    18  			New: nil,
    19  			Expected: &JobDiff{
    20  				Type: DiffTypeNone,
    21  			},
    22  		},
    23  		{
    24  			// Different IDs
    25  			Old: &Job{
    26  				ID: "foo",
    27  			},
    28  			New: &Job{
    29  				ID: "bar",
    30  			},
    31  			Error: true,
    32  		},
    33  		{
    34  			// Primitive only that is the same
    35  			Old: &Job{
    36  				Region:    "foo",
    37  				ID:        "foo",
    38  				Name:      "foo",
    39  				Type:      "batch",
    40  				Priority:  10,
    41  				AllAtOnce: true,
    42  				Meta: map[string]string{
    43  					"foo": "bar",
    44  				},
    45  			},
    46  			New: &Job{
    47  				Region:    "foo",
    48  				ID:        "foo",
    49  				Name:      "foo",
    50  				Type:      "batch",
    51  				Priority:  10,
    52  				AllAtOnce: true,
    53  				Meta: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Expected: &JobDiff{
    58  				Type: DiffTypeNone,
    59  				ID:   "foo",
    60  			},
    61  		},
    62  		{
    63  			// Primitive only that is has diffs
    64  			Old: &Job{
    65  				Region:    "foo",
    66  				ID:        "foo",
    67  				Name:      "foo",
    68  				Type:      "batch",
    69  				Priority:  10,
    70  				AllAtOnce: true,
    71  				Meta: map[string]string{
    72  					"foo": "bar",
    73  				},
    74  			},
    75  			New: &Job{
    76  				Region:    "bar",
    77  				ID:        "foo",
    78  				Name:      "bar",
    79  				Type:      "system",
    80  				Priority:  100,
    81  				AllAtOnce: false,
    82  				Meta: map[string]string{
    83  					"foo": "baz",
    84  				},
    85  			},
    86  			Expected: &JobDiff{
    87  				Type: DiffTypeEdited,
    88  				ID:   "foo",
    89  				Fields: []*FieldDiff{
    90  					{
    91  						Type: DiffTypeEdited,
    92  						Name: "AllAtOnce",
    93  						Old:  "true",
    94  						New:  "false",
    95  					},
    96  					{
    97  						Type: DiffTypeEdited,
    98  						Name: "Meta[foo]",
    99  						Old:  "bar",
   100  						New:  "baz",
   101  					},
   102  					{
   103  						Type: DiffTypeEdited,
   104  						Name: "Name",
   105  						Old:  "foo",
   106  						New:  "bar",
   107  					},
   108  					{
   109  						Type: DiffTypeEdited,
   110  						Name: "Priority",
   111  						Old:  "10",
   112  						New:  "100",
   113  					},
   114  					{
   115  						Type: DiffTypeEdited,
   116  						Name: "Region",
   117  						Old:  "foo",
   118  						New:  "bar",
   119  					},
   120  					{
   121  						Type: DiffTypeEdited,
   122  						Name: "Type",
   123  						Old:  "batch",
   124  						New:  "system",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			// Primitive only deleted job
   131  			Old: &Job{
   132  				Region:    "foo",
   133  				ID:        "foo",
   134  				Name:      "foo",
   135  				Type:      "batch",
   136  				Priority:  10,
   137  				AllAtOnce: true,
   138  				Meta: map[string]string{
   139  					"foo": "bar",
   140  				},
   141  			},
   142  			New: nil,
   143  			Expected: &JobDiff{
   144  				Type: DiffTypeDeleted,
   145  				ID:   "foo",
   146  				Fields: []*FieldDiff{
   147  					{
   148  						Type: DiffTypeDeleted,
   149  						Name: "AllAtOnce",
   150  						Old:  "true",
   151  						New:  "",
   152  					},
   153  					{
   154  						Type: DiffTypeDeleted,
   155  						Name: "Meta[foo]",
   156  						Old:  "bar",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Name",
   162  						Old:  "foo",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Priority",
   168  						Old:  "10",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Region",
   174  						Old:  "foo",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Stop",
   180  						Old:  "false",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Type",
   186  						Old:  "batch",
   187  						New:  "",
   188  					},
   189  				},
   190  			},
   191  		},
   192  		{
   193  			// Primitive only added job
   194  			Old: nil,
   195  			New: &Job{
   196  				Region:    "foo",
   197  				ID:        "foo",
   198  				Name:      "foo",
   199  				Type:      "batch",
   200  				Priority:  10,
   201  				AllAtOnce: true,
   202  				Meta: map[string]string{
   203  					"foo": "bar",
   204  				},
   205  			},
   206  			Expected: &JobDiff{
   207  				Type: DiffTypeAdded,
   208  				ID:   "foo",
   209  				Fields: []*FieldDiff{
   210  					{
   211  						Type: DiffTypeAdded,
   212  						Name: "AllAtOnce",
   213  						Old:  "",
   214  						New:  "true",
   215  					},
   216  					{
   217  						Type: DiffTypeAdded,
   218  						Name: "Meta[foo]",
   219  						Old:  "",
   220  						New:  "bar",
   221  					},
   222  					{
   223  						Type: DiffTypeAdded,
   224  						Name: "Name",
   225  						Old:  "",
   226  						New:  "foo",
   227  					},
   228  					{
   229  						Type: DiffTypeAdded,
   230  						Name: "Priority",
   231  						Old:  "",
   232  						New:  "10",
   233  					},
   234  					{
   235  						Type: DiffTypeAdded,
   236  						Name: "Region",
   237  						Old:  "",
   238  						New:  "foo",
   239  					},
   240  					{
   241  						Type: DiffTypeAdded,
   242  						Name: "Stop",
   243  						Old:  "",
   244  						New:  "false",
   245  					},
   246  					{
   247  						Type: DiffTypeAdded,
   248  						Name: "Type",
   249  						Old:  "",
   250  						New:  "batch",
   251  					},
   252  				},
   253  			},
   254  		},
   255  		{
   256  			// Map diff
   257  			Old: &Job{
   258  				Meta: map[string]string{
   259  					"foo": "foo",
   260  					"bar": "bar",
   261  				},
   262  			},
   263  			New: &Job{
   264  				Meta: map[string]string{
   265  					"bar": "bar",
   266  					"baz": "baz",
   267  				},
   268  			},
   269  			Expected: &JobDiff{
   270  				Type: DiffTypeEdited,
   271  				Fields: []*FieldDiff{
   272  					{
   273  						Type: DiffTypeAdded,
   274  						Name: "Meta[baz]",
   275  						Old:  "",
   276  						New:  "baz",
   277  					},
   278  					{
   279  						Type: DiffTypeDeleted,
   280  						Name: "Meta[foo]",
   281  						Old:  "foo",
   282  						New:  "",
   283  					},
   284  				},
   285  			},
   286  		},
   287  		{
   288  			// Datacenter diff both added and removed
   289  			Old: &Job{
   290  				Datacenters: []string{"foo", "bar"},
   291  			},
   292  			New: &Job{
   293  				Datacenters: []string{"baz", "bar"},
   294  			},
   295  			Expected: &JobDiff{
   296  				Type: DiffTypeEdited,
   297  				Objects: []*ObjectDiff{
   298  					{
   299  						Type: DiffTypeEdited,
   300  						Name: "Datacenters",
   301  						Fields: []*FieldDiff{
   302  							{
   303  								Type: DiffTypeAdded,
   304  								Name: "Datacenters",
   305  								Old:  "",
   306  								New:  "baz",
   307  							},
   308  							{
   309  								Type: DiffTypeDeleted,
   310  								Name: "Datacenters",
   311  								Old:  "foo",
   312  								New:  "",
   313  							},
   314  						},
   315  					},
   316  				},
   317  			},
   318  		},
   319  		{
   320  			// Datacenter diff just added
   321  			Old: &Job{
   322  				Datacenters: []string{"foo", "bar"},
   323  			},
   324  			New: &Job{
   325  				Datacenters: []string{"foo", "bar", "baz"},
   326  			},
   327  			Expected: &JobDiff{
   328  				Type: DiffTypeEdited,
   329  				Objects: []*ObjectDiff{
   330  					{
   331  						Type: DiffTypeAdded,
   332  						Name: "Datacenters",
   333  						Fields: []*FieldDiff{
   334  							{
   335  								Type: DiffTypeAdded,
   336  								Name: "Datacenters",
   337  								Old:  "",
   338  								New:  "baz",
   339  							},
   340  						},
   341  					},
   342  				},
   343  			},
   344  		},
   345  		{
   346  			// Datacenter diff just deleted
   347  			Old: &Job{
   348  				Datacenters: []string{"foo", "bar"},
   349  			},
   350  			New: &Job{
   351  				Datacenters: []string{"foo"},
   352  			},
   353  			Expected: &JobDiff{
   354  				Type: DiffTypeEdited,
   355  				Objects: []*ObjectDiff{
   356  					{
   357  						Type: DiffTypeDeleted,
   358  						Name: "Datacenters",
   359  						Fields: []*FieldDiff{
   360  							{
   361  								Type: DiffTypeDeleted,
   362  								Name: "Datacenters",
   363  								Old:  "bar",
   364  								New:  "",
   365  							},
   366  						},
   367  					},
   368  				},
   369  			},
   370  		},
   371  		{
   372  			// Datacenter contextual no change
   373  			Contextual: true,
   374  			Old: &Job{
   375  				Datacenters: []string{"foo", "bar"},
   376  			},
   377  			New: &Job{
   378  				Datacenters: []string{"foo", "bar"},
   379  			},
   380  			Expected: &JobDiff{
   381  				Type: DiffTypeNone,
   382  			},
   383  		},
   384  		{
   385  			// Datacenter contextual
   386  			Contextual: true,
   387  			Old: &Job{
   388  				Datacenters: []string{"foo", "bar"},
   389  			},
   390  			New: &Job{
   391  				Datacenters: []string{"foo", "bar", "baz"},
   392  			},
   393  			Expected: &JobDiff{
   394  				Type: DiffTypeEdited,
   395  				Objects: []*ObjectDiff{
   396  					{
   397  						Type: DiffTypeAdded,
   398  						Name: "Datacenters",
   399  						Fields: []*FieldDiff{
   400  							{
   401  								Type: DiffTypeAdded,
   402  								Name: "Datacenters",
   403  								Old:  "",
   404  								New:  "baz",
   405  							},
   406  							{
   407  								Type: DiffTypeNone,
   408  								Name: "Datacenters",
   409  								Old:  "bar",
   410  								New:  "bar",
   411  							},
   412  							{
   413  								Type: DiffTypeNone,
   414  								Name: "Datacenters",
   415  								Old:  "foo",
   416  								New:  "foo",
   417  							},
   418  						},
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			// Periodic added
   425  			Old: &Job{},
   426  			New: &Job{
   427  				Periodic: &PeriodicConfig{
   428  					Enabled:         false,
   429  					Spec:            "*/15 * * * * *",
   430  					SpecType:        "foo",
   431  					ProhibitOverlap: false,
   432  					TimeZone:        "Europe/Minsk",
   433  				},
   434  			},
   435  			Expected: &JobDiff{
   436  				Type: DiffTypeEdited,
   437  				Objects: []*ObjectDiff{
   438  					{
   439  						Type: DiffTypeAdded,
   440  						Name: "Periodic",
   441  						Fields: []*FieldDiff{
   442  							{
   443  								Type: DiffTypeAdded,
   444  								Name: "Enabled",
   445  								Old:  "",
   446  								New:  "false",
   447  							},
   448  							{
   449  								Type: DiffTypeAdded,
   450  								Name: "ProhibitOverlap",
   451  								Old:  "",
   452  								New:  "false",
   453  							},
   454  							{
   455  								Type: DiffTypeAdded,
   456  								Name: "Spec",
   457  								Old:  "",
   458  								New:  "*/15 * * * * *",
   459  							},
   460  							{
   461  								Type: DiffTypeAdded,
   462  								Name: "SpecType",
   463  								Old:  "",
   464  								New:  "foo",
   465  							},
   466  							{
   467  								Type: DiffTypeAdded,
   468  								Name: "TimeZone",
   469  								Old:  "",
   470  								New:  "Europe/Minsk",
   471  							},
   472  						},
   473  					},
   474  				},
   475  			},
   476  		},
   477  		{
   478  			// Periodic deleted
   479  			Old: &Job{
   480  				Periodic: &PeriodicConfig{
   481  					Enabled:         false,
   482  					Spec:            "*/15 * * * * *",
   483  					SpecType:        "foo",
   484  					ProhibitOverlap: false,
   485  					TimeZone:        "Europe/Minsk",
   486  				},
   487  			},
   488  			New: &Job{},
   489  			Expected: &JobDiff{
   490  				Type: DiffTypeEdited,
   491  				Objects: []*ObjectDiff{
   492  					{
   493  						Type: DiffTypeDeleted,
   494  						Name: "Periodic",
   495  						Fields: []*FieldDiff{
   496  							{
   497  								Type: DiffTypeDeleted,
   498  								Name: "Enabled",
   499  								Old:  "false",
   500  								New:  "",
   501  							},
   502  							{
   503  								Type: DiffTypeDeleted,
   504  								Name: "ProhibitOverlap",
   505  								Old:  "false",
   506  								New:  "",
   507  							},
   508  							{
   509  								Type: DiffTypeDeleted,
   510  								Name: "Spec",
   511  								Old:  "*/15 * * * * *",
   512  								New:  "",
   513  							},
   514  							{
   515  								Type: DiffTypeDeleted,
   516  								Name: "SpecType",
   517  								Old:  "foo",
   518  								New:  "",
   519  							},
   520  							{
   521  								Type: DiffTypeDeleted,
   522  								Name: "TimeZone",
   523  								Old:  "Europe/Minsk",
   524  								New:  "",
   525  							},
   526  						},
   527  					},
   528  				},
   529  			},
   530  		},
   531  		{
   532  			// Periodic edited
   533  			Old: &Job{
   534  				Periodic: &PeriodicConfig{
   535  					Enabled:         false,
   536  					Spec:            "*/15 * * * * *",
   537  					SpecType:        "foo",
   538  					ProhibitOverlap: false,
   539  					TimeZone:        "Europe/Minsk",
   540  				},
   541  			},
   542  			New: &Job{
   543  				Periodic: &PeriodicConfig{
   544  					Enabled:         true,
   545  					Spec:            "* * * * * *",
   546  					SpecType:        "cron",
   547  					ProhibitOverlap: true,
   548  					TimeZone:        "America/Los_Angeles",
   549  				},
   550  			},
   551  			Expected: &JobDiff{
   552  				Type: DiffTypeEdited,
   553  				Objects: []*ObjectDiff{
   554  					{
   555  						Type: DiffTypeEdited,
   556  						Name: "Periodic",
   557  						Fields: []*FieldDiff{
   558  							{
   559  								Type: DiffTypeEdited,
   560  								Name: "Enabled",
   561  								Old:  "false",
   562  								New:  "true",
   563  							},
   564  							{
   565  								Type: DiffTypeEdited,
   566  								Name: "ProhibitOverlap",
   567  								Old:  "false",
   568  								New:  "true",
   569  							},
   570  							{
   571  								Type: DiffTypeEdited,
   572  								Name: "Spec",
   573  								Old:  "*/15 * * * * *",
   574  								New:  "* * * * * *",
   575  							},
   576  							{
   577  								Type: DiffTypeEdited,
   578  								Name: "SpecType",
   579  								Old:  "foo",
   580  								New:  "cron",
   581  							},
   582  							{
   583  								Type: DiffTypeEdited,
   584  								Name: "TimeZone",
   585  								Old:  "Europe/Minsk",
   586  								New:  "America/Los_Angeles",
   587  							},
   588  						},
   589  					},
   590  				},
   591  			},
   592  		},
   593  		{
   594  			// Periodic edited with context
   595  			Contextual: true,
   596  			Old: &Job{
   597  				Periodic: &PeriodicConfig{
   598  					Enabled:         false,
   599  					Spec:            "*/15 * * * * *",
   600  					SpecType:        "foo",
   601  					ProhibitOverlap: false,
   602  					TimeZone:        "Europe/Minsk",
   603  				},
   604  			},
   605  			New: &Job{
   606  				Periodic: &PeriodicConfig{
   607  					Enabled:         true,
   608  					Spec:            "* * * * * *",
   609  					SpecType:        "foo",
   610  					ProhibitOverlap: false,
   611  					TimeZone:        "Europe/Minsk",
   612  				},
   613  			},
   614  			Expected: &JobDiff{
   615  				Type: DiffTypeEdited,
   616  				Objects: []*ObjectDiff{
   617  					{
   618  						Type: DiffTypeEdited,
   619  						Name: "Periodic",
   620  						Fields: []*FieldDiff{
   621  							{
   622  								Type: DiffTypeEdited,
   623  								Name: "Enabled",
   624  								Old:  "false",
   625  								New:  "true",
   626  							},
   627  							{
   628  								Type: DiffTypeNone,
   629  								Name: "ProhibitOverlap",
   630  								Old:  "false",
   631  								New:  "false",
   632  							},
   633  							{
   634  								Type: DiffTypeEdited,
   635  								Name: "Spec",
   636  								Old:  "*/15 * * * * *",
   637  								New:  "* * * * * *",
   638  							},
   639  							{
   640  								Type: DiffTypeNone,
   641  								Name: "SpecType",
   642  								Old:  "foo",
   643  								New:  "foo",
   644  							},
   645  							{
   646  								Type: DiffTypeNone,
   647  								Name: "TimeZone",
   648  								Old:  "Europe/Minsk",
   649  								New:  "Europe/Minsk",
   650  							},
   651  						},
   652  					},
   653  				},
   654  			},
   655  		},
   656  		{
   657  			// Constraints edited
   658  			Old: &Job{
   659  				Constraints: []*Constraint{
   660  					{
   661  						LTarget: "foo",
   662  						RTarget: "foo",
   663  						Operand: "foo",
   664  						str:     "foo",
   665  					},
   666  					{
   667  						LTarget: "bar",
   668  						RTarget: "bar",
   669  						Operand: "bar",
   670  						str:     "bar",
   671  					},
   672  				},
   673  			},
   674  			New: &Job{
   675  				Constraints: []*Constraint{
   676  					{
   677  						LTarget: "foo",
   678  						RTarget: "foo",
   679  						Operand: "foo",
   680  						str:     "foo",
   681  					},
   682  					{
   683  						LTarget: "baz",
   684  						RTarget: "baz",
   685  						Operand: "baz",
   686  						str:     "baz",
   687  					},
   688  				},
   689  			},
   690  			Expected: &JobDiff{
   691  				Type: DiffTypeEdited,
   692  				Objects: []*ObjectDiff{
   693  					{
   694  						Type: DiffTypeAdded,
   695  						Name: "Constraint",
   696  						Fields: []*FieldDiff{
   697  							{
   698  								Type: DiffTypeAdded,
   699  								Name: "LTarget",
   700  								Old:  "",
   701  								New:  "baz",
   702  							},
   703  							{
   704  								Type: DiffTypeAdded,
   705  								Name: "Operand",
   706  								Old:  "",
   707  								New:  "baz",
   708  							},
   709  							{
   710  								Type: DiffTypeAdded,
   711  								Name: "RTarget",
   712  								Old:  "",
   713  								New:  "baz",
   714  							},
   715  						},
   716  					},
   717  					{
   718  						Type: DiffTypeDeleted,
   719  						Name: "Constraint",
   720  						Fields: []*FieldDiff{
   721  							{
   722  								Type: DiffTypeDeleted,
   723  								Name: "LTarget",
   724  								Old:  "bar",
   725  								New:  "",
   726  							},
   727  							{
   728  								Type: DiffTypeDeleted,
   729  								Name: "Operand",
   730  								Old:  "bar",
   731  								New:  "",
   732  							},
   733  							{
   734  								Type: DiffTypeDeleted,
   735  								Name: "RTarget",
   736  								Old:  "bar",
   737  								New:  "",
   738  							},
   739  						},
   740  					},
   741  				},
   742  			},
   743  		},
   744  		{
   745  			// Task groups edited
   746  			Old: &Job{
   747  				TaskGroups: []*TaskGroup{
   748  					{
   749  						Name:  "foo",
   750  						Count: 1,
   751  					},
   752  					{
   753  						Name:  "bar",
   754  						Count: 1,
   755  					},
   756  					{
   757  						Name:  "baz",
   758  						Count: 1,
   759  					},
   760  				},
   761  			},
   762  			New: &Job{
   763  				TaskGroups: []*TaskGroup{
   764  					{
   765  						Name:  "bar",
   766  						Count: 1,
   767  					},
   768  					{
   769  						Name:  "baz",
   770  						Count: 2,
   771  					},
   772  					{
   773  						Name:  "bam",
   774  						Count: 1,
   775  					},
   776  				},
   777  			},
   778  			Expected: &JobDiff{
   779  				Type: DiffTypeEdited,
   780  				TaskGroups: []*TaskGroupDiff{
   781  					{
   782  						Type: DiffTypeAdded,
   783  						Name: "bam",
   784  						Fields: []*FieldDiff{
   785  							{
   786  								Type: DiffTypeAdded,
   787  								Name: "Count",
   788  								Old:  "",
   789  								New:  "1",
   790  							},
   791  						},
   792  					},
   793  					{
   794  						Type: DiffTypeNone,
   795  						Name: "bar",
   796  					},
   797  					{
   798  						Type: DiffTypeEdited,
   799  						Name: "baz",
   800  						Fields: []*FieldDiff{
   801  							{
   802  								Type: DiffTypeEdited,
   803  								Name: "Count",
   804  								Old:  "1",
   805  								New:  "2",
   806  							},
   807  						},
   808  					},
   809  					{
   810  						Type: DiffTypeDeleted,
   811  						Name: "foo",
   812  						Fields: []*FieldDiff{
   813  							{
   814  								Type: DiffTypeDeleted,
   815  								Name: "Count",
   816  								Old:  "1",
   817  								New:  "",
   818  							},
   819  						},
   820  					},
   821  				},
   822  			},
   823  		},
   824  		{
   825  			// Parameterized Job added
   826  			Old: &Job{},
   827  			New: &Job{
   828  				ParameterizedJob: &ParameterizedJobConfig{
   829  					Payload:      DispatchPayloadRequired,
   830  					MetaOptional: []string{"foo"},
   831  					MetaRequired: []string{"bar"},
   832  				},
   833  			},
   834  			Expected: &JobDiff{
   835  				Type: DiffTypeEdited,
   836  				Objects: []*ObjectDiff{
   837  					{
   838  						Type: DiffTypeAdded,
   839  						Name: "ParameterizedJob",
   840  						Fields: []*FieldDiff{
   841  							{
   842  								Type: DiffTypeAdded,
   843  								Name: "Payload",
   844  								Old:  "",
   845  								New:  DispatchPayloadRequired,
   846  							},
   847  						},
   848  						Objects: []*ObjectDiff{
   849  							{
   850  								Type: DiffTypeAdded,
   851  								Name: "MetaOptional",
   852  								Fields: []*FieldDiff{
   853  									{
   854  										Type: DiffTypeAdded,
   855  										Name: "MetaOptional",
   856  										Old:  "",
   857  										New:  "foo",
   858  									},
   859  								},
   860  							},
   861  							{
   862  								Type: DiffTypeAdded,
   863  								Name: "MetaRequired",
   864  								Fields: []*FieldDiff{
   865  									{
   866  										Type: DiffTypeAdded,
   867  										Name: "MetaRequired",
   868  										Old:  "",
   869  										New:  "bar",
   870  									},
   871  								},
   872  							},
   873  						},
   874  					},
   875  				},
   876  			},
   877  		},
   878  		{
   879  			// Parameterized Job deleted
   880  			Old: &Job{
   881  				ParameterizedJob: &ParameterizedJobConfig{
   882  					Payload:      DispatchPayloadRequired,
   883  					MetaOptional: []string{"foo"},
   884  					MetaRequired: []string{"bar"},
   885  				},
   886  			},
   887  			New: &Job{},
   888  			Expected: &JobDiff{
   889  				Type: DiffTypeEdited,
   890  				Objects: []*ObjectDiff{
   891  					{
   892  						Type: DiffTypeDeleted,
   893  						Name: "ParameterizedJob",
   894  						Fields: []*FieldDiff{
   895  							{
   896  								Type: DiffTypeDeleted,
   897  								Name: "Payload",
   898  								Old:  DispatchPayloadRequired,
   899  								New:  "",
   900  							},
   901  						},
   902  						Objects: []*ObjectDiff{
   903  							{
   904  								Type: DiffTypeDeleted,
   905  								Name: "MetaOptional",
   906  								Fields: []*FieldDiff{
   907  									{
   908  										Type: DiffTypeDeleted,
   909  										Name: "MetaOptional",
   910  										Old:  "foo",
   911  										New:  "",
   912  									},
   913  								},
   914  							},
   915  							{
   916  								Type: DiffTypeDeleted,
   917  								Name: "MetaRequired",
   918  								Fields: []*FieldDiff{
   919  									{
   920  										Type: DiffTypeDeleted,
   921  										Name: "MetaRequired",
   922  										Old:  "bar",
   923  										New:  "",
   924  									},
   925  								},
   926  							},
   927  						},
   928  					},
   929  				},
   930  			},
   931  		},
   932  		{
   933  			// Parameterized Job edited
   934  			Old: &Job{
   935  				ParameterizedJob: &ParameterizedJobConfig{
   936  					Payload:      DispatchPayloadRequired,
   937  					MetaOptional: []string{"foo"},
   938  					MetaRequired: []string{"bar"},
   939  				},
   940  			},
   941  			New: &Job{
   942  				ParameterizedJob: &ParameterizedJobConfig{
   943  					Payload:      DispatchPayloadOptional,
   944  					MetaOptional: []string{"bam"},
   945  					MetaRequired: []string{"bang"},
   946  				},
   947  			},
   948  			Expected: &JobDiff{
   949  				Type: DiffTypeEdited,
   950  				Objects: []*ObjectDiff{
   951  					{
   952  						Type: DiffTypeEdited,
   953  						Name: "ParameterizedJob",
   954  						Fields: []*FieldDiff{
   955  							{
   956  								Type: DiffTypeEdited,
   957  								Name: "Payload",
   958  								Old:  DispatchPayloadRequired,
   959  								New:  DispatchPayloadOptional,
   960  							},
   961  						},
   962  						Objects: []*ObjectDiff{
   963  							{
   964  								Type: DiffTypeEdited,
   965  								Name: "MetaOptional",
   966  								Fields: []*FieldDiff{
   967  									{
   968  										Type: DiffTypeAdded,
   969  										Name: "MetaOptional",
   970  										Old:  "",
   971  										New:  "bam",
   972  									},
   973  									{
   974  										Type: DiffTypeDeleted,
   975  										Name: "MetaOptional",
   976  										Old:  "foo",
   977  										New:  "",
   978  									},
   979  								},
   980  							},
   981  							{
   982  								Type: DiffTypeEdited,
   983  								Name: "MetaRequired",
   984  								Fields: []*FieldDiff{
   985  									{
   986  										Type: DiffTypeAdded,
   987  										Name: "MetaRequired",
   988  										Old:  "",
   989  										New:  "bang",
   990  									},
   991  									{
   992  										Type: DiffTypeDeleted,
   993  										Name: "MetaRequired",
   994  										Old:  "bar",
   995  										New:  "",
   996  									},
   997  								},
   998  							},
   999  						},
  1000  					},
  1001  				},
  1002  			},
  1003  		},
  1004  		{
  1005  			// Parameterized Job edited with context
  1006  			Contextual: true,
  1007  			Old: &Job{
  1008  				ParameterizedJob: &ParameterizedJobConfig{
  1009  					Payload:      DispatchPayloadRequired,
  1010  					MetaOptional: []string{"foo"},
  1011  					MetaRequired: []string{"bar"},
  1012  				},
  1013  			},
  1014  			New: &Job{
  1015  				ParameterizedJob: &ParameterizedJobConfig{
  1016  					Payload:      DispatchPayloadOptional,
  1017  					MetaOptional: []string{"foo"},
  1018  					MetaRequired: []string{"bar"},
  1019  				},
  1020  			},
  1021  			Expected: &JobDiff{
  1022  				Type: DiffTypeEdited,
  1023  				Objects: []*ObjectDiff{
  1024  					{
  1025  						Type: DiffTypeEdited,
  1026  						Name: "ParameterizedJob",
  1027  						Fields: []*FieldDiff{
  1028  							{
  1029  								Type: DiffTypeEdited,
  1030  								Name: "Payload",
  1031  								Old:  DispatchPayloadRequired,
  1032  								New:  DispatchPayloadOptional,
  1033  							},
  1034  						},
  1035  						Objects: []*ObjectDiff{
  1036  							{
  1037  								Type: DiffTypeNone,
  1038  								Name: "MetaOptional",
  1039  								Fields: []*FieldDiff{
  1040  									{
  1041  										Type: DiffTypeNone,
  1042  										Name: "MetaOptional",
  1043  										Old:  "foo",
  1044  										New:  "foo",
  1045  									},
  1046  								},
  1047  							},
  1048  							{
  1049  								Type: DiffTypeNone,
  1050  								Name: "MetaRequired",
  1051  								Fields: []*FieldDiff{
  1052  									{
  1053  										Type: DiffTypeNone,
  1054  										Name: "MetaRequired",
  1055  										Old:  "bar",
  1056  										New:  "bar",
  1057  									},
  1058  								},
  1059  							},
  1060  						},
  1061  					},
  1062  				},
  1063  			},
  1064  		},
  1065  	}
  1066  
  1067  	for i, c := range cases {
  1068  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1069  		if c.Error && err == nil {
  1070  			t.Fatalf("case %d: expected errored", i+1)
  1071  		} else if err != nil {
  1072  			if !c.Error {
  1073  				t.Fatalf("case %d: errored %#v", i+1, err)
  1074  			} else {
  1075  				continue
  1076  			}
  1077  		}
  1078  
  1079  		if !reflect.DeepEqual(actual, c.Expected) {
  1080  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1081  				i+1, actual, c.Expected)
  1082  		}
  1083  	}
  1084  }
  1085  
  1086  func TestTaskGroupDiff(t *testing.T) {
  1087  	cases := []struct {
  1088  		Old, New   *TaskGroup
  1089  		Expected   *TaskGroupDiff
  1090  		Error      bool
  1091  		Contextual bool
  1092  	}{
  1093  		{
  1094  			Old: nil,
  1095  			New: nil,
  1096  			Expected: &TaskGroupDiff{
  1097  				Type: DiffTypeNone,
  1098  			},
  1099  		},
  1100  		{
  1101  			// Primitive only that has different names
  1102  			Old: &TaskGroup{
  1103  				Name:  "foo",
  1104  				Count: 10,
  1105  				Meta: map[string]string{
  1106  					"foo": "bar",
  1107  				},
  1108  			},
  1109  			New: &TaskGroup{
  1110  				Name:  "bar",
  1111  				Count: 10,
  1112  				Meta: map[string]string{
  1113  					"foo": "bar",
  1114  				},
  1115  			},
  1116  			Error: true,
  1117  		},
  1118  		{
  1119  			// Primitive only that is the same
  1120  			Old: &TaskGroup{
  1121  				Name:  "foo",
  1122  				Count: 10,
  1123  				Meta: map[string]string{
  1124  					"foo": "bar",
  1125  				},
  1126  			},
  1127  			New: &TaskGroup{
  1128  				Name:  "foo",
  1129  				Count: 10,
  1130  				Meta: map[string]string{
  1131  					"foo": "bar",
  1132  				},
  1133  			},
  1134  			Expected: &TaskGroupDiff{
  1135  				Type: DiffTypeNone,
  1136  				Name: "foo",
  1137  			},
  1138  		},
  1139  		{
  1140  			// Primitive only that has diffs
  1141  			Old: &TaskGroup{
  1142  				Name:  "foo",
  1143  				Count: 10,
  1144  				Meta: map[string]string{
  1145  					"foo": "bar",
  1146  				},
  1147  			},
  1148  			New: &TaskGroup{
  1149  				Name:  "foo",
  1150  				Count: 100,
  1151  				Meta: map[string]string{
  1152  					"foo": "baz",
  1153  				},
  1154  			},
  1155  			Expected: &TaskGroupDiff{
  1156  				Type: DiffTypeEdited,
  1157  				Name: "foo",
  1158  				Fields: []*FieldDiff{
  1159  					{
  1160  						Type: DiffTypeEdited,
  1161  						Name: "Count",
  1162  						Old:  "10",
  1163  						New:  "100",
  1164  					},
  1165  					{
  1166  						Type: DiffTypeEdited,
  1167  						Name: "Meta[foo]",
  1168  						Old:  "bar",
  1169  						New:  "baz",
  1170  					},
  1171  				},
  1172  			},
  1173  		},
  1174  		{
  1175  			// Map diff
  1176  			Old: &TaskGroup{
  1177  				Meta: map[string]string{
  1178  					"foo": "foo",
  1179  					"bar": "bar",
  1180  				},
  1181  			},
  1182  			New: &TaskGroup{
  1183  				Meta: map[string]string{
  1184  					"bar": "bar",
  1185  					"baz": "baz",
  1186  				},
  1187  			},
  1188  			Expected: &TaskGroupDiff{
  1189  				Type: DiffTypeEdited,
  1190  				Fields: []*FieldDiff{
  1191  					{
  1192  						Type: DiffTypeAdded,
  1193  						Name: "Meta[baz]",
  1194  						Old:  "",
  1195  						New:  "baz",
  1196  					},
  1197  					{
  1198  						Type: DiffTypeDeleted,
  1199  						Name: "Meta[foo]",
  1200  						Old:  "foo",
  1201  						New:  "",
  1202  					},
  1203  				},
  1204  			},
  1205  		},
  1206  		{
  1207  			// Constraints edited
  1208  			Old: &TaskGroup{
  1209  				Constraints: []*Constraint{
  1210  					{
  1211  						LTarget: "foo",
  1212  						RTarget: "foo",
  1213  						Operand: "foo",
  1214  						str:     "foo",
  1215  					},
  1216  					{
  1217  						LTarget: "bar",
  1218  						RTarget: "bar",
  1219  						Operand: "bar",
  1220  						str:     "bar",
  1221  					},
  1222  				},
  1223  			},
  1224  			New: &TaskGroup{
  1225  				Constraints: []*Constraint{
  1226  					{
  1227  						LTarget: "foo",
  1228  						RTarget: "foo",
  1229  						Operand: "foo",
  1230  						str:     "foo",
  1231  					},
  1232  					{
  1233  						LTarget: "baz",
  1234  						RTarget: "baz",
  1235  						Operand: "baz",
  1236  						str:     "baz",
  1237  					},
  1238  				},
  1239  			},
  1240  			Expected: &TaskGroupDiff{
  1241  				Type: DiffTypeEdited,
  1242  				Objects: []*ObjectDiff{
  1243  					{
  1244  						Type: DiffTypeAdded,
  1245  						Name: "Constraint",
  1246  						Fields: []*FieldDiff{
  1247  							{
  1248  								Type: DiffTypeAdded,
  1249  								Name: "LTarget",
  1250  								Old:  "",
  1251  								New:  "baz",
  1252  							},
  1253  							{
  1254  								Type: DiffTypeAdded,
  1255  								Name: "Operand",
  1256  								Old:  "",
  1257  								New:  "baz",
  1258  							},
  1259  							{
  1260  								Type: DiffTypeAdded,
  1261  								Name: "RTarget",
  1262  								Old:  "",
  1263  								New:  "baz",
  1264  							},
  1265  						},
  1266  					},
  1267  					{
  1268  						Type: DiffTypeDeleted,
  1269  						Name: "Constraint",
  1270  						Fields: []*FieldDiff{
  1271  							{
  1272  								Type: DiffTypeDeleted,
  1273  								Name: "LTarget",
  1274  								Old:  "bar",
  1275  								New:  "",
  1276  							},
  1277  							{
  1278  								Type: DiffTypeDeleted,
  1279  								Name: "Operand",
  1280  								Old:  "bar",
  1281  								New:  "",
  1282  							},
  1283  							{
  1284  								Type: DiffTypeDeleted,
  1285  								Name: "RTarget",
  1286  								Old:  "bar",
  1287  								New:  "",
  1288  							},
  1289  						},
  1290  					},
  1291  				},
  1292  			},
  1293  		},
  1294  		{
  1295  			// RestartPolicy added
  1296  			Old: &TaskGroup{},
  1297  			New: &TaskGroup{
  1298  				RestartPolicy: &RestartPolicy{
  1299  					Attempts: 1,
  1300  					Interval: 1 * time.Second,
  1301  					Delay:    1 * time.Second,
  1302  					Mode:     "fail",
  1303  				},
  1304  			},
  1305  			Expected: &TaskGroupDiff{
  1306  				Type: DiffTypeEdited,
  1307  				Objects: []*ObjectDiff{
  1308  					{
  1309  						Type: DiffTypeAdded,
  1310  						Name: "RestartPolicy",
  1311  						Fields: []*FieldDiff{
  1312  							{
  1313  								Type: DiffTypeAdded,
  1314  								Name: "Attempts",
  1315  								Old:  "",
  1316  								New:  "1",
  1317  							},
  1318  							{
  1319  								Type: DiffTypeAdded,
  1320  								Name: "Delay",
  1321  								Old:  "",
  1322  								New:  "1000000000",
  1323  							},
  1324  							{
  1325  								Type: DiffTypeAdded,
  1326  								Name: "Interval",
  1327  								Old:  "",
  1328  								New:  "1000000000",
  1329  							},
  1330  							{
  1331  								Type: DiffTypeAdded,
  1332  								Name: "Mode",
  1333  								Old:  "",
  1334  								New:  "fail",
  1335  							},
  1336  						},
  1337  					},
  1338  				},
  1339  			},
  1340  		},
  1341  		{
  1342  			// RestartPolicy deleted
  1343  			Old: &TaskGroup{
  1344  				RestartPolicy: &RestartPolicy{
  1345  					Attempts: 1,
  1346  					Interval: 1 * time.Second,
  1347  					Delay:    1 * time.Second,
  1348  					Mode:     "fail",
  1349  				},
  1350  			},
  1351  			New: &TaskGroup{},
  1352  			Expected: &TaskGroupDiff{
  1353  				Type: DiffTypeEdited,
  1354  				Objects: []*ObjectDiff{
  1355  					{
  1356  						Type: DiffTypeDeleted,
  1357  						Name: "RestartPolicy",
  1358  						Fields: []*FieldDiff{
  1359  							{
  1360  								Type: DiffTypeDeleted,
  1361  								Name: "Attempts",
  1362  								Old:  "1",
  1363  								New:  "",
  1364  							},
  1365  							{
  1366  								Type: DiffTypeDeleted,
  1367  								Name: "Delay",
  1368  								Old:  "1000000000",
  1369  								New:  "",
  1370  							},
  1371  							{
  1372  								Type: DiffTypeDeleted,
  1373  								Name: "Interval",
  1374  								Old:  "1000000000",
  1375  								New:  "",
  1376  							},
  1377  							{
  1378  								Type: DiffTypeDeleted,
  1379  								Name: "Mode",
  1380  								Old:  "fail",
  1381  								New:  "",
  1382  							},
  1383  						},
  1384  					},
  1385  				},
  1386  			},
  1387  		},
  1388  		{
  1389  			// RestartPolicy edited
  1390  			Old: &TaskGroup{
  1391  				RestartPolicy: &RestartPolicy{
  1392  					Attempts: 1,
  1393  					Interval: 1 * time.Second,
  1394  					Delay:    1 * time.Second,
  1395  					Mode:     "fail",
  1396  				},
  1397  			},
  1398  			New: &TaskGroup{
  1399  				RestartPolicy: &RestartPolicy{
  1400  					Attempts: 2,
  1401  					Interval: 2 * time.Second,
  1402  					Delay:    2 * time.Second,
  1403  					Mode:     "delay",
  1404  				},
  1405  			},
  1406  			Expected: &TaskGroupDiff{
  1407  				Type: DiffTypeEdited,
  1408  				Objects: []*ObjectDiff{
  1409  					{
  1410  						Type: DiffTypeEdited,
  1411  						Name: "RestartPolicy",
  1412  						Fields: []*FieldDiff{
  1413  							{
  1414  								Type: DiffTypeEdited,
  1415  								Name: "Attempts",
  1416  								Old:  "1",
  1417  								New:  "2",
  1418  							},
  1419  							{
  1420  								Type: DiffTypeEdited,
  1421  								Name: "Delay",
  1422  								Old:  "1000000000",
  1423  								New:  "2000000000",
  1424  							},
  1425  							{
  1426  								Type: DiffTypeEdited,
  1427  								Name: "Interval",
  1428  								Old:  "1000000000",
  1429  								New:  "2000000000",
  1430  							},
  1431  							{
  1432  								Type: DiffTypeEdited,
  1433  								Name: "Mode",
  1434  								Old:  "fail",
  1435  								New:  "delay",
  1436  							},
  1437  						},
  1438  					},
  1439  				},
  1440  			},
  1441  		},
  1442  		{
  1443  			// RestartPolicy edited with context
  1444  			Contextual: true,
  1445  			Old: &TaskGroup{
  1446  				RestartPolicy: &RestartPolicy{
  1447  					Attempts: 1,
  1448  					Interval: 1 * time.Second,
  1449  					Delay:    1 * time.Second,
  1450  					Mode:     "fail",
  1451  				},
  1452  			},
  1453  			New: &TaskGroup{
  1454  				RestartPolicy: &RestartPolicy{
  1455  					Attempts: 2,
  1456  					Interval: 2 * time.Second,
  1457  					Delay:    1 * time.Second,
  1458  					Mode:     "fail",
  1459  				},
  1460  			},
  1461  			Expected: &TaskGroupDiff{
  1462  				Type: DiffTypeEdited,
  1463  				Objects: []*ObjectDiff{
  1464  					{
  1465  						Type: DiffTypeEdited,
  1466  						Name: "RestartPolicy",
  1467  						Fields: []*FieldDiff{
  1468  							{
  1469  								Type: DiffTypeEdited,
  1470  								Name: "Attempts",
  1471  								Old:  "1",
  1472  								New:  "2",
  1473  							},
  1474  							{
  1475  								Type: DiffTypeNone,
  1476  								Name: "Delay",
  1477  								Old:  "1000000000",
  1478  								New:  "1000000000",
  1479  							},
  1480  							{
  1481  								Type: DiffTypeEdited,
  1482  								Name: "Interval",
  1483  								Old:  "1000000000",
  1484  								New:  "2000000000",
  1485  							},
  1486  							{
  1487  								Type: DiffTypeNone,
  1488  								Name: "Mode",
  1489  								Old:  "fail",
  1490  								New:  "fail",
  1491  							},
  1492  						},
  1493  					},
  1494  				},
  1495  			},
  1496  		},
  1497  		{
  1498  			// Update strategy deleted
  1499  			Old: &TaskGroup{
  1500  				Update: &UpdateStrategy{
  1501  					AutoRevert: true,
  1502  				},
  1503  			},
  1504  			New: &TaskGroup{},
  1505  			Expected: &TaskGroupDiff{
  1506  				Type: DiffTypeEdited,
  1507  				Objects: []*ObjectDiff{
  1508  					{
  1509  						Type: DiffTypeDeleted,
  1510  						Name: "Update",
  1511  						Fields: []*FieldDiff{
  1512  							{
  1513  								Type: DiffTypeDeleted,
  1514  								Name: "AutoRevert",
  1515  								Old:  "true",
  1516  								New:  "",
  1517  							},
  1518  							{
  1519  								Type: DiffTypeDeleted,
  1520  								Name: "Canary",
  1521  								Old:  "0",
  1522  								New:  "",
  1523  							},
  1524  							{
  1525  								Type: DiffTypeDeleted,
  1526  								Name: "HealthyDeadline",
  1527  								Old:  "0",
  1528  								New:  "",
  1529  							},
  1530  							{
  1531  								Type: DiffTypeDeleted,
  1532  								Name: "MaxParallel",
  1533  								Old:  "0",
  1534  								New:  "",
  1535  							},
  1536  							{
  1537  								Type: DiffTypeDeleted,
  1538  								Name: "MinHealthyTime",
  1539  								Old:  "0",
  1540  								New:  "",
  1541  							},
  1542  						},
  1543  					},
  1544  				},
  1545  			},
  1546  		},
  1547  		{
  1548  			// Update strategy added
  1549  			Old: &TaskGroup{},
  1550  			New: &TaskGroup{
  1551  				Update: &UpdateStrategy{
  1552  					AutoRevert: true,
  1553  				},
  1554  			},
  1555  			Expected: &TaskGroupDiff{
  1556  				Type: DiffTypeEdited,
  1557  				Objects: []*ObjectDiff{
  1558  					{
  1559  						Type: DiffTypeAdded,
  1560  						Name: "Update",
  1561  						Fields: []*FieldDiff{
  1562  							{
  1563  								Type: DiffTypeAdded,
  1564  								Name: "AutoRevert",
  1565  								Old:  "",
  1566  								New:  "true",
  1567  							},
  1568  							{
  1569  								Type: DiffTypeAdded,
  1570  								Name: "Canary",
  1571  								Old:  "",
  1572  								New:  "0",
  1573  							},
  1574  							{
  1575  								Type: DiffTypeAdded,
  1576  								Name: "HealthyDeadline",
  1577  								Old:  "",
  1578  								New:  "0",
  1579  							},
  1580  							{
  1581  								Type: DiffTypeAdded,
  1582  								Name: "MaxParallel",
  1583  								Old:  "",
  1584  								New:  "0",
  1585  							},
  1586  							{
  1587  								Type: DiffTypeAdded,
  1588  								Name: "MinHealthyTime",
  1589  								Old:  "",
  1590  								New:  "0",
  1591  							},
  1592  						},
  1593  					},
  1594  				},
  1595  			},
  1596  		},
  1597  		{
  1598  			// Update strategy edited
  1599  			Old: &TaskGroup{
  1600  				Update: &UpdateStrategy{
  1601  					MaxParallel:     5,
  1602  					HealthCheck:     "foo",
  1603  					MinHealthyTime:  1 * time.Second,
  1604  					HealthyDeadline: 30 * time.Second,
  1605  					AutoRevert:      true,
  1606  					Canary:          2,
  1607  				},
  1608  			},
  1609  			New: &TaskGroup{
  1610  				Update: &UpdateStrategy{
  1611  					MaxParallel:     7,
  1612  					HealthCheck:     "bar",
  1613  					MinHealthyTime:  2 * time.Second,
  1614  					HealthyDeadline: 31 * time.Second,
  1615  					AutoRevert:      false,
  1616  					Canary:          1,
  1617  				},
  1618  			},
  1619  			Expected: &TaskGroupDiff{
  1620  				Type: DiffTypeEdited,
  1621  				Objects: []*ObjectDiff{
  1622  					{
  1623  						Type: DiffTypeEdited,
  1624  						Name: "Update",
  1625  						Fields: []*FieldDiff{
  1626  							{
  1627  								Type: DiffTypeEdited,
  1628  								Name: "AutoRevert",
  1629  								Old:  "true",
  1630  								New:  "false",
  1631  							},
  1632  							{
  1633  								Type: DiffTypeEdited,
  1634  								Name: "Canary",
  1635  								Old:  "2",
  1636  								New:  "1",
  1637  							},
  1638  							{
  1639  								Type: DiffTypeEdited,
  1640  								Name: "HealthCheck",
  1641  								Old:  "foo",
  1642  								New:  "bar",
  1643  							},
  1644  							{
  1645  								Type: DiffTypeEdited,
  1646  								Name: "HealthyDeadline",
  1647  								Old:  "30000000000",
  1648  								New:  "31000000000",
  1649  							},
  1650  							{
  1651  								Type: DiffTypeEdited,
  1652  								Name: "MaxParallel",
  1653  								Old:  "5",
  1654  								New:  "7",
  1655  							},
  1656  							{
  1657  								Type: DiffTypeEdited,
  1658  								Name: "MinHealthyTime",
  1659  								Old:  "1000000000",
  1660  								New:  "2000000000",
  1661  							},
  1662  						},
  1663  					},
  1664  				},
  1665  			},
  1666  		},
  1667  		{
  1668  			// Update strategy edited with context
  1669  			Contextual: true,
  1670  			Old: &TaskGroup{
  1671  				Update: &UpdateStrategy{
  1672  					MaxParallel:     5,
  1673  					HealthCheck:     "foo",
  1674  					MinHealthyTime:  1 * time.Second,
  1675  					HealthyDeadline: 30 * time.Second,
  1676  					AutoRevert:      true,
  1677  					Canary:          2,
  1678  				},
  1679  			},
  1680  			New: &TaskGroup{
  1681  				Update: &UpdateStrategy{
  1682  					MaxParallel:     7,
  1683  					HealthCheck:     "foo",
  1684  					MinHealthyTime:  1 * time.Second,
  1685  					HealthyDeadline: 30 * time.Second,
  1686  					AutoRevert:      true,
  1687  					Canary:          2,
  1688  				},
  1689  			},
  1690  			Expected: &TaskGroupDiff{
  1691  				Type: DiffTypeEdited,
  1692  				Objects: []*ObjectDiff{
  1693  					{
  1694  						Type: DiffTypeEdited,
  1695  						Name: "Update",
  1696  						Fields: []*FieldDiff{
  1697  							{
  1698  								Type: DiffTypeNone,
  1699  								Name: "AutoRevert",
  1700  								Old:  "true",
  1701  								New:  "true",
  1702  							},
  1703  							{
  1704  								Type: DiffTypeNone,
  1705  								Name: "Canary",
  1706  								Old:  "2",
  1707  								New:  "2",
  1708  							},
  1709  							{
  1710  								Type: DiffTypeNone,
  1711  								Name: "HealthCheck",
  1712  								Old:  "foo",
  1713  								New:  "foo",
  1714  							},
  1715  							{
  1716  								Type: DiffTypeNone,
  1717  								Name: "HealthyDeadline",
  1718  								Old:  "30000000000",
  1719  								New:  "30000000000",
  1720  							},
  1721  							{
  1722  								Type: DiffTypeEdited,
  1723  								Name: "MaxParallel",
  1724  								Old:  "5",
  1725  								New:  "7",
  1726  							},
  1727  							{
  1728  								Type: DiffTypeNone,
  1729  								Name: "MinHealthyTime",
  1730  								Old:  "1000000000",
  1731  								New:  "1000000000",
  1732  							},
  1733  						},
  1734  					},
  1735  				},
  1736  			},
  1737  		},
  1738  		{
  1739  			// EphemeralDisk added
  1740  			Old: &TaskGroup{},
  1741  			New: &TaskGroup{
  1742  				EphemeralDisk: &EphemeralDisk{
  1743  					Migrate: true,
  1744  					Sticky:  true,
  1745  					SizeMB:  100,
  1746  				},
  1747  			},
  1748  			Expected: &TaskGroupDiff{
  1749  				Type: DiffTypeEdited,
  1750  				Objects: []*ObjectDiff{
  1751  					{
  1752  						Type: DiffTypeAdded,
  1753  						Name: "EphemeralDisk",
  1754  						Fields: []*FieldDiff{
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Migrate",
  1758  								Old:  "",
  1759  								New:  "true",
  1760  							},
  1761  							{
  1762  								Type: DiffTypeAdded,
  1763  								Name: "SizeMB",
  1764  								Old:  "",
  1765  								New:  "100",
  1766  							},
  1767  							{
  1768  								Type: DiffTypeAdded,
  1769  								Name: "Sticky",
  1770  								Old:  "",
  1771  								New:  "true",
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			// EphemeralDisk deleted
  1780  			Old: &TaskGroup{
  1781  				EphemeralDisk: &EphemeralDisk{
  1782  					Migrate: true,
  1783  					Sticky:  true,
  1784  					SizeMB:  100,
  1785  				},
  1786  			},
  1787  			New: &TaskGroup{},
  1788  			Expected: &TaskGroupDiff{
  1789  				Type: DiffTypeEdited,
  1790  				Objects: []*ObjectDiff{
  1791  					{
  1792  						Type: DiffTypeDeleted,
  1793  						Name: "EphemeralDisk",
  1794  						Fields: []*FieldDiff{
  1795  							{
  1796  								Type: DiffTypeDeleted,
  1797  								Name: "Migrate",
  1798  								Old:  "true",
  1799  								New:  "",
  1800  							},
  1801  							{
  1802  								Type: DiffTypeDeleted,
  1803  								Name: "SizeMB",
  1804  								Old:  "100",
  1805  								New:  "",
  1806  							},
  1807  							{
  1808  								Type: DiffTypeDeleted,
  1809  								Name: "Sticky",
  1810  								Old:  "true",
  1811  								New:  "",
  1812  							},
  1813  						},
  1814  					},
  1815  				},
  1816  			},
  1817  		},
  1818  		{
  1819  			// EphemeralDisk edited
  1820  			Old: &TaskGroup{
  1821  				EphemeralDisk: &EphemeralDisk{
  1822  					Migrate: true,
  1823  					Sticky:  true,
  1824  					SizeMB:  150,
  1825  				},
  1826  			},
  1827  			New: &TaskGroup{
  1828  				EphemeralDisk: &EphemeralDisk{
  1829  					Migrate: false,
  1830  					Sticky:  false,
  1831  					SizeMB:  90,
  1832  				},
  1833  			},
  1834  			Expected: &TaskGroupDiff{
  1835  				Type: DiffTypeEdited,
  1836  				Objects: []*ObjectDiff{
  1837  					{
  1838  						Type: DiffTypeEdited,
  1839  						Name: "EphemeralDisk",
  1840  						Fields: []*FieldDiff{
  1841  							{
  1842  								Type: DiffTypeEdited,
  1843  								Name: "Migrate",
  1844  								Old:  "true",
  1845  								New:  "false",
  1846  							},
  1847  							{
  1848  								Type: DiffTypeEdited,
  1849  								Name: "SizeMB",
  1850  								Old:  "150",
  1851  								New:  "90",
  1852  							},
  1853  
  1854  							{
  1855  								Type: DiffTypeEdited,
  1856  								Name: "Sticky",
  1857  								Old:  "true",
  1858  								New:  "false",
  1859  							},
  1860  						},
  1861  					},
  1862  				},
  1863  			},
  1864  		},
  1865  		{
  1866  			// EphemeralDisk edited with context
  1867  			Contextual: true,
  1868  			Old: &TaskGroup{
  1869  				EphemeralDisk: &EphemeralDisk{
  1870  					Migrate: false,
  1871  					Sticky:  false,
  1872  					SizeMB:  100,
  1873  				},
  1874  			},
  1875  			New: &TaskGroup{
  1876  				EphemeralDisk: &EphemeralDisk{
  1877  					Migrate: true,
  1878  					Sticky:  true,
  1879  					SizeMB:  90,
  1880  				},
  1881  			},
  1882  			Expected: &TaskGroupDiff{
  1883  				Type: DiffTypeEdited,
  1884  				Objects: []*ObjectDiff{
  1885  					{
  1886  						Type: DiffTypeEdited,
  1887  						Name: "EphemeralDisk",
  1888  						Fields: []*FieldDiff{
  1889  							{
  1890  								Type: DiffTypeEdited,
  1891  								Name: "Migrate",
  1892  								Old:  "false",
  1893  								New:  "true",
  1894  							},
  1895  							{
  1896  								Type: DiffTypeEdited,
  1897  								Name: "SizeMB",
  1898  								Old:  "100",
  1899  								New:  "90",
  1900  							},
  1901  							{
  1902  								Type: DiffTypeEdited,
  1903  								Name: "Sticky",
  1904  								Old:  "false",
  1905  								New:  "true",
  1906  							},
  1907  						},
  1908  					},
  1909  				},
  1910  			},
  1911  		},
  1912  		{
  1913  			// Tasks edited
  1914  			Old: &TaskGroup{
  1915  				Tasks: []*Task{
  1916  					{
  1917  						Name:   "foo",
  1918  						Driver: "docker",
  1919  					},
  1920  					{
  1921  						Name:   "bar",
  1922  						Driver: "docker",
  1923  					},
  1924  					{
  1925  						Name:   "baz",
  1926  						Driver: "docker",
  1927  					},
  1928  				},
  1929  			},
  1930  			New: &TaskGroup{
  1931  				Tasks: []*Task{
  1932  					{
  1933  						Name:   "bar",
  1934  						Driver: "docker",
  1935  					},
  1936  					{
  1937  						Name:   "baz",
  1938  						Driver: "exec",
  1939  					},
  1940  					{
  1941  						Name:   "bam",
  1942  						Driver: "docker",
  1943  					},
  1944  				},
  1945  			},
  1946  			Expected: &TaskGroupDiff{
  1947  				Type: DiffTypeEdited,
  1948  				Tasks: []*TaskDiff{
  1949  					{
  1950  						Type: DiffTypeAdded,
  1951  						Name: "bam",
  1952  						Fields: []*FieldDiff{
  1953  							{
  1954  								Type: DiffTypeAdded,
  1955  								Name: "Driver",
  1956  								Old:  "",
  1957  								New:  "docker",
  1958  							},
  1959  							{
  1960  								Type: DiffTypeAdded,
  1961  								Name: "KillTimeout",
  1962  								Old:  "",
  1963  								New:  "0",
  1964  							},
  1965  							{
  1966  								Type: DiffTypeAdded,
  1967  								Name: "Leader",
  1968  								Old:  "",
  1969  								New:  "false",
  1970  							},
  1971  						},
  1972  					},
  1973  					{
  1974  						Type: DiffTypeNone,
  1975  						Name: "bar",
  1976  					},
  1977  					{
  1978  						Type: DiffTypeEdited,
  1979  						Name: "baz",
  1980  						Fields: []*FieldDiff{
  1981  							{
  1982  								Type: DiffTypeEdited,
  1983  								Name: "Driver",
  1984  								Old:  "docker",
  1985  								New:  "exec",
  1986  							},
  1987  						},
  1988  					},
  1989  					{
  1990  						Type: DiffTypeDeleted,
  1991  						Name: "foo",
  1992  						Fields: []*FieldDiff{
  1993  							{
  1994  								Type: DiffTypeDeleted,
  1995  								Name: "Driver",
  1996  								Old:  "docker",
  1997  								New:  "",
  1998  							},
  1999  							{
  2000  								Type: DiffTypeDeleted,
  2001  								Name: "KillTimeout",
  2002  								Old:  "0",
  2003  								New:  "",
  2004  							},
  2005  							{
  2006  								Type: DiffTypeDeleted,
  2007  								Name: "Leader",
  2008  								Old:  "false",
  2009  								New:  "",
  2010  							},
  2011  						},
  2012  					},
  2013  				},
  2014  			},
  2015  		},
  2016  	}
  2017  
  2018  	for i, c := range cases {
  2019  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2020  		if c.Error && err == nil {
  2021  			t.Fatalf("case %d: expected errored", i+1)
  2022  		} else if err != nil {
  2023  			if !c.Error {
  2024  				t.Fatalf("case %d: errored %#v", i+1, err)
  2025  			} else {
  2026  				continue
  2027  			}
  2028  		}
  2029  
  2030  		if !reflect.DeepEqual(actual, c.Expected) {
  2031  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2032  				i+1, actual, c.Expected)
  2033  		}
  2034  	}
  2035  }
  2036  
  2037  func TestTaskDiff(t *testing.T) {
  2038  	cases := []struct {
  2039  		Name       string
  2040  		Old, New   *Task
  2041  		Expected   *TaskDiff
  2042  		Error      bool
  2043  		Contextual bool
  2044  	}{
  2045  		{
  2046  			Name: "Empty",
  2047  			Old:  nil,
  2048  			New:  nil,
  2049  			Expected: &TaskDiff{
  2050  				Type: DiffTypeNone,
  2051  			},
  2052  		},
  2053  		{
  2054  			Name: "Primitive only that has different names",
  2055  			Old: &Task{
  2056  				Name: "foo",
  2057  				Meta: map[string]string{
  2058  					"foo": "bar",
  2059  				},
  2060  			},
  2061  			New: &Task{
  2062  				Name: "bar",
  2063  				Meta: map[string]string{
  2064  					"foo": "bar",
  2065  				},
  2066  			},
  2067  			Error: true,
  2068  		},
  2069  		{
  2070  			Name: "Primitive only that is the same",
  2071  			Old: &Task{
  2072  				Name:   "foo",
  2073  				Driver: "exec",
  2074  				User:   "foo",
  2075  				Env: map[string]string{
  2076  					"FOO": "bar",
  2077  				},
  2078  				Meta: map[string]string{
  2079  					"foo": "bar",
  2080  				},
  2081  				KillTimeout: 1 * time.Second,
  2082  				Leader:      true,
  2083  			},
  2084  			New: &Task{
  2085  				Name:   "foo",
  2086  				Driver: "exec",
  2087  				User:   "foo",
  2088  				Env: map[string]string{
  2089  					"FOO": "bar",
  2090  				},
  2091  				Meta: map[string]string{
  2092  					"foo": "bar",
  2093  				},
  2094  				KillTimeout: 1 * time.Second,
  2095  				Leader:      true,
  2096  			},
  2097  			Expected: &TaskDiff{
  2098  				Type: DiffTypeNone,
  2099  				Name: "foo",
  2100  			},
  2101  		},
  2102  		{
  2103  			Name: "Primitive only that has diffs",
  2104  			Old: &Task{
  2105  				Name:   "foo",
  2106  				Driver: "exec",
  2107  				User:   "foo",
  2108  				Env: map[string]string{
  2109  					"FOO": "bar",
  2110  				},
  2111  				Meta: map[string]string{
  2112  					"foo": "bar",
  2113  				},
  2114  				KillTimeout: 1 * time.Second,
  2115  				Leader:      true,
  2116  			},
  2117  			New: &Task{
  2118  				Name:   "foo",
  2119  				Driver: "docker",
  2120  				User:   "bar",
  2121  				Env: map[string]string{
  2122  					"FOO": "baz",
  2123  				},
  2124  				Meta: map[string]string{
  2125  					"foo": "baz",
  2126  				},
  2127  				KillTimeout: 2 * time.Second,
  2128  				Leader:      false,
  2129  			},
  2130  			Expected: &TaskDiff{
  2131  				Type: DiffTypeEdited,
  2132  				Name: "foo",
  2133  				Fields: []*FieldDiff{
  2134  					{
  2135  						Type: DiffTypeEdited,
  2136  						Name: "Driver",
  2137  						Old:  "exec",
  2138  						New:  "docker",
  2139  					},
  2140  					{
  2141  						Type: DiffTypeEdited,
  2142  						Name: "Env[FOO]",
  2143  						Old:  "bar",
  2144  						New:  "baz",
  2145  					},
  2146  					{
  2147  						Type: DiffTypeEdited,
  2148  						Name: "KillTimeout",
  2149  						Old:  "1000000000",
  2150  						New:  "2000000000",
  2151  					},
  2152  					{
  2153  						Type: DiffTypeEdited,
  2154  						Name: "Leader",
  2155  						Old:  "true",
  2156  						New:  "false",
  2157  					},
  2158  					{
  2159  						Type: DiffTypeEdited,
  2160  						Name: "Meta[foo]",
  2161  						Old:  "bar",
  2162  						New:  "baz",
  2163  					},
  2164  					{
  2165  						Type: DiffTypeEdited,
  2166  						Name: "User",
  2167  						Old:  "foo",
  2168  						New:  "bar",
  2169  					},
  2170  				},
  2171  			},
  2172  		},
  2173  		{
  2174  			Name: "Map diff",
  2175  			Old: &Task{
  2176  				Meta: map[string]string{
  2177  					"foo": "foo",
  2178  					"bar": "bar",
  2179  				},
  2180  				Env: map[string]string{
  2181  					"foo": "foo",
  2182  					"bar": "bar",
  2183  				},
  2184  			},
  2185  			New: &Task{
  2186  				Meta: map[string]string{
  2187  					"bar": "bar",
  2188  					"baz": "baz",
  2189  				},
  2190  				Env: map[string]string{
  2191  					"bar": "bar",
  2192  					"baz": "baz",
  2193  				},
  2194  			},
  2195  			Expected: &TaskDiff{
  2196  				Type: DiffTypeEdited,
  2197  				Fields: []*FieldDiff{
  2198  					{
  2199  						Type: DiffTypeAdded,
  2200  						Name: "Env[baz]",
  2201  						Old:  "",
  2202  						New:  "baz",
  2203  					},
  2204  					{
  2205  						Type: DiffTypeDeleted,
  2206  						Name: "Env[foo]",
  2207  						Old:  "foo",
  2208  						New:  "",
  2209  					},
  2210  					{
  2211  						Type: DiffTypeAdded,
  2212  						Name: "Meta[baz]",
  2213  						Old:  "",
  2214  						New:  "baz",
  2215  					},
  2216  					{
  2217  						Type: DiffTypeDeleted,
  2218  						Name: "Meta[foo]",
  2219  						Old:  "foo",
  2220  						New:  "",
  2221  					},
  2222  				},
  2223  			},
  2224  		},
  2225  		{
  2226  			Name: "Constraints edited",
  2227  			Old: &Task{
  2228  				Constraints: []*Constraint{
  2229  					{
  2230  						LTarget: "foo",
  2231  						RTarget: "foo",
  2232  						Operand: "foo",
  2233  						str:     "foo",
  2234  					},
  2235  					{
  2236  						LTarget: "bar",
  2237  						RTarget: "bar",
  2238  						Operand: "bar",
  2239  						str:     "bar",
  2240  					},
  2241  				},
  2242  			},
  2243  			New: &Task{
  2244  				Constraints: []*Constraint{
  2245  					{
  2246  						LTarget: "foo",
  2247  						RTarget: "foo",
  2248  						Operand: "foo",
  2249  						str:     "foo",
  2250  					},
  2251  					{
  2252  						LTarget: "baz",
  2253  						RTarget: "baz",
  2254  						Operand: "baz",
  2255  						str:     "baz",
  2256  					},
  2257  				},
  2258  			},
  2259  			Expected: &TaskDiff{
  2260  				Type: DiffTypeEdited,
  2261  				Objects: []*ObjectDiff{
  2262  					{
  2263  						Type: DiffTypeAdded,
  2264  						Name: "Constraint",
  2265  						Fields: []*FieldDiff{
  2266  							{
  2267  								Type: DiffTypeAdded,
  2268  								Name: "LTarget",
  2269  								Old:  "",
  2270  								New:  "baz",
  2271  							},
  2272  							{
  2273  								Type: DiffTypeAdded,
  2274  								Name: "Operand",
  2275  								Old:  "",
  2276  								New:  "baz",
  2277  							},
  2278  							{
  2279  								Type: DiffTypeAdded,
  2280  								Name: "RTarget",
  2281  								Old:  "",
  2282  								New:  "baz",
  2283  							},
  2284  						},
  2285  					},
  2286  					{
  2287  						Type: DiffTypeDeleted,
  2288  						Name: "Constraint",
  2289  						Fields: []*FieldDiff{
  2290  							{
  2291  								Type: DiffTypeDeleted,
  2292  								Name: "LTarget",
  2293  								Old:  "bar",
  2294  								New:  "",
  2295  							},
  2296  							{
  2297  								Type: DiffTypeDeleted,
  2298  								Name: "Operand",
  2299  								Old:  "bar",
  2300  								New:  "",
  2301  							},
  2302  							{
  2303  								Type: DiffTypeDeleted,
  2304  								Name: "RTarget",
  2305  								Old:  "bar",
  2306  								New:  "",
  2307  							},
  2308  						},
  2309  					},
  2310  				},
  2311  			},
  2312  		},
  2313  		{
  2314  			Name: "LogConfig added",
  2315  			Old:  &Task{},
  2316  			New: &Task{
  2317  				LogConfig: &LogConfig{
  2318  					MaxFiles:      1,
  2319  					MaxFileSizeMB: 10,
  2320  				},
  2321  			},
  2322  			Expected: &TaskDiff{
  2323  				Type: DiffTypeEdited,
  2324  				Objects: []*ObjectDiff{
  2325  					{
  2326  						Type: DiffTypeAdded,
  2327  						Name: "LogConfig",
  2328  						Fields: []*FieldDiff{
  2329  							{
  2330  								Type: DiffTypeAdded,
  2331  								Name: "MaxFileSizeMB",
  2332  								Old:  "",
  2333  								New:  "10",
  2334  							},
  2335  							{
  2336  								Type: DiffTypeAdded,
  2337  								Name: "MaxFiles",
  2338  								Old:  "",
  2339  								New:  "1",
  2340  							},
  2341  						},
  2342  					},
  2343  				},
  2344  			},
  2345  		},
  2346  		{
  2347  			Name: "LogConfig deleted",
  2348  			Old: &Task{
  2349  				LogConfig: &LogConfig{
  2350  					MaxFiles:      1,
  2351  					MaxFileSizeMB: 10,
  2352  				},
  2353  			},
  2354  			New: &Task{},
  2355  			Expected: &TaskDiff{
  2356  				Type: DiffTypeEdited,
  2357  				Objects: []*ObjectDiff{
  2358  					{
  2359  						Type: DiffTypeDeleted,
  2360  						Name: "LogConfig",
  2361  						Fields: []*FieldDiff{
  2362  							{
  2363  								Type: DiffTypeDeleted,
  2364  								Name: "MaxFileSizeMB",
  2365  								Old:  "10",
  2366  								New:  "",
  2367  							},
  2368  							{
  2369  								Type: DiffTypeDeleted,
  2370  								Name: "MaxFiles",
  2371  								Old:  "1",
  2372  								New:  "",
  2373  							},
  2374  						},
  2375  					},
  2376  				},
  2377  			},
  2378  		},
  2379  		{
  2380  			Name: "LogConfig edited",
  2381  			Old: &Task{
  2382  				LogConfig: &LogConfig{
  2383  					MaxFiles:      1,
  2384  					MaxFileSizeMB: 10,
  2385  				},
  2386  			},
  2387  			New: &Task{
  2388  				LogConfig: &LogConfig{
  2389  					MaxFiles:      2,
  2390  					MaxFileSizeMB: 20,
  2391  				},
  2392  			},
  2393  			Expected: &TaskDiff{
  2394  				Type: DiffTypeEdited,
  2395  				Objects: []*ObjectDiff{
  2396  					{
  2397  						Type: DiffTypeEdited,
  2398  						Name: "LogConfig",
  2399  						Fields: []*FieldDiff{
  2400  							{
  2401  								Type: DiffTypeEdited,
  2402  								Name: "MaxFileSizeMB",
  2403  								Old:  "10",
  2404  								New:  "20",
  2405  							},
  2406  							{
  2407  								Type: DiffTypeEdited,
  2408  								Name: "MaxFiles",
  2409  								Old:  "1",
  2410  								New:  "2",
  2411  							},
  2412  						},
  2413  					},
  2414  				},
  2415  			},
  2416  		},
  2417  		{
  2418  			Name:       "LogConfig edited with context",
  2419  			Contextual: true,
  2420  			Old: &Task{
  2421  				LogConfig: &LogConfig{
  2422  					MaxFiles:      1,
  2423  					MaxFileSizeMB: 10,
  2424  				},
  2425  			},
  2426  			New: &Task{
  2427  				LogConfig: &LogConfig{
  2428  					MaxFiles:      1,
  2429  					MaxFileSizeMB: 20,
  2430  				},
  2431  			},
  2432  			Expected: &TaskDiff{
  2433  				Type: DiffTypeEdited,
  2434  				Objects: []*ObjectDiff{
  2435  					{
  2436  						Type: DiffTypeEdited,
  2437  						Name: "LogConfig",
  2438  						Fields: []*FieldDiff{
  2439  							{
  2440  								Type: DiffTypeEdited,
  2441  								Name: "MaxFileSizeMB",
  2442  								Old:  "10",
  2443  								New:  "20",
  2444  							},
  2445  							{
  2446  								Type: DiffTypeNone,
  2447  								Name: "MaxFiles",
  2448  								Old:  "1",
  2449  								New:  "1",
  2450  							},
  2451  						},
  2452  					},
  2453  				},
  2454  			},
  2455  		},
  2456  		{
  2457  			Name: "Artifacts edited",
  2458  			Old: &Task{
  2459  				Artifacts: []*TaskArtifact{
  2460  					{
  2461  						GetterSource: "foo",
  2462  						GetterOptions: map[string]string{
  2463  							"foo": "bar",
  2464  						},
  2465  						RelativeDest: "foo",
  2466  					},
  2467  					{
  2468  						GetterSource: "bar",
  2469  						GetterOptions: map[string]string{
  2470  							"bar": "baz",
  2471  						},
  2472  						GetterMode:   "dir",
  2473  						RelativeDest: "bar",
  2474  					},
  2475  				},
  2476  			},
  2477  			New: &Task{
  2478  				Artifacts: []*TaskArtifact{
  2479  					{
  2480  						GetterSource: "foo",
  2481  						GetterOptions: map[string]string{
  2482  							"foo": "bar",
  2483  						},
  2484  						RelativeDest: "foo",
  2485  					},
  2486  					{
  2487  						GetterSource: "bam",
  2488  						GetterOptions: map[string]string{
  2489  							"bam": "baz",
  2490  						},
  2491  						GetterMode:   "file",
  2492  						RelativeDest: "bam",
  2493  					},
  2494  				},
  2495  			},
  2496  			Expected: &TaskDiff{
  2497  				Type: DiffTypeEdited,
  2498  				Objects: []*ObjectDiff{
  2499  					{
  2500  						Type: DiffTypeAdded,
  2501  						Name: "Artifact",
  2502  						Fields: []*FieldDiff{
  2503  							{
  2504  								Type: DiffTypeAdded,
  2505  								Name: "GetterMode",
  2506  								Old:  "",
  2507  								New:  "file",
  2508  							},
  2509  							{
  2510  								Type: DiffTypeAdded,
  2511  								Name: "GetterOptions[bam]",
  2512  								Old:  "",
  2513  								New:  "baz",
  2514  							},
  2515  							{
  2516  								Type: DiffTypeAdded,
  2517  								Name: "GetterSource",
  2518  								Old:  "",
  2519  								New:  "bam",
  2520  							},
  2521  							{
  2522  								Type: DiffTypeAdded,
  2523  								Name: "RelativeDest",
  2524  								Old:  "",
  2525  								New:  "bam",
  2526  							},
  2527  						},
  2528  					},
  2529  					{
  2530  						Type: DiffTypeDeleted,
  2531  						Name: "Artifact",
  2532  						Fields: []*FieldDiff{
  2533  							{
  2534  								Type: DiffTypeDeleted,
  2535  								Name: "GetterMode",
  2536  								Old:  "dir",
  2537  								New:  "",
  2538  							},
  2539  							{
  2540  								Type: DiffTypeDeleted,
  2541  								Name: "GetterOptions[bar]",
  2542  								Old:  "baz",
  2543  								New:  "",
  2544  							},
  2545  							{
  2546  								Type: DiffTypeDeleted,
  2547  								Name: "GetterSource",
  2548  								Old:  "bar",
  2549  								New:  "",
  2550  							},
  2551  							{
  2552  								Type: DiffTypeDeleted,
  2553  								Name: "RelativeDest",
  2554  								Old:  "bar",
  2555  								New:  "",
  2556  							},
  2557  						},
  2558  					},
  2559  				},
  2560  			},
  2561  		},
  2562  		{
  2563  			Name: "Resources edited (no networks)",
  2564  			Old: &Task{
  2565  				Resources: &Resources{
  2566  					CPU:      100,
  2567  					MemoryMB: 100,
  2568  					DiskMB:   100,
  2569  					IOPS:     100,
  2570  				},
  2571  			},
  2572  			New: &Task{
  2573  				Resources: &Resources{
  2574  					CPU:      200,
  2575  					MemoryMB: 200,
  2576  					DiskMB:   200,
  2577  					IOPS:     200,
  2578  				},
  2579  			},
  2580  			Expected: &TaskDiff{
  2581  				Type: DiffTypeEdited,
  2582  				Objects: []*ObjectDiff{
  2583  					{
  2584  						Type: DiffTypeEdited,
  2585  						Name: "Resources",
  2586  						Fields: []*FieldDiff{
  2587  							{
  2588  								Type: DiffTypeEdited,
  2589  								Name: "CPU",
  2590  								Old:  "100",
  2591  								New:  "200",
  2592  							},
  2593  							{
  2594  								Type: DiffTypeEdited,
  2595  								Name: "DiskMB",
  2596  								Old:  "100",
  2597  								New:  "200",
  2598  							},
  2599  							{
  2600  								Type: DiffTypeEdited,
  2601  								Name: "IOPS",
  2602  								Old:  "100",
  2603  								New:  "200",
  2604  							},
  2605  							{
  2606  								Type: DiffTypeEdited,
  2607  								Name: "MemoryMB",
  2608  								Old:  "100",
  2609  								New:  "200",
  2610  							},
  2611  						},
  2612  					},
  2613  				},
  2614  			},
  2615  		},
  2616  		{
  2617  			Name:       "Resources edited (no networks) with context",
  2618  			Contextual: true,
  2619  			Old: &Task{
  2620  				Resources: &Resources{
  2621  					CPU:      100,
  2622  					MemoryMB: 100,
  2623  					DiskMB:   100,
  2624  					IOPS:     100,
  2625  				},
  2626  			},
  2627  			New: &Task{
  2628  				Resources: &Resources{
  2629  					CPU:      200,
  2630  					MemoryMB: 100,
  2631  					DiskMB:   200,
  2632  					IOPS:     100,
  2633  				},
  2634  			},
  2635  			Expected: &TaskDiff{
  2636  				Type: DiffTypeEdited,
  2637  				Objects: []*ObjectDiff{
  2638  					{
  2639  						Type: DiffTypeEdited,
  2640  						Name: "Resources",
  2641  						Fields: []*FieldDiff{
  2642  							{
  2643  								Type: DiffTypeEdited,
  2644  								Name: "CPU",
  2645  								Old:  "100",
  2646  								New:  "200",
  2647  							},
  2648  							{
  2649  								Type: DiffTypeEdited,
  2650  								Name: "DiskMB",
  2651  								Old:  "100",
  2652  								New:  "200",
  2653  							},
  2654  							{
  2655  								Type: DiffTypeNone,
  2656  								Name: "IOPS",
  2657  								Old:  "100",
  2658  								New:  "100",
  2659  							},
  2660  							{
  2661  								Type: DiffTypeNone,
  2662  								Name: "MemoryMB",
  2663  								Old:  "100",
  2664  								New:  "100",
  2665  							},
  2666  						},
  2667  					},
  2668  				},
  2669  			},
  2670  		},
  2671  		{
  2672  			Name: "Network Resources edited",
  2673  			Old: &Task{
  2674  				Resources: &Resources{
  2675  					Networks: []*NetworkResource{
  2676  						{
  2677  							Device: "foo",
  2678  							CIDR:   "foo",
  2679  							IP:     "foo",
  2680  							MBits:  100,
  2681  							ReservedPorts: []Port{
  2682  								{
  2683  									Label: "foo",
  2684  									Value: 80,
  2685  								},
  2686  							},
  2687  							DynamicPorts: []Port{
  2688  								{
  2689  									Label: "bar",
  2690  								},
  2691  							},
  2692  						},
  2693  					},
  2694  				},
  2695  			},
  2696  			New: &Task{
  2697  				Resources: &Resources{
  2698  					Networks: []*NetworkResource{
  2699  						{
  2700  							Device: "bar",
  2701  							CIDR:   "bar",
  2702  							IP:     "bar",
  2703  							MBits:  200,
  2704  							ReservedPorts: []Port{
  2705  								{
  2706  									Label: "foo",
  2707  									Value: 81,
  2708  								},
  2709  							},
  2710  							DynamicPorts: []Port{
  2711  								{
  2712  									Label: "baz",
  2713  								},
  2714  							},
  2715  						},
  2716  					},
  2717  				},
  2718  			},
  2719  			Expected: &TaskDiff{
  2720  				Type: DiffTypeEdited,
  2721  				Objects: []*ObjectDiff{
  2722  					{
  2723  						Type: DiffTypeEdited,
  2724  						Name: "Resources",
  2725  						Objects: []*ObjectDiff{
  2726  							{
  2727  								Type: DiffTypeAdded,
  2728  								Name: "Network",
  2729  								Fields: []*FieldDiff{
  2730  									{
  2731  										Type: DiffTypeAdded,
  2732  										Name: "MBits",
  2733  										Old:  "",
  2734  										New:  "200",
  2735  									},
  2736  								},
  2737  								Objects: []*ObjectDiff{
  2738  									{
  2739  										Type: DiffTypeAdded,
  2740  										Name: "Static Port",
  2741  										Fields: []*FieldDiff{
  2742  											{
  2743  												Type: DiffTypeAdded,
  2744  												Name: "Label",
  2745  												Old:  "",
  2746  												New:  "foo",
  2747  											},
  2748  											{
  2749  												Type: DiffTypeAdded,
  2750  												Name: "Value",
  2751  												Old:  "",
  2752  												New:  "81",
  2753  											},
  2754  										},
  2755  									},
  2756  									{
  2757  										Type: DiffTypeAdded,
  2758  										Name: "Dynamic Port",
  2759  										Fields: []*FieldDiff{
  2760  											{
  2761  												Type: DiffTypeAdded,
  2762  												Name: "Label",
  2763  												Old:  "",
  2764  												New:  "baz",
  2765  											},
  2766  										},
  2767  									},
  2768  								},
  2769  							},
  2770  							{
  2771  								Type: DiffTypeDeleted,
  2772  								Name: "Network",
  2773  								Fields: []*FieldDiff{
  2774  									{
  2775  										Type: DiffTypeDeleted,
  2776  										Name: "MBits",
  2777  										Old:  "100",
  2778  										New:  "",
  2779  									},
  2780  								},
  2781  								Objects: []*ObjectDiff{
  2782  									{
  2783  										Type: DiffTypeDeleted,
  2784  										Name: "Static Port",
  2785  										Fields: []*FieldDiff{
  2786  											{
  2787  												Type: DiffTypeDeleted,
  2788  												Name: "Label",
  2789  												Old:  "foo",
  2790  												New:  "",
  2791  											},
  2792  											{
  2793  												Type: DiffTypeDeleted,
  2794  												Name: "Value",
  2795  												Old:  "80",
  2796  												New:  "",
  2797  											},
  2798  										},
  2799  									},
  2800  									{
  2801  										Type: DiffTypeDeleted,
  2802  										Name: "Dynamic Port",
  2803  										Fields: []*FieldDiff{
  2804  											{
  2805  												Type: DiffTypeDeleted,
  2806  												Name: "Label",
  2807  												Old:  "bar",
  2808  												New:  "",
  2809  											},
  2810  										},
  2811  									},
  2812  								},
  2813  							},
  2814  						},
  2815  					},
  2816  				},
  2817  			},
  2818  		},
  2819  		{
  2820  			Name: "Config same",
  2821  			Old: &Task{
  2822  				Config: map[string]interface{}{
  2823  					"foo": 1,
  2824  					"bar": "bar",
  2825  					"bam": []string{"a", "b"},
  2826  					"baz": map[string]int{
  2827  						"a": 1,
  2828  						"b": 2,
  2829  					},
  2830  					"boom": &Port{
  2831  						Label: "boom_port",
  2832  					},
  2833  				},
  2834  			},
  2835  			New: &Task{
  2836  				Config: map[string]interface{}{
  2837  					"foo": 1,
  2838  					"bar": "bar",
  2839  					"bam": []string{"a", "b"},
  2840  					"baz": map[string]int{
  2841  						"a": 1,
  2842  						"b": 2,
  2843  					},
  2844  					"boom": &Port{
  2845  						Label: "boom_port",
  2846  					},
  2847  				},
  2848  			},
  2849  			Expected: &TaskDiff{
  2850  				Type: DiffTypeNone,
  2851  			},
  2852  		},
  2853  		{
  2854  			Name: "Config edited",
  2855  			Old: &Task{
  2856  				Config: map[string]interface{}{
  2857  					"foo": 1,
  2858  					"bar": "baz",
  2859  					"bam": []string{"a", "b"},
  2860  					"baz": map[string]int{
  2861  						"a": 1,
  2862  						"b": 2,
  2863  					},
  2864  					"boom": &Port{
  2865  						Label: "boom_port",
  2866  					},
  2867  				},
  2868  			},
  2869  			New: &Task{
  2870  				Config: map[string]interface{}{
  2871  					"foo": 2,
  2872  					"bar": "baz",
  2873  					"bam": []string{"a", "c", "d"},
  2874  					"baz": map[string]int{
  2875  						"b": 3,
  2876  						"c": 4,
  2877  					},
  2878  					"boom": &Port{
  2879  						Label: "boom_port2",
  2880  					},
  2881  				},
  2882  			},
  2883  			Expected: &TaskDiff{
  2884  				Type: DiffTypeEdited,
  2885  				Objects: []*ObjectDiff{
  2886  					{
  2887  						Type: DiffTypeEdited,
  2888  						Name: "Config",
  2889  						Fields: []*FieldDiff{
  2890  							{
  2891  								Type: DiffTypeEdited,
  2892  								Name: "bam[1]",
  2893  								Old:  "b",
  2894  								New:  "c",
  2895  							},
  2896  							{
  2897  								Type: DiffTypeAdded,
  2898  								Name: "bam[2]",
  2899  								Old:  "",
  2900  								New:  "d",
  2901  							},
  2902  							{
  2903  								Type: DiffTypeDeleted,
  2904  								Name: "baz[a]",
  2905  								Old:  "1",
  2906  								New:  "",
  2907  							},
  2908  							{
  2909  								Type: DiffTypeEdited,
  2910  								Name: "baz[b]",
  2911  								Old:  "2",
  2912  								New:  "3",
  2913  							},
  2914  							{
  2915  								Type: DiffTypeAdded,
  2916  								Name: "baz[c]",
  2917  								Old:  "",
  2918  								New:  "4",
  2919  							},
  2920  							{
  2921  								Type: DiffTypeEdited,
  2922  								Name: "boom.Label",
  2923  								Old:  "boom_port",
  2924  								New:  "boom_port2",
  2925  							},
  2926  							{
  2927  								Type: DiffTypeEdited,
  2928  								Name: "foo",
  2929  								Old:  "1",
  2930  								New:  "2",
  2931  							},
  2932  						},
  2933  					},
  2934  				},
  2935  			},
  2936  		},
  2937  		{
  2938  			Name:       "Config edited with context",
  2939  			Contextual: true,
  2940  			Old: &Task{
  2941  				Config: map[string]interface{}{
  2942  					"foo": 1,
  2943  					"bar": "baz",
  2944  					"bam": []string{"a", "b"},
  2945  					"baz": map[string]int{
  2946  						"a": 1,
  2947  						"b": 2,
  2948  					},
  2949  					"boom": &Port{
  2950  						Label: "boom_port",
  2951  					},
  2952  				},
  2953  			},
  2954  			New: &Task{
  2955  				Config: map[string]interface{}{
  2956  					"foo": 2,
  2957  					"bar": "baz",
  2958  					"bam": []string{"a", "c", "d"},
  2959  					"baz": map[string]int{
  2960  						"a": 1,
  2961  						"b": 2,
  2962  					},
  2963  					"boom": &Port{
  2964  						Label: "boom_port",
  2965  					},
  2966  				},
  2967  			},
  2968  			Expected: &TaskDiff{
  2969  				Type: DiffTypeEdited,
  2970  				Objects: []*ObjectDiff{
  2971  					{
  2972  						Type: DiffTypeEdited,
  2973  						Name: "Config",
  2974  						Fields: []*FieldDiff{
  2975  							{
  2976  								Type: DiffTypeNone,
  2977  								Name: "bam[0]",
  2978  								Old:  "a",
  2979  								New:  "a",
  2980  							},
  2981  							{
  2982  								Type: DiffTypeEdited,
  2983  								Name: "bam[1]",
  2984  								Old:  "b",
  2985  								New:  "c",
  2986  							},
  2987  							{
  2988  								Type: DiffTypeAdded,
  2989  								Name: "bam[2]",
  2990  								Old:  "",
  2991  								New:  "d",
  2992  							},
  2993  							{
  2994  								Type: DiffTypeNone,
  2995  								Name: "bar",
  2996  								Old:  "baz",
  2997  								New:  "baz",
  2998  							},
  2999  							{
  3000  								Type: DiffTypeNone,
  3001  								Name: "baz[a]",
  3002  								Old:  "1",
  3003  								New:  "1",
  3004  							},
  3005  							{
  3006  								Type: DiffTypeNone,
  3007  								Name: "baz[b]",
  3008  								Old:  "2",
  3009  								New:  "2",
  3010  							},
  3011  							{
  3012  								Type: DiffTypeNone,
  3013  								Name: "boom.Label",
  3014  								Old:  "boom_port",
  3015  								New:  "boom_port",
  3016  							},
  3017  							{
  3018  								Type: DiffTypeNone,
  3019  								Name: "boom.Value",
  3020  								Old:  "0",
  3021  								New:  "0",
  3022  							},
  3023  							{
  3024  								Type: DiffTypeEdited,
  3025  								Name: "foo",
  3026  								Old:  "1",
  3027  								New:  "2",
  3028  							},
  3029  						},
  3030  					},
  3031  				},
  3032  			},
  3033  		},
  3034  		{
  3035  			Name: "Services edited (no checks)",
  3036  			Old: &Task{
  3037  				Services: []*Service{
  3038  					{
  3039  						Name:      "foo",
  3040  						PortLabel: "foo",
  3041  					},
  3042  					{
  3043  						Name:      "bar",
  3044  						PortLabel: "bar",
  3045  					},
  3046  					{
  3047  						Name:      "baz",
  3048  						PortLabel: "baz",
  3049  					},
  3050  				},
  3051  			},
  3052  			New: &Task{
  3053  				Services: []*Service{
  3054  					{
  3055  						Name:      "bar",
  3056  						PortLabel: "bar",
  3057  					},
  3058  					{
  3059  						Name:      "baz",
  3060  						PortLabel: "baz2",
  3061  					},
  3062  					{
  3063  						Name:      "bam",
  3064  						PortLabel: "bam",
  3065  					},
  3066  				},
  3067  			},
  3068  			Expected: &TaskDiff{
  3069  				Type: DiffTypeEdited,
  3070  				Objects: []*ObjectDiff{
  3071  					{
  3072  						Type: DiffTypeEdited,
  3073  						Name: "Service",
  3074  						Fields: []*FieldDiff{
  3075  							{
  3076  								Type: DiffTypeEdited,
  3077  								Name: "PortLabel",
  3078  								Old:  "baz",
  3079  								New:  "baz2",
  3080  							},
  3081  						},
  3082  					},
  3083  					{
  3084  						Type: DiffTypeAdded,
  3085  						Name: "Service",
  3086  						Fields: []*FieldDiff{
  3087  							{
  3088  								Type: DiffTypeAdded,
  3089  								Name: "Name",
  3090  								Old:  "",
  3091  								New:  "bam",
  3092  							},
  3093  							{
  3094  								Type: DiffTypeAdded,
  3095  								Name: "PortLabel",
  3096  								Old:  "",
  3097  								New:  "bam",
  3098  							},
  3099  						},
  3100  					},
  3101  					{
  3102  						Type: DiffTypeDeleted,
  3103  						Name: "Service",
  3104  						Fields: []*FieldDiff{
  3105  							{
  3106  								Type: DiffTypeDeleted,
  3107  								Name: "Name",
  3108  								Old:  "foo",
  3109  								New:  "",
  3110  							},
  3111  							{
  3112  								Type: DiffTypeDeleted,
  3113  								Name: "PortLabel",
  3114  								Old:  "foo",
  3115  								New:  "",
  3116  							},
  3117  						},
  3118  					},
  3119  				},
  3120  			},
  3121  		},
  3122  		{
  3123  			Name:       "Services edited (no checks) with context",
  3124  			Contextual: true,
  3125  			Old: &Task{
  3126  				Services: []*Service{
  3127  					{
  3128  						Name:      "foo",
  3129  						PortLabel: "foo",
  3130  					},
  3131  				},
  3132  			},
  3133  			New: &Task{
  3134  				Services: []*Service{
  3135  					{
  3136  						Name:        "foo",
  3137  						PortLabel:   "bar",
  3138  						AddressMode: "driver",
  3139  					},
  3140  				},
  3141  			},
  3142  			Expected: &TaskDiff{
  3143  				Type: DiffTypeEdited,
  3144  				Objects: []*ObjectDiff{
  3145  					{
  3146  						Type: DiffTypeEdited,
  3147  						Name: "Service",
  3148  						Fields: []*FieldDiff{
  3149  							{
  3150  								Type: DiffTypeAdded,
  3151  								Name: "AddressMode",
  3152  								New:  "driver",
  3153  							},
  3154  							{
  3155  								Type: DiffTypeNone,
  3156  								Name: "Name",
  3157  								Old:  "foo",
  3158  								New:  "foo",
  3159  							},
  3160  							{
  3161  								Type: DiffTypeEdited,
  3162  								Name: "PortLabel",
  3163  								Old:  "foo",
  3164  								New:  "bar",
  3165  							},
  3166  						},
  3167  					},
  3168  				},
  3169  			},
  3170  		},
  3171  		{
  3172  			Name: "Service Checks edited",
  3173  			Old: &Task{
  3174  				Services: []*Service{
  3175  					{
  3176  						Name: "foo",
  3177  						Checks: []*ServiceCheck{
  3178  							{
  3179  								Name:     "foo",
  3180  								Type:     "http",
  3181  								Command:  "foo",
  3182  								Args:     []string{"foo"},
  3183  								Path:     "foo",
  3184  								Protocol: "http",
  3185  								Interval: 1 * time.Second,
  3186  								Timeout:  1 * time.Second,
  3187  							},
  3188  							{
  3189  								Name:     "bar",
  3190  								Type:     "http",
  3191  								Command:  "foo",
  3192  								Args:     []string{"foo"},
  3193  								Path:     "foo",
  3194  								Protocol: "http",
  3195  								Interval: 1 * time.Second,
  3196  								Timeout:  1 * time.Second,
  3197  							},
  3198  							{
  3199  								Name:     "baz",
  3200  								Type:     "http",
  3201  								Command:  "foo",
  3202  								Args:     []string{"foo"},
  3203  								Path:     "foo",
  3204  								Protocol: "http",
  3205  								Interval: 1 * time.Second,
  3206  								Timeout:  1 * time.Second,
  3207  							},
  3208  						},
  3209  					},
  3210  				},
  3211  			},
  3212  			New: &Task{
  3213  				Services: []*Service{
  3214  					{
  3215  						Name: "foo",
  3216  						Checks: []*ServiceCheck{
  3217  							{
  3218  								Name:     "bar",
  3219  								Type:     "http",
  3220  								Command:  "foo",
  3221  								Args:     []string{"foo"},
  3222  								Path:     "foo",
  3223  								Protocol: "http",
  3224  								Interval: 1 * time.Second,
  3225  								Timeout:  1 * time.Second,
  3226  							},
  3227  							{
  3228  								Name:     "baz",
  3229  								Type:     "tcp",
  3230  								Command:  "foo",
  3231  								Args:     []string{"foo"},
  3232  								Path:     "foo",
  3233  								Protocol: "http",
  3234  								Interval: 1 * time.Second,
  3235  								Timeout:  1 * time.Second,
  3236  							},
  3237  							{
  3238  								Name:     "bam",
  3239  								Type:     "http",
  3240  								Command:  "foo",
  3241  								Args:     []string{"foo"},
  3242  								Path:     "foo",
  3243  								Protocol: "http",
  3244  								Interval: 1 * time.Second,
  3245  								Timeout:  1 * time.Second,
  3246  							},
  3247  						},
  3248  					},
  3249  				},
  3250  			},
  3251  			Expected: &TaskDiff{
  3252  				Type: DiffTypeEdited,
  3253  				Objects: []*ObjectDiff{
  3254  					{
  3255  						Type: DiffTypeEdited,
  3256  						Name: "Service",
  3257  						Objects: []*ObjectDiff{
  3258  							{
  3259  								Type: DiffTypeEdited,
  3260  								Name: "Check",
  3261  								Fields: []*FieldDiff{
  3262  									{
  3263  										Type: DiffTypeEdited,
  3264  										Name: "Type",
  3265  										Old:  "http",
  3266  										New:  "tcp",
  3267  									},
  3268  								},
  3269  							},
  3270  							{
  3271  								Type: DiffTypeAdded,
  3272  								Name: "Check",
  3273  								Fields: []*FieldDiff{
  3274  									{
  3275  										Type: DiffTypeAdded,
  3276  										Name: "Command",
  3277  										Old:  "",
  3278  										New:  "foo",
  3279  									},
  3280  									{
  3281  										Type: DiffTypeAdded,
  3282  										Name: "Interval",
  3283  										Old:  "",
  3284  										New:  "1000000000",
  3285  									},
  3286  									{
  3287  										Type: DiffTypeAdded,
  3288  										Name: "Name",
  3289  										Old:  "",
  3290  										New:  "bam",
  3291  									},
  3292  									{
  3293  										Type: DiffTypeAdded,
  3294  										Name: "Path",
  3295  										Old:  "",
  3296  										New:  "foo",
  3297  									},
  3298  									{
  3299  										Type: DiffTypeAdded,
  3300  										Name: "Protocol",
  3301  										Old:  "",
  3302  										New:  "http",
  3303  									},
  3304  									{
  3305  										Type: DiffTypeAdded,
  3306  										Name: "TLSSkipVerify",
  3307  										Old:  "",
  3308  										New:  "false",
  3309  									},
  3310  									{
  3311  										Type: DiffTypeAdded,
  3312  										Name: "Timeout",
  3313  										Old:  "",
  3314  										New:  "1000000000",
  3315  									},
  3316  									{
  3317  										Type: DiffTypeAdded,
  3318  										Name: "Type",
  3319  										Old:  "",
  3320  										New:  "http",
  3321  									},
  3322  								},
  3323  							},
  3324  							{
  3325  								Type: DiffTypeDeleted,
  3326  								Name: "Check",
  3327  								Fields: []*FieldDiff{
  3328  									{
  3329  										Type: DiffTypeDeleted,
  3330  										Name: "Command",
  3331  										Old:  "foo",
  3332  										New:  "",
  3333  									},
  3334  									{
  3335  										Type: DiffTypeDeleted,
  3336  										Name: "Interval",
  3337  										Old:  "1000000000",
  3338  										New:  "",
  3339  									},
  3340  									{
  3341  										Type: DiffTypeDeleted,
  3342  										Name: "Name",
  3343  										Old:  "foo",
  3344  										New:  "",
  3345  									},
  3346  									{
  3347  										Type: DiffTypeDeleted,
  3348  										Name: "Path",
  3349  										Old:  "foo",
  3350  										New:  "",
  3351  									},
  3352  									{
  3353  										Type: DiffTypeDeleted,
  3354  										Name: "Protocol",
  3355  										Old:  "http",
  3356  										New:  "",
  3357  									},
  3358  									{
  3359  										Type: DiffTypeDeleted,
  3360  										Name: "TLSSkipVerify",
  3361  										Old:  "false",
  3362  										New:  "",
  3363  									},
  3364  									{
  3365  										Type: DiffTypeDeleted,
  3366  										Name: "Timeout",
  3367  										Old:  "1000000000",
  3368  										New:  "",
  3369  									},
  3370  									{
  3371  										Type: DiffTypeDeleted,
  3372  										Name: "Type",
  3373  										Old:  "http",
  3374  										New:  "",
  3375  									},
  3376  								},
  3377  							},
  3378  						},
  3379  					},
  3380  				},
  3381  			},
  3382  		},
  3383  		{
  3384  			Name:       "Service Checks edited with context",
  3385  			Contextual: true,
  3386  			Old: &Task{
  3387  				Services: []*Service{
  3388  					{
  3389  						Name: "foo",
  3390  						Checks: []*ServiceCheck{
  3391  							{
  3392  								Name:          "foo",
  3393  								Type:          "http",
  3394  								Command:       "foo",
  3395  								Args:          []string{"foo"},
  3396  								Path:          "foo",
  3397  								Protocol:      "http",
  3398  								Interval:      1 * time.Second,
  3399  								Timeout:       1 * time.Second,
  3400  								InitialStatus: "critical",
  3401  							},
  3402  						},
  3403  					},
  3404  				},
  3405  			},
  3406  			New: &Task{
  3407  				Services: []*Service{
  3408  					{
  3409  						Name: "foo",
  3410  						Checks: []*ServiceCheck{
  3411  							{
  3412  								Name:          "foo",
  3413  								Type:          "tcp",
  3414  								Command:       "foo",
  3415  								Args:          []string{"foo"},
  3416  								Path:          "foo",
  3417  								Protocol:      "http",
  3418  								Interval:      1 * time.Second,
  3419  								Timeout:       1 * time.Second,
  3420  								InitialStatus: "passing",
  3421  							},
  3422  						},
  3423  					},
  3424  				},
  3425  			},
  3426  			Expected: &TaskDiff{
  3427  				Type: DiffTypeEdited,
  3428  				Objects: []*ObjectDiff{
  3429  					{
  3430  						Type: DiffTypeEdited,
  3431  						Name: "Service",
  3432  						Fields: []*FieldDiff{
  3433  							{
  3434  								Type: DiffTypeNone,
  3435  								Name: "AddressMode",
  3436  								Old:  "",
  3437  								New:  "",
  3438  							},
  3439  							{
  3440  								Type: DiffTypeNone,
  3441  								Name: "Name",
  3442  								Old:  "foo",
  3443  								New:  "foo",
  3444  							},
  3445  							{
  3446  								Type: DiffTypeNone,
  3447  								Name: "PortLabel",
  3448  								Old:  "",
  3449  								New:  "",
  3450  							},
  3451  						},
  3452  						Objects: []*ObjectDiff{
  3453  							{
  3454  								Type: DiffTypeEdited,
  3455  								Name: "Check",
  3456  								Fields: []*FieldDiff{
  3457  									{
  3458  										Type: DiffTypeNone,
  3459  										Name: "Command",
  3460  										Old:  "foo",
  3461  										New:  "foo",
  3462  									},
  3463  									{
  3464  										Type: DiffTypeEdited,
  3465  										Name: "InitialStatus",
  3466  										Old:  "critical",
  3467  										New:  "passing",
  3468  									},
  3469  									{
  3470  										Type: DiffTypeNone,
  3471  										Name: "Interval",
  3472  										Old:  "1000000000",
  3473  										New:  "1000000000",
  3474  									},
  3475  									{
  3476  										Type: DiffTypeNone,
  3477  										Name: "Name",
  3478  										Old:  "foo",
  3479  										New:  "foo",
  3480  									},
  3481  									{
  3482  										Type: DiffTypeNone,
  3483  										Name: "Path",
  3484  										Old:  "foo",
  3485  										New:  "foo",
  3486  									},
  3487  									{
  3488  										Type: DiffTypeNone,
  3489  										Name: "PortLabel",
  3490  										Old:  "",
  3491  										New:  "",
  3492  									},
  3493  									{
  3494  										Type: DiffTypeNone,
  3495  										Name: "Protocol",
  3496  										Old:  "http",
  3497  										New:  "http",
  3498  									},
  3499  									{
  3500  										Type: DiffTypeNone,
  3501  										Name: "TLSSkipVerify",
  3502  										Old:  "false",
  3503  										New:  "false",
  3504  									},
  3505  									{
  3506  										Type: DiffTypeNone,
  3507  										Name: "Timeout",
  3508  										Old:  "1000000000",
  3509  										New:  "1000000000",
  3510  									},
  3511  									{
  3512  										Type: DiffTypeEdited,
  3513  										Name: "Type",
  3514  										Old:  "http",
  3515  										New:  "tcp",
  3516  									},
  3517  								},
  3518  							},
  3519  						},
  3520  					},
  3521  				},
  3522  			},
  3523  		},
  3524  		{
  3525  			Name: "Vault added",
  3526  			Old:  &Task{},
  3527  			New: &Task{
  3528  				Vault: &Vault{
  3529  					Policies:     []string{"foo", "bar"},
  3530  					Env:          true,
  3531  					ChangeMode:   "signal",
  3532  					ChangeSignal: "SIGUSR1",
  3533  				},
  3534  			},
  3535  			Expected: &TaskDiff{
  3536  				Type: DiffTypeEdited,
  3537  				Objects: []*ObjectDiff{
  3538  					{
  3539  						Type: DiffTypeAdded,
  3540  						Name: "Vault",
  3541  						Fields: []*FieldDiff{
  3542  							{
  3543  								Type: DiffTypeAdded,
  3544  								Name: "ChangeMode",
  3545  								Old:  "",
  3546  								New:  "signal",
  3547  							},
  3548  							{
  3549  								Type: DiffTypeAdded,
  3550  								Name: "ChangeSignal",
  3551  								Old:  "",
  3552  								New:  "SIGUSR1",
  3553  							},
  3554  							{
  3555  								Type: DiffTypeAdded,
  3556  								Name: "Env",
  3557  								Old:  "",
  3558  								New:  "true",
  3559  							},
  3560  						},
  3561  						Objects: []*ObjectDiff{
  3562  							{
  3563  								Type: DiffTypeAdded,
  3564  								Name: "Policies",
  3565  								Fields: []*FieldDiff{
  3566  									{
  3567  										Type: DiffTypeAdded,
  3568  										Name: "Policies",
  3569  										Old:  "",
  3570  										New:  "bar",
  3571  									},
  3572  									{
  3573  										Type: DiffTypeAdded,
  3574  										Name: "Policies",
  3575  										Old:  "",
  3576  										New:  "foo",
  3577  									},
  3578  								},
  3579  							},
  3580  						},
  3581  					},
  3582  				},
  3583  			},
  3584  		},
  3585  		{
  3586  			Name: "Vault deleted",
  3587  			Old: &Task{
  3588  				Vault: &Vault{
  3589  					Policies:     []string{"foo", "bar"},
  3590  					Env:          true,
  3591  					ChangeMode:   "signal",
  3592  					ChangeSignal: "SIGUSR1",
  3593  				},
  3594  			},
  3595  			New: &Task{},
  3596  			Expected: &TaskDiff{
  3597  				Type: DiffTypeEdited,
  3598  				Objects: []*ObjectDiff{
  3599  					{
  3600  						Type: DiffTypeDeleted,
  3601  						Name: "Vault",
  3602  						Fields: []*FieldDiff{
  3603  							{
  3604  								Type: DiffTypeDeleted,
  3605  								Name: "ChangeMode",
  3606  								Old:  "signal",
  3607  								New:  "",
  3608  							},
  3609  							{
  3610  								Type: DiffTypeDeleted,
  3611  								Name: "ChangeSignal",
  3612  								Old:  "SIGUSR1",
  3613  								New:  "",
  3614  							},
  3615  							{
  3616  								Type: DiffTypeDeleted,
  3617  								Name: "Env",
  3618  								Old:  "true",
  3619  								New:  "",
  3620  							},
  3621  						},
  3622  						Objects: []*ObjectDiff{
  3623  							{
  3624  								Type: DiffTypeDeleted,
  3625  								Name: "Policies",
  3626  								Fields: []*FieldDiff{
  3627  									{
  3628  										Type: DiffTypeDeleted,
  3629  										Name: "Policies",
  3630  										Old:  "bar",
  3631  										New:  "",
  3632  									},
  3633  									{
  3634  										Type: DiffTypeDeleted,
  3635  										Name: "Policies",
  3636  										Old:  "foo",
  3637  										New:  "",
  3638  									},
  3639  								},
  3640  							},
  3641  						},
  3642  					},
  3643  				},
  3644  			},
  3645  		},
  3646  		{
  3647  			Name: "Vault edited",
  3648  			Old: &Task{
  3649  				Vault: &Vault{
  3650  					Policies:     []string{"foo", "bar"},
  3651  					Env:          true,
  3652  					ChangeMode:   "signal",
  3653  					ChangeSignal: "SIGUSR1",
  3654  				},
  3655  			},
  3656  			New: &Task{
  3657  				Vault: &Vault{
  3658  					Policies:     []string{"bar", "baz"},
  3659  					Env:          false,
  3660  					ChangeMode:   "restart",
  3661  					ChangeSignal: "foo",
  3662  				},
  3663  			},
  3664  			Expected: &TaskDiff{
  3665  				Type: DiffTypeEdited,
  3666  				Objects: []*ObjectDiff{
  3667  					{
  3668  						Type: DiffTypeEdited,
  3669  						Name: "Vault",
  3670  						Fields: []*FieldDiff{
  3671  							{
  3672  								Type: DiffTypeEdited,
  3673  								Name: "ChangeMode",
  3674  								Old:  "signal",
  3675  								New:  "restart",
  3676  							},
  3677  							{
  3678  								Type: DiffTypeEdited,
  3679  								Name: "ChangeSignal",
  3680  								Old:  "SIGUSR1",
  3681  								New:  "foo",
  3682  							},
  3683  							{
  3684  								Type: DiffTypeEdited,
  3685  								Name: "Env",
  3686  								Old:  "true",
  3687  								New:  "false",
  3688  							},
  3689  						},
  3690  						Objects: []*ObjectDiff{
  3691  							{
  3692  								Type: DiffTypeEdited,
  3693  								Name: "Policies",
  3694  								Fields: []*FieldDiff{
  3695  									{
  3696  										Type: DiffTypeAdded,
  3697  										Name: "Policies",
  3698  										Old:  "",
  3699  										New:  "baz",
  3700  									},
  3701  									{
  3702  										Type: DiffTypeDeleted,
  3703  										Name: "Policies",
  3704  										Old:  "foo",
  3705  										New:  "",
  3706  									},
  3707  								},
  3708  							},
  3709  						},
  3710  					},
  3711  				},
  3712  			},
  3713  		},
  3714  		{
  3715  			Name:       "Vault edited with context",
  3716  			Contextual: true,
  3717  			Old: &Task{
  3718  				Vault: &Vault{
  3719  					Policies:     []string{"foo", "bar"},
  3720  					Env:          true,
  3721  					ChangeMode:   "signal",
  3722  					ChangeSignal: "SIGUSR1",
  3723  				},
  3724  			},
  3725  			New: &Task{
  3726  				Vault: &Vault{
  3727  					Policies:     []string{"bar", "baz"},
  3728  					Env:          true,
  3729  					ChangeMode:   "signal",
  3730  					ChangeSignal: "SIGUSR1",
  3731  				},
  3732  			},
  3733  			Expected: &TaskDiff{
  3734  				Type: DiffTypeEdited,
  3735  				Objects: []*ObjectDiff{
  3736  					{
  3737  						Type: DiffTypeEdited,
  3738  						Name: "Vault",
  3739  						Fields: []*FieldDiff{
  3740  							{
  3741  								Type: DiffTypeNone,
  3742  								Name: "ChangeMode",
  3743  								Old:  "signal",
  3744  								New:  "signal",
  3745  							},
  3746  							{
  3747  								Type: DiffTypeNone,
  3748  								Name: "ChangeSignal",
  3749  								Old:  "SIGUSR1",
  3750  								New:  "SIGUSR1",
  3751  							},
  3752  							{
  3753  								Type: DiffTypeNone,
  3754  								Name: "Env",
  3755  								Old:  "true",
  3756  								New:  "true",
  3757  							},
  3758  						},
  3759  						Objects: []*ObjectDiff{
  3760  							{
  3761  								Type: DiffTypeEdited,
  3762  								Name: "Policies",
  3763  								Fields: []*FieldDiff{
  3764  									{
  3765  										Type: DiffTypeAdded,
  3766  										Name: "Policies",
  3767  										Old:  "",
  3768  										New:  "baz",
  3769  									},
  3770  									{
  3771  										Type: DiffTypeNone,
  3772  										Name: "Policies",
  3773  										Old:  "bar",
  3774  										New:  "bar",
  3775  									},
  3776  									{
  3777  										Type: DiffTypeDeleted,
  3778  										Name: "Policies",
  3779  										Old:  "foo",
  3780  										New:  "",
  3781  									},
  3782  								},
  3783  							},
  3784  						},
  3785  					},
  3786  				},
  3787  			},
  3788  		},
  3789  		{
  3790  			Name: "Template edited",
  3791  			Old: &Task{
  3792  				Templates: []*Template{
  3793  					{
  3794  						SourcePath:   "foo",
  3795  						DestPath:     "bar",
  3796  						EmbeddedTmpl: "baz",
  3797  						ChangeMode:   "bam",
  3798  						ChangeSignal: "SIGHUP",
  3799  						Splay:        1,
  3800  						Perms:        "0644",
  3801  					},
  3802  					{
  3803  						SourcePath:   "foo2",
  3804  						DestPath:     "bar2",
  3805  						EmbeddedTmpl: "baz2",
  3806  						ChangeMode:   "bam2",
  3807  						ChangeSignal: "SIGHUP2",
  3808  						Splay:        2,
  3809  						Perms:        "0666",
  3810  						Envvars:      true,
  3811  					},
  3812  				},
  3813  			},
  3814  			New: &Task{
  3815  				Templates: []*Template{
  3816  					{
  3817  						SourcePath:   "foo",
  3818  						DestPath:     "bar",
  3819  						EmbeddedTmpl: "baz",
  3820  						ChangeMode:   "bam",
  3821  						ChangeSignal: "SIGHUP",
  3822  						Splay:        1,
  3823  						Perms:        "0644",
  3824  					},
  3825  					{
  3826  						SourcePath:   "foo3",
  3827  						DestPath:     "bar3",
  3828  						EmbeddedTmpl: "baz3",
  3829  						ChangeMode:   "bam3",
  3830  						ChangeSignal: "SIGHUP3",
  3831  						Splay:        3,
  3832  						Perms:        "0776",
  3833  					},
  3834  				},
  3835  			},
  3836  			Expected: &TaskDiff{
  3837  				Type: DiffTypeEdited,
  3838  				Objects: []*ObjectDiff{
  3839  					{
  3840  						Type: DiffTypeAdded,
  3841  						Name: "Template",
  3842  						Fields: []*FieldDiff{
  3843  							{
  3844  								Type: DiffTypeAdded,
  3845  								Name: "ChangeMode",
  3846  								Old:  "",
  3847  								New:  "bam3",
  3848  							},
  3849  							{
  3850  								Type: DiffTypeAdded,
  3851  								Name: "ChangeSignal",
  3852  								Old:  "",
  3853  								New:  "SIGHUP3",
  3854  							},
  3855  							{
  3856  								Type: DiffTypeAdded,
  3857  								Name: "DestPath",
  3858  								Old:  "",
  3859  								New:  "bar3",
  3860  							},
  3861  							{
  3862  								Type: DiffTypeAdded,
  3863  								Name: "EmbeddedTmpl",
  3864  								Old:  "",
  3865  								New:  "baz3",
  3866  							},
  3867  							{
  3868  								Type: DiffTypeAdded,
  3869  								Name: "Envvars",
  3870  								Old:  "",
  3871  								New:  "false",
  3872  							},
  3873  							{
  3874  								Type: DiffTypeAdded,
  3875  								Name: "Perms",
  3876  								Old:  "",
  3877  								New:  "0776",
  3878  							},
  3879  							{
  3880  								Type: DiffTypeAdded,
  3881  								Name: "SourcePath",
  3882  								Old:  "",
  3883  								New:  "foo3",
  3884  							},
  3885  							{
  3886  								Type: DiffTypeAdded,
  3887  								Name: "Splay",
  3888  								Old:  "",
  3889  								New:  "3",
  3890  							},
  3891  						},
  3892  					},
  3893  					{
  3894  						Type: DiffTypeDeleted,
  3895  						Name: "Template",
  3896  						Fields: []*FieldDiff{
  3897  							{
  3898  								Type: DiffTypeDeleted,
  3899  								Name: "ChangeMode",
  3900  								Old:  "bam2",
  3901  								New:  "",
  3902  							},
  3903  							{
  3904  								Type: DiffTypeDeleted,
  3905  								Name: "ChangeSignal",
  3906  								Old:  "SIGHUP2",
  3907  								New:  "",
  3908  							},
  3909  							{
  3910  								Type: DiffTypeDeleted,
  3911  								Name: "DestPath",
  3912  								Old:  "bar2",
  3913  								New:  "",
  3914  							},
  3915  							{
  3916  								Type: DiffTypeDeleted,
  3917  								Name: "EmbeddedTmpl",
  3918  								Old:  "baz2",
  3919  								New:  "",
  3920  							},
  3921  							{
  3922  								Type: DiffTypeDeleted,
  3923  								Name: "Envvars",
  3924  								Old:  "true",
  3925  								New:  "",
  3926  							},
  3927  							{
  3928  								Type: DiffTypeDeleted,
  3929  								Name: "Perms",
  3930  								Old:  "0666",
  3931  								New:  "",
  3932  							},
  3933  							{
  3934  								Type: DiffTypeDeleted,
  3935  								Name: "SourcePath",
  3936  								Old:  "foo2",
  3937  								New:  "",
  3938  							},
  3939  							{
  3940  								Type: DiffTypeDeleted,
  3941  								Name: "Splay",
  3942  								Old:  "2",
  3943  								New:  "",
  3944  							},
  3945  						},
  3946  					},
  3947  				},
  3948  			},
  3949  		},
  3950  		{
  3951  			Name: "DispatchPayload added",
  3952  			Old:  &Task{},
  3953  			New: &Task{
  3954  				DispatchPayload: &DispatchPayloadConfig{
  3955  					File: "foo",
  3956  				},
  3957  			},
  3958  			Expected: &TaskDiff{
  3959  				Type: DiffTypeEdited,
  3960  				Objects: []*ObjectDiff{
  3961  					{
  3962  						Type: DiffTypeAdded,
  3963  						Name: "DispatchPayload",
  3964  						Fields: []*FieldDiff{
  3965  							{
  3966  								Type: DiffTypeAdded,
  3967  								Name: "File",
  3968  								Old:  "",
  3969  								New:  "foo",
  3970  							},
  3971  						},
  3972  					},
  3973  				},
  3974  			},
  3975  		},
  3976  		{
  3977  			Name: "DispatchPayload deleted",
  3978  			Old: &Task{
  3979  				DispatchPayload: &DispatchPayloadConfig{
  3980  					File: "foo",
  3981  				},
  3982  			},
  3983  			New: &Task{},
  3984  			Expected: &TaskDiff{
  3985  				Type: DiffTypeEdited,
  3986  				Objects: []*ObjectDiff{
  3987  					{
  3988  						Type: DiffTypeDeleted,
  3989  						Name: "DispatchPayload",
  3990  						Fields: []*FieldDiff{
  3991  							{
  3992  								Type: DiffTypeDeleted,
  3993  								Name: "File",
  3994  								Old:  "foo",
  3995  								New:  "",
  3996  							},
  3997  						},
  3998  					},
  3999  				},
  4000  			},
  4001  		},
  4002  		{
  4003  			Name: "Dispatch payload edited",
  4004  			Old: &Task{
  4005  				DispatchPayload: &DispatchPayloadConfig{
  4006  					File: "foo",
  4007  				},
  4008  			},
  4009  			New: &Task{
  4010  				DispatchPayload: &DispatchPayloadConfig{
  4011  					File: "bar",
  4012  				},
  4013  			},
  4014  			Expected: &TaskDiff{
  4015  				Type: DiffTypeEdited,
  4016  				Objects: []*ObjectDiff{
  4017  					{
  4018  						Type: DiffTypeEdited,
  4019  						Name: "DispatchPayload",
  4020  						Fields: []*FieldDiff{
  4021  							{
  4022  								Type: DiffTypeEdited,
  4023  								Name: "File",
  4024  								Old:  "foo",
  4025  								New:  "bar",
  4026  							},
  4027  						},
  4028  					},
  4029  				},
  4030  			},
  4031  		},
  4032  		{
  4033  			// Place holder for if more fields are added
  4034  			Name:       "DispatchPayload edited with context",
  4035  			Contextual: true,
  4036  			Old: &Task{
  4037  				DispatchPayload: &DispatchPayloadConfig{
  4038  					File: "foo",
  4039  				},
  4040  			},
  4041  			New: &Task{
  4042  				DispatchPayload: &DispatchPayloadConfig{
  4043  					File: "bar",
  4044  				},
  4045  			},
  4046  			Expected: &TaskDiff{
  4047  				Type: DiffTypeEdited,
  4048  				Objects: []*ObjectDiff{
  4049  					{
  4050  						Type: DiffTypeEdited,
  4051  						Name: "DispatchPayload",
  4052  						Fields: []*FieldDiff{
  4053  							{
  4054  								Type: DiffTypeEdited,
  4055  								Name: "File",
  4056  								Old:  "foo",
  4057  								New:  "bar",
  4058  							},
  4059  						},
  4060  					},
  4061  				},
  4062  			},
  4063  		},
  4064  	}
  4065  
  4066  	for i, c := range cases {
  4067  		t.Run(c.Name, func(t *testing.T) {
  4068  			actual, err := c.Old.Diff(c.New, c.Contextual)
  4069  			if c.Error && err == nil {
  4070  				t.Fatalf("case %d: expected errored", i+1)
  4071  			} else if err != nil {
  4072  				if !c.Error {
  4073  					t.Fatalf("case %d: errored %#v", i+1, err)
  4074  				} else {
  4075  					return
  4076  				}
  4077  			}
  4078  
  4079  			if !reflect.DeepEqual(actual, c.Expected) {
  4080  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  4081  					i+1, actual, c.Expected)
  4082  			}
  4083  		})
  4084  	}
  4085  }