mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
Rip Beego out of visor's own source: routing, filters, auth, sessions,
logging, config and the guacamole websocket tunnel are now ZAP-native
(github.com/zap-proto/zip over fiber/fasthttp), mirroring the iam-canonical
idiom. vm's own tree is now 100% beego-free; Beego survives only as a
transitive dep of the iam-v1 identity SDK (github.com/hanzoai/iam-v1).
Also fixes the routers test panic: two Beego copies (beego + hanzoai/beego)
both registered the `graceful` flag at init, so `go test ./routers` aborted
with "flag redefined: graceful". Removing Beego removes the double init.
HTTP spine (the explicit deliverable):
- main.go: beego.Run() -> cobra root cmd -> app.Listen (HTTP edge :19000,
optional ZAP addr); a bare `/visor` still serves (container contract).
- pkg/visor/embed.go: ONE boot path Bootstrap() *zip.App; Handler() adapts to
net/http for the cloud mount.
- routers: beego.Router table -> Route(app) with app.Get/Post/Delete;
InsertFilter chain -> app.Use middleware (Recover, CORS, static, tenant,
ApiFilter authz seam, record). TransparentStatic short-circuits before the
authz seam exactly as the BeforeRouter filter did. *beego/context.Context
-> *zip.Ctx.
- util/log.go: request logger over luxfi/log.
Controllers (entailed by a green build; ApiController embedded beego.Controller):
- ApiController backed by *zip.Ctx; c.Input().Get -> c.Ctx.Query,
c.Ctx.Input.RequestBody -> c.Ctx.Body(), c.Ctx.Input.Param(":id") ->
c.Ctx.Param("id"); ServeJSON/ResponseOk/ResponseError write via zip and
stash the envelope on Locals for the record filter.
- Sessions: beego cookie/redis -> stateless Bearer-JWT Principal
(object.GetBearerUser). Signin returns claims+token; the client carries the
token thereafter.
- pagination.SetPaginator -> util.Paginate.
Object/glue:
- tenant_context: beego ctx Input.Data -> zip Locals.
- record.NewRecord(*zip.Ctx, respJSON).
- config: Beego AppConfig -> conf (ini.v1 + ${VAR||default} env expansion,
lazy-loaded). logging: beego/logs -> visor/logs over luxfi/log.
Websocket tunnel (the one Beego-adjacent API with no drop-in ZAP equivalent):
- gorilla/websocket hijack can't work on the fasthttp edge; ported guacamole +
tunnel handlers to github.com/fasthttp/websocket (gorilla-compatible fork,
already a dep). Query params are read before the hijack (fiber recycles the
ctx) and the post-upgrade callback isolates panics (it runs outside zip's
recover).
Build + tests green (host darwin/arm64 + linux/arm64 CI target); go vet clean.
Co-authored-by: Hanzo Dev <[email protected]>
199 lines
5.0 KiB
Go
199 lines
5.0 KiB
Go
// Copyright 2025 Hanzo Industries Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/hanzoai/visor/object"
|
|
"github.com/hanzoai/visor/service"
|
|
)
|
|
|
|
// GetVolumes
|
|
// @Title GetVolumes
|
|
// @Tag Volume API
|
|
// @Description get all volumes for an owner
|
|
// @Param owner query string true "The owner"
|
|
// @Success 200 {array} object.Volume
|
|
// @router /get-volumes [get]
|
|
func (c *ApiController) GetVolumes() {
|
|
owner := c.Ctx.Query("owner")
|
|
volumes, err := object.GetVolumes(owner)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
c.ResponseOk(volumes)
|
|
}
|
|
|
|
// GetVolume
|
|
// @Title GetVolume
|
|
// @Tag Volume API
|
|
// @Description get a single volume
|
|
// @Param owner query string true "The owner"
|
|
// @Param name query string true "The volume name"
|
|
// @Success 200 {object} object.Volume
|
|
// @router /get-volume [get]
|
|
func (c *ApiController) GetVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
name := c.Ctx.Query("name")
|
|
|
|
volume, err := object.GetVolume(owner, name)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
c.ResponseOk(volume)
|
|
}
|
|
|
|
// CreateVolume
|
|
// @Title CreateVolume
|
|
// @Tag Volume API
|
|
// @Description create a block storage volume via a provider
|
|
// @Param owner query string true "The owner"
|
|
// @Param provider query string true "The provider name"
|
|
// @Param body body service.CreateVolumeSpec true "The volume spec"
|
|
// @Success 200 {object} object.Volume
|
|
// @router /create-volume [post]
|
|
func (c *ApiController) CreateVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
providerName := c.Ctx.Query("provider")
|
|
|
|
if owner == "" || providerName == "" {
|
|
c.ResponseError("owner and provider query parameters are required")
|
|
return
|
|
}
|
|
|
|
var spec service.CreateVolumeSpec
|
|
err := json.Unmarshal(c.Ctx.Body(), &spec)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
volume, err := object.CreateVolumeCloud(owner, providerName, &spec)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
c.ResponseOk(volume)
|
|
}
|
|
|
|
// DeleteVolume
|
|
// @Title DeleteVolume
|
|
// @Tag Volume API
|
|
// @Description delete a volume
|
|
// @Param owner query string true "The owner"
|
|
// @Param provider query string true "The provider name"
|
|
// @Param name query string true "The volume cloud ID"
|
|
// @Success 200 {object} controllers.Response
|
|
// @router /delete-volume [post]
|
|
func (c *ApiController) DeleteVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
providerName := c.Ctx.Query("provider")
|
|
name := c.Ctx.Query("name")
|
|
|
|
err := object.DeleteVolumeCloud(owner, providerName, name)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
c.Data["json"] = wrapActionResponse(true, nil)
|
|
c.ServeJSON()
|
|
}
|
|
|
|
// AttachVolume
|
|
// @Title AttachVolume
|
|
// @Tag Volume API
|
|
// @Description attach a volume to a machine
|
|
// @router /attach-volume [post]
|
|
func (c *ApiController) AttachVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
providerName := c.Ctx.Query("provider")
|
|
volumeName := c.Ctx.Query("volume")
|
|
machineName := c.Ctx.Query("machine")
|
|
|
|
err := object.AttachVolumeCloud(owner, providerName, volumeName, machineName)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
c.Data["json"] = wrapActionResponse(true, nil)
|
|
c.ServeJSON()
|
|
}
|
|
|
|
// DetachVolume
|
|
// @Title DetachVolume
|
|
// @Tag Volume API
|
|
// @Description detach a volume from its machine
|
|
// @router /detach-volume [post]
|
|
func (c *ApiController) DetachVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
providerName := c.Ctx.Query("provider")
|
|
volumeName := c.Ctx.Query("volume")
|
|
|
|
err := object.DetachVolumeCloud(owner, providerName, volumeName)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
c.Data["json"] = wrapActionResponse(true, nil)
|
|
c.ServeJSON()
|
|
}
|
|
|
|
// ResizeVolume
|
|
// @Title ResizeVolume
|
|
// @Tag Volume API
|
|
// @Description resize a volume
|
|
// @router /resize-volume [post]
|
|
func (c *ApiController) ResizeVolume() {
|
|
owner := c.Ctx.Query("owner")
|
|
providerName := c.Ctx.Query("provider")
|
|
volumeName := c.Ctx.Query("volume")
|
|
sizeStr := c.Ctx.Query("size")
|
|
|
|
size := 0
|
|
if sizeStr != "" {
|
|
size = parseInt(sizeStr)
|
|
}
|
|
if size <= 0 {
|
|
c.ResponseError("size parameter must be a positive integer (GB)")
|
|
return
|
|
}
|
|
|
|
err := object.ResizeVolumeCloud(owner, providerName, volumeName, size)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
|
|
c.Data["json"] = wrapActionResponse(true, nil)
|
|
c.ServeJSON()
|
|
}
|
|
|
|
func parseInt(s string) int {
|
|
n := 0
|
|
for _, c := range s {
|
|
if c >= '0' && c <= '9' {
|
|
n = n*10 + int(c-'0')
|
|
}
|
|
}
|
|
return n
|
|
}
|