Compare commits
11 commits
632a3ae4df
...
05530894ae
| Author | SHA1 | Date | |
|---|---|---|---|
| 05530894ae | |||
| 936b278652 | |||
| 5a37cfad8b | |||
| 30c1954efe | |||
| 3c172132ba | |||
| b194346047 | |||
| 5da9643e92 | |||
| b922dbda22 | |||
| 80e800baf4 | |||
|
|
de81fcdc57 | ||
| e8e2a5fa56 |
14 changed files with 674 additions and 98 deletions
43
.forgejo/workflows/release.yml
Normal file
43
.forgejo/workflows/release.yml
Normal 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
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
|
|
@ -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 }}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
version: 2
|
||||
gitea_urls:
|
||||
api: https://codeberg.org/api/v1/
|
||||
download: https://codeberg.org
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
|
|
@ -26,6 +29,10 @@ signs:
|
|||
- artifacts: checksum
|
||||
args:
|
||||
- "--batch"
|
||||
- "--pinentry-mode"
|
||||
- "loopback"
|
||||
- "--passphrase"
|
||||
- "{{ .Env.PASSPHRASE }}"
|
||||
- "--local-user"
|
||||
- "{{ .Env.GPG_FINGERPRINT }}"
|
||||
- "--output"
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ resource "towerops_check" "wan_ping" {
|
|||
|
||||
- `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"`.
|
||||
- `dns_server` (String) - DNS server to query. Uses system default if not set.
|
||||
- `expected_result` (String) - Expected DNS resolution result.
|
||||
|
||||
#### Ping Check Fields (used when `check_type = "ping"`)
|
||||
|
|
|
|||
|
|
@ -15,12 +15,11 @@ resource "towerops_check" "radius_port" {
|
|||
port = 1812
|
||||
}
|
||||
|
||||
# DNS resolution check
|
||||
# DNS resolution check (uses device IP as DNS server when device_id is set)
|
||||
resource "towerops_check" "dns_resolution" {
|
||||
name = "DNS Resolution"
|
||||
check_type = "dns"
|
||||
hostname = "google.com"
|
||||
dns_server = "10.0.0.1"
|
||||
record_type = "A"
|
||||
}
|
||||
|
||||
|
|
|
|||
26
go.mod
26
go.mod
|
|
@ -1,4 +1,4 @@
|
|||
module github.com/towerops/terraform-provider-towerops
|
||||
module codeberg.org/towerops/terraform-provider-towerops
|
||||
|
||||
go 1.25.0
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/agext/levenshtein v1.2.2 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // 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/google/go-cmp v0.7.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-hclog v1.6.3 // 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-uuid v1.0.3 // 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/yamux v0.1.2 // 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/go-testing-interface v1.14.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/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect
|
||||
google.golang.org/grpc v1.81.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
|
|
|||
73
go.sum
73
go.sum
|
|
@ -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/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
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.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
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/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
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-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-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs=
|
||||
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/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
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-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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
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/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
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=
|
||||
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/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
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-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-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.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
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-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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
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-20200116001909-b77594299b42/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-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.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-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.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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
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-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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
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.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
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-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ type CheckResourceModel struct {
|
|||
// DNS config
|
||||
Hostname types.String `tfsdk:"hostname"`
|
||||
RecordType types.String `tfsdk:"record_type"`
|
||||
DNSServer types.String `tfsdk:"dns_server"`
|
||||
ExpectedResult types.String `tfsdk:"expected_result"`
|
||||
|
||||
// Ping config
|
||||
|
|
@ -205,10 +204,6 @@ func (r *CheckResource) Schema(ctx context.Context, req resource.SchemaRequest,
|
|||
Computed: true,
|
||||
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{
|
||||
Description: "Expected DNS resolution result.",
|
||||
Optional: true,
|
||||
|
|
@ -490,9 +485,6 @@ func buildConfig(data CheckResourceModel) map[string]any {
|
|||
if !data.RecordType.IsNull() {
|
||||
config["record_type"] = data.RecordType.ValueString()
|
||||
}
|
||||
if !data.DNSServer.IsNull() {
|
||||
config["server"] = data.DNSServer.ValueString()
|
||||
}
|
||||
if !data.ExpectedResult.IsNull() {
|
||||
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 {
|
||||
data.RecordType = types.StringValue(v)
|
||||
}
|
||||
if v, ok := config["server"].(string); ok {
|
||||
data.DNSServer = types.StringValue(v)
|
||||
}
|
||||
if v, ok := config["expected"].(string); ok {
|
||||
data.ExpectedResult = types.StringValue(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ type Device struct {
|
|||
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"`
|
||||
|
|
@ -662,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)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ type DeviceResourceModel struct {
|
|||
SNMPEnabled types.Bool `tfsdk:"snmp_enabled"`
|
||||
SNMPVersion types.String `tfsdk:"snmp_version"`
|
||||
SNMPPort types.Int64 `tfsdk:"snmp_port"`
|
||||
DeviceRole types.String `tfsdk:"device_role"`
|
||||
SNMPv3SecurityLevel types.String `tfsdk:"snmpv3_security_level"`
|
||||
SNMPv3Username types.String `tfsdk:"snmpv3_username"`
|
||||
SNMPv3AuthProtocol types.String `tfsdk:"snmpv3_auth_protocol"`
|
||||
|
|
@ -119,6 +120,11 @@ func (r *DeviceResource) Schema(ctx context.Context, req resource.SchemaRequest,
|
|||
Computed: true,
|
||||
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{
|
||||
Description: "SNMPv3 security level (noAuthNoPriv, authNoPriv, or authPriv). Only used when snmp_version is '3'.",
|
||||
Optional: true,
|
||||
|
|
@ -205,6 +211,11 @@ func (r *DeviceResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
device.Description = &desc
|
||||
}
|
||||
|
||||
if !data.DeviceRole.IsNull() {
|
||||
role := data.DeviceRole.ValueString()
|
||||
device.DeviceRole = &role
|
||||
}
|
||||
|
||||
if !data.MonitoringEnabled.IsNull() {
|
||||
enabled := data.MonitoringEnabled.ValueBool()
|
||||
device.MonitoringEnabled = &enabled
|
||||
|
|
@ -284,6 +295,13 @@ func (r *DeviceResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
if device.DeviceRole != nil {
|
||||
data.DeviceRole = types.StringValue(*device.DeviceRole)
|
||||
} else {
|
||||
data.DeviceRole = types.StringNull()
|
||||
}
|
||||
|
||||
if device.MonitoringEnabled != nil {
|
||||
data.MonitoringEnabled = types.BoolValue(*device.MonitoringEnabled)
|
||||
}
|
||||
|
|
@ -428,6 +452,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
device.Description = &desc
|
||||
}
|
||||
|
||||
if !data.DeviceRole.IsNull() {
|
||||
role := data.DeviceRole.ValueString()
|
||||
device.DeviceRole = &role
|
||||
}
|
||||
|
||||
if !data.MonitoringEnabled.IsNull() {
|
||||
enabled := data.MonitoringEnabled.ValueBool()
|
||||
device.MonitoringEnabled = &enabled
|
||||
|
|
@ -513,6 +542,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
if created.Description != nil {
|
||||
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 {
|
||||
data.MonitoringEnabled = types.BoolValue(*created.MonitoringEnabled)
|
||||
}
|
||||
|
|
@ -552,6 +586,11 @@ func (r *DeviceResource) Update(ctx context.Context, req resource.UpdateRequest,
|
|||
if updated.Description != nil {
|
||||
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 {
|
||||
data.MonitoringEnabled = types.BoolValue(*updated.MonitoringEnabled)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Reso
|
|||
NewAgentResource,
|
||||
NewIntegrationResource,
|
||||
NewMaintenanceWindowResource,
|
||||
NewCoverageResource,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,10 +68,12 @@ func (r *SiteResource) Schema(ctx context.Context, req resource.SchemaRequest, r
|
|||
"latitude": schema.Float64Attribute{
|
||||
Description: "The latitude of the site (-90 to 90).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"longitude": schema.Float64Attribute{
|
||||
Description: "The longitude of the site (-180 to 180).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"snmp_community": schema.StringAttribute{
|
||||
Description: "The default SNMP community string for devices at this site.",
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -5,7 +5,7 @@ import (
|
|||
"log"
|
||||
|
||||
"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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue