hashicorp / nomad

Nomad is an easy-to-use, flexible, and performant workload orchestrator that can deploy a mix of microservice, batch, containerized, and non-containerized applications. Nomad is easy to operate and scale and has native Consul and Vault integrations.
https://www.nomadproject.io/
Other
14.89k stars 1.95k forks source link

Nomad Job Dispatch - Payload via HTTP request body #24312

Open EtienneBruines opened 3 days ago

EtienneBruines commented 3 days ago

Proposal

The ability to send the job payload as the http request body, instead of having to base64-encode it.

That would allow support for something like this:

POST /v1/job/my-parameterized-job/dispatch?BodyAsPayload=true HTTP/1.1
Content-Type: text/plain

This is the amazing payload that will be sent to the parameterized job.

Use-cases

Some external systems support 'webhooks', but don't allow for much control over the contents of the request body. These bodies usually contain information on the event or the changed entity, but do not allow us to specify "put that information in a Payload object and base64-encode everything". This makes it difficult to use Nomad's dispatch-job as a webhook handler.

It would be nice to be able to tell Nomad (via a query parameter or a HTTP header) to interpret the entire HTTP request body as the raw payload.

Attempted Solutions

gulducat commented 2 days ago

Heya, thanks for the suggestion!

I agree this would be a nifty enhancement for dispatch jobs, especially for the webhook use case you describe.

I think a patch like this might be sufficient ```diff diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index 4eba764fbe..a5979bbfee 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -5,6 +5,7 @@ package agent import ( "fmt" + "io" "maps" "net/http" "slices" @@ -876,8 +877,17 @@ func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Requ return nil, CodedError(405, ErrInvalidMethod) } args := structs.JobDispatchRequest{} - if err := decodeBody(req, &args); err != nil { - return nil, CodedError(400, err.Error()) + var err error + payloadAsBody := req.URL.Query().Get("payloadAsBody") + if payloadAsBody != "" { // TODO: boolean + args.Payload, err = io.ReadAll(req.Body) + if err != nil { + return nil, CodedError(400, err.Error()) + } + } else { + if err = decodeBody(req, &args); err != nil { + return nil, CodedError(400, err.Error()) + } } if args.JobID != "" && args.JobID != jobID { return nil, CodedError(400, "Job ID does not match") ```

but we'll need to bounce it around internally a bit. I'll get it in the queue for consideration. Thanks again!