功能:扩展云管资源运维、账号授权及监控管理
This commit is contained in:
1 parent
eef485306f
commit
6134d49c16
87 files changed
+29498
-126
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
Name string `json:"storage"`
|
||||
Available float64 `json:"avail"`
|
||||
Active int `json:"active"`
|
||||
Enabled int `json:"enabled"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (c *Client) VMConfig(ctx context.Context, node string, vmid int) (map[string]any, error) {
|
||||
var config map[string]any
|
||||
err := c.request(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/config", url.PathEscape(node), vmid), nil, &config)
|
||||
return config, err
|
||||
}
|
||||
func (c *Client) Storages(ctx context.Context, node string) ([]Storage, error) {
|
||||
var items []Storage
|
||||
err := c.request(ctx, "GET", "/nodes/"+url.PathEscape(node)+"/storage", nil, &items)
|
||||
return items, err
|
||||
}
|
||||
func (c *Client) HasPermission(ctx context.Context, path, permission string) (bool, error) {
|
||||
var permissions map[string]map[string]int
|
||||
err := c.request(ctx, "GET", "/access/permissions?path="+url.QueryEscape(path), nil, &permissions)
|
||||
return permissions[path][permission] == 1, err
|
||||
}
|
||||
func (c *Client) CanClone(ctx context.Context, node string, vmid int, full bool) (bool, error) {
|
||||
feature := "clone"
|
||||
if full {
|
||||
feature = "copy"
|
||||
}
|
||||
var data struct {
|
||||
HasFeature json.RawMessage `json:"hasFeature"`
|
||||
}
|
||||
err := c.request(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/feature?feature=%s", url.PathEscape(node), vmid, feature), nil, &data)
|
||||
return string(data.HasFeature) == "1" || string(data.HasFeature) == "true", err
|
||||
}
|
||||
func (c *Client) Clone(ctx context.Context, node string, source, target int, full bool, storage string) (string, error) {
|
||||
form := url.Values{"newid": {strconv.Itoa(target)}, "name": {fmt.Sprintf("vdi-%d", target)}, "full": {"0"}}
|
||||
if full {
|
||||
form.Set("full", "1")
|
||||
if storage != "" {
|
||||
form.Set("storage", storage)
|
||||
}
|
||||
}
|
||||
var upid string
|
||||
err := c.request(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%d/clone", url.PathEscape(node), source), form, &upid)
|
||||
if err == nil && upid == "" {
|
||||
err = errors.New("PVE did not return a clone task ID")
|
||||
}
|
||||
return upid, err
|
||||
}
|
||||
|
||||
// TemplateDisks rejects passthrough and unmeasurable disks before a clone is queued.
|
||||
// It returns per-storage virtual disk sizes for a conservative capacity check.
|
||||
func TemplateDisks(config map[string]any) (map[string]float64, error) {
|
||||
if fmt.Sprint(config["template"]) != "1" {
|
||||
return nil, errors.New("所选 VM 不是 Proxmox 模板")
|
||||
}
|
||||
if lock, ok := config["lock"]; ok && fmt.Sprint(lock) != "" {
|
||||
return nil, errors.New("模板处于锁定状态,请等待 PVE 操作结束")
|
||||
}
|
||||
disks := map[string]float64{}
|
||||
pattern := regexp.MustCompile(`^((scsi|sata|ide|virtio)\d+|efidisk0|tpmstate0)$`)
|
||||
for key, value := range config {
|
||||
if strings.HasPrefix(key, "hostpci") || strings.HasPrefix(key, "usb") || key == "args" || key == "hookscript" {
|
||||
return nil, errors.New("模板包含直通设备或宿主自定义脚本,请先制作通用模板")
|
||||
}
|
||||
if !pattern.MatchString(key) {
|
||||
continue
|
||||
}
|
||||
disk := fmt.Sprint(value)
|
||||
if strings.Contains(disk, "media=cdrom") || strings.Contains(disk, "cloudinit") {
|
||||
continue
|
||||
}
|
||||
volume := strings.Split(disk, ",")[0]
|
||||
storage, _, ok := strings.Cut(volume, ":")
|
||||
if !ok || storage == "" {
|
||||
return nil, errors.New("模板包含非存储卷磁盘,不能自动克隆")
|
||||
}
|
||||
size := float64(0)
|
||||
for _, part := range strings.Split(disk, ",") {
|
||||
if !strings.HasPrefix(part, "size=") {
|
||||
continue
|
||||
}
|
||||
text := strings.TrimPrefix(part, "size=")
|
||||
if len(text) == 0 {
|
||||
continue
|
||||
}
|
||||
multiplier := float64(1)
|
||||
if index := strings.IndexByte("KMGT", text[len(text)-1]); index >= 0 {
|
||||
for n := 0; n <= index; n++ {
|
||||
multiplier *= 1024
|
||||
}
|
||||
text = text[:len(text)-1]
|
||||
}
|
||||
amount, err := strconv.ParseFloat(text, 64)
|
||||
if err != nil || amount <= 0 || math.IsNaN(amount) || math.IsInf(amount, 0) {
|
||||
return nil, errors.New("无法识别模板磁盘容量")
|
||||
}
|
||||
size = amount * multiplier
|
||||
}
|
||||
if size <= 0 {
|
||||
return nil, errors.New("模板磁盘缺少容量信息")
|
||||
}
|
||||
disks[storage] += size
|
||||
}
|
||||
if len(disks) == 0 {
|
||||
return nil, errors.New("模板没有可克隆的系统磁盘")
|
||||
}
|
||||
return disks, nil
|
||||
}
|
||||
Reference in new issue
Block a user