From 87b4053b6bf6cd19b7338f88e8bee05b6a8c8b35 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:30:23 -0500 Subject: [PATCH 01/10] feat(api): add POST /volumes/from-archive endpoint for multipart uploads Split volume creation into two endpoints so Stainless can generate proper SDK methods for both: - POST /volumes (JSON) - create empty volume - POST /volumes/from-archive (multipart) - create volume from tar.gz This allows the Go SDK to generate a VolumeService.NewFromArchive() method with proper Content io.Reader field and MarshalMultipart() support, similar to how BuildService.New() works. Previously, having both content types on a single endpoint caused Stainless to only generate the JSON variant, requiring manual multipart request construction in client code. --- openapi.yaml | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index bd85c46..98e0d12 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1805,11 +1805,8 @@ paths: schema: $ref: "#/components/schemas/Error" post: - summary: Create volume - description: | - Creates a new volume. Supports two modes: - - JSON body: Creates an empty volume of the specified size - - Multipart form: Creates a volume pre-populated with content from a tar.gz archive + summary: Create empty volume + description: Creates a new empty volume of the specified size. operationId: createVolume security: - bearerAuth: [] @@ -1819,6 +1816,50 @@ paths: application/json: schema: $ref: "#/components/schemas/CreateVolumeRequest" + responses: + 201: + description: Volume created + content: + application/json: + schema: + $ref: "#/components/schemas/Volume" + 400: + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - volume with this ID already exists + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /volumes/from-archive: + post: + summary: Create volume from archive + description: | + Creates a new volume pre-populated with content from a tar.gz archive. + The archive is extracted into the volume's root directory. + operationId: createVolumeFromArchive + security: + - bearerAuth: [] + requestBody: + required: true + content: multipart/form-data: schema: type: object From 49c6b62d48ed1d208e4ad90b10d008c907ff426a Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:30:56 -0500 Subject: [PATCH 02/10] feat(stainless): add create_from_archive method for volumes Maps to the new POST /volumes/from-archive endpoint which will generate VolumeService.NewFromArchive() with multipart support. --- stainless.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/stainless.yaml b/stainless.yaml index d8699a0..bc609a4 100644 --- a/stainless.yaml +++ b/stainless.yaml @@ -108,6 +108,7 @@ resources: methods: list: get /volumes create: post /volumes + create_from_archive: post /volumes/from-archive get: get /volumes/{id} delete: delete /volumes/{id} From 428f1df8fad9b509a46c6484c07b6e62252c9d18 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:36:20 -0500 Subject: [PATCH 03/10] feat(api): implement CreateVolumeFromArchive handler - Regenerate oapi code from updated OpenAPI spec - Simplify CreateVolume handler to JSON-only (empty volumes) - Add CreateVolumeFromArchive handler for multipart uploads - Remove unused multipart import from volumes.go The new endpoint POST /volumes/from-archive handles creating volumes pre-populated with content from tar.gz archives. --- cmd/api/api/volumes.go | 101 ++++--- lib/oapi/oapi.go | 610 ++++++++++++++++++++++++++++------------- 2 files changed, 476 insertions(+), 235 deletions(-) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index ad76376..7455c48 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -4,7 +4,6 @@ import ( "context" "errors" "io" - "mime/multipart" "strconv" "github.com/kernel/hypeman/lib/logger" @@ -34,65 +33,63 @@ func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRe return oapi.ListVolumes200JSONResponse(oapiVols), nil } -// CreateVolume creates a new volume -// Supports two modes: -// - JSON body: Creates an empty volume of the specified size -// - Multipart form: Creates a volume pre-populated with content from a tar.gz archive +// CreateVolume creates a new empty volume of the specified size func (s *ApiService) CreateVolume(ctx context.Context, request oapi.CreateVolumeRequestObject) (oapi.CreateVolumeResponseObject, error) { log := logger.FromContext(ctx) - // Handle JSON request (empty volume) - if request.JSONBody != nil { - domainReq := volumes.CreateVolumeRequest{ - Name: request.JSONBody.Name, - SizeGb: request.JSONBody.SizeGb, - Id: request.JSONBody.Id, - } - - vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) - if err != nil { - if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolume409JSONResponse{ - Code: "already_exists", - Message: "volume with this ID already exists", - }, nil - } - log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.JSONBody.Name) - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", - }, nil - } - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + if request.Body == nil { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_request", + Message: "request body is required", + }, nil } - // Handle multipart request (volume with archive content) - if request.MultipartBody != nil { - return s.createVolumeFromMultipart(ctx, request.MultipartBody) + domainReq := volumes.CreateVolumeRequest{ + Name: request.Body.Name, + SizeGb: request.Body.SizeGb, + Id: request.Body.Id, } - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_request", - Message: "request body is required", - }, nil + vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) + if err != nil { + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolume409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, nil + } + log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.Body.Name) + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil } -// createVolumeFromMultipart handles creating a volume from multipart form data with archive content -func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartReader *multipart.Reader) (oapi.CreateVolumeResponseObject, error) { +// CreateVolumeFromArchive creates a new volume pre-populated with content from a tar.gz archive +func (s *ApiService) CreateVolumeFromArchive(ctx context.Context, request oapi.CreateVolumeFromArchiveRequestObject) (oapi.CreateVolumeFromArchiveResponseObject, error) { log := logger.FromContext(ctx) + if request.Body == nil { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "invalid_request", + Message: "multipart body is required", + }, nil + } + var name string var sizeGb int var id *string var archiveReader io.Reader for { - part, err := multipartReader.NextPart() + part, err := request.Body.NextPart() if err == io.EOF { break } if err != nil { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "invalid_form", Message: "failed to parse multipart form: " + err.Error(), }, nil @@ -102,7 +99,7 @@ func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartRea case "name": data, err := io.ReadAll(part) if err != nil { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "invalid_field", Message: "failed to read name field", }, nil @@ -111,14 +108,14 @@ func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartRea case "size_gb": data, err := io.ReadAll(part) if err != nil { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "invalid_field", Message: "failed to read size_gb field", }, nil } sizeGb, err = strconv.Atoi(string(data)) if err != nil || sizeGb <= 0 { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "invalid_field", Message: "size_gb must be a positive integer", }, nil @@ -126,7 +123,7 @@ func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartRea case "id": data, err := io.ReadAll(part) if err != nil { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "invalid_field", Message: "failed to read id field", }, nil @@ -139,13 +136,13 @@ func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartRea archiveReader = part // Process the archive immediately while we have the reader if name == "" { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "missing_field", Message: "name is required", }, nil } if sizeGb <= 0 { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "missing_field", Message: "size_gb is required", }, nil @@ -161,38 +158,38 @@ func (s *ApiService) createVolumeFromMultipart(ctx context.Context, multipartRea vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, archiveReader) if err != nil { if errors.Is(err, volumes.ErrArchiveTooLarge) { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "archive_too_large", Message: err.Error(), }, nil } if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolume409JSONResponse{ + return oapi.CreateVolumeFromArchive409JSONResponse{ Code: "already_exists", Message: "volume with this ID already exists", }, nil } log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", name) - return oapi.CreateVolume500JSONResponse{ + return oapi.CreateVolumeFromArchive500JSONResponse{ Code: "internal_error", Message: "failed to create volume", }, nil } - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + return oapi.CreateVolumeFromArchive201JSONResponse(volumeToOAPI(*vol)), nil } } // If we get here without processing content, it means content was not provided if archiveReader == nil { - return oapi.CreateVolume400JSONResponse{ + return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "missing_file", - Message: "content file is required for multipart requests", + Message: "content file is required", }, nil } // Should not reach here - return oapi.CreateVolume500JSONResponse{ + return oapi.CreateVolumeFromArchive500JSONResponse{ Code: "internal_error", Message: "unexpected error processing request", }, nil diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index be37cc7..44fc5ad 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -878,8 +878,8 @@ type StatInstancePathParams struct { FollowLinks *bool `form:"follow_links,omitempty" json:"follow_links,omitempty"` } -// CreateVolumeMultipartBody defines parameters for CreateVolume. -type CreateVolumeMultipartBody struct { +// CreateVolumeFromArchiveMultipartBody defines parameters for CreateVolumeFromArchive. +type CreateVolumeFromArchiveMultipartBody struct { // Content tar.gz archive file containing the volume content Content openapi_types.File `json:"content"` @@ -914,8 +914,8 @@ type AttachVolumeJSONRequestBody = AttachVolumeRequest // CreateVolumeJSONRequestBody defines body for CreateVolume for application/json ContentType. type CreateVolumeJSONRequestBody = CreateVolumeRequest -// CreateVolumeMultipartRequestBody defines body for CreateVolume for multipart/form-data ContentType. -type CreateVolumeMultipartRequestBody CreateVolumeMultipartBody +// CreateVolumeFromArchiveMultipartRequestBody defines body for CreateVolumeFromArchive for multipart/form-data ContentType. +type CreateVolumeFromArchiveMultipartRequestBody CreateVolumeFromArchiveMultipartBody // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1107,6 +1107,9 @@ type ClientInterface interface { CreateVolume(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateVolumeFromArchiveWithBody request with any body + CreateVolumeFromArchiveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteVolume request DeleteVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1606,6 +1609,18 @@ func (c *Client) CreateVolume(ctx context.Context, body CreateVolumeJSONRequestB return c.Client.Do(req) } +func (c *Client) CreateVolumeFromArchiveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateVolumeFromArchiveRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteVolumeRequest(c.Server, id) if err != nil { @@ -2919,6 +2934,35 @@ func NewCreateVolumeRequestWithBody(server string, contentType string, body io.R return req, nil } +// NewCreateVolumeFromArchiveRequestWithBody generates requests for CreateVolumeFromArchive with any type of body +func NewCreateVolumeFromArchiveRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/volumes/from-archive") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewDeleteVolumeRequest generates requests for DeleteVolume func NewDeleteVolumeRequest(server string, id string) (*http.Request, error) { var err error @@ -3147,6 +3191,9 @@ type ClientWithResponsesInterface interface { CreateVolumeWithResponse(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) + // CreateVolumeFromArchiveWithBodyWithResponse request with any body + CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) + // DeleteVolumeWithResponse request DeleteVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteVolumeResponse, error) @@ -4005,6 +4052,32 @@ func (r CreateVolumeResponse) StatusCode() int { return 0 } +type CreateVolumeFromArchiveResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Volume + JSON400 *Error + JSON401 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r CreateVolumeFromArchiveResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateVolumeFromArchiveResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteVolumeResponse struct { Body []byte HTTPResponse *http.Response @@ -4416,6 +4489,15 @@ func (c *ClientWithResponses) CreateVolumeWithResponse(ctx context.Context, body return ParseCreateVolumeResponse(rsp) } +// CreateVolumeFromArchiveWithBodyWithResponse request with arbitrary body returning *CreateVolumeFromArchiveResponse +func (c *ClientWithResponses) CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) { + rsp, err := c.CreateVolumeFromArchiveWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateVolumeFromArchiveResponse(rsp) +} + // DeleteVolumeWithResponse request returning *DeleteVolumeResponse func (c *ClientWithResponses) DeleteVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteVolumeResponse, error) { rsp, err := c.DeleteVolume(ctx, id, reqEditors...) @@ -5911,6 +5993,60 @@ func ParseCreateVolumeResponse(rsp *http.Response) (*CreateVolumeResponse, error return response, nil } +// ParseCreateVolumeFromArchiveResponse parses an HTTP response from a CreateVolumeFromArchiveWithResponse call +func ParseCreateVolumeFromArchiveResponse(rsp *http.Response) (*CreateVolumeFromArchiveResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateVolumeFromArchiveResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Volume + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseDeleteVolumeResponse parses an HTTP response from a DeleteVolumeWithResponse call func ParseDeleteVolumeResponse(rsp *http.Response) (*DeleteVolumeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -6095,9 +6231,12 @@ type ServerInterface interface { // List volumes // (GET /volumes) ListVolumes(w http.ResponseWriter, r *http.Request) - // Create volume + // Create empty volume // (POST /volumes) CreateVolume(w http.ResponseWriter, r *http.Request) + // Create volume from archive + // (POST /volumes/from-archive) + CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) // Delete volume // (DELETE /volumes/{id}) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) @@ -6314,12 +6453,18 @@ func (_ Unimplemented) ListVolumes(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Create volume +// Create empty volume // (POST /volumes) func (_ Unimplemented) CreateVolume(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// Create volume from archive +// (POST /volumes/from-archive) +func (_ Unimplemented) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // Delete volume // (DELETE /volumes/{id}) func (_ Unimplemented) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) { @@ -7337,6 +7482,26 @@ func (siw *ServerInterfaceWrapper) CreateVolume(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } +// CreateVolumeFromArchive operation middleware +func (siw *ServerInterfaceWrapper) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateVolumeFromArchive(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // DeleteVolume operation middleware func (siw *ServerInterfaceWrapper) DeleteVolume(w http.ResponseWriter, r *http.Request) { @@ -7617,6 +7782,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/volumes", wrapper.CreateVolume) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/volumes/from-archive", wrapper.CreateVolumeFromArchive) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/volumes/{id}", wrapper.DeleteVolume) }) @@ -8974,8 +9142,7 @@ func (response ListVolumes500JSONResponse) VisitListVolumesResponse(w http.Respo } type CreateVolumeRequestObject struct { - JSONBody *CreateVolumeJSONRequestBody - MultipartBody *multipart.Reader + Body *CreateVolumeJSONRequestBody } type CreateVolumeResponseObject interface { @@ -9027,6 +9194,59 @@ func (response CreateVolume500JSONResponse) VisitCreateVolumeResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type CreateVolumeFromArchiveRequestObject struct { + Body *multipart.Reader +} + +type CreateVolumeFromArchiveResponseObject interface { + VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error +} + +type CreateVolumeFromArchive201JSONResponse Volume + +func (response CreateVolumeFromArchive201JSONResponse) VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolumeFromArchive400JSONResponse Error + +func (response CreateVolumeFromArchive400JSONResponse) VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolumeFromArchive401JSONResponse Error + +func (response CreateVolumeFromArchive401JSONResponse) VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolumeFromArchive409JSONResponse Error + +func (response CreateVolumeFromArchive409JSONResponse) VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolumeFromArchive500JSONResponse Error + +func (response CreateVolumeFromArchive500JSONResponse) VisitCreateVolumeFromArchiveResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type DeleteVolumeRequestObject struct { Id string `json:"id"` } @@ -9209,9 +9429,12 @@ type StrictServerInterface interface { // List volumes // (GET /volumes) ListVolumes(ctx context.Context, request ListVolumesRequestObject) (ListVolumesResponseObject, error) - // Create volume + // Create empty volume // (POST /volumes) CreateVolume(ctx context.Context, request CreateVolumeRequestObject) (CreateVolumeResponseObject, error) + // Create volume from archive + // (POST /volumes/from-archive) + CreateVolumeFromArchive(ctx context.Context, request CreateVolumeFromArchiveRequestObject) (CreateVolumeFromArchiveResponseObject, error) // Delete volume // (DELETE /volumes/{id}) DeleteVolume(ctx context.Context, request DeleteVolumeRequestObject) (DeleteVolumeResponseObject, error) @@ -10156,23 +10379,12 @@ func (sh *strictHandler) ListVolumes(w http.ResponseWriter, r *http.Request) { func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { var request CreateVolumeRequestObject - if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { - - var body CreateVolumeJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.JSONBody = &body - } - if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { - if reader, err := r.MultipartReader(); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) - return - } else { - request.MultipartBody = reader - } + var body CreateVolumeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.CreateVolume(ctx, request.(CreateVolumeRequestObject)) @@ -10194,6 +10406,37 @@ func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { } } +// CreateVolumeFromArchive operation middleware +func (sh *strictHandler) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { + var request CreateVolumeFromArchiveRequestObject + + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.Body = reader + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateVolumeFromArchive(ctx, request.(CreateVolumeFromArchiveRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateVolumeFromArchive") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateVolumeFromArchiveResponseObject); ok { + if err := validResponse.VisitCreateVolumeFromArchiveResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // DeleteVolume operation middleware func (sh *strictHandler) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) { var request DeleteVolumeRequestObject @@ -10249,166 +10492,167 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "8PHdx/8fAAD//9YX/Kah4AAA", } // GetSwagger returns the content of the embedded swagger specification file From a1bf657305760a5a53652275b075c6fd22504a6b Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:39:39 -0500 Subject: [PATCH 04/10] feat(api): maintain backward compatibility for POST /volumes multipart - Keep multipart/form-data support on POST /volumes (marked as deprecated) - New integrations should use POST /volumes/from-archive instead - Update handler to support both JSON and multipart on /volumes - Regenerate oapi code This ensures existing clients using POST /volumes with multipart continue to work while new clients use the dedicated endpoint. --- cmd/api/api/volumes.go | 175 +++++++++++++++++--- lib/oapi/oapi.go | 367 ++++++++++++++++++++++------------------- openapi.yaml | 32 +++- 3 files changed, 383 insertions(+), 191 deletions(-) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index 7455c48..47e3a1e 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "mime/multipart" "strconv" "github.com/kernel/hypeman/lib/logger" @@ -33,38 +34,170 @@ func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRe return oapi.ListVolumes200JSONResponse(oapiVols), nil } -// CreateVolume creates a new empty volume of the specified size +// CreateVolume creates a new volume +// Supports two modes: +// - JSON body: Creates an empty volume of the specified size +// - Multipart form (DEPRECATED): Creates a volume from a tar.gz archive +// New integrations should use CreateVolumeFromArchive instead func (s *ApiService) CreateVolume(ctx context.Context, request oapi.CreateVolumeRequestObject) (oapi.CreateVolumeResponseObject, error) { log := logger.FromContext(ctx) - if request.Body == nil { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_request", - Message: "request body is required", - }, nil + // Handle JSON request (empty volume) + if request.JSONBody != nil { + domainReq := volumes.CreateVolumeRequest{ + Name: request.JSONBody.Name, + SizeGb: request.JSONBody.SizeGb, + Id: request.JSONBody.Id, + } + + vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) + if err != nil { + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolume409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, nil + } + log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.JSONBody.Name) + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil } - domainReq := volumes.CreateVolumeRequest{ - Name: request.Body.Name, - SizeGb: request.Body.SizeGb, - Id: request.Body.Id, + // Handle multipart request (DEPRECATED - volume with archive content) + if request.MultipartBody != nil { + return s.createVolumeFromMultipartDeprecated(ctx, request.MultipartBody) } - vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) - if err != nil { - if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolume409JSONResponse{ - Code: "already_exists", - Message: "volume with this ID already exists", + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_request", + Message: "request body is required", + }, nil +} + +// createVolumeFromMultipartDeprecated handles the deprecated multipart form on POST /volumes +// New integrations should use POST /volumes/from-archive instead +func (s *ApiService) createVolumeFromMultipartDeprecated(ctx context.Context, multipartReader *multipart.Reader) (oapi.CreateVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + var name string + var sizeGb int + var id *string + var archiveReader io.Reader + + for { + part, err := multipartReader.NextPart() + if err == io.EOF { + break + } + if err != nil { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_form", + Message: "failed to parse multipart form: " + err.Error(), }, nil } - log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.Body.Name) - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", + + switch part.FormName() { + case "name": + data, err := io.ReadAll(part) + if err != nil { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_field", + Message: "failed to read name field", + }, nil + } + name = string(data) + case "size_gb": + data, err := io.ReadAll(part) + if err != nil { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_field", + Message: "failed to read size_gb field", + }, nil + } + sizeGb, err = strconv.Atoi(string(data)) + if err != nil || sizeGb <= 0 { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_field", + Message: "size_gb must be a positive integer", + }, nil + } + case "id": + data, err := io.ReadAll(part) + if err != nil { + return oapi.CreateVolume400JSONResponse{ + Code: "invalid_field", + Message: "failed to read id field", + }, nil + } + idStr := string(data) + if idStr != "" { + id = &idStr + } + case "content": + archiveReader = part + // Process the archive immediately while we have the reader + if name == "" { + return oapi.CreateVolume400JSONResponse{ + Code: "missing_field", + Message: "name is required", + }, nil + } + if sizeGb <= 0 { + return oapi.CreateVolume400JSONResponse{ + Code: "missing_field", + Message: "size_gb is required", + }, nil + } + + // Create the volume from archive + domainReq := volumes.CreateVolumeFromArchiveRequest{ + Name: name, + SizeGb: sizeGb, + Id: id, + } + + vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, archiveReader) + if err != nil { + if errors.Is(err, volumes.ErrArchiveTooLarge) { + return oapi.CreateVolume400JSONResponse{ + Code: "archive_too_large", + Message: err.Error(), + }, nil + } + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolume409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, nil + } + log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", name) + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + + return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + } + } + + // If we get here without processing content, it means content was not provided + if archiveReader == nil { + return oapi.CreateVolume400JSONResponse{ + Code: "missing_file", + Message: "content file is required for multipart requests", }, nil } - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + + // Should not reach here + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "unexpected error processing request", + }, nil } // CreateVolumeFromArchive creates a new volume pre-populated with content from a tar.gz archive diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 44fc5ad..00c371e 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -878,6 +878,21 @@ type StatInstancePathParams struct { FollowLinks *bool `form:"follow_links,omitempty" json:"follow_links,omitempty"` } +// CreateVolumeMultipartBody defines parameters for CreateVolume. +type CreateVolumeMultipartBody struct { + // Content tar.gz archive file containing the volume content (DEPRECATED - use /volumes/from-archive) + Content openapi_types.File `json:"content"` + + // Id Optional custom volume ID (auto-generated if not provided) + Id *string `json:"id,omitempty"` + + // Name Volume name + Name string `json:"name"` + + // SizeGb Maximum size in GB (extraction fails if content exceeds this) + SizeGb int `json:"size_gb"` +} + // CreateVolumeFromArchiveMultipartBody defines parameters for CreateVolumeFromArchive. type CreateVolumeFromArchiveMultipartBody struct { // Content tar.gz archive file containing the volume content @@ -914,6 +929,9 @@ type AttachVolumeJSONRequestBody = AttachVolumeRequest // CreateVolumeJSONRequestBody defines body for CreateVolume for application/json ContentType. type CreateVolumeJSONRequestBody = CreateVolumeRequest +// CreateVolumeMultipartRequestBody defines body for CreateVolume for multipart/form-data ContentType. +type CreateVolumeMultipartRequestBody CreateVolumeMultipartBody + // CreateVolumeFromArchiveMultipartRequestBody defines body for CreateVolumeFromArchive for multipart/form-data ContentType. type CreateVolumeFromArchiveMultipartRequestBody CreateVolumeFromArchiveMultipartBody @@ -6231,7 +6249,7 @@ type ServerInterface interface { // List volumes // (GET /volumes) ListVolumes(w http.ResponseWriter, r *http.Request) - // Create empty volume + // Create volume // (POST /volumes) CreateVolume(w http.ResponseWriter, r *http.Request) // Create volume from archive @@ -6453,7 +6471,7 @@ func (_ Unimplemented) ListVolumes(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Create empty volume +// Create volume // (POST /volumes) func (_ Unimplemented) CreateVolume(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -9142,7 +9160,8 @@ func (response ListVolumes500JSONResponse) VisitListVolumesResponse(w http.Respo } type CreateVolumeRequestObject struct { - Body *CreateVolumeJSONRequestBody + JSONBody *CreateVolumeJSONRequestBody + MultipartBody *multipart.Reader } type CreateVolumeResponseObject interface { @@ -9429,7 +9448,7 @@ type StrictServerInterface interface { // List volumes // (GET /volumes) ListVolumes(ctx context.Context, request ListVolumesRequestObject) (ListVolumesResponseObject, error) - // Create empty volume + // Create volume // (POST /volumes) CreateVolume(ctx context.Context, request CreateVolumeRequestObject) (CreateVolumeResponseObject, error) // Create volume from archive @@ -10379,12 +10398,23 @@ func (sh *strictHandler) ListVolumes(w http.ResponseWriter, r *http.Request) { func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { var request CreateVolumeRequestObject - var body CreateVolumeJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + + var body CreateVolumeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.JSONBody = &body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.MultipartBody = reader + } } - request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.CreateVolume(ctx, request.(CreateVolumeRequestObject)) @@ -10492,167 +10522,168 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "8PHdx/8fAAD//9YX/Kah4AAA", + "E9GY6saNJCN+mMM2jvIPL1OgXNfDoxZTqRLnC1jlLNQEoRptdGezVkr5jXmme1/KTgolLORCKpIYg32S", + "xXCxDaLWbX4AXC7R0UVUSUhW3QWXVak8wzUbk4mWhykRemz9OWQ/K2wPn1l7oXBOvueGBr8PuxYSloIp", + "h1UT1Gp1MNLU5Sr12U55etVPntJzMFSrJUIk6sT0xtS9Q3OJYv2wtdLSNfVDvnT2g0+nrLxCju9Wq8HZ", + "HJn/CBzutMbWXAXIB8fWXpAysTj+AxvtZ2tyLV8TG5ZQdLArlVLsX7MzooRugwVBIY9jyNpv9PftVPBw", + "G8q7hSmNTJ03mBwwvObXCYx4fH4J7UyG9e41038sFxirT9TVKTvdfr3G92dKS/4P1nPMAleRhX/Df7h1", + "Nj8KaKQh2UCiPF2lifP0hyJuK8b+MFsfpNkKZ7H5ajpTgUNQiqWtCew3UW0hre0P5uF03Ym+wuHsyhVj", + "+D60XZu7fd0wboEPgijtmiJibt3fP03yPL3+A71ZpQHnlgBKTDk2wS8FTNmOPxp2f/kwtDIcNwpCu1fa", + "chktvhvaum/JZ+fgblSU4fFQyNxgmlsJ5Bcve59EuQzYStvMlXeCmnS5aumqk3XLRfJMAs3ch1SUZcnr", + "cfWvWV6AzCXw1NZV15lWKKLyxvRgrac+8teJM3aeLRZ3zRRHIY5Dk9Y9L5hmihzKBuvrTamI4Fejt2IQ", + "z0bnleJkXhHsIZkcfpyA3SuXHAOMs+rUyvDvK9vmPoK/rTDbIPTbreBHlGyLwO8SsNoUODHN++giS1Mu", + "lETqlkPFYAnhNpAHdcyjxRDl3zFkisxZFmerg+UlLaBCk/72rFr1pHPy7PzNs+Ojt89Otkq9VRSiegWV", + "/jVD6FISdP764i3KLQPdtmebAH+F8hDg+LkFr9B0hYvcjJyrVF8v6L2ubXQ3LdxSmkt1C6tAQnlVFFvh", + "QW+Hhartogx81IOyFV5gbrUr+OArV5WXkAkzqXjiJnB6gjo4U7w3JUzvQlGtJRV8TqN60c/vpFLfGb6j", + "SZbkZa5fPIWiwcJEekH5eIgzdPAldyEhkYTAr60Nq/otF/Sz+/5pJS++HIN0nLpRX/1x6fjeVOLizvHp", + "Se3G8QO8yDF3mFXoJxU+VHaLrpdeKBWk50oLRQZUjjCbxMrbGcn5J5XI0jYcFkMNJTfHP0skOFcoooKE", + "iovFOpHyXPDkyK7iCxbx+nKy4AeL/8HiHwSLL9JP6y1GXORIrjhHMRZTsvVDDDx8MWB5tOWZFZnQ7jpf", + "O/doS6/l17jKl7vO7/ci39X349Erpet9gNkk5rnLoukG4feFgoP7kw/3fXPw6gGfAL0gzj1TujUIHege", + "fQjzkoc4RhGZk5inUKfctA26QSZiW3V5uL0d63YzLtXwcHA4CD6++/j/AwAA//9D9mcxneMAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 98e0d12..fa230da 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1805,8 +1805,12 @@ paths: schema: $ref: "#/components/schemas/Error" post: - summary: Create empty volume - description: Creates a new empty volume of the specified size. + summary: Create volume + description: | + Creates a new volume. Supports two modes: + - JSON body: Creates an empty volume of the specified size + - Multipart form (DEPRECATED): Creates a volume from a tar.gz archive. + Use POST /volumes/from-archive instead for new integrations. operationId: createVolume security: - bearerAuth: [] @@ -1816,6 +1820,30 @@ paths: application/json: schema: $ref: "#/components/schemas/CreateVolumeRequest" + multipart/form-data: + schema: + type: object + required: + - name + - size_gb + - content + properties: + name: + type: string + description: Volume name + example: my-data-volume + size_gb: + type: integer + description: Maximum size in GB (extraction fails if content exceeds this) + example: 10 + id: + type: string + description: Optional custom volume ID (auto-generated if not provided) + example: vol-data-1 + content: + type: string + format: binary + description: tar.gz archive file containing the volume content (DEPRECATED - use /volumes/from-archive) responses: 201: description: Volume created From c0c317a8edfad8edc7df4de5b6e83f242be7e040 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:14:02 -0500 Subject: [PATCH 05/10] fix(api): handle multipart form fields in any order Buffer the content field to a temp file during parsing to decouple field order from validation. This fixes misleading "name is required" errors when clients send content before other form fields. The multipart/form-data spec doesn't guarantee field ordering, so servers should handle fields in any order. The previous implementation required name and size_gb to arrive before content. Changes: - Add parseVolumeMultipartForm helper that buffers content to temp file - Validate required fields after all fields are parsed - Clean up temp files via defer form.Close() - Apply fix to both CreateVolumeFromArchive and deprecated POST /volumes --- cmd/api/api/volumes.go | 428 +++++++++++++++++++++-------------------- 1 file changed, 222 insertions(+), 206 deletions(-) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index 47e3a1e..8047683 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -5,6 +5,7 @@ import ( "errors" "io" "mime/multipart" + "os" "strconv" "github.com/kernel/hypeman/lib/logger" @@ -13,6 +14,107 @@ import ( "github.com/kernel/hypeman/lib/volumes" ) +// volumeMultipartForm holds parsed multipart form data for volume creation +type volumeMultipartForm struct { + Name string + SizeGb int + ID *string + ContentFile *os.File // Temp file containing the archive content +} + +// Close cleans up any temp files +func (f *volumeMultipartForm) Close() { + if f.ContentFile != nil { + f.ContentFile.Close() + os.Remove(f.ContentFile.Name()) + } +} + +// parseVolumeMultipartForm parses a multipart form for volume creation. +// It buffers the content field to a temp file to handle any field order. +// Caller must call form.Close() to clean up temp files. +func parseVolumeMultipartForm(multipartReader *multipart.Reader) (*volumeMultipartForm, error) { + form := &volumeMultipartForm{} + + for { + part, err := multipartReader.NextPart() + if err == io.EOF { + break + } + if err != nil { + form.Close() + return nil, &formError{Code: "invalid_form", Message: "failed to parse multipart form: " + err.Error()} + } + + switch part.FormName() { + case "name": + data, err := io.ReadAll(part) + if err != nil { + form.Close() + return nil, &formError{Code: "invalid_field", Message: "failed to read name field"} + } + form.Name = string(data) + case "size_gb": + data, err := io.ReadAll(part) + if err != nil { + form.Close() + return nil, &formError{Code: "invalid_field", Message: "failed to read size_gb field"} + } + sizeGb, err := strconv.Atoi(string(data)) + if err != nil || sizeGb <= 0 { + form.Close() + return nil, &formError{Code: "invalid_field", Message: "size_gb must be a positive integer"} + } + form.SizeGb = sizeGb + case "id": + data, err := io.ReadAll(part) + if err != nil { + form.Close() + return nil, &formError{Code: "invalid_field", Message: "failed to read id field"} + } + idStr := string(data) + if idStr != "" { + form.ID = &idStr + } + case "content": + // Buffer content to a temp file to handle any field order + tempFile, err := os.CreateTemp("", "volume-archive-*.tar.gz") + if err != nil { + form.Close() + return nil, &formError{Code: "internal_error", Message: "failed to create temp file"} + } + _, err = io.Copy(tempFile, part) + if err != nil { + tempFile.Close() + os.Remove(tempFile.Name()) + form.Close() + return nil, &formError{Code: "invalid_field", Message: "failed to read content field"} + } + // Seek back to beginning for reading + _, err = tempFile.Seek(0, 0) + if err != nil { + tempFile.Close() + os.Remove(tempFile.Name()) + form.Close() + return nil, &formError{Code: "internal_error", Message: "failed to seek temp file"} + } + form.ContentFile = tempFile + } + } + + return form, nil +} + +// formError represents a form parsing error +type formError struct { + Code string + Message string +} + +func (e *formError) Error() string { + return e.Message +} + // ListVolumes lists all volumes func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRequestObject) (oapi.ListVolumesResponseObject, error) { log := logger.FromContext(ctx) @@ -83,121 +185,78 @@ func (s *ApiService) CreateVolume(ctx context.Context, request oapi.CreateVolume func (s *ApiService) createVolumeFromMultipartDeprecated(ctx context.Context, multipartReader *multipart.Reader) (oapi.CreateVolumeResponseObject, error) { log := logger.FromContext(ctx) - var name string - var sizeGb int - var id *string - var archiveReader io.Reader - - for { - part, err := multipartReader.NextPart() - if err == io.EOF { - break - } - if err != nil { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_form", - Message: "failed to parse multipart form: " + err.Error(), - }, nil - } - - switch part.FormName() { - case "name": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_field", - Message: "failed to read name field", - }, nil - } - name = string(data) - case "size_gb": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_field", - Message: "failed to read size_gb field", - }, nil - } - sizeGb, err = strconv.Atoi(string(data)) - if err != nil || sizeGb <= 0 { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_field", - Message: "size_gb must be a positive integer", - }, nil - } - case "id": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_field", - Message: "failed to read id field", - }, nil - } - idStr := string(data) - if idStr != "" { - id = &idStr - } - case "content": - archiveReader = part - // Process the archive immediately while we have the reader - if name == "" { - return oapi.CreateVolume400JSONResponse{ - Code: "missing_field", - Message: "name is required", - }, nil - } - if sizeGb <= 0 { - return oapi.CreateVolume400JSONResponse{ - Code: "missing_field", - Message: "size_gb is required", - }, nil - } - - // Create the volume from archive - domainReq := volumes.CreateVolumeFromArchiveRequest{ - Name: name, - SizeGb: sizeGb, - Id: id, - } - - vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, archiveReader) - if err != nil { - if errors.Is(err, volumes.ErrArchiveTooLarge) { - return oapi.CreateVolume400JSONResponse{ - Code: "archive_too_large", - Message: err.Error(), - }, nil - } - if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolume409JSONResponse{ - Code: "already_exists", - Message: "volume with this ID already exists", - }, nil - } - log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", name) + // Parse the multipart form (handles any field order via temp file buffering) + form, err := parseVolumeMultipartForm(multipartReader) + if err != nil { + var formErr *formError + if errors.As(err, &formErr) { + if formErr.Code == "internal_error" { return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", + Code: formErr.Code, + Message: formErr.Message, }, nil } - - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + return oapi.CreateVolume400JSONResponse{ + Code: formErr.Code, + Message: formErr.Message, + }, nil } + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to parse form", + }, nil } + defer form.Close() - // If we get here without processing content, it means content was not provided - if archiveReader == nil { + // Validate required fields after parsing all fields + if form.Name == "" { + return oapi.CreateVolume400JSONResponse{ + Code: "missing_field", + Message: "name is required", + }, nil + } + if form.SizeGb <= 0 { + return oapi.CreateVolume400JSONResponse{ + Code: "missing_field", + Message: "size_gb is required", + }, nil + } + if form.ContentFile == nil { return oapi.CreateVolume400JSONResponse{ Code: "missing_file", Message: "content file is required for multipart requests", }, nil } - // Should not reach here - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "unexpected error processing request", - }, nil + // Create the volume from archive + domainReq := volumes.CreateVolumeFromArchiveRequest{ + Name: form.Name, + SizeGb: form.SizeGb, + Id: form.ID, + } + + vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, form.ContentFile) + if err != nil { + if errors.Is(err, volumes.ErrArchiveTooLarge) { + return oapi.CreateVolume400JSONResponse{ + Code: "archive_too_large", + Message: err.Error(), + }, nil + } + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolume409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, nil + } + log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", form.Name) + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + + return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil } // CreateVolumeFromArchive creates a new volume pre-populated with content from a tar.gz archive @@ -211,121 +270,78 @@ func (s *ApiService) CreateVolumeFromArchive(ctx context.Context, request oapi.C }, nil } - var name string - var sizeGb int - var id *string - var archiveReader io.Reader - - for { - part, err := request.Body.NextPart() - if err == io.EOF { - break - } - if err != nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_form", - Message: "failed to parse multipart form: " + err.Error(), - }, nil - } - - switch part.FormName() { - case "name": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_field", - Message: "failed to read name field", - }, nil - } - name = string(data) - case "size_gb": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_field", - Message: "failed to read size_gb field", - }, nil - } - sizeGb, err = strconv.Atoi(string(data)) - if err != nil || sizeGb <= 0 { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_field", - Message: "size_gb must be a positive integer", - }, nil - } - case "id": - data, err := io.ReadAll(part) - if err != nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_field", - Message: "failed to read id field", - }, nil - } - idStr := string(data) - if idStr != "" { - id = &idStr - } - case "content": - archiveReader = part - // Process the archive immediately while we have the reader - if name == "" { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "missing_field", - Message: "name is required", - }, nil - } - if sizeGb <= 0 { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "missing_field", - Message: "size_gb is required", - }, nil - } - - // Create the volume from archive - domainReq := volumes.CreateVolumeFromArchiveRequest{ - Name: name, - SizeGb: sizeGb, - Id: id, - } - - vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, archiveReader) - if err != nil { - if errors.Is(err, volumes.ErrArchiveTooLarge) { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "archive_too_large", - Message: err.Error(), - }, nil - } - if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolumeFromArchive409JSONResponse{ - Code: "already_exists", - Message: "volume with this ID already exists", - }, nil - } - log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", name) + // Parse the multipart form (handles any field order via temp file buffering) + form, err := parseVolumeMultipartForm(request.Body) + if err != nil { + var formErr *formError + if errors.As(err, &formErr) { + if formErr.Code == "internal_error" { return oapi.CreateVolumeFromArchive500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", + Code: formErr.Code, + Message: formErr.Message, }, nil } - - return oapi.CreateVolumeFromArchive201JSONResponse(volumeToOAPI(*vol)), nil + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: formErr.Code, + Message: formErr.Message, + }, nil } + return oapi.CreateVolumeFromArchive500JSONResponse{ + Code: "internal_error", + Message: "failed to parse form", + }, nil } + defer form.Close() - // If we get here without processing content, it means content was not provided - if archiveReader == nil { + // Validate required fields after parsing all fields + if form.Name == "" { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "missing_field", + Message: "name is required", + }, nil + } + if form.SizeGb <= 0 { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "missing_field", + Message: "size_gb is required", + }, nil + } + if form.ContentFile == nil { return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "missing_file", Message: "content file is required", }, nil } - // Should not reach here - return oapi.CreateVolumeFromArchive500JSONResponse{ - Code: "internal_error", - Message: "unexpected error processing request", - }, nil + // Create the volume from archive + domainReq := volumes.CreateVolumeFromArchiveRequest{ + Name: form.Name, + SizeGb: form.SizeGb, + Id: form.ID, + } + + vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, form.ContentFile) + if err != nil { + if errors.Is(err, volumes.ErrArchiveTooLarge) { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "archive_too_large", + Message: err.Error(), + }, nil + } + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolumeFromArchive409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, nil + } + log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", form.Name) + return oapi.CreateVolumeFromArchive500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + + return oapi.CreateVolumeFromArchive201JSONResponse(volumeToOAPI(*vol)), nil } // GetVolume gets volume details From 2a2072798f2def3884c16ddf56cc115200d78e23 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:50:51 -0500 Subject: [PATCH 06/10] fix(api): reject duplicate content fields in multipart form Prevent temp file leaks by checking if ContentFile is already set before creating a new temp file. If a client sends multiple content fields, the request is rejected with a clear error message. --- cmd/api/api/volumes.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index 8047683..4f2b2cb 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -77,6 +77,11 @@ func parseVolumeMultipartForm(multipartReader *multipart.Reader) (*volumeMultipa form.ID = &idStr } case "content": + // Reject duplicate content fields to prevent temp file leaks + if form.ContentFile != nil { + form.Close() + return nil, &formError{Code: "invalid_form", Message: "duplicate content field"} + } // Buffer content to a temp file to handle any field order tempFile, err := os.CreateTemp("", "volume-archive-*.tar.gz") if err != nil { From dcb49fb213ee184581d15cdb86095e4fbde62595 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:08:23 -0500 Subject: [PATCH 07/10] fix(stainless): correct Go SDK repo path from onkernel to kernel The Stainless config referenced onkernel/hypeman-go but the actual SDK repo is kernel/hypeman-go. This caused mixed import paths in the generated code (some imports used onkernel, others used kernel). --- stainless.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stainless.yaml b/stainless.yaml index bc609a4..dde042e 100644 --- a/stainless.yaml +++ b/stainless.yaml @@ -21,15 +21,15 @@ targets: # [docs]: https://www.stainless.com/docs/reference/editions edition: go.2025-10-08 package_name: hypeman - production_repo: onkernel/hypeman-go + production_repo: kernel/hypeman-go options: enable_v2: true cli: edition: cli.2025-10-08 binary_name: hypeman - production_repo: onkernel/hypeman-cli + production_repo: kernel/hypeman-cli options: - go_sdk_package: github.com/onkernel/hypeman-go + go_sdk_package: github.com/kernel/hypeman-go publish: homebrew: tap_repo: kernel/homebrew-tap From 2b578ba4cf3421447b6bb07a929bba96ff231baa Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:06:48 -0500 Subject: [PATCH 08/10] refactor(api): use query params for volume-from-archive endpoint BREAKING CHANGE: Remove deprecated multipart support from POST /volumes Changes: - POST /volumes now only accepts JSON body (empty volumes) - POST /volumes/from-archive uses query params (name, size_gb, id) and application/gzip body for direct streaming - Remove temp file buffering - archive streams directly to volume - Simplify handler code significantly This approach: - Avoids multipart field ordering issues entirely - Enables direct streaming (better performance) - Cleaner API design per reviewer feedback --- cmd/api/api/volumes.go | 280 +++------------------- lib/oapi/oapi.go | 510 ++++++++++++++++++++++------------------- openapi.yaml | 83 +++---- 3 files changed, 335 insertions(+), 538 deletions(-) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index 4f2b2cb..f6f4bcb 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -3,10 +3,6 @@ package api import ( "context" "errors" - "io" - "mime/multipart" - "os" - "strconv" "github.com/kernel/hypeman/lib/logger" mw "github.com/kernel/hypeman/lib/middleware" @@ -14,112 +10,6 @@ import ( "github.com/kernel/hypeman/lib/volumes" ) -// volumeMultipartForm holds parsed multipart form data for volume creation -type volumeMultipartForm struct { - Name string - SizeGb int - ID *string - ContentFile *os.File // Temp file containing the archive content -} - -// Close cleans up any temp files -func (f *volumeMultipartForm) Close() { - if f.ContentFile != nil { - f.ContentFile.Close() - os.Remove(f.ContentFile.Name()) - } -} - -// parseVolumeMultipartForm parses a multipart form for volume creation. -// It buffers the content field to a temp file to handle any field order. -// Caller must call form.Close() to clean up temp files. -func parseVolumeMultipartForm(multipartReader *multipart.Reader) (*volumeMultipartForm, error) { - form := &volumeMultipartForm{} - - for { - part, err := multipartReader.NextPart() - if err == io.EOF { - break - } - if err != nil { - form.Close() - return nil, &formError{Code: "invalid_form", Message: "failed to parse multipart form: " + err.Error()} - } - - switch part.FormName() { - case "name": - data, err := io.ReadAll(part) - if err != nil { - form.Close() - return nil, &formError{Code: "invalid_field", Message: "failed to read name field"} - } - form.Name = string(data) - case "size_gb": - data, err := io.ReadAll(part) - if err != nil { - form.Close() - return nil, &formError{Code: "invalid_field", Message: "failed to read size_gb field"} - } - sizeGb, err := strconv.Atoi(string(data)) - if err != nil || sizeGb <= 0 { - form.Close() - return nil, &formError{Code: "invalid_field", Message: "size_gb must be a positive integer"} - } - form.SizeGb = sizeGb - case "id": - data, err := io.ReadAll(part) - if err != nil { - form.Close() - return nil, &formError{Code: "invalid_field", Message: "failed to read id field"} - } - idStr := string(data) - if idStr != "" { - form.ID = &idStr - } - case "content": - // Reject duplicate content fields to prevent temp file leaks - if form.ContentFile != nil { - form.Close() - return nil, &formError{Code: "invalid_form", Message: "duplicate content field"} - } - // Buffer content to a temp file to handle any field order - tempFile, err := os.CreateTemp("", "volume-archive-*.tar.gz") - if err != nil { - form.Close() - return nil, &formError{Code: "internal_error", Message: "failed to create temp file"} - } - _, err = io.Copy(tempFile, part) - if err != nil { - tempFile.Close() - os.Remove(tempFile.Name()) - form.Close() - return nil, &formError{Code: "invalid_field", Message: "failed to read content field"} - } - // Seek back to beginning for reading - _, err = tempFile.Seek(0, 0) - if err != nil { - tempFile.Close() - os.Remove(tempFile.Name()) - form.Close() - return nil, &formError{Code: "internal_error", Message: "failed to seek temp file"} - } - form.ContentFile = tempFile - } - } - - return form, nil -} - -// formError represents a form parsing error -type formError struct { - Code string - Message string -} - -func (e *formError) Error() string { - return e.Message -} - // ListVolumes lists all volumes func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRequestObject) (oapi.ListVolumesResponseObject, error) { log := logger.FromContext(ctx) @@ -141,191 +31,73 @@ func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRe return oapi.ListVolumes200JSONResponse(oapiVols), nil } -// CreateVolume creates a new volume -// Supports two modes: -// - JSON body: Creates an empty volume of the specified size -// - Multipart form (DEPRECATED): Creates a volume from a tar.gz archive -// New integrations should use CreateVolumeFromArchive instead +// CreateVolume creates a new empty volume of the specified size func (s *ApiService) CreateVolume(ctx context.Context, request oapi.CreateVolumeRequestObject) (oapi.CreateVolumeResponseObject, error) { log := logger.FromContext(ctx) - // Handle JSON request (empty volume) - if request.JSONBody != nil { - domainReq := volumes.CreateVolumeRequest{ - Name: request.JSONBody.Name, - SizeGb: request.JSONBody.SizeGb, - Id: request.JSONBody.Id, - } - - vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) - if err != nil { - if errors.Is(err, volumes.ErrAlreadyExists) { - return oapi.CreateVolume409JSONResponse{ - Code: "already_exists", - Message: "volume with this ID already exists", - }, nil - } - log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.JSONBody.Name) - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", - }, nil - } - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil - } - - // Handle multipart request (DEPRECATED - volume with archive content) - if request.MultipartBody != nil { - return s.createVolumeFromMultipartDeprecated(ctx, request.MultipartBody) - } - - return oapi.CreateVolume400JSONResponse{ - Code: "invalid_request", - Message: "request body is required", - }, nil -} - -// createVolumeFromMultipartDeprecated handles the deprecated multipart form on POST /volumes -// New integrations should use POST /volumes/from-archive instead -func (s *ApiService) createVolumeFromMultipartDeprecated(ctx context.Context, multipartReader *multipart.Reader) (oapi.CreateVolumeResponseObject, error) { - log := logger.FromContext(ctx) - - // Parse the multipart form (handles any field order via temp file buffering) - form, err := parseVolumeMultipartForm(multipartReader) - if err != nil { - var formErr *formError - if errors.As(err, &formErr) { - if formErr.Code == "internal_error" { - return oapi.CreateVolume500JSONResponse{ - Code: formErr.Code, - Message: formErr.Message, - }, nil - } - return oapi.CreateVolume400JSONResponse{ - Code: formErr.Code, - Message: formErr.Message, - }, nil - } - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to parse form", - }, nil - } - defer form.Close() - - // Validate required fields after parsing all fields - if form.Name == "" { - return oapi.CreateVolume400JSONResponse{ - Code: "missing_field", - Message: "name is required", - }, nil - } - if form.SizeGb <= 0 { - return oapi.CreateVolume400JSONResponse{ - Code: "missing_field", - Message: "size_gb is required", - }, nil - } - if form.ContentFile == nil { + if request.Body == nil { return oapi.CreateVolume400JSONResponse{ - Code: "missing_file", - Message: "content file is required for multipart requests", + Code: "invalid_request", + Message: "request body is required", }, nil } - // Create the volume from archive - domainReq := volumes.CreateVolumeFromArchiveRequest{ - Name: form.Name, - SizeGb: form.SizeGb, - Id: form.ID, + domainReq := volumes.CreateVolumeRequest{ + Name: request.Body.Name, + SizeGb: request.Body.SizeGb, + Id: request.Body.Id, } - vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, form.ContentFile) + vol, err := s.VolumeManager.CreateVolume(ctx, domainReq) if err != nil { - if errors.Is(err, volumes.ErrArchiveTooLarge) { - return oapi.CreateVolume400JSONResponse{ - Code: "archive_too_large", - Message: err.Error(), - }, nil - } if errors.Is(err, volumes.ErrAlreadyExists) { return oapi.CreateVolume409JSONResponse{ Code: "already_exists", Message: "volume with this ID already exists", }, nil } - log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", form.Name) + log.ErrorContext(ctx, "failed to create volume", "error", err, "name", request.Body.Name) return oapi.CreateVolume500JSONResponse{ Code: "internal_error", Message: "failed to create volume", }, nil } - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil } // CreateVolumeFromArchive creates a new volume pre-populated with content from a tar.gz archive +// The archive is streamed directly into the volume without intermediate buffering func (s *ApiService) CreateVolumeFromArchive(ctx context.Context, request oapi.CreateVolumeFromArchiveRequestObject) (oapi.CreateVolumeFromArchiveResponseObject, error) { log := logger.FromContext(ctx) - if request.Body == nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "invalid_request", - Message: "multipart body is required", - }, nil - } - - // Parse the multipart form (handles any field order via temp file buffering) - form, err := parseVolumeMultipartForm(request.Body) - if err != nil { - var formErr *formError - if errors.As(err, &formErr) { - if formErr.Code == "internal_error" { - return oapi.CreateVolumeFromArchive500JSONResponse{ - Code: formErr.Code, - Message: formErr.Message, - }, nil - } - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: formErr.Code, - Message: formErr.Message, - }, nil - } - return oapi.CreateVolumeFromArchive500JSONResponse{ - Code: "internal_error", - Message: "failed to parse form", - }, nil - } - defer form.Close() - - // Validate required fields after parsing all fields - if form.Name == "" { + // Validate required parameters + if request.Params.Name == "" { return oapi.CreateVolumeFromArchive400JSONResponse{ Code: "missing_field", - Message: "name is required", + Message: "name query parameter is required", }, nil } - if form.SizeGb <= 0 { + if request.Params.SizeGb <= 0 { return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "missing_field", - Message: "size_gb is required", + Code: "invalid_field", + Message: "size_gb must be a positive integer", }, nil } - if form.ContentFile == nil { + if request.Body == nil { return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "missing_file", - Message: "content file is required", + Code: "missing_body", + Message: "request body (tar.gz archive) is required", }, nil } - // Create the volume from archive + // Create the volume from archive - stream directly without buffering domainReq := volumes.CreateVolumeFromArchiveRequest{ - Name: form.Name, - SizeGb: form.SizeGb, - Id: form.ID, + Name: request.Params.Name, + SizeGb: request.Params.SizeGb, + Id: request.Params.Id, } - vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, form.ContentFile) + vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, request.Body) if err != nil { if errors.Is(err, volumes.ErrArchiveTooLarge) { return oapi.CreateVolumeFromArchive400JSONResponse{ @@ -339,7 +111,7 @@ func (s *ApiService) CreateVolumeFromArchive(ctx context.Context, request oapi.C Message: "volume with this ID already exists", }, nil } - log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", form.Name) + log.ErrorContext(ctx, "failed to create volume from archive", "error", err, "name", request.Params.Name) return oapi.CreateVolumeFromArchive500JSONResponse{ Code: "internal_error", Message: "failed to create volume", diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 00c371e..a5f6066 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -878,34 +878,16 @@ type StatInstancePathParams struct { FollowLinks *bool `form:"follow_links,omitempty" json:"follow_links,omitempty"` } -// CreateVolumeMultipartBody defines parameters for CreateVolume. -type CreateVolumeMultipartBody struct { - // Content tar.gz archive file containing the volume content (DEPRECATED - use /volumes/from-archive) - Content openapi_types.File `json:"content"` - - // Id Optional custom volume ID (auto-generated if not provided) - Id *string `json:"id,omitempty"` - +// CreateVolumeFromArchiveParams defines parameters for CreateVolumeFromArchive. +type CreateVolumeFromArchiveParams struct { // Name Volume name - Name string `json:"name"` + Name string `form:"name" json:"name"` // SizeGb Maximum size in GB (extraction fails if content exceeds this) - SizeGb int `json:"size_gb"` -} - -// CreateVolumeFromArchiveMultipartBody defines parameters for CreateVolumeFromArchive. -type CreateVolumeFromArchiveMultipartBody struct { - // Content tar.gz archive file containing the volume content - Content openapi_types.File `json:"content"` + SizeGb int `form:"size_gb" json:"size_gb"` // Id Optional custom volume ID (auto-generated if not provided) - Id *string `json:"id,omitempty"` - - // Name Volume name - Name string `json:"name"` - - // SizeGb Maximum size in GB (extraction fails if content exceeds this) - SizeGb int `json:"size_gb"` + Id *string `form:"id,omitempty" json:"id,omitempty"` } // CreateBuildMultipartRequestBody defines body for CreateBuild for multipart/form-data ContentType. @@ -929,12 +911,6 @@ type AttachVolumeJSONRequestBody = AttachVolumeRequest // CreateVolumeJSONRequestBody defines body for CreateVolume for application/json ContentType. type CreateVolumeJSONRequestBody = CreateVolumeRequest -// CreateVolumeMultipartRequestBody defines body for CreateVolume for multipart/form-data ContentType. -type CreateVolumeMultipartRequestBody CreateVolumeMultipartBody - -// CreateVolumeFromArchiveMultipartRequestBody defines body for CreateVolumeFromArchive for multipart/form-data ContentType. -type CreateVolumeFromArchiveMultipartRequestBody CreateVolumeFromArchiveMultipartBody - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1126,7 +1102,7 @@ type ClientInterface interface { CreateVolume(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateVolumeFromArchiveWithBody request with any body - CreateVolumeFromArchiveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateVolumeFromArchiveWithBody(ctx context.Context, params *CreateVolumeFromArchiveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteVolume request DeleteVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1627,8 +1603,8 @@ func (c *Client) CreateVolume(ctx context.Context, body CreateVolumeJSONRequestB return c.Client.Do(req) } -func (c *Client) CreateVolumeFromArchiveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateVolumeFromArchiveRequestWithBody(c.Server, contentType, body) +func (c *Client) CreateVolumeFromArchiveWithBody(ctx context.Context, params *CreateVolumeFromArchiveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateVolumeFromArchiveRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -2953,7 +2929,7 @@ func NewCreateVolumeRequestWithBody(server string, contentType string, body io.R } // NewCreateVolumeFromArchiveRequestWithBody generates requests for CreateVolumeFromArchive with any type of body -func NewCreateVolumeFromArchiveRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateVolumeFromArchiveRequestWithBody(server string, params *CreateVolumeFromArchiveParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -2971,6 +2947,52 @@ func NewCreateVolumeFromArchiveRequestWithBody(server string, contentType string return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "size_gb", runtime.ParamLocationQuery, params.SizeGb); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -3210,7 +3232,7 @@ type ClientWithResponsesInterface interface { CreateVolumeWithResponse(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) // CreateVolumeFromArchiveWithBodyWithResponse request with any body - CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) + CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, params *CreateVolumeFromArchiveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) // DeleteVolumeWithResponse request DeleteVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteVolumeResponse, error) @@ -4508,8 +4530,8 @@ func (c *ClientWithResponses) CreateVolumeWithResponse(ctx context.Context, body } // CreateVolumeFromArchiveWithBodyWithResponse request with arbitrary body returning *CreateVolumeFromArchiveResponse -func (c *ClientWithResponses) CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) { - rsp, err := c.CreateVolumeFromArchiveWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateVolumeFromArchiveWithBodyWithResponse(ctx context.Context, params *CreateVolumeFromArchiveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeFromArchiveResponse, error) { + rsp, err := c.CreateVolumeFromArchiveWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } @@ -6249,12 +6271,12 @@ type ServerInterface interface { // List volumes // (GET /volumes) ListVolumes(w http.ResponseWriter, r *http.Request) - // Create volume + // Create empty volume // (POST /volumes) CreateVolume(w http.ResponseWriter, r *http.Request) // Create volume from archive // (POST /volumes/from-archive) - CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) + CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request, params CreateVolumeFromArchiveParams) // Delete volume // (DELETE /volumes/{id}) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) @@ -6471,7 +6493,7 @@ func (_ Unimplemented) ListVolumes(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Create volume +// Create empty volume // (POST /volumes) func (_ Unimplemented) CreateVolume(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -6479,7 +6501,7 @@ func (_ Unimplemented) CreateVolume(w http.ResponseWriter, r *http.Request) { // Create volume from archive // (POST /volumes/from-archive) -func (_ Unimplemented) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request, params CreateVolumeFromArchiveParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -7503,14 +7525,57 @@ func (siw *ServerInterfaceWrapper) CreateVolume(w http.ResponseWriter, r *http.R // CreateVolumeFromArchive operation middleware func (siw *ServerInterfaceWrapper) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { + var err error + ctx := r.Context() ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) r = r.WithContext(ctx) + // Parameter object where we will unmarshal all parameters from the context + var params CreateVolumeFromArchiveParams + + // ------------- Required query parameter "name" ------------- + + if paramValue := r.URL.Query().Get("name"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + // ------------- Required query parameter "size_gb" ------------- + + if paramValue := r.URL.Query().Get("size_gb"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "size_gb"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "size_gb", r.URL.Query(), ¶ms.SizeGb) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "size_gb", Err: err}) + return + } + + // ------------- Optional query parameter "id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateVolumeFromArchive(w, r) + siw.Handler.CreateVolumeFromArchive(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -9160,8 +9225,7 @@ func (response ListVolumes500JSONResponse) VisitListVolumesResponse(w http.Respo } type CreateVolumeRequestObject struct { - JSONBody *CreateVolumeJSONRequestBody - MultipartBody *multipart.Reader + Body *CreateVolumeJSONRequestBody } type CreateVolumeResponseObject interface { @@ -9214,7 +9278,8 @@ func (response CreateVolume500JSONResponse) VisitCreateVolumeResponse(w http.Res } type CreateVolumeFromArchiveRequestObject struct { - Body *multipart.Reader + Params CreateVolumeFromArchiveParams + Body io.Reader } type CreateVolumeFromArchiveResponseObject interface { @@ -9448,7 +9513,7 @@ type StrictServerInterface interface { // List volumes // (GET /volumes) ListVolumes(ctx context.Context, request ListVolumesRequestObject) (ListVolumesResponseObject, error) - // Create volume + // Create empty volume // (POST /volumes) CreateVolume(ctx context.Context, request CreateVolumeRequestObject) (CreateVolumeResponseObject, error) // Create volume from archive @@ -10398,23 +10463,12 @@ func (sh *strictHandler) ListVolumes(w http.ResponseWriter, r *http.Request) { func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { var request CreateVolumeRequestObject - if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { - - var body CreateVolumeJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.JSONBody = &body - } - if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { - if reader, err := r.MultipartReader(); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) - return - } else { - request.MultipartBody = reader - } + var body CreateVolumeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.CreateVolume(ctx, request.(CreateVolumeRequestObject)) @@ -10437,15 +10491,12 @@ func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { } // CreateVolumeFromArchive operation middleware -func (sh *strictHandler) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request) { +func (sh *strictHandler) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request, params CreateVolumeFromArchiveParams) { var request CreateVolumeFromArchiveRequestObject - if reader, err := r.MultipartReader(); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) - return - } else { - request.Body = reader - } + request.Params = params + + request.Body = r.Body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.CreateVolumeFromArchive(ctx, request.(CreateVolumeFromArchiveRequestObject)) @@ -10522,168 +10573,167 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index fa230da..c53992f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1805,12 +1805,8 @@ paths: schema: $ref: "#/components/schemas/Error" post: - summary: Create volume - description: | - Creates a new volume. Supports two modes: - - JSON body: Creates an empty volume of the specified size - - Multipart form (DEPRECATED): Creates a volume from a tar.gz archive. - Use POST /volumes/from-archive instead for new integrations. + summary: Create empty volume + description: Creates a new empty volume of the specified size. operationId: createVolume security: - bearerAuth: [] @@ -1820,30 +1816,6 @@ paths: application/json: schema: $ref: "#/components/schemas/CreateVolumeRequest" - multipart/form-data: - schema: - type: object - required: - - name - - size_gb - - content - properties: - name: - type: string - description: Volume name - example: my-data-volume - size_gb: - type: integer - description: Maximum size in GB (extraction fails if content exceeds this) - example: 10 - id: - type: string - description: Optional custom volume ID (auto-generated if not provided) - example: vol-data-1 - content: - type: string - format: binary - description: tar.gz archive file containing the volume content (DEPRECATED - use /volumes/from-archive) responses: 201: description: Volume created @@ -1881,37 +1853,40 @@ paths: summary: Create volume from archive description: | Creates a new volume pre-populated with content from a tar.gz archive. - The archive is extracted into the volume's root directory. + The archive is streamed directly into the volume's root directory. operationId: createVolumeFromArchive security: - bearerAuth: [] + parameters: + - name: name + in: query + required: true + schema: + type: string + description: Volume name + example: my-data-volume + - name: size_gb + in: query + required: true + schema: + type: integer + description: Maximum size in GB (extraction fails if content exceeds this) + example: 10 + - name: id + in: query + required: false + schema: + type: string + description: Optional custom volume ID (auto-generated if not provided) + example: vol-data-1 requestBody: required: true + description: tar.gz archive file containing the volume content content: - multipart/form-data: + application/gzip: schema: - type: object - required: - - name - - size_gb - - content - properties: - name: - type: string - description: Volume name - example: my-data-volume - size_gb: - type: integer - description: Maximum size in GB (extraction fails if content exceeds this) - example: 10 - id: - type: string - description: Optional custom volume ID (auto-generated if not provided) - example: vol-data-1 - content: - type: string - format: binary - description: tar.gz archive file containing the volume content + type: string + format: binary responses: 201: description: Volume created From 20f5fcf2a413b20c1a3512b371c20a5a943308be Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:19:23 -0500 Subject: [PATCH 09/10] fix(api): remove dead code nil check on request.Body In Go's net/http, r.Body is never nil - empty requests use http.NoBody. The nil check was unreachable dead code. Empty/invalid archives will fail with a clear gzip error from the volume manager instead. --- cmd/api/api/volumes.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index f6f4bcb..66f87d7 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -83,12 +83,8 @@ func (s *ApiService) CreateVolumeFromArchive(ctx context.Context, request oapi.C Message: "size_gb must be a positive integer", }, nil } - if request.Body == nil { - return oapi.CreateVolumeFromArchive400JSONResponse{ - Code: "missing_body", - Message: "request body (tar.gz archive) is required", - }, nil - } + // Note: request.Body is never nil in Go's net/http (empty body = http.NoBody) + // Empty/invalid archives will fail with a clear gzip error downstream // Create the volume from archive - stream directly without buffering domainReq := volumes.CreateVolumeFromArchiveRequest{ From 2466fb79299de9aeffe11e27189ca04e5bf5e181 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:25:47 -0500 Subject: [PATCH 10/10] fix(test): update volumes_test.go to use Body instead of JSONBody --- cmd/api/api/volumes_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/api/api/volumes_test.go b/cmd/api/api/volumes_test.go index 91127e2..8d3e766 100644 --- a/cmd/api/api/volumes_test.go +++ b/cmd/api/api/volumes_test.go @@ -33,7 +33,7 @@ func TestGetVolume_ByName(t *testing.T) { // Create a volume createResp, err := svc.CreateVolume(ctx(), oapi.CreateVolumeRequestObject{ - JSONBody: &oapi.CreateVolumeRequest{ + Body: &oapi.CreateVolumeRequest{ Name: "my-data", SizeGb: 1, }, @@ -58,7 +58,7 @@ func TestDeleteVolume_ByName(t *testing.T) { // Create a volume _, err := svc.CreateVolume(ctx(), oapi.CreateVolumeRequestObject{ - JSONBody: &oapi.CreateVolumeRequest{ + Body: &oapi.CreateVolumeRequest{ Name: "to-delete", SizeGb: 1, },