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