go-proxmox

repository·main·Indexed 42 days ago

https://github.com/luthermonson/go-proxmox

A comprehensive Go client for Proxmox VE providing typed wrappers for the entire /api2/json API surface. It supports PVE 8.x and 9.x, offering full context support, flexible authentication (API tokens, credentials, OTP), and advanced functionality for managing VMs, cloud-init, and Software Defined Networking (SDN).

Tokens
76.8K
Snippets
303
Records
413
Agent score
54%

What's inside go-proxmox

  1. Overview of go-proxmox

    main

    The go-proxmox package is a standalone Go client for Proxmox VE. It provides typed Go wrappers and JSON marshaling/unmarshaling for the Proxmox /api2/json surface.

    Key features include:

    • Full context support for Go concurrency patterns.
    • Configuration options for flexible client creation.
    • Client logging for debugging.
    • Unit testing with mocks and integration testing support.
    • Advanced functionality like booting VMs from qcow URLs, dynamic host targeting, and cloud-init support via no-cloud ISOs.
  2. Authenticate with 2FA and OTP

    main

    For interactive tools requiring a human-provided TOTP code, use proxmox.WithOTP and proxmox.WithEagerAuth.

    Important: WithEagerAuth PVE's pveproxy enforces a 3-second delay on every 401 response. By default, the library's first request is unauthenticated. Calling WithEagerAuth forces the library to run CreateSession inside NewClient, so the 3-second penalty is paid once at startup rather than on the first user-facing request.

    Note: The OTP is consumed exactly once. If the session is lost (e.g., PVE restart), you must construct a fresh client with a new OTP.

    client := proxmox.NewClient(url,
    	proxmox.WithCredentials(&proxmox.Credentials{
    		Username: "admin",
    		Password: password,
    	}),
    	proxmox.WithDefaultRealm("pam"),
    	proxmox.WithOTP("123456"),
    	proxmox.WithEagerAuth(),
    )
  3. Handle non-comparable `Client` structs

    main

    In v0.7.0, the Client struct is no longer comparable because it now contains a sync.Mutex. This means you cannot use the equality operators == or != on dereferenced Client values (e.g., *clientA == *clientB).

    If you need to check if two client variables refer to the same instance, compare their pointers instead (e.g., clientA == clientB).

  4. API coverage and limitations

    main

    The client wraps 100% of the upstream PVE /api2/json surface for PVE 8.x and 9.x. Every endpoint in the Proxmox API viewer has a corresponding typed Go wrapper, with two specific exceptions:

    1. mtunnelwebsocket URL builders: The library returns the signed URL via MigrationTunnelWebSocketPath, but the caller is responsible for plumbing this into their own websocket dialer.
    2. file-restore/download: This is a streaming binary endpoint that is not wrapped as a standard JSON endpoint.
  5. Use maps for indexed device fields (v0.7.0)

    main

    In v0.7.0, explicit numbered fields for devices (like Net0, SCSI0, IDE0, etc.) have been removed from VirtualMachineConfig and ContainerConfig. These are replaced by maps that are automatically populated during JSON unmarshaling. This allows the client to support more indices than the previous hardcoded limits.

    // Replacement mapping examples:
    // cfg.Net0 -> cfg.Nets["net0"]
    // cfg.SCSI0 -> cfg.SCSIs["scsi0"]
    // cfg.IDE0 -> cfg.IDEs["ide0"]
    // cfg.SATA0 -> cfg.SATAs["sata0"]
    // cfg.VirtIO0 -> cfg.VirtIOs["virtio0"]
    // cfg.USB0 -> cfg.USBs["usb0"]
    // cfg.HostPCI0 -> cfg.HostPCIs["hostpci0"]
    // cfg.Numa0 -> cfg.Numas["numa0"]
    // cfg.Serial0 -> cfg.Serials["serial0"]
    // cfg.Parallel0 -> cfg.Parallels["parallel0"]
    // cfg.IPConfig0 -> cfg.IPConfigs["ipconfig0"]
    // cfg.Unused0 -> cfg.Unuseds["unused0"]
    // container cfg.Dev0 -> cfg.Devs["dev0"]
    // container cfg.Mp0 -> cfg.Mps["mp0"]
  6. SDN workflow orchestration pattern

    main

    The SDN walkthrough follows a specific lifecycle for managing Software Defined Networking (SDN) configurations in Proxmox:

    1. Capability Probing: Use SDNSubdirs (which uses ACL-filtered diridx) to check available capabilities.
    2. Resource Creation:
      • Create a VXLAN zone using proxmox.CSV for comma-joined fields like nodes or peers.
      • Create a vnet inside the zone.
      • Create a subnet on the *VNet instance handle.
      • Create an EVPN controller.
    3. Configuration Management:
      • Preview pending changes using SDNDryRun.
      • Acquire a lock using SDNLock.
      • Apply the configuration using SDNApply.
      • Roll back changes if an error occurs during the apply phase.
    4. Cleanup: Delete resources in reverse order (subnet $\rightarrow$ vnet $\rightarrow$ zone $\rightarrow$ controller) to return the cluster to its original state.
  7. How CephMon handles work

    main

    Starting in v0.6.0, the CephMon struct no longer represents the row shape from /cluster/ceph status results. Instead, it is an instance handle for /nodes/{node}/ceph/mon/{monid} endpoints.

    If you were previously reading cluster-wide Ceph status fields (like CrushLocation, Priority, Rank, Weight, or PublicAddr) directly from a cluster-ceph response, you must now use node-level instance handles. To get these handles, use node.CephMons(ctx), which returns a slice of *CephMon instances. For the cleaned-up public address field, use (*CephMon).Addr.

  8. Traverse resources using instance handles

    main

    The library uses a getter-returns-handle pattern for resource traversal. Instead of passing (node, id) to every method, you call a getter on a parent object to obtain an instance handle that carries the necessary context.

    Example flow:

    1. Get a parent handle (e.g., client.Cluster(ctx)).
    2. Call a specific resource getter on that handle (e.g., cluster.SDNController("name")).
    3. Perform operations directly on the returned handle (e.g., ctrl.Update(...)).
    cluster, _ := client.Cluster(ctx)
    
    // SDN controllers — getter on the parent, operations on the instance.
    ctrl := cluster.SDNController("evpn-1")
    if err := ctrl.Update(ctx, &proxmox.SDNControllerOptions{ASN: proxmox.IntOrBool(65000)}); err != nil {
        panic(err)
    }
  9. Install and set up the Proxmox VNC and Terminal Proxy

    main

    To use the Proxmox VNC and Terminal Proxy, you must first generate SSL certificates, configure your Proxmox credentials via environment variables, and install Go dependencies.

    Prerequisites

    • Go 1.18 or higher
    • OpenSSL
    • A Proxmox environment

    Setup Steps

    1. Generate SSL Certificates: Use OpenSSL to create server.key and server.crt for secure WebSocket communication.
    2. Configure Environment: Create a .env file in the root directory with your Proxmox connection details.
    3. Install Dependencies: Run go mod tidy to fetch required modules.
    # 1. Generate SSL Certificates
    openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
    
    # 2. Create .env file (example content)
    # PROXMOX_USERNAME=root@pam
    # PROXMOX_PASSWORD=password
    # PROXMOX_URL=https://192.168.0.200:8006/api2/json
    # PROXMOX_NODE=proxmox5
    # PROXMOX_VM=216
    
    # 3. Install dependencies
    go mod tidy
  10. Configure request retries for transient failures

    main

    The library can automatically retry requests that fail due to transient issues like cluster transitions (502/503) or rate limiting (429).

    Use proxmox.WithRetry() to enable default behavior (3 attempts, 200ms–5s exponential backoff with jitter). You can customize the maximum attempts, backoff range, or the retry condition.

    Note: Only idempotent verbs (GET, PUT, DELETE) and POST requests with fully-buffered bodies are eligible for retries.

    // Custom retry logic
    retryOn423 := func(res *http.Response, err error) bool {
        if err != nil {
            return true
        }
        return res.StatusCode == http.StatusLocked
    }
    
    client := proxmox.NewClient(url,
        proxmox.WithAPIToken("automation@pve!ci", "<secret>"),
        proxmox.WithRetry(proxmox.WithRetryCondition(retryOn423)),
    )
  11. Run integration tests against a Proxmox API

    main

    Integration tests require a live Proxmox environment and specific environment variables to authenticate via API token. Ensure you set all five required environment variables before running the command.

    Note: Tests are designed to be idempotent; they should create and remove all testing data they generate to avoid consuming extra resources in your Proxmox VE environment.

    # Bash
    export PROXMOX_URL="https://192.168.1.6:8006/api2/json"
    export PROXMOX_USERNAME="root@pam"
    export PROXMOX_PASSWORD="password"
    export PROXMOX_TOKENID="root@pam!mytoken"
    export PROXMOX_SECRET="somegeneratedapitokenguidefromtheproxmoxui"
    
    mage test:integration
  12. Migrate StorageContent.Encryption to StorageContent.Encrypted

    main

    In v0.7.1, the Encryption field on StorageContent was renamed to Encrypted to match the correct upstream JSON tag (encrypted). Previously, the field was always empty because the JSON tag was incorrect.

    Action: Rename all references from .Encryption to .Encrypted when reading /nodes/{node}/storage/{storage}/content entries.

    // after
    for _, entry := range contents {
        if entry.Encrypted != "" {
            log.Printf("encrypted: %s", entry.Encrypted)
        }
    }