nomad icon indicating copy to clipboard operation
nomad copied to clipboard

Nomad Job Dispatch - Payload via HTTP request body

Open EtienneBruines opened this issue 1 year ago • 1 comments
trafficstars

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

  • Replace the HTTP request body in the load balancer, before sending it off to Nomad. Even though load balancers (like haproxy, traefik) have good support for adding/replacing HTTP headers, they generally do not like modifying HTTP request bodies.

EtienneBruines avatar Oct 29 '24 09:10 EtienneBruines

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 --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!

gulducat avatar Oct 29 '24 19:10 gulducat

We kicked it around over here, and instead of a query argument toggling the behavior on this API, we can provide a new endpoint: /v1/job/{job_id}/dispatch/payload. That way the complexity of the existing endpoint remains the same, and the new one is quite simple to describe in documentation.

Have a look at the PR if you'd like to confirm this suits your use case. Thanks again for the suggestion!

gulducat avatar Nov 06 '24 20:11 gulducat

Thank you for your quick reply and hard work!

I gave the PR a thorough glance (i.e., without building it) and it looks like everything we had hoped for. Awesome!

EtienneBruines avatar Nov 06 '24 20:11 EtienneBruines

I'm going to lock this issue because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

github-actions[bot] avatar Mar 08 '25 02:03 github-actions[bot]