From 65dd897130862265561818b5e77d037087f1187f Mon Sep 17 00:00:00 2001 From: sp-yduck Date: Sat, 22 Jul 2023 19:19:09 +0900 Subject: [PATCH] add proxmox pkg --- api/qemu_agent_type.go | 12 +++++ proxmox/client.go | 14 ----- proxmox/node.go | 15 ++++++ proxmox/qemu.go | 116 +++++++++++++++++++++++++++++++++++++++++ proxmox/service.go | 98 ++++++++++++++++++++++++++++++++++ 5 files changed, 241 insertions(+), 14 deletions(-) create mode 100644 api/qemu_agent_type.go delete mode 100644 proxmox/client.go create mode 100644 proxmox/node.go create mode 100644 proxmox/qemu.go create mode 100644 proxmox/service.go diff --git a/api/qemu_agent_type.go b/api/qemu_agent_type.go new file mode 100644 index 0000000..63fc06e --- /dev/null +++ b/api/qemu_agent_type.go @@ -0,0 +1,12 @@ +package api + +type OSInfo struct { + ID string `json:"id"` + KernelRelease string `json:"kernel-release"` + KernelVersion string `json:"kernel-version"` + Machine string `json:"machine"` + Name string `json:"name"` + PrettyName string `json:"pretty-name"` + Version string `json:"version"` + VersionID string `json:"version-id"` +} diff --git a/proxmox/client.go b/proxmox/client.go deleted file mode 100644 index dd7e8de..0000000 --- a/proxmox/client.go +++ /dev/null @@ -1,14 +0,0 @@ -package proxmox - -import ( - "github.com/sp-yduck/proxmox-go/api" - "github.com/sp-yduck/proxmox-go/rest" -) - -type Service struct { - restclient *rest.RESTClient -} - -func (s *Service) Nodes() ([]*api.Node, error) { - return s.restclient.GetNodes() -} diff --git a/proxmox/node.go b/proxmox/node.go new file mode 100644 index 0000000..1f2b3b2 --- /dev/null +++ b/proxmox/node.go @@ -0,0 +1,15 @@ +package proxmox + +import ( + "context" + + "github.com/sp-yduck/proxmox-go/api" +) + +func (s *Service) Nodes(ctx context.Context) ([]*api.Node, error) { + return s.restclient.GetNodes(ctx) +} + +func (s *Service) Node(ctx context.Context, name string) (*api.Node, error) { + return s.restclient.GetNode(ctx, name) +} diff --git a/proxmox/qemu.go b/proxmox/qemu.go new file mode 100644 index 0000000..46b1ef4 --- /dev/null +++ b/proxmox/qemu.go @@ -0,0 +1,116 @@ +package proxmox + +import ( + "context" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/sp-yduck/proxmox-go/api" + "github.com/sp-yduck/proxmox-go/rest" +) + +type VirtualMachine struct { + restclient *rest.RESTClient + node string + vm *api.VirtualMachine + config *api.VirtualMachineConfig +} + +const ( + UUIDFormat = `[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}` +) + +// VirtualMachines returns all qemus across all proxmox nodes +func (s *Service) VirtualMachines(ctx context.Context) ([]*api.VirtualMachine, error) { + nodes, err := s.Nodes(ctx) + if err != nil { + return nil, err + } + var vms []*api.VirtualMachine + for _, node := range nodes { + v, err := s.restclient.GetVirtualMachines(ctx, node.Node) + if err != nil { + return nil, err + } + vms = append(vms, v...) + } + return vms, nil +} + +func (s *Service) NewVirtualMachine(ctx context.Context, vmid int) (*VirtualMachine, error) { + nodes, err := s.Nodes(ctx) + if err != nil { + return nil, err + } + for _, node := range nodes { + vm, err := s.restclient.GetVirtualMachine(ctx, node.Node, vmid) + if err != nil { + if rest.IsNotFound(err) { + continue + } + return nil, err + } + return &VirtualMachine{restclient: s.restclient, vm: vm, node: node.Node}, nil + } + return nil, rest.NotFoundErr +} + +func (s *Service) VirtualMachineFromUUID(ctx context.Context, uuid string) (*VirtualMachine, error) { + nodes, err := s.Nodes(ctx) + if err != nil { + return nil, err + } + for _, node := range nodes { + vms, err := s.restclient.GetVirtualMachines(ctx, node.Node) + if err != nil { + return nil, err + } + for _, vm := range vms { + config, err := s.restclient.GetVirtualMachineConfig(ctx, node.Node, vm.VMID) + if err != nil { + return nil, err + } + vmuuid, err := convertSMBiosToUUID(config.SMBios1) + if err != nil { + return nil, err + } + if vmuuid == uuid { + return &VirtualMachine{restclient: s.restclient, vm: vm, node: node.Node, config: config}, nil + } + } + } + return nil, rest.NotFoundErr +} + +func convertSMBiosToUUID(smbios string) (string, error) { + re := regexp.MustCompile(fmt.Sprintf("uuid=%s", UUIDFormat)) + match := re.FindString(smbios) + if match == "" { + return "", errors.New("failed to fetch uuid form smbios") + } + // match: uuid= + return strings.Split(match, "=")[1], nil +} + +func (c *VirtualMachine) GetConfig(ctx context.Context) (*api.VirtualMachineConfig, error) { + if c.config != nil { + return c.config, nil + } + config, err := c.restclient.GetVirtualMachineConfig(ctx, c.node, c.vm.VMID) + if err != nil { + return nil, err + } + c.config = config + return c.config, err +} + +func (c *VirtualMachine) GetOSInfo(ctx context.Context) (*api.OSInfo, error) { + var osInfo *api.OSInfo + path := fmt.Sprintf("/nodes/%s/qemu/%d/agent/get-osinfo", c.node, c.vm.VMID) + if err := c.restclient.Get(ctx, path, &osInfo); err != nil { + return nil, err + } + return osInfo, nil +} diff --git a/proxmox/service.go b/proxmox/service.go new file mode 100644 index 0000000..af1c0bb --- /dev/null +++ b/proxmox/service.go @@ -0,0 +1,98 @@ +package proxmox + +import ( + "crypto/tls" + "errors" + "net/http" + + "github.com/sp-yduck/proxmox-go/rest" +) + +type Service struct { + restclient *rest.RESTClient +} + +type AuthConfig struct { + Username string + Password string + TokenID string + Secret string +} + +func NewService(url string, authConfig AuthConfig, insecure bool) (*Service, error) { + var loginOption rest.ClientOption + if authConfig.Username != "" && authConfig.Password != "" { + loginOption = rest.WithUserPassword(authConfig.Username, authConfig.Password) + } else if authConfig.TokenID != "" && authConfig.Secret != "" { + loginOption = rest.WithAPIToken(authConfig.TokenID, authConfig.Secret) + } else { + return nil, errors.New("invalid authentication config") + } + clientOptions := []rest.ClientOption{loginOption} + + if insecure { + baseClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + clientOptions = append(clientOptions, rest.WithClient(baseClient)) + } + + restclient, err := rest.NewRESTClient(url, clientOptions...) + if err != nil { + return nil, err + } + return &Service{restclient: restclient}, nil +} + +func NewServiceWithUserPassword(url, user, password string, insecure bool) (*Service, error) { + clientOptions := []rest.ClientOption{ + rest.WithUserPassword(user, password), + } + + if insecure { + baseClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + clientOptions = append(clientOptions, rest.WithClient(baseClient)) + } + + restclient, err := rest.NewRESTClient(url, clientOptions...) + if err != nil { + return nil, err + } + return &Service{restclient: restclient}, nil +} + +func NewServiceWithAPIToken(url, tokenid, secret string, insecure bool) (*Service, error) { + clientOptions := []rest.ClientOption{ + rest.WithAPIToken(tokenid, secret), + } + if insecure { + baseClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + clientOptions = append(clientOptions, rest.WithClient(baseClient)) + } + + restclient, err := rest.NewRESTClient(url, clientOptions...) + if err != nil { + return nil, err + } + return &Service{restclient: restclient}, nil +} + +func (s *Service) RESTClient() *rest.RESTClient { + return s.restclient +}