Compare commits

...

11 commits

Author SHA1 Message Date
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
936b278652
ci: unset GITHUB_TOKEN so GoReleaser uses GITEA_TOKEN 2026-05-05 13:47:01 -05:00
5a37cfad8b
ci: rename release token secret to CODEBERG_TOKEN 2026-05-05 13:43:30 -05:00
30c1954efe
ci: retrigger workflow 2026-05-05 13:14:20 -05:00
3c172132ba
ci: switch runs-on to ubuntu-latest 2026-05-05 13:06:55 -05:00
b194346047
ci: remove container image pin from release workflow 2026-05-05 13:05:41 -05:00
5da9643e92
chore: migrate from GitHub to Codeberg/Forgejo
- Rename Go module path to codeberg.org/towerops/terraform-provider-towerops
- Replace .github/workflows/release.yml with .forgejo/workflows/release.yml
- Configure GoReleaser to publish to Codeberg via Gitea API
- Bump indirect dependencies (golang.org/x/*, grpc, go-plugin, etc.)
2026-05-05 12:59:27 -05:00
b922dbda22
refactor: remove device_role_source field
- Removed DeviceRoleSource field from Device struct and DeviceResourceModel
- Updated device_role description to reflect default value "other"
- Device role is now optional and defaults to "other" if not specified
- Cleaned up all CRUD operations to remove device_role_source handling

All device types are now manually selected with "other" as default.
2026-03-22 11:20:04 -05:00
80e800baf4
feat: add device_role and device_role_source fields to device resource
- Add device_role field to Device struct in client
- Add device_role and device_role_source to DeviceResourceModel
- Add schema attributes for device_role (optional/computed) and device_role_source (computed)
- Handle device_role in Create, Read, and Update operations
- Valid device_role values: server, switch, router, access_point, backhaul, other
- device_role_source automatically set to 'manual' by API when device_role is provided
2026-03-22 11:06:14 -05:00
Graham McIntire
de81fcdc57
Merge pull request #2 from towerops-app/fix/site-lat-lng-computed
fix site lat/lng inconsistent result after apply
2026-03-18 09:34:27 -05:00
e8e2a5fa56
fix site lat/lng inconsistent result after apply
mark latitude and longitude as Computed in addition to Optional so
the provider accepts server-set values (geocoding, UI) when the user
omits them from config
2026-03-18 09:33:22 -05:00
14 changed files with 674 additions and 98 deletions

View file

@ -0,0 +1,43 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- uses: https://code.forgejo.org/actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Install GnuPG and GoReleaser
run: |
apt-get update
apt-get install -y gnupg curl
curl -sfL "https://github.com/goreleaser/goreleaser/releases/latest/download/goreleaser_Linux_x86_64.tar.gz" \
| tar -xz -C /usr/local/bin goreleaser
- name: Import GPG key
id: import_gpg
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent || true
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
FINGERPRINT=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT"
- name: Run GoReleaser
env:
GITEA_TOKEN: ${{ secrets.CODEBERG_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
PASSPHRASE: ${{ secrets.PASSPHRASE }}
run: |
unset GITHUB_TOKEN
goreleaser release --clean

View file

@ -1,33 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
id: import_gpg
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}

View file

@ -1,4 +1,7 @@
version: 2 version: 2
gitea_urls:
api: https://codeberg.org/api/v1/
download: https://codeberg.org
builds: builds:
- env: - env:
- CGO_ENABLED=0 - CGO_ENABLED=0
@ -26,6 +29,10 @@ signs:
- artifacts: checksum - artifacts: checksum
args: args:
- "--batch" - "--batch"
- "--pinentry-mode"
- "loopback"
- "--passphrase"
- "{{ .Env.PASSPHRASE }}"
- "--local-user" - "--local-user"
- "{{ .Env.GPG_FINGERPRINT }}" - "{{ .Env.GPG_FINGERPRINT }}"
- "--output" - "--output"

View file

@ -141,7 +141,6 @@ resource "towerops_check" "wan_ping" {
- `hostname` (String) - The hostname to resolve. Required for DNS checks. - `hostname` (String) - The hostname to resolve. Required for DNS checks.
- `record_type` (String) - DNS record type (`A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `PTR`). Default: `"A"`. - `record_type` (String) - DNS record type (`A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `PTR`). Default: `"A"`.
- `dns_server` (String) - DNS server to query. Uses system default if not set.
- `expected_result` (String) - Expected DNS resolution result. - `expected_result` (String) - Expected DNS resolution result.
#### Ping Check Fields (used when `check_type = "ping"`) #### Ping Check Fields (used when `check_type = "ping"`)

View file

@ -15,12 +15,11 @@ resource "towerops_check" "radius_port" {
port = 1812 port = 1812
} }
# DNS resolution check # DNS resolution check (uses device IP as DNS server when device_id is set)
resource "towerops_check" "dns_resolution" { resource "towerops_check" "dns_resolution" {
name = "DNS Resolution" name = "DNS Resolution"
check_type = "dns" check_type = "dns"
hostname = "google.com" hostname = "google.com"
dns_server = "10.0.0.1"
record_type = "A" record_type = "A"
} }

26
go.mod
View file

@ -1,4 +1,4 @@
module github.com/towerops/terraform-provider-towerops module codeberg.org/towerops/terraform-provider-towerops
go 1.25.0 go 1.25.0
@ -13,7 +13,7 @@ require (
github.com/agext/levenshtein v1.2.2 // indirect github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.19.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
@ -22,7 +22,7 @@ require (
github.com/hashicorp/go-cty v1.5.0 // indirect github.com/hashicorp/go-cty v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.7.0 // indirect github.com/hashicorp/go-plugin v1.8.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/go-version v1.8.0 // indirect
@ -37,7 +37,7 @@ require (
github.com/hashicorp/terraform-svchost v0.2.1 // indirect github.com/hashicorp/terraform-svchost v0.2.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@ -48,15 +48,15 @@ require (
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect github.com/zclconf/go-cty v1.17.0 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.50.0 // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.36.0 // indirect
golang.org/x/tools v0.41.0 // indirect golang.org/x/tools v0.43.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect
google.golang.org/grpc v1.79.2 // indirect google.golang.org/grpc v1.81.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )

73
go.sum
View file

@ -24,8 +24,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
@ -64,8 +64,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs=
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-plugin v1.8.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@ -118,8 +118,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
@ -163,34 +163,34 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -201,33 +201,32 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=

View file

@ -55,7 +55,6 @@ type CheckResourceModel struct {
// DNS config // DNS config
Hostname types.String `tfsdk:"hostname"` Hostname types.String `tfsdk:"hostname"`
RecordType types.String `tfsdk:"record_type"` RecordType types.String `tfsdk:"record_type"`
DNSServer types.String `tfsdk:"dns_server"`
ExpectedResult types.String `tfsdk:"expected_result"` ExpectedResult types.String `tfsdk:"expected_result"`
// Ping config // Ping config
@ -205,10 +204,6 @@ func (r *CheckResource) Schema(ctx context.Context, req resource.SchemaRequest,
Computed: true, Computed: true,
Default: stringdefault.StaticString("A"), Default: stringdefault.StaticString("A"),
}, },
"dns_server": schema.StringAttribute{
Description: "DNS server to query. Uses system default if not set.",
Optional: true,
},
"expected_result": schema.StringAttribute{ "expected_result": schema.StringAttribute{
Description: "Expected DNS resolution result.", Description: "Expected DNS resolution result.",
Optional: true, Optional: true,
@ -490,9 +485,6 @@ func buildConfig(data CheckResourceModel) map[string]any {
if !data.RecordType.IsNull() { if !data.RecordType.IsNull() {
config["record_type"] = data.RecordType.ValueString() config["record_type"] = data.RecordType.ValueString()
} }
if !data.DNSServer.IsNull() {
config["server"] = data.DNSServer.ValueString()
}
if !data.ExpectedResult.IsNull() { if !data.ExpectedResult.IsNull() {
config["expected"] = data.ExpectedResult.ValueString() config["expected"] = data.ExpectedResult.ValueString()
} }
@ -624,9 +616,6 @@ func unpackConfig(config map[string]any, checkType string, data *CheckResourceMo
if v, ok := config["record_type"].(string); ok { if v, ok := config["record_type"].(string); ok {
data.RecordType = types.StringValue(v) data.RecordType = types.StringValue(v)
} }
if v, ok := config["server"].(string); ok {
data.DNSServer = types.StringValue(v)
}
if v, ok := config["expected"].(string); ok { if v, ok := config["expected"].(string); ok {
data.ExpectedResult = types.StringValue(v) data.ExpectedResult = types.StringValue(v)
} }

View file

@ -61,6 +61,7 @@ type Device struct {
SNMPVersion *string `json:"snmp_version,omitempty"` SNMPVersion *string `json:"snmp_version,omitempty"`
SNMPPort *int `json:"snmp_port,omitempty"` SNMPPort *int `json:"snmp_port,omitempty"`
CheckIntervalSeconds *int `json:"check_interval_seconds,omitempty"` CheckIntervalSeconds *int `json:"check_interval_seconds,omitempty"`
DeviceRole *string `json:"device_role,omitempty"`
// SNMPv3 fields // SNMPv3 fields
SNMPv3SecurityLevel *string `json:"snmpv3_security_level,omitempty"` SNMPv3SecurityLevel *string `json:"snmpv3_security_level,omitempty"`
SNMPv3Username *string `json:"snmpv3_username,omitempty"` SNMPv3Username *string `json:"snmpv3_username,omitempty"`
@ -662,3 +663,117 @@ func (c *Client) UpdateOrganization(org Organization) (*Organization, error) {
return &result.Data, nil 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
}

View 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.790).",
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 (3650).",
Required: true,
},
"azimuth_deg": schema.Float64Attribute{
Description: "Antenna boresight azimuth in degrees, true north (0360).",
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.325).",
Required: true,
},
"cable_loss_db": schema.Float64Attribute{
Description: "Coax / waveguide loss in dB.",
Optional: true,
Computed: true,
},
"foliage_tuning": schema.Int64Attribute{
Description: "0100 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 (0100).",
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)
}
}

View file

@ -36,6 +36,7 @@ type DeviceResourceModel struct {
SNMPEnabled types.Bool `tfsdk:"snmp_enabled"` SNMPEnabled types.Bool `tfsdk:"snmp_enabled"`
SNMPVersion types.String `tfsdk:"snmp_version"` SNMPVersion types.String `tfsdk:"snmp_version"`
SNMPPort types.Int64 `tfsdk:"snmp_port"` SNMPPort types.Int64 `tfsdk:"snmp_port"`
DeviceRole types.String `tfsdk:"device_role"`
SNMPv3SecurityLevel types.String `tfsdk:"snmpv3_security_level"` SNMPv3SecurityLevel types.String `tfsdk:"snmpv3_security_level"`
SNMPv3Username types.String `tfsdk:"snmpv3_username"` SNMPv3Username types.String `tfsdk:"snmpv3_username"`
SNMPv3AuthProtocol types.String `tfsdk:"snmpv3_auth_protocol"` SNMPv3AuthProtocol types.String `tfsdk:"snmpv3_auth_protocol"`
@ -119,6 +120,11 @@ func (r *DeviceResource) Schema(ctx context.Context, req resource.SchemaRequest,
Computed: true, Computed: true,
Default: int64default.StaticInt64(161), Default: int64default.StaticInt64(161),
}, },
"device_role": schema.StringAttribute{
Description: "The device type/role. Valid values: server, switch, router, access_point, backhaul, other. Defaults to 'other' if not specified.",
Optional: true,
Computed: true,
},
"snmpv3_security_level": schema.StringAttribute{ "snmpv3_security_level": schema.StringAttribute{
Description: "SNMPv3 security level (noAuthNoPriv, authNoPriv, or authPriv). Only used when snmp_version is '3'.", Description: "SNMPv3 security level (noAuthNoPriv, authNoPriv, or authPriv). Only used when snmp_version is '3'.",
Optional: true, Optional: true,
@ -205,6 +211,11 @@ func (r *DeviceResource) Create(ctx context.Context, req resource.CreateRequest,
device.Description = &desc device.Description = &desc
} }
if !data.DeviceRole.IsNull() {
role := data.DeviceRole.ValueString()
device.DeviceRole = &role
}
if !data.MonitoringEnabled.IsNull() { if !data.MonitoringEnabled.IsNull() {
enabled := data.MonitoringEnabled.ValueBool() enabled := data.MonitoringEnabled.ValueBool()
device.MonitoringEnabled = &enabled device.MonitoringEnabled = &enabled
@ -284,6 +295,13 @@ func (r *DeviceResource) Create(ctx context.Context, req resource.CreateRequest,
data.Name = types.StringValue(*created.Name) data.Name = types.StringValue(*created.Name)
} }
if created.DeviceRole != nil {
data.DeviceRole = types.StringValue(*created.DeviceRole)
} else {
data.DeviceRole = types.StringNull()
}
if created.MonitoringEnabled != nil { if created.MonitoringEnabled != nil {
data.MonitoringEnabled = types.BoolValue(*created.MonitoringEnabled) data.MonitoringEnabled = types.BoolValue(*created.MonitoringEnabled)
} }
@ -340,6 +358,12 @@ func (r *DeviceResource) Read(ctx context.Context, req resource.ReadRequest, res
data.Description = types.StringNull() data.Description = types.StringNull()
} }
if device.DeviceRole != nil {
data.DeviceRole = types.StringValue(*device.DeviceRole)
} else {
data.DeviceRole = types.StringNull()
}
if device.MonitoringEnabled != nil { if device.MonitoringEnabled != nil {
data.MonitoringEnabled = types.BoolValue(*device.MonitoringEnabled) data.MonitoringEnabled = types.BoolValue(*device.MonitoringEnabled)
} }
@ -428,6 +452,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
device.Description = &desc device.Description = &desc
} }
if !data.DeviceRole.IsNull() {
role := data.DeviceRole.ValueString()
device.DeviceRole = &role
}
if !data.MonitoringEnabled.IsNull() { if !data.MonitoringEnabled.IsNull() {
enabled := data.MonitoringEnabled.ValueBool() enabled := data.MonitoringEnabled.ValueBool()
device.MonitoringEnabled = &enabled device.MonitoringEnabled = &enabled
@ -513,6 +542,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
if created.Description != nil { if created.Description != nil {
data.Description = types.StringValue(*created.Description) data.Description = types.StringValue(*created.Description)
} }
if created.DeviceRole != nil {
data.DeviceRole = types.StringValue(*created.DeviceRole)
} else {
data.DeviceRole = types.StringNull()
}
if created.MonitoringEnabled != nil { if created.MonitoringEnabled != nil {
data.MonitoringEnabled = types.BoolValue(*created.MonitoringEnabled) data.MonitoringEnabled = types.BoolValue(*created.MonitoringEnabled)
} }
@ -552,6 +586,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
if updated.Description != nil { if updated.Description != nil {
data.Description = types.StringValue(*updated.Description) data.Description = types.StringValue(*updated.Description)
} }
if updated.DeviceRole != nil {
data.DeviceRole = types.StringValue(*updated.DeviceRole)
} else {
data.DeviceRole = types.StringNull()
}
if updated.MonitoringEnabled != nil { if updated.MonitoringEnabled != nil {
data.MonitoringEnabled = types.BoolValue(*updated.MonitoringEnabled) data.MonitoringEnabled = types.BoolValue(*updated.MonitoringEnabled)
} }

View file

@ -95,6 +95,7 @@ func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Reso
NewAgentResource, NewAgentResource,
NewIntegrationResource, NewIntegrationResource,
NewMaintenanceWindowResource, NewMaintenanceWindowResource,
NewCoverageResource,
} }
} }

View file

@ -68,10 +68,12 @@ func (r *SiteResource) Schema(ctx context.Context, req resource.SchemaRequest, r
"latitude": schema.Float64Attribute{ "latitude": schema.Float64Attribute{
Description: "The latitude of the site (-90 to 90).", Description: "The latitude of the site (-90 to 90).",
Optional: true, Optional: true,
Computed: true,
}, },
"longitude": schema.Float64Attribute{ "longitude": schema.Float64Attribute{
Description: "The longitude of the site (-180 to 180).", Description: "The longitude of the site (-180 to 180).",
Optional: true, Optional: true,
Computed: true,
}, },
"snmp_community": schema.StringAttribute{ "snmp_community": schema.StringAttribute{
Description: "The default SNMP community string for devices at this site.", Description: "The default SNMP community string for devices at this site.",

View file

@ -5,7 +5,7 @@ import (
"log" "log"
"github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/towerops/terraform-provider-towerops/internal/provider" "codeberg.org/towerops/terraform-provider-towerops/internal/provider"
) )
var version = "dev" var version = "dev"