Files
terraform-provider-virtfusion/scripts/check-endpoint-drift.go
Andrew 6b7430b67b 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>
2026-03-16 02:01:16 -04:00

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)
}