nomad
nomad copied to clipboard
Nomad Job Dispatch - Payload via HTTP request body
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.
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!
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!
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!
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.