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:
71
internal/client/client.go
Normal file
71
internal/client/client.go
Normal 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()
|
||||
}
|
||||
34
internal/client/errors.go
Normal file
34
internal/client/errors.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) EZSCALE.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package client
|
||||
|
||||
import "fmt"
|
||||
|
||||
// APIError represents an error returned by the VirtFusion API.
|
||||
type APIError struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Body string
|
||||
Errors map[string][]string
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
if len(e.Errors) > 0 {
|
||||
return fmt.Sprintf("VirtFusion API error %d (%s): %v", e.StatusCode, e.Status, e.Errors)
|
||||
}
|
||||
if e.Body != "" {
|
||||
return fmt.Sprintf("VirtFusion API error %d (%s): %s", e.StatusCode, e.Status, e.Body)
|
||||
}
|
||||
return fmt.Sprintf("VirtFusion API error %d (%s)", e.StatusCode, e.Status)
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the error is a 404 Not Found response.
|
||||
func (e *APIError) IsNotFound() bool {
|
||||
return e.StatusCode == 404
|
||||
}
|
||||
|
||||
// IsValidationError returns true if the error is a 422 Unprocessable Entity response.
|
||||
func (e *APIError) IsValidationError() bool {
|
||||
return e.StatusCode == 422
|
||||
}
|
||||
192
internal/client/request.go
Normal file
192
internal/client/request.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) EZSCALE.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// paginatedResponse is the envelope returned by VirtFusion's Laravel-style pagination.
|
||||
type paginatedResponse struct {
|
||||
CurrentPage int `json:"current_page"`
|
||||
LastPage int `json:"last_page"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// Get performs a GET request to the given path.
|
||||
func (c *Client) Get(ctx context.Context, path string) (json.RawMessage, error) {
|
||||
return c.doRequest(ctx, http.MethodGet, path, nil)
|
||||
}
|
||||
|
||||
// GetAllPages fetches all pages from a paginated endpoint and returns
|
||||
// a synthetic JSON response with all items merged into a single "data" array.
|
||||
// If the response is not paginated (no last_page field or single page), it
|
||||
// returns the original response unchanged.
|
||||
func (c *Client) GetAllPages(ctx context.Context, path string) (json.RawMessage, error) {
|
||||
firstRaw, err := c.Get(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var page paginatedResponse
|
||||
if err := json.Unmarshal(firstRaw, &page); err != nil || page.LastPage == 0 {
|
||||
// Not a paginated response — return as-is.
|
||||
return firstRaw, nil
|
||||
}
|
||||
|
||||
if page.LastPage <= 1 {
|
||||
return firstRaw, nil
|
||||
}
|
||||
|
||||
// Collect data arrays from all pages.
|
||||
allItems, err := flattenJSONArray(page.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing page 1 data: %w", err)
|
||||
}
|
||||
|
||||
sep := "&"
|
||||
if !strings.Contains(path, "?") {
|
||||
sep = "?"
|
||||
}
|
||||
|
||||
for p := 2; p <= page.LastPage; p++ {
|
||||
pageRaw, err := c.Get(ctx, fmt.Sprintf("%s%spage=%d", path, sep, p))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching page %d: %w", p, err)
|
||||
}
|
||||
|
||||
var pageResp paginatedResponse
|
||||
if err := json.Unmarshal(pageRaw, &pageResp); err != nil {
|
||||
return nil, fmt.Errorf("parsing page %d: %w", p, err)
|
||||
}
|
||||
|
||||
items, err := flattenJSONArray(pageResp.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing page %d data: %w", p, err)
|
||||
}
|
||||
allItems = append(allItems, items...)
|
||||
}
|
||||
|
||||
mergedData, err := json.Marshal(allItems)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling merged data: %w", err)
|
||||
}
|
||||
|
||||
// Build a response that looks like {"data": [...all items...]} so
|
||||
// existing list response types (e.g. ServerListResponse) unmarshal correctly.
|
||||
result, err := json.Marshal(map[string]json.RawMessage{"data": mergedData})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling merged response: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// flattenJSONArray unmarshals a JSON array into individual raw messages.
|
||||
func flattenJSONArray(raw json.RawMessage) ([]json.RawMessage, error) {
|
||||
var items []json.RawMessage
|
||||
if err := json.Unmarshal(raw, &items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Post performs a POST request to the given path with the given body.
|
||||
func (c *Client) Post(ctx context.Context, path string, body interface{}) (json.RawMessage, error) {
|
||||
return c.doRequest(ctx, http.MethodPost, path, body)
|
||||
}
|
||||
|
||||
// Put performs a PUT request to the given path with the given body.
|
||||
func (c *Client) Put(ctx context.Context, path string, body interface{}) (json.RawMessage, error) {
|
||||
return c.doRequest(ctx, http.MethodPut, path, body)
|
||||
}
|
||||
|
||||
// Delete performs a DELETE request to the given path.
|
||||
func (c *Client) Delete(ctx context.Context, path string) (json.RawMessage, error) {
|
||||
return c.doRequest(ctx, http.MethodDelete, path, nil)
|
||||
}
|
||||
|
||||
// DeleteWithBody performs a DELETE request with a JSON body.
|
||||
func (c *Client) DeleteWithBody(ctx context.Context, path string, body interface{}) (json.RawMessage, error) {
|
||||
return c.doRequest(ctx, http.MethodDelete, path, body)
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (json.RawMessage, error) {
|
||||
// Split path from query string before joining, since url.JoinPath
|
||||
// escapes '?' as '%3F' when it appears in the path.
|
||||
pathPart := path
|
||||
queryPart := ""
|
||||
if idx := strings.IndexByte(path, '?'); idx >= 0 {
|
||||
pathPart = path[:idx]
|
||||
queryPart = path[idx:]
|
||||
}
|
||||
|
||||
fullURL, err := url.JoinPath(c.BaseURL, pathPart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building URL: %w", err)
|
||||
}
|
||||
fullURL += queryPart
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling request body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(jsonBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, fullURL, bodyReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 204 No Content is a success with no body
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
apiErr := &APIError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Status: resp.Status,
|
||||
Body: string(respBody),
|
||||
}
|
||||
|
||||
// Try to parse validation errors
|
||||
var errResp struct {
|
||||
Errors map[string][]string `json:"errors"`
|
||||
}
|
||||
if json.Unmarshal(respBody, &errResp) == nil && len(errResp.Errors) > 0 {
|
||||
apiErr.Errors = errResp.Errors
|
||||
}
|
||||
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
return json.RawMessage(respBody), nil
|
||||
}
|
||||
516
internal/client/types.go
Normal file
516
internal/client/types.go
Normal file
@@ -0,0 +1,516 @@
|
||||
// Copyright (c) EZSCALE.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package client
|
||||
|
||||
// ServerCreateRequest represents the request body for creating a server.
|
||||
type ServerCreateRequest struct {
|
||||
PackageID int64 `json:"packageId"`
|
||||
UserID int64 `json:"userId"`
|
||||
HypervisorID int64 `json:"hypervisorId"`
|
||||
Ipv4 *int64 `json:"ipv4,omitempty"`
|
||||
Storage *int64 `json:"storage,omitempty"`
|
||||
Memory *int64 `json:"memory,omitempty"`
|
||||
CPUCores *int64 `json:"cpuCores,omitempty"`
|
||||
Traffic *int64 `json:"traffic,omitempty"`
|
||||
NetworkSpeedInbound *int64 `json:"networkSpeedInbound,omitempty"`
|
||||
NetworkSpeedOutbound *int64 `json:"networkSpeedOutbound,omitempty"`
|
||||
StorageProfile *int64 `json:"storageProfile,omitempty"`
|
||||
NetworkProfile *int64 `json:"networkProfile,omitempty"`
|
||||
DryRun *bool `json:"dryRun,omitempty"`
|
||||
AdditionalStorage1 *int64 `json:"additionalStorage1,omitempty"`
|
||||
AdditionalStorage1Profile *int64 `json:"additionalStorage1Profile,omitempty"`
|
||||
AdditionalStorage2 *int64 `json:"additionalStorage2,omitempty"`
|
||||
AdditionalStorage2Profile *int64 `json:"additionalStorage2Profile,omitempty"`
|
||||
}
|
||||
|
||||
// ServerResponse represents the response from the API for server operations.
|
||||
type ServerResponse struct {
|
||||
Data ServerData `json:"data"`
|
||||
}
|
||||
|
||||
// ServerData represents a server in the API.
|
||||
// The API returns "ownerId" (not "userId") and nests CPU/memory/storage
|
||||
// under settings.resources and cpu.cores for detailed (single) responses.
|
||||
// List responses use a flatter structure with "owner" as an int.
|
||||
type ServerData struct {
|
||||
ID int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
OwnerID int64 `json:"ownerId"`
|
||||
HypervisorID int64 `json:"hypervisorId"`
|
||||
Suspended bool `json:"suspended"`
|
||||
|
||||
// Nested objects present in detailed (single-server) responses.
|
||||
CPU *ServerCPU `json:"cpu,omitempty"`
|
||||
Settings *ServerSettings `json:"settings,omitempty"`
|
||||
Traffic interface{} `json:"traffic,omitempty"`
|
||||
}
|
||||
|
||||
// ServerCPU represents CPU info from the detailed server response.
|
||||
type ServerCPU struct {
|
||||
Cores int64 `json:"cores"`
|
||||
}
|
||||
|
||||
// ServerSettings holds nested settings from the detailed server response.
|
||||
type ServerSettings struct {
|
||||
Resources *ServerSettingsResources `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// ServerSettingsResources holds resource allocations from server settings.
|
||||
type ServerSettingsResources struct {
|
||||
Memory int64 `json:"memory"`
|
||||
Storage int64 `json:"storage"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
CPUCores int64 `json:"cpuCores"`
|
||||
}
|
||||
|
||||
// ServerListResponse represents a list of servers.
|
||||
type ServerListResponse struct {
|
||||
Data []ServerData `json:"data"`
|
||||
}
|
||||
|
||||
// ServerBuildRequest represents the request body for building a server.
|
||||
type ServerBuildRequest struct {
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
OperatingSystemID int64 `json:"operatingSystemId"`
|
||||
VNC bool `json:"vnc"`
|
||||
Ipv6 bool `json:"ipv6"`
|
||||
SSHKeys []int64 `json:"sshKeys,omitempty"`
|
||||
Email bool `json:"email"`
|
||||
}
|
||||
|
||||
// SSHKeyCreateRequest represents the request body for creating an SSH key.
|
||||
type SSHKeyCreateRequest struct {
|
||||
UserID int64 `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
}
|
||||
|
||||
// SSHKeyResponse represents the response from the API for SSH key operations.
|
||||
type SSHKeyResponse struct {
|
||||
Data SSHKeyData `json:"data"`
|
||||
}
|
||||
|
||||
// SSHKeyData represents an SSH key in the API.
|
||||
type SSHKeyData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
Enabled bool `json:"enabled"`
|
||||
UserID int64 `json:"userId"`
|
||||
CreatedAt string `json:"created"`
|
||||
UpdatedAt string `json:"updated"`
|
||||
}
|
||||
|
||||
// SSHKeyListResponse represents a list of SSH keys.
|
||||
type SSHKeyListResponse struct {
|
||||
Data []SSHKeyData `json:"data"`
|
||||
}
|
||||
|
||||
// UserCreateRequest represents the request body for creating a user.
|
||||
type UserCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
ExtRelationID string `json:"extRelationId"`
|
||||
}
|
||||
|
||||
// UserResponse represents the response from the API for user operations.
|
||||
type UserResponse struct {
|
||||
Data UserData `json:"data"`
|
||||
}
|
||||
|
||||
// UserData represents a user in the API.
|
||||
type UserData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
ExtRelationID string `json:"extRelationId"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt string `json:"created"`
|
||||
UpdatedAt string `json:"updated"`
|
||||
}
|
||||
|
||||
// UserModifyRequest represents the request body for modifying a user.
|
||||
type UserModifyRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// HypervisorResponse represents the response from the API for hypervisor operations.
|
||||
type HypervisorResponse struct {
|
||||
Data HypervisorData `json:"data"`
|
||||
}
|
||||
|
||||
// HypervisorData represents a hypervisor in the API.
|
||||
type HypervisorData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Hostname string `json:"hostname"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// HypervisorListResponse represents a list of hypervisors.
|
||||
type HypervisorListResponse struct {
|
||||
Data []HypervisorData `json:"data"`
|
||||
}
|
||||
|
||||
// HypervisorGroupResponse represents the response from the API for hypervisor group operations.
|
||||
type HypervisorGroupResponse struct {
|
||||
Data HypervisorGroupData `json:"data"`
|
||||
}
|
||||
|
||||
// HypervisorGroupData represents a hypervisor group in the API.
|
||||
type HypervisorGroupData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// HypervisorGroupListResponse represents a list of hypervisor groups.
|
||||
type HypervisorGroupListResponse struct {
|
||||
Data []HypervisorGroupData `json:"data"`
|
||||
}
|
||||
|
||||
// PackageResponse represents the response from the API for package operations.
|
||||
type PackageResponse struct {
|
||||
Data PackageData `json:"data"`
|
||||
}
|
||||
|
||||
// PackageData represents a package in the API.
|
||||
// The API uses "primaryStorage", "primaryNetworkSpeedIn/Out" etc.
|
||||
type PackageData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CPUCores int64 `json:"cpuCores"`
|
||||
Memory int64 `json:"memory"`
|
||||
Storage int64 `json:"primaryStorage"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
NetworkSpeedInbound int64 `json:"primaryNetworkSpeedIn"`
|
||||
NetworkSpeedOutbound int64 `json:"primaryNetworkSpeedOut"`
|
||||
Ipv4 int64 `json:"ipv4"`
|
||||
StorageProfile int64 `json:"primaryStorageProfile"`
|
||||
NetworkProfile int64 `json:"primaryNetworkProfile"`
|
||||
}
|
||||
|
||||
// PackageListResponse represents a list of packages.
|
||||
type PackageListResponse struct {
|
||||
Data []PackageData `json:"data"`
|
||||
}
|
||||
|
||||
// IPBlockResponse represents the response from the API for IP block operations.
|
||||
type IPBlockResponse struct {
|
||||
Data IPBlockData `json:"data"`
|
||||
}
|
||||
|
||||
// IPBlockData represents an IP block in the API.
|
||||
// The API nests gateway/netmask under "ipv4" and returns "type" as an int.
|
||||
type IPBlockData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type int64 `json:"type"`
|
||||
IPv4 IPBlockIPv4 `json:"ipv4"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// IPBlockIPv4 represents the IPv4 section of an IP block.
|
||||
type IPBlockIPv4 struct {
|
||||
Gateway string `json:"gateway"`
|
||||
Netmask string `json:"netmask"`
|
||||
}
|
||||
|
||||
// IPBlockListResponse represents a list of IP blocks.
|
||||
type IPBlockListResponse struct {
|
||||
Data []IPBlockData `json:"data"`
|
||||
}
|
||||
|
||||
// FirewallResponse represents the response from the API for firewall operations.
|
||||
type FirewallResponse struct {
|
||||
Data FirewallData `json:"data"`
|
||||
}
|
||||
|
||||
// FirewallData represents firewall data in the API.
|
||||
type FirewallData struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Rules []FirewallRule `json:"rules"`
|
||||
}
|
||||
|
||||
// FirewallRule represents a single firewall rule.
|
||||
type FirewallRule struct {
|
||||
Action string `json:"action"`
|
||||
Direction string `json:"direction"`
|
||||
Protocol string `json:"protocol"`
|
||||
Port string `json:"port"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
// FirewallSetRulesRequest represents the request body for setting firewall rules.
|
||||
type FirewallSetRulesRequest struct {
|
||||
Rules []FirewallRule `json:"rules"`
|
||||
}
|
||||
|
||||
// NetworkWhitelistRequest represents the request body for adding a network whitelist entry.
|
||||
type NetworkWhitelistRequest struct {
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
// TrafficBlockRequest represents the request body for adding a traffic block.
|
||||
type TrafficBlockRequest struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// TrafficBlockResponse represents the response for traffic block operations.
|
||||
type TrafficBlockResponse struct {
|
||||
Data TrafficBlockData `json:"data"`
|
||||
}
|
||||
|
||||
// TrafficBlockData represents a traffic block.
|
||||
type TrafficBlockData struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// TrafficBlockListResponse represents a list of traffic blocks.
|
||||
type TrafficBlockListResponse struct {
|
||||
Data []TrafficBlockData `json:"data"`
|
||||
}
|
||||
|
||||
// TrafficResponse represents server traffic data.
|
||||
type TrafficResponse struct {
|
||||
Data TrafficData `json:"data"`
|
||||
}
|
||||
|
||||
// TrafficData represents traffic usage data.
|
||||
type TrafficData struct {
|
||||
Used int64 `json:"used"`
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
||||
// IPBlockRangeRequest represents the request body for adding an IPv4 range.
|
||||
type IPBlockRangeRequest struct {
|
||||
StartIP string `json:"startIp"`
|
||||
EndIP string `json:"endIp"`
|
||||
Gateway string `json:"gateway"`
|
||||
Netmask string `json:"netmask"`
|
||||
}
|
||||
|
||||
// ServerModifyNameRequest represents the request body for modifying a server name.
|
||||
type ServerModifyNameRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ServerModifyCPURequest represents the request body for modifying server CPU.
|
||||
type ServerModifyCPURequest struct {
|
||||
CPUCores int64 `json:"cpuCores"`
|
||||
}
|
||||
|
||||
// ServerModifyMemoryRequest represents the request body for modifying server memory.
|
||||
type ServerModifyMemoryRequest struct {
|
||||
Memory int64 `json:"memory"`
|
||||
}
|
||||
|
||||
// ServerModifyTrafficRequest represents the request body for modifying server traffic.
|
||||
type ServerModifyTrafficRequest struct {
|
||||
Traffic int64 `json:"traffic"`
|
||||
}
|
||||
|
||||
// ServerModifyCPUThrottleRequest represents the request body for modifying CPU throttle.
|
||||
type ServerModifyCPUThrottleRequest struct {
|
||||
Percentage int64 `json:"percentage"`
|
||||
}
|
||||
|
||||
// ServerCustomXMLRequest represents the request body for setting custom XML.
|
||||
type ServerCustomXMLRequest struct {
|
||||
XML string `json:"xml"`
|
||||
}
|
||||
|
||||
// VNCResponse represents the response for VNC operations.
|
||||
type VNCResponse struct {
|
||||
Data VNCData `json:"data"`
|
||||
}
|
||||
|
||||
// VNCData represents VNC connection data.
|
||||
type VNCData struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// BackupResponse represents the response for backup operations.
|
||||
type BackupResponse struct {
|
||||
Data []BackupData `json:"data"`
|
||||
}
|
||||
|
||||
// BackupData represents a backup entry.
|
||||
type BackupData struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt string `json:"created"`
|
||||
}
|
||||
|
||||
// DNSServiceResponse represents the response for DNS service operations.
|
||||
type DNSServiceResponse struct {
|
||||
Data DNSServiceData `json:"data"`
|
||||
}
|
||||
|
||||
// DNSServiceData represents a DNS service.
|
||||
type DNSServiceData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ISOResponse represents the response for ISO operations.
|
||||
type ISOResponse struct {
|
||||
Data ISOData `json:"data"`
|
||||
}
|
||||
|
||||
// ISOData represents an ISO.
|
||||
type ISOData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// QueueResponse represents the response for queue operations.
|
||||
type QueueResponse struct {
|
||||
Data QueueData `json:"data"`
|
||||
}
|
||||
|
||||
// QueueData represents a queue item.
|
||||
type QueueData struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Action string `json:"action"`
|
||||
CreatedAt string `json:"created"`
|
||||
}
|
||||
|
||||
// TemplateResponse represents the response for template operations.
|
||||
type TemplateResponse struct {
|
||||
Data []TemplateData `json:"data"`
|
||||
}
|
||||
|
||||
// TemplateData represents a template.
|
||||
type TemplateData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// SelfServiceCreditRequest represents the request body for adding credit.
|
||||
type SelfServiceCreditRequest struct {
|
||||
Amount float64 `json:"amount"`
|
||||
CurrencyCode string `json:"currencyCode"`
|
||||
UserID int64 `json:"userId"`
|
||||
}
|
||||
|
||||
// SelfServiceCreditResponse represents the response for credit operations.
|
||||
type SelfServiceCreditResponse struct {
|
||||
Data SelfServiceCreditData `json:"data"`
|
||||
}
|
||||
|
||||
// SelfServiceCreditData represents credit data.
|
||||
type SelfServiceCreditData struct {
|
||||
ID int64 `json:"id"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// SelfServiceResourcePackRequest represents the request body for resource packs.
|
||||
type SelfServiceResourcePackRequest struct {
|
||||
Name string `json:"name"`
|
||||
UserID int64 `json:"userId"`
|
||||
PackID int64 `json:"packId"`
|
||||
}
|
||||
|
||||
// SelfServiceResourcePackResponse represents the response for resource pack operations.
|
||||
type SelfServiceResourcePackResponse struct {
|
||||
Data SelfServiceResourcePackData `json:"data"`
|
||||
}
|
||||
|
||||
// SelfServiceResourcePackData represents a resource pack.
|
||||
type SelfServiceResourcePackData struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UserID int64 `json:"userId"`
|
||||
PackID int64 `json:"packId"`
|
||||
}
|
||||
|
||||
// CurrencyResponse represents the response for currency operations.
|
||||
type CurrencyResponse struct {
|
||||
Data []CurrencyData `json:"data"`
|
||||
}
|
||||
|
||||
// CurrencyData represents a currency.
|
||||
type CurrencyData struct {
|
||||
ID int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ServerIPv4AddRequest represents the request body for adding IPv4 to a server.
|
||||
type ServerIPv4AddRequest struct {
|
||||
Quantity int64 `json:"quantity,omitempty"`
|
||||
}
|
||||
|
||||
// AuthTokenResponse represents the response for auth token generation.
|
||||
type AuthTokenResponse struct {
|
||||
Data AuthTokenData `json:"data"`
|
||||
}
|
||||
|
||||
// AuthTokenData represents an auth token.
|
||||
type AuthTokenData struct {
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// PasswordResetResponse represents the response for password reset operations.
|
||||
type PasswordResetResponse struct {
|
||||
Data PasswordResetData `json:"data"`
|
||||
}
|
||||
|
||||
// PasswordResetData represents password reset data.
|
||||
type PasswordResetData struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// HypervisorGroupResourcesResponse represents the response for hypervisor group resources.
|
||||
// The API returns a paginated array of per-hypervisor resource entries.
|
||||
type HypervisorGroupResourcesResponse struct {
|
||||
Data []HypervisorGroupResourceEntry `json:"data"`
|
||||
}
|
||||
|
||||
// HypervisorGroupResourceEntry represents a single hypervisor's resources within a group.
|
||||
type HypervisorGroupResourceEntry struct {
|
||||
Hypervisor HypervisorData `json:"hypervisor"`
|
||||
Resources HypervisorGroupResourceDetail `json:"resources"`
|
||||
}
|
||||
|
||||
// HypervisorGroupResourceDetail represents the resource metrics for a hypervisor.
|
||||
type HypervisorGroupResourceDetail struct {
|
||||
Memory ResourceMetric `json:"memory"`
|
||||
CPUCores ResourceMetric `json:"cpuCores"`
|
||||
LocalStorage ResourceMetric `json:"localStorage"`
|
||||
}
|
||||
|
||||
// ResourceMetric represents a resource metric with max/allocated/free values.
|
||||
type ResourceMetric struct {
|
||||
Max int64 `json:"max"`
|
||||
Allocated int64 `json:"allocated"`
|
||||
Free int64 `json:"free"`
|
||||
}
|
||||
|
||||
// SelfServiceHourlyStatsResponse represents the response for hourly stats.
|
||||
type SelfServiceHourlyStatsResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// SelfServiceReportResponse represents the response for self-service reports.
|
||||
type SelfServiceReportResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// SelfServiceUsageResponse represents the response for self-service usage.
|
||||
type SelfServiceUsageResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
Reference in New Issue
Block a user