From a9beef7edd71278de6b84fb5d522466bba4d17c0 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Thu, 5 Sep 2024 09:02:45 -0400 Subject: [PATCH] jobspec: remove HCL1 support (#23912) This changeset removes support for parsing jobspecs via the long-deprecated HCLv1. Fixes: https://github.com/hashicorp/nomad/issues/20195 Ref: https://hashicorp.atlassian.net/browse/NET-10220 --- .changelog/23912.txt | 3 + GNUmakefile | 1 - api/jobs.go | 10 +- api/jobs_test.go | 25 +- ci/test-core.json | 1 - command/agent/job_endpoint.go | 23 +- command/helpers.go | 28 +- command/helpers_test.go | 80 - command/job_plan.go | 11 +- command/job_plan_test.go | 19 - command/job_run.go | 11 +- command/job_run_test.go | 18 - command/job_validate.go | 11 +- command/job_validate_test.go | 16 - e2e/rescheduling/rescheduling_test.go | 13 +- e2e/volumes/volumes_test.go | 3 +- jobspec/.copywrite.hcl | 12 - jobspec/LICENSE | 365 --- jobspec/helper.go | 116 - jobspec/helper_test.go | 12 - jobspec/parse.go | 569 ----- jobspec/parse_group.go | 457 ---- jobspec/parse_job.go | 307 --- jobspec/parse_multiregion.go | 165 -- jobspec/parse_network.go | 138 -- jobspec/parse_service.go | 1351 ----------- jobspec/parse_task.go | 798 ------- jobspec/parse_test.go | 2041 ----------------- jobspec/test-fixtures/artifacts.hcl | 37 - jobspec/test-fixtures/bad-artifact.hcl | 18 - jobspec/test-fixtures/bad-ports.hcl | 104 - jobspec/test-fixtures/basic.hcl | 391 ---- jobspec/test-fixtures/basic_wrong_key.hcl | 138 -- jobspec/test-fixtures/consul-namespace.hcl | 10 - jobspec/test-fixtures/csi-plugin.hcl | 17 - jobspec/test-fixtures/default-job.hcl | 4 - .../distinctHosts-constraint.hcl | 8 - .../distinctProperty-constraint.hcl | 8 - .../test-fixtures/incorrect-service-def.hcl | 96 - .../test-fixtures/job-with-kill-signal.hcl | 13 - jobspec/test-fixtures/migrate-job.hcl | 34 - jobspec/test-fixtures/multi-network.hcl | 29 - jobspec/test-fixtures/multi-resource.hcl | 30 - jobspec/test-fixtures/multi-vault.hcl | 30 - jobspec/test-fixtures/multiregion.hcl | 29 - jobspec/test-fixtures/overlapping-ports.hcl | 106 - jobspec/test-fixtures/parameterized_job.hcl | 20 - jobspec/test-fixtures/parse-ports.hcl | 14 - jobspec/test-fixtures/periodic-cron.hcl | 10 - jobspec/test-fixtures/periodic-crons.hcl | 13 - jobspec/test-fixtures/regexp-constraint.hcl | 9 - .../reschedule-job-unlimited.hcl | 27 - jobspec/test-fixtures/reschedule-job.hcl | 27 - jobspec/test-fixtures/resources-cores.hcl | 17 - .../service-check-bad-header-2.hcl | 31 - .../service-check-bad-header.hcl | 31 - .../service-check-driver-address.hcl | 41 - .../service-check-initial-status.hcl | 31 - .../test-fixtures/service-check-pass-fail.hcl | 30 - .../test-fixtures/service-check-restart.hcl | 26 - .../service-enable-tag-override.hcl | 15 - jobspec/test-fixtures/service-meta.hcl | 18 - jobspec/test-fixtures/service-provider.hcl | 17 - .../test-fixtures/service-tagged-address.hcl | 15 - .../test-fixtures/set-contains-constraint.hcl | 9 - jobspec/test-fixtures/specify-job.hcl | 7 - jobspec/test-fixtures/task-nested-config.hcl | 16 - .../task-scaling-policy-invalid-resource.hcl | 20 - .../task-scaling-policy-invalid-type.hcl | 21 - .../task-scaling-policy-missing-name.hcl | 20 - .../task-scaling-policy-multi-cpu.hcl | 30 - .../task-scaling-policy-multi-name.hcl | 20 - jobspec/test-fixtures/task-scaling-policy.hcl | 30 - .../tg-network-with-hostname.hcl | 37 - jobspec/test-fixtures/tg-network.hcl | 87 - .../tg-scaling-policy-invalid-type.hcl | 20 - .../tg-scaling-policy-minimal.hcl | 10 - .../tg-scaling-policy-missing-max.hcl | 10 - .../tg-scaling-policy-multi-policy.hcl | 22 - .../tg-scaling-policy-with-label.hcl | 19 - jobspec/test-fixtures/tg-scaling-policy.hcl | 23 - .../test-fixtures/tg-service-check-expose.hcl | 26 - jobspec/test-fixtures/tg-service-check.hcl | 37 - .../tg-service-connect-gateway-ingress.hcl | 77 - .../tg-service-connect-gateway-mesh.hcl | 22 - ...tg-service-connect-gateway-terminating.hcl | 50 - .../tg-service-connect-local-service.hcl | 21 - .../tg-service-connect-native.hcl | 15 - .../tg-service-connect-proxy.hcl | 61 - .../tg-service-connect-resources.hcl | 23 - ...g-service-connect-sidecar_disablecheck.hcl | 18 - .../tg-service-connect-sidecar_meta.hcl | 22 - .../tg-service-connect-sidecar_task-name.hcl | 20 - .../tg-service-enable-tag-override.hcl | 11 - .../test-fixtures/tg-service-proxy-expose.hcl | 32 - jobspec/test-fixtures/vault_inheritance.hcl | 28 - jobspec/test-fixtures/version-constraint.hcl | 9 - jobspec/utils.go | 37 - jobspec/utils_test.go | 45 - jobspec2/parse_test.go | 50 - .../test-fixtures/config-compatibility.hcl | 81 - nomad/structs/structs.go | 1 + website/content/api-docs/jobs.mdx | 2 +- website/content/docs/commands/job/plan.mdx | 5 +- website/content/docs/commands/job/run.mdx | 5 +- .../content/docs/commands/job/validate.mdx | 5 +- .../docs/job-specification/hcl2/index.mdx | 13 +- .../content/docs/upgrade/upgrade-specific.mdx | 8 + 108 files changed, 42 insertions(+), 9181 deletions(-) create mode 100644 .changelog/23912.txt delete mode 100644 jobspec/.copywrite.hcl delete mode 100644 jobspec/LICENSE delete mode 100644 jobspec/helper.go delete mode 100644 jobspec/helper_test.go delete mode 100644 jobspec/parse.go delete mode 100644 jobspec/parse_group.go delete mode 100644 jobspec/parse_job.go delete mode 100644 jobspec/parse_multiregion.go delete mode 100644 jobspec/parse_network.go delete mode 100644 jobspec/parse_service.go delete mode 100644 jobspec/parse_task.go delete mode 100644 jobspec/parse_test.go delete mode 100644 jobspec/test-fixtures/artifacts.hcl delete mode 100644 jobspec/test-fixtures/bad-artifact.hcl delete mode 100644 jobspec/test-fixtures/bad-ports.hcl delete mode 100644 jobspec/test-fixtures/basic.hcl delete mode 100644 jobspec/test-fixtures/basic_wrong_key.hcl delete mode 100644 jobspec/test-fixtures/consul-namespace.hcl delete mode 100644 jobspec/test-fixtures/csi-plugin.hcl delete mode 100644 jobspec/test-fixtures/default-job.hcl delete mode 100644 jobspec/test-fixtures/distinctHosts-constraint.hcl delete mode 100644 jobspec/test-fixtures/distinctProperty-constraint.hcl delete mode 100644 jobspec/test-fixtures/incorrect-service-def.hcl delete mode 100644 jobspec/test-fixtures/job-with-kill-signal.hcl delete mode 100644 jobspec/test-fixtures/migrate-job.hcl delete mode 100644 jobspec/test-fixtures/multi-network.hcl delete mode 100644 jobspec/test-fixtures/multi-resource.hcl delete mode 100644 jobspec/test-fixtures/multi-vault.hcl delete mode 100644 jobspec/test-fixtures/multiregion.hcl delete mode 100644 jobspec/test-fixtures/overlapping-ports.hcl delete mode 100644 jobspec/test-fixtures/parameterized_job.hcl delete mode 100644 jobspec/test-fixtures/parse-ports.hcl delete mode 100644 jobspec/test-fixtures/periodic-cron.hcl delete mode 100644 jobspec/test-fixtures/periodic-crons.hcl delete mode 100644 jobspec/test-fixtures/regexp-constraint.hcl delete mode 100644 jobspec/test-fixtures/reschedule-job-unlimited.hcl delete mode 100644 jobspec/test-fixtures/reschedule-job.hcl delete mode 100644 jobspec/test-fixtures/resources-cores.hcl delete mode 100644 jobspec/test-fixtures/service-check-bad-header-2.hcl delete mode 100644 jobspec/test-fixtures/service-check-bad-header.hcl delete mode 100644 jobspec/test-fixtures/service-check-driver-address.hcl delete mode 100644 jobspec/test-fixtures/service-check-initial-status.hcl delete mode 100644 jobspec/test-fixtures/service-check-pass-fail.hcl delete mode 100644 jobspec/test-fixtures/service-check-restart.hcl delete mode 100644 jobspec/test-fixtures/service-enable-tag-override.hcl delete mode 100644 jobspec/test-fixtures/service-meta.hcl delete mode 100644 jobspec/test-fixtures/service-provider.hcl delete mode 100644 jobspec/test-fixtures/service-tagged-address.hcl delete mode 100644 jobspec/test-fixtures/set-contains-constraint.hcl delete mode 100644 jobspec/test-fixtures/specify-job.hcl delete mode 100644 jobspec/test-fixtures/task-nested-config.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy-invalid-resource.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy-invalid-type.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy-missing-name.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy-multi-cpu.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy-multi-name.hcl delete mode 100644 jobspec/test-fixtures/task-scaling-policy.hcl delete mode 100644 jobspec/test-fixtures/tg-network-with-hostname.hcl delete mode 100644 jobspec/test-fixtures/tg-network.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy-invalid-type.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy-minimal.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy-missing-max.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy-multi-policy.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy-with-label.hcl delete mode 100644 jobspec/test-fixtures/tg-scaling-policy.hcl delete mode 100644 jobspec/test-fixtures/tg-service-check-expose.hcl delete mode 100644 jobspec/test-fixtures/tg-service-check.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-gateway-ingress.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-gateway-mesh.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-gateway-terminating.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-local-service.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-native.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-proxy.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-resources.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-sidecar_disablecheck.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-sidecar_meta.hcl delete mode 100644 jobspec/test-fixtures/tg-service-connect-sidecar_task-name.hcl delete mode 100644 jobspec/test-fixtures/tg-service-enable-tag-override.hcl delete mode 100644 jobspec/test-fixtures/tg-service-proxy-expose.hcl delete mode 100644 jobspec/test-fixtures/vault_inheritance.hcl delete mode 100644 jobspec/test-fixtures/version-constraint.hcl delete mode 100644 jobspec/utils.go delete mode 100644 jobspec/utils_test.go delete mode 100644 jobspec2/test-fixtures/config-compatibility.hcl diff --git a/.changelog/23912.txt b/.changelog/23912.txt new file mode 100644 index 000000000..a53a583a7 --- /dev/null +++ b/.changelog/23912.txt @@ -0,0 +1,3 @@ +```release-note:breaking-change +jobspec: Removed support for HCLv1 +``` diff --git a/GNUmakefile b/GNUmakefile index 1a5982a00..fbc49415d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -443,7 +443,6 @@ copywriteheaders: cd api && $(CURDIR)/scripts/copywrite-exceptions.sh cd drivers/shared && $(CURDIR)/scripts/copywrite-exceptions.sh cd plugins && $(CURDIR)/scripts/copywrite-exceptions.sh - cd jobspec && $(CURDIR)/scripts/copywrite-exceptions.sh cd jobspec2 && $(CURDIR)/scripts/copywrite-exceptions.sh cd demo && $(CURDIR)/scripts/copywrite-exceptions.sh diff --git a/api/jobs.go b/api/jobs.go index a0788f517..bd52927d8 100644 --- a/api/jobs.go +++ b/api/jobs.go @@ -75,9 +75,6 @@ type JobsParseRequest struct { // JobHCL is an hcl jobspec JobHCL string - // HCLv1 indicates whether the JobHCL should be parsed with the hcl v1 parser - HCLv1 bool `json:"hclv1,omitempty"` - // Variables are HCL2 variables associated with the job. Only works with hcl2. // // Interpreted as if it were the content of a variables file. @@ -105,7 +102,7 @@ func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) { } // ParseHCLOpts is used to request the server convert the HCL representation of a -// Job to JSON on our behalf. Accepts HCL1 or HCL2 jobs as input. +// Job to JSON on our behalf. Only accepts HCL2 jobs as input. func (j *Jobs) ParseHCLOpts(req *JobsParseRequest) (*Job, error) { var job Job _, err := j.client.put("/v1/jobs/parse", req, &job, nil) @@ -929,10 +926,11 @@ type ParameterizedJobConfig struct { // the job submission. type JobSubmission struct { // Source contains the original job definition (may be in the format of - // hcl1, hcl2, or json). + // hcl1, hcl2, or json). HCL1 jobs can no longer be parsed. Source string - // Format indicates what the Source content was (hcl1, hcl2, or json). + // Format indicates what the Source content was (hcl1, hcl2, or json). HCL1 + // jobs can no longer be parsed. Format string // VariableFlags contains the CLI "-var" flag arguments as submitted with the diff --git a/api/jobs_test.go b/api/jobs_test.go index 85873e959..fa68dcf9a 100644 --- a/api/jobs_test.go +++ b/api/jobs_test.go @@ -1685,7 +1685,7 @@ func TestJobs_Submission_namespaces(t *testing.T) { _, wm, err = jobs.RegisterOpts(job2, &RegisterOptions{ Submission: &JobSubmission{ Source: "second job source", - Format: "hcl1", + Format: "hcl2", }, }, &WriteOptions{Namespace: "second"}) must.NoError(t, err) @@ -2577,7 +2577,6 @@ func TestJobs_Parse(t *testing.T) { // Test ParseHCLOpts req := &JobsParseRequest{ JobHCL: jobspec, - HCLv1: false, Canonicalize: false, } @@ -2588,31 +2587,9 @@ func TestJobs_Parse(t *testing.T) { // Test ParseHCLOpts with Canonicalize=true req = &JobsParseRequest{ JobHCL: jobspec, - HCLv1: false, Canonicalize: true, } job2Canonicalized, err := c.Jobs().ParseHCLOpts(req) must.NoError(t, err) must.Eq(t, job1Canonicalized, job2Canonicalized) - - // Test ParseHCLOpts with HCLv1=true - req = &JobsParseRequest{ - JobHCL: jobspec, - HCLv1: true, - Canonicalize: false, - } - - job3, err := c.Jobs().ParseHCLOpts(req) - must.NoError(t, err) - must.Eq(t, job1, job3) - - // Test ParseHCLOpts with HCLv1=true and Canonicalize=true - req = &JobsParseRequest{ - JobHCL: jobspec, - HCLv1: true, - Canonicalize: true, - } - job3Canonicalized, err := c.Jobs().ParseHCLOpts(req) - must.NoError(t, err) - must.Eq(t, job1Canonicalized, job3Canonicalized) } diff --git a/ci/test-core.json b/ci/test-core.json index abeb6bea9..95f354fbe 100644 --- a/ci/test-core.json +++ b/ci/test-core.json @@ -32,7 +32,6 @@ "command/ui/...", "helper/...", "internal/...", - "jobspec/...", "jobspec2/...", "lib/...", "nomad/auth/...", diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index 2f7b9f2c2..7ad431803 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/nomad/acl" api "github.com/hashicorp/nomad/api" cstructs "github.com/hashicorp/nomad/client/structs" - "github.com/hashicorp/nomad/jobspec" "github.com/hashicorp/nomad/jobspec2" "github.com/hashicorp/nomad/nomad/structs" ) @@ -848,22 +847,14 @@ func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Reques return nil, CodedError(400, "Job spec is empty") } - var jobStruct *api.Job - if args.HCLv1 { - jobStruct, err = jobspec.Parse(strings.NewReader(args.JobHCL)) - } else { - jobStruct, err = jobspec2.ParseWithConfig(&jobspec2.ParseConfig{ - Path: "input.hcl", - Body: []byte(args.JobHCL), - AllowFS: false, - VarContent: args.Variables, - }) - if err != nil { - return nil, CodedError(400, fmt.Sprintf("Failed to parse job: %v", err)) - } - } + jobStruct, err := jobspec2.ParseWithConfig(&jobspec2.ParseConfig{ + Path: "input.hcl", + Body: []byte(args.JobHCL), + AllowFS: false, + VarContent: args.Variables, + }) if err != nil { - return nil, CodedError(400, err.Error()) + return nil, CodedError(400, fmt.Sprintf("Failed to parse job: %v", err)) } if args.Canonicalize { diff --git a/command/helpers.go b/command/helpers.go index fbc5fadca..d434117ee 100644 --- a/command/helpers.go +++ b/command/helpers.go @@ -19,7 +19,6 @@ import ( gg "github.com/hashicorp/go-getter" "github.com/hashicorp/nomad/api" flaghelper "github.com/hashicorp/nomad/helper/flags" - "github.com/hashicorp/nomad/jobspec" "github.com/hashicorp/nomad/jobspec2" "github.com/kr/text" "github.com/mitchellh/cli" @@ -30,7 +29,6 @@ import ( const ( formatJSON = "json" - formatHCL1 = "hcl1" formatHCL2 = "hcl2" ) @@ -410,11 +408,8 @@ type JobGetter struct { } func (j *JobGetter) Validate() error { - if j.HCL1 && j.Strict { - return fmt.Errorf("cannot parse job file as HCLv1 and HCLv2 strict.") - } - if j.HCL1 && j.JSON { - return fmt.Errorf("cannot parse job file as HCL and JSON.") + if j.HCL1 { + return fmt.Errorf("HCLv1 is no longer supported") } if len(j.Vars) > 0 && j.JSON { return fmt.Errorf("cannot use variables with JSON files.") @@ -422,12 +417,6 @@ func (j *JobGetter) Validate() error { if len(j.VarFiles) > 0 && j.JSON { return fmt.Errorf("cannot use variables with JSON files.") } - if len(j.Vars) > 0 && j.HCL1 { - return fmt.Errorf("cannot use variables with HCLv1.") - } - if len(j.VarFiles) > 0 && j.HCL1 { - return fmt.Errorf("cannot use variables with HCLv1.") - } return nil } @@ -496,14 +485,6 @@ func (j *JobGetter) Get(jpath string) (*api.JobSubmission, *api.Job, error) { jobfile = io.TeeReader(jobfile, &source) var err error switch { - case j.HCL1: - jobStruct, err = jobspec.Parse(jobfile) - - // include the hcl1 source as the submission - jobSubmission = &api.JobSubmission{ - Source: source.String(), - Format: formatHCL1, - } case j.JSON: // Support JSON files with both a top-level Job key as well as @@ -577,11 +558,6 @@ func (j *JobGetter) Get(jpath string) (*api.JobSubmission, *api.Job, error) { Source: source.String(), Format: formatHCL2, } - if err != nil { - if _, merr := jobspec.Parse(&source); merr == nil { - return nil, nil, fmt.Errorf("Failed to parse using HCL 2. Use the HCL 1 parser with `nomad run -hcl1`, or address the following issues:\n%v", err) - } - } } if err != nil { diff --git a/command/helpers_test.go b/command/helpers_test.go index 5d5645b54..bacd5a81b 100644 --- a/command/helpers_test.go +++ b/command/helpers_test.go @@ -288,54 +288,6 @@ func TestJobGetter_LocalFile(t *testing.T) { } } -// TestJobGetter_LocalFile_InvalidHCL2 asserts that a custom message is emited -// if the file is a valid HCL1 but not HCL2 -func TestJobGetter_LocalFile_InvalidHCL2(t *testing.T) { - ci.Parallel(t) - - cases := []struct { - name string - hcl string - expectHCL1Message bool - }{ - { - "invalid HCL", - "nothing", - false, - }, - { - "invalid HCL2", - `job "example" { - meta { "key.with.dot" = "b" } -}`, - true, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - fh, err := os.CreateTemp("", "nomad") - must.NoError(t, err) - defer os.Remove(fh.Name()) - defer fh.Close() - - _, err = fh.WriteString(c.hcl) - must.NoError(t, err) - - j := &JobGetter{} - _, _, err = j.ApiJob(fh.Name()) - must.Error(t, err) - - exptMessage := "Failed to parse using HCL 2. Use the HCL 1" - if c.expectHCL1Message { - must.ErrorContains(t, err, exptMessage) - } else { - must.StrNotContains(t, err.Error(), exptMessage) - } - }) - } -} - // TestJobGetter_HCL2_Variables asserts variable arguments from CLI // and varfiles are both honored func TestJobGetter_HCL2_Variables(t *testing.T) { @@ -471,38 +423,6 @@ func TestJobGetter_Validate(t *testing.T) { jg JobGetter errContains string }{ - { - "StrictAndHCL1", - JobGetter{ - HCL1: true, - Strict: true, - }, - "HCLv1 and HCLv2 strict", - }, - { - "JSONandHCL1", - JobGetter{ - HCL1: true, - JSON: true, - }, - "HCL and JSON", - }, - { - "VarsAndHCL1", - JobGetter{ - HCL1: true, - Vars: []string{"foo"}, - }, - "variables with HCLv1", - }, - { - "VarFilesAndHCL1", - JobGetter{ - HCL1: true, - VarFiles: []string{"foo.var"}, - }, - "variables with HCLv1", - }, { "VarsAndJSON", JobGetter{ diff --git a/command/job_plan.go b/command/job_plan.go index 3fbf346c7..f28dea2ed 100644 --- a/command/job_plan.go +++ b/command/job_plan.go @@ -89,13 +89,10 @@ Plan Options: from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. - -hcl1 - Parses the job file as HCLv1. Takes precedence over "-hcl2-strict". - -hcl2-strict Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. Defaults - to true, but ignored if "-hcl1" is also defined. + to true. -policy-override Sets the flag to force override any soft mandatory Sentinel policies. @@ -139,7 +136,6 @@ func (c *JobPlanCommand) AutocompleteFlags() complete.Flags { "-policy-override": complete.PredictNothing, "-verbose": complete.PredictNothing, "-json": complete.PredictNothing, - "-hcl1": complete.PredictNothing, "-hcl2-strict": complete.PredictNothing, "-vault-token": complete.PredictAnything, "-vault-namespace": complete.PredictAnything, @@ -167,7 +163,6 @@ func (c *JobPlanCommand) Run(args []string) int { flagSet.BoolVar(&policyOverride, "policy-override", false, "") flagSet.BoolVar(&verbose, "verbose", false, "") flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "") - flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "") flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "") flagSet.StringVar(&vaultToken, "vault-token", "", "") flagSet.StringVar(&vaultNamespace, "vault-namespace", "", "") @@ -186,10 +181,6 @@ func (c *JobPlanCommand) Run(args []string) int { return 255 } - if c.JobGetter.HCL1 { - c.JobGetter.Strict = false - } - if err := c.JobGetter.Validate(); err != nil { c.Ui.Error(fmt.Sprintf("Invalid job options: %s", err)) return 255 diff --git a/command/job_plan_test.go b/command/job_plan_test.go index 81f3bc315..aafd17fd6 100644 --- a/command/job_plan_test.go +++ b/command/job_plan_test.go @@ -116,25 +116,6 @@ job "job1" { } } -func TestPlanCommand_hcl1_hcl2_strict(t *testing.T) { - ci.Parallel(t) - - _, _, addr := testServer(t, false, nil) - - t.Run("-hcl1 implies -hcl2-strict is false", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := &JobPlanCommand{Meta: Meta{Ui: ui}} - got := cmd.Run([]string{ - "-hcl1", "-hcl2-strict", - "-address", addr, - "asset/example-short.nomad.hcl", - }) - // Exit code 1 here means that an alloc will be created, which is - // expected. - must.One(t, got) - }) -} - func TestPlanCommand_From_STDIN(t *testing.T) { _, _, addr := testServer(t, false, nil) diff --git a/command/job_run.go b/command/job_run.go index cd20e95b2..1a9a216ce 100644 --- a/command/job_run.go +++ b/command/job_run.go @@ -97,13 +97,10 @@ Run Options: from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. - -hcl1 - Parses the job file as HCLv1. Takes precedence over "-hcl2-strict". - -hcl2-strict Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. Defaults - to true, but ignored if "-hcl1" is also defined. + to true. -output Output the JSON that would be submitted to the HTTP API without submitting @@ -175,7 +172,6 @@ func (c *JobRunCommand) AutocompleteFlags() complete.Flags { "-policy-override": complete.PredictNothing, "-preserve-counts": complete.PredictNothing, "-json": complete.PredictNothing, - "-hcl1": complete.PredictNothing, "-hcl2-strict": complete.PredictNothing, "-var": complete.PredictAnything, "-var-file": complete.PredictFiles("*.var"), @@ -206,7 +202,6 @@ func (c *JobRunCommand) Run(args []string) int { flagSet.BoolVar(&override, "policy-override", false, "") flagSet.BoolVar(&preserveCounts, "preserve-counts", false, "") flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "") - flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "") flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "") flagSet.StringVar(&checkIndexStr, "check-index", "", "") flagSet.StringVar(&consulToken, "consul-token", "", "") @@ -235,10 +230,6 @@ func (c *JobRunCommand) Run(args []string) int { return 1 } - if c.JobGetter.HCL1 { - c.JobGetter.Strict = false - } - if err := c.JobGetter.Validate(); err != nil { c.Ui.Error(fmt.Sprintf("Invalid job options: %s", err)) return 1 diff --git a/command/job_run_test.go b/command/job_run_test.go index cdb056831..97505e05a 100644 --- a/command/job_run_test.go +++ b/command/job_run_test.go @@ -56,24 +56,6 @@ job "job1" { } } -func TestRunCommand_hcl1_hcl2_strict(t *testing.T) { - ci.Parallel(t) - - _, _, addr := testServer(t, false, nil) - - t.Run("-hcl1 implies -hcl2-strict is false", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := &JobRunCommand{Meta: Meta{Ui: ui}} - got := cmd.Run([]string{ - "-hcl1", "-hcl2-strict", - "-address", addr, - "-detach", - "asset/example-short.nomad.hcl", - }) - must.Zero(t, got) - }) -} - func TestRunCommand_Fails(t *testing.T) { ci.Parallel(t) diff --git a/command/job_validate.go b/command/job_validate.go index 8daf84226..9c75ef417 100644 --- a/command/job_validate.go +++ b/command/job_validate.go @@ -51,13 +51,10 @@ Validate Options: from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. - -hcl1 - Parses the job file as HCLv1. Takes precedence over "-hcl2-strict". - -hcl2-strict Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. Defaults - to true, but ignored if "-hcl1" is also defined. + to true. -vault-token Used to validate if the user submitting the job has permission to run the job @@ -90,7 +87,6 @@ func (c *JobValidateCommand) Synopsis() string { func (c *JobValidateCommand) AutocompleteFlags() complete.Flags { return complete.Flags{ - "-hcl1": complete.PredictNothing, "-hcl2-strict": complete.PredictNothing, "-vault-token": complete.PredictAnything, "-vault-namespace": complete.PredictAnything, @@ -115,7 +111,6 @@ func (c *JobValidateCommand) Run(args []string) int { flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient) flagSet.Usage = func() { c.Ui.Output(c.Help()) } flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "") - flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "") flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "") flagSet.StringVar(&vaultToken, "vault-token", "", "") flagSet.StringVar(&vaultNamespace, "vault-namespace", "", "") @@ -134,10 +129,6 @@ func (c *JobValidateCommand) Run(args []string) int { return 1 } - if c.JobGetter.HCL1 { - c.JobGetter.Strict = false - } - if err := c.JobGetter.Validate(); err != nil { c.Ui.Error(fmt.Sprintf("Invalid job options: %s", err)) return 1 diff --git a/command/job_validate_test.go b/command/job_validate_test.go index 6adc2ae30..52163f1d4 100644 --- a/command/job_validate_test.go +++ b/command/job_validate_test.go @@ -71,22 +71,6 @@ func TestValidateCommand_Files(t *testing.T) { must.One(t, code) }) } -func TestValidateCommand_hcl1_hcl2_strict(t *testing.T) { - ci.Parallel(t) - - _, _, addr := testServer(t, false, nil) - - t.Run("-hcl1 implies -hcl2-strict is false", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := &JobValidateCommand{Meta: Meta{Ui: ui}} - got := cmd.Run([]string{ - "-hcl1", "-hcl2-strict", - "-address", addr, - "asset/example-short.nomad.hcl", - }) - must.Zero(t, got) - }) -} func TestValidateCommand_Fails(t *testing.T) { ci.Parallel(t) diff --git a/e2e/rescheduling/rescheduling_test.go b/e2e/rescheduling/rescheduling_test.go index d1a13b483..3f59c6c57 100644 --- a/e2e/rescheduling/rescheduling_test.go +++ b/e2e/rescheduling/rescheduling_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/nomad/e2e/e2eutil" "github.com/hashicorp/nomad/helper/uuid" - "github.com/hashicorp/nomad/jobspec" "github.com/shoenig/test" "github.com/shoenig/test/must" "github.com/shoenig/test/wait" @@ -112,7 +111,7 @@ func TestRescheduling_MaxAttempts(t *testing.T) { must.Sprint("should have exactly 3 failed allocs"), ) - job, err := jobspec.ParseFile("./input/rescheduling_fail.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_fail.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "sleep 15000"} @@ -178,7 +177,7 @@ func TestRescheduling_WithUpdate(t *testing.T) { ) // reschedule to make fail - job, err := jobspec.ParseFile("./input/rescheduling_update.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_update.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "lol"} @@ -216,7 +215,7 @@ func TestRescheduling_WithCanary(t *testing.T) { must.Sprint("deployment should be successful")) // reschedule to make fail - job, err := jobspec.ParseFile("./input/rescheduling_canary.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_canary.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "lol"} @@ -258,7 +257,7 @@ func TestRescheduling_WithCanaryAutoRevert(t *testing.T) { must.Sprint("deployment should be successful")) // reschedule to make fail - job, err := jobspec.ParseFile("./input/rescheduling_canary_autorevert.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_canary_autorevert.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "lol"} @@ -306,7 +305,7 @@ func TestRescheduling_MaxParallel(t *testing.T) { must.Sprint("deployment should be successful")) // reschedule to make fail - job, err := jobspec.ParseFile("./input/rescheduling_maxp.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_maxp.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "lol"} @@ -353,7 +352,7 @@ func TestRescheduling_MaxParallelAutoRevert(t *testing.T) { must.Sprint("deployment should be successful")) // reschedule to make fail - job, err := jobspec.ParseFile("./input/rescheduling_maxp_autorevert.nomad") + job, err := e2eutil.Parse2(t, "./input/rescheduling_maxp_autorevert.nomad") must.NoError(t, err) job.ID = &jobID job.TaskGroups[0].Tasks[0].Config["args"] = []string{"-c", "lol"} diff --git a/e2e/volumes/volumes_test.go b/e2e/volumes/volumes_test.go index 8f2d1bbc7..fd7daad81 100644 --- a/e2e/volumes/volumes_test.go +++ b/e2e/volumes/volumes_test.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/e2e/e2eutil" "github.com/hashicorp/nomad/helper/uuid" - "github.com/hashicorp/nomad/jobspec" "github.com/hashicorp/nomad/testutil" ) @@ -60,7 +59,7 @@ func TestVolumeMounts(t *testing.T) { // modify the job so that we make sure it's placed back on the same host. // we want to be able to verify that the data from the previous alloc is // still there - job, err := jobspec.ParseFile("./input/volumes.nomad") + job, err := e2eutil.Parse2(t, "./input/volumes.nomad") require.NoError(t, err) job.ID = &jobID job.Constraints = []*api.Constraint{ diff --git a/jobspec/.copywrite.hcl b/jobspec/.copywrite.hcl deleted file mode 100644 index 61b20a2c8..000000000 --- a/jobspec/.copywrite.hcl +++ /dev/null @@ -1,12 +0,0 @@ -schema_version = 1 - -project { - license = "MPL-2.0" - copyright_year = 2024 - - header_ignore = [ - // Enterprise files do not fall under the open source licensing. CE-ENT - // merge conflicts might happen here, please be sure to put new CE - // exceptions above this comment. - ] -} diff --git a/jobspec/LICENSE b/jobspec/LICENSE deleted file mode 100644 index f4f97ee58..000000000 --- a/jobspec/LICENSE +++ /dev/null @@ -1,365 +0,0 @@ -Copyright (c) 2015 HashiCorp, Inc. - -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. - diff --git a/jobspec/helper.go b/jobspec/helper.go deleted file mode 100644 index f83408abc..000000000 --- a/jobspec/helper.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -// These functions are copied from helper/funcs.go -// added here to avoid jobspec depending on any other package - -import ( - "fmt" - "reflect" - "strings" - "time" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl/hcl/ast" -) - -// stringToPtr returns the pointer to a string -func stringToPtr(str string) *string { - return &str -} - -// timeToPtr returns the pointer to a time.Duration. -func timeToPtr(t time.Duration) *time.Duration { - return &t -} - -// boolToPtr returns the pointer to a boolean -func boolToPtr(b bool) *bool { - return &b -} - -func checkHCLKeys(node ast.Node, valid []string) error { - var list *ast.ObjectList - switch n := node.(type) { - case *ast.ObjectList: - list = n - case *ast.ObjectType: - list = n.List - default: - return fmt.Errorf("cannot check HCL keys of type %T", n) - } - - validMap := make(map[string]struct{}, len(valid)) - for _, v := range valid { - validMap[v] = struct{}{} - } - - var result error - for _, item := range list.Items { - key := item.Keys[0].Token.Value().(string) - if _, ok := validMap[key]; !ok { - result = multierror.Append(result, fmt.Errorf( - "invalid key: %s", key)) - } - } - - return result -} - -// UnusedKeys returns a pretty-printed error if any `hcl:",unusedKeys"` is not empty -func unusedKeys(obj interface{}) error { - val := reflect.ValueOf(obj) - if val.Kind() == reflect.Ptr { - val = reflect.Indirect(val) - } - return unusedKeysImpl([]string{}, val) -} - -func unusedKeysImpl(path []string, val reflect.Value) error { - stype := val.Type() - for i := 0; i < stype.NumField(); i++ { - ftype := stype.Field(i) - fval := val.Field(i) - tags := strings.Split(ftype.Tag.Get("hcl"), ",") - name := tags[0] - tags = tags[1:] - - if fval.Kind() == reflect.Ptr { - fval = reflect.Indirect(fval) - } - - // struct? recurse. Add the struct's key to the path - if fval.Kind() == reflect.Struct { - err := unusedKeysImpl(append([]string{name}, path...), fval) - if err != nil { - return err - } - continue - } - - // Search the hcl tags for "unusedKeys" - unusedKeys := false - for _, p := range tags { - if p == "unusedKeys" { - unusedKeys = true - break - } - } - - if unusedKeys { - ks, ok := fval.Interface().([]string) - if ok && len(ks) != 0 { - ps := "" - if len(path) > 0 { - ps = strings.Join(path, ".") + " " - } - return fmt.Errorf("%sunexpected keys %s", - ps, - strings.Join(ks, ", ")) - } - } - } - return nil -} diff --git a/jobspec/helper_test.go b/jobspec/helper_test.go deleted file mode 100644 index 0cbe0f862..000000000 --- a/jobspec/helper_test.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -// These functions are copied from helper/funcs.go -// added here to avoid jobspec depending on any other package - -// intToPtr returns the pointer to an int -func intToPtr(i int) *int { - return &i -} diff --git a/jobspec/parse.go b/jobspec/parse.go deleted file mode 100644 index 245e6a9dd..000000000 --- a/jobspec/parse.go +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "strconv" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$") -var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String()) - -// Parse parses the job spec from the given io.Reader. -// -// Due to current internal limitations, the entire contents of the -// io.Reader will be copied into memory first before parsing. -func Parse(r io.Reader) (*api.Job, error) { - // Copy the reader into an in-memory buffer first since HCL requires it. - var buf bytes.Buffer - if _, err := io.Copy(&buf, r); err != nil { - return nil, err - } - - // Parse the buffer - root, err := hcl.Parse(buf.String()) - if err != nil { - return nil, fmt.Errorf("error parsing: %s", err) - } - buf.Reset() - - // Top-level item should be a list - list, ok := root.Node.(*ast.ObjectList) - if !ok { - return nil, fmt.Errorf("error parsing: root should be an object") - } - - // Check for invalid keys - valid := []string{ - "job", - } - if err := checkHCLKeys(list, valid); err != nil { - return nil, err - } - - var job api.Job - - // Parse the job out - matches := list.Filter("job") - if len(matches.Items) == 0 { - return nil, fmt.Errorf("'job' block not found") - } - if err := parseJob(&job, matches); err != nil { - return nil, fmt.Errorf("error parsing 'job': %s", err) - } - - return &job, nil -} - -// ParseFile parses the given path as a job spec. -func ParseFile(path string) (*api.Job, error) { - path, err := filepath.Abs(path) - if err != nil { - return nil, err - } - - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - return Parse(f) -} - -func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'reschedule' block allowed") - } - - // Get our job object - obj := list.Items[0] - - // Check for invalid keys - valid := []string{ - "attempts", - "interval", - "unlimited", - "delay", - "max_delay", - "delay_function", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - var result api.ReschedulePolicy - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &result, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - - *final = &result - return nil -} - -func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error { - for _, o := range list.Elem().Items { - // Check for invalid keys - valid := []string{ - "attribute", - "distinct_hosts", - "distinct_property", - "operator", - "regexp", - "set_contains", - "value", - "version", - "semver", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - m["LTarget"] = m["attribute"] - m["RTarget"] = m["value"] - m["Operand"] = m["operator"] - - // If "version" is provided, set the operand - // to "version" and the value to the "RTarget" - if constraint, ok := m[api.ConstraintVersion]; ok { - m["Operand"] = api.ConstraintVersion - m["RTarget"] = constraint - } - - // If "semver" is provided, set the operand - // to "semver" and the value to the "RTarget" - if constraint, ok := m[api.ConstraintSemver]; ok { - m["Operand"] = api.ConstraintSemver - m["RTarget"] = constraint - } - - // If "regexp" is provided, set the operand - // to "regexp" and the value to the "RTarget" - if constraint, ok := m[api.ConstraintRegex]; ok { - m["Operand"] = api.ConstraintRegex - m["RTarget"] = constraint - } - - // If "set_contains" is provided, set the operand - // to "set_contains" and the value to the "RTarget" - if constraint, ok := m[api.ConstraintSetContains]; ok { - m["Operand"] = api.ConstraintSetContains - m["RTarget"] = constraint - } - - if value, ok := m[api.ConstraintDistinctHosts]; ok { - enabled, err := parseBool(value) - if err != nil { - return fmt.Errorf("distinct_hosts should be set to true or false; %v", err) - } - - // If it is not enabled, skip the constraint. - if !enabled { - continue - } - - m["Operand"] = api.ConstraintDistinctHosts - m["RTarget"] = strconv.FormatBool(enabled) - } - - if property, ok := m[api.ConstraintDistinctProperty]; ok { - m["Operand"] = api.ConstraintDistinctProperty - m["LTarget"] = property - } - - // Build the constraint - var c api.Constraint - if err := mapstructure.WeakDecode(m, &c); err != nil { - return err - } - if c.Operand == "" { - c.Operand = "=" - } - - *result = append(*result, &c) - } - - return nil -} - -func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error { - for _, o := range list.Elem().Items { - // Check for invalid keys - valid := []string{ - "attribute", - "operator", - "regexp", - "set_contains", - "set_contains_any", - "set_contains_all", - "value", - "version", - "semver", - "weight", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - m["LTarget"] = m["attribute"] - m["RTarget"] = m["value"] - m["Operand"] = m["operator"] - - // If "version" is provided, set the operand - // to "version" and the value to the "RTarget" - if affinity, ok := m[api.ConstraintVersion]; ok { - m["Operand"] = api.ConstraintVersion - m["RTarget"] = affinity - } - - // If "semver" is provided, set the operand - // to "semver" and the value to the "RTarget" - if affinity, ok := m[api.ConstraintSemver]; ok { - m["Operand"] = api.ConstraintSemver - m["RTarget"] = affinity - } - - // If "regexp" is provided, set the operand - // to "regexp" and the value to the "RTarget" - if affinity, ok := m[api.ConstraintRegex]; ok { - m["Operand"] = api.ConstraintRegex - m["RTarget"] = affinity - } - - // If "set_contains_any" is provided, set the operand - // to "set_contains_any" and the value to the "RTarget" - if affinity, ok := m[api.ConstraintSetContainsAny]; ok { - m["Operand"] = api.ConstraintSetContainsAny - m["RTarget"] = affinity - } - - // If "set_contains_all" is provided, set the operand - // to "set_contains_all" and the value to the "RTarget" - if affinity, ok := m[api.ConstraintSetContainsAll]; ok { - m["Operand"] = api.ConstraintSetContainsAll - m["RTarget"] = affinity - } - - // set_contains is a synonym of set_contains_all - if affinity, ok := m[api.ConstraintSetContains]; ok { - m["Operand"] = api.ConstraintSetContains - m["RTarget"] = affinity - } - - // Build the affinity - var a api.Affinity - if err := mapstructure.WeakDecode(m, &a); err != nil { - return err - } - if a.Operand == "" { - a.Operand = "=" - } - - *result = append(*result, &a) - } - - return nil -} - -func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error { - for _, o := range list.Elem().Items { - // Check for invalid keys - valid := []string{ - "attribute", - "weight", - "target", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - // We need this later - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("spread should be an object") - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - delete(m, "target") - // Build spread - var s api.Spread - if err := mapstructure.WeakDecode(m, &s); err != nil { - return err - } - - // Parse spread target - if o := listVal.Filter("target"); len(o.Items) > 0 { - if err := parseSpreadTarget(&s.SpreadTarget, o); err != nil { - return multierror.Prefix(err, "target ->") - } - } - - *result = append(*result, &s) - } - - return nil -} - -func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error { - seen := make(map[string]struct{}) - for _, item := range list.Items { - if len(item.Keys) != 1 { - return fmt.Errorf("missing spread target") - } - n := item.Keys[0].Token.Value().(string) - - // Make sure we haven't already found this - if _, ok := seen[n]; ok { - return fmt.Errorf("target '%s' defined more than once", n) - } - seen[n] = struct{}{} - - // We need this later - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("target should be an object") - } - - // Check for invalid keys - valid := []string{ - "percent", - "value", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return err - } - - // Decode spread target - var g api.SpreadTarget - g.Value = n - if err := mapstructure.WeakDecode(m, &g); err != nil { - return err - } - *result = append(*result, &g) - } - return nil -} - -// parseBool takes an interface value and tries to convert it to a boolean and -// returns an error if the type can't be converted. -func parseBool(value interface{}) (bool, error) { - var enabled bool - var err error - switch data := value.(type) { - case string: - enabled, err = strconv.ParseBool(data) - case bool: - enabled = data - default: - err = fmt.Errorf("%v couldn't be converted to boolean value", value) - } - - return enabled, err -} - -func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'update' block allowed") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - // Check for invalid keys - valid := []string{ - "stagger", - "max_parallel", - "health_check", - "min_healthy_time", - "healthy_deadline", - "progress_deadline", - "auto_revert", - "auto_promote", - "canary", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: result, - }) - if err != nil { - return err - } - return dec.Decode(m) -} - -func parseDisconnect(result **api.DisconnectStrategy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'disconnect' block allowed") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - // Check for invalid keys - valid := []string{ - "lost_after", - "replace", - "reconcile", - "stop_on_client_after", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: result, - }) - if err != nil { - return err - } - return dec.Decode(m) -} - -func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'migrate' block allowed") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - // Check for invalid keys - valid := []string{ - "max_parallel", - "health_check", - "min_healthy_time", - "healthy_deadline", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: result, - }) - if err != nil { - return err - } - return dec.Decode(m) -} - -func parseVault(result *api.Vault, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) == 0 { - return nil - } - if len(list.Items) > 1 { - return fmt.Errorf("only one 'vault' block allowed per task") - } - - // Get our resource object - o := list.Items[0] - - // We need this later - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("vault: should be an object") - } - - // Check for invalid keys - valid := []string{ - "namespace", - "policies", - "env", - "disable_file", - "change_mode", - "change_signal", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, "vault ->") - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - if err := mapstructure.WeakDecode(m, result); err != nil { - return err - } - - return nil -} diff --git a/jobspec/parse_group.go b/jobspec/parse_group.go deleted file mode 100644 index e940955b7..000000000 --- a/jobspec/parse_group.go +++ /dev/null @@ -1,457 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -func parseGroups(result *api.Job, list *ast.ObjectList) error { - list = list.Children() - if len(list.Items) == 0 { - return nil - } - - // Go through each object and turn it into an actual result. - collection := make([]*api.TaskGroup, 0, len(list.Items)) - seen := make(map[string]struct{}) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - // Make sure we haven't already found this - if _, ok := seen[n]; ok { - return fmt.Errorf("group '%s' defined more than once", n) - } - seen[n] = struct{}{} - - // We need this later - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("group '%s': should be an object", n) - } - - // Check for invalid keys - valid := []string{ - "count", - "constraint", - "consul", - "affinity", - "restart", - "meta", - "task", - "ephemeral_disk", - "update", - "disconnect", - "reschedule", - "vault", - "migrate", - "spread", - "shutdown_delay", - "network", - "service", - "volume", - "scaling", - "stop_after_client_disconnect", - "max_client_disconnect", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return err - } - - delete(m, "constraint") - delete(m, "consul") - delete(m, "affinity") - delete(m, "meta") - delete(m, "task") - delete(m, "restart") - delete(m, "ephemeral_disk") - delete(m, "update") - delete(m, "disconnect") - delete(m, "vault") - delete(m, "migrate") - delete(m, "spread") - delete(m, "network") - delete(m, "service") - delete(m, "volume") - delete(m, "scaling") - - // Build the group with the basic decode - var g api.TaskGroup - g.Name = stringToPtr(n) - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &g, - }) - - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - - // Parse constraints - if o := listVal.Filter("constraint"); len(o.Items) > 0 { - if err := parseConstraints(&g.Constraints, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n)) - } - } - - // Parse consul - if o := listVal.Filter("consul"); len(o.Items) > 0 { - if err := parseConsul(&g.Consul, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', consul ->", n)) - } - } - - // Parse affinities - if o := listVal.Filter("affinity"); len(o.Items) > 0 { - if err := parseAffinities(&g.Affinities, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', affinity ->", n)) - } - } - - // Parse restart policy - if o := listVal.Filter("restart"); len(o.Items) > 0 { - if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n)) - } - } - - // Parse spread - if o := listVal.Filter("spread"); len(o.Items) > 0 { - if err := parseSpread(&g.Spreads, o); err != nil { - return multierror.Prefix(err, "spread ->") - } - } - - // Parse network - if o := listVal.Filter("network"); len(o.Items) > 0 { - networks, err := ParseNetwork(o) - if err != nil { - return err - } - g.Networks = []*api.NetworkResource{networks} - } - - // Parse reschedule policy - if o := listVal.Filter("reschedule"); len(o.Items) > 0 { - if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n)) - } - } - // Parse ephemeral disk - if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 { - g.EphemeralDisk = &api.EphemeralDisk{} - if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n)) - } - } - - // If we have an update strategy, then parse that - if o := listVal.Filter("update"); len(o.Items) > 0 { - if err := parseUpdate(&g.Update, o); err != nil { - return multierror.Prefix(err, "update ->") - } - } - - // If we have an disconnect strategy, then parse that - if o := listVal.Filter("disconnect"); len(o.Items) > 0 { - if err := parseDisconnect(&g.Disconnect, o); err != nil { - return multierror.Prefix(err, "update ->") - } - } - - // If we have a migration strategy, then parse that - if o := listVal.Filter("migrate"); len(o.Items) > 0 { - if err := parseMigrate(&g.Migrate, o); err != nil { - return multierror.Prefix(err, "migrate ->") - } - } - - // Parse out meta fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { - for _, o := range metaO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - if err := mapstructure.WeakDecode(m, &g.Meta); err != nil { - return err - } - } - } - - // Parse any volume declarations - if o := listVal.Filter("volume"); len(o.Items) > 0 { - if err := parseVolumes(&g.Volumes, o); err != nil { - return multierror.Prefix(err, "volume ->") - } - } - - // Parse scaling policy - if o := listVal.Filter("scaling"); len(o.Items) > 0 { - if err := parseGroupScalingPolicy(&g.Scaling, o); err != nil { - return multierror.Prefix(err, "scaling ->") - } - } - - // Parse tasks - if o := listVal.Filter("task"); len(o.Items) > 0 { - if err := parseTasks(&g.Tasks, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n)) - } - } - - // If we have a vault block, then parse that - if o := listVal.Filter("vault"); len(o.Items) > 0 { - tgVault := &api.Vault{ - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr("restart"), - } - - if err := parseVault(tgVault, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n)) - } - - // Go through the tasks and if they don't have a Vault block, set it - for _, task := range g.Tasks { - if task.Vault == nil { - task.Vault = tgVault - } - } - } - - if o := listVal.Filter("service"); len(o.Items) > 0 { - if err := parseGroupServices(&g, o); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) - } - } - collection = append(collection, &g) - } - - result.TaskGroups = append(result.TaskGroups, collection...) - return nil -} - -func parseConsul(result **api.Consul, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'consul' block allowed") - } - - // Get our consul object - obj := list.Items[0] - - // Check for invalid keys - valid := []string{ - "namespace", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - var consul api.Consul - if err := mapstructure.WeakDecode(m, &consul); err != nil { - return err - } - *result = &consul - - return nil -} - -func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'ephemeral_disk' block allowed") - } - - // Get our ephemeral_disk object - obj := list.Items[0] - - // Check for invalid keys - valid := []string{ - "sticky", - "size", - "migrate", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - var ephemeralDisk api.EphemeralDisk - if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil { - return err - } - *result = &ephemeralDisk - - return nil -} - -func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'restart' block allowed") - } - - // Get our job object - obj := list.Items[0] - - // Check for invalid keys - valid := []string{ - "attempts", - "interval", - "delay", - "mode", - "render_templates", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - var result api.RestartPolicy - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &result, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - - *final = &result - return nil -} - -func parseVolumes(out *map[string]*api.VolumeRequest, list *ast.ObjectList) error { - hcl.DecodeObject(out, list) - - for k, v := range *out { - err := unusedKeys(v) - if err != nil { - return err - } - // This is supported by `hcl:",key"`, but that only works if we start at the - // parent ast.ObjectItem - v.Name = k - } - - return nil -} - -func parseGroupScalingPolicy(out **api.ScalingPolicy, list *ast.ObjectList) error { - if len(list.Items) > 1 { - return fmt.Errorf("only one 'scaling' block allowed") - } - item := list.Items[0] - if len(item.Keys) != 0 { - return fmt.Errorf("task group scaling policy should not have a name") - } - p, err := parseScalingPolicy(item) - if err != nil { - return err - } - - // group-specific validation - if p.Max == nil { - return fmt.Errorf("missing 'max'") - } - if p.Type == "" { - p.Type = "horizontal" - } else if p.Type != "horizontal" { - return fmt.Errorf("task group scaling policy had invalid type: %q", p.Type) - } - *out = p - return nil -} - -func parseScalingPolicy(item *ast.ObjectItem) (*api.ScalingPolicy, error) { - // We need this later - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("should be an object") - } - - valid := []string{ - "min", - "max", - "policy", - "enabled", - "type", - } - if err := checkHCLKeys(item.Val, valid); err != nil { - return nil, err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return nil, err - } - delete(m, "policy") - - var result api.ScalingPolicy - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: &result, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, err - } - - // If we have policy, then parse that - if o := listVal.Filter("policy"); len(o.Items) > 0 { - if len(o.Elem().Items) > 1 { - return nil, fmt.Errorf("only one 'policy' block allowed per 'scaling' block") - } - p := o.Elem().Items[0] - var m map[string]interface{} - if err := hcl.DecodeObject(&m, p.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &result.Policy); err != nil { - return nil, err - } - } - - return &result, nil -} diff --git a/jobspec/parse_job.go b/jobspec/parse_job.go deleted file mode 100644 index 9cedf3309..000000000 --- a/jobspec/parse_job.go +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -func parseJob(result *api.Job, list *ast.ObjectList) error { - if len(list.Items) != 1 { - return fmt.Errorf("only one 'job' block allowed") - } - list = list.Children() - if len(list.Items) != 1 { - return fmt.Errorf("'job' block missing name") - } - - // Get our job object - obj := list.Items[0] - - // Decode the full thing into a map[string]interface for ease - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - delete(m, "constraint") - delete(m, "affinity") - delete(m, "meta") - delete(m, "migrate") - delete(m, "parameterized") - delete(m, "periodic") - delete(m, "reschedule") - delete(m, "update") - delete(m, "vault") - delete(m, "spread") - delete(m, "multiregion") - - // Set the ID and name to the object key - result.ID = stringToPtr(obj.Keys[0].Token.Value().(string)) - result.Name = stringToPtr(*result.ID) - - // Decode the rest - if err := mapstructure.WeakDecode(m, result); err != nil { - return err - } - - // Value should be an object - var listVal *ast.ObjectList - if ot, ok := obj.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("job '%s' value: should be an object", *result.ID) - } - - // Check for invalid keys - valid := []string{ - "all_at_once", - "constraint", - "affinity", - "spread", - "datacenters", - "node_pool", - "group", - "id", - "meta", - "migrate", - "name", - "namespace", - "parameterized", - "periodic", - "priority", - "region", - "reschedule", - "task", - "type", - "update", - "vault", - "vault_token", - "consul_token", - "multiregion", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, "job:") - } - - // Parse constraints - if o := listVal.Filter("constraint"); len(o.Items) > 0 { - if err := parseConstraints(&result.Constraints, o); err != nil { - return multierror.Prefix(err, "constraint ->") - } - } - - // Parse affinities - if o := listVal.Filter("affinity"); len(o.Items) > 0 { - if err := parseAffinities(&result.Affinities, o); err != nil { - return multierror.Prefix(err, "affinity ->") - } - } - - // If we have an update strategy, then parse that - if o := listVal.Filter("update"); len(o.Items) > 0 { - if err := parseUpdate(&result.Update, o); err != nil { - return multierror.Prefix(err, "update ->") - } - } - - // If we have a periodic definition, then parse that - if o := listVal.Filter("periodic"); len(o.Items) > 0 { - if err := parsePeriodic(&result.Periodic, o); err != nil { - return multierror.Prefix(err, "periodic ->") - } - } - - // Parse spread - if o := listVal.Filter("spread"); len(o.Items) > 0 { - if err := parseSpread(&result.Spreads, o); err != nil { - return multierror.Prefix(err, "spread ->") - } - } - - // If we have a parameterized definition, then parse that - if o := listVal.Filter("parameterized"); len(o.Items) > 0 { - if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil { - return multierror.Prefix(err, "parameterized ->") - } - } - - // If we have a reschedule block, then parse that - if o := listVal.Filter("reschedule"); len(o.Items) > 0 { - if err := parseReschedulePolicy(&result.Reschedule, o); err != nil { - return multierror.Prefix(err, "reschedule ->") - } - } - - // If we have a migration strategy, then parse that - if o := listVal.Filter("migrate"); len(o.Items) > 0 { - if err := parseMigrate(&result.Migrate, o); err != nil { - return multierror.Prefix(err, "migrate ->") - } - } - - // If we have a multiregion block, then parse that - if o := listVal.Filter("multiregion"); len(o.Items) > 0 { - var mr api.Multiregion - if err := parseMultiregion(&mr, o); err != nil { - return multierror.Prefix(err, "multiregion ->") - } - result.Multiregion = &mr - } - - // Parse out meta fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { - for _, o := range metaO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - if err := mapstructure.WeakDecode(m, &result.Meta); err != nil { - return err - } - } - } - - // If we have tasks outside, create TaskGroups for them - if o := listVal.Filter("task"); len(o.Items) > 0 { - var tasks []*api.Task - if err := parseTasks(&tasks, o); err != nil { - return multierror.Prefix(err, "task:") - } - - result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2) - for i, t := range tasks { - result.TaskGroups[i] = &api.TaskGroup{ - Name: stringToPtr(t.Name), - Tasks: []*api.Task{t}, - } - } - } - - // Parse the task groups - if o := listVal.Filter("group"); len(o.Items) > 0 { - if err := parseGroups(result, o); err != nil { - return multierror.Prefix(err, "group:") - } - } - - // If we have a vault block, then parse that - if o := listVal.Filter("vault"); len(o.Items) > 0 { - jobVault := &api.Vault{ - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr("restart"), - } - - if err := parseVault(jobVault, o); err != nil { - return multierror.Prefix(err, "vault ->") - } - - // Go through the task groups/tasks and if they don't have a Vault block, set it - for _, tg := range result.TaskGroups { - for _, task := range tg.Tasks { - if task.Vault == nil { - task.Vault = jobVault - } - } - } - } - - return nil -} - -func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'periodic' block allowed per job") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - // Check for invalid keys - valid := []string{ - "enabled", - "cron", - "crons", - "prohibit_overlap", - "time_zone", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - if value, ok := m["enabled"]; ok { - enabled, err := parseBool(value) - if err != nil { - return fmt.Errorf("periodic.enabled should be set to true or false; %v", err) - } - m["Enabled"] = enabled - } - - // If "cron" is provided, set the type to "cron" and store the spec. - if cron, ok := m["cron"]; ok { - m["SpecType"] = api.PeriodicSpecCron - m["Spec"] = cron - } - - // If "crons" is provided, set the type to "cron" and store the spec. - if cron, ok := m["crons"]; ok { - m["SpecType"] = api.PeriodicSpecCron - m["Specs"] = cron - } - - // Build the constraint - var p api.PeriodicConfig - if err := mapstructure.WeakDecode(m, &p); err != nil { - return err - } - *result = &p - return nil -} - -func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'parameterized' block allowed per job") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - // Check for invalid keys - valid := []string{ - "payload", - "meta_required", - "meta_optional", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - // Build the parameterized job block - var d api.ParameterizedJobConfig - if err := mapstructure.WeakDecode(m, &d); err != nil { - return err - } - - *result = &d - return nil -} diff --git a/jobspec/parse_multiregion.go b/jobspec/parse_multiregion.go deleted file mode 100644 index fb7154a31..000000000 --- a/jobspec/parse_multiregion.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -func parseMultiregion(result *api.Multiregion, list *ast.ObjectList) error { - - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'multiregion' block allowed") - } - if len(list.Items) == 0 { - return nil - } - - // Get our multiregion object and decode it - obj := list.Items[0] - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - // Value should be an object - var listVal *ast.ObjectList - if ot, ok := obj.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("multiregion should be an object") - } - - // Check for invalid keys - valid := []string{ - "strategy", - "region", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - // If we have a strategy, then parse that - if o := listVal.Filter("strategy"); len(o.Items) > 0 { - if err := parseMultiregionStrategy(&result.Strategy, o); err != nil { - return multierror.Prefix(err, "strategy ->") - } - } - // If we have regions, then parse those - if o := listVal.Filter("region"); len(o.Items) > 0 { - if err := parseMultiregionRegions(result, o); err != nil { - return multierror.Prefix(err, "regions ->") - } - } else { - return fmt.Errorf("'multiregion' requires one or more 'region' blocks") - } - return nil -} - -func parseMultiregionStrategy(final **api.MultiregionStrategy, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'strategy' block allowed") - } - - // Get our job object - obj := list.Items[0] - - // Check for invalid keys - valid := []string{ - "max_parallel", - "on_failure", - } - if err := checkHCLKeys(obj.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, obj.Val); err != nil { - return err - } - - var result api.MultiregionStrategy - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: &result, - }) - - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - *final = &result - return nil -} - -func parseMultiregionRegions(result *api.Multiregion, list *ast.ObjectList) error { - list = list.Children() - if len(list.Items) == 0 { - return nil - } - - // Go through each object and turn it into an actual result. - collection := make([]*api.MultiregionRegion, 0, len(list.Items)) - seen := make(map[string]struct{}) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - // Make sure we haven't already found this - if _, ok := seen[n]; ok { - return fmt.Errorf("region '%s' defined more than once", n) - } - seen[n] = struct{}{} - - // We need this later - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("region '%s': should be an object", n) - } - - // Check for invalid keys - valid := []string{ - "count", - "datacenters", - "meta", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return err - } - - // Build the region with the basic decode - var r api.MultiregionRegion - r.Name = n - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: &r, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - collection = append(collection, &r) - } - - result.Regions = append(result.Regions, collection...) - return nil -} diff --git a/jobspec/parse_network.go b/jobspec/parse_network.go deleted file mode 100644 index bd583661c..000000000 --- a/jobspec/parse_network.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - "strings" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -// ParseNetwork parses a collection containing exactly one NetworkResource -func ParseNetwork(o *ast.ObjectList) (*api.NetworkResource, error) { - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one 'network' resource allowed") - } - - // Check for invalid keys - valid := []string{ - "mode", - "mbits", - "dns", - "port", - "hostname", - } - if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { - return nil, multierror.Prefix(err, "network ->") - } - - var r api.NetworkResource - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil { - return nil, err - } - - delete(m, "dns") - if err := mapstructure.WeakDecode(m, &r); err != nil { - return nil, err - } - - var networkObj *ast.ObjectList - if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok { - networkObj = ot.List - } else { - return nil, fmt.Errorf("should be an object") - } - if err := parsePorts(networkObj, &r); err != nil { - return nil, multierror.Prefix(err, "network, ports ->") - } - - // Filter dns - if dns := networkObj.Filter("dns"); len(dns.Items) > 0 { - if len(dns.Items) > 1 { - return nil, multierror.Prefix(fmt.Errorf("cannot have more than 1 dns block"), "network ->") - } - - d, err := parseDNS(dns.Items[0]) - if err != nil { - return nil, multierror.Prefix(err, "network ->") - } - - r.DNS = d - } - - return &r, nil -} - -func parsePorts(networkObj *ast.ObjectList, nw *api.NetworkResource) error { - portsObjList := networkObj.Filter("port") - knownPortLabels := make(map[string]bool) - for _, port := range portsObjList.Items { - if len(port.Keys) == 0 { - return fmt.Errorf("ports must be named") - } - - // check for invalid keys - valid := []string{ - "static", - "to", - "host_network", - } - if err := checkHCLKeys(port.Val, valid); err != nil { - return err - } - - label := port.Keys[0].Token.Value().(string) - if !reDynamicPorts.MatchString(label) { - return errPortLabel - } - l := strings.ToLower(label) - if knownPortLabels[l] { - return fmt.Errorf("found a port label collision: %s", label) - } - - var res api.Port - if err := hcl.DecodeObject(&res, port.Val); err != nil { - return err - } - - res.Label = label - if res.Value > 0 { - nw.ReservedPorts = append(nw.ReservedPorts, res) - } else { - nw.DynamicPorts = append(nw.DynamicPorts, res) - } - knownPortLabels[l] = true - } - return nil -} - -func parseDNS(dns *ast.ObjectItem) (*api.DNSConfig, error) { - valid := []string{ - "servers", - "searches", - "options", - } - - if err := checkHCLKeys(dns.Val, valid); err != nil { - return nil, multierror.Prefix(err, "dns ->") - } - - var dnsCfg api.DNSConfig - var m map[string]interface{} - if err := hcl.DecodeObject(&m, dns.Val); err != nil { - return nil, err - } - - if err := mapstructure.WeakDecode(m, &dnsCfg); err != nil { - return nil, err - } - - return &dnsCfg, nil -} diff --git a/jobspec/parse_service.go b/jobspec/parse_service.go deleted file mode 100644 index 3d8504cd1..000000000 --- a/jobspec/parse_service.go +++ /dev/null @@ -1,1351 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/mitchellh/mapstructure" -) - -func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error { - g.Services = make([]*api.Service, len(serviceObjs.Items)) - for idx, o := range serviceObjs.Items { - service, err := parseService(o) - if err != nil { - return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) - } - g.Services[idx] = service - } - - return nil -} - -func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) { - services := make([]*api.Service, len(serviceObjs.Items)) - for idx, o := range serviceObjs.Items { - service, err := parseService(o) - if err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) - } - services[idx] = service - } - return services, nil -} - -func parseService(o *ast.ObjectItem) (*api.Service, error) { - // Check for invalid keys - valid := []string{ - "name", - "tags", - "canary_tags", - "enable_tag_override", - "port", - "check", - "address_mode", - "check_restart", - "connect", - "task", - "meta", - "canary_meta", - "tagged_addresses", - "on_update", - "provider", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, err - } - - var service api.Service - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "check") - delete(m, "check_restart") - delete(m, "connect") - delete(m, "meta") - delete(m, "canary_meta") - delete(m, "tagged_addresses") - - if err := mapstructure.WeakDecode(m, &service); err != nil { - return nil, err - } - - // Filter list - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("'%s': should be an object", service.Name) - } - - if co := listVal.Filter("check"); len(co.Items) > 0 { - if err := parseChecks(&service, co); err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) - } - } - - // Filter check_restart - if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 { - if len(cro.Items) > 1 { - return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name) - } - cr, err := parseCheckRestart(cro.Items[0]) - if err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) - } - service.CheckRestart = cr - - } - - // Filter connect - if co := listVal.Filter("connect"); len(co.Items) > 0 { - if len(co.Items) > 1 { - return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect block", service.Name) - } - c, err := parseConnect(co.Items[0]) - if err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) - } - service.Connect = c - } - - // Parse out meta fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { - for _, o := range metaO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &service.Meta); err != nil { - return nil, err - } - } - } - - // Parse out canary_meta fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 { - for _, o := range metaO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil { - return nil, err - } - } - } - - // Parse out tagged_addresses fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if taO := listVal.Filter("tagged_addresses"); len(taO.Items) > 0 { - for _, o := range taO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &service.TaggedAddresses); err != nil { - return nil, err - } - } - } - - return &service, nil -} - -func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) { - valid := []string{ - "native", - "gateway", - "sidecar_service", - "sidecar_task", - } - - if err := checkHCLKeys(co.Val, valid); err != nil { - return nil, multierror.Prefix(err, "connect ->") - } - - var connect api.ConsulConnect - var m map[string]interface{} - if err := hcl.DecodeObject(&m, co.Val); err != nil { - return nil, err - } - - delete(m, "gateway") - delete(m, "sidecar_service") - delete(m, "sidecar_task") - - if err := mapstructure.WeakDecode(m, &connect); err != nil { - return nil, err - } - - var connectList *ast.ObjectList - if ot, ok := co.Val.(*ast.ObjectType); ok { - connectList = ot.List - } else { - return nil, fmt.Errorf("connect should be an object") - } - - // Parse the gateway - o := connectList.Filter("gateway") - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one 'gateway' block allowed per task") - } else if len(o.Items) == 1 { - g, err := parseGateway(o.Items[0]) - if err != nil { - return nil, fmt.Errorf("gateway, %v", err) - } - connect.Gateway = g - } - - // Parse the sidecar_service - o = connectList.Filter("sidecar_service") - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task") - } - if len(o.Items) == 1 { - r, err := parseSidecarService(o.Items[0]) - if err != nil { - return nil, fmt.Errorf("sidecar_service, %v", err) - } - connect.SidecarService = r - } - - // Parse the sidecar_task - o = connectList.Filter("sidecar_task") - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task") - } - if len(o.Items) == 1 { - t, err := parseSidecarTask(o.Items[0]) - if err != nil { - return nil, fmt.Errorf("sidecar_task, %v", err) - } - connect.SidecarTask = t - } - - return &connect, nil -} - -func parseGateway(o *ast.ObjectItem) (*api.ConsulGateway, error) { - valid := []string{ - "proxy", - "ingress", - "terminating", - "mesh", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "gateway ->") - } - - var gateway api.ConsulGateway - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "proxy") - delete(m, "ingress") - delete(m, "terminating") - delete(m, "mesh") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &gateway, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, fmt.Errorf("gateway: %v", err) - } - - // list of parameters - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("proxy: should be an object") - } - - // extract and parse the proxy block - po := listVal.Filter("proxy") - if len(po.Items) != 1 { - return nil, fmt.Errorf("must have one 'proxy' block") - } - proxy, err := parseGatewayProxy(po.Items[0]) - if err != nil { - return nil, fmt.Errorf("proxy, %v", err) - } - gateway.Proxy = proxy - - // extract and parse the ingress block - if io := listVal.Filter("ingress"); len(io.Items) > 0 { - if len(io.Items) > 1 { - return nil, fmt.Errorf("ingress, %s", "multiple ingress blocks not allowed") - } - - ingress, err := parseIngressConfigEntry(io.Items[0]) - if err != nil { - return nil, fmt.Errorf("ingress, %v", err) - } - gateway.Ingress = ingress - } - - if to := listVal.Filter("terminating"); len(to.Items) > 0 { - if len(to.Items) > 1 { - return nil, fmt.Errorf("terminating, %s", "multiple terminating blocks not allowed") - } - - terminating, err := parseTerminatingConfigEntry(to.Items[0]) - if err != nil { - return nil, fmt.Errorf("terminating, %v", err) - } - gateway.Terminating = terminating - } - - if mo := listVal.Filter("mesh"); len(mo.Items) > 0 { - if len(mo.Items) > 1 { - return nil, fmt.Errorf("mesh, %s", "multiple mesh blocks not allowed") - } - - // mesh should have no keys - if err := checkHCLKeys(mo.Items[0].Val, []string{}); err != nil { - return nil, fmt.Errorf("mesh, %s", err) - } - - gateway.Mesh = &api.ConsulMeshConfigEntry{} - } - - return &gateway, nil -} - -// parseGatewayProxy parses envoy gateway proxy options supported by Consul. -// -// consul.io/docs/connect/proxies/envoy#gateway-options -func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) { - valid := []string{ - "connect_timeout", - "envoy_gateway_bind_tagged_addresses", - "envoy_gateway_bind_addresses", - "envoy_gateway_no_default_bind", - "envoy_dns_discovery_type", - "config", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "proxy ->") - } - - var proxy api.ConsulGatewayProxy - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "config") - delete(m, "envoy_gateway_bind_addresses") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &proxy, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, fmt.Errorf("proxy: %v", err) - } - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("proxy: should be an object") - } - - // need to parse envoy_gateway_bind_addresses if present - - if ebo := listVal.Filter("envoy_gateway_bind_addresses"); len(ebo.Items) > 0 { - proxy.EnvoyGatewayBindAddresses = make(map[string]*api.ConsulGatewayBindAddress) - for _, listenerM := range ebo.Items { // object item, each listener object - listenerName := listenerM.Keys[0].Token.Value().(string) - - var listenerListVal *ast.ObjectList - if ot, ok := listenerM.Val.(*ast.ObjectType); ok { - listenerListVal = ot.List - } else { - return nil, fmt.Errorf("listener: should be an object") - } - - var bind api.ConsulGatewayBindAddress - if err := hcl.DecodeObject(&bind, listenerListVal); err != nil { - return nil, fmt.Errorf("port: should be an int") - } - bind.Name = listenerName - proxy.EnvoyGatewayBindAddresses[listenerName] = &bind - } - } - - // need to parse the opaque config if present - - if co := listVal.Filter("config"); len(co.Items) > 1 { - return nil, fmt.Errorf("only 1 meta object supported") - } else if len(co.Items) == 1 { - var mSlice []map[string]interface{} - if err := hcl.DecodeObject(&mSlice, co.Items[0].Val); err != nil { - return nil, err - } - - if len(mSlice) > 1 { - return nil, fmt.Errorf("only 1 meta object supported") - } - - m := mSlice[0] - - if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil { - return nil, err - } - - proxy.Config = flattenMapSlice(proxy.Config) - } - - return &proxy, nil -} - -func parseConsulHTTPHeaderModifiers(o *ast.ObjectItem) (*api.ConsulHTTPHeaderModifiers, error) { - valid := []string{ - "add", - "set", - "remove", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "httpHeaderModifiers ->") - } - - var httpHeaderModifiers api.ConsulHTTPHeaderModifiers - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "add") - delete(m, "set") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &httpHeaderModifiers, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - // Filter list - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("'httpHeaderModifiers: should be an object") - } - - // Parse Add - if addO := listVal.Filter("add"); len(addO.Items) > 0 { - for _, o := range addO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &httpHeaderModifiers.Add); err != nil { - return nil, err - } - } - } - - // Parse Set - if setO := listVal.Filter("set"); len(setO.Items) > 0 { - for _, o := range setO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &httpHeaderModifiers.Set); err != nil { - return nil, err - } - } - } - - return &httpHeaderModifiers, nil -} - -func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, error) { - valid := []string{ - "name", - "hosts", - "tls", - "request_headers", - "response_headers", - "max_connections", - "max_pending_requests", - "max_concurrent_requests", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "service ->") - } - - var service api.ConsulIngressService - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "tls") - delete(m, "request_headers") - delete(m, "response_headers") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &service, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("service: should be an object") - } - - // Parse TLS - if tlsO := listVal.Filter("tls"); len(tlsO.Items) > 0 { - service.TLS, err = parseConsulGatewayTLS(tlsO.Items[0]) - if err != nil { - return nil, err - } - } - - // Parse Request Headers - if rqstHO := listVal.Filter("request_headers"); len(rqstHO.Items) > 0 { - service.RequestHeaders, err = parseConsulHTTPHeaderModifiers(rqstHO.Items[0]) - if err != nil { - return nil, err - } - } - - // Parse Response Headers - if rspHO := listVal.Filter("response_headers"); len(rspHO.Items) > 0 { - service.ResponseHeaders, err = parseConsulHTTPHeaderModifiers(rspHO.Items[0]) - if err != nil { - return nil, err - } - } - - return &service, nil -} - -func parseConsulLinkedService(o *ast.ObjectItem) (*api.ConsulLinkedService, error) { - valid := []string{ - "name", - "ca_file", - "cert_file", - "key_file", - "sni", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "service ->") - } - - var service api.ConsulLinkedService - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &service, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - return &service, nil -} - -func parseConsulIngressListener(o *ast.ObjectItem) (*api.ConsulIngressListener, error) { - valid := []string{ - "port", - "protocol", - "service", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "listener ->") - } - - var listener api.ConsulIngressListener - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "service") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &listener, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - // Parse services - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("listener: should be an object") - } - - so := listVal.Filter("service") - if len(so.Items) > 0 { - listener.Services = make([]*api.ConsulIngressService, len(so.Items)) - for i := range so.Items { - is, err := parseConsulIngressService(so.Items[i]) - if err != nil { - return nil, err - } - listener.Services[i] = is - } - } - return &listener, nil -} - -func parseConsulGatewayTLSSDS(o *ast.ObjectItem) (*api.ConsulGatewayTLSSDSConfig, error) { - valid := []string{ - "cluster_name", - "cert_resource", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "sds ->") - } - - var sds api.ConsulGatewayTLSSDSConfig - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &sds, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - return &sds, nil -} - -func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, error) { - valid := []string{ - "enabled", - "tls_min_version", - "tls_max_version", - "cipher_suites", - "sds", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "tls ->") - } - - var tls api.ConsulGatewayTLSConfig - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "sds") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &tls, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - // Parse SDS - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("tls: should be an object") - } - - so := listVal.Filter("sds") - if len(so.Items) > 0 { - tls.SDS, err = parseConsulGatewayTLSSDS(so.Items[0]) - if err != nil { - return nil, err - } - } - - return &tls, nil -} - -func parseIngressConfigEntry(o *ast.ObjectItem) (*api.ConsulIngressConfigEntry, error) { - valid := []string{ - "tls", - "listener", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "ingress ->") - } - - var ingress api.ConsulIngressConfigEntry - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "tls") - delete(m, "listener") - - // Parse tls and listener(s) - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("ingress: should be an object") - } - - if to := listVal.Filter("tls"); len(to.Items) > 1 { - return nil, fmt.Errorf("only 1 tls object supported") - } else if len(to.Items) == 1 { - if tls, err := parseConsulGatewayTLS(to.Items[0]); err != nil { - return nil, err - } else { - ingress.TLS = tls - } - } - - lo := listVal.Filter("listener") - if len(lo.Items) > 0 { - ingress.Listeners = make([]*api.ConsulIngressListener, len(lo.Items)) - for i := range lo.Items { - listener, err := parseConsulIngressListener(lo.Items[i]) - if err != nil { - return nil, err - } - ingress.Listeners[i] = listener - } - } - - return &ingress, nil -} - -func parseTerminatingConfigEntry(o *ast.ObjectItem) (*api.ConsulTerminatingConfigEntry, error) { - valid := []string{ - "service", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "terminating ->") - } - - var terminating api.ConsulTerminatingConfigEntry - - // Parse service(s) - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("terminating: should be an object") - } - - lo := listVal.Filter("service") - if len(lo.Items) > 0 { - terminating.Services = make([]*api.ConsulLinkedService, len(lo.Items)) - for i := range lo.Items { - service, err := parseConsulLinkedService(lo.Items[i]) - if err != nil { - return nil, err - } - terminating.Services[i] = service - } - } - - return &terminating, nil -} - -func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) { - valid := []string{ - "port", - "proxy", - "tags", - "disable_default_tcp_check", - "meta", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "sidecar_service ->") - } - - var sidecar api.ConsulSidecarService - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "proxy") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &sidecar, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, fmt.Errorf("sidecar_service: %v", err) - } - - var proxyList *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - proxyList = ot.List - } else { - return nil, fmt.Errorf("sidecar_service: should be an object") - } - - // Parse the proxy - po := proxyList.Filter("proxy") - if len(po.Items) == 0 { - return &sidecar, nil - } - if len(po.Items) > 1 { - return nil, fmt.Errorf("only one 'proxy' block allowed per task") - } - - r, err := parseProxy(po.Items[0]) - if err != nil { - return nil, fmt.Errorf("proxy, %v", err) - } - sidecar.Proxy = r - - return &sidecar, nil -} - -func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) { - task, err := parseTask(item, sidecarTaskKeys) - if err != nil { - return nil, err - } - - sidecarTask := &api.SidecarTask{ - Name: task.Name, - Driver: task.Driver, - User: task.User, - Config: task.Config, - Env: task.Env, - Resources: task.Resources, - Meta: task.Meta, - KillTimeout: task.KillTimeout, - LogConfig: task.LogConfig, - KillSignal: task.KillSignal, - } - - // Parse ShutdownDelay separatly to get pointer - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return nil, err - } - - m = map[string]interface{}{ - "shutdown_delay": m["shutdown_delay"], - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: sidecarTask, - }) - - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, err - } - return sidecarTask, nil -} - -func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) { - valid := []string{ - "local_service_address", - "local_service_port", - "upstreams", - "expose", - "transparent_proxy", - "config", - } - - if err := checkHCLKeys(o.Val, valid); err != nil { - return nil, multierror.Prefix(err, "proxy ->") - } - - var proxy api.ConsulProxy - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - delete(m, "upstreams") - delete(m, "expose") - delete(m, "transparent_proxy") - delete(m, "config") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &proxy, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, fmt.Errorf("proxy: %v", err) - } - - // Parse upstreams, expose, and config - - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("proxy: should be an object") - } - - uo := listVal.Filter("upstreams") - if len(uo.Items) > 0 { - proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items)) - for i := range uo.Items { - u, err := parseUpstream(uo.Items[i]) - if err != nil { - return nil, err - } - proxy.Upstreams[i] = u - } - } - - if eo := listVal.Filter("expose"); len(eo.Items) > 1 { - return nil, fmt.Errorf("only 1 expose object supported") - } else if len(eo.Items) == 1 { - if e, err := parseExpose(eo.Items[0]); err != nil { - return nil, err - } else { - proxy.Expose = e - } - } - - if tpo := listVal.Filter("transparent_proxy"); len(tpo.Items) > 1 { - return nil, fmt.Errorf("only 1 transparent_proxy object supported") - } else if len(tpo.Items) == 1 { - if tp, err := parseTproxy(tpo.Items[0]); err != nil { - return nil, err - } else { - proxy.TransparentProxy = tp - } - } - - // If we have config, then parse that - if o := listVal.Filter("config"); len(o.Items) > 1 { - return nil, fmt.Errorf("only 1 meta object supported") - } else if len(o.Items) == 1 { - var mSlice []map[string]interface{} - if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil { - return nil, err - } - - if len(mSlice) > 1 { - return nil, fmt.Errorf("only 1 meta object supported") - } - - m := mSlice[0] - - if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil { - return nil, err - } - - proxy.Config = flattenMapSlice(proxy.Config) - } - - return &proxy, nil -} - -func parseExpose(eo *ast.ObjectItem) (*api.ConsulExposeConfig, error) { - valid := []string{ - "path", // an array of path blocks - } - - if err := checkHCLKeys(eo.Val, valid); err != nil { - return nil, multierror.Prefix(err, "expose ->") - } - - var expose api.ConsulExposeConfig - - var listVal *ast.ObjectList - if eoType, ok := eo.Val.(*ast.ObjectType); ok { - listVal = eoType.List - } else { - return nil, fmt.Errorf("expose: should be an object") - } - - // Parse the expose block - - po := listVal.Filter("path") // array - if len(po.Items) > 0 { - expose.Paths = make([]*api.ConsulExposePath, len(po.Items)) - for i := range po.Items { - p, err := parseExposePath(po.Items[i]) - if err != nil { - return nil, err - } - expose.Paths[i] = p - } - } - - return &expose, nil -} - -func parseExposePath(epo *ast.ObjectItem) (*api.ConsulExposePath, error) { - valid := []string{ - "path", - "protocol", - "local_path_port", - "listener_port", - } - - if err := checkHCLKeys(epo.Val, valid); err != nil { - return nil, multierror.Prefix(err, "path ->") - } - - var path api.ConsulExposePath - var m map[string]interface{} - if err := hcl.DecodeObject(&m, epo.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &path, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - return &path, nil -} - -func parseTproxy(epo *ast.ObjectItem) (*api.ConsulTransparentProxy, error) { - valid := []string{ - "uid", - "outbound_port", - "exclude_inbound_ports", - "exclude_outbound_ports", - "exclude_outbound_cidrs", - "exclude_uids", - "no_dns", - } - - if err := checkHCLKeys(epo.Val, valid); err != nil { - return nil, multierror.Prefix(err, "tproxy ->") - } - - var tproxy api.ConsulTransparentProxy - var m map[string]interface{} - if err := hcl.DecodeObject(&m, epo.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &tproxy, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - return &tproxy, nil -} - -func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) { - valid := []string{ - "destination_name", - "destination_peer", - "destination_partition", - "destination_type", - "local_bind_port", - "local_bind_address", - "local_bind_socket_path", - "local_bind_socket_mode", - "datacenter", - "mesh_gateway", - } - - if err := checkHCLKeys(uo.Val, valid); err != nil { - return nil, multierror.Prefix(err, "upstream ->") - } - - var upstream api.ConsulUpstream - var m map[string]interface{} - if err := hcl.DecodeObject(&m, uo.Val); err != nil { - return nil, err - } - - delete(m, "mesh_gateway") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &upstream, - }) - if err != nil { - return nil, err - } - - if err := dec.Decode(m); err != nil { - return nil, err - } - - var listVal *ast.ObjectList - if ot, ok := uo.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("'%s': should be an object", upstream.DestinationName) - } - - if mgO := listVal.Filter("mesh_gateway"); len(mgO.Items) > 0 { - if len(mgO.Items) > 1 { - return nil, fmt.Errorf("upstream '%s': cannot have more than 1 mesh_gateway", upstream.DestinationName) - } - - mgw, err := parseMeshGateway(mgO.Items[0]) - if err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", upstream.DestinationName)) - } - - upstream.MeshGateway = mgw - - } - return &upstream, nil -} - -func parseMeshGateway(gwo *ast.ObjectItem) (*api.ConsulMeshGateway, error) { - valid := []string{ - "mode", - } - - if err := checkHCLKeys(gwo.Val, valid); err != nil { - return nil, multierror.Prefix(err, "mesh_gateway ->") - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, gwo.Val); err != nil { - return nil, err - } - - var mgw api.ConsulMeshGateway - if err := mapstructure.WeakDecode(m, &mgw); err != nil { - return nil, err - } - - return &mgw, nil -} - -func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error { - service.Checks = make([]api.ServiceCheck, len(checkObjs.Items)) - for idx, co := range checkObjs.Items { - // Check for invalid keys - valid := []string{ - "name", - "type", - "interval", - "timeout", - "path", - "protocol", - "port", - "expose", - "command", - "args", - "initial_status", - "notes", - "tls_skip_verify", - "header", - "method", - "check_restart", - "address_mode", - "grpc_service", - "grpc_use_tls", - "task", - "success_before_passing", - "failures_before_critical", - "failures_before_warning", - "on_update", - "body", - } - if err := checkHCLKeys(co.Val, valid); err != nil { - return multierror.Prefix(err, "check ->") - } - - var check api.ServiceCheck - var cm map[string]interface{} - if err := hcl.DecodeObject(&cm, co.Val); err != nil { - return err - } - - // HCL allows repeating blocks so merge 'header' into a single - // map[string][]string. - if headerI, ok := cm["header"]; ok { - headerRaw, ok := headerI.([]map[string]interface{}) - if !ok { - return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI) - } - m := map[string][]string{} - for _, rawm := range headerRaw { - for k, vI := range rawm { - vs, ok := vI.([]interface{}) - if !ok { - return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI) - } - for _, vI := range vs { - v, ok := vI.(string) - if !ok { - return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI) - } - m[k] = append(m[k], v) - } - } - } - - check.Header = m - - // Remove "header" as it has been parsed - delete(cm, "header") - } - - delete(cm, "check_restart") - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &check, - }) - if err != nil { - return err - } - if err := dec.Decode(cm); err != nil { - return err - } - - // Filter check_restart - var checkRestartList *ast.ObjectList - if ot, ok := co.Val.(*ast.ObjectType); ok { - checkRestartList = ot.List - } else { - return fmt.Errorf("check_restart '%s': should be an object", check.Name) - } - - if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 { - if len(cro.Items) > 1 { - return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name) - } - cr, err := parseCheckRestart(cro.Items[0]) - if err != nil { - return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name)) - } - check.CheckRestart = cr - } - - service.Checks[idx] = check - } - - return nil -} - -func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) { - valid := []string{ - "limit", - "grace", - "ignore_warnings", - } - - if err := checkHCLKeys(cro.Val, valid); err != nil { - return nil, multierror.Prefix(err, "check_restart ->") - } - - var checkRestart api.CheckRestart - var crm map[string]interface{} - if err := hcl.DecodeObject(&crm, cro.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &checkRestart, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(crm); err != nil { - return nil, err - } - - return &checkRestart, nil -} diff --git a/jobspec/parse_task.go b/jobspec/parse_task.go deleted file mode 100644 index 9ea4a0cc6..000000000 --- a/jobspec/parse_task.go +++ /dev/null @@ -1,798 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "fmt" - "strings" - "time" - - multierror "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/hashicorp/nomad/api" - "github.com/hashicorp/nomad/helper/pointer" - "github.com/mitchellh/mapstructure" -) - -var ( - commonTaskKeys = []string{ - "driver", - "user", - "config", - "env", - "resources", - "meta", - "logs", - "kill_timeout", - "shutdown_delay", - "kill_signal", - "scaling", - } - - normalTaskKeys = append(commonTaskKeys, - "action", - "artifact", - "constraint", - "affinity", - "dispatch_payload", - "identity", - "lifecycle", - "leader", - "restart", - "service", - "template", - "vault", - "kind", - "volume_mount", - "csi_plugin", - ) - - sidecarTaskKeys = append(commonTaskKeys, - "name", - ) -) - -func parseTasks(result *[]*api.Task, list *ast.ObjectList) error { - list = list.Children() - if len(list.Items) == 0 { - return nil - } - - // Go through each object and turn it into an actual result. - seen := make(map[string]struct{}) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - // Make sure we haven't already found this - if _, ok := seen[n]; ok { - return fmt.Errorf("task '%s' defined more than once", n) - } - seen[n] = struct{}{} - - t, err := parseTask(item, normalTaskKeys) - if err != nil { - return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) - } - - t.Name = n - - *result = append(*result, t) - } - - return nil -} - -func parseTask(item *ast.ObjectItem, keys []string) (*api.Task, error) { - // We need this later - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("should be an object") - } - - // Check for invalid keys - if err := checkHCLKeys(listVal, keys); err != nil { - return nil, err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return nil, err - } - delete(m, "artifact") - delete(m, "config") - delete(m, "constraint") - delete(m, "affinity") - delete(m, "dispatch_payload") - delete(m, "lifecycle") - delete(m, "env") - delete(m, "identity") - delete(m, "logs") - delete(m, "meta") - delete(m, "resources") - delete(m, "restart") - delete(m, "service") - delete(m, "template") - delete(m, "vault") - delete(m, "volume_mount") - delete(m, "csi_plugin") - delete(m, "scaling") - - // Build the task - var t api.Task - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &t, - }) - - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, err - } - - // If we have env, then parse them - if o := listVal.Filter("env"); len(o.Items) > 0 { - for _, o := range o.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &t.Env); err != nil { - return nil, err - } - } - } - - if o := listVal.Filter("service"); len(o.Items) > 0 { - services, err := parseServices(o) - if err != nil { - return nil, err - } - - t.Services = services - } - - if o := listVal.Filter("csi_plugin"); len(o.Items) > 0 { - if len(o.Items) != 1 { - return nil, fmt.Errorf("csi_plugin -> Expected single block, got %d", len(o.Items)) - } - i := o.Elem().Items[0] - - var m map[string]interface{} - var cfg api.TaskCSIPluginConfig - if err := hcl.DecodeObject(&m, i.Val); err != nil { - return nil, err - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: &cfg, - }) - if err != nil { - return nil, err - } - if err := dec.Decode(m); err != nil { - return nil, err - } - - t.CSIPluginConfig = &cfg - } - - // If we have config, then parse that - if o := listVal.Filter("config"); len(o.Items) > 0 { - for _, o := range o.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - - if err := mapstructure.WeakDecode(m, &t.Config); err != nil { - return nil, err - } - } - } - - // Parse constraints - if o := listVal.Filter("constraint"); len(o.Items) > 0 { - if err := parseConstraints(&t.Constraints, o); err != nil { - return nil, multierror.Prefix(err, "constraint ->") - } - } - - // Parse affinities - if o := listVal.Filter("affinity"); len(o.Items) > 0 { - if err := parseAffinities(&t.Affinities, o); err != nil { - return nil, multierror.Prefix(err, "affinity ->") - } - } - - // Parse out meta fields. These are in HCL as a list so we need - // to iterate over them and merge them. - if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { - for _, o := range metaO.Elem().Items { - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return nil, err - } - if err := mapstructure.WeakDecode(m, &t.Meta); err != nil { - return nil, err - } - } - } - - // Parse volume mounts - if o := listVal.Filter("volume_mount"); len(o.Items) > 0 { - if err := parseVolumeMounts(&t.VolumeMounts, o); err != nil { - return nil, multierror.Prefix(err, "volume_mount ->") - } - } - - // If we have resources, then parse that - if o := listVal.Filter("resources"); len(o.Items) > 0 { - var r api.Resources - if err := parseResources(&r, o); err != nil { - return nil, multierror.Prefix(err, "resources ->") - } - - t.Resources = &r - } - - // Parse restart policy - if o := listVal.Filter("restart"); len(o.Items) > 0 { - if err := parseRestartPolicy(&t.RestartPolicy, o); err != nil { - return nil, multierror.Prefix(err, "restart ->") - } - } - - // If we have logs then parse that - if o := listVal.Filter("logs"); len(o.Items) > 0 { - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items)) - } - var m map[string]interface{} - logsBlock := o.Items[0] - - // Check for invalid keys - valid := []string{ - "max_files", - "max_file_size", - "enabled", // COMPAT(1.6.0): remove in favor of disabled - "disabled", - } - if err := checkHCLKeys(logsBlock.Val, valid); err != nil { - return nil, multierror.Prefix(err, "logs ->") - } - - if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil { - return nil, err - } - - var log api.LogConfig - if err := mapstructure.WeakDecode(m, &log); err != nil { - return nil, err - } - - t.LogConfig = &log - } - - // Parse artifacts - if o := listVal.Filter("artifact"); len(o.Items) > 0 { - if err := parseArtifacts(&t.Artifacts, o); err != nil { - return nil, multierror.Prefix(err, "artifact ->") - } - } - - // Parse identity - if o := listVal.Filter("identity"); len(o.Items) > 0 { - v := &api.WorkloadIdentity{} - if err := parseIdentity(v, o); err != nil { - return nil, multierror.Prefix(err, "identity ->") - } - t.Identity = v - } - - // Parse templates - if o := listVal.Filter("template"); len(o.Items) > 0 { - if err := parseTemplates(&t.Templates, o); err != nil { - return nil, multierror.Prefix(err, "template ->") - } - } - - // Parse scaling policies - if o := listVal.Filter("scaling"); len(o.Items) > 0 { - if err := parseTaskScalingPolicies(&t.ScalingPolicies, o); err != nil { - return nil, err - } - } - - // If we have a vault block, then parse that - if o := listVal.Filter("vault"); len(o.Items) > 0 { - v := &api.Vault{ - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr("restart"), - } - - if err := parseVault(v, o); err != nil { - return nil, multierror.Prefix(err, "vault ->") - } - - t.Vault = v - } - - // If we have a dispatch_payload block parse that - if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 { - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items)) - } - var m map[string]interface{} - dispatchBlock := o.Items[0] - - // Check for invalid keys - valid := []string{ - "file", - } - if err := checkHCLKeys(dispatchBlock.Val, valid); err != nil { - return nil, multierror.Prefix(err, "dispatch_payload ->") - } - - if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil { - return nil, err - } - - t.DispatchPayload = &api.DispatchPayloadConfig{} - if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil { - return nil, err - } - } - - // If we have a lifecycle block parse that - if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { - if len(o.Items) > 1 { - return nil, fmt.Errorf("only one lifecycle block is allowed in a task. Number of lifecycle blocks found: %d", len(o.Items)) - } - - var m map[string]interface{} - lifecycleBlock := o.Items[0] - - // Check for invalid keys - valid := []string{ - "hook", - "sidecar", - } - if err := checkHCLKeys(lifecycleBlock.Val, valid); err != nil { - return nil, multierror.Prefix(err, "lifecycle ->") - } - - if err := hcl.DecodeObject(&m, lifecycleBlock.Val); err != nil { - return nil, err - } - - t.Lifecycle = &api.TaskLifecycle{} - if err := mapstructure.WeakDecode(m, t.Lifecycle); err != nil { - return nil, err - } - } - return &t, nil -} - -func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error { - for _, o := range list.Elem().Items { - // Check for invalid keys - valid := []string{ - "source", - "options", - "headers", - "mode", - "destination", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - delete(m, "options") - - var ta api.TaskArtifact - if err := mapstructure.WeakDecode(m, &ta); err != nil { - return err - } - - var optionList *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - optionList = ot.List - } else { - return fmt.Errorf("artifact should be an object") - } - - if oo := optionList.Filter("options"); len(oo.Items) > 0 { - options := make(map[string]string) - if err := parseArtifactOption(options, oo); err != nil { - return multierror.Prefix(err, "options: ") - } - ta.GetterOptions = options - } - - *result = append(*result, &ta) - } - - return nil -} - -func parseArtifactOption(result map[string]string, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) > 1 { - return fmt.Errorf("only one 'options' block allowed per artifact") - } - - // Get our resource object - o := list.Items[0] - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - if err := mapstructure.WeakDecode(m, &result); err != nil { - return err - } - - return nil -} - -func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { - for _, o := range list.Elem().Items { - // we'll need a list of all ast objects for later - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("should be an object") - } - - // Check for invalid keys - valid := []string{ - "change_mode", - "change_signal", - "change_script", - "data", - "destination", - "left_delimiter", - "perms", - "uid", - "gid", - "right_delimiter", - "source", - "splay", - "env", - "vault_grace", //COMPAT(0.12) not used; emits warning in 0.11. - "wait", - "error_on_missing_key", - } - if err := checkHCLKeys(o.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - delete(m, "change_script") // change_script is its own object - - templ := &api.Template{ - ChangeMode: stringToPtr("restart"), - Splay: timeToPtr(5 * time.Second), - Perms: stringToPtr("0644"), - ErrMissingKey: pointer.Of(false), - } - - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: templ, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - - // If we have change_script, parse it - if o := listVal.Filter("change_script"); len(o.Items) > 0 { - if len(o.Items) != 1 { - return fmt.Errorf( - "change_script -> expected single block, got %d", len(o.Items), - ) - } - var m map[string]interface{} - changeScriptBlock := o.Items[0] - - // check for invalid fields - valid := []string{"command", "args", "timeout", "fail_on_error"} - if err := checkHCLKeys(changeScriptBlock.Val, valid); err != nil { - return multierror.Prefix(err, "change_script ->") - } - - if err := hcl.DecodeObject(&m, changeScriptBlock.Val); err != nil { - return err - } - - templ.ChangeScript = &api.ChangeScript{} - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - WeaklyTypedInput: true, - Result: templ.ChangeScript, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - } - - *result = append(*result, templ) - } - - return nil -} - -func parseTaskScalingPolicies(result *[]*api.ScalingPolicy, list *ast.ObjectList) error { - if len(list.Items) == 0 { - return nil - } - - errPrefix := "scaling ->" - // Go through each object and turn it into an actual result. - seen := make(map[string]bool) - for _, item := range list.Items { - if l := len(item.Keys); l == 0 { - return multierror.Prefix(fmt.Errorf("task scaling policy missing name"), errPrefix) - } else if l > 1 { - return multierror.Prefix(fmt.Errorf("task scaling policy should only have one name"), errPrefix) - } - n := item.Keys[0].Token.Value().(string) - errPrefix = fmt.Sprintf("scaling[%v] ->", n) - - var policyType string - switch strings.ToLower(n) { - case "cpu": - policyType = "vertical_cpu" - case "mem": - policyType = "vertical_mem" - default: - return multierror.Prefix(fmt.Errorf(`scaling policy name must be "cpu" or "mem"`), errPrefix) - } - - // Make sure we haven't already found this - if seen[n] { - return multierror.Prefix(fmt.Errorf("scaling policy cannot be defined more than once"), errPrefix) - } - seen[n] = true - - p, err := parseScalingPolicy(item) - if err != nil { - return multierror.Prefix(err, errPrefix) - } - - if p.Type == "" { - p.Type = policyType - } else if p.Type != policyType { - return multierror.Prefix(fmt.Errorf("policy had invalid 'type': %q", p.Type), errPrefix) - } - - *result = append(*result, p) - } - - return nil -} - -func parseResources(result *api.Resources, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) == 0 { - return nil - } - if len(list.Items) > 1 { - return fmt.Errorf("only one 'resource' block allowed per task") - } - - // Get our resource object - o := list.Items[0] - - // We need this later - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("resource: should be an object") - } - - // Check for invalid keys - valid := []string{ - "cpu", - "iops", // COMPAT(0.10): Remove after one release to allow it to be removed from jobspecs - "disk", - "memory", - "memory_max", - "network", - "device", - "cores", - } - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, "resources ->") - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - delete(m, "network") - delete(m, "device") - - if err := mapstructure.WeakDecode(m, result); err != nil { - return err - } - - // Parse the network resources - if o := listVal.Filter("network"); len(o.Items) > 0 { - r, err := ParseNetwork(o) - if err != nil { - return fmt.Errorf("resource, %v", err) - } - result.Networks = []*api.NetworkResource{r} - } - - // Parse the device resources - if o := listVal.Filter("device"); len(o.Items) > 0 { - result.Devices = make([]*api.RequestedDevice, len(o.Items)) - for idx, do := range o.Items { - if l := len(do.Keys); l == 0 { - return multierror.Prefix(fmt.Errorf("missing device name"), fmt.Sprintf("resources, device[%d]->", idx)) - } else if l > 1 { - return multierror.Prefix(fmt.Errorf("only one name may be specified"), fmt.Sprintf("resources, device[%d]->", idx)) - } - name := do.Keys[0].Token.Value().(string) - - // Value should be an object - var listVal *ast.ObjectList - if ot, ok := do.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("device should be an object") - } - - // Check for invalid keys - valid := []string{ - "name", - "count", - "affinity", - "constraint", - } - if err := checkHCLKeys(do.Val, valid); err != nil { - return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx)) - } - - // Set the name - var r api.RequestedDevice - r.Name = name - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, do.Val); err != nil { - return err - } - - delete(m, "constraint") - delete(m, "affinity") - - if err := mapstructure.WeakDecode(m, &r); err != nil { - return err - } - - // Parse constraints - if o := listVal.Filter("constraint"); len(o.Items) > 0 { - if err := parseConstraints(&r.Constraints, o); err != nil { - return multierror.Prefix(err, "constraint ->") - } - } - - // Parse affinities - if o := listVal.Filter("affinity"); len(o.Items) > 0 { - if err := parseAffinities(&r.Affinities, o); err != nil { - return multierror.Prefix(err, "affinity ->") - } - } - - result.Devices[idx] = &r - } - } - - return nil -} - -func parseVolumeMounts(out *[]*api.VolumeMount, list *ast.ObjectList) error { - mounts := make([]*api.VolumeMount, len(list.Items)) - - for i, item := range list.Items { - valid := []string{ - "volume", - "read_only", - "destination", - "propagation_mode", - } - if err := checkHCLKeys(item.Val, valid); err != nil { - return err - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, item.Val); err != nil { - return err - } - - var result api.VolumeMount - dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: &result, - }) - if err != nil { - return err - } - if err := dec.Decode(m); err != nil { - return err - } - - mounts[i] = &result - } - - *out = mounts - return nil -} - -func parseIdentity(out *api.WorkloadIdentity, list *ast.ObjectList) error { - list = list.Elem() - if len(list.Items) == 0 { - return nil - } - if len(list.Items) > 1 { - return fmt.Errorf("only one 'identity' block allowed per task") - } - - o := list.Items[0] - var listVal *ast.ObjectList - if ot, ok := o.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return fmt.Errorf("identity: should be an object") - } - - valid := []string{ - "env", - "file", - } - - if err := checkHCLKeys(listVal, valid); err != nil { - return multierror.Prefix(err, "identity ->") - } - - var m map[string]interface{} - if err := hcl.DecodeObject(&m, o.Val); err != nil { - return err - } - - if err := mapstructure.WeakDecode(m, out); err != nil { - return err - } - - return nil -} diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go deleted file mode 100644 index 14d2194af..000000000 --- a/jobspec/parse_test.go +++ /dev/null @@ -1,2041 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "path/filepath" - "strings" - "testing" - "time" - - capi "github.com/hashicorp/consul/api" - "github.com/hashicorp/nomad/api" - "github.com/hashicorp/nomad/ci" - "github.com/hashicorp/nomad/helper/pointer" - "github.com/shoenig/test/must" -) - -// consts copied from nomad/structs package to keep jobspec isolated from rest of nomad -const ( - // vaultChangeModeRestart restarts the task when a new token is retrieved. - vaultChangeModeRestart = "restart" - - // vaultChangeModeSignal signals the task when a new token is retrieved. - vaultChangeModeSignal = "signal" - - // templateChangeModeRestart marks that the task should be restarted if the - templateChangeModeRestart = "restart" - - // templateChangeModeScript marks that ac script should be executed on - // template re-render - templateChangeModeScript = "script" -) - -// Helper functions below are only used by this test suite -func int8ToPtr(i int8) *int8 { - return &i -} -func uint64ToPtr(u uint64) *uint64 { - return &u -} -func int64ToPtr(i int64) *int64 { - return &i -} - -func TestParse(t *testing.T) { - ci.Parallel(t) - - cases := []struct { - File string - Result *api.Job - Err bool - }{ - { - "basic.hcl", - &api.Job{ - ID: stringToPtr("binstore-storagelocker"), - Name: stringToPtr("binstore-storagelocker"), - Type: stringToPtr("batch"), - Priority: intToPtr(52), - AllAtOnce: boolToPtr(true), - Datacenters: []string{"us2", "eu1"}, - Region: stringToPtr("fooregion"), - Namespace: stringToPtr("foonamespace"), - NodePool: stringToPtr("dev"), - ConsulToken: stringToPtr("abc"), - VaultToken: stringToPtr("foo"), - - Meta: map[string]string{ - "foo": "bar", - }, - - Constraints: []*api.Constraint{ - { - LTarget: "kernel.os", - RTarget: "windows", - Operand: "=", - }, - { - LTarget: "${attr.vault.version}", - RTarget: ">= 0.6.1", - Operand: "semver", - }, - }, - - Affinities: []*api.Affinity{ - { - LTarget: "${meta.team}", - RTarget: "mobile", - Operand: "=", - Weight: int8ToPtr(50), - }, - }, - - Spreads: []*api.Spread{ - { - Attribute: "${meta.rack}", - Weight: int8ToPtr(100), - SpreadTarget: []*api.SpreadTarget{ - { - Value: "r1", - Percent: 40, - }, - { - Value: "r2", - Percent: 60, - }, - }, - }, - }, - - Update: &api.UpdateStrategy{ - Stagger: timeToPtr(60 * time.Second), - MaxParallel: intToPtr(2), - HealthCheck: stringToPtr("manual"), - MinHealthyTime: timeToPtr(10 * time.Second), - HealthyDeadline: timeToPtr(10 * time.Minute), - ProgressDeadline: timeToPtr(10 * time.Minute), - AutoRevert: boolToPtr(true), - AutoPromote: boolToPtr(true), - Canary: intToPtr(1), - }, - - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("outside"), - - Tasks: []*api.Task{ - { - Name: "outside", - Driver: "java", - Config: map[string]interface{}{ - "jar_path": "s3://my-cool-store/foo.jar", - }, - Meta: map[string]string{ - "my-cool-key": "foobar", - }, - }, - }, - }, - - { - Name: stringToPtr("binsl"), - Count: intToPtr(5), - StopAfterClientDisconnect: timeToPtr(120 * time.Second), - MaxClientDisconnect: timeToPtr(120 * time.Hour), - Constraints: []*api.Constraint{ - { - LTarget: "kernel.os", - RTarget: "linux", - Operand: "=", - }, - }, - Volumes: map[string]*api.VolumeRequest{ - "foo": { - Name: "foo", - Type: "host", - Source: "/path", - ExtraKeysHCL: nil, - }, - "bar": { - Name: "bar", - Type: "csi", - Source: "bar-vol", - ReadOnly: true, - AccessMode: "single-mode-writer", - AttachmentMode: "file-system", - MountOptions: &api.CSIMountOptions{ - FSType: "ext4", - }, - ExtraKeysHCL: nil, - }, - "baz": { - Name: "baz", - Type: "csi", - Source: "bar-vol", - MountOptions: &api.CSIMountOptions{ - MountFlags: []string{ - "ro", - }, - }, - PerAlloc: true, - ExtraKeysHCL: nil, - }, - }, - Affinities: []*api.Affinity{ - { - LTarget: "${node.datacenter}", - RTarget: "dc2", - Operand: "=", - Weight: int8ToPtr(100), - }, - }, - Meta: map[string]string{ - "elb_mode": "tcp", - "elb_interval": "10", - "elb_checks": "3", - }, - RestartPolicy: &api.RestartPolicy{ - Interval: timeToPtr(10 * time.Minute), - Attempts: intToPtr(5), - Delay: timeToPtr(15 * time.Second), - Mode: stringToPtr("delay"), - RenderTemplates: boolToPtr(false), - }, - Spreads: []*api.Spread{ - { - Attribute: "${node.datacenter}", - Weight: int8ToPtr(50), - SpreadTarget: []*api.SpreadTarget{ - { - Value: "dc1", - Percent: 50, - }, - { - Value: "dc2", - Percent: 25, - }, - { - Value: "dc3", - Percent: 25, - }, - }, - }, - }, - Disconnect: &api.DisconnectStrategy{ - StopOnClientAfter: timeToPtr(120 * time.Second), - LostAfter: timeToPtr(120 * time.Hour), - Replace: boolToPtr(true), - Reconcile: stringToPtr("best_score"), - }, - ReschedulePolicy: &api.ReschedulePolicy{ - Interval: timeToPtr(12 * time.Hour), - Attempts: intToPtr(5), - }, - EphemeralDisk: &api.EphemeralDisk{ - Sticky: boolToPtr(true), - SizeMB: intToPtr(150), - }, - Update: &api.UpdateStrategy{ - MaxParallel: intToPtr(3), - HealthCheck: stringToPtr("checks"), - MinHealthyTime: timeToPtr(1 * time.Second), - HealthyDeadline: timeToPtr(1 * time.Minute), - ProgressDeadline: timeToPtr(1 * time.Minute), - AutoRevert: boolToPtr(false), - AutoPromote: boolToPtr(false), - Canary: intToPtr(2), - }, - Migrate: &api.MigrateStrategy{ - MaxParallel: intToPtr(2), - HealthCheck: stringToPtr("task_states"), - MinHealthyTime: timeToPtr(11 * time.Second), - HealthyDeadline: timeToPtr(11 * time.Minute), - }, - Tasks: []*api.Task{ - { - Name: "binstore", - Driver: "docker", - User: "bob", - Kind: "connect-proxy:test", - Config: map[string]interface{}{ - "image": "hashicorp/binstore", - "labels": []map[string]interface{}{ - { - "FOO": "bar", - }, - }, - }, - VolumeMounts: []*api.VolumeMount{ - { - Volume: stringToPtr("foo"), - Destination: stringToPtr("/mnt/foo"), - }, - }, - Affinities: []*api.Affinity{ - { - LTarget: "${meta.foo}", - RTarget: "a,b,c", - Operand: "set_contains", - Weight: int8ToPtr(25), - }, - }, - RestartPolicy: &api.RestartPolicy{ - Attempts: intToPtr(10), - }, - Services: []*api.Service{ - { - Tags: []string{"foo", "bar"}, - CanaryTags: []string{"canary", "bam"}, - Meta: map[string]string{ - "abc": "123", - }, - CanaryMeta: map[string]string{ - "canary": "boom", - }, - PortLabel: "http", - Checks: []api.ServiceCheck{ - { - Name: "check-name", - Type: "tcp", - PortLabel: "admin", - Interval: 10 * time.Second, - Timeout: 2 * time.Second, - GRPCService: "foo.Bar", - GRPCUseTLS: true, - CheckRestart: &api.CheckRestart{ - Limit: 3, - Grace: timeToPtr(10 * time.Second), - IgnoreWarnings: true, - }, - }, - }, - }, - }, - Env: map[string]string{ - "HELLO": "world", - "LOREM": "ipsum", - }, - Resources: &api.Resources{ - CPU: intToPtr(500), - MemoryMB: intToPtr(128), - MemoryMaxMB: intToPtr(256), - Networks: []*api.NetworkResource{ - { - MBits: intToPtr(100), - ReservedPorts: []api.Port{{Label: "one", Value: 1}, {Label: "two", Value: 2}, {Label: "three", Value: 3}}, - DynamicPorts: []api.Port{{Label: "http", Value: 0}, {Label: "https", Value: 0}, {Label: "admin", Value: 0}}, - }, - }, - Devices: []*api.RequestedDevice{ - { - Name: "nvidia/gpu", - Count: uint64ToPtr(10), - Constraints: []*api.Constraint{ - { - LTarget: "${device.attr.memory}", - RTarget: "2GB", - Operand: ">", - }, - }, - Affinities: []*api.Affinity{ - { - LTarget: "${device.model}", - RTarget: "1080ti", - Operand: "=", - Weight: int8ToPtr(50), - }, - }, - }, - { - Name: "intel/gpu", - Count: nil, - }, - }, - }, - KillTimeout: timeToPtr(22 * time.Second), - ShutdownDelay: 11 * time.Second, - LogConfig: &api.LogConfig{ - MaxFiles: intToPtr(14), - MaxFileSizeMB: intToPtr(101), - Disabled: boolToPtr(false), - }, - Artifacts: []*api.TaskArtifact{ - { - GetterSource: stringToPtr("http://foo.com/artifact"), - GetterOptions: map[string]string{ - "checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f", - }, - }, - { - GetterSource: stringToPtr("http://bar.com/artifact"), - RelativeDest: stringToPtr("test/foo/"), - GetterOptions: map[string]string{ - "checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c", - }, - GetterMode: stringToPtr("file"), - }, - }, - Vault: &api.Vault{ - Namespace: stringToPtr("ns1"), - Policies: []string{"foo", "bar"}, - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr(vaultChangeModeRestart), - }, - Templates: []*api.Template{ - { - SourcePath: stringToPtr("foo"), - DestPath: stringToPtr("foo"), - ChangeMode: stringToPtr("foo"), - ChangeSignal: stringToPtr("foo"), - Splay: timeToPtr(10 * time.Second), - Perms: stringToPtr("0644"), - Envvars: boolToPtr(true), - VaultGrace: timeToPtr(33 * time.Second), - ErrMissingKey: boolToPtr(true), - }, - { - SourcePath: stringToPtr("bar"), - DestPath: stringToPtr("bar"), - ChangeMode: stringToPtr(templateChangeModeScript), - ChangeScript: &api.ChangeScript{ - Args: []string{"-debug", "-verbose"}, - Command: stringToPtr("/bin/foo"), - Timeout: timeToPtr(5 * time.Second), - FailOnError: boolToPtr(false), - }, - Splay: timeToPtr(5 * time.Second), - Perms: stringToPtr("777"), - Uid: intToPtr(1001), - Gid: intToPtr(20), - LeftDelim: stringToPtr("--"), - RightDelim: stringToPtr("__"), - ErrMissingKey: boolToPtr(false), - }, - }, - Leader: true, - KillSignal: "", - }, - { - Name: "storagelocker", - Driver: "docker", - User: "", - Lifecycle: &api.TaskLifecycle{ - Hook: "prestart", - Sidecar: true, - }, - Config: map[string]interface{}{ - "image": "hashicorp/storagelocker", - }, - Resources: &api.Resources{ - CPU: intToPtr(500), - MemoryMB: intToPtr(128), - }, - Constraints: []*api.Constraint{ - { - LTarget: "kernel.arch", - RTarget: "amd64", - Operand: "=", - }, - }, - Vault: &api.Vault{ - Policies: []string{"foo", "bar"}, - Env: boolToPtr(false), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr(vaultChangeModeSignal), - ChangeSignal: stringToPtr("SIGUSR1"), - }, - }, - }, - }, - }, - }, - false, - }, - - { - "multi-network.hcl", - nil, - true, - }, - - { - "multi-resource.hcl", - nil, - true, - }, - - { - "multi-vault.hcl", - nil, - true, - }, - - { - "default-job.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - }, - false, - }, - - { - "version-constraint.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Constraints: []*api.Constraint{ - { - LTarget: "$attr.kernel.version", - RTarget: "~> 3.2", - Operand: api.ConstraintVersion, - }, - }, - }, - false, - }, - - { - "regexp-constraint.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Constraints: []*api.Constraint{ - { - LTarget: "$attr.kernel.version", - RTarget: "[0-9.]+", - Operand: api.ConstraintRegex, - }, - }, - }, - false, - }, - - { - "set-contains-constraint.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Constraints: []*api.Constraint{ - { - LTarget: "$meta.data", - RTarget: "foo,bar,baz", - Operand: api.ConstraintSetContains, - }, - }, - }, - false, - }, - - { - "distinctHosts-constraint.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Constraints: []*api.Constraint{ - { - Operand: api.ConstraintDistinctHosts, - RTarget: "true", - }, - }, - }, - false, - }, - - { - "distinctProperty-constraint.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Constraints: []*api.Constraint{ - { - Operand: api.ConstraintDistinctProperty, - LTarget: "${meta.rack}", - }, - }, - }, - false, - }, - - { - "periodic-cron.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Periodic: &api.PeriodicConfig{ - SpecType: stringToPtr(api.PeriodicSpecCron), - Spec: stringToPtr("*/5 * * *"), - ProhibitOverlap: boolToPtr(true), - TimeZone: stringToPtr("Europe/Minsk"), - }, - }, - false, - }, - - { - "periodic-crons.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Periodic: &api.PeriodicConfig{ - SpecType: stringToPtr(api.PeriodicSpecCron), - Specs: []string{"*/5 * * *", "*/7 * * *"}, - ProhibitOverlap: boolToPtr(true), - TimeZone: stringToPtr("Europe/Minsk"), - }, - }, - false, - }, - - { - "specify-job.hcl", - &api.Job{ - ID: stringToPtr("job1"), - Name: stringToPtr("My Job"), - }, - false, - }, - - { - "task-nested-config.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "docker", - Config: map[string]interface{}{ - "image": "hashicorp/image", - "port_map": []map[string]interface{}{ - { - "db": 1234, - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - - { - "bad-artifact.hcl", - nil, - true, - }, - - { - "artifacts.hcl", - &api.Job{ - ID: stringToPtr("binstore-storagelocker"), - Name: stringToPtr("binstore-storagelocker"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("binsl"), - Tasks: []*api.Task{ - { - Name: "binstore", - Driver: "docker", - Artifacts: []*api.TaskArtifact{ - { - GetterSource: stringToPtr("http://foo.com/bar"), - GetterOptions: map[string]string{"foo": "bar"}, - RelativeDest: stringToPtr(""), - }, - { - GetterSource: stringToPtr("http://foo.com/baz"), - GetterOptions: nil, - RelativeDest: nil, - }, - { - GetterSource: stringToPtr("http://foo.com/bam"), - GetterOptions: nil, - RelativeDest: stringToPtr("var/foo"), - }, - { - GetterSource: stringToPtr("https://example.com/file.txt"), - GetterHeaders: map[string]string{ - "User-Agent": "nomad", - "X-Nomad-Alloc": "alloc", - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "csi-plugin.hcl", - &api.Job{ - ID: stringToPtr("binstore-storagelocker"), - Name: stringToPtr("binstore-storagelocker"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("binsl"), - Tasks: []*api.Task{ - { - Name: "binstore", - Driver: "docker", - CSIPluginConfig: &api.TaskCSIPluginConfig{ - ID: "org.hashicorp.csi", - Type: api.CSIPluginTypeMonolith, - MountDir: "/csi/test", - HealthTimeout: 1 * time.Minute, - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-check-initial-status.hcl", - &api.Job{ - ID: stringToPtr("check_initial_status"), - Name: stringToPtr("check_initial_status"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Count: intToPtr(1), - Tasks: []*api.Task{ - { - Name: "task", - Services: []*api.Service{ - { - Tags: []string{"foo", "bar"}, - PortLabel: "http", - Checks: []api.ServiceCheck{ - { - Name: "check-name", - Type: "http", - Path: "/", - Interval: 10 * time.Second, - Timeout: 2 * time.Second, - InitialStatus: capi.HealthPassing, - Method: "POST", - Header: map[string][]string{ - "Authorization": {"Basic ZWxhc3RpYzpjaGFuZ2VtZQ=="}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-check-pass-fail.hcl", - &api.Job{ - ID: stringToPtr("check_pass_fail"), - Name: stringToPtr("check_pass_fail"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Count: intToPtr(1), - Tasks: []*api.Task{{ - Name: "task", - Services: []*api.Service{{ - Name: "service", - PortLabel: "http", - Checks: []api.ServiceCheck{{ - Name: "check-name", - Type: "http", - Path: "/", - Interval: 10 * time.Second, - Timeout: 2 * time.Second, - InitialStatus: capi.HealthPassing, - Method: "POST", - SuccessBeforePassing: 3, - FailuresBeforeCritical: 4, - FailuresBeforeWarning: 2, - }}, - }}, - }}, - }}, - }, - false, - }, - { - "service-check-pass-fail.hcl", - &api.Job{ - ID: stringToPtr("check_pass_fail"), - Name: stringToPtr("check_pass_fail"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Count: intToPtr(1), - Tasks: []*api.Task{{ - Name: "task", - Services: []*api.Service{{ - Name: "service", - PortLabel: "http", - Checks: []api.ServiceCheck{{ - Name: "check-name", - Type: "http", - Path: "/", - Interval: 10 * time.Second, - Timeout: 2 * time.Second, - InitialStatus: capi.HealthPassing, - Method: "POST", - SuccessBeforePassing: 3, - FailuresBeforeCritical: 4, - FailuresBeforeWarning: 2, - }}, - }}, - }}, - }}, - }, - false, - }, - { - "service-check-bad-header.hcl", - nil, - true, - }, - { - "service-check-bad-header-2.hcl", - nil, - true, - }, - { - // TODO This should be pushed into the API - "vault_inheritance.hcl", - &api.Job{ - ID: stringToPtr("example"), - Name: stringToPtr("example"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("cache"), - Tasks: []*api.Task{ - { - Name: "redis", - Vault: &api.Vault{ - Policies: []string{"group"}, - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr(vaultChangeModeRestart), - }, - }, - { - Name: "redis2", - Vault: &api.Vault{ - Policies: []string{"task"}, - Env: boolToPtr(false), - DisableFile: boolToPtr(true), - ChangeMode: stringToPtr(vaultChangeModeRestart), - }, - }, - }, - }, - { - Name: stringToPtr("cache2"), - Tasks: []*api.Task{ - { - Name: "redis", - Vault: &api.Vault{ - Policies: []string{"job"}, - Env: boolToPtr(true), - DisableFile: boolToPtr(false), - ChangeMode: stringToPtr(vaultChangeModeRestart), - }, - }, - }, - }, - }, - }, - false, - }, - { - "parameterized_job.hcl", - &api.Job{ - ID: stringToPtr("parameterized_job"), - Name: stringToPtr("parameterized_job"), - - ParameterizedJob: &api.ParameterizedJobConfig{ - Payload: "required", - MetaRequired: []string{"foo", "bar"}, - MetaOptional: []string{"baz", "bam"}, - }, - - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("foo"), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "docker", - DispatchPayload: &api.DispatchPayloadConfig{ - File: "foo/bar", - }, - }, - }, - }, - }, - }, - false, - }, - { - "job-with-kill-signal.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "docker", - KillSignal: "SIGQUIT", - Config: map[string]interface{}{ - "image": "hashicorp/image", - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-tagged-address.hcl", - &api.Job{ - ID: stringToPtr("service_tagged_address"), - Name: stringToPtr("service_tagged_address"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Services: []*api.Service{ - { - Name: "service1", - TaggedAddresses: map[string]string{ - "public_wan": "1.2.3.4", - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-check-driver-address.hcl", - &api.Job{ - ID: stringToPtr("address_mode_driver"), - Name: stringToPtr("address_mode_driver"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Tasks: []*api.Task{ - { - Name: "task", - Services: []*api.Service{ - { - Name: "http-service", - PortLabel: "http", - AddressMode: "auto", - Checks: []api.ServiceCheck{ - { - Name: "http-check", - Type: "http", - Path: "/", - PortLabel: "http", - AddressMode: "driver", - }, - }, - }, - { - Name: "random-service", - PortLabel: "9000", - AddressMode: "driver", - Checks: []api.ServiceCheck{ - { - Name: "random-check", - Type: "tcp", - PortLabel: "9001", - AddressMode: "driver", - }, - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-check-restart.hcl", - &api.Job{ - ID: stringToPtr("service_check_restart"), - Name: stringToPtr("service_check_restart"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Tasks: []*api.Task{ - { - Name: "task", - Services: []*api.Service{ - { - Name: "http-service", - CheckRestart: &api.CheckRestart{ - Limit: 3, - Grace: timeToPtr(10 * time.Second), - IgnoreWarnings: true, - }, - Checks: []api.ServiceCheck{ - { - Name: "random-check", - Type: "tcp", - PortLabel: "9001", - }, - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-meta.hcl", - &api.Job{ - ID: stringToPtr("service_meta"), - Name: stringToPtr("service_meta"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Tasks: []*api.Task{ - { - Name: "task", - Services: []*api.Service{ - { - Name: "http-service", - Meta: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-enable-tag-override.hcl", - &api.Job{ - ID: stringToPtr("service_eto"), - Name: stringToPtr("service_eto"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Tasks: []*api.Task{{ - Name: "task", - Services: []*api.Service{{ - Name: "example", - EnableTagOverride: true, - }}, - }}, - }}, - }, - false, - }, - { - "reschedule-job.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Type: stringToPtr("batch"), - Datacenters: []string{"dc1"}, - Reschedule: &api.ReschedulePolicy{ - Attempts: intToPtr(15), - Interval: timeToPtr(30 * time.Minute), - DelayFunction: stringToPtr("constant"), - Delay: timeToPtr(10 * time.Second), - }, - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Count: intToPtr(3), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "raw_exec", - Config: map[string]interface{}{ - "command": "bash", - "args": []interface{}{"-c", "echo hi"}, - }, - }, - }, - }, - }, - }, - false, - }, - { - "reschedule-job-unlimited.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Type: stringToPtr("batch"), - Datacenters: []string{"dc1"}, - Reschedule: &api.ReschedulePolicy{ - DelayFunction: stringToPtr("exponential"), - Delay: timeToPtr(10 * time.Second), - MaxDelay: timeToPtr(120 * time.Second), - Unlimited: boolToPtr(true), - }, - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Count: intToPtr(3), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "raw_exec", - Config: map[string]interface{}{ - "command": "bash", - "args": []interface{}{"-c", "echo hi"}, - }, - }, - }, - }, - }, - }, - false, - }, - { - "migrate-job.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Type: stringToPtr("batch"), - Datacenters: []string{"dc1"}, - Migrate: &api.MigrateStrategy{ - MaxParallel: intToPtr(2), - HealthCheck: stringToPtr("task_states"), - MinHealthyTime: timeToPtr(11 * time.Second), - HealthyDeadline: timeToPtr(11 * time.Minute), - }, - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Count: intToPtr(3), - Migrate: &api.MigrateStrategy{ - MaxParallel: intToPtr(3), - HealthCheck: stringToPtr("checks"), - MinHealthyTime: timeToPtr(1 * time.Second), - HealthyDeadline: timeToPtr(1 * time.Minute), - }, - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "raw_exec", - Config: map[string]interface{}{ - "command": "bash", - "args": []interface{}{"-c", "echo hi"}, - }, - }, - }, - }, - }, - }, - false, - }, - { - "tg-network.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - Datacenters: []string{"dc1"}, - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - ShutdownDelay: timeToPtr(14 * time.Second), - Count: intToPtr(3), - Networks: []*api.NetworkResource{ - { - Mode: "bridge", - ReservedPorts: []api.Port{ - { - Label: "http", - Value: 80, - To: 8080, - HostNetwork: "public", - }, - }, - DNS: &api.DNSConfig{ - Servers: []string{"8.8.8.8"}, - Options: []string{"ndots:2", "edns0"}, - }, - }, - }, - Services: []*api.Service{ - { - Name: "connect-service", - Tags: []string{"foo", "bar"}, - CanaryTags: []string{"canary", "bam"}, - PortLabel: "1234", - Connect: &api.ConsulConnect{ - SidecarService: &api.ConsulSidecarService{ - Tags: []string{"side1", "side2"}, - Proxy: &api.ConsulProxy{ - LocalServicePort: 8080, - Upstreams: []*api.ConsulUpstream{ - { - DestinationName: "other-service", - DestinationPeer: "10.0.0.1:6379", - DestinationPartition: "infra", - DestinationType: "tcp", - LocalBindPort: 4567, - LocalBindAddress: "0.0.0.0", - LocalBindSocketPath: "/var/run/testsocket.sock", - LocalBindSocketMode: "0666", - Datacenter: "dc1", - - MeshGateway: &api.ConsulMeshGateway{ - Mode: "local", - }, - }, - }, - }, - }, - SidecarTask: &api.SidecarTask{ - Resources: &api.Resources{ - CPU: intToPtr(500), - MemoryMB: intToPtr(1024), - }, - Env: map[string]string{ - "FOO": "abc", - }, - ShutdownDelay: timeToPtr(5 * time.Second), - }, - }, - }, - }, - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "raw_exec", - Config: map[string]interface{}{ - "command": "bash", - "args": []interface{}{"-c", "echo hi"}, - }, - Resources: &api.Resources{ - Networks: []*api.NetworkResource{ - { - MBits: intToPtr(10), - }, - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "tg-service-check.hcl", - &api.Job{ - ID: stringToPtr("group_service_check_script"), - Name: stringToPtr("group_service_check_script"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Count: intToPtr(1), - Networks: []*api.NetworkResource{ - { - Mode: "bridge", - ReservedPorts: []api.Port{ - { - Label: "http", - Value: 80, - To: 8080, - }, - }, - }, - }, - Services: []*api.Service{ - { - Name: "foo-service", - PortLabel: "http", - OnUpdate: "ignore", - Checks: []api.ServiceCheck{ - { - Name: "check-name", - Type: "script", - Command: "/bin/true", - Interval: time.Duration(10 * time.Second), - Timeout: time.Duration(2 * time.Second), - InitialStatus: "passing", - TaskName: "foo", - OnUpdate: "ignore", - Body: "post body", - }, - }, - }, - }, - Tasks: []*api.Task{{Name: "foo"}}, - }, - }, - }, - false, - }, - { - "tg-service-proxy-expose.hcl", - &api.Job{ - ID: stringToPtr("group_service_proxy_expose"), - Name: stringToPtr("group_service_proxy_expose"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - SidecarService: &api.ConsulSidecarService{ - Proxy: &api.ConsulProxy{ - Expose: &api.ConsulExposeConfig{ - Paths: []*api.ConsulExposePath{{ - Path: "/health", - Protocol: "http", - LocalPathPort: 2222, - ListenerPort: "healthcheck", - }, { - Path: "/metrics", - Protocol: "grpc", - LocalPathPort: 3000, - ListenerPort: "metrics", - }}, - }, - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-sidecar_task-name.hcl", - &api.Job{ - ID: stringToPtr("sidecar_task_name"), - Name: stringToPtr("sidecar_task_name"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - Native: false, - SidecarService: &api.ConsulSidecarService{}, - SidecarTask: &api.SidecarTask{ - Name: "my-sidecar", - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-sidecar_disablecheck.hcl", - &api.Job{ - ID: stringToPtr("sidecar_disablecheck"), - Name: stringToPtr("sidecar_disablecheck"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - Native: false, - SidecarService: &api.ConsulSidecarService{ - DisableDefaultTCPCheck: true, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-sidecar_meta.hcl", - &api.Job{ - ID: stringToPtr("sidecar_meta"), - Name: stringToPtr("sidecar_meta"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - Native: false, - SidecarService: &api.ConsulSidecarService{ - Meta: map[string]string{ - "test-key": "test-value", - "test-key1": "test-value1", - "test-key2": "test-value2", - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-resources.hcl", - &api.Job{ - ID: stringToPtr("sidecar_task_resources"), - Name: stringToPtr("sidecar_task_resources"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - SidecarTask: &api.SidecarTask{ - Resources: &api.Resources{ - CPU: intToPtr(111), - MemoryMB: intToPtr(222), - MemoryMaxMB: intToPtr(333), - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-proxy.hcl", - &api.Job{ - ID: stringToPtr("service-connect-proxy"), - Name: stringToPtr("service-connect-proxy"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - Native: false, - SidecarService: &api.ConsulSidecarService{ - Proxy: &api.ConsulProxy{ - LocalServiceAddress: "10.0.1.2", - LocalServicePort: 8080, - Expose: &api.ConsulExposeConfig{ - Paths: []*api.ConsulExposePath{{ - Path: "/metrics", - Protocol: "http", - LocalPathPort: 9001, - ListenerPort: "metrics", - }, { - Path: "/health", - Protocol: "http", - LocalPathPort: 9002, - ListenerPort: "health", - }}, - }, - Upstreams: []*api.ConsulUpstream{{ - DestinationName: "upstream1", - LocalBindPort: 2001, - }, { - DestinationName: "upstream2", - LocalBindPort: 2002, - }}, - TransparentProxy: &api.ConsulTransparentProxy{ - UID: "101", - OutboundPort: 15001, - ExcludeInboundPorts: []string{"www", "9000"}, - ExcludeOutboundPorts: []uint16{443, 80}, - ExcludeOutboundCIDRs: []string{"10.0.0.0/8"}, - ExcludeUIDs: []string{"10", "1001"}, - NoDNS: true, - }, - Config: map[string]interface{}{ - "foo": "bar", - }, - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-local-service.hcl", - &api.Job{ - ID: stringToPtr("connect-proxy-local-service"), - Name: stringToPtr("connect-proxy-local-service"), - Type: stringToPtr("service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - Native: false, - SidecarService: &api.ConsulSidecarService{ - Proxy: &api.ConsulProxy{ - LocalServiceAddress: "10.0.1.2", - LocalServicePort: 9876, - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-check-expose.hcl", - &api.Job{ - ID: stringToPtr("group_service_proxy_expose"), - Name: stringToPtr("group_service_proxy_expose"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - Connect: &api.ConsulConnect{ - SidecarService: &api.ConsulSidecarService{ - Proxy: &api.ConsulProxy{}, - }, - }, - Checks: []api.ServiceCheck{{ - Name: "example-check1", - Expose: true, - }, { - Name: "example-check2", - Expose: false, - }}, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-native.hcl", - &api.Job{ - ID: stringToPtr("connect_native_service"), - Name: stringToPtr("connect_native_service"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - TaskName: "task1", - Connect: &api.ConsulConnect{ - Native: true, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-enable-tag-override.hcl", - &api.Job{ - ID: stringToPtr("group_service_eto"), - Name: stringToPtr("group_service_eto"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "example", - EnableTagOverride: true, - }}, - }}, - }, - false, - }, - { - "tg-scaling-policy.hcl", - &api.Job{ - ID: stringToPtr("elastic"), - Name: stringToPtr("elastic"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Scaling: &api.ScalingPolicy{ - Type: "horizontal", - Min: int64ToPtr(5), - Max: int64ToPtr(100), - Policy: map[string]interface{}{ - "foo": "bar", - "b": true, - "val": 5, - "f": .1, - - "check": []map[string]interface{}{ - {"foo": []map[string]interface{}{ - {"query": "some_query"}, - }}, - }, - }, - Enabled: boolToPtr(false), - }, - }, - }, - }, - false, - }, - { - "task-scaling-policy.hcl", - &api.Job{ - ID: stringToPtr("foo"), - Name: stringToPtr("foo"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("bar"), - Tasks: []*api.Task{ - { - Name: "bar", - Driver: "docker", - ScalingPolicies: []*api.ScalingPolicy{ - { - Type: "vertical_cpu", - Target: nil, - Min: int64ToPtr(50), - Max: int64ToPtr(1000), - Policy: map[string]interface{}{ - "test": "cpu", - }, - Enabled: boolToPtr(true), - }, - { - Type: "vertical_mem", - Target: nil, - Min: int64ToPtr(128), - Max: int64ToPtr(1024), - Policy: map[string]interface{}{ - "test": "mem", - }, - Enabled: boolToPtr(false), - }, - }, - }, - }, - }, - }, - }, - false, - }, - { - "tg-service-connect-gateway-ingress.hcl", - &api.Job{ - ID: stringToPtr("connect_gateway_ingress"), - Name: stringToPtr("connect_gateway_ingress"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "ingress-gateway-service", - Connect: &api.ConsulConnect{ - Gateway: &api.ConsulGateway{ - Proxy: &api.ConsulGatewayProxy{ - ConnectTimeout: timeToPtr(3 * time.Second), - EnvoyGatewayBindTaggedAddresses: true, - EnvoyGatewayBindAddresses: map[string]*api.ConsulGatewayBindAddress{ - "listener1": {Name: "listener1", Address: "10.0.0.1", Port: 8888}, - "listener2": {Name: "listener2", Address: "10.0.0.2", Port: 8889}, - }, - EnvoyGatewayNoDefaultBind: true, - Config: map[string]interface{}{"foo": "bar"}, - }, - Ingress: &api.ConsulIngressConfigEntry{ - TLS: &api.ConsulGatewayTLSConfig{ - Enabled: true, - TLSMinVersion: "TLSv1_2", - CipherSuites: []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, - }, - Listeners: []*api.ConsulIngressListener{{ - Port: 8001, - Protocol: "tcp", - Services: []*api.ConsulIngressService{{ - Name: "service1", - Hosts: []string{ - "127.0.0.1:8001", - "[::1]:8001", - }}, { - Name: "service2", - Hosts: []string{ - "10.0.0.1:8001", - }}, - }}, { - Port: 8080, - Protocol: "http", - Services: []*api.ConsulIngressService{{ - Name: "nginx", - Hosts: []string{ - "2.2.2.2:8080", - }, - TLS: &api.ConsulGatewayTLSConfig{ - SDS: &api.ConsulGatewayTLSSDSConfig{ - ClusterName: "foo", - CertResource: "bar", - }, - }, - RequestHeaders: &api.ConsulHTTPHeaderModifiers{ - Add: map[string]string{ - "test": "testvalue", - }, - }, - ResponseHeaders: &api.ConsulHTTPHeaderModifiers{ - Remove: []string{"test2"}, - }, - MaxConnections: pointer.Of(uint32(5120)), - MaxPendingRequests: pointer.Of(uint32(512)), - MaxConcurrentRequests: pointer.Of(uint32(2048)), - }}, - }, - }, - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-gateway-terminating.hcl", - &api.Job{ - ID: stringToPtr("connect_gateway_terminating"), - Name: stringToPtr("connect_gateway_terminating"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "terminating-gateway-service", - Connect: &api.ConsulConnect{ - Gateway: &api.ConsulGateway{ - Proxy: &api.ConsulGatewayProxy{ - ConnectTimeout: timeToPtr(3 * time.Second), - EnvoyGatewayBindTaggedAddresses: true, - EnvoyGatewayBindAddresses: map[string]*api.ConsulGatewayBindAddress{ - "listener1": {Name: "listener1", Address: "10.0.0.1", Port: 8888}, - "listener2": {Name: "listener2", Address: "10.0.0.2", Port: 8889}, - }, - EnvoyGatewayNoDefaultBind: true, - EnvoyDNSDiscoveryType: "LOGICAL_DNS", - Config: map[string]interface{}{"foo": "bar"}, - }, - Terminating: &api.ConsulTerminatingConfigEntry{ - Services: []*api.ConsulLinkedService{{ - Name: "service1", - CAFile: "ca.pem", - CertFile: "cert.pem", - KeyFile: "key.pem", - }, { - Name: "service2", - SNI: "myhost", - }}, - }, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-service-connect-gateway-mesh.hcl", - &api.Job{ - ID: stringToPtr("connect_gateway_mesh"), - Name: stringToPtr("connect_gateway_mesh"), - TaskGroups: []*api.TaskGroup{{ - Name: stringToPtr("group"), - Services: []*api.Service{{ - Name: "mesh-gateway-service", - Connect: &api.ConsulConnect{ - Gateway: &api.ConsulGateway{ - Proxy: &api.ConsulGatewayProxy{ - Config: map[string]interface{}{"foo": "bar"}, - }, - Mesh: &api.ConsulMeshConfigEntry{}, - }, - }, - }}, - }}, - }, - false, - }, - { - "tg-scaling-policy-minimal.hcl", - &api.Job{ - ID: stringToPtr("elastic"), - Name: stringToPtr("elastic"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Scaling: &api.ScalingPolicy{ - Type: "horizontal", - Min: nil, - Max: int64ToPtr(10), - Policy: nil, - Enabled: nil, - }, - }, - }, - }, - false, - }, - { - "tg-scaling-policy-missing-max.hcl", - nil, - true, - }, - { - "tg-scaling-policy-multi-policy.hcl", - nil, - true, - }, - { - "tg-scaling-policy-with-label.hcl", - nil, - true, - }, - { - "tg-scaling-policy-invalid-type.hcl", - nil, - true, - }, - { - "task-scaling-policy-missing-name.hcl", - nil, - true, - }, - { - "task-scaling-policy-multi-name.hcl", - nil, - true, - }, - { - "task-scaling-policy-multi-cpu.hcl", - nil, - true, - }, - { - "task-scaling-policy-invalid-type.hcl", - nil, - true, - }, - { - "task-scaling-policy-invalid-resource.hcl", - nil, - true, - }, - { - "consul-namespace.hcl", - &api.Job{ - ID: stringToPtr("consul-namespace"), - Name: stringToPtr("consul-namespace"), - TaskGroups: []*api.TaskGroup{ - { - Name: stringToPtr("group"), - Consul: &api.Consul{ - Namespace: "foo", - }, - }, - }, - }, - false, - }, - { - "multiregion.hcl", - &api.Job{ - ID: stringToPtr("multiregion_job"), - Name: stringToPtr("multiregion_job"), - Multiregion: &api.Multiregion{ - Strategy: &api.MultiregionStrategy{ - MaxParallel: intToPtr(1), - OnFailure: stringToPtr("fail_all"), - }, - Regions: []*api.MultiregionRegion{ - { - Name: "west", - Count: intToPtr(2), - Datacenters: []string{"west-1"}, - Meta: map[string]string{"region_code": "W"}, - }, - { - Name: "east", - Count: intToPtr(1), - Datacenters: []string{"east-1", "east-2"}, - Meta: map[string]string{"region_code": "E"}, - }, - }, - }, - }, - false, - }, - { - "resources-cores.hcl", - &api.Job{ - ID: stringToPtr("cores-test"), - Name: stringToPtr("cores-test"), - TaskGroups: []*api.TaskGroup{ - { - Count: intToPtr(5), - Name: stringToPtr("group"), - Tasks: []*api.Task{ - { - Name: "task", - Driver: "docker", - Resources: &api.Resources{ - Cores: intToPtr(4), - MemoryMB: intToPtr(128), - }, - }, - }, - }, - }, - }, - false, - }, - { - "service-provider.hcl", - &api.Job{ - ID: stringToPtr("service-provider"), - Name: stringToPtr("service-provider"), - TaskGroups: []*api.TaskGroup{ - { - Count: intToPtr(5), - Name: stringToPtr("group"), - Tasks: []*api.Task{ - { - Name: "task", - Driver: "docker", - Services: []*api.Service{ - { - Name: "service-provider", - Provider: "nomad", - }, - }, - }, - }, - }, - }, - }, - false, - }, - } - - for _, tc := range cases { - t.Run(tc.File, func(t *testing.T) { - t.Logf("Testing parse: %s", tc.File) - - path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File)) - must.NoError(t, err) - - actual, err := ParseFile(path) - if tc.Err { - must.Error(t, err) - } else { - must.NoError(t, err) - must.Eq(t, tc.Result, actual) - } - }) - } -} - -func TestBadPorts(t *testing.T) { - ci.Parallel(t) - - path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl")) - if err != nil { - t.Fatalf("Can't get absolute path for file: %s", err) - } - - _, err = ParseFile(path) - - if !strings.Contains(err.Error(), errPortLabel.Error()) { - t.Fatalf("\nExpected error\n %s\ngot\n %v", errPortLabel, err) - } -} - -func TestOverlappingPorts(t *testing.T) { - ci.Parallel(t) - - path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl")) - if err != nil { - t.Fatalf("Can't get absolute path for file: %s", err) - } - - _, err = ParseFile(path) - - if err == nil { - t.Fatalf("Expected an error") - } - - if !strings.Contains(err.Error(), "found a port label collision") { - t.Fatalf("Expected collision error; got %v", err) - } -} - -func TestIncorrectKey(t *testing.T) { - ci.Parallel(t) - - path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl")) - if err != nil { - t.Fatalf("Can't get absolute path for file: %s", err) - } - - _, err = ParseFile(path) - - if err == nil { - t.Fatalf("Expected an error") - } - - if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service (0): 'foo', check -> invalid key: nterval") { - t.Fatalf("Expected key error; got %v", err) - } -} - -// TestPortParsing validates that the removal of the mapstructure tags on the -// Port struct don't cause issues with HCL 1 parsing. -// -// TODO: in the future, see if we need `mapstructure` tags on any of the API -func TestPortParsing(t *testing.T) { - ci.Parallel(t) - - var err error - var path string - var job *api.Job - - path, err = filepath.Abs(filepath.Join("./test-fixtures", "parse-ports.hcl")) - must.NoError(t, err, must.Sprint("Can't get absolute path for file: parse-ports.hcl")) - - job, err = ParseFile(path) - must.NoError(t, err) - must.NotNil(t, job) - must.Len(t, 1, job.TaskGroups) - must.Len(t, 1, job.TaskGroups[0].Networks) - must.Len(t, 1, job.TaskGroups[0].Networks[0].ReservedPorts) - must.Len(t, 1, job.TaskGroups[0].Networks[0].DynamicPorts) - must.Eq(t, 9000, job.TaskGroups[0].Networks[0].ReservedPorts[0].Value) - must.Eq(t, 0, job.TaskGroups[0].Networks[0].DynamicPorts[0].Value) -} diff --git a/jobspec/test-fixtures/artifacts.hcl b/jobspec/test-fixtures/artifacts.hcl deleted file mode 100644 index 191e1fc37..000000000 --- a/jobspec/test-fixtures/artifacts.hcl +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - group "binsl" { - task "binstore" { - driver = "docker" - - artifact { - source = "http://foo.com/bar" - destination = "" - - options { - foo = "bar" - } - } - - artifact { - source = "http://foo.com/baz" - } - - artifact { - source = "http://foo.com/bam" - destination = "var/foo" - } - - artifact { - source = "https://example.com/file.txt" - - headers { - User-Agent = "nomad" - X-Nomad-Alloc = "alloc" - } - } - } - } -} diff --git a/jobspec/test-fixtures/bad-artifact.hcl b/jobspec/test-fixtures/bad-artifact.hcl deleted file mode 100644 index f62520120..000000000 --- a/jobspec/test-fixtures/bad-artifact.hcl +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - group "binsl" { - count = 5 - - task "binstore" { - driver = "docker" - - artifact { - bad = "bad" - } - - resources {} - } - } -} diff --git a/jobspec/test-fixtures/bad-ports.hcl b/jobspec/test-fixtures/bad-ports.hcl deleted file mode 100644 index 9dfe1e30a..000000000 --- a/jobspec/test-fixtures/bad-ports.hcl +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - region = "global" - type = "service" - priority = 50 - all_at_once = true - datacenters = ["us2", "eu1"] - consul_token = "abc" - vault_token = "foo" - - meta { - foo = "bar" - } - - constraint { - attribute = "kernel.os" - value = "windows" - } - - update { - stagger = "60s" - max_parallel = 2 - } - - task "outside" { - driver = "java" - - config { - jar_path = "s3://my-cool-store/foo.jar" - } - - meta { - my-cool-key = "foobar" - } - } - - group "binsl" { - count = 5 - - task "binstore" { - driver = "docker" - - config { - image = "hashicorp/binstore" - } - - resources { - cpu = 500 - memory = 128 - - network { - mbits = "100" - - port "one" { - static = 1 - } - - port "two" { - static = 2 - } - - port "three" { - static = 3 - } - - port "this_is_aport" {} - - port "" {} - } - } - } - - task "storagelocker" { - driver = "docker" - - config { - image = "hashicorp/storagelocker" - } - - resources { - cpu = 500 - memory = 128 - } - - constraint { - attribute = "kernel.arch" - value = "amd64" - } - } - - constraint { - attribute = "kernel.os" - value = "linux" - } - - meta { - elb_mode = "tcp" - elb_interval = 10 - elb_checks = 3 - } - } -} diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl deleted file mode 100644 index ed60d8799..000000000 --- a/jobspec/test-fixtures/basic.hcl +++ /dev/null @@ -1,391 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - region = "fooregion" - namespace = "foonamespace" - node_pool = "dev" - type = "batch" - priority = 52 - all_at_once = true - datacenters = ["us2", "eu1"] - consul_token = "abc" - vault_token = "foo" - - meta { - foo = "bar" - } - - constraint { - attribute = "kernel.os" - value = "windows" - } - - constraint { - attribute = "${attr.vault.version}" - value = ">= 0.6.1" - operator = "semver" - } - - affinity { - attribute = "${meta.team}" - value = "mobile" - operator = "=" - weight = 50 - } - - spread { - attribute = "${meta.rack}" - weight = 100 - - target "r1" { - percent = 40 - } - - target "r2" { - percent = 60 - } - } - - update { - stagger = "60s" - max_parallel = 2 - health_check = "manual" - min_healthy_time = "10s" - healthy_deadline = "10m" - progress_deadline = "10m" - auto_revert = true - auto_promote = true - canary = 1 - } - - task "outside" { - driver = "java" - - config { - jar_path = "s3://my-cool-store/foo.jar" - } - - meta { - my-cool-key = "foobar" - } - } - - group "binsl" { - count = 5 - - volume "foo" { - type = "host" - source = "/path" - } - - volume "bar" { - type = "csi" - source = "bar-vol" - read_only = true - attachment_mode = "file-system" - access_mode = "single-mode-writer" - - mount_options { - fs_type = "ext4" - } - } - - volume "baz" { - type = "csi" - source = "bar-vol" - - mount_options { - mount_flags = ["ro"] - } - - per_alloc = true - } - - restart { - attempts = 5 - interval = "10m" - delay = "15s" - mode = "delay" - render_templates = false - } - - reschedule { - attempts = 5 - interval = "12h" - } - - ephemeral_disk { - sticky = true - size = 150 - } - - update { - max_parallel = 3 - health_check = "checks" - min_healthy_time = "1s" - healthy_deadline = "1m" - progress_deadline = "1m" - auto_revert = false - auto_promote = false - canary = 2 - } - - migrate { - max_parallel = 2 - health_check = "task_states" - min_healthy_time = "11s" - healthy_deadline = "11m" - } - - affinity { - attribute = "${node.datacenter}" - value = "dc2" - operator = "=" - weight = 100 - } - - spread { - attribute = "${node.datacenter}" - weight = 50 - - target "dc1" { - percent = 50 - } - - target "dc2" { - percent = 25 - } - - target "dc3" { - percent = 25 - } - } - - stop_after_client_disconnect = "120s" - max_client_disconnect = "120h" - - disconnect { - lost_after = "120h" - stop_on_client_after = "120s" - replace = true - reconcile = "best_score" - } - - task "binstore" { - driver = "docker" - user = "bob" - leader = true - kind = "connect-proxy:test" - - affinity { - attribute = "${meta.foo}" - value = "a,b,c" - operator = "set_contains" - weight = 25 - } - - config { - image = "hashicorp/binstore" - - labels { - FOO = "bar" - } - } - - volume_mount { - volume = "foo" - destination = "/mnt/foo" - } - - restart { - attempts = 10 - } - - logs { - disabled = false - max_files = 14 - max_file_size = 101 - } - - env { - HELLO = "world" - LOREM = "ipsum" - } - - service { - meta { - abc = "123" - } - - - canary_meta { - canary = "boom" - } - - tags = ["foo", "bar"] - canary_tags = ["canary", "bam"] - port = "http" - - check { - name = "check-name" - type = "tcp" - interval = "10s" - timeout = "2s" - port = "admin" - grpc_service = "foo.Bar" - grpc_use_tls = true - - check_restart { - limit = 3 - grace = "10s" - ignore_warnings = true - } - } - } - - resources { - cpu = 500 - memory = 128 - memory_max = 256 - - network { - mbits = "100" - - port "one" { - static = 1 - } - - port "two" { - static = 2 - } - - port "three" { - static = 3 - } - - port "http" {} - - port "https" {} - - port "admin" {} - } - - device "nvidia/gpu" { - count = 10 - - constraint { - attribute = "${device.attr.memory}" - value = "2GB" - operator = ">" - } - - affinity { - attribute = "${device.model}" - value = "1080ti" - weight = 50 - } - } - - device "intel/gpu" {} - } - - kill_timeout = "22s" - - shutdown_delay = "11s" - - artifact { - source = "http://foo.com/artifact" - - options { - checksum = "md5:b8a4f3f72ecab0510a6a31e997461c5f" - } - } - - artifact { - source = "http://bar.com/artifact" - destination = "test/foo/" - mode = "file" - - options { - checksum = "md5:ff1cc0d3432dad54d607c1505fb7245c" - } - } - - vault { - namespace = "ns1" - policies = ["foo", "bar"] - } - - template { - source = "foo" - destination = "foo" - change_mode = "foo" - change_signal = "foo" - splay = "10s" - env = true - vault_grace = "33s" - error_on_missing_key = true - } - - template { - source = "bar" - destination = "bar" - change_mode = "script" - change_script { - command = "/bin/foo" - args = ["-debug", "-verbose"] - timeout = "5s" - fail_on_error = false - } - perms = "777" - uid = 1001 - gid = 20 - left_delimiter = "--" - right_delimiter = "__" - } - } - - task "storagelocker" { - driver = "docker" - - lifecycle { - hook = "prestart" - sidecar = true - } - - config { - image = "hashicorp/storagelocker" - } - - resources { - cpu = 500 - memory = 128 - } - - constraint { - attribute = "kernel.arch" - value = "amd64" - } - - vault { - policies = ["foo", "bar"] - env = false - disable_file = false - change_mode = "signal" - change_signal = "SIGUSR1" - } - } - - constraint { - attribute = "kernel.os" - value = "linux" - } - - meta { - elb_mode = "tcp" - elb_interval = 10 - elb_checks = 3 - } - } -} diff --git a/jobspec/test-fixtures/basic_wrong_key.hcl b/jobspec/test-fixtures/basic_wrong_key.hcl deleted file mode 100644 index 6150cfde2..000000000 --- a/jobspec/test-fixtures/basic_wrong_key.hcl +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - region = "global" - type = "service" - priority = 50 - all_at_once = true - datacenters = ["us2", "eu1"] - consul_token = "abc" - vault_token = "foo" - - meta { - foo = "bar" - } - - constraint { - attribute = "kernel.os" - value = "windows" - } - - update { - stagger = "60s" - max_parallel = 2 - } - - task "outside" { - driver = "java" - - config { - jar_path = "s3://my-cool-store/foo.jar" - } - - meta { - my-cool-key = "foobar" - } - } - - group "binsl" { - count = 5 - - restart { - attempts = 5 - interval = "10m" - delay = "15s" - mode = "delay" - } - - task "binstore" { - driver = "docker" - - config { - image = "hashicorp/binstore" - } - - logs { - max_files = 10 - max_file_size = 100 - } - - env { - HELLO = "world" - LOREM = "ipsum" - } - - service { - name = "foo" - tags = ["foo", "bar"] - port = "http" - - check { - name = "check-name" - type = "tcp" - nterval = "10s" - timeout = "2s" - } - } - - resources { - cpu = 500 - memory = 128 - - network { - mbits = "100" - - port "one" { - static = 1 - } - - port "two" { - static = 2 - } - - port "three" { - static = 3 - } - - port "http" {} - - port "https" {} - - port "admin" {} - } - } - - kill_timeout = "22s" - } - - task "storagelocker" { - driver = "docker" - - config { - image = "hashicorp/storagelocker" - } - - resources { - cpu = 500 - memory = 128 - } - - constraint { - attribute = "kernel.arch" - value = "amd64" - } - } - - constraint { - attribute = "kernel.os" - value = "linux" - } - - meta { - elb_mode = "tcp" - elb_interval = 10 - elb_checks = 3 - } - } -} diff --git a/jobspec/test-fixtures/consul-namespace.hcl b/jobspec/test-fixtures/consul-namespace.hcl deleted file mode 100644 index 4d8a166bd..000000000 --- a/jobspec/test-fixtures/consul-namespace.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "consul-namespace" { - group "group" { - consul { - namespace = "foo" - } - } -} diff --git a/jobspec/test-fixtures/csi-plugin.hcl b/jobspec/test-fixtures/csi-plugin.hcl deleted file mode 100644 index 9daf3d096..000000000 --- a/jobspec/test-fixtures/csi-plugin.hcl +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - group "binsl" { - task "binstore" { - driver = "docker" - - csi_plugin { - id = "org.hashicorp.csi" - type = "monolith" - mount_dir = "/csi/test" - health_timeout = "1m" - } - } - } -} diff --git a/jobspec/test-fixtures/default-job.hcl b/jobspec/test-fixtures/default-job.hcl deleted file mode 100644 index 379f4c34c..000000000 --- a/jobspec/test-fixtures/default-job.hcl +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" {} diff --git a/jobspec/test-fixtures/distinctHosts-constraint.hcl b/jobspec/test-fixtures/distinctHosts-constraint.hcl deleted file mode 100644 index 69cc051cc..000000000 --- a/jobspec/test-fixtures/distinctHosts-constraint.hcl +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - constraint { - distinct_hosts = "true" - } -} diff --git a/jobspec/test-fixtures/distinctProperty-constraint.hcl b/jobspec/test-fixtures/distinctProperty-constraint.hcl deleted file mode 100644 index e7851e1e2..000000000 --- a/jobspec/test-fixtures/distinctProperty-constraint.hcl +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - constraint { - distinct_property = "${meta.rack}" - } -} diff --git a/jobspec/test-fixtures/incorrect-service-def.hcl b/jobspec/test-fixtures/incorrect-service-def.hcl deleted file mode 100644 index 3c8177ad6..000000000 --- a/jobspec/test-fixtures/incorrect-service-def.hcl +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - region = "global" - type = "service" - priority = 50 - all_at_once = true - datacenters = ["us2", "eu1"] - consul_token = "abc" - vault_token = "foo" - - meta { - foo = "bar" - } - - constraint { - attribute = "kernel.os" - value = "windows" - } - - update { - stagger = "60s" - max_parallel = 2 - } - - task "outside" { - driver = "java" - - config { - jar_path = "s3://my-cool-store/foo.jar" - } - - meta { - my-cool-key = "foobar" - } - } - - group "binsl" { - count = 5 - - restart { - attempts = 5 - interval = "10m" - delay = "15s" - } - - task "binstore" { - driver = "docker" - - config { - image = "hashicorp/binstore" - } - - env { - HELLO = "world" - LOREM = "ipsum" - } - - service { - tags = ["foo", "bar"] - port = "http" - - check { - name = "check-name" - type = "http" - interval = "10s" - timeout = "2s" - } - } - - service { - port = "one" - } - - resources { - cpu = 500 - memory = 128 - - network { - mbits = "100" - - port "one" { - static = 1 - } - - port "three" { - static = 3 - } - - port "http" {} - } - } - } - } -} diff --git a/jobspec/test-fixtures/job-with-kill-signal.hcl b/jobspec/test-fixtures/job-with-kill-signal.hcl deleted file mode 100644 index 2e35dd3ab..000000000 --- a/jobspec/test-fixtures/job-with-kill-signal.hcl +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - kill_signal = "SIGQUIT" - - config { - image = "hashicorp/image" - } - } -} diff --git a/jobspec/test-fixtures/migrate-job.hcl b/jobspec/test-fixtures/migrate-job.hcl deleted file mode 100644 index dd423e2bd..000000000 --- a/jobspec/test-fixtures/migrate-job.hcl +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - datacenters = ["dc1"] - type = "batch" - - migrate { - max_parallel = 2 - health_check = "task_states" - min_healthy_time = "11s" - healthy_deadline = "11m" - } - - group "bar" { - count = 3 - - task "bar" { - driver = "raw_exec" - - config { - command = "bash" - args = ["-c", "echo hi"] - } - } - - migrate { - max_parallel = 3 - health_check = "checks" - min_healthy_time = "1s" - healthy_deadline = "1m" - } - } -} diff --git a/jobspec/test-fixtures/multi-network.hcl b/jobspec/test-fixtures/multi-network.hcl deleted file mode 100644 index 7c8dca7a9..000000000 --- a/jobspec/test-fixtures/multi-network.hcl +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - group "binsl" { - count = 5 - - task "binstore" { - driver = "docker" - - resources { - cpu = 500 - memory = 128 - - network { - mbits = "100" - reserved_ports = [1, 2, 3] - dynamic_ports = ["HTTP", "HTTPS", "ADMIN"] - } - - network { - mbits = "128" - reserved_ports = [1, 2, 3] - dynamic_ports = ["HTTP", "HTTPS", "ADMIN"] - } - } - } - } -} diff --git a/jobspec/test-fixtures/multi-resource.hcl b/jobspec/test-fixtures/multi-resource.hcl deleted file mode 100644 index 7938c6b22..000000000 --- a/jobspec/test-fixtures/multi-resource.hcl +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - group "binsl" { - ephemeral_disk { - size = 500 - } - - ephemeral_disk { - size = 100 - } - - count = 5 - - task "binstore" { - driver = "docker" - - resources { - cpu = 500 - memory = 128 - } - - resources { - cpu = 500 - memory = 128 - } - } - } -} diff --git a/jobspec/test-fixtures/multi-vault.hcl b/jobspec/test-fixtures/multi-vault.hcl deleted file mode 100644 index 18d0842c1..000000000 --- a/jobspec/test-fixtures/multi-vault.hcl +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - datacenters = ["us2", "eu1"] - - group "binsl" { - task "binstore" { - driver = "docker" - user = "bob" - - config { - image = "hashicorp/binstore" - } - - logs { - max_files = 10 - max_file_size = 100 - } - - vault { - policies = ["foo", "bar"] - } - - vault { - policies = ["1", "2"] - } - } - } -} diff --git a/jobspec/test-fixtures/multiregion.hcl b/jobspec/test-fixtures/multiregion.hcl deleted file mode 100644 index a610cb8d0..000000000 --- a/jobspec/test-fixtures/multiregion.hcl +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "multiregion_job" { - - multiregion { - - strategy { - max_parallel = 1 - on_failure = "fail_all" - } - - region "west" { - count = 2 - datacenters = ["west-1"] - meta { - region_code = "W" - } - } - - region "east" { - count = 1 - datacenters = ["east-1", "east-2"] - meta { - region_code = "E" - } - } - } -} diff --git a/jobspec/test-fixtures/overlapping-ports.hcl b/jobspec/test-fixtures/overlapping-ports.hcl deleted file mode 100644 index 192bbda41..000000000 --- a/jobspec/test-fixtures/overlapping-ports.hcl +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "binstore-storagelocker" { - region = "global" - type = "service" - priority = 50 - all_at_once = true - datacenters = ["us2", "eu1"] - consul_token = "abc" - vault_token = "foo" - - meta { - foo = "bar" - } - - constraint { - attribute = "kernel.os" - value = "windows" - } - - update { - stagger = "60s" - max_parallel = 2 - } - - task "outside" { - driver = "java" - - config { - jar_path = "s3://my-cool-store/foo.jar" - } - - meta { - my-cool-key = "foobar" - } - } - - group "binsl" { - count = 5 - - task "binstore" { - driver = "docker" - - config { - image = "hashicorp/binstore" - } - - resources { - cpu = 500 - memory = 128 - - network { - mbits = "100" - - port "one" { - static = 1 - } - - port "two" { - static = 2 - } - - port "three" { - static = 3 - } - - port "Http" {} - - port "http" {} - - port "HTTP" {} - } - } - } - - task "storagelocker" { - driver = "docker" - - config { - image = "hashicorp/storagelocker" - } - - resources { - cpu = 500 - memory = 128 - } - - constraint { - attribute = "kernel.arch" - value = "amd64" - } - } - - constraint { - attribute = "kernel.os" - value = "linux" - } - - meta { - elb_mode = "tcp" - elb_interval = 10 - elb_checks = 3 - } - } -} diff --git a/jobspec/test-fixtures/parameterized_job.hcl b/jobspec/test-fixtures/parameterized_job.hcl deleted file mode 100644 index 1a5b911ea..000000000 --- a/jobspec/test-fixtures/parameterized_job.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "parameterized_job" { - parameterized { - payload = "required" - meta_required = ["foo", "bar"] - meta_optional = ["baz", "bam"] - } - - group "foo" { - task "bar" { - driver = "docker" - - dispatch_payload { - file = "foo/bar" - } - } - } -} diff --git a/jobspec/test-fixtures/parse-ports.hcl b/jobspec/test-fixtures/parse-ports.hcl deleted file mode 100644 index 3879c27e8..000000000 --- a/jobspec/test-fixtures/parse-ports.hcl +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "parse-ports" { - group "group" { - network { - port "static" { - static = 9000 - } - - port "dynamic" {} - } - } -} diff --git a/jobspec/test-fixtures/periodic-cron.hcl b/jobspec/test-fixtures/periodic-cron.hcl deleted file mode 100644 index af789e238..000000000 --- a/jobspec/test-fixtures/periodic-cron.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - periodic { - cron = "*/5 * * *" - prohibit_overlap = true - time_zone = "Europe/Minsk" - } -} diff --git a/jobspec/test-fixtures/periodic-crons.hcl b/jobspec/test-fixtures/periodic-crons.hcl deleted file mode 100644 index 3baa1b4a1..000000000 --- a/jobspec/test-fixtures/periodic-crons.hcl +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - periodic { - crons = [ - "*/5 * * *", - "*/7 * * *" - ] - prohibit_overlap = true - time_zone = "Europe/Minsk" - } -} diff --git a/jobspec/test-fixtures/regexp-constraint.hcl b/jobspec/test-fixtures/regexp-constraint.hcl deleted file mode 100644 index 8f76db365..000000000 --- a/jobspec/test-fixtures/regexp-constraint.hcl +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - constraint { - attribute = "$attr.kernel.version" - regexp = "[0-9.]+" - } -} diff --git a/jobspec/test-fixtures/reschedule-job-unlimited.hcl b/jobspec/test-fixtures/reschedule-job-unlimited.hcl deleted file mode 100644 index eaaf736fc..000000000 --- a/jobspec/test-fixtures/reschedule-job-unlimited.hcl +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - datacenters = ["dc1"] - type = "batch" - - reschedule { - delay = "10s" - delay_function = "exponential" - max_delay = "120s" - unlimited = true - } - - group "bar" { - count = 3 - - task "bar" { - driver = "raw_exec" - - config { - command = "bash" - args = ["-c", "echo hi"] - } - } - } -} diff --git a/jobspec/test-fixtures/reschedule-job.hcl b/jobspec/test-fixtures/reschedule-job.hcl deleted file mode 100644 index 5b15f638b..000000000 --- a/jobspec/test-fixtures/reschedule-job.hcl +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - datacenters = ["dc1"] - type = "batch" - - reschedule { - attempts = 15 - interval = "30m" - delay = "10s" - delay_function = "constant" - } - - group "bar" { - count = 3 - - task "bar" { - driver = "raw_exec" - - config { - command = "bash" - args = ["-c", "echo hi"] - } - } - } -} diff --git a/jobspec/test-fixtures/resources-cores.hcl b/jobspec/test-fixtures/resources-cores.hcl deleted file mode 100644 index 71bf13b55..000000000 --- a/jobspec/test-fixtures/resources-cores.hcl +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "cores-test" { - group "group" { - count = 5 - - task "task" { - driver = "docker" - - resources { - cores = 4 - memory = 128 - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-bad-header-2.hcl b/jobspec/test-fixtures/service-check-bad-header-2.hcl deleted file mode 100644 index e81a94b5b..000000000 --- a/jobspec/test-fixtures/service-check-bad-header-2.hcl +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "check_bad_header" { - type = "service" - - group "group" { - count = 1 - - task "task" { - service { - tags = ["bar"] - port = "http" - - check { - name = "check-name" - type = "http" - path = "/" - method = "POST" - interval = "10s" - timeout = "2s" - initial_status = "passing" - - header { - Authorization = ["ok", 840] - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-bad-header.hcl b/jobspec/test-fixtures/service-check-bad-header.hcl deleted file mode 100644 index 3e97a2674..000000000 --- a/jobspec/test-fixtures/service-check-bad-header.hcl +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "check_bad_header" { - type = "service" - - group "group" { - count = 1 - - task "task" { - service { - tags = ["bar"] - port = "http" - - check { - name = "check-name" - type = "http" - path = "/" - method = "POST" - interval = "10s" - timeout = "2s" - initial_status = "passing" - - header { - Authorization = "Should be a []string!" - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-driver-address.hcl b/jobspec/test-fixtures/service-check-driver-address.hcl deleted file mode 100644 index 877af770b..000000000 --- a/jobspec/test-fixtures/service-check-driver-address.hcl +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "address_mode_driver" { - type = "service" - - group "group" { - task "task" { - service { - name = "http-service" - port = "http" - - address_mode = "auto" - - check { - name = "http-check" - type = "http" - path = "/" - port = "http" - - address_mode = "driver" - } - } - - service { - name = "random-service" - port = "9000" - - address_mode = "driver" - - check { - name = "random-check" - type = "tcp" - port = "9001" - - address_mode = "driver" - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-initial-status.hcl b/jobspec/test-fixtures/service-check-initial-status.hcl deleted file mode 100644 index 07f02c422..000000000 --- a/jobspec/test-fixtures/service-check-initial-status.hcl +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "check_initial_status" { - type = "service" - - group "group" { - count = 1 - - task "task" { - service { - tags = ["foo", "bar"] - port = "http" - - check { - name = "check-name" - type = "http" - path = "/" - method = "POST" - interval = "10s" - timeout = "2s" - initial_status = "passing" - - header { - Authorization = ["Basic ZWxhc3RpYzpjaGFuZ2VtZQ=="] - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-pass-fail.hcl b/jobspec/test-fixtures/service-check-pass-fail.hcl deleted file mode 100644 index 0d8478fb6..000000000 --- a/jobspec/test-fixtures/service-check-pass-fail.hcl +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "check_pass_fail" { - type = "service" - - group "group" { - count = 1 - - task "task" { - service { - name = "service" - port = "http" - - check { - name = "check-name" - type = "http" - path = "/" - method = "POST" - interval = "10s" - timeout = "2s" - initial_status = "passing" - success_before_passing = 3 - failures_before_critical = 4 - failures_before_warning = 2 - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-check-restart.hcl b/jobspec/test-fixtures/service-check-restart.hcl deleted file mode 100644 index 79c088ad7..000000000 --- a/jobspec/test-fixtures/service-check-restart.hcl +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service_check_restart" { - type = "service" - - group "group" { - task "task" { - service { - name = "http-service" - - check_restart { - limit = 3 - grace = "10s" - ignore_warnings = true - } - - check { - name = "random-check" - type = "tcp" - port = "9001" - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-enable-tag-override.hcl b/jobspec/test-fixtures/service-enable-tag-override.hcl deleted file mode 100644 index 7e2492a5d..000000000 --- a/jobspec/test-fixtures/service-enable-tag-override.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service_eto" { - type = "service" - - group "group" { - task "task" { - service { - name = "example" - enable_tag_override = true - } - } - } -} diff --git a/jobspec/test-fixtures/service-meta.hcl b/jobspec/test-fixtures/service-meta.hcl deleted file mode 100644 index 2bcb15b1b..000000000 --- a/jobspec/test-fixtures/service-meta.hcl +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service_meta" { - type = "service" - - group "group" { - task "task" { - service { - name = "http-service" - - meta { - foo = "bar" - } - } - } - } -} diff --git a/jobspec/test-fixtures/service-provider.hcl b/jobspec/test-fixtures/service-provider.hcl deleted file mode 100644 index 81b11c47c..000000000 --- a/jobspec/test-fixtures/service-provider.hcl +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service-provider" { - group "group" { - count = 5 - - task "task" { - driver = "docker" - - service { - name = "service-provider" - provider = "nomad" - } - } - } -} diff --git a/jobspec/test-fixtures/service-tagged-address.hcl b/jobspec/test-fixtures/service-tagged-address.hcl deleted file mode 100644 index 764021562..000000000 --- a/jobspec/test-fixtures/service-tagged-address.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service_tagged_address" { - type = "service" - - group "group" { - service { - name = "service1" - tagged_addresses { - public_wan = "1.2.3.4" - } - } - } -} diff --git a/jobspec/test-fixtures/set-contains-constraint.hcl b/jobspec/test-fixtures/set-contains-constraint.hcl deleted file mode 100644 index ce08bd83b..000000000 --- a/jobspec/test-fixtures/set-contains-constraint.hcl +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - constraint { - attribute = "$meta.data" - set_contains = "foo,bar,baz" - } -} diff --git a/jobspec/test-fixtures/specify-job.hcl b/jobspec/test-fixtures/specify-job.hcl deleted file mode 100644 index 998aa374a..000000000 --- a/jobspec/test-fixtures/specify-job.hcl +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "default" { - id = "job1" - name = "My Job" -} diff --git a/jobspec/test-fixtures/task-nested-config.hcl b/jobspec/test-fixtures/task-nested-config.hcl deleted file mode 100644 index 441a5d5a3..000000000 --- a/jobspec/test-fixtures/task-nested-config.hcl +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - config { - image = "hashicorp/image" - - port_map { - db = 1234 - } - } - } -} diff --git a/jobspec/test-fixtures/task-scaling-policy-invalid-resource.hcl b/jobspec/test-fixtures/task-scaling-policy-invalid-resource.hcl deleted file mode 100644 index 30955d804..000000000 --- a/jobspec/test-fixtures/task-scaling-policy-invalid-resource.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling "wrong" { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - } -} - diff --git a/jobspec/test-fixtures/task-scaling-policy-invalid-type.hcl b/jobspec/test-fixtures/task-scaling-policy-invalid-type.hcl deleted file mode 100644 index 6a8265ae2..000000000 --- a/jobspec/test-fixtures/task-scaling-policy-invalid-type.hcl +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling "cpu" { - type = "vertical_mem" - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - } -} - diff --git a/jobspec/test-fixtures/task-scaling-policy-missing-name.hcl b/jobspec/test-fixtures/task-scaling-policy-missing-name.hcl deleted file mode 100644 index e35494047..000000000 --- a/jobspec/test-fixtures/task-scaling-policy-missing-name.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - } -} - diff --git a/jobspec/test-fixtures/task-scaling-policy-multi-cpu.hcl b/jobspec/test-fixtures/task-scaling-policy-multi-cpu.hcl deleted file mode 100644 index ca5ff4a1b..000000000 --- a/jobspec/test-fixtures/task-scaling-policy-multi-cpu.hcl +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling "cpu" { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - scaling "cpu" { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - } -} - diff --git a/jobspec/test-fixtures/task-scaling-policy-multi-name.hcl b/jobspec/test-fixtures/task-scaling-policy-multi-name.hcl deleted file mode 100644 index bcbb84d58..000000000 --- a/jobspec/test-fixtures/task-scaling-policy-multi-name.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling "cpu" "mem" { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - } -} - diff --git a/jobspec/test-fixtures/task-scaling-policy.hcl b/jobspec/test-fixtures/task-scaling-policy.hcl deleted file mode 100644 index f8f70d4d5..000000000 --- a/jobspec/test-fixtures/task-scaling-policy.hcl +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - task "bar" { - driver = "docker" - - scaling "cpu" { - enabled = true - min = 50 - max = 1000 - - policy { - test = "cpu" - } - } - - scaling "mem" { - enabled = false - min = 128 - max = 1024 - - policy { - test = "mem" - } - } - - } -} - diff --git a/jobspec/test-fixtures/tg-network-with-hostname.hcl b/jobspec/test-fixtures/tg-network-with-hostname.hcl deleted file mode 100644 index 3951dd795..000000000 --- a/jobspec/test-fixtures/tg-network-with-hostname.hcl +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - datacenters = ["dc1"] - - group "bar" { - count = 3 - shutdown_delay = "14s" - - network { - mode = "bridge" - hostname = "foobar" - - port "http" { - static = 80 - to = 8080 - host_network = "public" - } - } - - task "bar" { - driver = "raw_exec" - - config { - command = "bash" - args = ["-c", "echo hi"] - } - - resources { - network { - mbits = 10 - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-network.hcl b/jobspec/test-fixtures/tg-network.hcl deleted file mode 100644 index c3cf36ede..000000000 --- a/jobspec/test-fixtures/tg-network.hcl +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - datacenters = ["dc1"] - - group "bar" { - count = 3 - shutdown_delay = "14s" - - network { - mode = "bridge" - - port "http" { - static = 80 - to = 8080 - host_network = "public" - } - - dns { - servers = ["8.8.8.8"] - options = ["ndots:2", "edns0"] - } - } - - service { - name = "connect-service" - tags = ["foo", "bar"] - canary_tags = ["canary", "bam"] - port = "1234" - - connect { - sidecar_service { - tags = ["side1", "side2"] - - proxy { - local_service_port = 8080 - - upstreams { - destination_name = "other-service" - destination_peer = "10.0.0.1:6379" - destination_partition = "infra" - destination_type = "tcp" - local_bind_port = 4567 - local_bind_address = "0.0.0.0" - local_bind_socket_path = "/var/run/testsocket.sock" - local_bind_socket_mode = "0666" - datacenter = "dc1" - - mesh_gateway { - mode = "local" - } - } - } - } - - sidecar_task { - resources { - cpu = 500 - memory = 1024 - } - - env { - FOO = "abc" - } - - shutdown_delay = "5s" - } - } - } - - task "bar" { - driver = "raw_exec" - - config { - command = "bash" - args = ["-c", "echo hi"] - } - - resources { - network { - mbits = 10 - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy-invalid-type.hcl b/jobspec/test-fixtures/tg-scaling-policy-invalid-type.hcl deleted file mode 100644 index b8e0257c3..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy-invalid-type.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling { - type = "vertical_cpu" - enabled = false - min = 5 - max = 100 - - policy { - foo = "bar" - b = true - val = 5 - f = 0.1 - } - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy-minimal.hcl b/jobspec/test-fixtures/tg-scaling-policy-minimal.hcl deleted file mode 100644 index 8e4386709..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy-minimal.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling { - max = 10 - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy-missing-max.hcl b/jobspec/test-fixtures/tg-scaling-policy-missing-max.hcl deleted file mode 100644 index 9760983c5..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy-missing-max.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling { - // required: max = ... - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy-multi-policy.hcl b/jobspec/test-fixtures/tg-scaling-policy-multi-policy.hcl deleted file mode 100644 index 69c1a75e5..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy-multi-policy.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling { - enabled = false - min = 5 - max = 100 - - policy { - foo = "right" - b = true - } - - policy { - foo = "wrong" - c = false - } - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy-with-label.hcl b/jobspec/test-fixtures/tg-scaling-policy-with-label.hcl deleted file mode 100644 index db9a05117..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy-with-label.hcl +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling "no-label-allowed" { - enabled = false - min = 5 - max = 100 - - policy { - foo = "bar" - b = true - val = 5 - f = 0.1 - } - } - } -} diff --git a/jobspec/test-fixtures/tg-scaling-policy.hcl b/jobspec/test-fixtures/tg-scaling-policy.hcl deleted file mode 100644 index ada22c1db..000000000 --- a/jobspec/test-fixtures/tg-scaling-policy.hcl +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "elastic" { - group "group" { - scaling { - enabled = false - min = 5 - max = 100 - - policy { - foo = "bar" - b = true - val = 5 - f = 0.1 - - check "foo" { - query = "some_query" - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-check-expose.hcl b/jobspec/test-fixtures/tg-service-check-expose.hcl deleted file mode 100644 index 3183bb30a..000000000 --- a/jobspec/test-fixtures/tg-service-check-expose.hcl +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "group_service_proxy_expose" { - group "group" { - service { - name = "example" - - connect { - sidecar_service { - proxy {} - } - } - - check { - name = "example-check1" - expose = true - } - - check { - name = "example-check2" - expose = false - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-check.hcl b/jobspec/test-fixtures/tg-service-check.hcl deleted file mode 100644 index 6e183ac3e..000000000 --- a/jobspec/test-fixtures/tg-service-check.hcl +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "group_service_check_script" { - group "group" { - count = 1 - - network { - mode = "bridge" - - port "http" { - static = 80 - to = 8080 - } - } - - service { - name = "foo-service" - port = "http" - on_update = "ignore" - - check { - name = "check-name" - type = "script" - command = "/bin/true" - interval = "10s" - timeout = "2s" - initial_status = "passing" - task = "foo" - on_update = "ignore" - body = "post body" - } - } - - task "foo" {} - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-gateway-ingress.hcl b/jobspec/test-fixtures/tg-service-connect-gateway-ingress.hcl deleted file mode 100644 index b87804e3c..000000000 --- a/jobspec/test-fixtures/tg-service-connect-gateway-ingress.hcl +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "connect_gateway_ingress" { - group "group" { - service { - name = "ingress-gateway-service" - - connect { - gateway { - proxy { - connect_timeout = "3s" - envoy_gateway_bind_tagged_addresses = true - envoy_gateway_bind_addresses "listener1" { - address = "10.0.0.1" - port = 8888 - } - envoy_gateway_bind_addresses "listener2" { - address = "10.0.0.2" - port = 8889 - } - envoy_gateway_no_default_bind = true - config { - foo = "bar" - } - } - ingress { - tls { - enabled = true - tls_min_version = "TLSv1_2" - cipher_suites = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"] - } - - listener { - port = 8001 - protocol = "tcp" - service { - name = "service1" - hosts = ["127.0.0.1:8001", "[::1]:8001"] - } - service { - name = "service2" - hosts = ["10.0.0.1:8001"] - } - } - - listener { - port = 8080 - protocol = "http" - service { - name = "nginx" - hosts = ["2.2.2.2:8080"] - tls { - sds { - cluster_name = "foo" - cert_resource = "bar" - } - } - request_headers { - add { - test = "testvalue" - } - } - response_headers { - remove = ["test2"] - } - max_connections = 5120 - max_pending_requests = 512 - max_concurrent_requests = 2048 - } - } - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-gateway-mesh.hcl b/jobspec/test-fixtures/tg-service-connect-gateway-mesh.hcl deleted file mode 100644 index 81741142f..000000000 --- a/jobspec/test-fixtures/tg-service-connect-gateway-mesh.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "connect_gateway_mesh" { - group "group" { - service { - name = "mesh-gateway-service" - - connect { - gateway { - proxy { - config { - foo = "bar" - } - } - - mesh {} - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-gateway-terminating.hcl b/jobspec/test-fixtures/tg-service-connect-gateway-terminating.hcl deleted file mode 100644 index 671fb9815..000000000 --- a/jobspec/test-fixtures/tg-service-connect-gateway-terminating.hcl +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "connect_gateway_terminating" { - group "group" { - service { - name = "terminating-gateway-service" - - connect { - gateway { - proxy { - connect_timeout = "3s" - envoy_gateway_bind_tagged_addresses = true - - envoy_gateway_bind_addresses "listener1" { - address = "10.0.0.1" - port = 8888 - } - - envoy_gateway_bind_addresses "listener2" { - address = "10.0.0.2" - port = 8889 - } - - envoy_gateway_no_default_bind = true - envoy_dns_discovery_type = "LOGICAL_DNS" - - config { - foo = "bar" - } - } - - terminating { - service { - name = "service1" - ca_file = "ca.pem" - cert_file = "cert.pem" - key_file = "key.pem" - } - - service { - name = "service2" - sni = "myhost" - } - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-local-service.hcl b/jobspec/test-fixtures/tg-service-connect-local-service.hcl deleted file mode 100644 index be9fc402c..000000000 --- a/jobspec/test-fixtures/tg-service-connect-local-service.hcl +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "connect-proxy-local-service" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - sidecar_service { - proxy { - local_service_port = 9876 - local_service_address = "10.0.1.2" - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-native.hcl b/jobspec/test-fixtures/tg-service-connect-native.hcl deleted file mode 100644 index 2eaa157c9..000000000 --- a/jobspec/test-fixtures/tg-service-connect-native.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "connect_native_service" { - group "group" { - service { - name = "example" - task = "task1" - - connect { - native = true - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-proxy.hcl b/jobspec/test-fixtures/tg-service-connect-proxy.hcl deleted file mode 100644 index e09076c52..000000000 --- a/jobspec/test-fixtures/tg-service-connect-proxy.hcl +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "service-connect-proxy" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - sidecar_service { - proxy { - local_service_port = 8080 - local_service_address = "10.0.1.2" - - upstreams { - destination_name = "upstream1" - local_bind_port = 2001 - } - - upstreams { - destination_name = "upstream2" - local_bind_port = 2002 - } - - expose { - path { - path = "/metrics" - protocol = "http" - local_path_port = 9001 - listener_port = "metrics" - } - - path { - path = "/health" - protocol = "http" - local_path_port = 9002 - listener_port = "health" - } - } - - transparent_proxy { - uid = "101" - outbound_port = 15001 - exclude_inbound_ports = ["www", "9000"] - exclude_outbound_ports = [443, 80] - exclude_outbound_cidrs = ["10.0.0.0/8"] - exclude_uids = ["10", "1001"] - no_dns = true - } - - config { - foo = "bar" - } - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-resources.hcl b/jobspec/test-fixtures/tg-service-connect-resources.hcl deleted file mode 100644 index 59306404d..000000000 --- a/jobspec/test-fixtures/tg-service-connect-resources.hcl +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "sidecar_task_resources" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - # should still work without sidecar_service being set (i.e. connect gateway) - sidecar_task { - resources { - cpu = 111 - memory = 222 - memory_max = 333 - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-sidecar_disablecheck.hcl b/jobspec/test-fixtures/tg-service-connect-sidecar_disablecheck.hcl deleted file mode 100644 index 99f4780d6..000000000 --- a/jobspec/test-fixtures/tg-service-connect-sidecar_disablecheck.hcl +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "sidecar_disablecheck" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - sidecar_service { - disable_default_tcp_check = true - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-sidecar_meta.hcl b/jobspec/test-fixtures/tg-service-connect-sidecar_meta.hcl deleted file mode 100644 index bb8164d8d..000000000 --- a/jobspec/test-fixtures/tg-service-connect-sidecar_meta.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "sidecar_meta" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - sidecar_service { - meta { - test-key = "test-value" - test-key1 = "test-value1" - test-key2 = "test-value2" - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-connect-sidecar_task-name.hcl b/jobspec/test-fixtures/tg-service-connect-sidecar_task-name.hcl deleted file mode 100644 index f103536f7..000000000 --- a/jobspec/test-fixtures/tg-service-connect-sidecar_task-name.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "sidecar_task_name" { - type = "service" - - group "group" { - service { - name = "example" - - connect { - sidecar_service {} - - sidecar_task { - name = "my-sidecar" - } - } - } - } -} diff --git a/jobspec/test-fixtures/tg-service-enable-tag-override.hcl b/jobspec/test-fixtures/tg-service-enable-tag-override.hcl deleted file mode 100644 index ddef2f98b..000000000 --- a/jobspec/test-fixtures/tg-service-enable-tag-override.hcl +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "group_service_eto" { - group "group" { - service { - name = "example" - enable_tag_override = true - } - } -} diff --git a/jobspec/test-fixtures/tg-service-proxy-expose.hcl b/jobspec/test-fixtures/tg-service-proxy-expose.hcl deleted file mode 100644 index 05b182aa5..000000000 --- a/jobspec/test-fixtures/tg-service-proxy-expose.hcl +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "group_service_proxy_expose" { - group "group" { - service { - name = "example" - - connect { - sidecar_service { - proxy { - expose { - path { - path = "/health" - protocol = "http" - local_path_port = 2222 - listener_port = "healthcheck" - } - - path { - path = "/metrics" - protocol = "grpc" - local_path_port = 3000 - listener_port = "metrics" - } - } - } - } - } - } - } -} diff --git a/jobspec/test-fixtures/vault_inheritance.hcl b/jobspec/test-fixtures/vault_inheritance.hcl deleted file mode 100644 index 4ee592187..000000000 --- a/jobspec/test-fixtures/vault_inheritance.hcl +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "example" { - vault { - policies = ["job"] - } - - group "cache" { - vault { - policies = ["group"] - } - - task "redis" {} - - task "redis2" { - vault { - policies = ["task"] - env = false - disable_file = true - } - } - } - - group "cache2" { - task "redis" {} - } -} diff --git a/jobspec/test-fixtures/version-constraint.hcl b/jobspec/test-fixtures/version-constraint.hcl deleted file mode 100644 index e26184bd3..000000000 --- a/jobspec/test-fixtures/version-constraint.hcl +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "foo" { - constraint { - attribute = "$attr.kernel.version" - version = "~> 3.2" - } -} diff --git a/jobspec/utils.go b/jobspec/utils.go deleted file mode 100644 index c35cf3b5e..000000000 --- a/jobspec/utils.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -// flattenMapSlice flattens any occurrences of []map[string]interface{} into -// map[string]interface{}. -func flattenMapSlice(m map[string]interface{}) map[string]interface{} { - newM := make(map[string]interface{}, len(m)) - - for k, v := range m { - var newV interface{} - - switch mapV := v.(type) { - case []map[string]interface{}: - // Recurse into each map and flatten values - newMap := map[string]interface{}{} - for _, innerM := range mapV { - for innerK, innerV := range flattenMapSlice(innerM) { - newMap[innerK] = innerV - } - } - newV = newMap - - case map[string]interface{}: - // Recursively flatten maps - newV = flattenMapSlice(mapV) - - default: - newV = v - } - - newM[k] = newV - } - - return newM -} diff --git a/jobspec/utils_test.go b/jobspec/utils_test.go deleted file mode 100644 index bd345c19e..000000000 --- a/jobspec/utils_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package jobspec - -import ( - "testing" - - "github.com/hashicorp/nomad/ci" - "github.com/stretchr/testify/require" -) - -// TestFlattenMapSlice asserts flattenMapSlice recursively flattens a slice of maps into a -// single map. -func TestFlattenMapSlice(t *testing.T) { - ci.Parallel(t) - - input := map[string]interface{}{ - "foo": 123, - "bar": []map[string]interface{}{ - { - "baz": 456, - }, - { - "baz": 789, - }, - { - "baax": true, - }, - }, - "nil": nil, - } - - output := map[string]interface{}{ - "foo": 123, - "bar": map[string]interface{}{ - "baz": 789, - "baax": true, - }, - "nil": nil, - } - - require.Equal(t, output, flattenMapSlice(input)) - -} diff --git a/jobspec2/parse_test.go b/jobspec2/parse_test.go index bfbb01cbf..6fd3072f3 100644 --- a/jobspec2/parse_test.go +++ b/jobspec2/parse_test.go @@ -12,60 +12,10 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/pointer" - "github.com/hashicorp/nomad/jobspec" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" ) -func TestEquivalentToHCL1(t *testing.T) { - ci.Parallel(t) - - hclSpecDir := "../jobspec/test-fixtures/" - fis, err := os.ReadDir(hclSpecDir) - require.NoError(t, err) - - for _, fi := range fis { - name := fi.Name() - - t.Run(name, func(t *testing.T) { - f, err := os.Open(hclSpecDir + name) - require.NoError(t, err) - defer f.Close() - - job1, err := jobspec.Parse(f) - if err != nil { - t.Skip("file is not parsable in v1") - } - - f.Seek(0, 0) - - job2, err := Parse(name, f) - require.NoError(t, err) - - require.Equal(t, job1, job2) - }) - } -} - -func TestEquivalentToHCL1_ComplexConfig(t *testing.T) { - ci.Parallel(t) - - name := "./test-fixtures/config-compatibility.hcl" - f, err := os.Open(name) - require.NoError(t, err) - defer f.Close() - - job1, err := jobspec.Parse(f) - require.NoError(t, err) - - f.Seek(0, 0) - - job2, err := Parse(name, f) - require.NoError(t, err) - - require.Equal(t, job1, job2) -} - func TestParse_ConnectJob(t *testing.T) { ci.Parallel(t) diff --git a/jobspec2/test-fixtures/config-compatibility.hcl b/jobspec2/test-fixtures/config-compatibility.hcl deleted file mode 100644 index 13dade371..000000000 --- a/jobspec2/test-fixtures/config-compatibility.hcl +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -job "example" { - group "group" { - task "task" { - config { - ## arbitrary structures to compare HCLv1 and HCLv2 - ## HCLv2 must parse the same way HCLv1 parser does - - - # primitive attrs - attr_string = "b" - attr_int = 3 - attr_large_int = 21474836470 - attr_float = 3.2 - - # lists attrs - attr_list_string = ["a", "b"] - attr_list_int = [1, 2, 4] - attr_list_float = [1.2, 2.3, 4.2] - attr_list_hetro = [1, "a", 3.4, { "k" = "v" }] - attr_list_empty = [] - - # map attrs - attr_map = { "KEY" = "VAL", "KEY2" = "VAL2" } - attr_map_empty = {} - - # simple blocks - block1 { - k = "b" - key2 = "v2" - } - labeled_block "label1" { - k = "b" - } - multi_labeled_block "label1" "label2" "label3" { - k = "b" - } - - # repeated block - repeated_block_type { - a = 2 - } - repeated_block_type { - b = 3 - } - - # repeated blocks with labels - label_repeated "l1" { - a = 1 - } - label_repeated "l1" { - a = 2 - } - label_repeated "l2" "l21" { - a = 3 - } - label_repeated "l2" "l21" "l23" { - a = 4 - } - label_repeated "l3" { - a = 5 - } - - # nested blocks - outer_block "l1" { - level = 1 - inner_block "l2" { - level = 2 - most_inner "l3" { - level = 3 - - inner_map = { "K" = "V" } - } - } - } - } - } - } -} diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 10f020dde..9c2abf6cd 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -4335,6 +4335,7 @@ type JobSubmission struct { Source string // Format indicates whether the original job was hcl1, hcl2, or json. + // hcl1 format has been removed and can no longer be parsed. Format string // VariableFlags contain the CLI "-var" flag arguments as submitted with the diff --git a/website/content/api-docs/jobs.mdx b/website/content/api-docs/jobs.mdx index c9ef94c0f..ff5c176f1 100644 --- a/website/content/api-docs/jobs.mdx +++ b/website/content/api-docs/jobs.mdx @@ -608,7 +608,7 @@ nomad operator api /v1/job/my-job/submission?version=42 #### Field Reference - `JobID`: The ID of the job associated with the original job file. -- `Format`: The file format of the original job file. One of `hcl2`, `hcl1`, or `json`. +- `Format`: The file format of the original job file. One of `hcl2` or `json`. - `Source`: The literal content of the original job file. - `VariableFlags`: The key-value pairs of HCL variables as submitted via `-var` command line arguments when submitting the job via CLI. diff --git a/website/content/docs/commands/job/plan.mdx b/website/content/docs/commands/job/plan.mdx index 2d62783d1..8573ebc6d 100644 --- a/website/content/docs/commands/job/plan.mdx +++ b/website/content/docs/commands/job/plan.mdx @@ -71,12 +71,9 @@ capability for the job's namespace. such as from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. -- `-hcl1`: If set, HCL1 parser is used for parsing the job spec. Takes - precedence over `-hcl2-strict`. - - `-hcl2-strict`: Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. - Defaults to true, but ignored if `-hcl1` is defined. + Defaults to true. - `-vault-token`: Used to validate if the user submitting the job has permission to run the job according to its Vault policies. A Vault token must diff --git a/website/content/docs/commands/job/run.mdx b/website/content/docs/commands/job/run.mdx index 36f2c87f8..b8dad76e8 100644 --- a/website/content/docs/commands/job/run.mdx +++ b/website/content/docs/commands/job/run.mdx @@ -78,12 +78,9 @@ that volume. such as from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. See [JSON Jobs] for details. -- `-hcl1`: If set, HCL1 parser is used for parsing the job spec. Takes - precedence over `-hcl2-strict`. - - `-hcl2-strict`: Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. - Defaults to true, but ignored if `-hcl1` is defined. + Defaults to true. - `-output`: Output the JSON that would be submitted to the HTTP API without submitting the job. diff --git a/website/content/docs/commands/job/validate.mdx b/website/content/docs/commands/job/validate.mdx index 738f7f759..b388afddf 100644 --- a/website/content/docs/commands/job/validate.mdx +++ b/website/content/docs/commands/job/validate.mdx @@ -46,12 +46,9 @@ capability for the job's namespace. such as from "nomad job inspect" or "nomad run -output", the value of the field is used as the job. -- `-hcl1`: If set, HCL1 parser is used for parsing the job spec. Takes - precedence over `-hcl2-strict`. - - `-hcl2-strict`: Whether an error should be produced from the HCL2 parser where a variable has been supplied which is not defined within the root variables. - Defaults to true, but ignored if `-hcl1` is defined. + Defaults to true. - `-vault-token`: Used to validate if the user submitting the job has permission to run the job according to its Vault policies. A Vault token must diff --git a/website/content/docs/job-specification/hcl2/index.mdx b/website/content/docs/job-specification/hcl2/index.mdx index 0643bb06c..d8c811920 100644 --- a/website/content/docs/job-specification/hcl2/index.mdx +++ b/website/content/docs/job-specification/hcl2/index.mdx @@ -35,8 +35,8 @@ context from the local environment of the CLI (e.g., files, environment variable Since HCL is a superset of JSON, `nomad job run example.json` will attempt to parse a JSON job using the HCL parser. However, the JSON format accepted by -the HCL parser is not the same as the [API's JSON format][json-jobs-api]. The -HCL parser's JSON format is unspecified, so the API format is preferred. You can +the HCL parser is not the same as the [API's JSON format][json-jobs-api]. The +HCL parser's JSON format is unspecified, so the API format is preferred. You can use the API format with the [`-json` command line flag][run-json]: ```shell-session @@ -83,15 +83,6 @@ For full details about Nomad's syntax, see: - [Configuration Syntax](/nomad/docs/job-specification/hcl2/syntax) - [Expressions](/nomad/docs/job-specification/hcl2/expressions) -## Backward Compatibilities - -HCL2 syntax closely mirrors HCL1, but has some minor changes. Most existing -Nomad job specifications will not require any changes. - -When you run `nomad job run` or `nomad job plan`, the CLI will report any -required changes. Also, you can activate a backwards compatibility mode by -passing `-hcl1` to use Nomad's HCL1 parser instead. - ### Blocks Block syntax is as follows, using unquoted attributes and quoted values: diff --git a/website/content/docs/upgrade/upgrade-specific.mdx b/website/content/docs/upgrade/upgrade-specific.mdx index 2de18374e..e4eba79be 100644 --- a/website/content/docs/upgrade/upgrade-specific.mdx +++ b/website/content/docs/upgrade/upgrade-specific.mdx @@ -13,6 +13,14 @@ upgrade. However, specific versions of Nomad may have more details provided for their upgrades as a result of new features or changed behavior. This page is used to document those details separately from the standard upgrade flow. +## Nomad 1.9.0 + +#### Support for HCLv1 removed + +Nomad 1.9.0 no longer supports the HCLv1 format for job specifications. Using +the `-hcl1` option for the `job run`, `job plan`, and `job validate` commands +will no longer work. + ## Nomad 1.8.3 #### Nomad keyring rotation