Skip to content

Commit

Permalink
Merge pull request #111 from terraform-providers/fix-dont-panic-with-…
Browse files Browse the repository at this point in the history
…invalid-json

Fix panic when using invalid JSON jobspec
  • Loading branch information
lgfa29 committed May 29, 2020
2 parents 117bbec + 912b904 commit de0dad9
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 8 deletions.
45 changes: 37 additions & 8 deletions nomad/resource_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,17 +548,20 @@ func resourceJobCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {

func parseJobspec(raw string, is_json bool, vaultToken *string) (*api.Job, error) {
var job *api.Job
var err error

if is_json {
err := json.Unmarshal([]byte(raw), &job)
if err != nil {
return nil, fmt.Errorf("error parsing jobspec: %s", err)
}
job, err = parseJSONJobspec(raw)
} else {
var err error
job, err = jobspec.Parse(strings.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("error parsing jobspec: %s", err)
}
}
if err != nil {
return nil, fmt.Errorf("error parsing jobspec: %s", err)
}

// If job is empty after parsing, the input is not a valid Nomad job.
if job == nil || reflect.DeepEqual(job, &api.Job{}) {
return nil, fmt.Errorf("error parsing jobspec: input JSON is not a valid Nomad jobspec")
}

// Inject the Vault token
Expand All @@ -567,6 +570,32 @@ func parseJobspec(raw string, is_json bool, vaultToken *string) (*api.Job, error
return job, nil
}

func parseJSONJobspec(raw string) (*api.Job, error) {
// `nomad job run -output` returns a jobspec with a "Job" root, so
// partially parse the input JSON to detect if we have this root.
var root map[string]json.RawMessage

err := json.Unmarshal([]byte(raw), &root)
if err != nil {
return nil, err
}

jobBytes, ok := root["Job"]
if !ok {
// Parse the input as is if there's no "Job" root.
jobBytes = []byte(raw)
}

// Parse actual job.
var job api.Job
err = json.Unmarshal(jobBytes, &job)
if err != nil {
return nil, err
}

return &job, nil
}

func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} {
ret := make([]interface{}, 0, len(tgs))

Expand Down
91 changes: 91 additions & 0 deletions nomad/resource_job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,40 @@ func TestResourceJob_consulConnect(t *testing.T) {
}

func TestResourceJob_json(t *testing.T) {
// Test invalid JSON inputs.
re := regexp.MustCompile("error parsing jobspec")
r.Test(t, r.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
Steps: []r.TestStep{
{
Config: testResourceJob_invalidJSONConfig,
ExpectError: re,
},
{
Config: testResourceJob_invalidJSONConfig_notJobspec,
ExpectError: re,
},
},

CheckDestroy: testResourceJob_checkDestroy("foo-json"),
})

// Test jobspec with "Job" root.
r.Test(t, r.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
Steps: []r.TestStep{
{
Config: testResourceJob_jsonConfigWithRoot,
Check: testResourceJob_initialCheck(t),
},
},

CheckDestroy: testResourceJob_checkDestroy("foo-json"),
})

// Test plain jobspec.
r.Test(t, r.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -468,6 +502,63 @@ resource "nomad_job" "test" {
}
`

var testResourceJob_invalidJSONConfig = `
resource "nomad_job" "test" {
json = true
jobspec = "not json"
}
`

var testResourceJob_invalidJSONConfig_notJobspec = `
resource "nomad_job" "test" {
json = true
jobspec = <<EOT
{
"not": "job"
}
EOT
}
`

var testResourceJob_jsonConfigWithRoot = `
resource "nomad_job" "test" {
json = true
jobspec = <<EOT
{
"Job": {
"Datacenters": [ "dc1" ],
"ID": "foo-json",
"Name": "foo-json",
"Type": "service",
"TaskGroups": [
{
"Name": "foo",
"Tasks": [{
"Config": {
"command": "/bin/sleep",
"args": [ "1" ]
},
"Driver": "raw_exec",
"Leader": true,
"LogConfig": {
"MaxFileSizeMB": 10,
"MaxFiles": 3
},
"Name": "foo",
"Resources": {
"CPU": 100,
"MemoryMB": 10
}
}
]
}
]
}
}
EOT
}
`

var testResourceJob_jsonConfig = `
resource "nomad_job" "test" {
json = true
Expand Down
23 changes: 23 additions & 0 deletions website/docs/r/job.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ EOT
}
```

## JSON jobspec

The input jobspec can also be provided as JSON instead of HCL by setting the
argument `json` to `true`:

```hcl
resource "nomad_job" "app" {
jobspec = "${file("${path.module}/job.json")}"
json = true
}
```

When using JSON, the input jobspec should have the same structured used by the
[Nomad API](https://www.nomadproject.io/api-docs/json-jobs/). The Nomad CLI
can translate HCL jobs to JSON:

```shellsession
nomad job run -output my-job.nomad > my-job.json
```

Or you can also use the [`/v1/jobs/parse`](https://www.nomadproject.io/api-docs/jobs/#parse-job)
API endpoint.

## Argument Reference

The following arguments are supported:
Expand Down

0 comments on commit de0dad9

Please sign in to comment.