Overhaul VirtFusion provider: 20 resources, 30 data sources, multipage pagination

Complete rewrite of the VirtFusion Terraform provider with full API coverage:

- 20 managed resources (server, build, SSH key, user, firewall, IP blocks, etc.)
- 30 data sources (hypervisors, packages, servers, IP blocks, self-service, etc.)
- New HTTP client with proper error handling, query parameter support, and
  automatic multipage pagination via GetAllPages (fetches all pages from
  Laravel-style paginated endpoints and merges into a single response)
- Fixed type mismatches against live API: ServerData.Suspended (int→bool),
  IPBlockData.Type (string→int), PackageData json tags (primaryStorage, etc.),
  ServerData nested CPU/Settings/Resources structure, HypervisorGroupResources
  array response
- Configurable results-per-page (default 300) on all list data sources
- Migrated CI from GitHub Actions to Gitea Actions
- Updated goreleaser config, go.mod dependencies, and examples

Verified against live VirtFusion instance at cp.vps.ezscale.tech:
all data sources return correct data with full pagination support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 02:01:16 -04:00
parent a3a16f46fa
commit 6b7430b67b
92 changed files with 18443 additions and 1488 deletions

71
internal/client/client.go Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) EZSCALE.
// SPDX-License-Identifier: MPL-2.0
package client
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
// Client is the VirtFusion API client.
type Client struct {
BaseURL string
Token string
HTTPClient *http.Client
}
// New creates a new VirtFusion API client.
// The endpoint can be a hostname (e.g. "cp.example.com") or a full URL
// (e.g. "https://cp.example.com" or "https://cp.example.com/api/v1").
func New(endpoint, token string) (*Client, error) {
if endpoint == "" {
return nil, fmt.Errorf("endpoint is required")
}
if token == "" {
return nil, fmt.Errorf("api_token is required")
}
baseURL := normalizeEndpoint(endpoint)
return &Client{
BaseURL: baseURL,
Token: token,
HTTPClient: &http.Client{
Timeout: 60 * time.Second,
},
}, nil
}
// normalizeEndpoint takes a user-provided endpoint and returns a full base URL
// ending with /api/v1. Supports:
// - "cp.example.com" → "https://cp.example.com/api/v1"
// - "https://cp.example.com" → "https://cp.example.com/api/v1"
// - "https://cp.example.com/api/v1" → "https://cp.example.com/api/v1"
func normalizeEndpoint(endpoint string) string {
endpoint = strings.TrimRight(endpoint, "/")
// If no scheme, add https://
if !strings.Contains(endpoint, "://") {
endpoint = "https://" + endpoint
}
// Parse to validate
u, err := url.Parse(endpoint)
if err != nil {
// Fall back to simple construction
return endpoint + "/api/v1"
}
// If path already ends with /api/v1, use as-is
if strings.HasSuffix(u.Path, "/api/v1") {
return u.String()
}
// Otherwise append /api/v1
u.Path = strings.TrimRight(u.Path, "/") + "/api/v1"
return u.String()
}