Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HCL2 on nomad Go package #185

Merged
merged 9 commits into from
Jan 13, 2021
70 changes: 55 additions & 15 deletions nomad/resource_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import (
"time"

"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/jobspec2"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

"github.com/hashicorp/nomad/jobspec"
)

func resourceJob() *schema.Resource {
Expand Down Expand Up @@ -73,6 +73,12 @@ func resourceJob() *schema.Resource {
Type: schema.TypeString,
},

"hcl2": {
Description: "If true, the `jobspec` will be parsed as HCL2 instead of HCL.",
Optional: true,
Type: schema.TypeBool,
},

"json": {
Description: "If true, the `jobspec` will be parsed as json instead of HCL.",
Optional: true,
Expand Down Expand Up @@ -240,7 +246,11 @@ func resourceJobRegister(d *schema.ResourceData, meta interface{}) error {
// Get the jobspec itself
jobspecRaw := d.Get("jobspec").(string)
is_json := d.Get("json").(bool)
job, err := parseJobspec(jobspecRaw, is_json, providerConfig.vaultToken)
parserHCL2 := d.Get("hcl2").(bool)
if is_json && parserHCL2 {
return fmt.Errorf("invalid combination. is_json is %t and parserHCL2 is %t", is_json, parserHCL2)
}
job, err := parseJobspec(jobspecRaw, is_json, parserHCL2, providerConfig.vaultToken)
if err != nil {
return err
}
Expand Down Expand Up @@ -505,7 +515,11 @@ func resourceJobCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
}

is_json := d.Get("json").(bool)
job, err := parseJobspec(newSpecRaw.(string), is_json, providerConfig.vaultToken) // catch syntax errors client-side during plan
parserHCL2 := d.Get("hcl2").(bool)
if is_json && parserHCL2 {
return fmt.Errorf("invalid combination. is_json is %t and parserHCL2 is %t", is_json, parserHCL2)
}
job, err := parseJobspec(newSpecRaw.(string), is_json, parserHCL2, providerConfig.vaultToken) // catch syntax errors client-side during plan
if err != nil {
return err
}
Expand Down Expand Up @@ -580,15 +594,19 @@ func resourceJobCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
return nil
}

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

if is_json {
switch {
case is_json:
job, err = parseJSONJobspec(raw)
} else {
case parserHCL2:
job, err = jobspec2.Parse(raw, strings.NewReader(raw))
default:
job, err = jobspec.Parse(strings.NewReader(raw))
}

if err != nil {
return nil, fmt.Errorf("error parsing jobspec: %s", err)
}
Expand Down Expand Up @@ -707,16 +725,38 @@ func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} {
// jobspecDiffSuppress is the DiffSuppressFunc used by the schema to
// check if two jobspecs are equal.
func jobspecDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
// TODO: does this need to consider is_json ???
// Parse the old job
oldJob, err := jobspec.Parse(strings.NewReader(old))
if err != nil {
var oldJob *api.Job
var newJob *api.Job
var oldErr error
var newErr error

is_json := d.Get("json").(bool)
parserHCL2 := d.Get("hcl2").(bool)
if is_json && parserHCL2 {
log.Printf("invalid combination. is_json is %t and parserHCL2 is %t\n", is_json, parserHCL2)
return false
}

// Parse the new job
newJob, err := jobspec.Parse(strings.NewReader(new))
if err != nil {
switch {
case is_json:
oldJob, oldErr = parseJSONJobspec(old)
newJob, newErr = parseJSONJobspec(new)
case parserHCL2:
oldJob, oldErr = jobspec2.Parse(old, strings.NewReader(old))
newJob, newErr = jobspec2.Parse(new, strings.NewReader(new))
default:
oldJob, oldErr = jobspec.Parse(strings.NewReader(old))
newJob, newErr = jobspec.Parse(strings.NewReader(new))
}
if oldErr != nil {
log.Println("error parsing old jobspec")
log.Printf("%v\n", oldJob)
log.Printf("%v", oldErr)
return false
}
if newErr != nil {
log.Println("error parsing new jobspec")
log.Printf("%v\n", newJob)
log.Printf("%v", newErr)
return false
}

Expand Down
76 changes: 75 additions & 1 deletion nomad/resource_job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,47 @@ func testResourceJob_parameterizedCheck(s *terraform.State) error {
return nil
}

func TestResourceJob_hcl2(t *testing.T) {
r.Test(t, r.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t); testCheckMinVersion(t, "0.11.0-beta1") },
Steps: []r.TestStep{
{
Config: testResourceJob_hcl2,
Check: testResourceJob_hcl2Check,
},
},
CheckDestroy: testResourceJob_checkDestroy("foo-hcl2"),
})
}

func testResourceJob_hcl2Check(s *terraform.State) error {
resourceState := s.Modules[0].Resources["nomad_job.hcl2"]
if resourceState == nil {
return errors.New("resource not found in state")
}

instanceState := resourceState.Primary
if instanceState == nil {
return errors.New("resource has no primary instance")
}

jobID := instanceState.ID

providerConfig := testProvider.Meta().(ProviderConfig)
client := providerConfig.client
job, _, err := client.Jobs().Info(jobID, nil)
if err != nil {
return fmt.Errorf("error reading back job: %s", err)
}

if got, want := *job.ID, jobID; got != want {
return fmt.Errorf("jobID is %q; want %q", got, want)
}

return nil
}

var testResourceJob_parameterizedJob = `
resource "nomad_job" "parameterized" {
jobspec = <<EOT
Expand Down Expand Up @@ -2654,7 +2695,7 @@ resource "nomad_job" "test" {
job "foo-csi-controller" {
datacenters = ["dc1"]
group "foo-controller" {
stop_after_client_disconnect = true
stop_after_client_disconnect = "90s"
task "plugin" {
driver = "docker"

Expand Down Expand Up @@ -2714,3 +2755,36 @@ job "foo-multiregion" {
EOT
}
`

var testResourceJob_hcl2 = `
resource "nomad_job" "hcl2" {
hcl2 = true
jobspec = <<EOT
variables {
args = ["10"]
}
job "foo-hcl2" {
datacenters = ["dc1"]
group "hcl2" {
restart {
attempts = 5
interval = "10m"
delay = "15s"
mode = "delay"
}

task "sleep" {
driver = "raw_exec"
config {
command = "/bin/sleep"
args = var.args
}
restart {
attempts = 10
}
}
}
}
EOT
}
`
15 changes: 15 additions & 0 deletions website/docs/r/job.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ 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.

## HCL2 jobspec

The input jobspec can also be provided in the [HCL2 format](https://www.nomadproject.io/docs/job-specification/hcl2)
by setting the argument `hcl2` to `true`:

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

## Argument Reference

The following arguments are supported:
Expand All @@ -110,3 +122,6 @@ The following arguments are supported:

- `json` `(boolean: false)` - Set this to true if your jobspec is structured with
JSON instead of the default HCL.

- `hcl2` `(boolean: false)` - Set this to true if your jobspec uses the HCL2
format instead of the default HCL.