Skip to content

Commit 6788d0a

Browse files
authored
job: add support for job submissions (#405)
If available, use the job submission source to detect changes to `jobspec`. This can mitigate drift detection problems such as #1. Read HCL2 variables from job submission even if the `nomad_job` resource does not speify an `hcl2` block.
1 parent 1b74304 commit 6788d0a

File tree

4 files changed

+124
-2
lines changed

4 files changed

+124
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ IMPROVEMENTS:
88
* resource/nomad_csi_volume: update import key to be `<volume id>@<namespace>` to allow importing volumes from namespaces other than `default` ([#408](https://github.com/hashicorp/terraform-provider-nomad/pull/408))
99
* resource/nomad_csi_volume_registration: update import key to be `<volume id>@<namespace>` to allow importing volume registrations from namespaces other than `default` ([#408](https://github.com/hashicorp/terraform-provider-nomad/pull/408))
1010
on Nomad version 1.6.3 or later, if the CSI plugin supports it ([#382](https://github.com/hashicorp/terraform-provider-nomad/pull/382))
11+
* resource/nomad_job: read and submit original jobspec on state refresh and job register ([#405](https://github.com/hashicorp/terraform-provider-nomad/pull/405))
1112
* resource/nomad_job: Add `rerun_if_dead` attribute to allow forcing a job to run again if it's marked as `dead`. ([#407](https://github.com/hashicorp/terraform-provider-nomad/pull/407))
1213
* resource/nomad_job: update import key to be `<job id>@<namespace>` to allow importing jobs from namespaces other than `default` ([#408](https://github.com/hashicorp/terraform-provider-nomad/pull/408))
1314

nomad/resource_job.go

+82
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"github.com/hashicorp/nomad/jobspec2"
2020
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
2121
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
22+
"golang.org/x/exp/maps"
23+
2224
"github.com/hashicorp/terraform-provider-nomad/nomad/helper"
2325
)
2426

@@ -351,6 +353,11 @@ type HCL1JobParserConfig struct {
351353
type HCL2JobParserConfig struct {
352354
AllowFS bool
353355
Vars map[string]string
356+
357+
// Deprecated: Starting in v2.0.0 the provider assumes HCL2 parsing by
358+
// default. This field should only be used to update the `hcl2` attribute
359+
// in state without causing a diff.
360+
Enabled bool
354361
}
355362

356363
// ResourceFieldGetter are able to retrieve field values.
@@ -407,9 +414,23 @@ func resourceJobRegister(d *schema.ResourceData, meta interface{}) error {
407414
if err != nil {
408415
wantModifyIndex = 0
409416
}
417+
418+
sub := &api.JobSubmission{
419+
Source: jobspecRaw,
420+
Format: "hcl2",
421+
VariableFlags: jobParserConfig.HCL2.Vars,
422+
}
423+
switch {
424+
case jobParserConfig.JSON.Enabled:
425+
sub.Format = "json"
426+
case jobParserConfig.HCL1.Enabled:
427+
sub.Format = "hcl1"
428+
}
429+
410430
resp, _, err := client.Jobs().RegisterOpts(job, &api.RegisterOptions{
411431
PolicyOverride: d.Get("policy_override").(bool),
412432
ModifyIndex: wantModifyIndex,
433+
Submission: sub,
413434
}, &api.WriteOptions{
414435
Namespace: *job.Namespace,
415436
})
@@ -641,6 +662,56 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error {
641662
d.Set("allocation_ids", nil)
642663
}
643664

665+
// Update jobspec submission data if available.
666+
// Safely ignore errors as this is an optional step.
667+
sub, _, err := client.Jobs().Submission(*job.ID, int(*job.Version), opts)
668+
if err != nil {
669+
log.Printf("[WARN] failed to read job submission: %v", err)
670+
} else {
671+
err := resourceJobReadSubmission(sub, d, meta)
672+
if err != nil {
673+
log.Printf("[WARN] failed to update job submission: %v", err)
674+
}
675+
}
676+
677+
return nil
678+
}
679+
680+
func resourceJobReadSubmission(sub *api.JobSubmission, d *schema.ResourceData, meta any) error {
681+
if sub == nil {
682+
return nil
683+
}
684+
685+
if sub.Source != "" {
686+
d.Set("jobspec", sub.Source)
687+
}
688+
689+
if sub.Format == "hcl2" {
690+
var err error
691+
var hcl2Config HCL2JobParserConfig
692+
693+
hcl2, ok := d.GetOk("hcl2")
694+
if ok {
695+
hcl2Config, err = parseHCL2JobParserConfig(hcl2)
696+
if err != nil {
697+
return fmt.Errorf("failed to parse HCL2 config: %v", err)
698+
}
699+
} else {
700+
// Use default values if hcl2 is not set.
701+
hcl2Config = HCL2JobParserConfig{
702+
AllowFS: false,
703+
Enabled: true,
704+
}
705+
}
706+
707+
// Only update hcl2 if there are changes to variables to avoid
708+
// unnecessary updates if hcl2 is not set.
709+
if !maps.Equal(sub.VariableFlags, hcl2Config.Vars) {
710+
hcl2Config.Vars = sub.VariableFlags
711+
d.Set("hcl2", flattenHCL2JobParserConfig(hcl2Config))
712+
}
713+
}
714+
644715
return nil
645716
}
646717

@@ -827,6 +898,9 @@ func parseHCL2JobParserConfig(raw interface{}) (HCL2JobParserConfig, error) {
827898
if allowFS, ok := hcl2Map["allow_fs"].(bool); ok {
828899
config.AllowFS = allowFS
829900
}
901+
if enabled, ok := hcl2Map["enabled"].(bool); ok {
902+
config.Enabled = enabled
903+
}
830904
if vars, ok := hcl2Map["vars"].(map[string]interface{}); ok {
831905
config.Vars = make(map[string]string)
832906
for k, v := range vars {
@@ -837,6 +911,14 @@ func parseHCL2JobParserConfig(raw interface{}) (HCL2JobParserConfig, error) {
837911
return config, nil
838912
}
839913

914+
func flattenHCL2JobParserConfig(c HCL2JobParserConfig) []any {
915+
return []any{map[string]any{
916+
"allow_fs": c.AllowFS,
917+
"enabled": c.Enabled,
918+
"vars": c.Vars,
919+
}}
920+
}
921+
840922
func parseJobspec(raw string, config JobParserConfig, vaultToken *string, consulToken *string) (*api.Job, error) {
841923
var job *api.Job
842924
var err error

nomad/resource_job_test.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10-
"io/ioutil"
10+
"os"
1111
"reflect"
1212
"regexp"
1313
"strings"
@@ -641,7 +641,7 @@ func testResourceJob_hcl2Check(s *terraform.State) error {
641641
}
642642
got := *tpl.EmbeddedTmpl
643643

644-
want, err := ioutil.ReadFile("./test-fixtures/hello.txt")
644+
want, err := os.ReadFile("./test-fixtures/hello.txt")
645645
if err != nil {
646646
return fmt.Errorf("failed to open template data: %v", err)
647647
}
@@ -650,6 +650,28 @@ func testResourceJob_hcl2Check(s *terraform.State) error {
650650
return fmt.Errorf("template content mismatch (-want +got):\n%s", diff)
651651
}
652652

653+
sub, _, err := client.Jobs().Submission(jobID, int(*job.Version), &api.QueryOptions{
654+
Namespace: *job.Namespace,
655+
})
656+
if err != nil {
657+
return fmt.Errorf("error reading job submissions: %s", err)
658+
}
659+
if diff := cmp.Diff(instanceState.Attributes["jobspec"], sub.Source); diff != "" {
660+
return fmt.Errorf("job source mismatch (-want +got):\n%s", diff)
661+
}
662+
663+
wantVars := make(map[string]string)
664+
for k, v := range instanceState.Attributes {
665+
if !strings.HasPrefix(k, "hcl2.0.vars") || k == "hcl2.0.vars.%" {
666+
continue
667+
}
668+
varKey := strings.TrimPrefix(k, "hcl2.0.vars.")
669+
wantVars[varKey] = v
670+
}
671+
if diff := cmp.Diff(wantVars, sub.VariableFlags); diff != "" {
672+
return fmt.Errorf("job hcl2 variables mismatch (-want +got):\n%s", diff)
673+
}
674+
653675
return nil
654676
}
655677

@@ -1199,6 +1221,16 @@ func testResourceJob_initialCheckNS(t *testing.T, expectedNamespace string) r.Te
11991221
return fmt.Errorf("job namespace is %q; want %q", got, want)
12001222
}
12011223

1224+
sub, _, err := client.Jobs().Submission(jobID, int(*job.Version), &api.QueryOptions{
1225+
Namespace: expectedNamespace,
1226+
})
1227+
if err != nil {
1228+
return fmt.Errorf("error reading job submissions: %s", err)
1229+
}
1230+
if diff := cmp.Diff(instanceState.Attributes["jobspec"], sub.Source); diff != "" {
1231+
return fmt.Errorf("job source mismatch (-want +got):\n%s", diff)
1232+
}
1233+
12021234
return nil
12031235
}
12041236
}

website/docs/r/job.html.markdown

+7
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ EOF
249249
}
250250
```
251251

252+
## Tracking Jobspec Changes
253+
254+
The Nomad API allows [submitting the raw jobspec when registering and updating
255+
jobs](https://developer.hashicorp.com/nomad/api-docs/jobs#submission). If
256+
available, the job submission source is used to detect changes to the `jobspec`
257+
and `hcl2.vars` arguments.
258+
252259
## Argument Reference
253260

254261
The following arguments are supported:

0 commit comments

Comments
 (0)