diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go index ad76376..66f87d7 100644 --- a/cmd/api/api/volumes.go +++ b/cmd/api/api/volumes.go @@ -3,9 +3,6 @@ package api import ( "context" "errors" - "io" - "mime/multipart" - "strconv" "github.com/kernel/hypeman/lib/logger" mw "github.com/kernel/hypeman/lib/middleware" @@ -34,168 +31,90 @@ 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 +// 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) - var name string - var sizeGb int - var id *string - var archiveReader io.Reader + // Validate required parameters + if request.Params.Name == "" { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "missing_field", + Message: "name query parameter is required", + }, nil + } + if request.Params.SizeGb <= 0 { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "invalid_field", + Message: "size_gb must be a positive integer", + }, 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{ + Name: request.Params.Name, + SizeGb: request.Params.SizeGb, + Id: request.Params.Id, + } - 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(), + vol, err := s.VolumeManager.CreateVolumeFromArchive(ctx, domainReq, request.Body) + if err != nil { + if errors.Is(err, volumes.ErrArchiveTooLarge) { + return oapi.CreateVolumeFromArchive400JSONResponse{ + Code: "archive_too_large", + Message: 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) - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "failed to create volume", - }, nil - } - - return oapi.CreateVolume201JSONResponse(volumeToOAPI(*vol)), nil + if errors.Is(err, volumes.ErrAlreadyExists) { + return oapi.CreateVolumeFromArchive409JSONResponse{ + Code: "already_exists", + Message: "volume with this ID already exists", + }, 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", + 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", }, nil } - // Should not reach here - return oapi.CreateVolume500JSONResponse{ - Code: "internal_error", - Message: "unexpected error processing request", - }, nil + return oapi.CreateVolumeFromArchive201JSONResponse(volumeToOAPI(*vol)), nil } // GetVolume gets volume details 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, }, diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index be37cc7..a5f6066 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -878,19 +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 - 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"` + SizeGb int `form:"size_gb" json:"size_gb"` + + // Id Optional custom volume ID (auto-generated if not provided) + Id *string `form:"id,omitempty" json:"id,omitempty"` } // CreateBuildMultipartRequestBody defines body for CreateBuild for multipart/form-data ContentType. @@ -914,9 +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 - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1107,6 +1101,9 @@ type ClientInterface interface { CreateVolume(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateVolumeFromArchiveWithBody request with any body + 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) @@ -1606,6 +1603,18 @@ func (c *Client) CreateVolume(ctx context.Context, body CreateVolumeJSONRequestB return c.Client.Do(req) } +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 + } + 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 +2928,81 @@ 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, params *CreateVolumeFromArchiveParams, 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 + } + + 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 + } + + 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 +3231,9 @@ type ClientWithResponsesInterface interface { CreateVolumeWithResponse(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) + // CreateVolumeFromArchiveWithBodyWithResponse request with any body + 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) @@ -4005,6 +4092,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 +4529,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, 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 + } + 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 +6033,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 +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, params CreateVolumeFromArchiveParams) // Delete volume // (DELETE /volumes/{id}) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) @@ -6314,12 +6493,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, params CreateVolumeFromArchiveParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Delete volume // (DELETE /volumes/{id}) func (_ Unimplemented) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) { @@ -7337,6 +7522,69 @@ 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) { + + 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, params) + })) + + 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 +7865,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 +9225,7 @@ func (response ListVolumes500JSONResponse) VisitListVolumesResponse(w http.Respo } type CreateVolumeRequestObject struct { - JSONBody *CreateVolumeJSONRequestBody - MultipartBody *multipart.Reader + Body *CreateVolumeJSONRequestBody } type CreateVolumeResponseObject interface { @@ -9027,6 +9277,60 @@ func (response CreateVolume500JSONResponse) VisitCreateVolumeResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type CreateVolumeFromArchiveRequestObject struct { + Params CreateVolumeFromArchiveParams + Body io.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 +9513,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 +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)) @@ -10194,6 +10490,34 @@ func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { } } +// CreateVolumeFromArchive operation middleware +func (sh *strictHandler) CreateVolumeFromArchive(w http.ResponseWriter, r *http.Request, params CreateVolumeFromArchiveParams) { + var request CreateVolumeFromArchiveRequestObject + + 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)) + } + 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 +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 bd85c46..c53992f 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,30 +1816,77 @@ paths: application/json: schema: $ref: "#/components/schemas/CreateVolumeRequest" - multipart/form-data: + 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 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: + 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 diff --git a/stainless.yaml b/stainless.yaml index d8699a0..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 @@ -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}