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>
155 lines
3.6 KiB
Go
155 lines
3.6 KiB
Go
//go:build ignore
|
|
|
|
// check-endpoint-drift compares the current OpenAPI spec against the endpoint manifest
|
|
// and reports any added or removed endpoints. Exit 0 if no drift, exit 1 if drift detected.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Endpoint struct {
|
|
Method string `json:"method" yaml:"-"`
|
|
Path string `json:"path" yaml:"-"`
|
|
Summary string `json:"summary" yaml:"-"`
|
|
Tag string `json:"tag" yaml:"-"`
|
|
}
|
|
|
|
func endpointKey(e Endpoint) string {
|
|
return e.Method + " " + e.Path
|
|
}
|
|
|
|
type OpenAPISpec struct {
|
|
Paths map[string]map[string]struct {
|
|
Summary string `yaml:"summary"`
|
|
Tags []string `yaml:"tags"`
|
|
} `yaml:"paths"`
|
|
}
|
|
|
|
func extractEndpoints(specPath string) ([]Endpoint, error) {
|
|
data, err := os.ReadFile(specPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading spec: %w", err)
|
|
}
|
|
|
|
var spec OpenAPISpec
|
|
if err := yaml.Unmarshal(data, &spec); err != nil {
|
|
return nil, fmt.Errorf("parsing spec: %w", err)
|
|
}
|
|
|
|
validMethods := map[string]bool{
|
|
"get": true, "post": true, "put": true, "delete": true, "patch": true,
|
|
}
|
|
|
|
var endpoints []Endpoint
|
|
for path, methods := range spec.Paths {
|
|
for method, details := range methods {
|
|
if !validMethods[strings.ToLower(method)] {
|
|
continue
|
|
}
|
|
tag := "Untagged"
|
|
if len(details.Tags) > 0 {
|
|
tag = details.Tags[0]
|
|
}
|
|
endpoints = append(endpoints, Endpoint{
|
|
Method: strings.ToUpper(method),
|
|
Path: path,
|
|
Summary: details.Summary,
|
|
Tag: tag,
|
|
})
|
|
}
|
|
}
|
|
|
|
sort.Slice(endpoints, func(i, j int) bool {
|
|
if endpoints[i].Path != endpoints[j].Path {
|
|
return endpoints[i].Path < endpoints[j].Path
|
|
}
|
|
return endpoints[i].Method < endpoints[j].Method
|
|
})
|
|
|
|
return endpoints, nil
|
|
}
|
|
|
|
func main() {
|
|
// Resolve paths relative to this script's location
|
|
_, filename, _, _ := runtime.Caller(0)
|
|
rootDir := filepath.Dir(filepath.Dir(filename))
|
|
|
|
specPath := filepath.Join(rootDir, "openapi.yaml")
|
|
manifestPath := filepath.Join(rootDir, "endpoint-manifest.json")
|
|
|
|
current, err := extractEndpoints(specPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error extracting endpoints: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
manifestData, err := os.ReadFile(manifestPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: endpoint-manifest.json not found. Copy it from the MCP repo first.\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
var manifest []Endpoint
|
|
if err := json.Unmarshal(manifestData, &manifest); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing manifest: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
manifestKeys := make(map[string]bool)
|
|
for _, e := range manifest {
|
|
manifestKeys[endpointKey(e)] = true
|
|
}
|
|
currentKeys := make(map[string]bool)
|
|
for _, e := range current {
|
|
currentKeys[endpointKey(e)] = true
|
|
}
|
|
|
|
var added, removed []Endpoint
|
|
for _, e := range current {
|
|
if !manifestKeys[endpointKey(e)] {
|
|
added = append(added, e)
|
|
}
|
|
}
|
|
for _, e := range manifest {
|
|
if !currentKeys[endpointKey(e)] {
|
|
removed = append(removed, e)
|
|
}
|
|
}
|
|
|
|
if len(added) == 0 && len(removed) == 0 {
|
|
fmt.Printf("No endpoint drift detected. %d endpoints match the manifest.\n", len(current))
|
|
os.Exit(0)
|
|
}
|
|
|
|
fmt.Println("Endpoint drift detected!")
|
|
fmt.Println()
|
|
|
|
if len(added) > 0 {
|
|
fmt.Printf("New endpoints (%d):\n", len(added))
|
|
for _, e := range added {
|
|
fmt.Printf(" + %s %s — %s [%s]\n", e.Method, e.Path, e.Summary, e.Tag)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
if len(removed) > 0 {
|
|
fmt.Printf("Removed endpoints (%d):\n", len(removed))
|
|
for _, e := range removed {
|
|
fmt.Printf(" - %s %s — %s [%s]\n", e.Method, e.Path, e.Summary, e.Tag)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
fmt.Println("Update endpoint-manifest.json from the MCP repo to resolve drift.")
|
|
os.Exit(1)
|
|
}
|