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.
This commit is contained in:
parent
936b278652
commit
05530894ae
3 changed files with 531 additions and 0 deletions
|
|
@ -663,3 +663,117 @@ func (c *Client) UpdateOrganization(org Organization) (*Organization, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
416
internal/provider/coverage_resource.go
Normal file
416
internal/provider/coverage_resource.go
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
var _ resource.Resource = &CoverageResource{}
|
||||
var _ resource.ResourceWithImportState = &CoverageResource{}
|
||||
|
||||
// CoverageResource defines the resource implementation for cnHeat-style
|
||||
// RF coverage predictions.
|
||||
type CoverageResource struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// CoverageResourceModel is the Terraform-side shape of the resource.
|
||||
//
|
||||
// The compute pipeline is asynchronous — `terraform apply` returns as
|
||||
// soon as the prediction is queued. `status` will move from "queued"
|
||||
// → "computing" → "ready" (or "failed") and updates on subsequent
|
||||
// `terraform refresh` / `plan` runs. Use `terraform_data` triggers
|
||||
// or external polling if downstream resources need to wait for ready.
|
||||
type CoverageResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
SiteID types.String `tfsdk:"site_id"`
|
||||
DeviceID types.String `tfsdk:"device_id"`
|
||||
AntennaSlug types.String `tfsdk:"antenna_slug"`
|
||||
FrequencyGHz types.Float64 `tfsdk:"frequency_ghz"`
|
||||
TXPowerDbm types.Float64 `tfsdk:"tx_power_dbm"`
|
||||
HeightAGLFt types.Float64 `tfsdk:"height_agl_ft"`
|
||||
AzimuthDeg types.Float64 `tfsdk:"azimuth_deg"`
|
||||
DowntiltDeg types.Float64 `tfsdk:"downtilt_deg"`
|
||||
RadiusMi types.Float64 `tfsdk:"radius_mi"`
|
||||
CableLossDb types.Float64 `tfsdk:"cable_loss_db"`
|
||||
FoliageTuning types.Int64 `tfsdk:"foliage_tuning"`
|
||||
LatitudeOverride types.Float64 `tfsdk:"latitude_override"`
|
||||
LongitudeOverride types.Float64 `tfsdk:"longitude_override"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
ProgressPct types.Int64 `tfsdk:"progress_pct"`
|
||||
ErrorMessage types.String `tfsdk:"error_message"`
|
||||
ComputedAt types.String `tfsdk:"computed_at"`
|
||||
PNGPath types.String `tfsdk:"png_path"`
|
||||
RasterPath types.String `tfsdk:"raster_path"`
|
||||
InsertedAt types.String `tfsdk:"inserted_at"`
|
||||
}
|
||||
|
||||
// NewCoverageResource creates a new coverage resource.
|
||||
func NewCoverageResource() resource.Resource {
|
||||
return &CoverageResource{}
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_coverage"
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages a TowerOps RF coverage prediction. Compute runs asynchronously; the `status` field reaches \"ready\" once the heatmap is on disk.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "Unique identifier of the coverage.",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: "Human-readable name for the coverage (unique per site).",
|
||||
Required: true,
|
||||
},
|
||||
"site_id": schema.StringAttribute{
|
||||
Description: "ID of the site the coverage belongs to.",
|
||||
Required: true,
|
||||
},
|
||||
"device_id": schema.StringAttribute{
|
||||
Description: "Optional ID of the device this coverage is attached to.",
|
||||
Optional: true,
|
||||
},
|
||||
"antenna_slug": schema.StringAttribute{
|
||||
Description: "Slug of the antenna in Towerops's catalog (e.g. \"rf-elements-twistport-symmetrical-horn-30\").",
|
||||
Required: true,
|
||||
},
|
||||
"frequency_ghz": schema.Float64Attribute{
|
||||
Description: "Operating frequency in GHz (0.7–90).",
|
||||
Required: true,
|
||||
},
|
||||
"tx_power_dbm": schema.Float64Attribute{
|
||||
Description: "Transmit power at the radio in dBm (-10 to 50).",
|
||||
Required: true,
|
||||
},
|
||||
"height_agl_ft": schema.Float64Attribute{
|
||||
Description: "Antenna height above ground level in feet (3–650).",
|
||||
Required: true,
|
||||
},
|
||||
"azimuth_deg": schema.Float64Attribute{
|
||||
Description: "Antenna boresight azimuth in degrees, true north (0–360).",
|
||||
Required: true,
|
||||
},
|
||||
"downtilt_deg": schema.Float64Attribute{
|
||||
Description: "Antenna mechanical down-tilt in degrees (positive = down).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"radius_mi": schema.Float64Attribute{
|
||||
Description: "Coverage radius in miles (0.3–25).",
|
||||
Required: true,
|
||||
},
|
||||
"cable_loss_db": schema.Float64Attribute{
|
||||
Description: "Coax / waveguide loss in dB.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"foliage_tuning": schema.Int64Attribute{
|
||||
Description: "0–100 slider that increases predicted path loss in wooded areas.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"latitude_override": schema.Float64Attribute{
|
||||
Description: "Optional latitude override for the antenna location. Defaults to the parent site's latitude.",
|
||||
Optional: true,
|
||||
},
|
||||
"longitude_override": schema.Float64Attribute{
|
||||
Description: "Optional longitude override for the antenna location. Defaults to the parent site's longitude.",
|
||||
Optional: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Description: "Compute status: \"draft\", \"queued\", \"computing\", \"ready\", or \"failed\".",
|
||||
Computed: true,
|
||||
},
|
||||
"progress_pct": schema.Int64Attribute{
|
||||
Description: "Compute progress in percent (0–100).",
|
||||
Computed: true,
|
||||
},
|
||||
"error_message": schema.StringAttribute{
|
||||
Description: "Failure reason when status is \"failed\".",
|
||||
Computed: true,
|
||||
},
|
||||
"computed_at": schema.StringAttribute{
|
||||
Description: "Timestamp when the most recent successful compute finished.",
|
||||
Computed: true,
|
||||
},
|
||||
"png_path": schema.StringAttribute{
|
||||
Description: "Server-relative URL of the colored heatmap PNG (default SM-height tier).",
|
||||
Computed: true,
|
||||
},
|
||||
"raster_path": schema.StringAttribute{
|
||||
Description: "Server-relative URL of the Float32 GeoTIFF raster.",
|
||||
Computed: true,
|
||||
},
|
||||
"inserted_at": schema.StringAttribute{
|
||||
Description: "Timestamp the coverage record was created.",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*Client)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Resource Configure Type",
|
||||
fmt.Sprintf("Expected *Client, got: %T", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var data CoverageResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
created, err := r.client.CreateCoverage(buildCoverageFromModel(&data))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to create coverage", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applyCoverageToModel(&data, created)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var data CoverageResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
cov, err := r.client.GetCoverage(data.ID.ValueString())
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Failed to read coverage", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applyCoverageToModel(&data, cov)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var data CoverageResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
updated, err := r.client.UpdateCoverage(data.ID.ValueString(), buildCoverageFromModel(&data))
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
created, createErr := r.client.CreateCoverage(buildCoverageFromModel(&data))
|
||||
if createErr != nil {
|
||||
resp.Diagnostics.AddError("Failed to recreate coverage after 404", createErr.Error())
|
||||
return
|
||||
}
|
||||
applyCoverageToModel(&data, created)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Failed to update coverage", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applyCoverageToModel(&data, updated)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *CoverageResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var data CoverageResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.client.DeleteCoverage(data.ID.ValueString()); err != nil && !errors.Is(err, ErrNotFound) {
|
||||
resp.Diagnostics.AddError("Failed to delete coverage", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CoverageResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
|
||||
// buildCoverageFromModel converts the Terraform plan into the JSON
|
||||
// shape the API expects.
|
||||
func buildCoverageFromModel(data *CoverageResourceModel) Coverage {
|
||||
cov := Coverage{
|
||||
Name: data.Name.ValueString(),
|
||||
SiteID: data.SiteID.ValueString(),
|
||||
AntennaSlug: data.AntennaSlug.ValueString(),
|
||||
}
|
||||
|
||||
if !data.DeviceID.IsNull() && !data.DeviceID.IsUnknown() {
|
||||
v := data.DeviceID.ValueString()
|
||||
cov.DeviceID = &v
|
||||
}
|
||||
if !data.FrequencyGHz.IsNull() && !data.FrequencyGHz.IsUnknown() {
|
||||
v := data.FrequencyGHz.ValueFloat64()
|
||||
cov.FrequencyGHz = &v
|
||||
}
|
||||
if !data.TXPowerDbm.IsNull() && !data.TXPowerDbm.IsUnknown() {
|
||||
v := data.TXPowerDbm.ValueFloat64()
|
||||
cov.TXPowerDbm = &v
|
||||
}
|
||||
if !data.HeightAGLFt.IsNull() && !data.HeightAGLFt.IsUnknown() {
|
||||
v := data.HeightAGLFt.ValueFloat64()
|
||||
cov.HeightAGLFt = &v
|
||||
}
|
||||
if !data.AzimuthDeg.IsNull() && !data.AzimuthDeg.IsUnknown() {
|
||||
v := data.AzimuthDeg.ValueFloat64()
|
||||
cov.AzimuthDeg = &v
|
||||
}
|
||||
if !data.DowntiltDeg.IsNull() && !data.DowntiltDeg.IsUnknown() {
|
||||
v := data.DowntiltDeg.ValueFloat64()
|
||||
cov.DowntiltDeg = &v
|
||||
}
|
||||
if !data.RadiusMi.IsNull() && !data.RadiusMi.IsUnknown() {
|
||||
v := data.RadiusMi.ValueFloat64()
|
||||
cov.RadiusMi = &v
|
||||
}
|
||||
if !data.CableLossDb.IsNull() && !data.CableLossDb.IsUnknown() {
|
||||
v := data.CableLossDb.ValueFloat64()
|
||||
cov.CableLossDb = &v
|
||||
}
|
||||
if !data.FoliageTuning.IsNull() && !data.FoliageTuning.IsUnknown() {
|
||||
v := int(data.FoliageTuning.ValueInt64())
|
||||
cov.FoliageTuning = &v
|
||||
}
|
||||
if !data.LatitudeOverride.IsNull() && !data.LatitudeOverride.IsUnknown() {
|
||||
v := data.LatitudeOverride.ValueFloat64()
|
||||
cov.LatitudeOverride = &v
|
||||
}
|
||||
if !data.LongitudeOverride.IsNull() && !data.LongitudeOverride.IsUnknown() {
|
||||
v := data.LongitudeOverride.ValueFloat64()
|
||||
cov.LongitudeOverride = &v
|
||||
}
|
||||
|
||||
return cov
|
||||
}
|
||||
|
||||
// applyCoverageToModel maps the API response back onto the Terraform
|
||||
// model.
|
||||
func applyCoverageToModel(data *CoverageResourceModel, cov *Coverage) {
|
||||
data.ID = types.StringValue(cov.ID)
|
||||
data.Name = types.StringValue(cov.Name)
|
||||
data.SiteID = types.StringValue(cov.SiteID)
|
||||
data.AntennaSlug = types.StringValue(cov.AntennaSlug)
|
||||
|
||||
if cov.DeviceID != nil {
|
||||
data.DeviceID = types.StringValue(*cov.DeviceID)
|
||||
} else {
|
||||
data.DeviceID = types.StringNull()
|
||||
}
|
||||
if cov.FrequencyGHz != nil {
|
||||
data.FrequencyGHz = types.Float64Value(*cov.FrequencyGHz)
|
||||
} else if cov.FrequencyMHz != nil {
|
||||
data.FrequencyGHz = types.Float64Value(float64(*cov.FrequencyMHz) / 1000.0)
|
||||
}
|
||||
if cov.TXPowerDbm != nil {
|
||||
data.TXPowerDbm = types.Float64Value(*cov.TXPowerDbm)
|
||||
}
|
||||
if cov.HeightAGLFt != nil {
|
||||
data.HeightAGLFt = types.Float64Value(*cov.HeightAGLFt)
|
||||
} else if cov.HeightAGLM != nil {
|
||||
data.HeightAGLFt = types.Float64Value(*cov.HeightAGLM / 0.3048)
|
||||
}
|
||||
if cov.AzimuthDeg != nil {
|
||||
data.AzimuthDeg = types.Float64Value(*cov.AzimuthDeg)
|
||||
}
|
||||
if cov.DowntiltDeg != nil {
|
||||
data.DowntiltDeg = types.Float64Value(*cov.DowntiltDeg)
|
||||
} else {
|
||||
data.DowntiltDeg = types.Float64Value(0)
|
||||
}
|
||||
if cov.RadiusMi != nil {
|
||||
data.RadiusMi = types.Float64Value(*cov.RadiusMi)
|
||||
} else if cov.RadiusM != nil {
|
||||
data.RadiusMi = types.Float64Value(float64(*cov.RadiusM) / 1609.344)
|
||||
}
|
||||
if cov.CableLossDb != nil {
|
||||
data.CableLossDb = types.Float64Value(*cov.CableLossDb)
|
||||
} else {
|
||||
data.CableLossDb = types.Float64Value(0)
|
||||
}
|
||||
if cov.FoliageTuning != nil {
|
||||
data.FoliageTuning = types.Int64Value(int64(*cov.FoliageTuning))
|
||||
} else {
|
||||
data.FoliageTuning = types.Int64Value(0)
|
||||
}
|
||||
if cov.LatitudeOverride != nil {
|
||||
data.LatitudeOverride = types.Float64Value(*cov.LatitudeOverride)
|
||||
} else {
|
||||
data.LatitudeOverride = types.Float64Null()
|
||||
}
|
||||
if cov.LongitudeOverride != nil {
|
||||
data.LongitudeOverride = types.Float64Value(*cov.LongitudeOverride)
|
||||
} else {
|
||||
data.LongitudeOverride = types.Float64Null()
|
||||
}
|
||||
|
||||
data.Status = types.StringValue(cov.Status)
|
||||
data.ProgressPct = types.Int64Value(int64(cov.ProgressPct))
|
||||
if cov.ErrorMessage != "" {
|
||||
data.ErrorMessage = types.StringValue(cov.ErrorMessage)
|
||||
} else {
|
||||
data.ErrorMessage = types.StringNull()
|
||||
}
|
||||
if cov.ComputedAt != "" {
|
||||
data.ComputedAt = types.StringValue(cov.ComputedAt)
|
||||
} else {
|
||||
data.ComputedAt = types.StringNull()
|
||||
}
|
||||
if cov.PNGPath != "" {
|
||||
data.PNGPath = types.StringValue(cov.PNGPath)
|
||||
} else {
|
||||
data.PNGPath = types.StringNull()
|
||||
}
|
||||
if cov.RasterPath != "" {
|
||||
data.RasterPath = types.StringValue(cov.RasterPath)
|
||||
} else {
|
||||
data.RasterPath = types.StringNull()
|
||||
}
|
||||
if cov.InsertedAt != "" {
|
||||
data.InsertedAt = types.StringValue(cov.InsertedAt)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,6 +95,7 @@ func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Reso
|
|||
NewAgentResource,
|
||||
NewIntegrationResource,
|
||||
NewMaintenanceWindowResource,
|
||||
NewCoverageResource,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue