- Complete project structure with Next.js frontend - GraphQL API backend with Apollo Server - Portal application with NextAuth - Crossplane Proxmox provider - GitOps configurations - CI/CD pipelines - Testing infrastructure (Vitest, Jest, Go tests) - Error handling and monitoring - Security hardening - UI component library - Documentation
263 lines
5.6 KiB
Go
263 lines
5.6 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Client represents a Proxmox API client
|
|
type Client struct {
|
|
endpoint string
|
|
username string
|
|
password string
|
|
token string
|
|
}
|
|
|
|
// NewClient creates a new Proxmox API client
|
|
func NewClient(endpoint, username, password string) *Client {
|
|
return &Client{
|
|
endpoint: endpoint,
|
|
username: username,
|
|
password: password,
|
|
}
|
|
}
|
|
|
|
// RetryConfig defines retry behavior
|
|
type RetryConfig struct {
|
|
MaxRetries int
|
|
BaseDelay time.Duration
|
|
MaxDelay time.Duration
|
|
}
|
|
|
|
// DefaultRetryConfig returns default retry configuration
|
|
func DefaultRetryConfig() RetryConfig {
|
|
return RetryConfig{
|
|
MaxRetries: 3,
|
|
BaseDelay: time.Second,
|
|
MaxDelay: 30 * time.Second,
|
|
}
|
|
}
|
|
|
|
// RetryableError indicates an error that should be retried
|
|
type RetryableError struct {
|
|
Err error
|
|
RetryAfter time.Duration
|
|
}
|
|
|
|
func (e *RetryableError) Error() string {
|
|
return e.Err.Error()
|
|
}
|
|
|
|
// IsRetryable checks if an error is retryable
|
|
func IsRetryable(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
_, ok := err.(*RetryableError)
|
|
return ok
|
|
}
|
|
|
|
// Retry executes a function with retry logic
|
|
func Retry(ctx context.Context, fn func() error, config RetryConfig) error {
|
|
var lastErr error
|
|
|
|
for attempt := 0; attempt <= config.MaxRetries; attempt++ {
|
|
if attempt > 0 {
|
|
delay := config.BaseDelay * time.Duration(1<<uint(attempt-1))
|
|
if delay > config.MaxDelay {
|
|
delay = config.MaxDelay
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(delay):
|
|
}
|
|
}
|
|
|
|
err := fn()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
lastErr = err
|
|
|
|
if !IsRetryable(err) {
|
|
return err
|
|
}
|
|
|
|
if attempt < config.MaxRetries {
|
|
if retryErr, ok := err.(*RetryableError); ok && retryErr.RetryAfter > 0 {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(retryErr.RetryAfter):
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors.Wrapf(lastErr, "failed after %d retries", config.MaxRetries)
|
|
}
|
|
|
|
// CreateVM creates a virtual machine
|
|
func (c *Client) CreateVM(ctx context.Context, spec VMSpec) (*VM, error) {
|
|
config := DefaultRetryConfig()
|
|
|
|
var vm *VM
|
|
err := Retry(ctx, func() error {
|
|
var retryErr error
|
|
vm, retryErr = c.createVM(ctx, spec)
|
|
if retryErr != nil {
|
|
// Check if error is retryable (network errors, temporary failures)
|
|
if isNetworkError(retryErr) || isTemporaryError(retryErr) {
|
|
return &RetryableError{Err: retryErr}
|
|
}
|
|
return retryErr
|
|
}
|
|
return nil
|
|
}, config)
|
|
|
|
return vm, err
|
|
}
|
|
|
|
// createVM performs the actual VM creation
|
|
func (c *Client) createVM(ctx context.Context, spec VMSpec) (*VM, error) {
|
|
// TODO: Implement actual Proxmox API call
|
|
return nil, fmt.Errorf("not implemented")
|
|
}
|
|
|
|
// UpdateVM updates a virtual machine
|
|
func (c *Client) UpdateVM(ctx context.Context, vmID int, spec VMSpec) (*VM, error) {
|
|
config := DefaultRetryConfig()
|
|
|
|
var vm *VM
|
|
err := Retry(ctx, func() error {
|
|
var retryErr error
|
|
vm, retryErr = c.updateVM(ctx, vmID, spec)
|
|
if retryErr != nil {
|
|
if isNetworkError(retryErr) || isTemporaryError(retryErr) {
|
|
return &RetryableError{Err: retryErr}
|
|
}
|
|
return retryErr
|
|
}
|
|
return nil
|
|
}, config)
|
|
|
|
return vm, err
|
|
}
|
|
|
|
func (c *Client) updateVM(ctx context.Context, vmID int, spec VMSpec) (*VM, error) {
|
|
// TODO: Implement actual Proxmox API call
|
|
return nil, fmt.Errorf("not implemented")
|
|
}
|
|
|
|
// DeleteVM deletes a virtual machine
|
|
func (c *Client) DeleteVM(ctx context.Context, vmID int) error {
|
|
config := DefaultRetryConfig()
|
|
|
|
return Retry(ctx, func() error {
|
|
err := c.deleteVM(ctx, vmID)
|
|
if err != nil {
|
|
if isNetworkError(err) || isTemporaryError(err) {
|
|
return &RetryableError{Err: err}
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}, config)
|
|
}
|
|
|
|
func (c *Client) deleteVM(ctx context.Context, vmID int) error {
|
|
// TODO: Implement actual Proxmox API call
|
|
return fmt.Errorf("not implemented")
|
|
}
|
|
|
|
// GetVMStatus gets the status of a virtual machine
|
|
func (c *Client) GetVMStatus(ctx context.Context, vmID int) (*VMStatus, error) {
|
|
config := DefaultRetryConfig()
|
|
|
|
var status *VMStatus
|
|
err := Retry(ctx, func() error {
|
|
var retryErr error
|
|
status, retryErr = c.getVMStatus(ctx, vmID)
|
|
if retryErr != nil {
|
|
if isNetworkError(retryErr) || isTemporaryError(retryErr) {
|
|
return &RetryableError{Err: retryErr}
|
|
}
|
|
return retryErr
|
|
}
|
|
return nil
|
|
}, config)
|
|
|
|
return status, err
|
|
}
|
|
|
|
func (c *Client) getVMStatus(ctx context.Context, vmID int) (*VMStatus, error) {
|
|
// TODO: Implement actual Proxmox API call
|
|
return nil, fmt.Errorf("not implemented")
|
|
}
|
|
|
|
// Helper functions
|
|
func isNetworkError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
errStr := err.Error()
|
|
return contains(errStr, "network") || contains(errStr, "timeout") || contains(errStr, "connection")
|
|
}
|
|
|
|
func isTemporaryError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
errStr := err.Error()
|
|
return contains(errStr, "temporary") || contains(errStr, "503") || contains(errStr, "502")
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(substr) == 0 || indexOfSubstring(s, substr) >= 0)
|
|
}
|
|
|
|
func indexOfSubstring(s, substr string) int {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// VMSpec represents VM specification
|
|
type VMSpec struct {
|
|
Node string
|
|
Name string
|
|
CPU int
|
|
Memory string
|
|
Disk string
|
|
Storage string
|
|
Network string
|
|
Image string
|
|
}
|
|
|
|
// VM represents a virtual machine
|
|
type VM struct {
|
|
ID int
|
|
Name string
|
|
Status string
|
|
IP string
|
|
Node string
|
|
Created time.Time
|
|
}
|
|
|
|
// VMStatus represents VM status
|
|
type VMStatus struct {
|
|
State string
|
|
IPAddress string
|
|
CPU float64
|
|
Memory int64
|
|
}
|