Files

162 lines
5.8 KiB
Go

package tools
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// vm_net.go — сетевые интерфейсы ВМ (net0..netN): добавить/обновить/удалить.
// Это структурные изменения конфига → write gate + confirm + probe. Модель
// задаёт iface (net0), модель/бридж; guard валидирует имя интерфейса.
func registerVMNetTools(s *mcp.Server) {
registerPatternTool(s, &mcp.Tool{
Name: "vm_add_network",
Description: "Add a network interface (netN) to a VM, e.g. net0 via vmbr0. Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM ID", true),
"iface": strProps("Interface slot (e.g. net0)", true),
"model": strProps("NIC model", true, "virtio", "e1000", "vmxnet3", "rtl8139"),
"bridge": strProps("Bridge (e.g. vmbr0)", true),
"mac": strProps("MAC (empty = auto)", false),
"firewall": boolProps("Enable firewall (default: true)", false),
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
}, []string{"node", "vmid", "iface", "bridge", "confirm"}),
}, vmPatterns, vmAddNetworkHandler)
registerPatternTool(s, &mcp.Tool{
Name: "vm_update_network",
Description: "Update an existing network interface (model/bridge/mac/firewall). Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM ID", true),
"iface": strProps("Interface slot (e.g. net0)", true),
"model": strProps("NIC model", false, "virtio", "e1000", "vmxnet3", "rtl8139"),
"bridge": strProps("Bridge (e.g. vmbr0)", false),
"mac": strProps("MAC address", false),
"firewall": boolProps("Enable firewall (default: true)", false),
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
}, []string{"node", "vmid", "iface", "confirm"}),
}, vmPatterns, vmUpdateNetworkHandler)
registerPatternTool(s, &mcp.Tool{
Name: "vm_remove_network",
Description: "Remove a network interface from a VM. Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM ID", true),
"iface": strProps("Interface slot (e.g. net0)", true),
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
}, []string{"node", "vmid", "iface", "confirm"}),
}, vmPatterns, vmRemoveNetworkHandler)
}
func vmAddNetworkHandler(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, "vm_add_network"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
iface, err := requireName(args, "iface")
if err != nil {
return errorResult(err.Error()), nil
}
model := getString(args, "model", "virtio")
bridge := getString(args, "bridge", "")
if bridge == "" {
return errorResult("'bridge' is required"), nil
}
value := fmt.Sprintf("%s,bridge=%s", model, bridge)
if mac := getString(args, "mac", ""); mac != "" {
value += ",mac=" + mac
}
if getBool(args, "firewall", true) {
value += ",firewall=1"
}
upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{iface: {value}})
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("vm_add_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil
}
func vmUpdateNetworkHandler(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, "vm_update_network"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
iface, err := requireName(args, "iface")
if err != nil {
return errorResult(err.Error()), nil
}
// update перезаписывает весь сетевой спецификатор: собираем из явных полей.
parts := []string{getString(args, "model", "virtio")}
if b := getString(args, "bridge", ""); b != "" {
parts = append(parts, "bridge="+b)
}
if m := getString(args, "mac", ""); m != "" {
parts = append(parts, "mac="+m)
}
if getBool(args, "firewall", true) {
parts = append(parts, "firewall=1")
}
upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{iface: {strings.Join(parts, ",")}})
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("vm_update_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil
}
func vmRemoveNetworkHandler(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, "vm_remove_network"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
iface, err := requireName(args, "iface")
if err != nil {
return errorResult(err.Error()), nil
}
upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{"delete": {iface}})
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("vm_remove_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil
}