diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index 07cc2d7190..941a09c77f 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -1494,6 +1494,21 @@ type TemplateLegacy struct { UpdatedAt time.Time `json:"updatedAt"` } +// TemplateRefreshEnvdResponse defines model for TemplateRefreshEnvdResponse. +type TemplateRefreshEnvdResponse struct { + // Aliases Aliases of the template (inherited from the source) + Aliases *[]string `json:"aliases,omitempty"` + + // BuildID Identifier of the new build; poll its status/logs endpoints + BuildID string `json:"buildID"` + + // FromEnvdVersion envd version the source build was baked with + FromEnvdVersion string `json:"fromEnvdVersion"` + + // TemplateID Identifier of the template being refreshed + TemplateID string `json:"templateID"` +} + // TemplateRequestResponseV3 defines model for TemplateRequestResponseV3. type TemplateRequestResponseV3 struct { // Aliases Aliases of the template @@ -2894,6 +2909,17 @@ type ClientInterface interface { // Corresponds with POST /v2/templates/{templateID}/builds/{buildID} (the `PostV2TemplatesTemplateIDBuildsBuildID` operationId). PostV2TemplatesTemplateIDBuildsBuildID(ctx context.Context, templateID TemplateID, buildID BuildID, body PostV2TemplatesTemplateIDBuildsBuildIDJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostV2TemplatesTemplateIDRefreshEnvd Rebuild a template with the current envd + // + // Derives a new build of the template FROM the template's own latest ready + // build, forcing the host's current envd binary to replace the one baked in + // by the original build. Specs (cpu, memory) and the alias are inherited + // from the source build. Returns the new build to poll via the existing + // build status/logs endpoints. + // + // Corresponds with POST /v2/templates/{templateID}/refresh-envd (the `PostV2TemplatesTemplateIDRefreshEnvd` operationId). + PostV2TemplatesTemplateIDRefreshEnvd(ctx context.Context, templateID TemplateID, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostV3TemplatesWithBody Create template (v3) // // Create a new template. @@ -4519,6 +4545,27 @@ func (c *Client) PostV2TemplatesTemplateIDBuildsBuildID(ctx context.Context, tem return c.Client.Do(req) } +// PostV2TemplatesTemplateIDRefreshEnvd Rebuild a template with the current envd +// +// Derives a new build of the template FROM the template's own latest ready +// build, forcing the host's current envd binary to replace the one baked in +// by the original build. Specs (cpu, memory) and the alias are inherited +// from the source build. Returns the new build to poll via the existing +// build status/logs endpoints. +// +// Corresponds with POST /v2/templates/{templateID}/refresh-envd (the `PostV2TemplatesTemplateIDRefreshEnvd` operationId). +func (c *Client) PostV2TemplatesTemplateIDRefreshEnvd(ctx context.Context, templateID TemplateID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV2TemplatesTemplateIDRefreshEnvdRequest(c.Server, templateID) + 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) +} + // PostV3TemplatesWithBody Create template (v3) // // Create a new template. @@ -7844,6 +7891,40 @@ func NewPostV2TemplatesTemplateIDBuildsBuildIDRequestWithBody(server string, tem return req, nil } +// NewPostV2TemplatesTemplateIDRefreshEnvdRequest constructs an http.Request for the PostV2TemplatesTemplateIDRefreshEnvd method +func NewPostV2TemplatesTemplateIDRefreshEnvdRequest(server string, templateID TemplateID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "templateID", templateID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v2/templates/%s/refresh-envd", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPostV3TemplatesRequest calls the generic PostV3Templates builder with application/json body func NewPostV3TemplatesRequest(server string, body PostV3TemplatesJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -8860,6 +8941,19 @@ type ClientWithResponsesInterface interface { // Corresponds with POST /v2/templates/{templateID}/builds/{buildID} (the `PostV2TemplatesTemplateIDBuildsBuildID` operationId). PostV2TemplatesTemplateIDBuildsBuildIDWithResponse(ctx context.Context, templateID TemplateID, buildID BuildID, body PostV2TemplatesTemplateIDBuildsBuildIDJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV2TemplatesTemplateIDBuildsBuildIDResponse, error) + // PostV2TemplatesTemplateIDRefreshEnvdWithResponse Rebuild a template with the current envd + // + // Derives a new build of the template FROM the template's own latest ready + // build, forcing the host's current envd binary to replace the one baked in + // by the original build. Specs (cpu, memory) and the alias are inherited + // from the source build. Returns the new build to poll via the existing + // build status/logs endpoints. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /v2/templates/{templateID}/refresh-envd (the `PostV2TemplatesTemplateIDRefreshEnvd` operationId). + PostV2TemplatesTemplateIDRefreshEnvdWithResponse(ctx context.Context, templateID TemplateID, reqEditors ...RequestEditorFn) (*PostV2TemplatesTemplateIDRefreshEnvdResponse, error) + // PostV3TemplatesWithBodyWithResponse Create template (v3) // // Create a new template. @@ -13070,6 +13164,82 @@ func (r PostV2TemplatesTemplateIDBuildsBuildIDResponse) ContentType() string { return "" } +type PostV2TemplatesTemplateIDRefreshEnvdResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *TemplateRefreshEnvdResponse + // JSON400 the response for an HTTP 400 `application/json` response + JSON400 *N400 + // JSON401 the response for an HTTP 401 `application/json` response + JSON401 *N401 + // JSON403 the response for an HTTP 403 `application/json` response + JSON403 *N403 + // JSON404 the response for an HTTP 404 `application/json` response + JSON404 *N404 + // JSON500 the response for an HTTP 500 `application/json` response + JSON500 *N500 +} + +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON202() *TemplateRefreshEnvdResponse { + return r.JSON202 +} + +// GetJSON400 returns the response for an HTTP 400 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON400() *N400 { + return r.JSON400 +} + +// GetJSON401 returns the response for an HTTP 401 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON401() *N401 { + return r.JSON401 +} + +// GetJSON403 returns the response for an HTTP 403 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON403() *N403 { + return r.JSON403 +} + +// GetJSON404 returns the response for an HTTP 404 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON404() *N404 { + return r.JSON404 +} + +// GetJSON500 returns the response for an HTTP 500 `application/json` response +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetJSON500() *N500 { + return r.JSON500 +} + +// GetBody returns the raw response body bytes +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r PostV2TemplatesTemplateIDRefreshEnvdResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type PostV3TemplatesResponse struct { Body []byte HTTPResponse *http.Response @@ -14681,6 +14851,25 @@ func (c *ClientWithResponses) PostV2TemplatesTemplateIDBuildsBuildIDWithResponse return ParsePostV2TemplatesTemplateIDBuildsBuildIDResponse(rsp) } +// PostV2TemplatesTemplateIDRefreshEnvdWithResponse Rebuild a template with the current envd +// +// Derives a new build of the template FROM the template's own latest ready +// build, forcing the host's current envd binary to replace the one baked in +// by the original build. Specs (cpu, memory) and the alias are inherited +// from the source build. Returns the new build to poll via the existing +// build status/logs endpoints. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /v2/templates/{templateID}/refresh-envd (the `PostV2TemplatesTemplateIDRefreshEnvd` operationId). +func (c *ClientWithResponses) PostV2TemplatesTemplateIDRefreshEnvdWithResponse(ctx context.Context, templateID TemplateID, reqEditors ...RequestEditorFn) (*PostV2TemplatesTemplateIDRefreshEnvdResponse, error) { + rsp, err := c.PostV2TemplatesTemplateIDRefreshEnvd(ctx, templateID, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostV2TemplatesTemplateIDRefreshEnvdResponse(rsp) +} + // PostV3TemplatesWithBodyWithResponse Create template (v3) // // Create a new template. @@ -18092,6 +18281,67 @@ func ParsePostV2TemplatesTemplateIDBuildsBuildIDResponse(rsp *http.Response) (*P return response, nil } +// ParsePostV2TemplatesTemplateIDRefreshEnvdResponse parses an HTTP response from a PostV2TemplatesTemplateIDRefreshEnvdWithResponse call +func ParsePostV2TemplatesTemplateIDRefreshEnvdResponse(rsp *http.Response) (*PostV2TemplatesTemplateIDRefreshEnvdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostV2TemplatesTemplateIDRefreshEnvdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest TemplateRefreshEnvdResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest N400 + 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 N401 + 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 == 403: + var dest N403 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest N404 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest N500 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParsePostV3TemplatesResponse parses an HTTP response from a PostV3TemplatesWithResponse call func ParsePostV3TemplatesResponse(rsp *http.Response) (*PostV3TemplatesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -18529,6 +18779,9 @@ type ServerInterface interface { // PostV2TemplatesTemplateIDBuildsBuildID Start template build (v2) // (POST /v2/templates/{templateID}/builds/{buildID}) PostV2TemplatesTemplateIDBuildsBuildID(c *gin.Context, templateID TemplateID, buildID BuildID) + // PostV2TemplatesTemplateIDRefreshEnvd Rebuild a template with the current envd + // (POST /v2/templates/{templateID}/refresh-envd) + PostV2TemplatesTemplateIDRefreshEnvd(c *gin.Context, templateID TemplateID) // PostV3Templates Create template (v3) // (POST /v3/templates) PostV3Templates(c *gin.Context) @@ -20424,6 +20677,31 @@ func (siw *ServerInterfaceWrapper) PostV2TemplatesTemplateIDBuildsBuildID(c *gin siw.Handler.PostV2TemplatesTemplateIDBuildsBuildID(c, templateID, buildID) } +// PostV2TemplatesTemplateIDRefreshEnvd operation middleware +func (siw *ServerInterfaceWrapper) PostV2TemplatesTemplateIDRefreshEnvd(c *gin.Context) { + + var err error + _ = err + + // ------------- Path parameter "templateID" ------------- + var templateID TemplateID + + err = runtime.BindStyledParameterWithOptions("simple", "templateID", c.Param("templateID"), &templateID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter templateID: %w", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.PostV2TemplatesTemplateIDRefreshEnvd(c, templateID) +} + // PostV3Templates operation middleware func (siw *ServerInterfaceWrapper) PostV3Templates(c *gin.Context) { @@ -20574,6 +20852,7 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.POST(options.BaseURL+"/templates/:templateID", wrapper.PostTemplatesTemplateID) router.POST(options.BaseURL+"/templates/:templateID/builds/:buildID", wrapper.PostTemplatesTemplateIDBuildsBuildID) router.POST(options.BaseURL+"/v2/templates/:templateID/builds/:buildID", wrapper.PostV2TemplatesTemplateIDBuildsBuildID) + router.POST(options.BaseURL+"/v2/templates/:templateID/refresh-envd", wrapper.PostV2TemplatesTemplateIDRefreshEnvd) router.PATCH(options.BaseURL+"/v2/templates/:templateID", wrapper.PatchV2TemplatesTemplateID) router.GET(options.BaseURL+"/templates/:templateID/builds/:buildID/status", wrapper.GetTemplatesTemplateIDBuildsBuildIDStatus) router.GET(options.BaseURL+"/templates/:templateID/builds/:buildID/logs", wrapper.GetTemplatesTemplateIDBuildsBuildIDLogs) @@ -20614,271 +20893,276 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "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", - "GN52L99pNct/i0Tw+g643H3aUye2ty/noK59Xdb94E77Rjt3fpNW+lV2nZ111+xvliGQ7VjyrVj5l0Bp", - "NgH2PLA62ZTESrAx0l+jCcn5lbnWzAtYEESu07zKumF7a16DfSzJliRMUkUvCZLVxNxGqMAqnSPOYOUF", - "kRLPjElC3w8dly7BIp03llXg6yPCZpoB7P74p/tNUggasv62u5m74LE16623Zh3A6eMJjuunM/62+9US", - "Gr8zSe62Uye/q1Li/1aJncvke9fZnU0avq+or992H3rcl4XEY2zkLdeR9jp9D54vX1OtlIogFHjtwN8A", - "4b/v0N87WUS3kPcYW3yvscUb08+K2M51Izmj5PT1Yjnv+PoCiKx1eT2sUNJvQpmJRKuuxPbnTaVmRV+S", - "1VLR868iFT3/WlKRXYDj724hD0tAetQZeoSp573kwfOqIAOrfSL3dkzj94/uXic2c23aYqu1m0cmGy09", - "Gpy2wx73y4AGT4aR+lHivDTAmTvpyeQQ5X5T+M2seywLrCUDejO1YfYoEt+YC4YI2MbigANufzb/GJ6a", - "343b5iWL3b/ZYdcWdt16btJ5CLfx6dGMfpME/n58Svri5v2nnUHzd4kxO1+LvdWddR6R8RaL+fZxNdiV", - "uHRYU4l89HI0V6qUL7e3cUnHZHcyxmUJeGK//7wcLiNBxW62dGj+CIUxw79LunVBFo13bFSi/7uWueqx", - "bQOLLx+//L8AAAD//w==", + "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", + "3nFh0dXftcxVj+066Hz5+OX/BQAA//8=", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/packages/api/internal/handlers/template_refresh_envd.go b/packages/api/internal/handlers/template_refresh_envd.go new file mode 100644 index 0000000000..45a85c532d --- /dev/null +++ b/packages/api/internal/handlers/template_refresh_envd.go @@ -0,0 +1,264 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + + "github.com/e2b-dev/infra/packages/api/internal/api" + "github.com/e2b-dev/infra/packages/api/internal/template" + "github.com/e2b-dev/infra/packages/db/pkg/types" + "github.com/e2b-dev/infra/packages/db/queries" + "github.com/e2b-dev/infra/packages/shared/pkg/clusters" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" + "github.com/e2b-dev/infra/packages/shared/pkg/logger" + "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" + "github.com/e2b-dev/infra/packages/shared/pkg/templates" +) + +// refreshEnvdPlan is the decision derived from a source template's latest ready +// build: what to register and build so the host envd replaces the baked-in one. +type refreshEnvdPlan struct { + // Steps is the single trivial RUN step. A FROM TEMPLATE base layer is always + // cached, so this step gets UpdateEnvd=true and the swap is guaranteed rather + // than left to finalize (see phases/steps/builder.go). + Steps []api.TemplateStep + // FromTemplate is the source the derived build bases on: the template itself, + // resolved to its latest READY build, so the old layer is reused. + FromTemplate string + // CPU and RAM are inherited from the source build so the refresh does not + // silently drop specs to the RegisterBuild defaults (2 vCPU / 1024 MiB). + CPU int32 + RAM int32 + // Alias, when set, keeps the same name pointing at the new build (in-place). + Alias *string + // FromEnvdVersion is the source build's envd version, echoed back to the caller. + FromEnvdVersion string +} + +// buildRefreshEnvdPlan validates the source build and derives the refresh plan. +// Pure (no I/O) so the decisions are unit-tested without a DB. Returns an +// *api.APIError for the caller to surface. Cross-team access is NOT enforced +// here -- it is enforced upstream by GetTeamTemplate's team-scoped query, which +// returns no row (-> 404) for a template the key's team does not own. +func buildRefreshEnvdPlan(templateID string, src queries.GetTeamTemplateRow) (refreshEnvdPlan, *api.APIError) { + if src.BuildStatus != types.BuildStatusGroupReady { + return refreshEnvdPlan{}, &api.APIError{ + Code: http.StatusBadRequest, + ClientMsg: "Template has no ready build to refresh from", + Err: fmt.Errorf("template '%s' build status is %s", templateID, src.BuildStatus), + } + } + if src.BuildVcpu == 0 || src.BuildRamMb == 0 { + return refreshEnvdPlan{}, &api.APIError{ + Code: http.StatusBadRequest, + ClientMsg: fmt.Sprintf("Source build has no usable specs (cpu=%d ram=%d)", src.BuildVcpu, src.BuildRamMb), + Err: fmt.Errorf("source build missing specs: cpu=%d ram=%d", src.BuildVcpu, src.BuildRamMb), + } + } + + fromEnvdVersion := "unknown" + if src.BuildEnvdVersion != nil { + fromEnvdVersion = *src.BuildEnvdVersion + } + + var alias *string + if len(src.Aliases) > 0 { + alias = &src.Aliases[0] + } + + return refreshEnvdPlan{ + Steps: []api.TemplateStep{{Type: "RUN", Args: new([]string{"true"})}}, + FromTemplate: templateID, + CPU: int32(src.BuildVcpu), + RAM: int32(src.BuildRamMb), + Alias: alias, + FromEnvdVersion: fromEnvdVersion, + }, nil +} + +// PostV2TemplatesTemplateIDRefreshEnvd rebuilds a template with the host's current +// envd. It derives a new build FROM the template's own latest ready build, which +// keeps the base layer cached so only a trivial RUN step plus finalize run -- and +// a cached source layer is exactly what drives UpdateEnvd=true in the step phase +// (see packages/orchestrator/pkg/template/build/phases/steps/builder.go), so the +// host envd binary replaces the one baked into the original build. Specs and the +// alias are inherited from the source, in-place: the new build supersedes the +// default tag under the SAME templateID. +func (a *APIStore) PostV2TemplatesTemplateIDRefreshEnvd(c *gin.Context, templateID api.TemplateID) { + ctx := c.Request.Context() + + telemetry.ReportEvent(ctx, "started envd refresh build") + + team, apiErr := a.GetTeam(ctx, c, nil) + if apiErr != nil { + a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) + telemetry.ReportCriticalError(ctx, "error when getting team", apiErr.Err) + + return + } + + ctx = featureflags.AddToContext(ctx, featureflags.TemplateContext(templateID)) + telemetry.SetAttributes(ctx, + telemetry.WithTeamID(team.ID.String()), + telemetry.WithTemplateID(templateID), + ) + + // Team-scoped: the query filters on team_id and source='template', so another + // team's template returns no row and 404s here. + src, err := a.sqlcDB.GetTeamTemplate(ctx, queries.GetTeamTemplateParams{ + ID: templateID, + TeamID: team.ID, + }) + if err != nil { + a.sendAPIStoreError(c, http.StatusNotFound, fmt.Sprintf("Template '%s' not found", templateID)) + telemetry.ReportErrorByCode(ctx, http.StatusNotFound, "template not found", err, telemetry.WithTemplateID(templateID)) + + return + } + + plan, apiErr := buildRefreshEnvdPlan(templateID, src) + if apiErr != nil { + a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) + telemetry.ReportErrorByCode(ctx, apiErr.Code, "refresh plan rejected", apiErr.Err, telemetry.WithTemplateID(templateID)) + + return + } + + firecrackerVersion := a.featureFlags.StringFlag(ctx, featureflags.BuildFirecrackerVersion) + kernelVersion := a.featureFlags.StringFlag(ctx, featureflags.BuildKernelVersion) + + stepsMarshalled, err := json.Marshal(dockerfileStore{FromTemplate: &plan.FromTemplate, Steps: &plan.Steps}) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when processing steps: %s", err)) + telemetry.ReportCriticalError(ctx, "error when processing steps", err, telemetry.WithTemplateID(templateID)) + + return + } + + reg, apiErr := template.RegisterBuild(ctx, a.templateCache, a.sqlcDB, template.RegisterBuildData{ + ClusterID: clusters.WithClusterFallback(team.ClusterID), + TemplateID: templateID, + Team: team, + Alias: plan.Alias, + Dockerfile: string(stepsMarshalled), + CpuCount: &plan.CPU, + MemoryMB: &plan.RAM, + Version: templates.TemplateV2LatestVersion, + KernelVersion: kernelVersion, + FirecrackerVersion: firecrackerVersion, + }) + if apiErr != nil { + a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) + telemetry.ReportErrorByCode(ctx, apiErr.Code, "error when registering refresh build", apiErr.Err, telemetry.WithTemplateID(templateID)) + + return + } + for _, al := range reg.Aliases { + a.templateCache.InvalidateAlias(context.WithoutCancel(ctx), &team.Slug, al) + } + + buildUUID, err := uuid.Parse(reg.BuildID) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, "Error when parsing new build ID") + telemetry.ReportCriticalError(ctx, "invalid build ID", err, telemetry.WithTemplateID(templateID)) + + return + } + + if err := a.CheckAndCancelConcurrentBuilds(ctx, templateID, buildUUID, clusters.WithClusterFallback(team.ClusterID)); err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, "Error during refresh build request") + telemetry.ReportCriticalError(ctx, "error cancelling concurrent builds", err, telemetry.WithTemplateID(templateID)) + + return + } + + builderNode, err := a.templateManager.GetAvailableBuildClient(ctx, clusters.WithClusterFallback(team.ClusterID)) + if err != nil { + a.sendAPIStoreError(c, http.StatusServiceUnavailable, "Error when getting available build client") + telemetry.ReportCriticalError(ctx, "error getting build client", err, telemetry.WithTemplateID(templateID)) + + return + } + + machineInfo := builderNode.GetMachineInfo() + err = a.sqlcDB.UpdateTemplateBuild(ctx, queries.UpdateTemplateBuildParams{ + Dockerfile: new(string(stepsMarshalled)), + ClusterNodeID: new(builderNode.NodeID), + CpuArchitecture: new(machineInfo.CPUArchitecture), + CpuFamily: new(machineInfo.CPUFamily), + CpuModel: new(machineInfo.CPUModel), + CpuModelName: new(machineInfo.CPUModelName), + CpuFlags: machineInfo.CPUFlags, + BuildUuid: buildUUID, + }) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when updating build: %s", err)) + telemetry.ReportCriticalError(ctx, "error updating build", err, telemetry.WithTemplateID(templateID)) + + return + } + + // Reload the freshly-registered build for its resolved free-disk target and the + // seeded kernel/fc versions, exactly as the v2 start handler passes them on. + built, err := a.sqlcDB.GetTemplateBuildWithTemplate(ctx, queries.GetTemplateBuildWithTemplateParams{ + TemplateID: templateID, + BuildID: buildUUID, + }) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, "Error when reloading registered build") + telemetry.ReportCriticalError(ctx, "error reloading build", err, telemetry.WithTemplateID(templateID)) + + return + } + build := built.EnvBuild + + version, err := userAgentToTemplateVersion(ctx, logger.L().With(logger.WithTemplateID(templateID), logger.WithBuildID(reg.BuildID)), c.Request.UserAgent()) + if err != nil { + a.sendAPIStoreError(c, http.StatusBadRequest, fmt.Sprintf("Error when parsing user agent: %s", err)) + telemetry.ReportErrorByCode(ctx, http.StatusBadRequest, "error parsing user agent", err, telemetry.WithTemplateID(templateID)) + + return + } + + err = a.templateManager.CreateTemplate( + ctx, + team.ID, + team.Slug, + templateID, + buildUUID, + build.KernelVersion, + build.FirecrackerVersion, + nil, // startCmd: the derived build re-runs the source's baked config + build.Vcpu, + team.Limits.DiskMb, + build.FreeDiskSizeMb, + build.RamMb, + nil, // readyCmd + nil, // fromImage + &plan.FromTemplate, + nil, // fromImageRegistry + nil, // force + &plan.Steps, + clusters.WithClusterFallback(team.ClusterID), + builderNode.NodeID, + version, + ) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when starting refresh build: %s", err)) + telemetry.ReportCriticalError(ctx, "refresh build failed", err, telemetry.WithTemplateID(templateID)) + + return + } + + c.JSON(http.StatusAccepted, api.TemplateRefreshEnvdResponse{ + TemplateID: templateID, + BuildID: reg.BuildID, + FromEnvdVersion: plan.FromEnvdVersion, + Aliases: ®.Aliases, + }) +} diff --git a/packages/api/internal/handlers/template_refresh_envd_test.go b/packages/api/internal/handlers/template_refresh_envd_test.go new file mode 100644 index 0000000000..1e0745e18f --- /dev/null +++ b/packages/api/internal/handlers/template_refresh_envd_test.go @@ -0,0 +1,91 @@ +package handlers + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/db/pkg/types" + "github.com/e2b-dev/infra/packages/db/queries" +) + +// readySource is a source template row with a usable ready build. +func readySource() queries.GetTeamTemplateRow { + envd := "0.4.0" + + return queries.GetTeamTemplateRow{ + BuildStatus: types.BuildStatusGroupReady, + BuildVcpu: 4, + BuildRamMb: 2048, + BuildEnvdVersion: &envd, + Aliases: []string{"my-template"}, + } +} + +func TestBuildRefreshEnvdPlan_Success(t *testing.T) { + plan, apiErr := buildRefreshEnvdPlan("tmpl-1", readySource()) + require.Nil(t, apiErr) + + // FROM the template itself, resolved to its latest ready build upstream. + assert.Equal(t, "tmpl-1", plan.FromTemplate) + + // Exactly one trivial RUN step -- what guarantees the step phase (and thus + // the envd swap) runs instead of leaving it to finalize. + require.Len(t, plan.Steps, 1) + assert.Equal(t, "RUN", plan.Steps[0].Type) + require.NotNil(t, plan.Steps[0].Args) + assert.Equal(t, []string{"true"}, *plan.Steps[0].Args) + + // Specs inherited from the source, not dropped to RegisterBuild defaults. + assert.Equal(t, int32(4), plan.CPU) + assert.Equal(t, int32(2048), plan.RAM) + + // Alias inherited so the in-place rebuild keeps the same name. + require.NotNil(t, plan.Alias) + assert.Equal(t, "my-template", *plan.Alias) + + assert.Equal(t, "0.4.0", plan.FromEnvdVersion) +} + +func TestBuildRefreshEnvdPlan_NoReadyBuild(t *testing.T) { + src := readySource() + src.BuildStatus = types.BuildStatusGroupInProgress + + _, apiErr := buildRefreshEnvdPlan("tmpl-1", src) + require.NotNil(t, apiErr) + assert.Equal(t, http.StatusBadRequest, apiErr.Code) +} + +func TestBuildRefreshEnvdPlan_MissingSpecs(t *testing.T) { + for _, tc := range []struct { + name string + cpu int64 + ram int64 + }{ + {"zero cpu", 0, 2048}, + {"zero ram", 4, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + src := readySource() + src.BuildVcpu = tc.cpu + src.BuildRamMb = tc.ram + + _, apiErr := buildRefreshEnvdPlan("tmpl-1", src) + require.NotNil(t, apiErr) + assert.Equal(t, http.StatusBadRequest, apiErr.Code) + }) + } +} + +func TestBuildRefreshEnvdPlan_NoAliasNoEnvdVersion(t *testing.T) { + src := readySource() + src.Aliases = nil + src.BuildEnvdVersion = nil + + plan, apiErr := buildRefreshEnvdPlan("tmpl-1", src) + require.Nil(t, apiErr) + assert.Nil(t, plan.Alias, "no source alias -> nil, RegisterBuild leaves aliases untouched") + assert.Equal(t, "unknown", plan.FromEnvdVersion) +} diff --git a/spec/openapi.yml b/spec/openapi.yml index 1ddb5f2645..2616cbeb0f 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -1263,6 +1263,26 @@ components: buildStatus: $ref: "#/components/schemas/TemplateBuildStatus" + TemplateRefreshEnvdResponse: + required: + - templateID + - buildID + - fromEnvdVersion + properties: + templateID: + type: string + description: Identifier of the template being refreshed + buildID: + type: string + description: Identifier of the new build; poll its status/logs endpoints + fromEnvdVersion: + type: string + description: envd version the source build was baked with + aliases: + type: array + description: Aliases of the template (inherited from the source) + items: + type: string TemplateRequestResponseV3: required: - templateID @@ -3578,6 +3598,44 @@ paths: "500": $ref: "#/components/responses/500" + /v2/templates/{templateID}/refresh-envd: + post: + summary: Rebuild a template with the current envd + description: | + Derives a new build of the template FROM the template's own latest ready + build, forcing the host's current envd binary to replace the one baked in + by the original build. Specs (cpu, memory) and the alias are inherited + from the source build. Returns the new build to poll via the existing + build status/logs endpoints. + tags: [templates] + security: + - ApiKeyAuth: [] + - AuthProviderBearerAuth: [] + AuthProviderTeamAuth: [] + - AdminApiKeyAuth: [] + AdminTeamAuth: [] + - AdminJWTAuth: [] + AdminTeamAuth: [] + parameters: + - $ref: "#/components/parameters/templateID" + responses: + "202": + description: The refresh build was requested successfully + content: + application/json: + schema: + $ref: "#/components/schemas/TemplateRefreshEnvdResponse" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + /v2/templates/{templateID}: patch: summary: Update template (v2)