182 lines
6.0 KiB
Go
182 lines
6.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// snapshot.go — снапшоты VM/CT: list (read), create, delete, rollback.
|
|
// Снапшот — точка восстановления гостя; delete/rollback необратимы →
|
|
// confirm + gate + probe.
|
|
|
|
func registerSnapshotTools(s *mcp.Server) {
|
|
s.AddTool(&mcp.Tool{
|
|
Name: "snapshot_list",
|
|
Description: "List snapshots of a VM/CT. Read-only.",
|
|
InputSchema: schema(map[string]any{
|
|
"node": strProps("Node name", true),
|
|
"vmid": strProps("VM/CT ID", true),
|
|
"kind": strProps("Guest type", false, "qemu", "lxc"),
|
|
"host": strProps("Cluster alias (default: primary)", false),
|
|
}, []string{"node", "vmid"}),
|
|
}, snapshotListHandler)
|
|
|
|
registerPatternTool(s, &mcp.Tool{
|
|
Name: "snapshot_create",
|
|
Description: "Create a snapshot of a VM/CT. Requires confirm + write permission.",
|
|
InputSchema: schema(map[string]any{
|
|
"node": strProps("Node name", true),
|
|
"vmid": strProps("VM/CT ID", true),
|
|
"name": strProps("Snapshot name", true),
|
|
"desc": strProps("Snapshot description", false),
|
|
"vmstate": boolProps("Include running state (RAM). Default: false", false),
|
|
"kind": strProps("Guest type", false, "qemu", "lxc"),
|
|
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
|
|
}, []string{"node", "vmid", "name", "confirm"}),
|
|
}, vmPatterns, snapshotCreateHandler)
|
|
|
|
registerPatternTool(s, &mcp.Tool{
|
|
Name: "snapshot_delete",
|
|
Description: "Delete a snapshot. Requires confirm + write permission.",
|
|
InputSchema: schema(map[string]any{
|
|
"node": strProps("Node name", true),
|
|
"vmid": strProps("VM/CT ID", true),
|
|
"name": strProps("Snapshot name", true),
|
|
"kind": strProps("Guest type", false, "qemu", "lxc"),
|
|
"confirm": strProps("Set to \"true\" to confirm permanent deletion", true, "true"),
|
|
}, []string{"node", "vmid", "name", "confirm"}),
|
|
}, vmPatterns, snapshotDeleteHandler)
|
|
|
|
registerPatternTool(s, &mcp.Tool{
|
|
Name: "snapshot_rollback",
|
|
Description: "Roll back a VM/CT to a snapshot (restores disk/state). Requires confirm + write permission.",
|
|
InputSchema: schema(map[string]any{
|
|
"node": strProps("Node name", true),
|
|
"vmid": strProps("VM/CT ID", true),
|
|
"name": strProps("Snapshot name", true),
|
|
"kind": strProps("Guest type", false, "qemu", "lxc"),
|
|
"confirm": strProps("Set to \"true\" to confirm destructive rollback", true, "true"),
|
|
}, []string{"node", "vmid", "name", "confirm"}),
|
|
}, vmPatterns, snapshotRollbackHandler)
|
|
}
|
|
|
|
func snapshotListHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
t, err := tenantFor(ctx, req)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
args := requestArgs(req)
|
|
node, vmid, host, r := resolveVMArgs(t, args)
|
|
if r != nil {
|
|
return r, nil
|
|
}
|
|
kind := guestKind(args)
|
|
cctx, cancel := timeout(ctx, t)
|
|
defer cancel()
|
|
data, err := t.SnapshotList(cctx, host, node, kind, vmid)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(pretty(data)), nil
|
|
}
|
|
|
|
func snapshotCreateHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
t, err := tenantFor(ctx, req)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
args := requestArgs(req)
|
|
node, vmid, host, r := resolveVMArgs(t, args)
|
|
if r != nil {
|
|
return r, nil
|
|
}
|
|
if err := confirm(args, "snapshot_create"); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
if err := gateVMWrite(t, host, vmid); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
name, err := requireName(args, "name")
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
form := url.Values{"snapname": {name}}
|
|
if d := getString(args, "desc", ""); d != "" {
|
|
form.Set("description", d)
|
|
}
|
|
if getBool(args, "vmstate", false) {
|
|
form.Set("vmstate", "1")
|
|
}
|
|
upid, err := t.SnapshotCreate(ctx, host, node, guestKind(args), vmid, form)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(upidMsg("snapshot_create", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
|
|
}
|
|
|
|
func snapshotDeleteHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
t, err := tenantFor(ctx, req)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
args := requestArgs(req)
|
|
node, vmid, host, r := resolveVMArgs(t, args)
|
|
if r != nil {
|
|
return r, nil
|
|
}
|
|
if err := confirm(args, "snapshot_delete"); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
if err := gateVMWrite(t, host, vmid); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
name, err := requireName(args, "name")
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
upid, err := t.SnapshotDelete(ctx, host, node, guestKind(args), vmid, name)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(upidMsg("snapshot_delete", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
|
|
}
|
|
|
|
func snapshotRollbackHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
t, err := tenantFor(ctx, req)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
args := requestArgs(req)
|
|
node, vmid, host, r := resolveVMArgs(t, args)
|
|
if r != nil {
|
|
return r, nil
|
|
}
|
|
if err := confirm(args, "snapshot_rollback"); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
if err := gateVMWrite(t, host, vmid); err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
name, err := requireName(args, "name")
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
upid, err := t.SnapshotRollback(ctx, host, node, guestKind(args), vmid, name)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(upidMsg("snapshot_rollback", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
|
|
}
|
|
|
|
// guestKind возвращает тип гостя (qemu/lxc) из аргумента, по умолчанию qemu.
|
|
func guestKind(args map[string]any) string {
|
|
k := getString(args, "kind", vmTypeQEMU)
|
|
if k != vmTypeQEMU && k != vmTypeLXC {
|
|
return vmTypeQEMU
|
|
}
|
|
return k
|
|
}
|