Compare commits

...

19 Commits

Author SHA1 Message Date
benvin 093c2e022a Merge pull request 'Fix binarylane_server_reverse_name create hanging forever' (#4) from benvin/reverse-name-wait-fix into main
ci/woodpecker/tag/release Pipeline was successful
Reviewed-on: #4
2026-10-17 12:29:33 +11:00
unkin-agent a9c133e73c Wait for reverse names on the server and bound action waits
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
2026-10-16 23:51:11 +11:00
benvin f659cfcd25 Merge pull request 'Allow server firewall rules without destination_ports' (#3) from benvin/firewall-optional-ports into main
ci/woodpecker/tag/release Pipeline was successful
Reviewed-on: #3
2026-10-12 21:18:30 +11:00
benvin 3ed14bdae3 Merge pull request 'Add binarylane_server_reverse_name resource' (#2) from benvin/reverse-name into main
Reviewed-on: #2
2026-10-12 21:17:57 +11:00
benvin 2e9f10dc46 Merge pull request 'Add estate CI, release and git.unkin.net source address' (#1) from benvin/estate-ci into main
Reviewed-on: #1
2026-10-12 21:16:55 +11:00
unkin-agent 0876e38dea Accept 202 for reverse name changes and add unit and destroy checks
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
2026-10-12 00:51:39 +11:00
unkin-agent 00b6fae313 Allow server firewall rules without destination_ports
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
2026-10-12 00:46:52 +11:00
unkin-agent f42c218d98 Add binarylane_server_reverse_name resource for IPv4 PTR records
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
2026-10-12 00:46:36 +11:00
unkin-agent d2451eb650 Add estate CI, release and Makefile; use git.unkin.net/unkin/binarylane source
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
2026-10-12 00:40:26 +11:00
Oscar Hermoso 85f65c9391 👆 bump OpenAPI schema to v0.39.1, update dependencies (#137)
* 👆 bump OpenAPI schema to v0.39.1, update dependencies

* 📑 Update README with latest regions/sizes/images
2026-08-02 22:25:07 +08:00
dependabot[bot] 262bf0a571 🐹 Bump google.golang.org/grpc from 1.79.1 to 1.79.3 (#122)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 16:51:52 +08:00
Oscar Hermoso 77271b0efd ✂️ Add separate_private_network_interface to server resource & data source (#125) (#126)
* Initial plan

* Initial plan for separate_private_network_interface support

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/df438ec9-fdbb-47a3-8ebb-592ee2843eed

* Add separate_private_network_interface to server resource and data source

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/df438ec9-fdbb-47a3-8ebb-592ee2843eed

* Run go fmt and add AGENTS.md

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/1f274b93-d7c2-4547-a24d-8d36472c8092

* Add source_and_destination_check assertion to data source test checks

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/8684d505-33e7-407c-a4fb-c6260ffdb2ea

* go generate: update types_gen.go with latest API schema (Action.Reason, Action.Title, Invoice.InvoiceViewUrl)

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/1baa947c-aaee-4c45-b2b8-9495ccf38b82

* Fix separate_private_network_interface: add ModifyPlan unknown handling and IsUnknown guard in Update

Agent-Logs-Url: https://github.com/oscarhermoso/terraform-provider-binarylane/sessions/e9b9d51d-8447-48a5-8c61-9c749562c3e1

* Simplify separate_private_network_interface description and remove dead code

Match the OpenAPI schema phrasing for consistency with other attribute
descriptions, and drop the defensive IsUnknown guard in Update — ModifyPlan
already resolves Unknown to Null/false before Update runs.

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-09 10:52:53 +08:00
dependabot[bot] 34bfbcfc2b 🐹 Bump github.com/cloudflare/circl from 1.6.1 to 1.6.3 (#116)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.1 to 1.6.3.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.1...v1.6.3)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 11:37:16 +08:00
dependabot[bot] 50a112c60c 🐈 Bump goreleaser/goreleaser-action from 6.4.0 to 7.0.0 (#117)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 6.4.0 to 7.0.0.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/e435ccd777264be153ace6237001ef4d979d3a7a...ec59f474b9834571250b370d4735c50f8e2d1e29)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 11:36:30 +08:00
dependabot[bot] c09c34f780 🐈 Bump crazy-max/ghaction-import-gpg from 6.3.0 to 7.0.0 (#118)
Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 6.3.0 to 7.0.0.
- [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases)
- [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/e89d40939c28e39f97cf32126055eeae86ba74ec...2dc316deee8e90f13e1a351ab510b4d5bc0c82cd)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-import-gpg
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 11:35:53 +08:00
dependabot[bot] a0da966714 🐈 Bump hashicorp/setup-terraform from 3 to 4 (#119)
Bumps [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) from 3 to 4.
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/setup-terraform/compare/v3...v4)

---
updated-dependencies:
- dependency-name: hashicorp/setup-terraform
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 11:35:22 +08:00
Oscar Hermoso 14a275e06f 🎈 Update BinaryLane OpenAPI schema to v0.34.4 (#120) 2026-03-07 11:34:37 +08:00
dependabot[bot] f7c0e23fad 🐹 Bump the minor-patch group across 1 directory with 9 updates (#115)
Bumps the minor-patch group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) | `0.23.0` | `0.24.0` |
| [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) | `1.16.1` | `1.18.0` |
| [github.com/hashicorp/terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) | `0.6.0` | `0.7.0` |
| [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators) | `0.18.0` | `0.19.0` |
| [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing) | `1.13.3` | `1.14.0` |
| [github.com/oapi-codegen/oapi-codegen/v2](https://github.com/oapi-codegen/oapi-codegen) | `2.5.0` | `2.6.0` |
| [github.com/oapi-codegen/runtime](https://github.com/oapi-codegen/runtime) | `1.1.2` | `1.2.0` |



Updates `github.com/hashicorp/terraform-plugin-docs` from 0.23.0 to 0.24.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-docs/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-docs/compare/v0.23.0...v0.24.0)

Updates `github.com/hashicorp/terraform-plugin-framework` from 1.16.1 to 1.18.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework/compare/v1.16.1...v1.18.0)

Updates `github.com/hashicorp/terraform-plugin-framework-timeouts` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework-timeouts/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework-timeouts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework-timeouts/compare/v0.6.0...v0.7.0)

Updates `github.com/hashicorp/terraform-plugin-framework-validators` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework-validators/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework-validators/compare/v0.18.0...v0.19.0)

Updates `github.com/hashicorp/terraform-plugin-go` from 0.29.0 to 0.30.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-go/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-go/compare/v0.29.0...v0.30.0)

Updates `github.com/hashicorp/terraform-plugin-log` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-log/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-log/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-log/compare/v0.9.0...v0.10.0)

Updates `github.com/hashicorp/terraform-plugin-testing` from 1.13.3 to 1.14.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-testing/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-testing/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-testing/compare/v1.13.3...v1.14.0)

Updates `github.com/oapi-codegen/oapi-codegen/v2` from 2.5.0 to 2.6.0
- [Release notes](https://github.com/oapi-codegen/oapi-codegen/releases)
- [Commits](https://github.com/oapi-codegen/oapi-codegen/compare/v2.5.0...v2.6.0)

Updates `github.com/oapi-codegen/runtime` from 1.1.2 to 1.2.0
- [Release notes](https://github.com/oapi-codegen/runtime/releases)
- [Commits](https://github.com/oapi-codegen/runtime/compare/v1.1.2...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-framework
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-framework-timeouts
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-framework-validators
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-go
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-log
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-testing
  dependency-version: 1.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/oapi-codegen/oapi-codegen/v2
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/oapi-codegen/runtime
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 09:38:15 +08:00
Oscar Hermoso 9a58a8112c 👍 Support vpc_ipv4_address, bump BinaryLane OpenAPI schema to v0.34.0 (#114)
* 👍 Bump BinaryLane OpenAPI schema to v0.34.0

* 💄 Support change of IPv4 addresses
2026-02-08 16:58:30 +08:00
63 changed files with 3864 additions and 1635 deletions
+2 -2
View File
@@ -32,13 +32,13 @@ jobs:
go-version-file: "go.mod" go-version-file: "go.mod"
cache: true cache: true
- name: Import GPG key - name: Import GPG key
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
id: import_gpg id: import_gpg
with: with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }} passphrase: ${{ secrets.PASSPHRASE }}
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with: with:
args: release --clean args: release --clean
env: env:
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
- uses: hashicorp/setup-terraform@v3 - uses: hashicorp/setup-terraform@v4
with: with:
terraform_version: '1.5.*' terraform_version: '1.5.*'
terraform_wrapper: false terraform_wrapper: false
+1
View File
@@ -30,3 +30,4 @@ tmp
# Normally, you wouldn't want to exclude lock files, but our examples are tested # Normally, you wouldn't want to exclude lock files, but our examples are tested
# with a locally built provider and won't be useful to others. # with a locally built provider and won't be useful to others.
.terraform.lock.hcl .terraform.lock.hcl
*.zip
+17
View File
@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: ^docs/
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.1
hooks:
- id: go-fmt
- id: go-vet
exclude: ^tools/
- id: go-mod-tidy
+30
View File
@@ -0,0 +1,30 @@
when:
- event: pull_request
steps:
- name: build
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/gobuilder:0.1.2-alma9
environment:
GOCACHE_S3_BUCKET: gocache
GOCACHE_S3_REGION: us-east-1
GOCACHE_S3_ENDPOINT_URL: https://s3.ceph.unkin.net
GOCACHE_S3_PATH_STYLE: "true"
GOCACHE_KEY_PREFIX: ci-terraform-provider-binarylane
GOCACHE_METRICS: "true"
GOCACHEPROG: go-cache-plugin --cache-dir=/tmp/gocache
AWS_ACCESS_KEY_ID:
from_secret: GOCACHE_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: GOCACHE_AWS_SECRET_ACCESS_KEY
commands:
- make build
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+30
View File
@@ -0,0 +1,30 @@
when:
- event: pull_request
steps:
- name: pre-commit
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/gobuilder:0.1.2-alma9
environment:
GOCACHE_S3_BUCKET: gocache
GOCACHE_S3_REGION: us-east-1
GOCACHE_S3_ENDPOINT_URL: https://s3.ceph.unkin.net
GOCACHE_S3_PATH_STYLE: "true"
GOCACHE_KEY_PREFIX: ci-terraform-provider-binarylane
GOCACHE_METRICS: "true"
GOCACHEPROG: go-cache-plugin --cache-dir=/tmp/gocache
AWS_ACCESS_KEY_ID:
from_secret: GOCACHE_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: GOCACHE_AWS_SECRET_ACCESS_KEY
commands:
- uvx pre-commit run --all-files
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+52
View File
@@ -0,0 +1,52 @@
when:
- event: tag
steps:
- name: package
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/gobuilder:0.1.2-alma9
environment:
GOCACHE_S3_BUCKET: gocache
GOCACHE_S3_REGION: us-east-1
GOCACHE_S3_ENDPOINT_URL: https://s3.ceph.unkin.net
GOCACHE_S3_PATH_STYLE: "true"
GOCACHE_KEY_PREFIX: ci-terraform-provider-binarylane
GOCACHE_METRICS: "true"
GOCACHEPROG: go-cache-plugin --cache-dir=/tmp/gocache
AWS_ACCESS_KEY_ID:
from_secret: GOCACHE_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: GOCACHE_AWS_SECRET_ACCESS_KEY
commands:
- make package VERSION=${CI_COMMIT_TAG}
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
- name: upload
image: git.unkin.net/unkin/almalinux9-base:20260606
commands:
- |
VERSION=$$(echo ${CI_COMMIT_TAG} | sed 's/^v//')
FILE="terraform-provider-binarylane_$${VERSION}_linux_amd64.zip"
curl -f -X PUT \
"https://artifactapi.k8s.syd1.au.unkin.net/api/v2/remotes/terraform-unkin/files/unkin/binarylane/$${FILE}" \
-H "Content-Type: application/zip" \
--data-binary @"$${FILE}"
depends_on: [package]
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
+57
View File
@@ -0,0 +1,57 @@
when:
- event: pull_request
steps:
- name: lint
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/gobuilder:0.1.2-alma9
environment:
GOCACHE_S3_BUCKET: gocache
GOCACHE_S3_REGION: us-east-1
GOCACHE_S3_ENDPOINT_URL: https://s3.ceph.unkin.net
GOCACHE_S3_PATH_STYLE: "true"
GOCACHE_KEY_PREFIX: ci-terraform-provider-binarylane
GOCACHE_METRICS: "true"
GOCACHEPROG: go-cache-plugin --cache-dir=/tmp/gocache
AWS_ACCESS_KEY_ID:
from_secret: GOCACHE_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: GOCACHE_AWS_SECRET_ACCESS_KEY
commands:
- make lint
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
- name: test
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/gobuilder:0.1.2-alma9
environment:
GOCACHE_S3_BUCKET: gocache
GOCACHE_S3_REGION: us-east-1
GOCACHE_S3_ENDPOINT_URL: https://s3.ceph.unkin.net
GOCACHE_S3_PATH_STYLE: "true"
GOCACHE_KEY_PREFIX: ci-terraform-provider-binarylane
GOCACHE_METRICS: "true"
GOCACHEPROG: go-cache-plugin --cache-dir=/tmp/gocache
AWS_ACCESS_KEY_ID:
from_secret: GOCACHE_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: GOCACHE_AWS_SECRET_ACCESS_KEY
commands:
- make test
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+36
View File
@@ -0,0 +1,36 @@
# AGENTS.md
## Before starting
Run `go generate` to pull in the latest API schema before making changes.
## Code style
- Follow patterns in existing resources, especially `server_resource.go` and `server_data_source.go`.
- New server attributes that map to a post-creation API action (e.g. `change_*`) follow the `source_and_destination_check` / `separate_private_network_interface` pattern: read the planned value before the first state save, call the action after creation if the value differs from the server default, and handle updates in the `Update` method.
- Run `go fmt ./...` before finishing — the CI will fail without it.
- Don't add `Required: false` when `Optional: true` is already set; it is redundant.
## Testing
- Tests live in `internal/provider/*_test.go`.
- A valid `BINARYLANE_API_TOKEN` is required; tests create real servers that cost money — be conscientious.
- **Always run the sweeper after tests**, regardless of success or failure:
```sh
go test -v ./internal/provider/... -sweep=all
```
- Run a specific test with:
```sh
go test -v -run TestServerResource ./internal/provider/...
```
- New attributes on the server resource should be exercised inside an existing test step where possible (e.g. the first step of `TestServerResource` already provisions a VPC server).
## Workflow
1. `go generate` — fetch latest OpenAPI spec and regenerate code + docs.
2. Make changes.
3. `go fmt ./...` — format code.
4. `go build ./...` — verify it compiles.
5. `go vet ./...` — catch common mistakes.
6. Run relevant tests, then sweep.
7. `go generate` again to regenerate docs if schema or descriptions changed.
+1 -1
View File
@@ -14,7 +14,7 @@ Based on [this example from the terraform docs](https://developer.hashicorp.com/
provider_installation { provider_installation {
dev_overrides { dev_overrides {
# Example GOBIN path, will need to be replaced with your own GOBIN path. Default is $GOPATH/bin # Example GOBIN path, will need to be replaced with your own GOBIN path. Default is $GOPATH/bin
"oscarhermoso/binarylane" = "/home/oscarhermoso/Git/terraform-provider-binarylane/bin" "git.unkin.net/unkin/binarylane" = "/path/to/terraform-provider-binarylane/bin"
} }
direct {} direct {}
+64
View File
@@ -0,0 +1,64 @@
.PHONY: build install test lint fmt clean tidy package pre-commit patch minor major check-go
BINARY := terraform-provider-binarylane
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev")
OS_ARCH := linux_amd64
INSTALL_VERSION := $(shell echo $(VERSION) | sed 's/^v//')
INSTALL_DIR := ~/.terraform.d/plugins/git.unkin.net/unkin/binarylane/$(INSTALL_VERSION)/$(OS_ARCH)
ZIP := $(BINARY)_$(INSTALL_VERSION)_$(OS_ARCH).zip
GO_VERSION_REQUIRED := 1.25
GO_VERSION_ACTUAL := $(shell go version | sed 's/go version go\([0-9]*\.[0-9]*\).*/\1/')
check-go:
@if [ "$$(printf '%s\n%s' "$(GO_VERSION_REQUIRED)" "$(GO_VERSION_ACTUAL)" | sort -V | head -1)" != "$(GO_VERSION_REQUIRED)" ]; then \
echo "ERROR: Go >= $(GO_VERSION_REQUIRED) required, found $(GO_VERSION_ACTUAL)"; exit 1; \
fi
build: check-go tidy
go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)
install: build
mkdir -p $(INSTALL_DIR)
cp $(BINARY) $(INSTALL_DIR)/
test: check-go
go test -race -count=1 ./...
lint: check-go
go vet ./...
fmt: check-go
gofmt -w .
package: build
cp $(BINARY) $(BINARY)_v$(INSTALL_VERSION)
python3 -c "import zipfile,sys; z=zipfile.ZipFile(sys.argv[1],'w',zipfile.ZIP_DEFLATED); z.write(sys.argv[2]); z.close()" $(ZIP) $(BINARY)_v$(INSTALL_VERSION)
rm $(BINARY)_v$(INSTALL_VERSION)
pre-commit:
uvx pre-commit run --all-files
clean:
rm -f $(BINARY) *.zip
tidy:
go mod tidy
_LATEST := $(shell git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' | head -1)
_BASE := $(if $(_LATEST),$(_LATEST),v0.0.0)
_MAJ := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f1)
_MIN := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f2)
_PAT := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f3)
patch:
@NEW=v$(_MAJ).$(_MIN).$(shell expr $(_PAT) + 1); \
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
minor:
@NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
major:
@NEW=v$(shell expr $(_MAJ) + 1).0.0; \
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
+13 -6
View File
@@ -57,6 +57,7 @@ jq '[ .regions[] | .slug ] | sort' tmp/regions.json
```json ```json
[ [
"adl",
"bne", "bne",
"mel", "mel",
"per", "per",
@@ -80,21 +81,24 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
```json ```json
[ [
"alma-10",
"alma-8", "alma-8",
"alma-9", "alma-9",
"byo-os", "byo-os",
"byo-os-uefi",
"byo-os-virtio-disabled", "byo-os-virtio-disabled",
"cpanel-plus-whm", "cpanel-plus-whm",
"debian-11", "debian-11",
"debian-12", "debian-12",
"rocky-8", "debian-13",
"rocky-10",
"rocky-9", "rocky-9",
"ubuntu-20.04-neon-desktop",
"ubuntu-20.04.6", "ubuntu-20.04.6",
"ubuntu-22.04", "ubuntu-22.04",
"ubuntu-22.04-desktop", "ubuntu-22.04-desktop",
"ubuntu-24.04", "ubuntu-24.04",
"windows-2012-r2", "ubuntu-24.04-neon-desktop",
"ubuntu-26.04",
"windows-2016", "windows-2016",
"windows-2016-sql-2016-web", "windows-2016-sql-2016-web",
"windows-2019", "windows-2019",
@@ -102,7 +106,8 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
"windows-2019-sql-2017-web", "windows-2019-sql-2017-web",
"windows-2022", "windows-2022",
"windows-2022-sql-2019-std", "windows-2022-sql-2019-std",
"windows-2022-sql-2019-web" "windows-2022-sql-2019-web",
"windows-2025"
] ]
``` ```
@@ -125,14 +130,16 @@ jq '[ .sizes[] | .slug ] | sort' tmp/sizes.json
"cpu-4thr", "cpu-4thr",
"cpu-6thr", "cpu-6thr",
"cpu-8thr", "cpu-8thr",
"ded-3900x-1600gb", "ded-7900x-800gb",
"ded-e2136-400gb", "ded-e2136-400gb",
"ded-e2136-800gb", "ded-e2136-800gb",
"ded-e2288g-400gb",
"ded-e2288g-800gb", "ded-e2288g-800gb",
"hdd-1000gb", "hdd-1000gb",
"hdd-2000gb", "hdd-2000gb",
"hdd-4000gb",
"hdd-500gb", "hdd-500gb",
"hdd-6000gb",
"hdd-8000gb",
"std-1vcpu", "std-1vcpu",
"std-2vcpu", "std-2vcpu",
"std-4vcpu", "std-4vcpu",
+3 -1
View File
@@ -42,17 +42,19 @@ data "binarylane_server" "example" {
- \> 24576 MB must be a multiple of 4096 - \> 24576 MB must be a multiple of 4096
- `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name. - `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.
- `permalink` (String) A randomly generated two-word identifier assigned to servers in regions that support this feature - `permalink` (String) A randomly generated two-word identifier assigned to servers in regions that support this feature
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts. - `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts.
- `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server. - `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server.
- `private_ipv6_addresses` (List of String) The private IPv6 addresses assigned to the server. - `private_ipv6_addresses` (List of String) The private IPv6 addresses assigned to the server.
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server. - `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
- `public_ipv6_addresses` (List of String) The public IPv6 addresses assigned to the server. - `public_ipv6_addresses` (List of String) The public IPv6 addresses assigned to the server.
- `region` (String) The slug of the selected region. - `region` (String) The slug of the selected region.
- `separate_private_network_interface` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled, a separate private network interface is provided for the server's VPC traffic.
- `size` (String) The slug of the selected size. - `size` (String) The slug of the selected size.
- `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding. - `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding.
- `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys. - `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.
- `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script - `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
- `vpc_id` (Number) Leave null to use default (public) network for the selected region. - `vpc_id` (Number) Leave null to use default (public) network for the selected region.
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
<a id="nestedatt--advanced_features"></a> <a id="nestedatt--advanced_features"></a>
### Nested Schema for `advanced_features` ### Nested Schema for `advanced_features`
+1 -1
View File
@@ -74,7 +74,7 @@ Read-Only:
- `offsite_backups_cost_per_gigabyte` (Number) The additional cost per GB of storage for enabling offsite backups. See the API support document for how to calculate the final cost of backups based on the options selected. - `offsite_backups_cost_per_gigabyte` (Number) The additional cost per GB of storage for enabling offsite backups. See the API support document for how to calculate the final cost of backups based on the options selected.
- `restricted_disk_values` (List of Number) If this is null the normal valid values in the documentation for SizeOptionsRequest are used, otherwise only these values (in GB) are permitted. - `restricted_disk_values` (List of Number) If this is null the normal valid values in the documentation for SizeOptionsRequest are used, otherwise only these values (in GB) are permitted.
- `transfer_cost_per_additional_gigabyte` (Number) The additional cost per GB per month for additional included transfer. - `transfer_cost_per_additional_gigabyte` (Number) The additional cost per GB per month for additional included transfer.
- `transfer_max` (Number) The maximum transfer in TB permitted for this size. - `transfer_max` (Number) The maximum transfer in TB permitted for this size. If this is the same as `transfer` no additional transfer is supported.
- `weekly_backups` (Number) The number of weekly backups included in the base size cost. - `weekly_backups` (Number) The number of weekly backups included in the base size cost.
<a id="nestedatt--sizes--options--offsite_backup_frequency_cost"></a> <a id="nestedatt--sizes--options--offsite_backup_frequency_cost"></a>
+1 -1
View File
@@ -15,7 +15,7 @@ description: |-
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+3 -1
View File
@@ -52,12 +52,14 @@ resource "binarylane_server" "example" {
- \> 24576 MB must be a multiple of 4096 - \> 24576 MB must be a multiple of 4096
- `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name. - `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.
- `password` (String, Sensitive) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions. If omitted and the server supports password change actions a random password will be generated and emailed to the account email address. - `password` (String, Sensitive) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions. If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts. - `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts.
- `separate_private_network_interface` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled, a separate private network interface is provided for the server's VPC traffic.
- `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding. - `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding.
- `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys. - `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script - `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
- `vpc_id` (Number) Leave null to use default (public) network for the selected region. - `vpc_id` (Number) Leave null to use default (public) network for the selected region.
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
### Read-Only ### Read-Only
+48
View File
@@ -0,0 +1,48 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_server_reverse_name Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
Manage the reverse name (PTR record) for a public IPv4 address of a BinaryLane server. Destroying this resource clears the custom reverse name, restoring the BinaryLane default.
---
# binarylane_server_reverse_name (Resource)
Manage the reverse name (PTR record) for a public IPv4 address of a BinaryLane server. Destroying this resource clears the custom reverse name, restoring the BinaryLane default.
## Example Usage
```terraform
resource "binarylane_server" "example" {
# ...
}
resource "binarylane_server_reverse_name" "example" {
server_id = binarylane_server.example.id
ipv4_address = binarylane_server.example.public_ipv4_addresses.0
reverse_name = "mx1.example.com"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `ipv4_address` (String) The public IPv4 address to set the reverse name for.
- `reverse_name` (String) The reverse name (PTR record) for the IPv4 address.
- `server_id` (Number) The ID of the server the IPv4 address is assigned to.
### Read-Only
- `id` (String) The ID of this resource, in the format `<server_id>:<ipv4_address>`.
## Import
Import is supported using the following syntax:
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
```shell
terraform import binarylane_server_reverse_name.example "<server id>:<ipv4 address>"
```
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ If you want to deploy multiple parts of a cloud-init configuration, you can use
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+41
View File
@@ -0,0 +1,41 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore SSH keys
.id_ed25519*
.kube
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
@@ -0,0 +1 @@
terraform import binarylane_server_reverse_name.example "<server id>:<ipv4 address>"
@@ -0,0 +1,9 @@
resource "binarylane_server" "example" {
# ...
}
resource "binarylane_server_reverse_name" "example" {
server_id = binarylane_server.example.id
ipv4_address = binarylane_server.example.public_ipv4_addresses.0
reverse_name = "mx1.example.com"
}
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
binarylane = { binarylane = {
source = "oscarhermoso/binarylane" source = "git.unkin.net/unkin/binarylane"
} }
} }
} }
+40 -41
View File
@@ -1,22 +1,20 @@
module terraform-provider-binarylane module terraform-provider-binarylane
go 1.24.0 go 1.25.8
toolchain go1.24.1
require ( require (
github.com/deepmap/oapi-codegen v1.16.3 github.com/deepmap/oapi-codegen v1.16.3
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0 github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0
github.com/hashicorp/terraform-plugin-docs v0.23.0 github.com/hashicorp/terraform-plugin-docs v0.24.0
github.com/hashicorp/terraform-plugin-framework v1.16.1 github.com/hashicorp/terraform-plugin-framework v1.19.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.29.0 github.com/hashicorp/terraform-plugin-go v0.31.0
github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-log v0.11.0
github.com/hashicorp/terraform-plugin-testing v1.13.3 github.com/hashicorp/terraform-plugin-testing v1.16.0
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 github.com/oapi-codegen/oapi-codegen/v2 v2.6.0
github.com/oapi-codegen/runtime v1.1.2 github.com/oapi-codegen/runtime v1.6.0
) )
require ( require (
@@ -25,8 +23,8 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/agext/levenshtein v1.2.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect
@@ -34,12 +32,12 @@ require (
github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.19.0 // indirect
github.com/frankban/quicktest v1.14.6 // indirect github.com/frankban/quicktest v1.14.6 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
@@ -52,30 +50,30 @@ 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.7 // 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.7.0 // indirect github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect github.com/hashicorp/hc-install v0.9.4 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.23.1 // indirect github.com/hashicorp/terraform-exec v0.25.1 // indirect
github.com/hashicorp/terraform-json v0.27.0 // indirect github.com/hashicorp/terraform-json v0.27.2 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-registry-address v0.5.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.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/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // 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.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
@@ -96,25 +94,26 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/woodsbury/decimal128 v1.3.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yuin/goldmark v1.7.7 // indirect github.com/yuin/goldmark v1.7.7 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect github.com/zclconf/go-cty v1.18.1 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.41.0 // indirect golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.40.0 // indirect
golang.org/x/tools v0.36.0 // indirect golang.org/x/tools v0.47.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-20250728155136-f173205681a0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 // indirect
google.golang.org/grpc v1.75.1 // indirect google.golang.org/grpc v1.83.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+104 -99
View File
@@ -13,11 +13,11 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
@@ -36,11 +36,13 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@@ -56,22 +58,22 @@ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5ql
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
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.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -125,51 +127,51 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
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.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= github.com/hashicorp/hc-install v0.9.4 h1:KKWOpUG0EqIV63Qk2GGFrZ0s275NVs5lKf9N5vjBNoc=
github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= github.com/hashicorp/hc-install v0.9.4/go.mod h1:4LRYeEN2bMIFfIv57ldMWt9awfuZhvpbRt0vWmv51WU=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.23.1 h1:diK5NSSDXDKqHEOIQefBMu9ny+FhzwlwV0xgUTB7VTo= github.com/hashicorp/terraform-exec v0.25.1 h1:PRutYRGM8pixV3B8812NYoBK5O+yuf3qcB/70KFKGiU=
github.com/hashicorp/terraform-exec v0.23.1/go.mod h1:e4ZEg9BJDRaSalGm2z8vvrPONt0XWG0/tXpmzYTf+dM= github.com/hashicorp/terraform-exec v0.25.1/go.mod h1:+izOYrs9sKMQK4OYvGDnrSSJHY/pm4e4eXFqSL2Q5mA=
github.com/hashicorp/terraform-json v0.27.0 h1:REIlFzMMkIyTbhq69NC30bYiUYLv7iVhwM8ObnLo0p8= github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=
github.com/hashicorp/terraform-json v0.27.0/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 h1:eaI/3dsu2T5QAXbA+7N+B+UBj20GdtYnsRuYypKh3S4= github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 h1:eaI/3dsu2T5QAXbA+7N+B+UBj20GdtYnsRuYypKh3S4=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1/go.mod h1:kpYM23L7NtcfaQdWAN0QFkV/lU0w16qJ2ddAPCI4zAg= github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1/go.mod h1:kpYM23L7NtcfaQdWAN0QFkV/lU0w16qJ2ddAPCI4zAg=
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0 h1:IKpc337XKk50QyQPSxLrHwdqSo1E2XqCMxFkWsZcTvc= github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0 h1:IKpc337XKk50QyQPSxLrHwdqSo1E2XqCMxFkWsZcTvc=
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0/go.mod h1:tT6wl80h7nsMBw+1yZRgJXi+Ys85PUai11weDqysvp4= github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0/go.mod h1:tT6wl80h7nsMBw+1yZRgJXi+Ys85PUai11weDqysvp4=
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 h1:91dQG1A/DxP6vRz9GiytDTrZTXDbhHPvmpYnAyWA/Vw= github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 h1:91dQG1A/DxP6vRz9GiytDTrZTXDbhHPvmpYnAyWA/Vw=
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0/go.mod h1:fywrEKpordQypmAjz/HIfm2LuNVmyJ6KDe8XT9GdJxQ= github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0/go.mod h1:fywrEKpordQypmAjz/HIfm2LuNVmyJ6KDe8XT9GdJxQ=
github.com/hashicorp/terraform-plugin-docs v0.23.0 h1:sipnfD4/9EJBg9zekym+s1H6qmLAKJHhGWBwvN9v/hE= github.com/hashicorp/terraform-plugin-docs v0.24.0 h1:YNZYd+8cpYclQyXbl1EEngbld8w7/LPOm99GD5nikIU=
github.com/hashicorp/terraform-plugin-docs v0.23.0/go.mod h1:J4b5AtMRgJlDrwCQz+G4hKABgHY5m56PnsRmdAzBwW8= github.com/hashicorp/terraform-plugin-docs v0.24.0/go.mod h1:YLg+7LEwVmRuJc0EuCw0SPLxuQXw5mW8iJ5ml/kvi+o=
github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA= github.com/hashicorp/terraform-plugin-framework v1.19.0 h1:q0bwyhxAOR3vfdgbk9iplv3MlTv/dhBHTXjQOtQDoBA=
github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y= github.com/hashicorp/terraform-plugin-framework v1.19.0/go.mod h1:YRXOBu0jvs7xp4AThBbX4mAzYaMJ1JgtFH//oGKxwLc=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0 h1:Vv16e7EW4nT9668IV0RhdpEmnLl0im7BZx6J+QMlUkg= github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0/go.mod h1:rpHo9hZLn4vEkvNL5xsSdLRdaDZKSinuc0xL+BdOpVA= github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw= github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA= github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= github.com/hashicorp/terraform-plugin-go v0.31.0 h1:0Fz2r9DQ+kNNl6bx8HRxFd1TfMKUvnrOtvJPmp3Z0q8=
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= github.com/hashicorp/terraform-plugin-go v0.31.0/go.mod h1:A88bDhd/cW7FnwqxQRz3slT+QY6yzbHKc6AOTtmdeS8=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.11.0 h1:WjhcpZIVqP8YRe83+dIZXncwSgtu4vh27i23G33PUQY=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-log v0.11.0/go.mod h1:XygBz8+m5kgwTb73MMyrnUjeNQeVWECEfg+h2opMsj0=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1 h1:2yPUd7esMOpuTaG3y1iEla1iw+tla+3ZEkkBnmOAre4=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1/go.mod h1:sq8qsxh+PwdvTQFcd17kfCoBgQo46ADNMvCpKE7t/gY=
github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo= github.com/hashicorp/terraform-plugin-testing v1.16.0 h1:GB97nGnJ1hESpDrCjqZig38RodSF0gdRzxlDupLXP38=
github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE= github.com/hashicorp/terraform-plugin-testing v1.16.0/go.mod h1:eQPYAy9xFMV7xtIFX8Y+wJGtUB++HBl329zCF6PBMZk=
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= github.com/hashicorp/terraform-registry-address v0.5.0 h1:FAlhWOLFgMvo/4f5DPhCTwRYfHYdF1DjiOtgxfGr4p0=
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-registry-address v0.5.0/go.mod h1:wOJYCN60i/gSQGPCcGdamatjxn65EZBMFVt7c/Suzis=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.2.1 h1:ubvrTFw3Q7CsoEaX7V06PtCTKG3wu7GyyobAoN4eF3Q=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/terraform-svchost v0.2.1/go.mod h1:zDMheBLvNzu7Q6o9TBvPqiZToJcSuCLXjAXxBslSky4=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -200,12 +202,12 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.15/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.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@@ -213,8 +215,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
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=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@@ -225,10 +227,12 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU= github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w= github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 h1:4i+F2cvwBFZeplxCssNdLy3MhNzUD87mI3HnayHZkAU=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oapi-codegen/oapi-codegen/v2 v2.6.0/go.mod h1:eWHeJSohQJIINJZzzQriVynfGsnlQVh0UkN2UYYcw4Q=
github.com/oapi-codegen/runtime v1.6.0 h1:7Xx+GlueD6nRuyKoCPzL434Jfi3BetbiJOrzCHp/VPU=
github.com/oapi-codegen/runtime v1.6.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
@@ -287,10 +291,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
@@ -302,6 +306,8 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9N
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -317,37 +323,37 @@ github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU=
github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
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.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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=
@@ -358,14 +364,14 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -389,9 +395,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -401,27 +406,27 @@ golang.org/x/text v0.3.6/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
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.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 h1:mJiOtnGp0k/BcSgdu03G2NwnscCfCH+h2QKUBZr18KI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -430,8 +435,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -165,8 +165,8 @@ func SizesDataSourceSchema(ctx context.Context) schema.Schema {
}, },
"transfer_max": schema.Float64Attribute{ "transfer_max": schema.Float64Attribute{
Computed: true, Computed: true,
Description: "The maximum transfer in TB permitted for this size.", Description: "The maximum transfer in TB permitted for this size. If this is the same as `transfer` no additional transfer is supported.",
MarkdownDescription: "The maximum transfer in TB permitted for this size.", MarkdownDescription: "The maximum transfer in TB permitted for this size. If this is the same as `transfer` no additional transfer is supported.",
}, },
"weekly_backups": schema.Int64Attribute{ "weekly_backups": schema.Int64Attribute{
Computed: true, Computed: true,
+2 -2
View File
@@ -82,9 +82,9 @@ func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
resp.Diagnostics.AddError("Unexpected status code listing images", string(listResp.Body)) resp.Diagnostics.AddError("Unexpected status code listing images", string(listResp.Body))
return return
} }
imgResults = append(imgResults, *listResp.JSON200.Images...) imgResults = append(imgResults, listResp.JSON200.Images...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
@@ -96,7 +96,7 @@ func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRe
return return
} }
diags := setLoadBalancerModelState(ctx, &data, lbResp.JSON200.LoadBalancer) diags := setLoadBalancerModelState(ctx, &data, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
+13 -13
View File
@@ -209,7 +209,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
return return
} }
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer) diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -220,9 +220,9 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
// Wait for load balancer to be ready // Wait for load balancer to be ready
var actionId int64 var actionId int64
for _, action := range *lbResp.JSON200.Links.Actions { for _, action := range lbResp.JSON200.Links.Actions {
if *action.Rel == "create_load_balancer" { if action.Rel == "create_load_balancer" {
actionId = *action.Id actionId = action.Id
break break
} }
} }
@@ -268,7 +268,7 @@ func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadReques
return return
} }
diags := setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer) diags := setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -337,7 +337,7 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
return return
} }
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer) diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -345,9 +345,9 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
// Wait for load balancer to be ready // Wait for load balancer to be ready
var actionId int64 var actionId int64
for _, action := range *lbResp.JSON200.Links.Actions { for _, action := range lbResp.JSON200.Links.Actions {
if *action.Rel == "update_load_balancer" { if action.Rel == "update_load_balancer" {
actionId = *action.Id actionId = action.Id
break break
} }
} }
@@ -429,7 +429,7 @@ func (r *loadBalancerResource) ImportState(
return return
} }
loadBalancers := *lbResp.JSON200.LoadBalancers loadBalancers := lbResp.JSON200.LoadBalancers
for _, lb := range loadBalancers { for _, lb := range loadBalancers {
if lb.Name == req.ID { if lb.Name == req.ID {
loadBalancer = lb loadBalancer = lb
@@ -437,7 +437,7 @@ func (r *loadBalancerResource) ImportState(
break break
} }
} }
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil { if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
@@ -459,7 +459,7 @@ func setLoadBalancerModelState(ctx context.Context, data *loadBalancerDataModel,
if lb.Region == nil { if lb.Region == nil {
data.Region = types.StringNull() data.Region = types.StringNull()
} else { } else {
data.Region = types.StringValue(*lb.Region.Slug) data.Region = types.StringValue(lb.Region.Slug)
} }
data.ServerIds, diags = types.SetValueFrom(ctx, types.Int64Type, lb.ServerIds) data.ServerIds, diags = types.SetValueFrom(ctx, types.Int64Type, lb.ServerIds)
@@ -499,7 +499,7 @@ func (r *loadBalancerResource) waitForAction(ctx context.Context, actionId int64
if err != nil { if err != nil {
return fmt.Errorf("unexpected error waiting for action: action_id=%d, error: %w", actionId, err) return fmt.Errorf("unexpected error waiting for action: action_id=%d, error: %w", actionId, err)
} }
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored { if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.Status == binarylane.Errored {
return fmt.Errorf("action failed with error: action_id=%d, error: %s", actionId, readyResp.Body) return fmt.Errorf("action failed with error: action_id=%d, error: %s", actionId, readyResp.Body)
} }
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil { if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
@@ -182,7 +182,7 @@ func init() {
return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", listResp.Body) return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", listResp.Body)
} }
loadBalancers := *listResp.JSON200.LoadBalancers loadBalancers := listResp.JSON200.LoadBalancers
for _, lb := range loadBalancers { for _, lb := range loadBalancers {
if strings.HasPrefix(lb.Name, "tf-test-") { if strings.HasPrefix(lb.Name, "tf-test-") {
deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, lb.Id) deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, lb.Id)
@@ -195,7 +195,7 @@ func init() {
log.Println("Deleted load balancer during test sweep:", lb.Id) log.Println("Deleted load balancer during test sweep:", lb.Id)
} }
} }
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
+1
View File
@@ -103,6 +103,7 @@ func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Reso
return []func() resource.Resource{ return []func() resource.Resource{
NewServerResource, NewServerResource,
NewServerFirewallRulesResource, NewServerFirewallRulesResource,
NewServerReverseNameResource,
NewSshKeyResource, NewSshKeyResource,
NewVpcResource, NewVpcResource,
NewVpcRouteEntriesResource, NewVpcRouteEntriesResource,
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *regionsDataSource) Read(ctx context.Context, req datasource.ReadRequest
} }
regResults = append(regResults, listResp.JSON200.Regions...) regResults = append(regResults, listResp.JSON200.Regions...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
+21 -7
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"slices" "slices"
"strings"
"terraform-provider-binarylane/internal/resources" "terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
@@ -105,21 +106,34 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return return
} }
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id) data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name) data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug) data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug) data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug) data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0) data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking) data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId) data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink) data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory) data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk) data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck) data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
data.SeparatePrivateNetworkInterface = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SeparatePrivateNetworkInterface)
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures if serverResp.JSON200.Server.VpcId == nil {
data.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
data.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue( data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx), resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{ map[string]attr.Value{
@@ -121,6 +121,11 @@ func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.C
return return
} }
resp.Diagnostics.Append(resolveUnknownDestinationPorts(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Save data into Terraform state // Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
} }
@@ -207,6 +212,11 @@ func (r *serverFirewallRulesResource) Update(ctx context.Context, req resource.U
return return
} }
resp.Diagnostics.Append(resolveUnknownDestinationPorts(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Save updated data into Terraform state // Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
} }
@@ -245,6 +255,22 @@ func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.D
} }
} }
// Omitted destination_ports (all ports) are planned as unknown because the attribute is computed.
func resolveUnknownDestinationPorts(ctx context.Context, data *serverFirewallRulesResourceModel) diag.Diagnostics {
var rules []resources.FirewallRulesValue
diags := data.FirewallRules.ElementsAs(ctx, &rules, false)
if diags.HasError() {
return diags
}
for i := range rules {
if rules[i].DestinationPorts.IsUnknown() {
rules[i].DestinationPorts = types.ListNull(types.StringType)
}
}
data.FirewallRules, diags = types.ListValueFrom(ctx, resources.FirewallRulesValue{}.Type(ctx), rules)
return diags
}
func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.AdvancedFirewallRule) (basetypes.ListValue, diag.Diagnostics) { func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.AdvancedFirewallRule) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics var diags diag.Diagnostics
@@ -258,7 +284,7 @@ func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.Adva
destinationAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.DestinationAddresses) destinationAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.DestinationAddresses)
diags.Append(diag...) diags.Append(diag...)
var destinationPorts basetypes.ListValue destinationPorts := types.ListNull(types.StringType)
if rule.DestinationPorts != nil { if rule.DestinationPorts != nil {
destinationPorts, diag = types.ListValueFrom(ctx, types.StringType, rule.DestinationPorts) destinationPorts, diag = types.ListValueFrom(ctx, types.StringType, rule.DestinationPorts)
diags.Append(diag...) diags.Append(diag...)
@@ -0,0 +1,94 @@
package provider
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestServerFirewallRulesResourceOmittedPorts(t *testing.T) {
ctx := context.Background()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method == http.MethodGet {
_, _ = w.Write([]byte(`{"firewall_rules":[{"action":"accept","protocol":"icmp","source_addresses":["10.0.0.0/8"],"destination_addresses":["10.0.0.1"],"destination_ports":null}]}`))
return
}
_, _ = w.Write([]byte(`{}`))
}))
defer srv.Close()
client, err := binarylane.NewClientWithConfig(srv.URL, "test")
if err != nil {
t.Fatal(err)
}
r := &serverFirewallRulesResource{bc: &BinarylaneClient{client: client}}
schemaResp := resource.SchemaResponse{}
r.Schema(ctx, resource.SchemaRequest{}, &schemaResp)
s := schemaResp.Schema
emptyState := func() tfsdk.State {
return tfsdk.State{Schema: s, Raw: tftypes.NewValue(s.Type().TerraformType(ctx), nil)}
}
ruleType := resources.FirewallRulesValue{}
rule := resources.NewFirewallRulesValueMust(ruleType.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringNull(),
"action": types.StringValue("accept"),
"protocol": types.StringValue("icmp"),
"destination_addresses": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.1")}),
"destination_ports": types.ListUnknown(types.StringType),
"source_addresses": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/8")}),
})
model := serverFirewallRulesResourceModel{}
model.ServerId = types.Int64Value(1)
model.FirewallRules = types.ListValueMust(ruleType.Type(ctx), []attr.Value{rule})
plan := tfsdk.Plan{Schema: s, Raw: tftypes.NewValue(s.Type().TerraformType(ctx), nil)}
if diags := plan.Set(ctx, &model); diags.HasError() {
t.Fatal(diags)
}
createResp := resource.CreateResponse{State: emptyState()}
r.Create(ctx, resource.CreateRequest{Plan: plan}, &createResp)
if createResp.Diagnostics.HasError() {
t.Fatal(createResp.Diagnostics)
}
var ports types.List
createResp.State.GetAttribute(ctx, path.Root("firewall_rules").AtListIndex(0).AtName("destination_ports"), &ports)
if !ports.IsNull() {
t.Fatalf("create: expected null destination_ports, got %s", ports)
}
updateResp := resource.UpdateResponse{State: emptyState()}
r.Update(ctx, resource.UpdateRequest{Plan: plan}, &updateResp)
if updateResp.Diagnostics.HasError() {
t.Fatal(updateResp.Diagnostics)
}
updateResp.State.GetAttribute(ctx, path.Root("firewall_rules").AtListIndex(0).AtName("destination_ports"), &ports)
if !ports.IsNull() {
t.Fatalf("update: expected null destination_ports, got %s", ports)
}
readResp := resource.ReadResponse{State: createResp.State}
r.Read(ctx, resource.ReadRequest{State: createResp.State}, &readResp)
if readResp.Diagnostics.HasError() {
t.Fatal(readResp.Diagnostics)
}
readResp.State.GetAttribute(ctx, path.Root("firewall_rules").AtListIndex(0).AtName("destination_ports"), &ports)
if !ports.IsNull() {
t.Fatalf("read: expected null destination_ports, got %s", ports)
}
}
+269 -70
View File
@@ -186,10 +186,9 @@ func serverSchema(ctx context.Context) schema.Schema {
} }
// Additional attributes // Additional attributes
pwDescription := pwDescription := "If this is provided the specified or default remote user's account password will be set to this value. " +
"If this is provided the specified or default remote user's account password will be set to this value. " + "Only valid if the server supports password change actions. If omitted and the server supports password " +
"Only valid if the server supports password change actions. If omitted and the server supports password " + "change actions a random password will be generated and emailed to the account email address."
"change actions a random password will be generated and emailed to the account email address."
s.Attributes["password"] = schema.StringAttribute{ s.Attributes["password"] = schema.StringAttribute{
Description: pwDescription, Description: pwDescription,
MarkdownDescription: pwDescription, MarkdownDescription: pwDescription,
@@ -242,6 +241,20 @@ func serverSchema(ctx context.Context) schema.Schema {
}, },
} }
separatePrivateNicDescription := "This attribute can only be set if your server also has a `vpc_id` attribute set. " +
"When enabled, a separate private network interface is provided for the server's VPC traffic."
s.Attributes["separate_private_network_interface"] = schema.BoolAttribute{
Description: separatePrivateNicDescription,
MarkdownDescription: separatePrivateNicDescription,
Optional: true,
Computed: true,
Validators: []validator.Bool{
boolvalidator.AlsoRequires(path.Expressions{
path.MatchRoot("vpc_id"),
}...),
},
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server." privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
s.Attributes["private_ipv4_addresses"] = schema.ListAttribute{ s.Attributes["private_ipv4_addresses"] = schema.ListAttribute{
Description: privateIpv4AddressesDescription, Description: privateIpv4AddressesDescription,
@@ -364,11 +377,29 @@ func serverSchema(ctx context.Context) schema.Schema {
}, },
} }
s.Attributes["timeouts"] = s.Attributes["timeouts"] = timeouts.Attributes(ctx, timeouts.Opts{
timeouts.Attributes(ctx, timeouts.Opts{ Create: true,
Create: true, Update: true,
Update: true, })
})
vpcIpv4Addr := s.Attributes["vpc_ipv4_address"]
vpcIpv4AddrDescription := "If provided this will be the IPv4 address for the server's private VPC network adapter." +
" If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id`" +
" is provided."
s.Attributes["vpc_ipv4_address"] = schema.StringAttribute{
Description: vpcIpv4AddrDescription,
MarkdownDescription: vpcIpv4AddrDescription,
Optional: vpcIpv4Addr.IsOptional(),
Computed: vpcIpv4Addr.IsComputed(),
Validators: []validator.String{
stringvalidator.AlsoRequires(path.Expressions{
path.MatchRoot("vpc_id"),
}...),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
}
return s return s
} }
@@ -401,6 +432,24 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
} }
if plan.SeparatePrivateNetworkInterface.IsUnknown() {
if plan.VpcId.IsNull() {
plan.SeparatePrivateNetworkInterface = types.BoolNull()
} else {
plan.SeparatePrivateNetworkInterface = types.BoolPointerValue(Pointer(false))
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
if plan.VpcIpv4Address.IsUnknown() {
if plan.VpcId.IsNull() {
plan.VpcIpv4Address = types.StringNull()
} else {
plan.VpcIpv4Address = types.StringUnknown()
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
if req.State.Raw.IsNull() { if req.State.Raw.IsNull() {
// Creation plan, no further modification needed // Creation plan, no further modification needed
return return
@@ -412,7 +461,10 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
return return
} }
if !plan.VpcId.Equal(state.VpcId) { if !plan.VpcId.Equal(state.VpcId) || !plan.VpcIpv4Address.Equal(state.VpcIpv4Address) {
if config.VpcIpv4Address.IsNull() {
plan.VpcIpv4Address = types.StringUnknown()
}
plan.PrivateIPv4Addresses = types.ListUnknown(plan.PrivateIPv4Addresses.ElementType(ctx)) plan.PrivateIPv4Addresses = types.ListUnknown(plan.PrivateIPv4Addresses.ElementType(ctx))
} }
@@ -481,7 +533,7 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
debugAdvFeatResp, _ := json.Marshal(advFeatResp.JSON200) debugAdvFeatResp, _ := json.Marshal(advFeatResp.JSON200)
tflog.Debug(ctx, fmt.Sprintf("Recieved response for available advanced features: %s", debugAdvFeatResp)) tflog.Debug(ctx, fmt.Sprintf("Recieved response for available advanced features: %s", debugAdvFeatResp))
availableAdvFeat := *advFeatResp.JSON200.AvailableAdvancedServerFeatures.AdvancedFeatures availableAdvFeat := advFeatResp.JSON200.AvailableAdvancedServerFeatures.AdvancedFeatures
if plan.AdvancedFeatures.EmulatedHyperv.ValueBool() && !slices.Contains(availableAdvFeat, "emulated-hyperv") { if plan.AdvancedFeatures.EmulatedHyperv.ValueBool() && !slices.Contains(availableAdvFeat, "emulated-hyperv") {
resp.Diagnostics.AddAttributeError( resp.Diagnostics.AddAttributeError(
@@ -570,8 +622,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
Options: &binarylane.SizeOptionsRequest{ Options: &binarylane.SizeOptionsRequest{
Ipv4Addresses: data.PublicIpv4Count.ValueInt32Pointer(), Ipv4Addresses: data.PublicIpv4Count.ValueInt32Pointer(),
}, },
Backups: data.Backups.ValueBoolPointer(), Backups: data.Backups.ValueBoolPointer(),
Ipv6: data.Ipv6.ValueBoolPointer(), Ipv6: data.Ipv6.ValueBoolPointer(),
SeparatePrivateNetworkInterface: data.SeparatePrivateNetworkInterface.ValueBoolPointer(),
} }
if !data.Memory.IsNull() && !data.Memory.IsUnknown() { if !data.Memory.IsNull() && !data.Memory.IsUnknown() {
@@ -580,7 +633,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
if !data.Disk.IsNull() && !data.Disk.IsUnknown() { if !data.Disk.IsNull() && !data.Disk.IsUnknown() {
body.Options.Disk = data.Disk.ValueInt32Pointer() body.Options.Disk = data.Disk.ValueInt32Pointer()
} }
if !data.VpcIpv4Address.IsNull() && !data.VpcIpv4Address.IsUnknown() {
body.VpcIpv4Address = data.VpcIpv4Address.ValueStringPointer()
}
if data.Password.IsNull() { if data.Password.IsNull() {
data.Password = types.StringNull() data.Password = types.StringNull()
} else { } else {
@@ -606,9 +661,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// Wait for server to be ready // Wait for server to be ready
var createActionId int64 var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions { for _, action := range serverResp.JSON200.Links.Actions {
if *action.Rel == "create" { if action.Rel == "create" {
createActionId = *action.Id createActionId = action.Id
break break
} }
} }
@@ -618,29 +673,42 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body)) fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
return return
} }
err = r.waitForServerAction(ctx, *serverResp.JSON200.Server.Id, createActionId) err = r.waitForServerAction(ctx, serverResp.JSON200.Server.Id, createActionId)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be created", err.Error()) resp.Diagnostics.AddError("Error waiting for server to be created", err.Error())
} }
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id) data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name) data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug) data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug) data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug) data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0) data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking) data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId) data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink) data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported) data.PasswordChangeSupported = types.BoolValue(serverResp.JSON200.Server.PasswordChangeSupported)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory) data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk) data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
plannedSourceDestCheck := data.SourceAndDestinationCheck plannedSourceDestCheck := data.SourceAndDestinationCheck
serverRespSourceDestCheck := types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck) serverRespSourceDestCheck := types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
data.SourceAndDestinationCheck = serverRespSourceDestCheck data.SourceAndDestinationCheck = serverRespSourceDestCheck
data.SeparatePrivateNetworkInterface = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SeparatePrivateNetworkInterface)
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures if serverResp.JSON200.Server.VpcId == nil {
data.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
data.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue( data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx), resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{ map[string]attr.Value{
@@ -830,7 +898,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
fmt.Sprintf("Received %s changing network for server: server_id=%s. Details: %s", networkResp.Status(), state.Id.String(), networkResp.Body)) fmt.Sprintf("Received %s changing network for server: server_id=%s. Details: %s", networkResp.Status(), state.Id.String(), networkResp.Body))
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *networkResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), networkResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for server to change network", err.Error()) resp.Diagnostics.AddError("Error waiting for server to change network", err.Error())
return return
@@ -843,6 +911,55 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
} }
} }
// Change VPC IPv4 address
if !plan.VpcIpv4Address.Equal(state.VpcIpv4Address) && !plan.VpcIpv4Address.IsNull() && !plan.VpcIpv4Address.IsUnknown() {
// If VPC was just changed, IP address needs to be fetched so it can be sent in the request
if refreshNeeded {
diag := r.fetchServerResourceState(ctx, &state)
resp.Diagnostics.Append(diag...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}
// We might have got lucky - the new randomly assigned IP address might be the one we want, so check before making API call
if state.VpcIpv4Address.ValueString() != plan.VpcIpv4Address.ValueString() {
vpcIpv4Resp, err := r.bc.client.PostServersServerIdActionsChangeVpcIpv4WithResponse(
ctx,
state.Id.ValueInt64(),
binarylane.PostServersServerIdActionsChangeVpcIpv4JSONRequestBody{
Type: "change_vpc_ipv4",
CurrentIpv4Address: state.VpcIpv4Address.ValueString(),
NewIpv4Address: plan.VpcIpv4Address.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error changing VPC IPv4 address for server: server_id=%s", state.Id.String()),
err.Error())
return
}
if vpcIpv4Resp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code changing VPC IPv4 address for server",
fmt.Sprintf("Received %s changing VPC IPv4 address for server: server_id=%s. Details: %s", vpcIpv4Resp.Status(), state.Id.String(), vpcIpv4Resp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), vpcIpv4Resp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for VPC IPv4 address to change", err.Error())
return
}
state.VpcIpv4Address = plan.VpcIpv4Address
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
refreshNeeded = true // Refresh to populate private_ipv4_addresses
if resp.Diagnostics.HasError() {
return
}
}
}
// Resize operation // Resize operation
if !plan.Size.Equal(state.Size) || if !plan.Size.Equal(state.Size) ||
!plan.Memory.IsNull() && !plan.Memory.IsUnknown() && !plan.Memory.Equal(state.Memory) || !plan.Memory.IsNull() && !plan.Memory.IsUnknown() && !plan.Memory.Equal(state.Memory) ||
@@ -919,7 +1036,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *resizeResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), resizeResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error()) resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error())
return return
@@ -936,7 +1053,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
} }
} }
// Change ipv6 // Change IPv6
if !plan.Ipv6.Equal(state.Ipv6) { if !plan.Ipv6.Equal(state.Ipv6) {
ipv6Resp, err := r.bc.client.PostServersServerIdActionsChangeIpv6WithResponse( ipv6Resp, err := r.bc.client.PostServersServerIdActionsChangeIpv6WithResponse(
ctx, ctx,
@@ -951,7 +1068,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *ipv6Resp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), ipv6Resp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for changing IPv6", err.Error()) resp.Diagnostics.AddError("Error waiting for changing IPv6", err.Error())
return return
@@ -1010,7 +1127,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
fmt.Sprintf("Received %s rebuilding server: server_id=%s. Details: %s", rebuildResp.Status(), state.Id.String(), rebuildResp.Body)) fmt.Sprintf("Received %s rebuilding server: server_id=%s. Details: %s", rebuildResp.Status(), state.Id.String(), rebuildResp.Body))
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *rebuildResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), rebuildResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error()) resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error())
return return
@@ -1038,7 +1155,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error resetting password", err.Error()) resp.Diagnostics.AddError("Error resetting password", err.Error())
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *passwordResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), passwordResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for password reset", err.Error()) resp.Diagnostics.AddError("Error waiting for password reset", err.Error())
return return
@@ -1082,6 +1199,25 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
} }
} }
// Check separate_private_network_interface
if !plan.SeparatePrivateNetworkInterface.Equal(state.SeparatePrivateNetworkInterface) {
if !plan.SeparatePrivateNetworkInterface.IsNull() {
err := r.updateSeparatePrivateNetworkInterface(ctx, state.Id.ValueInt64(), plan.SeparatePrivateNetworkInterface.ValueBool())
if err != nil {
resp.Diagnostics.AddError("Error updating separate private network interface", err.Error())
return
}
}
state.SeparatePrivateNetworkInterface = plan.SeparatePrivateNetworkInterface
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}
// Enable or disable backups // Enable or disable backups
if !plan.Backups.Equal(state.Backups) { if !plan.Backups.Equal(state.Backups) {
if plan.Backups.ValueBool() { if plan.Backups.ValueBool() {
@@ -1092,7 +1228,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error enabling backups", err.Error()) resp.Diagnostics.AddError("Error enabling backups", err.Error())
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *backupResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), backupResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for backups to be enabled", err.Error()) resp.Diagnostics.AddError("Error waiting for backups to be enabled", err.Error())
return return
@@ -1105,7 +1241,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error disabling backups", err.Error()) resp.Diagnostics.AddError("Error disabling backups", err.Error())
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *backupResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), backupResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for backups to be disabled", err.Error()) resp.Diagnostics.AddError("Error waiting for backups to be disabled", err.Error())
return return
@@ -1132,7 +1268,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error changing \"port_blocking\" attribute", err.Error()) resp.Diagnostics.AddError("Error changing \"port_blocking\" attribute", err.Error())
return return
} }
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *portBlockingResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, state.Id.ValueInt64(), portBlockingResp.JSON200.Action.Id)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Error waiting for \"port_blocking\" attribute to change", err.Error()) resp.Diagnostics.AddError("Error waiting for \"port_blocking\" attribute to change", err.Error())
return return
@@ -1210,8 +1346,8 @@ func (r *serverResource) ImportState(
return return
} }
servers := *serverResp.JSON200.Servers servers := serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return *s.Name == name }) idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return s.Name == name })
if idx == -1 { if idx == -1 {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Could not find server by hostname", "Could not find server by hostname",
@@ -1221,32 +1357,40 @@ func (r *serverResource) ImportState(
} }
server := servers[idx] server := servers[idx]
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id)) diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(server.Id))
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
} }
var (
serverActionPollInterval = 5 * time.Second
serverActionTimeout = 20 * time.Minute
)
func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64, actionId int64) error { func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64, actionId int64) error {
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, serverActionTimeout)
defer cancel()
}
var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse
for { for {
select { readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, serverId, actionId)
case <-ctx.Done(): if err != nil && ctx.Err() == nil {
if lastReadyResp == nil { return fmt.Errorf("unexpected error waiting for server action: server_id=%d, action_id=%d, error: %w", serverId, actionId, err)
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d", serverId, actionId) }
} else { if err == nil {
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d, last response was status=%s, body: %s", if readyResp.StatusCode() == http.StatusNotFound {
serverId, actionId, lastReadyResp.Status(), lastReadyResp.Body) return fmt.Errorf("server action not found: server_id=%d, action_id=%d, body: %s", serverId, actionId, readyResp.Body)
} }
default: if readyResp.StatusCode() == http.StatusOK {
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, serverId, actionId) switch {
if err != nil { case readyResp.JSON200.Action.Status == binarylane.Errored:
return fmt.Errorf("unexpected error waiting for server action: server_id=%d, action_id=%d, error: %w", serverId, actionId, err) return fmt.Errorf("server action failed to with error: server_id=%d, action_id=%d, error: %s", serverId, actionId, readyResp.Body)
} case readyResp.JSON200.Action.Status == binarylane.Completed, readyResp.JSON200.Action.CompletedAt != nil:
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored { return nil
return fmt.Errorf("server action failed to with error: server_id=%d, action_id=%d, error: %s", serverId, actionId, readyResp.Body) }
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
return nil
} }
lastReadyResp = readyResp lastReadyResp = readyResp
tflog.Debug(ctx, tflog.Debug(ctx,
@@ -1255,7 +1399,16 @@ func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64
), ),
) )
} }
time.Sleep(time.Second * 5)
select {
case <-ctx.Done():
if lastReadyResp == nil {
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d", serverId, actionId)
}
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d, last response was status=%s, body: %s",
serverId, actionId, lastReadyResp.Status(), lastReadyResp.Body)
case <-time.After(serverActionPollInterval):
}
} }
} }
@@ -1298,7 +1451,7 @@ func (r *serverResource) updateSourceDestCheck(
return fmt.Errorf("unexpected HTTP status code changing source and destination check for server: server_id=%d, details: %s", serverId, sourceDestCheckResp.Body) return fmt.Errorf("unexpected HTTP status code changing source and destination check for server: server_id=%d, details: %s", serverId, sourceDestCheckResp.Body)
} }
err = r.waitForServerAction(ctx, serverId, *sourceDestCheckResp.JSON200.Action.Id) err = r.waitForServerAction(ctx, serverId, sourceDestCheckResp.JSON200.Action.Id)
if err != nil { if err != nil {
return fmt.Errorf("error changing source and destination check: %w", err) return fmt.Errorf("error changing source and destination check: %w", err)
} }
@@ -1306,6 +1459,37 @@ func (r *serverResource) updateSourceDestCheck(
return nil return nil
} }
func (r *serverResource) updateSeparatePrivateNetworkInterface(
ctx context.Context,
serverId int64,
enabled bool,
) error {
tflog.Info(ctx, fmt.Sprintf("Changing separate private network interface for server: server_id=%d, enabled=%t",
serverId, enabled))
separatePrivateNicResp, err := r.bc.client.PostServersServerIdActionsChangeSeparatePrivateNetworkInterfaceWithResponse(
ctx,
serverId,
binarylane.PostServersServerIdActionsChangeSeparatePrivateNetworkInterfaceJSONRequestBody{
Type: "change_separate_private_network_interface",
Enabled: enabled,
},
)
if err != nil {
return fmt.Errorf("error changing separate private network interface for server: server_id=%d, error: %w", serverId, err)
}
if separatePrivateNicResp.StatusCode() != http.StatusOK {
return fmt.Errorf("unexpected HTTP status code changing separate private network interface for server: server_id=%d, details: %s", serverId, separatePrivateNicResp.Body)
}
err = r.waitForServerAction(ctx, serverId, separatePrivateNicResp.JSON200.Action.Id)
if err != nil {
return fmt.Errorf("error changing separate private network interface: %w", err)
}
return nil
}
func (r *serverResource) fetchServerResourceState(ctx context.Context, state *serverResourceModel) diag.Diagnostics { func (r *serverResource) fetchServerResourceState(ctx context.Context, state *serverResourceModel) diag.Diagnostics {
var diags diag.Diagnostics var diags diag.Diagnostics
@@ -1327,23 +1511,36 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
} }
} }
state.Id = types.Int64Value(*serverResp.JSON200.Server.Id) state.Id = types.Int64Value(serverResp.JSON200.Server.Id)
state.Name = types.StringValue(*serverResp.JSON200.Server.Name) state.Name = types.StringValue(serverResp.JSON200.Server.Name)
state.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug) state.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
state.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug) state.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
state.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug) state.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
state.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking) state.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
state.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId) state.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
state.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink) state.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
state.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported) state.PasswordChangeSupported = types.BoolValue(serverResp.JSON200.Server.PasswordChangeSupported)
state.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck) state.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
state.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory) state.SeparatePrivateNetworkInterface = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SeparatePrivateNetworkInterface)
state.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk) state.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
state.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
state.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0) state.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures if serverResp.JSON200.Server.VpcId == nil {
state.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
state.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
state.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue( state.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx), resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{ map[string]attr.Value{
@@ -1366,9 +1563,10 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
privateIpv4Addresses := []string{} privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 { for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" { switch v4address.Type {
case "public":
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress) publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else if v4address.Type == "private" { case "private":
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress) privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
} }
} }
@@ -1395,9 +1593,10 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
privateIpv6Addresses := []string{} privateIpv6Addresses := []string{}
for _, v6address := range serverResp.JSON200.Server.Networks.V6 { for _, v6address := range serverResp.JSON200.Server.Networks.V6 {
if v6address.Type == "public" { switch v6address.Type {
case "public":
publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress) publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress)
} else { case "private":
privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress) privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress)
} }
} }
@@ -1506,7 +1705,7 @@ func (r *serverResource) updateAdvancedFeatures(
return fmt.Errorf("unexpected HTTP status code updating advanced features for server: server_id=%d, details: %s", serverId, resp.Body) return fmt.Errorf("unexpected HTTP status code updating advanced features for server: server_id=%d, details: %s", serverId, resp.Body)
} }
err = r.waitForServerAction(ctx, serverId, *resp.JSON200.Action.Id) err = r.waitForServerAction(ctx, serverId, resp.JSON200.Action.Id)
if err != nil { if err != nil {
return fmt.Errorf("failed to confirm advanced features for server was successful: %w", err) return fmt.Errorf("failed to confirm advanced features for server was successful: %w", err)
} }
+162 -8
View File
@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"regexp"
"strings" "strings"
"terraform-provider-binarylane/internal/binarylane" "terraform-provider-binarylane/internal/binarylane"
"testing" "testing"
@@ -57,6 +58,7 @@ resource "binarylane_server" "test" {
public_ipv4_count = 1 public_ipv4_count = 1
ssh_keys = [binarylane_ssh_key.initial.id] ssh_keys = [binarylane_ssh_key.initial.id]
source_and_destination_check = false source_and_destination_check = false
separate_private_network_interface = true
backups = true backups = true
port_blocking = false port_blocking = false
user_data = <<EOT user_data = <<EOT
@@ -81,6 +83,7 @@ data "binarylane_server" "test" {
resource.TestCheckResourceAttr("binarylane_server.test", "memory", "1152"), resource.TestCheckResourceAttr("binarylane_server.test", "memory", "1152"),
resource.TestCheckResourceAttr("binarylane_server.test", "disk", "20"), resource.TestCheckResourceAttr("binarylane_server.test", "disk", "20"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"), resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_ipv4_address"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"), resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "password", password1), resource.TestCheckResourceAttr("binarylane_server.test", "password", password1),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
@@ -93,6 +96,7 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.initial", "id"), resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.initial", "id"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "permalink"), resource.TestCheckResourceAttrSet("binarylane_server.test", "permalink"),
resource.TestCheckResourceAttr("binarylane_server.test", "source_and_destination_check", "false"), resource.TestCheckResourceAttr("binarylane_server.test", "source_and_destination_check", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "separate_private_network_interface", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "backups", "true"), resource.TestCheckResourceAttr("binarylane_server.test", "backups", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "false"), resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_devices", "false"), resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_devices", "false"),
@@ -114,6 +118,7 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-11"), resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-11"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"), resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_id"), resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_ipv4_address"),
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"), resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "user_data", `#cloud-config resource.TestCheckResourceAttr("data.binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt echo "Hello World" > /var/tmp/output.txt
@@ -122,6 +127,8 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "disk", "20"), resource.TestCheckResourceAttr("data.binarylane_server.test", "disk", "20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "backups", "true"), resource.TestCheckResourceAttr("data.binarylane_server.test", "backups", "true"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "port_blocking", "false"), resource.TestCheckResourceAttr("data.binarylane_server.test", "port_blocking", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "source_and_destination_check", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "separate_private_network_interface", "true"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_hyperv", "false"), resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_hyperv", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_devices", "false"), resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_devices", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.nested_virt", "false"), resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.nested_virt", "false"),
@@ -211,6 +218,7 @@ EOT
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"), resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "true"), resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "true"),
resource.TestCheckNoResourceAttr("binarylane_server.test", "source_and_destination_check"), resource.TestCheckNoResourceAttr("binarylane_server.test", "source_and_destination_check"),
resource.TestCheckNoResourceAttr("binarylane_server.test", "separate_private_network_interface"),
resource.TestCheckResourceAttr("binarylane_server.test", "backups", "false"), resource.TestCheckResourceAttr("binarylane_server.test", "backups", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"), resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
@@ -321,6 +329,153 @@ resource "binarylane_server" "test" {
}) })
} }
func TestServerVpcIpv4Change(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
password := GenerateTestPassword(t)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Plan should fail if vpc_ipv4_address is set without vpc_id
{
Config: providerConfig + `
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_ipv4_address = "10.241.69.69"
}
`,
PlanOnly: true,
ExpectError: regexp.MustCompile("Attribute \"vpc_id\" must be specified when \"vpc_ipv4_address\" is specified"),
},
// Setup
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test1.id
vpc_ipv4_address = "10.240.0.69"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
),
},
// Change IP without changing VPC
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test1.id
vpc_ipv4_address = "10.240.4.20"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
),
},
// Change VPC (should also change IP)
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test2.id
vpc_ipv4_address = "10.241.69.69"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test2", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
),
},
},
})
}
func GenerateTestPassword(t *testing.T) string { func GenerateTestPassword(t *testing.T) string {
t.Helper() t.Helper()
pwBytes := make([]byte, 12) pwBytes := make([]byte, 12)
@@ -336,7 +491,6 @@ func init() {
Name: "server", Name: "server",
F: func(_ string) error { F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig() client, err := binarylane.NewClientWithDefaultConfig()
if err != nil { if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err) return fmt.Errorf("Error creating Binary Lane API client: %w", err)
} }
@@ -362,25 +516,25 @@ func init() {
return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", listResp.Body) return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", listResp.Body)
} }
servers := *listResp.JSON200.Servers servers := listResp.JSON200.Servers
for _, s := range servers { for _, s := range servers {
if strings.HasPrefix(*s.Name, "tf-test-") { if strings.HasPrefix(s.Name, "tf-test-") {
reason := "Terraform deletion" reason := "Terraform deletion"
params := binarylane.DeleteServersServerIdParams{ params := binarylane.DeleteServersServerIdParams{
Reason: &reason, Reason: &reason,
} }
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, &params) deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, s.Id, &params)
if err != nil { if err != nil {
return fmt.Errorf("Error deleting server %d during test sweep: %w", *s.Id, err) return fmt.Errorf("Error deleting server %d during test sweep: %w", s.Id, err)
} }
if deleteResp.StatusCode() != http.StatusNoContent { if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", deleteResp.StatusCode(), *s.Id, deleteResp.Body) return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", deleteResp.StatusCode(), s.Id, deleteResp.Body)
} }
log.Println("Deleted server during test sweep:", *s.Id) log.Println("Deleted server during test sweep:", s.Id)
} }
} }
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
@@ -0,0 +1,320 @@
package provider
import (
"context"
"fmt"
"net/http"
"net/netip"
"strconv"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"time"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &serverReverseNameResource{}
_ resource.ResourceWithConfigure = &serverReverseNameResource{}
_ resource.ResourceWithImportState = &serverReverseNameResource{}
)
func NewServerReverseNameResource() resource.Resource {
return &serverReverseNameResource{}
}
type serverReverseNameResource struct {
bc *BinarylaneClient
}
type serverReverseNameResourceModel struct {
Id types.String `tfsdk:"id"`
ServerId types.Int64 `tfsdk:"server_id"`
Ipv4Address types.String `tfsdk:"ipv4_address"`
ReverseName types.String `tfsdk:"reverse_name"`
}
func (r *serverReverseNameResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
r.bc = &bc
}
func (r *serverReverseNameResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server_reverse_name"
}
func (r *serverReverseNameResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manage the reverse name (PTR record) for a public IPv4 address of a BinaryLane server. " +
"Destroying this resource clears the custom reverse name, restoring the BinaryLane default.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "The ID of this resource, in the format `<server_id>:<ipv4_address>`.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"server_id": schema.Int64Attribute{
Description: "The ID of the server the IPv4 address is assigned to.",
Required: true,
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"ipv4_address": schema.StringAttribute{
Description: "The public IPv4 address to set the reverse name for.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"reverse_name": schema.StringAttribute{
Description: "The reverse name (PTR record) for the IPv4 address.",
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
},
}
}
func (r *serverReverseNameResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data serverReverseNameResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
tflog.Debug(ctx, fmt.Sprintf("Creating server reverse name: server_id=%d, ipv4_address=%s",
data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
err := r.changeReverseName(ctx, data.ServerId.ValueInt64(), data.Ipv4Address.ValueString(), data.ReverseName.ValueStringPointer())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating server reverse name: server_id=%d, ipv4_address=%s", data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()),
err.Error(),
)
return
}
data.Id = types.StringValue(formatServerReverseNameId(data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverReverseNameResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data serverReverseNameResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server reverse name: server_id=%d, ipv4_address=%s",
data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, data.ServerId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server reverse name: server_id=%d", data.ServerId.ValueInt64()),
err.Error(),
)
return
}
if serverResp.StatusCode() == http.StatusNotFound {
tflog.Warn(ctx, fmt.Sprintf("Server not found, removing reverse name from state: server_id=%d", data.ServerId.ValueInt64()))
resp.State.RemoveResource(ctx)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server reverse name",
fmt.Sprintf("Received %s reading server reverse name: server_id=%d. Details: %s", serverResp.Status(), data.ServerId.ValueInt64(), serverResp.Body))
return
}
var network *binarylane.Network
for i, n := range serverResp.JSON200.Server.Networks.V4 {
if n.IpAddress == data.Ipv4Address.ValueString() {
network = &serverResp.JSON200.Server.Networks.V4[i]
break
}
}
if network == nil {
tflog.Warn(ctx, fmt.Sprintf("IPv4 address not found on server, removing reverse name from state: server_id=%d, ipv4_address=%s",
data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
resp.State.RemoveResource(ctx)
return
}
data.Id = types.StringValue(formatServerReverseNameId(data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
data.ReverseName = types.StringPointerValue(network.ReverseName)
// Save into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverReverseNameResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data serverReverseNameResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
tflog.Debug(ctx, fmt.Sprintf("Updating server reverse name: server_id=%d, ipv4_address=%s",
data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
err := r.changeReverseName(ctx, data.ServerId.ValueInt64(), data.Ipv4Address.ValueString(), data.ReverseName.ValueStringPointer())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating server reverse name: server_id=%d, ipv4_address=%s", data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()),
err.Error(),
)
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverReverseNameResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data serverReverseNameResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic: a null reverse name clears the custom reverse name
tflog.Debug(ctx, fmt.Sprintf("Deleting server reverse name: server_id=%d, ipv4_address=%s",
data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()))
err := r.changeReverseName(ctx, data.ServerId.ValueInt64(), data.Ipv4Address.ValueString(), nil)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting server reverse name: server_id=%d, ipv4_address=%s", data.ServerId.ValueInt64(), data.Ipv4Address.ValueString()),
err.Error(),
)
return
}
}
func (r *serverReverseNameResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
serverId, ipv4Address, err := parseServerReverseNameId(req.ID)
if err != nil {
resp.Diagnostics.AddError(
"Error importing server reverse name",
"Could not import server reverse name, unexpected error (ID should be <server_id>:<ipv4_address>): "+err.Error(),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("server_id"), serverId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("ipv4_address"), ipv4Address)...)
}
func (r *serverReverseNameResource) changeReverseName(ctx context.Context, serverId int64, ipv4Address string, reverseName *string) error {
actionResp, err := r.bc.client.PostServersServerIdActionsChangeReverseNameWithResponse(
ctx,
serverId,
binarylane.PostServersServerIdActionsChangeReverseNameJSONRequestBody{
Type: "change_reverse_name",
Ipv4Address: ipv4Address,
ReverseName: reverseName,
},
)
if err != nil {
return fmt.Errorf("error changing reverse name for server: server_id=%d, error: %w", serverId, err)
}
if actionResp.StatusCode() != http.StatusOK && actionResp.StatusCode() != http.StatusAccepted {
return fmt.Errorf("unexpected HTTP status code changing reverse name for server: server_id=%d, details: %s", serverId, actionResp.Body)
}
// The change_reverse_name action can stay unresolvable, so wait for the result on the server instead
if reverseName == nil {
return nil
}
return r.waitForReverseName(ctx, serverId, ipv4Address, *reverseName)
}
func (r *serverReverseNameResource) waitForReverseName(ctx context.Context, serverId int64, ipv4Address string, want string) error {
ctx, cancel := context.WithTimeout(ctx, serverActionTimeout)
defer cancel()
var last string
for {
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, serverId)
if err == nil && serverResp.StatusCode() == http.StatusOK {
for _, n := range serverResp.JSON200.Server.Networks.V4 {
if n.IpAddress != ipv4Address {
continue
}
if n.ReverseName != nil {
if *n.ReverseName == want {
return nil
}
last = *n.ReverseName
}
}
}
select {
case <-ctx.Done():
return fmt.Errorf("timed out waiting for reverse name: server_id=%d, ipv4_address=%s, want=%q, last=%q", serverId, ipv4Address, want, last)
case <-time.After(serverActionPollInterval):
}
}
}
func formatServerReverseNameId(serverId int64, ipv4Address string) string {
return fmt.Sprintf("%d:%s", serverId, ipv4Address)
}
func parseServerReverseNameId(id string) (int64, string, error) {
serverIdStr, ipv4Address, ok := strings.Cut(id, ":")
if !ok {
return 0, "", fmt.Errorf("missing ':' separator in %q", id)
}
serverId, err := strconv.ParseInt(serverIdStr, 10, 64)
if err != nil || serverId < 1 {
return 0, "", fmt.Errorf("invalid server_id %q", serverIdStr)
}
addr, err := netip.ParseAddr(ipv4Address)
if err != nil || !addr.Is4() {
return 0, "", fmt.Errorf("invalid ipv4_address %q", ipv4Address)
}
return serverId, ipv4Address, nil
}
@@ -0,0 +1,148 @@
package provider
import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"terraform-provider-binarylane/internal/binarylane"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestParseServerReverseNameId(t *testing.T) {
serverId, ip, err := parseServerReverseNameId(formatServerReverseNameId(12345, "203.0.113.7"))
if err != nil || serverId != 12345 || ip != "203.0.113.7" {
t.Fatalf("round trip: got (%d, %q, %v)", serverId, ip, err)
}
for _, id := range []string{
"",
"12345",
"12345:",
":203.0.113.7",
"abc:203.0.113.7",
"0:203.0.113.7",
"-1:203.0.113.7",
"12345:not-an-ip",
"12345:2001:db8::1",
"12345:203.0.113.7:80",
} {
if _, _, err := parseServerReverseNameId(id); err == nil {
t.Errorf("expected error parsing %q", id)
}
}
}
func TestServerReverseNameResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
password := GenerateTestPassword(t)
serverConfig := `
resource "binarylane_server" "test" {
name = "tf-test-server-reverse-name"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
public_ipv4_count = 1
}
`
var serverId int64
var ipv4Address string
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: func(_ *terraform.State) error {
return testAccCheckServerReverseNameCleared(serverId, ipv4Address)
},
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + serverConfig + `
resource "binarylane_server_reverse_name" "test" {
server_id = binarylane_server.test.id
ipv4_address = binarylane_server.test.public_ipv4_addresses.0
reverse_name = "tf-test-ptr.example.com"
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server_reverse_name.test", "server_id", "binarylane_server.test", "id"),
resource.TestCheckResourceAttrPair("binarylane_server_reverse_name.test", "ipv4_address", "binarylane_server.test", "public_ipv4_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server_reverse_name.test", "reverse_name", "tf-test-ptr.example.com"),
resource.TestCheckResourceAttrSet("binarylane_server_reverse_name.test", "id"),
func(s *terraform.State) error {
attrs := s.RootModule().Resources["binarylane_server_reverse_name.test"].Primary.Attributes
var err error
serverId, err = strconv.ParseInt(attrs["server_id"], 10, 64)
ipv4Address = attrs["ipv4_address"]
return err
},
),
},
// ImportState testing
{
ResourceName: "binarylane_server_reverse_name.test",
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: func(s *terraform.State) (string, error) {
resourceState := s.RootModule().Resources["binarylane_server_reverse_name.test"]
return resourceState.Primary.Attributes["id"], nil
},
},
// Update and Read testing
{
Config: providerConfig + serverConfig + `
resource "binarylane_server_reverse_name" "test" {
server_id = binarylane_server.test.id
ipv4_address = binarylane_server.test.public_ipv4_addresses.0
reverse_name = "tf-test-ptr-updated.example.com" # Updated reverse name
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("binarylane_server_reverse_name.test", "reverse_name", "tf-test-ptr-updated.example.com"),
),
},
// Removing the resource clears the reverse name while the server still exists
{
Config: providerConfig + serverConfig,
Check: func(_ *terraform.State) error {
return testAccCheckServerReverseNameCleared(serverId, ipv4Address)
},
},
// Delete testing automatically occurs in TestCase
},
})
}
func testAccCheckServerReverseNameCleared(serverId int64, ipv4Address string) error {
client, err := binarylane.NewClientWithConfig("", "")
if err != nil {
return err
}
resp, err := client.GetServersServerIdWithResponse(context.Background(), serverId)
if err != nil {
return err
}
if resp.StatusCode() == http.StatusNotFound {
return nil
}
if resp.StatusCode() != http.StatusOK {
return fmt.Errorf("unexpected status %s reading server %d: %s", resp.Status(), serverId, resp.Body)
}
for _, n := range resp.JSON200.Server.Networks.V4 {
if n.IpAddress != ipv4Address || n.ReverseName == nil {
continue
}
switch *n.ReverseName {
case "tf-test-ptr.example.com", "tf-test-ptr-updated.example.com":
return fmt.Errorf("reverse name for %s on server %d not cleared: %s", ipv4Address, serverId, *n.ReverseName)
}
}
return nil
}
@@ -0,0 +1,222 @@
package provider
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"terraform-provider-binarylane/internal/binarylane"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestServerReverseNameResourceUnit(t *testing.T) {
ctx := context.Background()
var (
postStatus = http.StatusOK
serverFound = true
serverIp = "203.0.113.7"
serverName = "ptr.example.com"
lastBody map[string]any
)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.Method == http.MethodPost && r.URL.Path == "/servers/1/actions":
b, _ := io.ReadAll(r.Body)
lastBody = nil
if err := json.Unmarshal(b, &lastBody); err != nil {
t.Errorf("decode POST body: %v", err)
}
w.WriteHeader(postStatus)
if postStatus == http.StatusOK {
_, _ = w.Write([]byte(`{"action":{"id":9,"status":"in-progress"}}`))
}
case r.Method == http.MethodGet && r.URL.Path == "/servers/1":
if !serverFound {
w.WriteHeader(http.StatusNotFound)
return
}
_, _ = fmt.Fprintf(w, `{"server":{"id":1,"networks":{"v4":[{"ip_address":%q,"type":"public","reverse_name":%q}]}}}`, serverIp, serverName)
default:
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusTeapot)
}
}))
defer srv.Close()
client, err := binarylane.NewClientWithConfig(srv.URL, "test")
if err != nil {
t.Fatal(err)
}
r := &serverReverseNameResource{bc: &BinarylaneClient{client: client}}
schemaResp := resource.SchemaResponse{}
r.Schema(ctx, resource.SchemaRequest{}, &schemaResp)
s := schemaResp.Schema
emptyState := func() tfsdk.State {
return tfsdk.State{Schema: s, Raw: tftypes.NewValue(s.Type().TerraformType(ctx), nil)}
}
model := serverReverseNameResourceModel{
Id: types.StringUnknown(),
ServerId: types.Int64Value(1),
Ipv4Address: types.StringValue("203.0.113.7"),
ReverseName: types.StringValue("ptr.example.com"),
}
plan := tfsdk.Plan{Schema: s, Raw: tftypes.NewValue(s.Type().TerraformType(ctx), nil)}
if diags := plan.Set(ctx, &model); diags.HasError() {
t.Fatal(diags)
}
createResp := resource.CreateResponse{State: emptyState()}
r.Create(ctx, resource.CreateRequest{Plan: plan}, &createResp)
if createResp.Diagnostics.HasError() {
t.Fatal(createResp.Diagnostics)
}
if lastBody["reverse_name"] != "ptr.example.com" || lastBody["ipv4_address"] != "203.0.113.7" {
t.Fatalf("create: unexpected request body %v", lastBody)
}
state := createResp.State
read := func() (serverReverseNameResourceModel, bool) {
t.Helper()
readResp := resource.ReadResponse{State: state}
r.Read(ctx, resource.ReadRequest{State: state}, &readResp)
if readResp.Diagnostics.HasError() {
t.Fatal(readResp.Diagnostics)
}
if readResp.State.Raw.IsNull() {
return serverReverseNameResourceModel{}, false
}
var got serverReverseNameResourceModel
if diags := readResp.State.Get(ctx, &got); diags.HasError() {
t.Fatal(diags)
}
return got, true
}
serverName = "drifted.example.com"
if got, ok := read(); !ok || got.ReverseName.ValueString() != "drifted.example.com" {
t.Fatalf("read: expected drift to drifted.example.com, got %v (present=%v)", got.ReverseName, ok)
}
serverIp = "198.51.100.1"
if _, ok := read(); ok {
t.Fatal("read: expected state removed when IP is not on the server")
}
serverFound = false
if _, ok := read(); ok {
t.Fatal("read: expected state removed when server is 404")
}
serverFound, serverIp, serverName = true, "203.0.113.7", "ptr.example.com"
postStatus = http.StatusAccepted
updateResp := resource.UpdateResponse{State: emptyState()}
r.Update(ctx, resource.UpdateRequest{Plan: plan, State: state}, &updateResp)
if updateResp.Diagnostics.HasError() {
t.Fatalf("update: 202 without action should succeed: %v", updateResp.Diagnostics)
}
deleteResp := resource.DeleteResponse{State: state}
r.Delete(ctx, resource.DeleteRequest{State: state}, &deleteResp)
if deleteResp.Diagnostics.HasError() {
t.Fatalf("delete: 202 without action should succeed: %v", deleteResp.Diagnostics)
}
if _, ok := lastBody["reverse_name"]; ok {
t.Fatalf("delete: request body must not contain reverse_name, got %v", lastBody)
}
if lastBody["ipv4_address"] != "203.0.113.7" {
t.Fatalf("delete: unexpected request body %v", lastBody)
}
}
// The change_reverse_name action never resolves while the PTR is already applied on the server.
func TestServerReverseNameCreateStuckActionUnit(t *testing.T) {
serverActionPollInterval = time.Millisecond
t.Cleanup(func() { serverActionPollInterval = 5 * time.Second })
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
serverGets := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.Method == http.MethodPost && r.URL.Path == "/servers/1/actions":
_, _ = w.Write([]byte(`{"action":{"id":9,"status":"in-progress"}}`))
case r.Method == http.MethodGet && r.URL.Path == "/servers/1/actions/9":
w.WriteHeader(http.StatusNotFound)
case r.Method == http.MethodGet && r.URL.Path == "/servers/1":
serverGets++
name := "default.example.net"
if serverGets > 2 {
name = "ptr.example.com"
}
_, _ = fmt.Fprintf(w, `{"server":{"id":1,"networks":{"v4":[{"ip_address":"203.0.113.7","type":"public","reverse_name":%q}]}}}`, name)
default:
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusTeapot)
}
}))
defer srv.Close()
client, err := binarylane.NewClientWithConfig(srv.URL, "test")
if err != nil {
t.Fatal(err)
}
r := &serverReverseNameResource{bc: &BinarylaneClient{client: client}}
want := "ptr.example.com"
if err := r.changeReverseName(ctx, 1, "203.0.113.7", &want); err != nil {
t.Fatalf("create must finish once the server reports the reverse name: %v", err)
}
if serverGets != 3 {
t.Fatalf("expected to poll the server until the reverse name applied, got %d GETs", serverGets)
}
}
func TestWaitForServerActionUnit(t *testing.T) {
serverActionPollInterval = time.Millisecond
t.Cleanup(func() { serverActionPollInterval = 5 * time.Second })
for name, tc := range map[string]struct {
status int
body string
wantErr bool
}{
"completed_at set": {http.StatusOK, `{"action":{"id":9,"status":"in-progress","completed_at":"2026-01-01T00:00:00Z"}}`, false},
"completed status only": {http.StatusOK, `{"action":{"id":9,"status":"completed"}}`, false},
"errored": {http.StatusOK, `{"action":{"id":9,"status":"errored"}}`, true},
"not found": {http.StatusNotFound, `{"message":"Action not found."}`, true},
"in progress until timeout": {http.StatusOK, `{"action":{"id":9,"status":"in-progress"}}`, true},
} {
t.Run(name, func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(tc.status)
_, _ = w.Write([]byte(tc.body))
}))
defer srv.Close()
client, err := binarylane.NewClientWithConfig(srv.URL, "test")
if err != nil {
t.Fatal(err)
}
sr := serverResource{bc: &BinarylaneClient{client: client}}
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
if err := sr.waitForServerAction(ctx, 1, 9); (err != nil) != tc.wantErr {
t.Fatalf("wantErr=%v, got %v", tc.wantErr, err)
}
})
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *sizesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
} }
regResults = append(regResults, listResp.JSON200.Sizes...) regResults = append(regResults, listResp.JSON200.Sizes...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
+4 -4
View File
@@ -118,13 +118,13 @@ func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return return
} }
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id) data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default) data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name) data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = TrimmedStringValue{ data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(*sshResp.JSON200.SshKey.PublicKey), StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
} }
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint) data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save data into Terraform state // Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+10 -10
View File
@@ -153,8 +153,8 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
} }
// Set data values // Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id) data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint) data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -191,13 +191,13 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
} }
// Set data values // Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id) data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default) data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name) data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = TrimmedStringValue{ data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(*sshResp.JSON200.SshKey.PublicKey), StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
} }
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint) data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save updated data into Terraform state // Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -312,13 +312,13 @@ func (r *sshKeyResource) ImportState(
sshKeys := sshResp.JSON200.SshKeys sshKeys := sshResp.JSON200.SshKeys
for _, key := range sshKeys { for _, key := range sshKeys {
if *key.Fingerprint == req.ID { if key.Fingerprint == req.ID {
sshKey = key sshKey = key
nextPage = false nextPage = false
break break
} }
} }
if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages == nil || sshResp.JSON200.Links.Pages.Next == nil { if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
@@ -326,7 +326,7 @@ func (r *sshKeyResource) ImportState(
page++ page++
} }
if sshKey.Id == nil { if sshKey.Id == 0 {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Could not find SSH key by fingerprint", "Could not find SSH key by fingerprint",
fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID), fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID),
@@ -334,6 +334,6 @@ func (r *sshKeyResource) ImportState(
return return
} }
diags := resp.State.SetAttribute(ctx, path.Root("id"), *sshKey.Id) diags := resp.State.SetAttribute(ctx, path.Root("id"), sshKey.Id)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
} }
+5 -5
View File
@@ -151,17 +151,17 @@ func init() {
for _, k := range keys { for _, k := range keys {
if strings.HasPrefix(*k.Name, "tf-test-") { if strings.HasPrefix(*k.Name, "tf-test-") {
deleteResp, err := client.DeleteAccountKeysKeyIdWithResponse(ctx, int(*k.Id)) deleteResp, err := client.DeleteAccountKeysKeyIdWithResponse(ctx, int(k.Id))
if err != nil { if err != nil {
return fmt.Errorf("Error deleting SSH key %d for test sweep: %w", *k.Id, err) return fmt.Errorf("Error deleting SSH key %d for test sweep: %w", k.Id, err)
} }
if deleteResp.StatusCode() != http.StatusNoContent { if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting SSH key %d for test sweep: %s", deleteResp.StatusCode(), *k.Id, deleteResp.Body) return fmt.Errorf("Unexpected status %d deleting SSH key %d for test sweep: %s", deleteResp.StatusCode(), k.Id, deleteResp.Body)
} }
log.Println("Deleted SSH key for test sweep:", *k.Id) log.Println("Deleted SSH key for test sweep:", k.Id)
} }
} }
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
+2 -2
View File
@@ -90,8 +90,8 @@ func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body)) fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return return
} }
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange) data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name) data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state // Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+7 -7
View File
@@ -120,8 +120,8 @@ func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, re
return return
} }
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id) data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name) data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save data into Terraform state // Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -157,9 +157,9 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body)) fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return return
} }
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id) data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange) data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name) data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state // Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -203,8 +203,8 @@ func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, re
return return
} }
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id) state.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name) state.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state // Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+7 -7
View File
@@ -156,20 +156,20 @@ func init() {
} }
vpcs := listResp.JSON200.Vpcs vpcs := listResp.JSON200.Vpcs
for _, vpc := range *vpcs { for _, vpc := range vpcs {
if strings.HasPrefix(*vpc.Name, "tf-test-") { if strings.HasPrefix(vpc.Name, "tf-test-") {
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, *vpc.Id) deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, vpc.Id)
if err != nil { if err != nil {
return fmt.Errorf("Error deleting VPC %d in test sweep: %w", *vpc.Id, err) return fmt.Errorf("Error deleting VPC %d in test sweep: %w", vpc.Id, err)
} }
if deleteResp.StatusCode() != http.StatusNoContent { if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting VPC %d for test sweep: %s", deleteResp.StatusCode(), *vpc.Id, deleteResp.Body) return fmt.Errorf("Unexpected status %d deleting VPC %d for test sweep: %s", deleteResp.StatusCode(), vpc.Id, deleteResp.Body)
} }
log.Println("Deleted VPC for test sweep:", *vpc.Id) log.Println("Deleted VPC for test sweep:", vpc.Id)
} }
} }
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil { if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false nextPage = false
break break
} }
@@ -93,7 +93,7 @@ func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.Rea
return return
} }
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries) routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...) resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -94,7 +94,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
return return
} }
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries) routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -136,7 +136,7 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
return return
} }
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries) routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...) resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -183,7 +183,7 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
return return
} }
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries) routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@@ -253,8 +253,8 @@ func GetRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteE
// TODO: This panics if invalid, should be handled better // TODO: This panics if invalid, should be handled better
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{ r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringValue(*route.Description), "description": types.StringValue(*route.Description),
"destination": types.StringValue(*route.Destination), "destination": types.StringValue(route.Destination),
"router": types.StringValue(*route.Router), "router": types.StringValue(route.Router),
}) })
routeEntriesValues = append(routeEntriesValues, r) routeEntriesValues = append(routeEntriesValues, r)
} }
@@ -55,6 +55,7 @@ func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
Description: "Leave null to accept the default '/' path.", Description: "Leave null to accept the default '/' path.",
MarkdownDescription: "Leave null to accept the default '/' path.", MarkdownDescription: "Leave null to accept the default '/' path.",
Validators: []validator.String{ Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(regexp.MustCompile("/[A-Za-z0-9/.?=&+%_-]*"), ""), stringvalidator.RegexMatches(regexp.MustCompile("/[A-Za-z0-9/.?=&+%_-]*"), ""),
}, },
}, },
+28 -14
View File
@@ -120,8 +120,8 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
"port_blocking": schema.BoolAttribute{ "port_blocking": schema.BoolAttribute{
Optional: true, Optional: true,
Computed: true, Computed: true,
Description: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.", Description: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts.",
MarkdownDescription: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.", MarkdownDescription: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts.",
}, },
"region": schema.StringAttribute{ "region": schema.StringAttribute{
Required: true, Required: true,
@@ -131,6 +131,12 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
stringvalidator.LengthAtLeast(1), stringvalidator.LengthAtLeast(1),
}, },
}, },
"separate_private_network_interface": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "If true this will enable a separate private network interface for the server. This is only available for servers in a VPC.",
MarkdownDescription: "If true this will enable a separate private network interface for the server. This is only available for servers in a VPC.",
},
"size": schema.StringAttribute{ "size": schema.StringAttribute{
Required: true, Required: true,
Description: "The slug of the selected size.", Description: "The slug of the selected size.",
@@ -161,23 +167,31 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "Leave null to use default (public) network for the selected region.", Description: "Leave null to use default (public) network for the selected region.",
MarkdownDescription: "Leave null to use default (public) network for the selected region.", MarkdownDescription: "Leave null to use default (public) network for the selected region.",
}, },
"vpc_ipv4_address": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided.",
MarkdownDescription: "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided.",
},
}, },
} }
} }
type ServerModel struct { type ServerModel struct {
AdvancedFeatures AdvancedFeaturesValue `tfsdk:"advanced_features"` AdvancedFeatures AdvancedFeaturesValue `tfsdk:"advanced_features"`
Backups types.Bool `tfsdk:"backups"` Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"` Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"` Image types.String `tfsdk:"image"`
Ipv6 types.Bool `tfsdk:"ipv6"` Ipv6 types.Bool `tfsdk:"ipv6"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
PortBlocking types.Bool `tfsdk:"port_blocking"` PortBlocking types.Bool `tfsdk:"port_blocking"`
Region types.String `tfsdk:"region"` Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"` SeparatePrivateNetworkInterface types.Bool `tfsdk:"separate_private_network_interface"`
SshKeys types.List `tfsdk:"ssh_keys"` Size types.String `tfsdk:"size"`
UserData types.String `tfsdk:"user_data"` SshKeys types.List `tfsdk:"ssh_keys"`
VpcId types.Int64 `tfsdk:"vpc_id"` UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
VpcIpv4Address types.String `tfsdk:"vpc_ipv4_address"`
} }
var _ basetypes.ObjectTypable = AdvancedFeaturesType{} var _ basetypes.ObjectTypable = AdvancedFeaturesType{}
+1 -1
View File
@@ -26,7 +26,7 @@ func main() {
flag.Parse() flag.Parse()
opts := providerserver.ServeOpts{ opts := providerserver.ServeOpts{
Address: "hashicorp.com/oscarhermoso/binarylane", Address: "git.unkin.net/unkin/binarylane",
Debug: debug, Debug: debug,
} }
+452 -670
View File
File diff suppressed because it is too large Load Diff
+26 -2
View File
@@ -573,7 +573,7 @@
"name": "transfer_max", "name": "transfer_max",
"float64": { "float64": {
"computed_optional_required": "computed", "computed_optional_required": "computed",
"description": "The maximum transfer in TB permitted for this size." "description": "The maximum transfer in TB permitted for this size. If this is the same as `transfer` no additional transfer is supported."
} }
}, },
{ {
@@ -743,6 +743,16 @@
"computed_optional_required": "computed_optional", "computed_optional_required": "computed_optional",
"description": "Leave null to accept the default '/' path.", "description": "Leave null to accept the default '/' path.",
"validators": [ "validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
},
{ {
"custom": { "custom": {
"imports": [ "imports": [
@@ -852,7 +862,7 @@
"name": "port_blocking", "name": "port_blocking",
"bool": { "bool": {
"computed_optional_required": "computed_optional", "computed_optional_required": "computed_optional",
"description": "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts." "description": "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts."
} }
}, },
{ {
@@ -874,6 +884,13 @@
] ]
} }
}, },
{
"name": "separate_private_network_interface",
"bool": {
"computed_optional_required": "computed_optional",
"description": "If true this will enable a separate private network interface for the server. This is only available for servers in a VPC."
}
},
{ {
"name": "size", "name": "size",
"string": { "string": {
@@ -929,6 +946,13 @@
"description": "Leave null to use default (public) network for the selected region." "description": "Leave null to use default (public) network for the selected region."
} }
}, },
{
"name": "vpc_ipv4_address",
"string": {
"computed_optional_required": "computed_optional",
"description": "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided."
}
},
{ {
"name": "id", "name": "id",
"int64": { "int64": {
+1
View File
@@ -144,3 +144,4 @@ cat <<<$(jq '.components.schemas.SizeType.properties.description += {"x-oapi-cod
# Edit description here because it's hard to override nested schema properties # Edit description here because it's hard to override nested schema properties
cat <<<$(jq '.components.schemas.ForwardingRule.properties.entry_protocol.description = "The protocol that traffic must match for the load balancer to forward it. Valid values are \"http\" and \"https\"."' $OPENAPI_FILE) >$OPENAPI_FILE cat <<<$(jq '.components.schemas.ForwardingRule.properties.entry_protocol.description = "The protocol that traffic must match for the load balancer to forward it. Valid values are \"http\" and \"https\"."' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.SizeOptions.properties.transfer_max.description = "The maximum transfer in TB permitted for this size. If this is the same as `transfer` no additional transfer is supported."' $OPENAPI_FILE) >$OPENAPI_FILE