terraform-provider-towerops/internal/provider/client.go
Graham McIntire 05530894ae
feat: towerops_coverage resource
Wraps the new /api/v1/coverages endpoints. Schema mirrors the cnHeat
mental model — antenna, frequency, height, azimuth, downtilt, radius
— with cnHeat-style imperial units (height_agl_ft, radius_mi,
frequency_ghz) the API converts to SI before validation.

Compute is asynchronous: 'terraform apply' returns once the prediction
is queued. The status / progress_pct / error_message / png_path /
raster_path / computed_at fields are computed and update on
'terraform refresh' as the worker progresses through queued →
computing → ready (or failed). Downstream resources that need a
ready heatmap can use a terraform_data trigger that polls the
status field.

Resource semantics:
- Create   POST   /api/v1/coverages           (auto-queues compute)
- Read     GET    /api/v1/coverages/:id
- Update   PATCH  /api/v1/coverages/:id       (does NOT auto-recompute)
- Delete   DELETE /api/v1/coverages/:id

Update doesn't auto-recompute because parameter tweaks may not
require a fresh heatmap; operators can call the recompute endpoint
directly via the existing client method when they want fresh
outputs.

ImportState supported via the standard UUID passthrough.
2026-05-06 17:21:30 -05:00

779 lines
25 KiB
Go

package provider
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
)
// ErrNotFound is returned when a resource is not found (404).
var ErrNotFound = errors.New("resource not found")
const defaultBaseURL = "https://towerops.net"
// Client is the TowerOps API client.
type Client struct {
BaseURL string
Token string
HTTPClient *http.Client
}
// NewClient creates a new TowerOps API client.
func NewClient(token, baseURL string) *Client {
if baseURL == "" {
baseURL = defaultBaseURL
}
return &Client{
BaseURL: baseURL,
Token: token,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// Site represents a TowerOps site.
type Site struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Location *string `json:"location,omitempty"`
Address *string `json:"address,omitempty"`
Latitude *float64 `json:"latitude,omitempty"`
Longitude *float64 `json:"longitude,omitempty"`
SNMPCommunity *string `json:"snmp_community,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// Device represents a TowerOps device.
type Device struct {
ID string `json:"id,omitempty"`
SiteID *string `json:"site_id,omitempty"`
OrganizationID *string `json:"organization_id,omitempty"`
Name *string `json:"name,omitempty"`
IPAddress string `json:"ip_address"`
Description *string `json:"description,omitempty"`
MonitoringEnabled *bool `json:"monitoring_enabled,omitempty"`
SNMPEnabled *bool `json:"snmp_enabled,omitempty"`
SNMPVersion *string `json:"snmp_version,omitempty"`
SNMPPort *int `json:"snmp_port,omitempty"`
CheckIntervalSeconds *int `json:"check_interval_seconds,omitempty"`
DeviceRole *string `json:"device_role,omitempty"`
// SNMPv3 fields
SNMPv3SecurityLevel *string `json:"snmpv3_security_level,omitempty"`
SNMPv3Username *string `json:"snmpv3_username,omitempty"`
SNMPv3AuthProtocol *string `json:"snmpv3_auth_protocol,omitempty"`
SNMPv3AuthPassword *string `json:"snmpv3_auth_password,omitempty"`
SNMPv3PrivProtocol *string `json:"snmpv3_priv_protocol,omitempty"`
SNMPv3PrivPassword *string `json:"snmpv3_priv_password,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// Organization represents a TowerOps organization.
type Organization struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Slug string `json:"slug,omitempty"`
UseSites bool `json:"use_sites"`
SnmpCommunity string `json:"snmp_community,omitempty"`
}
// organizationResponse wraps the API response for organization endpoints.
type organizationResponse struct {
Data Organization `json:"data"`
}
// APIError represents an error response from the API.
type APIError struct {
Error string `json:"error,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}
func (c *Client) doRequest(method, path string, body interface{}) ([]byte, error) {
var reqBody io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
reqBody = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequest(method, c.BaseURL+path, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode >= 400 {
if resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}
var apiErr APIError
if err := json.Unmarshal(respBody, &apiErr); err == nil {
if apiErr.Error != "" {
return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, apiErr.Error)
}
if len(apiErr.Errors) > 0 {
return nil, fmt.Errorf("API validation error (%d): %v", resp.StatusCode, apiErr.Errors)
}
}
return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(respBody))
}
return respBody, nil
}
// CreateSite creates a new site.
func (c *Client) CreateSite(site Site) (*Site, error) {
body := map[string]Site{"site": site}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/sites", body)
if err != nil {
return nil, err
}
var result Site
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetSite retrieves a site by ID.
func (c *Client) GetSite(id string) (*Site, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/sites/"+id, nil)
if err != nil {
return nil, err
}
var result Site
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateSite updates an existing site.
func (c *Client) UpdateSite(id string, site Site) (*Site, error) {
body := map[string]Site{"site": site}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/sites/"+id, body)
if err != nil {
return nil, err
}
var result Site
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteSite deletes a site.
func (c *Client) DeleteSite(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/sites/"+id, nil)
return err
}
// CreateDevice creates a new device.
func (c *Client) CreateDevice(device Device) (*Device, error) {
body := map[string]Device{"device": device}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/devices", body)
if err != nil {
return nil, err
}
var result Device
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetDevice retrieves a device by ID.
func (c *Client) GetDevice(id string) (*Device, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/devices/"+id, nil)
if err != nil {
return nil, err
}
var result Device
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateDevice updates an existing device.
func (c *Client) UpdateDevice(id string, device Device) (*Device, error) {
body := map[string]Device{"device": device}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/devices/"+id, body)
if err != nil {
return nil, err
}
var result Device
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteDevice deletes a device.
func (c *Client) DeleteDevice(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/devices/"+id, nil)
return err
}
// OnCallSchedule represents a TowerOps on-call schedule.
type OnCallSchedule struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Timezone string `json:"timezone"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// CreateSchedule creates a new on-call schedule.
func (c *Client) CreateSchedule(schedule OnCallSchedule) (*OnCallSchedule, error) {
body := map[string]OnCallSchedule{"schedule": schedule}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/schedules", body)
if err != nil {
return nil, err
}
var result OnCallSchedule
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetSchedule retrieves an on-call schedule by ID.
func (c *Client) GetSchedule(id string) (*OnCallSchedule, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/schedules/"+id, nil)
if err != nil {
return nil, err
}
var result OnCallSchedule
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateSchedule updates an existing on-call schedule.
func (c *Client) UpdateSchedule(id string, schedule OnCallSchedule) (*OnCallSchedule, error) {
body := map[string]OnCallSchedule{"schedule": schedule}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/schedules/"+id, body)
if err != nil {
return nil, err
}
var result OnCallSchedule
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteSchedule deletes an on-call schedule.
func (c *Client) DeleteSchedule(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/schedules/"+id, nil)
return err
}
// EscalationPolicyAPI represents a TowerOps escalation policy.
type EscalationPolicyAPI struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
RepeatCount *int `json:"repeat_count,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// CreateEscalationPolicy creates a new escalation policy.
func (c *Client) CreateEscalationPolicy(policy EscalationPolicyAPI) (*EscalationPolicyAPI, error) {
body := map[string]EscalationPolicyAPI{"escalation_policy": policy}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/escalation_policies", body)
if err != nil {
return nil, err
}
var result EscalationPolicyAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetEscalationPolicy retrieves an escalation policy by ID.
func (c *Client) GetEscalationPolicy(id string) (*EscalationPolicyAPI, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/escalation_policies/"+id, nil)
if err != nil {
return nil, err
}
var result EscalationPolicyAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateEscalationPolicy updates an existing escalation policy.
func (c *Client) UpdateEscalationPolicy(id string, policy EscalationPolicyAPI) (*EscalationPolicyAPI, error) {
body := map[string]EscalationPolicyAPI{"escalation_policy": policy}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/escalation_policies/"+id, body)
if err != nil {
return nil, err
}
var result EscalationPolicyAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteEscalationPolicy deletes an escalation policy.
func (c *Client) DeleteEscalationPolicy(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/escalation_policies/"+id, nil)
return err
}
// Agent represents a TowerOps agent token.
type Agent struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Enabled *bool `json:"enabled,omitempty"`
LastSeenAt *string `json:"last_seen_at,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
Token string `json:"token,omitempty"`
}
// agentCreateResponse wraps the create response which includes token.
type agentCreateResponse struct {
Agent
}
// CreateAgent creates a new agent token.
func (c *Client) CreateAgent(agent Agent) (*Agent, error) {
body := map[string]Agent{"agent": agent}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/agents", body)
if err != nil {
return nil, err
}
var result Agent
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetAgent retrieves an agent by ID.
func (c *Client) GetAgent(id string) (*Agent, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/agents/"+id, nil)
if err != nil {
return nil, err
}
var result Agent
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteAgent deletes an agent.
func (c *Client) DeleteAgent(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/agents/"+id, nil)
return err
}
// Integration represents a TowerOps integration.
type Integration struct {
ID string `json:"id,omitempty"`
Provider string `json:"provider"`
Enabled *bool `json:"enabled,omitempty"`
SyncIntervalMinutes *int `json:"sync_interval_minutes,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// integrationWithCredentials is used for create/update requests that include credentials.
type integrationWithCredentials struct {
Provider string `json:"provider"`
Enabled *bool `json:"enabled,omitempty"`
Credentials map[string]interface{} `json:"credentials,omitempty"`
SyncIntervalMinutes *int `json:"sync_interval_minutes,omitempty"`
}
// CreateIntegration creates a new integration.
func (c *Client) CreateIntegration(integration integrationWithCredentials) (*Integration, error) {
body := map[string]integrationWithCredentials{"integration": integration}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/integrations", body)
if err != nil {
return nil, err
}
var result Integration
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetIntegration retrieves an integration by ID.
func (c *Client) GetIntegration(id string) (*Integration, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/integrations/"+id, nil)
if err != nil {
return nil, err
}
var result Integration
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateIntegration updates an existing integration.
func (c *Client) UpdateIntegration(id string, integration integrationWithCredentials) (*Integration, error) {
body := map[string]integrationWithCredentials{"integration": integration}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/integrations/"+id, body)
if err != nil {
return nil, err
}
var result Integration
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteIntegration deletes an integration.
func (c *Client) DeleteIntegration(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/integrations/"+id, nil)
return err
}
// MaintenanceWindowAPI represents a TowerOps maintenance window.
type MaintenanceWindowAPI struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Reason *string `json:"reason,omitempty"`
StartsAt string `json:"starts_at"`
EndsAt string `json:"ends_at"`
SuppressAlerts *bool `json:"suppress_alerts,omitempty"`
SiteID *string `json:"site_id,omitempty"`
DeviceID *string `json:"device_id,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// CreateMaintenanceWindow creates a new maintenance window.
func (c *Client) CreateMaintenanceWindow(window MaintenanceWindowAPI) (*MaintenanceWindowAPI, error) {
body := map[string]MaintenanceWindowAPI{"maintenance_window": window}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/maintenance_windows", body)
if err != nil {
return nil, err
}
var result MaintenanceWindowAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetMaintenanceWindow retrieves a maintenance window by ID.
func (c *Client) GetMaintenanceWindow(id string) (*MaintenanceWindowAPI, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/maintenance_windows/"+id, nil)
if err != nil {
return nil, err
}
var result MaintenanceWindowAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateMaintenanceWindow updates an existing maintenance window.
func (c *Client) UpdateMaintenanceWindow(id string, window MaintenanceWindowAPI) (*MaintenanceWindowAPI, error) {
body := map[string]MaintenanceWindowAPI{"maintenance_window": window}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/maintenance_windows/"+id, body)
if err != nil {
return nil, err
}
var result MaintenanceWindowAPI
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteMaintenanceWindow deletes a maintenance window.
func (c *Client) DeleteMaintenanceWindow(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/maintenance_windows/"+id, nil)
return err
}
// Check represents a TowerOps service check.
type Check struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
CheckType string `json:"check_type"`
Description *string `json:"description,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
DeviceID *string `json:"device_id,omitempty"`
AgentTokenID *string `json:"agent_token_id,omitempty"`
IntervalSeconds *int `json:"interval_seconds,omitempty"`
TimeoutMs *int `json:"timeout_ms,omitempty"`
RetryIntervalSeconds *int `json:"retry_interval_seconds,omitempty"`
MaxCheckAttempts *int `json:"max_check_attempts,omitempty"`
Config map[string]any `json:"config"`
CurrentState *int `json:"current_state,omitempty"`
CurrentStateType *string `json:"current_state_type,omitempty"`
LastCheckAt *string `json:"last_check_at,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
}
// CreateCheck creates a new service check.
func (c *Client) CreateCheck(check Check) (*Check, error) {
body := map[string]Check{"check": check}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/checks", body)
if err != nil {
return nil, err
}
var result Check
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetCheck retrieves a check by ID.
func (c *Client) GetCheck(id string) (*Check, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/checks/"+id, nil)
if err != nil {
return nil, err
}
var result Check
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateCheck updates an existing check.
func (c *Client) UpdateCheck(id string, check Check) (*Check, error) {
body := map[string]Check{"check": check}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/checks/"+id, body)
if err != nil {
return nil, err
}
var result Check
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteCheck deletes a check.
func (c *Client) DeleteCheck(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/checks/"+id, nil)
return err
}
// GetOrganization retrieves the current organization settings.
func (c *Client) GetOrganization() (*Organization, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/organization", nil)
if err != nil {
return nil, err
}
var result organizationResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result.Data, nil
}
// UpdateOrganization updates the current organization settings.
func (c *Client) UpdateOrganization(org Organization) (*Organization, error) {
body := map[string]Organization{"organization": org}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/organization", body)
if err != nil {
return nil, err
}
var result organizationResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result.Data, nil
}
// Coverage represents a TowerOps RF coverage prediction.
//
// Inputs use the imperial virtual fields (height_agl_ft, radius_mi,
// frequency_ghz) that the Phoenix changeset converts to SI before
// validation. Read-back fields (status, png_path, etc.) are populated
// by the worker once compute completes.
type Coverage struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
OrganizationID string `json:"organization_id,omitempty"`
SiteID string `json:"site_id"`
DeviceID *string `json:"device_id,omitempty"`
AntennaSlug string `json:"antenna_slug"`
FrequencyGHz *float64 `json:"frequency_ghz,omitempty"`
FrequencyMHz *int `json:"frequency_mhz,omitempty"`
TXPowerDbm *float64 `json:"tx_power_dbm,omitempty"`
CableLossDb *float64 `json:"cable_loss_db,omitempty"`
SmGainDbi *float64 `json:"sm_gain_dbi,omitempty"`
HeightAGLFt *float64 `json:"height_agl_ft,omitempty"`
HeightAGLM *float64 `json:"height_agl_m,omitempty"`
HeightAboveRooftopM *float64 `json:"height_above_rooftop_m,omitempty"`
AzimuthDeg *float64 `json:"azimuth_deg,omitempty"`
DowntiltDeg *float64 `json:"downtilt_deg,omitempty"`
RadiusMi *float64 `json:"radius_mi,omitempty"`
RadiusM *int `json:"radius_m,omitempty"`
CellSizeM *int `json:"cell_size_m,omitempty"`
ReceiverHeightM *float64 `json:"receiver_height_m,omitempty"`
RxThresholdDbm *float64 `json:"rx_threshold_dbm,omitempty"`
FoliageTuning *int `json:"foliage_tuning,omitempty"`
LatitudeOverride *float64 `json:"latitude_override,omitempty"`
LongitudeOverride *float64 `json:"longitude_override,omitempty"`
Status string `json:"status,omitempty"`
ProgressPct int `json:"progress_pct,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
ComputedAt string `json:"computed_at,omitempty"`
PNGPath string `json:"png_path,omitempty"`
RasterPath string `json:"raster_path,omitempty"`
InsertedAt string `json:"inserted_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// CreateCoverage creates a new coverage and immediately queues
// compute. Returned struct will have status="queued"; poll
// GetCoverage to wait for status="ready".
func (c *Client) CreateCoverage(coverage Coverage) (*Coverage, error) {
body := map[string]Coverage{"coverage": coverage}
respBody, err := c.doRequest(http.MethodPost, "/api/v1/coverages", body)
if err != nil {
return nil, err
}
var result Coverage
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// GetCoverage retrieves a coverage by ID.
func (c *Client) GetCoverage(id string) (*Coverage, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/coverages/"+id, nil)
if err != nil {
return nil, err
}
var result Coverage
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// UpdateCoverage updates editable fields of a coverage. Does not
// auto-recompute — call RecomputeCoverage if you want fresh
// outputs after a parameter change.
func (c *Client) UpdateCoverage(id string, coverage Coverage) (*Coverage, error) {
body := map[string]Coverage{"coverage": coverage}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/coverages/"+id, body)
if err != nil {
return nil, err
}
var result Coverage
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
// DeleteCoverage deletes a coverage and its on-disk rasters.
func (c *Client) DeleteCoverage(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/coverages/"+id, nil)
return err
}
// RecomputeCoverage requeues the compute pipeline for an existing
// coverage. Returns the coverage with status="queued".
func (c *Client) RecomputeCoverage(id string) (*Coverage, error) {
respBody, err := c.doRequest(http.MethodPost, "/api/v1/coverages/"+id+"/recompute", nil)
if err != nil {
return nil, err
}
var result Coverage
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}