Compare commits

...

23 Commits

Author SHA1 Message Date
f83a26ff6b update go mod 2025-10-10 21:55:08 +00:00
612f2a159f update go mod 2025-10-02 17:46:52 +00:00
9be70e5900 update golang to 1.25.1 2025-09-16 23:10:01 +00:00
1225439b4a update makefile 2025-09-09 20:52:33 +00:00
692b348994 bump api version to 1.0.0 2025-09-07 05:57:24 +00:00
eac682236b update mod 2025-09-02 20:13:34 +00:00
b092b506be update go mod 2025-08-12 22:15:58 +00:00
d887444e41 update go mod 2025-07-30 20:15:51 +00:00
e0fc7253ac reduce usage of string split 2025-07-28 18:49:49 +00:00
3ed570f60a add MP value for container mp volumes 2025-07-09 00:21:37 +00:00
81815bab82 update go mod 2025-05-18 08:04:30 +00:00
b2360500f2 add supported cpu types to node model 2025-04-30 21:20:29 +00:00
cd12365336 change sync endpoints to synchronous calls 2025-04-21 19:19:37 +00:00
8e73db22b7 add volume_id and net_id vaues for volumes and nets,
add boot order for instances
2025-04-17 18:03:01 +00:00
a1b4353b06 rename volume volid to file to better represent the meaning,
add raw value string to devices,
fix JSON to go naming for device_name vendor_name ... in devices,
bump API version to 0.03
2025-04-16 20:07:56 +00:00
a07a0a5e98 add verbose falg to make build 2025-04-08 23:56:08 +00:00
4ef3f76589 add disk type (prefix) to instance volume model 2025-04-08 23:41:33 +00:00
6a65ca2021 update go mod 2025-04-08 23:41:01 +00:00
6a1d13538d delete debug print functions 2025-04-07 19:55:46 +00:00
2265a8e580 fix bug with incomplete cluster model when node is down,
improve various error messages
2025-04-07 19:25:28 +00:00
cc35e38455 fix linting 2025-04-02 21:34:14 +00:00
d957198eaf update go to 1.24.0 2025-03-02 23:54:21 +00:00
5dbb87d772 cleanup commented code 2025-02-27 00:39:55 +00:00
7 changed files with 292 additions and 176 deletions

View File

@@ -1,11 +1,13 @@
.PHONY: build test clean .PHONY: build test clean
build: clean build: clean
CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/ . @echo "======================== Building Binary ======================="
CGO_ENABLED=0 go build -ldflags="-s -w" -v -o dist/ .
test: clean test: clean
go run . go run .
clean: clean:
@echo "======================== Cleaning Project ======================"
go clean go clean
rm -f dist/* rm -rf dist/*

View File

@@ -13,7 +13,7 @@ import (
"github.com/luthermonson/go-proxmox" "github.com/luthermonson/go-proxmox"
) )
const APIVersion string = "0.0.2" const APIVersion string = "1.0.0"
var client ProxmoxClient var client ProxmoxClient
@@ -25,7 +25,7 @@ func Run() {
flag.Parse() flag.Parse()
config := GetConfig(*configPath) config := GetConfig(*configPath)
log.Println("Initialized config from " + *configPath) log.Printf("Initialized config from %s", *configPath)
token := fmt.Sprintf(`%s@%s!%s`, config.PVE.Token.USER, config.PVE.Token.REALM, config.PVE.Token.ID) token := fmt.Sprintf(`%s@%s!%s`, config.PVE.Token.USER, config.PVE.Token.REALM, config.PVE.Token.ID)
client = NewClient(config.PVE.URL, token, config.PVE.Token.Secret) client = NewClient(config.PVE.URL, token, config.PVE.Token.Secret)
@@ -120,17 +120,17 @@ func Run() {
}) })
router.POST("/sync", func(c *gin.Context) { router.POST("/sync", func(c *gin.Context) {
go func() { //go func() {
start := time.Now() start := time.Now()
log.Printf("Starting cluster sync\n") log.Printf("Starting cluster sync\n")
cluster.Sync() cluster.Sync()
log.Printf("Synced cluster in %fs\n", time.Since(start).Seconds()) log.Printf("Synced cluster in %fs\n", time.Since(start).Seconds())
}() //}()
}) })
router.POST("/nodes/:node/sync", func(c *gin.Context) { router.POST("/nodes/:node/sync", func(c *gin.Context) {
nodeid := c.Param("node") nodeid := c.Param("node")
go func() { //go func() {
start := time.Now() start := time.Now()
log.Printf("Starting %s sync\n", nodeid) log.Printf("Starting %s sync\n", nodeid)
err := cluster.RebuildHost(nodeid) err := cluster.RebuildHost(nodeid)
@@ -141,7 +141,7 @@ func Run() {
log.Printf("Synced %s in %fs\n", nodeid, time.Since(start).Seconds()) log.Printf("Synced %s in %fs\n", nodeid, time.Since(start).Seconds())
return return
} }
}() //}()
}) })
router.POST("/nodes/:node/instances/:vmid/sync", func(c *gin.Context) { router.POST("/nodes/:node/instances/:vmid/sync", func(c *gin.Context) {
@@ -152,7 +152,7 @@ func Run() {
return return
} }
go func() { //go func() {
start := time.Now() start := time.Now()
log.Printf("Starting %s.%d sync\n", nodeid, vmid) log.Printf("Starting %s.%d sync\n", nodeid, vmid)
@@ -176,7 +176,7 @@ func Run() {
log.Printf("Synced %s.%d in %fs\n", nodeid, vmid, time.Since(start).Seconds()) log.Printf("Synced %s.%d in %fs\n", nodeid, vmid, time.Since(start).Seconds())
return return
} }
}() //}()
}) })
router.Run("0.0.0.0:" + strconv.Itoa(config.ListenPort)) router.Run("0.0.0.0:" + strconv.Itoa(config.ListenPort))

View File

@@ -2,7 +2,7 @@ package app
import ( import (
"fmt" "fmt"
"strconv" "log"
"strings" "strings"
) )
@@ -26,8 +26,9 @@ func (cluster *Cluster) Sync() error {
for _, hostName := range nodes { for _, hostName := range nodes {
// rebuild node // rebuild node
err := cluster.RebuildHost(hostName) err := cluster.RebuildHost(hostName)
if err != nil { if err != nil { // if an error was encountered, continue and log the error
return err log.Print(err.Error())
continue
} }
} }
@@ -66,8 +67,8 @@ func (cluster *Cluster) GetNode(hostName string) (*Node, error) {
func (cluster *Cluster) RebuildHost(hostName string) error { func (cluster *Cluster) RebuildHost(hostName string) error {
host, err := cluster.pve.Node(hostName) host, err := cluster.pve.Node(hostName)
if err != nil { if err != nil { // host is probably down or otherwise unreachable
return err return fmt.Errorf("error retrieving %s: %s, possibly down?", hostName, err.Error())
} }
// aquire lock on host, release on return // aquire lock on host, release on return
@@ -84,8 +85,9 @@ func (cluster *Cluster) RebuildHost(hostName string) error {
} }
for _, vmid := range vms { for _, vmid := range vms {
err := host.RebuildInstance(VM, vmid) err := host.RebuildInstance(VM, vmid)
if err != nil { if err != nil { // if an error was encountered, continue and log the error
return err log.Print(err.Error())
continue
} }
} }
@@ -147,13 +149,13 @@ func (host *Node) RebuildInstance(instancetype InstanceType, vmid uint) error {
var err error var err error
instance, err = host.VirtualMachine(vmid) instance, err = host.VirtualMachine(vmid)
if err != nil { if err != nil {
return err return fmt.Errorf("error retrieving %d: %s, possibly down?", vmid, err.Error())
} }
} else if instancetype == CT { } else if instancetype == CT {
var err error var err error
instance, err = host.Container(vmid) instance, err = host.Container(vmid)
if err != nil { if err != nil {
return err return fmt.Errorf("error retrieving %d: %s, possibly down?", vmid, err.Error())
} }
} }
@@ -176,6 +178,10 @@ func (host *Node) RebuildInstance(instancetype InstanceType, vmid uint) error {
instance.RebuildDevice(host, deviceid) instance.RebuildDevice(host, deviceid)
} }
if instance.Type == VM {
instance.RebuildBoot()
}
return nil return nil
} }
@@ -187,6 +193,9 @@ func (instance *Instance) RebuildVolume(host *Node, volid string) error {
return err return err
} }
voltype := AnyPrefixes(volid, VolumeTypes)
volume.Type = voltype
volume.Volume_ID = VolumeID(volid)
instance.Volumes[VolumeID(volid)] = volume instance.Volumes[VolumeID(volid)] = volume
return nil return nil
@@ -194,17 +203,14 @@ func (instance *Instance) RebuildVolume(host *Node, volid string) error {
func (instance *Instance) RebuildNet(netid string) error { func (instance *Instance) RebuildNet(netid string) error {
net := instance.configNets[netid] net := instance.configNets[netid]
idnum, err := strconv.ParseUint(strings.TrimPrefix(netid, "net"), 10, 64)
if err != nil {
return err
}
netinfo, err := GetNetInfo(net) netinfo, err := GetNetInfo(net)
netinfo.Net_ID = NetID(netid)
if err != nil { if err != nil {
return nil return nil
} }
instance.Nets[NetID(idnum)] = netinfo instance.Nets[NetID(netid)] = netinfo
return nil return nil
} }
@@ -216,21 +222,62 @@ func (instance *Instance) RebuildDevice(host *Node, deviceid string) error {
} }
hostDeviceBusID := DeviceID(strings.Split(instanceDevice, ",")[0]) hostDeviceBusID := DeviceID(strings.Split(instanceDevice, ",")[0])
instanceDeviceBusID := DeviceID(deviceid)
idbid, err := strconv.ParseUint(strings.TrimPrefix(deviceid, "hostpci"), 10, 64)
if err != nil {
return err
}
instanceDeviceBusID := InstanceDeviceID(idbid)
if DeviceBusIDIsSuperDevice(hostDeviceBusID) { if DeviceBusIDIsSuperDevice(hostDeviceBusID) {
instance.Devices[InstanceDeviceID(instanceDeviceBusID)] = host.Devices[DeviceID(hostDeviceBusID)] instance.Devices[DeviceID(instanceDeviceBusID)] = host.Devices[DeviceBus(hostDeviceBusID)]
for _, function := range instance.Devices[InstanceDeviceID(instanceDeviceBusID)].Functions { for _, function := range instance.Devices[DeviceID(instanceDeviceBusID)].Functions {
function.Reserved = true function.Reserved = true
} }
} else { } else {
// sub function assignment not supported yet // sub function assignment not supported yet
} }
instance.Devices[DeviceID(instanceDeviceBusID)].Device_ID = DeviceID(deviceid)
instance.Devices[DeviceID(instanceDeviceBusID)].Value = instanceDevice
return nil return nil
} }
func (instance *Instance) RebuildBoot() {
instance.Boot = BootOrder{}
eligibleBoot := map[string]bool{}
for k := range instance.Volumes {
eligiblePrefix := AnyPrefixes(string(k), []string{"sata", "scsi", "ide"})
if eligiblePrefix != "" {
eligibleBoot[string(k)] = true
}
}
for k := range instance.Nets {
eligibleBoot[string(k)] = true
}
bootOrder := PVEObjectStringToMap(instance.configBoot)["order"]
if len(bootOrder) != 0 {
for bootTarget := range strings.SplitSeq(bootOrder, ";") { // iterate over elements selected for boot, add them to Enabled, and remove them from eligible boot target
_, isEligible := eligibleBoot[bootTarget]
if val, ok := instance.Volumes[VolumeID(bootTarget)]; ok && isEligible { // if the item is eligible and is in volumes
instance.Boot.Enabled = append(instance.Boot.Enabled, val)
delete(eligibleBoot, bootTarget)
} else if val, ok := instance.Nets[NetID(bootTarget)]; ok && isEligible { // if the item is eligible and is in nets
instance.Boot.Enabled = append(instance.Boot.Enabled, val)
delete(eligibleBoot, bootTarget)
} else { // item is not eligible for boot but is included in the boot order
log.Printf("Encountered enabled but non-eligible boot target %s in instance %s\n", bootTarget, instance.Name)
delete(eligibleBoot, bootTarget)
}
}
}
for bootTarget, isEligible := range eligibleBoot { // iterate over remaining items, add them to Disabled
if val, ok := instance.Volumes[VolumeID(bootTarget)]; ok && isEligible { // if the item is eligible and is in volumes
instance.Boot.Disabled = append(instance.Boot.Disabled, val)
} else if val, ok := instance.Nets[NetID(bootTarget)]; ok && isEligible { // if the item is eligible and is in nets
instance.Boot.Disabled = append(instance.Boot.Disabled, val)
} else { // item is not eligible and is not already in the boot order, skip adding to model
log.Printf("Encountered disabled and non-eligible boot target %s in instance %s\n", bootTarget, instance.Name)
}
}
}

View File

@@ -15,6 +15,20 @@ type ProxmoxClient struct {
client *proxmox.Client client *proxmox.Client
} }
type PVEDevice struct { // used only for requests to PVE
ID string `json:"id"`
Device_Name string `json:"device_name"`
Vendor_Name string `json:"vendor_name"`
Subsystem_Device_Name string `json:"subsystem_device_name"`
Subsystem_Vendor_Name string `json:"subsystem_vendor_name"`
}
type PVEProctype struct {
Custom int
Name string
Vendor string
}
func NewClient(url string, token string, secret string) ProxmoxClient { func NewClient(url string, token string, secret string) ProxmoxClient {
HTTPClient := http.Client{ HTTPClient := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
@@ -59,7 +73,7 @@ func (pve ProxmoxClient) Nodes() ([]string, error) {
// Gets a Node's resources but does not recursively expand instances // Gets a Node's resources but does not recursively expand instances
func (pve ProxmoxClient) Node(nodeName string) (*Node, error) { func (pve ProxmoxClient) Node(nodeName string) (*Node, error) {
host := Node{} host := Node{}
host.Devices = make(map[DeviceID]*Device) host.Devices = make(map[DeviceBus]*Device)
host.Instances = make(map[InstanceID]*Instance) host.Instances = make(map[InstanceID]*Instance)
node, err := pve.client.Node(context.Background(), nodeName) node, err := pve.client.Node(context.Background(), nodeName)
@@ -78,24 +92,33 @@ func (pve ProxmoxClient) Node(nodeName string) (*Node, error) {
if len(x) != 2 { // this should always be true, but skip if not if len(x) != 2 { // this should always be true, but skip if not
continue continue
} }
deviceid := DeviceID(x[0]) deviceid := DeviceBus(x[0])
functionid := FunctionID(x[1]) functionid := FunctionID(x[1])
if _, ok := host.Devices[deviceid]; !ok { if _, ok := host.Devices[deviceid]; !ok {
host.Devices[deviceid] = &Device{ host.Devices[deviceid] = &Device{
DeviceID: deviceid, Device_Bus: deviceid,
DeviceName: device.DeviceName, Device_Name: device.Device_Name,
VendorName: device.VendorName, Vendor_Name: device.Vendor_Name,
Functions: make(map[FunctionID]*Function), Functions: make(map[FunctionID]*Function),
} }
} }
host.Devices[deviceid].Functions[functionid] = &Function{ host.Devices[deviceid].Functions[functionid] = &Function{
FunctionID: functionid, Function_ID: functionid,
FunctionName: device.SubsystemDeviceName, Function_Name: device.Subsystem_Device_Name,
VendorName: device.SubsystemVendorName, Vendor_Name: device.Subsystem_Vendor_Name,
Reserved: false, Reserved: false,
} }
} }
proctypes := []PVEProctype{}
err = pve.client.Get(context.Background(), fmt.Sprintf("/nodes/%s/capabilities/qemu/cpu", nodeName), &proctypes)
if err != nil {
return &host, err
}
for _, proctype := range proctypes {
host.Proctypes = append(host.Proctypes, proctype.Name)
}
host.Name = node.Name host.Name = node.Name
host.Cores = uint64(node.CPUInfo.CPUs) host.Cores = uint64(node.CPUInfo.CPUs)
host.Memory = uint64(node.Memory.Total) host.Memory = uint64(node.Memory.Total)
@@ -130,6 +153,7 @@ func (host *Node) VirtualMachine(VMID uint) (*Instance, error) {
instance.configHostPCIs = config.MergeHostPCIs() instance.configHostPCIs = config.MergeHostPCIs()
instance.configNets = config.MergeNets() instance.configNets = config.MergeNets()
instance.configDisks = MergeVMDisksAndUnused(config) instance.configDisks = MergeVMDisksAndUnused(config)
instance.configBoot = config.Boot
instance.pveconfig = config instance.pveconfig = config
instance.Type = VM instance.Type = VM
@@ -140,7 +164,7 @@ func (host *Node) VirtualMachine(VMID uint) (*Instance, error) {
instance.Memory = uint64(vm.VirtualMachineConfig.Memory) * MiB instance.Memory = uint64(vm.VirtualMachineConfig.Memory) * MiB
instance.Volumes = make(map[VolumeID]*Volume) instance.Volumes = make(map[VolumeID]*Volume)
instance.Nets = make(map[NetID]*Net) instance.Nets = make(map[NetID]*Net)
instance.Devices = make(map[InstanceDeviceID]*Device) instance.Devices = make(map[DeviceID]*Device)
return &instance, nil return &instance, nil
} }
@@ -208,9 +232,11 @@ func MergeCTDisksAndUnused(cc *proxmox.ContainerConfig) map[string]string {
func GetVolumeInfo(host *Node, volume string) (*Volume, error) { func GetVolumeInfo(host *Node, volume string) (*Volume, error) {
volumeData := Volume{} volumeData := Volume{}
storageID := strings.Split(volume, ":")[0] volumeObj := PVEObjectStringToMap(volume)
volumeID := strings.Split(volume, ",")[0] volumeFile := volumeObj[""]
storage, err := host.pvenode.Storage(context.Background(), storageID) volumeStorage := strings.Split(volumeFile, ":")[0]
storage, err := host.pvenode.Storage(context.Background(), volumeStorage)
if err != nil { if err != nil {
return &volumeData, nil return &volumeData, nil
} }
@@ -221,37 +247,58 @@ func GetVolumeInfo(host *Node, volume string) (*Volume, error) {
} }
for _, c := range content { for _, c := range content {
if c.Volid == volumeID { if c.Volid == volumeFile {
volumeData.Storage = storageID volumeData.Storage = volumeStorage
volumeData.Format = c.Format volumeData.Format = c.Format
volumeData.Size = uint64(c.Size) volumeData.Size = uint64(c.Size)
volumeData.Volid = VolumeID(volumeID) volumeData.File = volumeFile
volumeData.MP = volumeObj["mp"]
} }
} }
return &volumeData, nil return &volumeData, nil
} }
func GetNetInfo(net string) (*Net, error) { func GetNetInfo(netstring string) (*Net, error) {
n := Net{} n := Net{}
for _, val := range strings.Split(net, ",") { netobj := PVEObjectStringToMap(netstring)
if strings.HasPrefix(val, "rate=") {
rate, err := strconv.ParseUint(strings.TrimPrefix(val, "rate="), 10, 64) rate, err := strconv.ParseUint(netobj["rate"], 10, 64)
if err != nil { if err != nil {
return &n, err return &n, err
} }
n.Rate = rate n.Rate = rate
} else if strings.HasPrefix(val, "tag=") {
vlan, err := strconv.ParseUint(strings.TrimPrefix(val, "tag="), 10, 64) vlan, err := strconv.ParseUint(netobj["tag"], 10, 64)
if err != nil { if err != nil {
return &n, err return &n, err
} }
n.VLAN = vlan n.VLAN = vlan
}
}
n.Value = net n.Value = netstring
return &n, nil return &n, nil
} }
// most pve objects (nets, disks, pcie, etc) have the following similar format:
// objname: v1,k2=v2,k3=v3,k4=v4 ...
// this function maps such strings to a map so that each individual key or value can be found more quickly
// in pcie or disks, the first value often does not have a key name, in such cases the key will be empty string ""
func PVEObjectStringToMap(objectstring string) map[string]string {
objectmap := map[string]string{}
for v := range strings.SplitSeq(objectstring, ",") {
key := ""
val := ""
if strings.Contains(v, "=") {
x := strings.Split(v, "=")
key = x[0]
val = x[1]
} else {
key = ""
val = v
}
objectmap[key] = val
}
return objectmap
}

View File

@@ -18,8 +18,9 @@ type Node struct {
Cores uint64 `json:"cores"` Cores uint64 `json:"cores"`
Memory uint64 `json:"memory"` Memory uint64 `json:"memory"`
Swap uint64 `json:"swap"` Swap uint64 `json:"swap"`
Devices map[DeviceID]*Device `json:"devices"` Devices map[DeviceBus]*Device `json:"devices"`
Instances map[InstanceID]*Instance `json:"instances"` Instances map[InstanceID]*Instance `json:"instances"`
Proctypes []string `json:"cpus"`
pvenode *proxmox.Node pvenode *proxmox.Node
} }
@@ -41,50 +42,64 @@ type Instance struct {
Swap uint64 `json:"swap"` Swap uint64 `json:"swap"`
Volumes map[VolumeID]*Volume `json:"volumes"` Volumes map[VolumeID]*Volume `json:"volumes"`
Nets map[NetID]*Net `json:"nets"` Nets map[NetID]*Net `json:"nets"`
Devices map[InstanceDeviceID]*Device `json:"devices"` Devices map[DeviceID]*Device `json:"devices"`
pveconfig interface{} Boot BootOrder `json:"boot"`
pveconfig any
configDisks map[string]string configDisks map[string]string
configNets map[string]string configNets map[string]string
configHostPCIs map[string]string configHostPCIs map[string]string
configBoot string
}
var VolumeTypes = []string{
"sata",
"scsi",
"ide",
"rootfs",
"mp",
"unused",
} }
type VolumeID string type VolumeID string
type Volume struct { type Volume struct {
Volume_ID VolumeID `json:"volume_id"`
Type string `json:"type"`
Storage string `json:"storage"` Storage string `json:"storage"`
Format string `json:"format"` Format string `json:"format"`
Size uint64 `json:"size"` Size uint64 `json:"size"`
Volid VolumeID `json:"volid"` File string `json:"file"`
MP string `json:"mp"`
} }
type NetID uint64 type NetID string
type Net struct { type Net struct {
Net_ID NetID `json:"net_id"`
Value string `json:"value"` Value string `json:"value"`
Rate uint64 `json:"rate"` Rate uint64 `json:"rate"`
VLAN uint64 `json:"vlan"` VLAN uint64 `json:"vlan"`
} }
type PVEDevice struct {
ID string `json:"id"`
DeviceName string `json:"device_name"`
VendorName string `json:"vendor_name"`
SubsystemDeviceName string `json:"subsystem_device_name"`
SubsystemVendorName string `json:"subsystem_vendor_name"`
}
type DeviceID string type DeviceID string
type InstanceDeviceID uint64 type DeviceBus string
type Device struct { type Device struct {
DeviceID DeviceID `json:"device_id"` Device_ID DeviceID `json:"device_id"`
DeviceName string `json:"device_name"` Device_Bus DeviceBus `json:"device_bus"`
VendorName string `json:"vendor_name"` Device_Name string `json:"device_name"`
Vendor_Name string `json:"vendor_name"`
Functions map[FunctionID]*Function `json:"functions"` Functions map[FunctionID]*Function `json:"functions"`
Reserved bool `json:"reserved"` Reserved bool `json:"reserved"`
Value string
} }
type FunctionID string type FunctionID string
type Function struct { type Function struct {
FunctionID FunctionID `json:"function_id"` Function_ID FunctionID `json:"function_id"`
FunctionName string `json:"subsystem_device_name"` Function_Name string `json:"subsystem_device_name"`
VendorName string `json:"subsystem_vendor_name"` Vendor_Name string `json:"subsystem_vendor_name"`
Reserved bool `json:"reserved"` Reserved bool `json:"reserved"`
} }
type BootOrder struct {
Enabled []any `json:"enabled"`
Disabled []any `json:"disabled"`
}

View File

@@ -36,7 +36,7 @@ func GetConfig(configPath string) Config {
return config return config
} }
// returns if a device pcie bus id is a super device or subsystem device // checks if a device pcie bus id is a super device or subsystem device
// //
// subsystem devices always has the format xxxx:yy.z, whereas super devices have the format xxxx:yy // subsystem devices always has the format xxxx:yy.z, whereas super devices have the format xxxx:yy
// //
@@ -45,13 +45,15 @@ func DeviceBusIDIsSuperDevice(BusID DeviceID) bool {
return !strings.ContainsRune(string(BusID), '.') return !strings.ContainsRune(string(BusID), '.')
} }
// returns if a device pcie bus id is a subdevice of specified super device // checks if string s has one of any prefixes, and returns the prefix or "" if there was no match
// //
// subsystem devices always has the format xxxx:yy.z, whereas super devices have the format xxxx:yy // matches the first prefix match in array order
// func AnyPrefixes(s string, prefixes []string) string {
// returns true if BusID has prefix SuperDeviceBusID and SuperDeviceBusID is a Super Device for _, prefix := range prefixes {
/* if strings.HasPrefix(s, prefix) {
func DeviceBusIDIsSubDevice(BusID string, SuperDeviceBusID string) bool { return prefix
return DeviceBusIDIsSuperDevice(SuperDeviceBusID) && strings.HasPrefix(BusID, SuperDeviceBusID) }
}
return ""
} }
*/

63
go.mod
View File

@@ -1,52 +1,55 @@
module proxmoxaas-fabric module proxmoxaas-fabric
go 1.23.6 go 1.25.1
require ( require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.11.0
github.com/luthermonson/go-proxmox v0.2.1 github.com/luthermonson/go-proxmox v0.2.3
) )
require ( require (
github.com/buger/goterm v1.0.4 // indirect github.com/buger/goterm v1.0.4 // indirect
github.com/bytedance/sonic v1.12.8 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/bytedance/sonic v1.14.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/diskfs/go-diskfs v1.4.2 // indirect github.com/diskfs/go-diskfs v1.7.0 // indirect
github.com/djherbis/times v1.6.0 // indirect github.com/djherbis/times v1.6.0 // indirect
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect github.com/elliotwutingfeng/asciiset v0.0.0-20250812055617-fb43ac3ba420 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/jinzhu/copier v0.4.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/knz/go-libedit v1.10.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/magefile/mage v1.15.0 // indirect github.com/magefile/mage v1.15.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/xattr v0.4.9 // indirect github.com/pkg/xattr v0.4.12 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect github.com/quic-go/qpack v0.5.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/quic-go/quic-go v0.55.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
golang.org/x/arch v0.14.0 // indirect go.uber.org/mock v0.6.0 // indirect
golang.org/x/crypto v0.32.0 // indirect golang.org/x/arch v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/net v0.46.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect golang.org/x/sync v0.17.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
) )