mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 08:25:43 +03:00
Merge pull request #4138 from hashicorp/i-hcl-json-endpoint
HCL to JSON api endpoint
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
## 0.8.2 (Unreleased)
|
||||
|
||||
IMPROVEMENTS:
|
||||
* api: Add /v1/jobs/parse api endpoint for rendering HCL jobs files as JSON [[GH-2782](https://github.com/hashicorp/nomad/issues/2782)]
|
||||
* client: Create new process group on process startup. [[GH-3572](https://github.com/hashicorp/nomad/issues/3572)]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
22
api/jobs.go
22
api/jobs.go
@@ -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
|
||||
// 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
|
||||
func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) {
|
||||
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}
|
||||
|
||||
@@ -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, expectedRegion string) {
|
||||
if job == nil {
|
||||
t.Fatal("job should not be nil")
|
||||
}
|
||||
|
||||
region := job.Region
|
||||
|
||||
if region == nil {
|
||||
if expectedRegion != "" {
|
||||
t.Fatalf("expected job region to be '%s' but was unset", expectedRegion)
|
||||
}
|
||||
} else {
|
||||
if expectedRegion != *region {
|
||||
t.Fatalf("expected job region '%s', but got '%s'", expectedRegion, *region)
|
||||
}
|
||||
}
|
||||
}
|
||||
job, err := jobs.ParseHCL(mock.HCL(), true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
checkJob(job, "global")
|
||||
|
||||
job, err = jobs.ParseHCL(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)
|
||||
|
||||
@@ -143,6 +143,7 @@ func (s *HTTPServer) Shutdown() {
|
||||
// registerHandlers is used to attach our handlers to the mux
|
||||
func (s *HTTPServer) registerHandlers(enableDebug bool) {
|
||||
s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest))
|
||||
s.mux.HandleFunc("/v1/jobs/parse", s.wrap(s.JobsParseRequest))
|
||||
s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest))
|
||||
|
||||
s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest))
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
return jobStruct, nil
|
||||
}
|
||||
|
||||
func ApiJobToStructJob(job *api.Job) *structs.Job {
|
||||
job.Canonicalize()
|
||||
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
})
|
||||
}
|
||||
func TestHTTP_JobQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
|
||||
@@ -63,6 +63,38 @@ func Node() *structs.Node {
|
||||
return node
|
||||
}
|
||||
|
||||
func HCL() string {
|
||||
return `job "my-job" {
|
||||
datacenters = ["dc1"]
|
||||
type = "service"
|
||||
constraint {
|
||||
attribute = "${attr.kernel.name}"
|
||||
value = "linux"
|
||||
}
|
||||
|
||||
group "web" {
|
||||
count = 10
|
||||
restart {
|
||||
attempts = 3
|
||||
interval = "10m"
|
||||
delay = "1m"
|
||||
mode = "delay"
|
||||
}
|
||||
task "web" {
|
||||
driver = "exec"
|
||||
config {
|
||||
command = "/bin/date"
|
||||
}
|
||||
resources {
|
||||
cpu = 500
|
||||
memory = 256
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func Job() *structs.Job {
|
||||
job := &structs.Job{
|
||||
Region: "global",
|
||||
|
||||
@@ -231,6 +231,83 @@ $ curl \
|
||||
}
|
||||
```
|
||||
|
||||
## Parse Job
|
||||
|
||||
This endpoint will parse a HCL jobspec and produce the equivalent JSON encoded
|
||||
job.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ------------------------- | -------------------------- |
|
||||
| `POST` | `/v1/jobs/parse` | `application/json` |
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user