github.com/emate/nomad@v0.8.2-wo-binpacking/jobspec/parse_test.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"path/filepath"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/api"
    11  	"github.com/hashicorp/nomad/helper"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/kr/pretty"
    14  
    15  	capi "github.com/hashicorp/consul/api"
    16  )
    17  
    18  func TestParse(t *testing.T) {
    19  	cases := []struct {
    20  		File   string
    21  		Result *api.Job
    22  		Err    bool
    23  	}{
    24  		{
    25  			"basic.hcl",
    26  			&api.Job{
    27  				ID:          helper.StringToPtr("binstore-storagelocker"),
    28  				Name:        helper.StringToPtr("binstore-storagelocker"),
    29  				Type:        helper.StringToPtr("batch"),
    30  				Priority:    helper.IntToPtr(52),
    31  				AllAtOnce:   helper.BoolToPtr(true),
    32  				Datacenters: []string{"us2", "eu1"},
    33  				Region:      helper.StringToPtr("fooregion"),
    34  				Namespace:   helper.StringToPtr("foonamespace"),
    35  				VaultToken:  helper.StringToPtr("foo"),
    36  
    37  				Meta: map[string]string{
    38  					"foo": "bar",
    39  				},
    40  
    41  				Constraints: []*api.Constraint{
    42  					{
    43  						LTarget: "kernel.os",
    44  						RTarget: "windows",
    45  						Operand: "=",
    46  					},
    47  				},
    48  
    49  				Update: &api.UpdateStrategy{
    50  					Stagger:         helper.TimeToPtr(60 * time.Second),
    51  					MaxParallel:     helper.IntToPtr(2),
    52  					HealthCheck:     helper.StringToPtr("manual"),
    53  					MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
    54  					HealthyDeadline: helper.TimeToPtr(10 * time.Minute),
    55  					AutoRevert:      helper.BoolToPtr(true),
    56  					Canary:          helper.IntToPtr(1),
    57  				},
    58  
    59  				TaskGroups: []*api.TaskGroup{
    60  					{
    61  						Name: helper.StringToPtr("outside"),
    62  						Tasks: []*api.Task{
    63  							{
    64  								Name:   "outside",
    65  								Driver: "java",
    66  								Config: map[string]interface{}{
    67  									"jar_path": "s3://my-cool-store/foo.jar",
    68  								},
    69  								Meta: map[string]string{
    70  									"my-cool-key": "foobar",
    71  								},
    72  							},
    73  						},
    74  					},
    75  
    76  					{
    77  						Name:  helper.StringToPtr("binsl"),
    78  						Count: helper.IntToPtr(5),
    79  						Constraints: []*api.Constraint{
    80  							{
    81  								LTarget: "kernel.os",
    82  								RTarget: "linux",
    83  								Operand: "=",
    84  							},
    85  						},
    86  						Meta: map[string]string{
    87  							"elb_mode":     "tcp",
    88  							"elb_interval": "10",
    89  							"elb_checks":   "3",
    90  						},
    91  						RestartPolicy: &api.RestartPolicy{
    92  							Interval: helper.TimeToPtr(10 * time.Minute),
    93  							Attempts: helper.IntToPtr(5),
    94  							Delay:    helper.TimeToPtr(15 * time.Second),
    95  							Mode:     helper.StringToPtr("delay"),
    96  						},
    97  						ReschedulePolicy: &api.ReschedulePolicy{
    98  							Interval: helper.TimeToPtr(12 * time.Hour),
    99  							Attempts: helper.IntToPtr(5),
   100  						},
   101  						EphemeralDisk: &api.EphemeralDisk{
   102  							Sticky: helper.BoolToPtr(true),
   103  							SizeMB: helper.IntToPtr(150),
   104  						},
   105  						Update: &api.UpdateStrategy{
   106  							MaxParallel:     helper.IntToPtr(3),
   107  							HealthCheck:     helper.StringToPtr("checks"),
   108  							MinHealthyTime:  helper.TimeToPtr(1 * time.Second),
   109  							HealthyDeadline: helper.TimeToPtr(1 * time.Minute),
   110  							AutoRevert:      helper.BoolToPtr(false),
   111  							Canary:          helper.IntToPtr(2),
   112  						},
   113  						Migrate: &api.MigrateStrategy{
   114  							MaxParallel:     helper.IntToPtr(2),
   115  							HealthCheck:     helper.StringToPtr("task_states"),
   116  							MinHealthyTime:  helper.TimeToPtr(11 * time.Second),
   117  							HealthyDeadline: helper.TimeToPtr(11 * time.Minute),
   118  						},
   119  						Tasks: []*api.Task{
   120  							{
   121  								Name:   "binstore",
   122  								Driver: "docker",
   123  								User:   "bob",
   124  								Config: map[string]interface{}{
   125  									"image": "hashicorp/binstore",
   126  									"labels": []map[string]interface{}{
   127  										{
   128  											"FOO": "bar",
   129  										},
   130  									},
   131  								},
   132  								Services: []*api.Service{
   133  									{
   134  										Tags:      []string{"foo", "bar"},
   135  										PortLabel: "http",
   136  										Checks: []api.ServiceCheck{
   137  											{
   138  												Name:      "check-name",
   139  												Type:      "tcp",
   140  												PortLabel: "admin",
   141  												Interval:  10 * time.Second,
   142  												Timeout:   2 * time.Second,
   143  												CheckRestart: &api.CheckRestart{
   144  													Limit:          3,
   145  													Grace:          helper.TimeToPtr(10 * time.Second),
   146  													IgnoreWarnings: true,
   147  												},
   148  											},
   149  										},
   150  									},
   151  								},
   152  								Env: map[string]string{
   153  									"HELLO": "world",
   154  									"LOREM": "ipsum",
   155  								},
   156  								Resources: &api.Resources{
   157  									CPU:      helper.IntToPtr(500),
   158  									MemoryMB: helper.IntToPtr(128),
   159  									Networks: []*api.NetworkResource{
   160  										{
   161  											MBits:         helper.IntToPtr(100),
   162  											ReservedPorts: []api.Port{{Label: "one", Value: 1}, {Label: "two", Value: 2}, {Label: "three", Value: 3}},
   163  											DynamicPorts:  []api.Port{{Label: "http", Value: 0}, {Label: "https", Value: 0}, {Label: "admin", Value: 0}},
   164  										},
   165  									},
   166  								},
   167  								KillTimeout:   helper.TimeToPtr(22 * time.Second),
   168  								ShutdownDelay: 11 * time.Second,
   169  								LogConfig: &api.LogConfig{
   170  									MaxFiles:      helper.IntToPtr(14),
   171  									MaxFileSizeMB: helper.IntToPtr(101),
   172  								},
   173  								Artifacts: []*api.TaskArtifact{
   174  									{
   175  										GetterSource: helper.StringToPtr("http://foo.com/artifact"),
   176  										GetterOptions: map[string]string{
   177  											"checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f",
   178  										},
   179  									},
   180  									{
   181  										GetterSource: helper.StringToPtr("http://bar.com/artifact"),
   182  										RelativeDest: helper.StringToPtr("test/foo/"),
   183  										GetterOptions: map[string]string{
   184  											"checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c",
   185  										},
   186  										GetterMode: helper.StringToPtr("file"),
   187  									},
   188  								},
   189  								Vault: &api.Vault{
   190  									Policies:   []string{"foo", "bar"},
   191  									Env:        helper.BoolToPtr(true),
   192  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   193  								},
   194  								Templates: []*api.Template{
   195  									{
   196  										SourcePath:   helper.StringToPtr("foo"),
   197  										DestPath:     helper.StringToPtr("foo"),
   198  										ChangeMode:   helper.StringToPtr("foo"),
   199  										ChangeSignal: helper.StringToPtr("foo"),
   200  										Splay:        helper.TimeToPtr(10 * time.Second),
   201  										Perms:        helper.StringToPtr("0644"),
   202  										Envvars:      helper.BoolToPtr(true),
   203  										VaultGrace:   helper.TimeToPtr(33 * time.Second),
   204  									},
   205  									{
   206  										SourcePath: helper.StringToPtr("bar"),
   207  										DestPath:   helper.StringToPtr("bar"),
   208  										ChangeMode: helper.StringToPtr(structs.TemplateChangeModeRestart),
   209  										Splay:      helper.TimeToPtr(5 * time.Second),
   210  										Perms:      helper.StringToPtr("777"),
   211  										LeftDelim:  helper.StringToPtr("--"),
   212  										RightDelim: helper.StringToPtr("__"),
   213  									},
   214  								},
   215  								Leader:     true,
   216  								KillSignal: "",
   217  							},
   218  							{
   219  								Name:   "storagelocker",
   220  								Driver: "docker",
   221  								User:   "",
   222  								Config: map[string]interface{}{
   223  									"image": "hashicorp/storagelocker",
   224  								},
   225  								Resources: &api.Resources{
   226  									CPU:      helper.IntToPtr(500),
   227  									MemoryMB: helper.IntToPtr(128),
   228  									IOPS:     helper.IntToPtr(30),
   229  								},
   230  								Constraints: []*api.Constraint{
   231  									{
   232  										LTarget: "kernel.arch",
   233  										RTarget: "amd64",
   234  										Operand: "=",
   235  									},
   236  								},
   237  								Vault: &api.Vault{
   238  									Policies:     []string{"foo", "bar"},
   239  									Env:          helper.BoolToPtr(false),
   240  									ChangeMode:   helper.StringToPtr(structs.VaultChangeModeSignal),
   241  									ChangeSignal: helper.StringToPtr("SIGUSR1"),
   242  								},
   243  							},
   244  						},
   245  					},
   246  				},
   247  			},
   248  			false,
   249  		},
   250  
   251  		{
   252  			"multi-network.hcl",
   253  			nil,
   254  			true,
   255  		},
   256  
   257  		{
   258  			"multi-resource.hcl",
   259  			nil,
   260  			true,
   261  		},
   262  
   263  		{
   264  			"multi-vault.hcl",
   265  			nil,
   266  			true,
   267  		},
   268  
   269  		{
   270  			"default-job.hcl",
   271  			&api.Job{
   272  				ID:   helper.StringToPtr("foo"),
   273  				Name: helper.StringToPtr("foo"),
   274  			},
   275  			false,
   276  		},
   277  
   278  		{
   279  			"version-constraint.hcl",
   280  			&api.Job{
   281  				ID:   helper.StringToPtr("foo"),
   282  				Name: helper.StringToPtr("foo"),
   283  				Constraints: []*api.Constraint{
   284  					{
   285  						LTarget: "$attr.kernel.version",
   286  						RTarget: "~> 3.2",
   287  						Operand: structs.ConstraintVersion,
   288  					},
   289  				},
   290  			},
   291  			false,
   292  		},
   293  
   294  		{
   295  			"regexp-constraint.hcl",
   296  			&api.Job{
   297  				ID:   helper.StringToPtr("foo"),
   298  				Name: helper.StringToPtr("foo"),
   299  				Constraints: []*api.Constraint{
   300  					{
   301  						LTarget: "$attr.kernel.version",
   302  						RTarget: "[0-9.]+",
   303  						Operand: structs.ConstraintRegex,
   304  					},
   305  				},
   306  			},
   307  			false,
   308  		},
   309  
   310  		{
   311  			"set-contains-constraint.hcl",
   312  			&api.Job{
   313  				ID:   helper.StringToPtr("foo"),
   314  				Name: helper.StringToPtr("foo"),
   315  				Constraints: []*api.Constraint{
   316  					{
   317  						LTarget: "$meta.data",
   318  						RTarget: "foo,bar,baz",
   319  						Operand: structs.ConstraintSetContains,
   320  					},
   321  				},
   322  			},
   323  			false,
   324  		},
   325  
   326  		{
   327  			"distinctHosts-constraint.hcl",
   328  			&api.Job{
   329  				ID:   helper.StringToPtr("foo"),
   330  				Name: helper.StringToPtr("foo"),
   331  				Constraints: []*api.Constraint{
   332  					{
   333  						Operand: structs.ConstraintDistinctHosts,
   334  					},
   335  				},
   336  			},
   337  			false,
   338  		},
   339  
   340  		{
   341  			"distinctProperty-constraint.hcl",
   342  			&api.Job{
   343  				ID:   helper.StringToPtr("foo"),
   344  				Name: helper.StringToPtr("foo"),
   345  				Constraints: []*api.Constraint{
   346  					{
   347  						Operand: structs.ConstraintDistinctProperty,
   348  						LTarget: "${meta.rack}",
   349  					},
   350  				},
   351  			},
   352  			false,
   353  		},
   354  
   355  		{
   356  			"periodic-cron.hcl",
   357  			&api.Job{
   358  				ID:   helper.StringToPtr("foo"),
   359  				Name: helper.StringToPtr("foo"),
   360  				Periodic: &api.PeriodicConfig{
   361  					SpecType:        helper.StringToPtr(api.PeriodicSpecCron),
   362  					Spec:            helper.StringToPtr("*/5 * * *"),
   363  					ProhibitOverlap: helper.BoolToPtr(true),
   364  					TimeZone:        helper.StringToPtr("Europe/Minsk"),
   365  				},
   366  			},
   367  			false,
   368  		},
   369  
   370  		{
   371  			"specify-job.hcl",
   372  			&api.Job{
   373  				ID:   helper.StringToPtr("job1"),
   374  				Name: helper.StringToPtr("My Job"),
   375  			},
   376  			false,
   377  		},
   378  
   379  		{
   380  			"task-nested-config.hcl",
   381  			&api.Job{
   382  				ID:   helper.StringToPtr("foo"),
   383  				Name: helper.StringToPtr("foo"),
   384  				TaskGroups: []*api.TaskGroup{
   385  					{
   386  						Name: helper.StringToPtr("bar"),
   387  						Tasks: []*api.Task{
   388  							{
   389  								Name:   "bar",
   390  								Driver: "docker",
   391  								Config: map[string]interface{}{
   392  									"image": "hashicorp/image",
   393  									"port_map": []map[string]interface{}{
   394  										{
   395  											"db": 1234,
   396  										},
   397  									},
   398  								},
   399  							},
   400  						},
   401  					},
   402  				},
   403  			},
   404  			false,
   405  		},
   406  
   407  		{
   408  			"bad-artifact.hcl",
   409  			nil,
   410  			true,
   411  		},
   412  
   413  		{
   414  			"artifacts.hcl",
   415  			&api.Job{
   416  				ID:   helper.StringToPtr("binstore-storagelocker"),
   417  				Name: helper.StringToPtr("binstore-storagelocker"),
   418  				TaskGroups: []*api.TaskGroup{
   419  					{
   420  						Name: helper.StringToPtr("binsl"),
   421  						Tasks: []*api.Task{
   422  							{
   423  								Name:   "binstore",
   424  								Driver: "docker",
   425  								Artifacts: []*api.TaskArtifact{
   426  									{
   427  										GetterSource:  helper.StringToPtr("http://foo.com/bar"),
   428  										GetterOptions: map[string]string{"foo": "bar"},
   429  										RelativeDest:  helper.StringToPtr(""),
   430  									},
   431  									{
   432  										GetterSource:  helper.StringToPtr("http://foo.com/baz"),
   433  										GetterOptions: nil,
   434  										RelativeDest:  nil,
   435  									},
   436  									{
   437  										GetterSource:  helper.StringToPtr("http://foo.com/bam"),
   438  										GetterOptions: nil,
   439  										RelativeDest:  helper.StringToPtr("var/foo"),
   440  									},
   441  								},
   442  							},
   443  						},
   444  					},
   445  				},
   446  			},
   447  			false,
   448  		},
   449  		{
   450  			"service-check-initial-status.hcl",
   451  			&api.Job{
   452  				ID:   helper.StringToPtr("check_initial_status"),
   453  				Name: helper.StringToPtr("check_initial_status"),
   454  				Type: helper.StringToPtr("service"),
   455  				TaskGroups: []*api.TaskGroup{
   456  					{
   457  						Name:  helper.StringToPtr("group"),
   458  						Count: helper.IntToPtr(1),
   459  						Tasks: []*api.Task{
   460  							{
   461  								Name: "task",
   462  								Services: []*api.Service{
   463  									{
   464  										Tags:      []string{"foo", "bar"},
   465  										PortLabel: "http",
   466  										Checks: []api.ServiceCheck{
   467  											{
   468  												Name:          "check-name",
   469  												Type:          "http",
   470  												Path:          "/",
   471  												Interval:      10 * time.Second,
   472  												Timeout:       2 * time.Second,
   473  												InitialStatus: capi.HealthPassing,
   474  												Method:        "POST",
   475  												Header: map[string][]string{
   476  													"Authorization": {"Basic ZWxhc3RpYzpjaGFuZ2VtZQ=="},
   477  												},
   478  											},
   479  										},
   480  									},
   481  								},
   482  							},
   483  						},
   484  					},
   485  				},
   486  			},
   487  			false,
   488  		},
   489  		{
   490  			"service-check-bad-header.hcl",
   491  			nil,
   492  			true,
   493  		},
   494  		{
   495  			"service-check-bad-header-2.hcl",
   496  			nil,
   497  			true,
   498  		},
   499  		{
   500  			// TODO This should be pushed into the API
   501  			"vault_inheritance.hcl",
   502  			&api.Job{
   503  				ID:   helper.StringToPtr("example"),
   504  				Name: helper.StringToPtr("example"),
   505  				TaskGroups: []*api.TaskGroup{
   506  					{
   507  						Name: helper.StringToPtr("cache"),
   508  						Tasks: []*api.Task{
   509  							{
   510  								Name: "redis",
   511  								Vault: &api.Vault{
   512  									Policies:   []string{"group"},
   513  									Env:        helper.BoolToPtr(true),
   514  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   515  								},
   516  							},
   517  							{
   518  								Name: "redis2",
   519  								Vault: &api.Vault{
   520  									Policies:   []string{"task"},
   521  									Env:        helper.BoolToPtr(false),
   522  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   523  								},
   524  							},
   525  						},
   526  					},
   527  					{
   528  						Name: helper.StringToPtr("cache2"),
   529  						Tasks: []*api.Task{
   530  							{
   531  								Name: "redis",
   532  								Vault: &api.Vault{
   533  									Policies:   []string{"job"},
   534  									Env:        helper.BoolToPtr(true),
   535  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   536  								},
   537  							},
   538  						},
   539  					},
   540  				},
   541  			},
   542  			false,
   543  		},
   544  		{
   545  			"parameterized_job.hcl",
   546  			&api.Job{
   547  				ID:   helper.StringToPtr("parameterized_job"),
   548  				Name: helper.StringToPtr("parameterized_job"),
   549  
   550  				ParameterizedJob: &api.ParameterizedJobConfig{
   551  					Payload:      "required",
   552  					MetaRequired: []string{"foo", "bar"},
   553  					MetaOptional: []string{"baz", "bam"},
   554  				},
   555  
   556  				TaskGroups: []*api.TaskGroup{
   557  					{
   558  						Name: helper.StringToPtr("foo"),
   559  						Tasks: []*api.Task{
   560  							{
   561  								Name:   "bar",
   562  								Driver: "docker",
   563  								DispatchPayload: &api.DispatchPayloadConfig{
   564  									File: "foo/bar",
   565  								},
   566  							},
   567  						},
   568  					},
   569  				},
   570  			},
   571  			false,
   572  		},
   573  		{
   574  			"job-with-kill-signal.hcl",
   575  			&api.Job{
   576  				ID:   helper.StringToPtr("foo"),
   577  				Name: helper.StringToPtr("foo"),
   578  				TaskGroups: []*api.TaskGroup{
   579  					{
   580  						Name: helper.StringToPtr("bar"),
   581  						Tasks: []*api.Task{
   582  							{
   583  								Name:       "bar",
   584  								Driver:     "docker",
   585  								KillSignal: "SIGQUIT",
   586  								Config: map[string]interface{}{
   587  									"image": "hashicorp/image",
   588  								},
   589  							},
   590  						},
   591  					},
   592  				},
   593  			},
   594  			false,
   595  		},
   596  		{
   597  			"service-check-driver-address.hcl",
   598  			&api.Job{
   599  				ID:   helper.StringToPtr("address_mode_driver"),
   600  				Name: helper.StringToPtr("address_mode_driver"),
   601  				Type: helper.StringToPtr("service"),
   602  				TaskGroups: []*api.TaskGroup{
   603  					{
   604  						Name: helper.StringToPtr("group"),
   605  						Tasks: []*api.Task{
   606  							{
   607  								Name: "task",
   608  								Services: []*api.Service{
   609  									{
   610  										Name:        "http-service",
   611  										PortLabel:   "http",
   612  										AddressMode: "auto",
   613  										Checks: []api.ServiceCheck{
   614  											{
   615  												Name:        "http-check",
   616  												Type:        "http",
   617  												Path:        "/",
   618  												PortLabel:   "http",
   619  												AddressMode: "driver",
   620  											},
   621  										},
   622  									},
   623  									{
   624  										Name:        "random-service",
   625  										PortLabel:   "9000",
   626  										AddressMode: "driver",
   627  										Checks: []api.ServiceCheck{
   628  											{
   629  												Name:        "random-check",
   630  												Type:        "tcp",
   631  												PortLabel:   "9001",
   632  												AddressMode: "driver",
   633  											},
   634  										},
   635  									},
   636  								},
   637  							},
   638  						},
   639  					},
   640  				},
   641  			},
   642  			false,
   643  		},
   644  		{
   645  			"service-check-restart.hcl",
   646  			&api.Job{
   647  				ID:   helper.StringToPtr("service_check_restart"),
   648  				Name: helper.StringToPtr("service_check_restart"),
   649  				Type: helper.StringToPtr("service"),
   650  				TaskGroups: []*api.TaskGroup{
   651  					{
   652  						Name: helper.StringToPtr("group"),
   653  						Tasks: []*api.Task{
   654  							{
   655  								Name: "task",
   656  								Services: []*api.Service{
   657  									{
   658  										Name: "http-service",
   659  										CheckRestart: &api.CheckRestart{
   660  											Limit:          3,
   661  											Grace:          helper.TimeToPtr(10 * time.Second),
   662  											IgnoreWarnings: true,
   663  										},
   664  										Checks: []api.ServiceCheck{
   665  											{
   666  												Name:      "random-check",
   667  												Type:      "tcp",
   668  												PortLabel: "9001",
   669  											},
   670  										},
   671  									},
   672  								},
   673  							},
   674  						},
   675  					},
   676  				},
   677  			},
   678  			false,
   679  		},
   680  		{
   681  			"reschedule-job.hcl",
   682  			&api.Job{
   683  				ID:          helper.StringToPtr("foo"),
   684  				Name:        helper.StringToPtr("foo"),
   685  				Type:        helper.StringToPtr("batch"),
   686  				Datacenters: []string{"dc1"},
   687  				Reschedule: &api.ReschedulePolicy{
   688  					Attempts:      helper.IntToPtr(15),
   689  					Interval:      helper.TimeToPtr(30 * time.Minute),
   690  					DelayFunction: helper.StringToPtr("constant"),
   691  					Delay:         helper.TimeToPtr(10 * time.Second),
   692  				},
   693  				TaskGroups: []*api.TaskGroup{
   694  					{
   695  						Name:  helper.StringToPtr("bar"),
   696  						Count: helper.IntToPtr(3),
   697  						Tasks: []*api.Task{
   698  							{
   699  								Name:   "bar",
   700  								Driver: "raw_exec",
   701  								Config: map[string]interface{}{
   702  									"command": "bash",
   703  									"args":    []interface{}{"-c", "echo hi"},
   704  								},
   705  							},
   706  						},
   707  					},
   708  				},
   709  			},
   710  			false,
   711  		},
   712  		{
   713  			"reschedule-job-unlimited.hcl",
   714  			&api.Job{
   715  				ID:          helper.StringToPtr("foo"),
   716  				Name:        helper.StringToPtr("foo"),
   717  				Type:        helper.StringToPtr("batch"),
   718  				Datacenters: []string{"dc1"},
   719  				Reschedule: &api.ReschedulePolicy{
   720  					DelayFunction: helper.StringToPtr("exponential"),
   721  					Delay:         helper.TimeToPtr(10 * time.Second),
   722  					MaxDelay:      helper.TimeToPtr(120 * time.Second),
   723  					Unlimited:     helper.BoolToPtr(true),
   724  				},
   725  				TaskGroups: []*api.TaskGroup{
   726  					{
   727  						Name:  helper.StringToPtr("bar"),
   728  						Count: helper.IntToPtr(3),
   729  						Tasks: []*api.Task{
   730  							{
   731  								Name:   "bar",
   732  								Driver: "raw_exec",
   733  								Config: map[string]interface{}{
   734  									"command": "bash",
   735  									"args":    []interface{}{"-c", "echo hi"},
   736  								},
   737  							},
   738  						},
   739  					},
   740  				},
   741  			},
   742  			false,
   743  		},
   744  		{
   745  			"migrate-job.hcl",
   746  			&api.Job{
   747  				ID:          helper.StringToPtr("foo"),
   748  				Name:        helper.StringToPtr("foo"),
   749  				Type:        helper.StringToPtr("batch"),
   750  				Datacenters: []string{"dc1"},
   751  				Migrate: &api.MigrateStrategy{
   752  					MaxParallel:     helper.IntToPtr(2),
   753  					HealthCheck:     helper.StringToPtr("task_states"),
   754  					MinHealthyTime:  helper.TimeToPtr(11 * time.Second),
   755  					HealthyDeadline: helper.TimeToPtr(11 * time.Minute),
   756  				},
   757  				TaskGroups: []*api.TaskGroup{
   758  					{
   759  						Name:  helper.StringToPtr("bar"),
   760  						Count: helper.IntToPtr(3),
   761  						Migrate: &api.MigrateStrategy{
   762  							MaxParallel:     helper.IntToPtr(3),
   763  							HealthCheck:     helper.StringToPtr("checks"),
   764  							MinHealthyTime:  helper.TimeToPtr(1 * time.Second),
   765  							HealthyDeadline: helper.TimeToPtr(1 * time.Minute),
   766  						},
   767  						Tasks: []*api.Task{
   768  							{
   769  								Name:   "bar",
   770  								Driver: "raw_exec",
   771  								Config: map[string]interface{}{
   772  									"command": "bash",
   773  									"args":    []interface{}{"-c", "echo hi"},
   774  								},
   775  							},
   776  						},
   777  					},
   778  				},
   779  			},
   780  			false,
   781  		},
   782  	}
   783  
   784  	for _, tc := range cases {
   785  		t.Logf("Testing parse: %s", tc.File)
   786  
   787  		path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File))
   788  		if err != nil {
   789  			t.Fatalf("file: %s\n\n%s", tc.File, err)
   790  			continue
   791  		}
   792  
   793  		actual, err := ParseFile(path)
   794  		if (err != nil) != tc.Err {
   795  			t.Fatalf("file: %s\n\n%s", tc.File, err)
   796  			continue
   797  		}
   798  
   799  		if !reflect.DeepEqual(actual, tc.Result) {
   800  			for _, d := range pretty.Diff(actual, tc.Result) {
   801  				t.Logf(d)
   802  			}
   803  			t.Fatalf("file: %s", tc.File)
   804  		}
   805  	}
   806  }
   807  
   808  func TestBadPorts(t *testing.T) {
   809  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl"))
   810  	if err != nil {
   811  		t.Fatalf("Can't get absolute path for file: %s", err)
   812  	}
   813  
   814  	_, err = ParseFile(path)
   815  
   816  	if !strings.Contains(err.Error(), errPortLabel.Error()) {
   817  		t.Fatalf("\nExpected error\n  %s\ngot\n  %v", errPortLabel, err)
   818  	}
   819  }
   820  
   821  func TestOverlappingPorts(t *testing.T) {
   822  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl"))
   823  	if err != nil {
   824  		t.Fatalf("Can't get absolute path for file: %s", err)
   825  	}
   826  
   827  	_, err = ParseFile(path)
   828  
   829  	if err == nil {
   830  		t.Fatalf("Expected an error")
   831  	}
   832  
   833  	if !strings.Contains(err.Error(), "found a port label collision") {
   834  		t.Fatalf("Expected collision error; got %v", err)
   835  	}
   836  }
   837  
   838  func TestIncorrectKey(t *testing.T) {
   839  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl"))
   840  	if err != nil {
   841  		t.Fatalf("Can't get absolute path for file: %s", err)
   842  	}
   843  
   844  	_, err = ParseFile(path)
   845  
   846  	if err == nil {
   847  		t.Fatalf("Expected an error")
   848  	}
   849  
   850  	if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service: 'foo', check -> invalid key: nterval") {
   851  		t.Fatalf("Expected key error; got %v", err)
   852  	}
   853  }