mirror of
https://github.com/hanzoai/runtime.git
synced 2026-10-30 18:23:13 +00:00
Every sandbox was created privileged under the daemon's default runtime, and the setting that would have changed it defaulted to an empty string that silently meant runc. For a service whose whole premise is running code somebody else wrote, that is not a boundary. An org sets its isolation and the create request carries it, so a sandbox records what it was made with: changing the policy moves new sandboxes and leaves running ones alone. gvisor is the default. firecracker is delivered through kata's shim, which is why it is one name rather than two. runc is refused unless the runner names that org, and an empty list names nobody. Absent means the default, unknown is a refusal, and a runtime the daemon does not have is a refusal. Nothing substitutes quietly. The runner took an organization in a field called UserId with a note beside it saying so. Deciding who may have runc through a field named after something else is not reviewable. Privileged is replaced by dropping everything and granting the eight the daemon uses. Nothing the sandbox does broke; what stopped working is mount, mknod, raw sockets, chroot, writing to proc, and seeing the host's disks. AUDIT_WRITE is among them because without it sudo works and complains every time. The creation histogram's lowest bucket was a tenth of a second, so the metric could not observe the claim the product makes about its own speed. It can now, and it carries the isolation, so comparing them is a query. Co-authored-by: Hanzo Dev <[email protected]>
294 lines
8.5 KiB
Go
294 lines
8.5 KiB
Go
// Copyright 2025 Hanzo Industries Inc.
|
|
// SPDX-License-Identifier: AGPL-3.0
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/hanzoai/runtime/apps/runner/pkg/api/dto"
|
|
"github.com/hanzoai/runtime/apps/runner/pkg/common"
|
|
"github.com/hanzoai/runtime/apps/runner/pkg/models/enums"
|
|
"github.com/hanzoai/runtime/apps/runner/pkg/runner"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Create godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Create a sandbox
|
|
// @Description Create a sandbox
|
|
// @Param sandbox body dto.CreateSandboxDTO true "Create sandbox"
|
|
// @Produce json
|
|
// @Success 201 {string} containerId
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes [post]
|
|
//
|
|
// @id Create
|
|
func Create(ctx *gin.Context) {
|
|
var createSandboxDto dto.CreateSandboxDTO
|
|
err := ctx.ShouldBindJSON(&createSandboxDto)
|
|
if err != nil {
|
|
ctx.Error(common.NewInvalidBodyRequestError(err))
|
|
return
|
|
}
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
containerId, err := runner.Docker.Create(ctx.Request.Context(), createSandboxDto)
|
|
if err != nil {
|
|
runner.Cache.SetSandboxState(ctx, createSandboxDto.Id, enums.SandboxStateError)
|
|
common.ContainerOperationCount.WithLabelValues("create", string(common.PrometheusOperationStatusFailure)).Inc()
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
common.ContainerOperationCount.WithLabelValues("create", string(common.PrometheusOperationStatusSuccess)).Inc()
|
|
|
|
ctx.JSON(http.StatusCreated, containerId)
|
|
}
|
|
|
|
// Destroy godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Destroy sandbox
|
|
// @Description Destroy sandbox
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Success 200 {string} string "Sandbox destroyed"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId}/destroy [post]
|
|
//
|
|
// @id Destroy
|
|
func Destroy(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err := runner.Docker.Destroy(ctx.Request.Context(), sandboxId)
|
|
if err != nil {
|
|
runner.Cache.SetSandboxState(ctx, sandboxId, enums.SandboxStateError)
|
|
common.ContainerOperationCount.WithLabelValues("destroy", string(common.PrometheusOperationStatusFailure)).Inc()
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
common.ContainerOperationCount.WithLabelValues("destroy", string(common.PrometheusOperationStatusSuccess)).Inc()
|
|
|
|
ctx.JSON(http.StatusOK, "Sandbox destroyed")
|
|
}
|
|
|
|
// CreateBackup godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Create sandbox backup
|
|
// @Description Create sandbox backup
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Param sandbox body dto.CreateBackupDTO true "Create backup"
|
|
// @Success 201 {string} string "Backup created"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId}/backup [post]
|
|
//
|
|
// @id CreateBackup
|
|
func CreateBackup(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
var createBackupDTO dto.CreateBackupDTO
|
|
err := ctx.ShouldBindJSON(&createBackupDTO)
|
|
if err != nil {
|
|
ctx.Error(common.NewInvalidBodyRequestError(err))
|
|
return
|
|
}
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err = runner.Docker.CreateBackup(ctx.Request.Context(), sandboxId, createBackupDTO)
|
|
if err != nil {
|
|
runner.Cache.SetBackupState(ctx, sandboxId, enums.BackupStateFailed)
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusCreated, "Backup created")
|
|
}
|
|
|
|
// Resize godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Resize sandbox
|
|
// @Description Resize sandbox
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Param sandbox body dto.ResizeSandboxDTO true "Resize sandbox"
|
|
// @Success 200 {string} string "Sandbox resized"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId}/resize [post]
|
|
//
|
|
// @id Resize
|
|
func Resize(ctx *gin.Context) {
|
|
var resizeDto dto.ResizeSandboxDTO
|
|
err := ctx.ShouldBindJSON(&resizeDto)
|
|
if err != nil {
|
|
ctx.Error(common.NewInvalidBodyRequestError(err))
|
|
return
|
|
}
|
|
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err = runner.Docker.Resize(ctx.Request.Context(), sandboxId, resizeDto)
|
|
if err != nil {
|
|
runner.Cache.SetSandboxState(ctx, sandboxId, enums.SandboxStateError)
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, "Sandbox resized")
|
|
}
|
|
|
|
// Start godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Start sandbox
|
|
// @Description Start sandbox
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Success 200 {string} string "Sandbox started"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId}/start [post]
|
|
//
|
|
// @id Start
|
|
func Start(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err := runner.Docker.Start(ctx.Request.Context(), sandboxId)
|
|
if err != nil {
|
|
runner.Cache.SetSandboxState(ctx, sandboxId, enums.SandboxStateError)
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, "Sandbox started")
|
|
}
|
|
|
|
// Stop godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Stop sandbox
|
|
// @Description Stop sandbox
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Success 200 {string} string "Sandbox stopped"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId}/stop [post]
|
|
//
|
|
// @id Stop
|
|
func Stop(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err := runner.Docker.Stop(ctx.Request.Context(), sandboxId)
|
|
if err != nil {
|
|
runner.Cache.SetSandboxState(ctx, sandboxId, enums.SandboxStateError)
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, "Sandbox stopped")
|
|
}
|
|
|
|
// Info godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Get sandbox info
|
|
// @Description Get sandbox info
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Success 200 {object} SandboxInfoResponse "Sandbox info"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId} [get]
|
|
//
|
|
// @id Info
|
|
func Info(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
info := runner.SandboxService.GetSandboxStatesInfo(ctx.Request.Context(), sandboxId)
|
|
|
|
ctx.JSON(http.StatusOK, SandboxInfoResponse{
|
|
State: info.SandboxState,
|
|
BackupState: info.BackupState,
|
|
})
|
|
}
|
|
|
|
type SandboxInfoResponse struct {
|
|
State enums.SandboxState `json:"state"`
|
|
BackupState enums.BackupState `json:"backupState"`
|
|
} // @name SandboxInfoResponse
|
|
|
|
// RemoveDestroyed godoc
|
|
//
|
|
// @Tags sandbox
|
|
// @Summary Remove a destroyed sandbox
|
|
// @Description Remove a sandbox that has been previously destroyed
|
|
// @Produce json
|
|
// @Param sandboxId path string true "Sandbox ID"
|
|
// @Success 200 {string} string "Sandbox removed"
|
|
// @Failure 400 {object} common.ErrorResponse
|
|
// @Failure 401 {object} common.ErrorResponse
|
|
// @Failure 404 {object} common.ErrorResponse
|
|
// @Failure 409 {object} common.ErrorResponse
|
|
// @Failure 500 {object} common.ErrorResponse
|
|
// @Router /sandboxes/{sandboxId} [delete]
|
|
//
|
|
// @id RemoveDestroyed
|
|
func RemoveDestroyed(ctx *gin.Context) {
|
|
sandboxId := ctx.Param("sandboxId")
|
|
|
|
runner := runner.GetInstance(nil)
|
|
|
|
err := runner.SandboxService.RemoveDestroyedSandbox(ctx.Request.Context(), sandboxId)
|
|
if err != nil {
|
|
if !common.IsNotFoundError(err) {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, "Sandbox removed")
|
|
}
|