-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
HCL to JSON api endpoint #4138
HCL to JSON api endpoint #4138
Changes from 10 commits
b2b9992
03f5c2f
14c44da
a5ffa22
c594520
4952c52
4303941
2e31bbe
815c9fc
8ef7d20
059ea94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,11 +36,33 @@ type Jobs struct { | |
client *Client | ||
} | ||
|
||
// JobsParseRequest is used for arguments of the /vi/jobs/parse endpoint | ||
type JobsParseRequest struct { | ||
//JobHCL is an hcl jobspec | ||
JobHCL string | ||
|
||
//Canonicalize is a flag as to if the server should return default values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put a newline between the previous field and the comment on the next one. |
||
//for unset fields | ||
Canonicalize bool | ||
} | ||
|
||
// Jobs returns a handle on the jobs endpoints. | ||
func (c *Client) Jobs() *Jobs { | ||
return &Jobs{client: c} | ||
} | ||
|
||
// Parse is used to convert the HCL repesentation of a Job to JSON server side. | ||
// To parse the HCL client side see package github.com/hashicorp/nomad/jobspec | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put a period between multiple sentences in comments as godoc/godoc.org will run them together into one line. |
||
func (j *Jobs) Parse(jobHCL string, canonicalize bool) (*Job, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name it |
||
var job Job | ||
req := &JobsParseRequest{ | ||
JobHCL: jobHCL, | ||
Canonicalize: canonicalize, | ||
} | ||
_, err := j.client.write("/v1/jobs/parse", req, &job, nil) | ||
return &job, err | ||
} | ||
|
||
func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) { | ||
var resp JobValidateResponse | ||
req := &JobValidateRequest{Job: job} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,43 @@ func TestJobs_Register(t *testing.T) { | |
} | ||
} | ||
|
||
func TestJobs_Parse(t *testing.T) { | ||
t.Parallel() | ||
c, s := makeClient(t, nil, nil) | ||
defer s.Stop() | ||
|
||
jobs := c.Jobs() | ||
|
||
checkJob := func(job *Job, expected string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected -> expectedRegion |
||
if job == nil { | ||
t.Fatal("job should not be nil") | ||
} | ||
|
||
region := job.Region | ||
|
||
if region == nil { | ||
if expected != "" { | ||
t.Fatalf("expected job region to be '%s' but was unset", expected) | ||
} | ||
} else { | ||
if expected != *region { | ||
t.Fatalf("expected job region '%s', but got '%s'", expected, *region) | ||
} | ||
} | ||
} | ||
job, err := jobs.Parse(mock.HCL(), true) | ||
if err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
checkJob(job, "global") | ||
|
||
job, err = jobs.Parse(mock.HCL(), false) | ||
if err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
checkJob(job, "") | ||
} | ||
|
||
func TestJobs_Validate(t *testing.T) { | ||
t.Parallel() | ||
c, s := makeClient(t, nil, nil) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |
|
||
"github.com/golang/snappy" | ||
"github.com/hashicorp/nomad/api" | ||
"github.com/hashicorp/nomad/jobspec" | ||
"github.com/hashicorp/nomad/nomad/structs" | ||
) | ||
|
||
|
@@ -544,6 +545,32 @@ func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Requ | |
return out, nil | ||
} | ||
|
||
// JobsParseRequest parses a hcl jobspec and returns a api.Job | ||
func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { | ||
if req.Method != http.MethodPut && req.Method != http.MethodPost { | ||
return nil, CodedError(405, ErrInvalidMethod) | ||
} | ||
|
||
args := &api.JobsParseRequest{} | ||
if err := decodeBody(req, &args); err != nil { | ||
return nil, CodedError(400, err.Error()) | ||
} | ||
if args.JobHCL == "" { | ||
return nil, CodedError(400, "Job spec is empty") | ||
} | ||
|
||
jobfile := strings.NewReader(args.JobHCL) | ||
jobStruct, err := jobspec.Parse(jobfile) | ||
if err != nil { | ||
return nil, CodedError(400, err.Error()) | ||
} | ||
|
||
if args.Canonicalize { | ||
jobStruct.Canonicalize() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not capture the warnings returned here and return it: https://github.com/hashicorp/nomad/blob/master/nomad/structs/structs.go#L906-L908 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I follow. |
||
} | ||
return jobStruct, nil | ||
} | ||
|
||
func ApiJobToStructJob(job *api.Job) *structs.Job { | ||
job.Canonicalize() | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -272,6 +272,38 @@ func TestHTTP_JobsRegister_Defaulting(t *testing.T) { | |||||
}) | ||||||
} | ||||||
|
||||||
func TestHTTP_JobsParse(t *testing.T) { | ||||||
t.Parallel() | ||||||
httpTest(t, nil, func(s *TestAgent) { | ||||||
buf := encodeReq(api.JobsParseRequest{JobHCL: mock.HCL()}) | ||||||
req, err := http.NewRequest("POST", "/v1/jobs/parse", buf) | ||||||
if err != nil { | ||||||
t.Fatalf("err: %v", err) | ||||||
} | ||||||
|
||||||
respW := httptest.NewRecorder() | ||||||
|
||||||
obj, err := s.Server.JobsParseRequest(respW, req) | ||||||
if err != nil { | ||||||
t.Fatalf("err: %v", err) | ||||||
} | ||||||
if obj == nil { | ||||||
t.Fatal("response should not be nil") | ||||||
} | ||||||
|
||||||
job := obj.(*api.Job) | ||||||
expected := mock.Job() | ||||||
if job.Name == nil || *job.Name != expected.Name { | ||||||
t.Fatalf("job name is '%s', expected '%s'", *job.Name, expected.Name) | ||||||
} | ||||||
|
||||||
if job.Datacenters == nil || | ||||||
job.Datacenters[0] != expected.Datacenters[0] { | ||||||
t.Fatalf("job datacenters is '%s', expected '%s'", | ||||||
job.Datacenters[0], expected.Datacenters[0]) | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would suggest checking all fields of the returned job against expected instead of just these three fields. Line 59 in 5c8b249
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nomad/command/agent/job_endpoint.go Line 547 in 537b947
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cautionary note - not sure if a direct translation like works okay when it comes to unset fields (esp if canonicalize is not called). so it may produce a diff complaining about empty list vs nil. I haven't tried it before, seem worth trying it and using it here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I spent some time looking at this and there are a few things:
At some point it feels like I'm testing more of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fair. It doesn't seem worth it here to use diff for this test given the above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the pointer though, I learned more about the structs package 😃 |
||||||
}) | ||||||
} | ||||||
func TestHTTP_JobQuery(t *testing.T) { | ||||||
t.Parallel() | ||||||
httpTest(t, nil, func(s *TestAgent) { | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -231,6 +231,83 @@ $ curl \ | |
} | ||
``` | ||
|
||
## Parse Job | ||
|
||
This endpoint will parse an hcl jobspec and produce the equivalent json encoded | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HCL and JSON capitalized |
||
job. | ||
|
||
| Method | Path | Produces | | ||
| ------ | ------------------------- | -------------------------- | | ||
| `POST` | `/v1/jobs/parse` | `application/json` | | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the standard blocking/acl section even though neither apply:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
The table below shows this endpoint's support for | ||
[blocking queries](/api/index.html#blocking-queries) and | ||
[required ACLs](/api/index.html#acls). | ||
|
||
| Blocking Queries | ACL Required | | ||
| ---------------- | ------------ | | ||
| `NO` | `none` | | ||
|
||
### Parameters | ||
|
||
- `JobHCL` `(string: <required>)` - Specifies the HCL definition of the job | ||
encoded in a JSON string. | ||
- `Canonicalize` `(bool: false)` - Flag to enable setting any unset fields to | ||
their default values. | ||
|
||
## Sample Payload | ||
|
||
```json | ||
{ | ||
"JobHCL":"job \"example\" { type = \"service\" group \"cache\" {} }", | ||
"Canonicalize": true | ||
} | ||
``` | ||
|
||
### Sample Request | ||
|
||
```text | ||
$ curl \ | ||
--request POST \ | ||
--data '{"Canonicalize": true, "JobHCL": "job \"my-job\" {}"}' \ | ||
https://localhost:4646/v1/jobs/parse | ||
``` | ||
|
||
### Sample Response | ||
|
||
```json | ||
{ | ||
"AllAtOnce": false, | ||
"Constraints": null, | ||
"CreateIndex": 0, | ||
"Datacenters": null, | ||
"ID": "my-job", | ||
"JobModifyIndex": 0, | ||
"Meta": null, | ||
"Migrate": null, | ||
"ModifyIndex": 0, | ||
"Name": "my-job", | ||
"Namespace": "default", | ||
"ParameterizedJob": null, | ||
"ParentID": "", | ||
"Payload": null, | ||
"Periodic": null, | ||
"Priority": 50, | ||
"Region": "global", | ||
"Reschedule": null, | ||
"Stable": false, | ||
"Status": "", | ||
"StatusDescription": "", | ||
"Stop": false, | ||
"SubmitTime": null, | ||
"TaskGroups": null, | ||
"Type": "service", | ||
"Update": null, | ||
"VaultToken": "", | ||
"Version": 0 | ||
} | ||
``` | ||
|
||
## Read Job | ||
|
||
This endpoint reads information about a single job for its specification and | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a space between forward slashes and the first letter