# CLAUDE.md — Project context for AI assistants ## Project Overview Terraform provider for VirtFusion (virtualization management platform). Written in Go using the [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework) (not the older SDKv2). - **Registry address**: `registry.terraform.io/EZSCALE/virtfusion` - **Git hosting**: `https://git.ezscale.cloud/EZSCALE/terraform-provider-virtfusion` (Gitea, not GitHub) - **VirtFusion API**: Laravel-based REST API with Bearer token auth. API docs defined in `openapi.yaml`. ## Architecture ``` main.go # Entry point, provider server internal/ client/ client.go # HTTP client constructor, endpoint normalization request.go # HTTP methods (Get, Post, Put, Delete, GetAllPages) types.go # API request/response struct definitions errors.go # APIError type with status code helpers provider/ provider.go # Provider config, resource/datasource registration, # shared pagination helpers (resultsSchemaAttribute, resultsQueryParam) resource_*.go # Managed resources (20 total) data_source_*.go # Data sources (30 total) ``` ## Key Conventions ### API Client - All HTTP methods return `(json.RawMessage, error)` — raw JSON to be unmarshalled by the caller. - `GetAllPages(ctx, path)` handles Laravel-style pagination automatically: fetches all pages, merges `data` arrays, returns a synthetic `{"data": [...]}` response. Use this for all list endpoints. - Query parameters must be appended to the path string (e.g., `/servers?results=300`). The client splits path from query before calling `url.JoinPath` to avoid `%3F` encoding. - List endpoints use `?results=N` to set page size (default 300 via `defaultResultsPerPage`). ### Data Sources - List data sources use `GetAllPages` and expose an optional `results` attribute (default 300). - Single-item data sources use `Get` and take a required `id` attribute. - Server list responses use a flat structure; single server GET returns nested objects (`cpu.cores`, `settings.resources.memory`, etc.). ### Resources - Server creation uses `ServerCreateRequest` (POST `/servers`) then separate PUT/POST calls for update-only attributes (name, CPU throttle, VNC, suspend, etc.). - User resources are keyed by `ext_relation_id` (external relation ID), not numeric ID. - **Security**: All user-supplied strings interpolated into URL paths MUST be wrapped with `url.PathEscape()`. - **Sensitive attributes**: `api_token`, `password`, `token`, and auth token `url` fields must be marked `Sensitive: true` in schemas. ### API Type Mappings (known quirks) - `ServerData.Suspended` is `bool` (API returns `true`/`false`, not `0`/`1`). - `ServerData` uses `ownerId` (not `userId`) and nests resources under `cpu.cores`, `settings.resources.*`. - `PackageData` uses `primaryStorage`, `primaryNetworkSpeedIn`, `primaryNetworkSpeedOut` (not `storage`, `networkSpeedInbound`, etc.). - `IPBlockData.Type` is `int64` (4=IPv4, 6=IPv6). Gateway/netmask are nested under `ipv4`. - Hypervisor group resources returns a paginated array of per-hypervisor entries, not a single aggregate object. - API error bodies are truncated to 500 bytes in error messages to prevent leaking sensitive data. ## Build & Test ```bash # Build go build ./... # Install locally (goes to $GOPATH/bin) go install . # Lint golangci-lint run # Run against live VirtFusion (read-only test) # Requires ~/.terraformrc with dev_overrides and test/main.tf (gitignored) cd test && terraform apply -auto-approve ``` ## Environment Variables - `VIRTFUSION_ENDPOINT` — API base URL (e.g., `https://cp.example.com` or `https://cp.example.com/api/v1`) - `VIRTFUSION_API_TOKEN` — API bearer token ## CI/CD Uses Gitea Actions (`.gitea/workflows/`). GoReleaser for binary releases (`.goreleaser.yml`). ## Common Pitfalls - `url.JoinPath` encodes `?` as `%3F` — never put query params in the path argument. The client handles this by splitting at `?` first. - The VirtFusion `/servers` endpoint caps at 20 results per page regardless of the `results` parameter. `GetAllPages` handles this transparently by fetching all pages. - Don't use `json:"storage"` for package storage — the API field is `json:"primaryStorage"`. - Single server GET and list server responses have different shapes. `ServerData` type handles both via optional nested pointer fields.