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.
779 lines
25 KiB
Go
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
|
|
}
|