Compare commits

...

32 Commits

Author SHA1 Message Date
dependabot[bot] 3b507277bd 🐈 Bump goreleaser/goreleaser-action
Bumps the minor-patch group with 1 update in the / directory: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action).


Updates `goreleaser/goreleaser-action` from 7.0.0 to 7.2.3
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/ec59f474b9834571250b370d4735c50f8e2d1e29...f06c13b6b1a9625abc9e6e439d9c05a8f2190e94)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 15:03:43 +00: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
Oscar Hermoso 08a37caf51 😹 Fix gh release edit number of args 2025-10-05 13:45:19 +08:00
Oscar Hermoso 709fb3ce28 🍞 Fix load balancer caring about order of server_ids (#104) 2025-10-05 13:24:58 +08:00
dependabot[bot] 5313e1c7ab 🐹 Bump github.com/hashicorp/terraform-plugin-framework (#103)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework).


Updates `github.com/hashicorp/terraform-plugin-framework` from 1.16.0 to 1.16.1
- [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.0...v1.16.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-framework
  dependency-version: 1.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 09:46:54 +08:00
dependabot[bot] ce349f53f0 🐈 Bump actions/setup-go from 5 to 6 (#100)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  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>
2025-10-05 09:46:41 +08:00
dependabot[bot] 9d3d3e9af3 🐹 Bump the minor-patch group with 4 updates (#101)
Bumps the minor-patch group with 4 updates: [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs), [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework), [github.com/hashicorp/terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) and [github.com/hashicorp/terraform-plugin-go](https://github.com/hashicorp/terraform-plugin-go).


Updates `github.com/hashicorp/terraform-plugin-docs` from 0.22.0 to 0.23.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.22.0...v0.23.0)

Updates `github.com/hashicorp/terraform-plugin-framework` from 1.15.1 to 1.16.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.15.1...v1.16.0)

Updates `github.com/hashicorp/terraform-plugin-framework-timeouts` from 0.5.0 to 0.6.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.5.0...v0.6.0)

Updates `github.com/hashicorp/terraform-plugin-go` from 0.28.0 to 0.29.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.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  dependency-version: 0.23.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.16.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.6.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.29.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>
2025-09-29 10:58:01 +08:00
Oscar Hermoso a0a227c88c Merge pull request #102 from an9wer/server-ipv6
6️⃣ Add "ipv6" attribute to servers
2025-09-29 09:54:22 +08:00
Oscar Hermoso f8edcd0e06 Add tests, update docs, bump OpenAPI schema version 2025-09-29 09:48:25 +08:00
an9wer 04552e0fcf Add 'ipv6' attribute to servers 2025-09-28 12:53:15 +09:30
dependabot[bot] 66fcb6f85e 🐹 Bump github.com/hashicorp/terraform-plugin-testing (#99)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing).


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

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-testing
  dependency-version: 1.13.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 12:50:05 +08:00
dependabot[bot] 806bdebaf8 🐈 Bump goreleaser/goreleaser-action in the minor-patch group (#94)
Bumps the minor-patch group with 1 update: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action).


Updates `goreleaser/goreleaser-action` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/9c156ee8a17a598857849441385a2041ef570552...e435ccd777264be153ace6237001ef4d979d3a7a)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 6.4.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>
2025-08-22 12:49:33 +08:00
dependabot[bot] dd06380c63 🐈 Bump actions/checkout from 4 to 5 (#95)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  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>
2025-08-22 12:49:03 +08:00
Oscar Hermoso 322a8fda80 👆 Update OpenAPI schema to 0.32.1 (#97) 2025-08-18 11:21:23 +08:00
Oscar Hermoso a50daac8e9 🧨 Fix unspecified attribute for load balancer (#93)
* 🧨 Fix unspecified attribute for load balancer

* remove duplicated `timeouts` in schema, test to ensure `ip` is set
2025-08-04 12:22:24 +08:00
dependabot[bot] 424828123d 🐹 Bump github.com/hashicorp/terraform-plugin-framework (#92)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework).


Updates `github.com/hashicorp/terraform-plugin-framework` from 1.15.0 to 1.15.1
- [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.15.0...v1.15.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-framework
  dependency-version: 1.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-02 16:12:23 +08:00
Oscar Hermoso 4c50298eca ⛑️ require replacement if VPC ip_range changes (#89) 2025-07-20 09:44:53 +08:00
dependabot[bot] cbef2be0fe 🐹 Bump the minor-patch group across 1 directory with 2 updates (#87)
Bumps the minor-patch group with 2 updates in the / directory: [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) and [github.com/oapi-codegen/oapi-codegen/v2](https://github.com/oapi-codegen/oapi-codegen).


Updates `github.com/hashicorp/terraform-plugin-docs` from 0.21.0 to 0.22.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.21.0...v0.22.0)

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

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  dependency-version: 0.22.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.5.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>
2025-07-18 14:41:42 +08:00
Oscar Hermoso 5767bb6739 🔑 Fix whitespace causing SSH key to be re-created (#86)
* 🔑 trim SSH key string with custom value
* 🔑 RequiresReplace plan modifier for TrimmedString
2025-07-18 13:55:07 +08:00
Oscar Hermoso 74eac97fe8 🥉 Working K3s example 2025-07-14 17:43:19 +08:00
dependabot[bot] a30b6b14a7 🐹 Bump github.com/getkin/kin-openapi from 0.127.0 to 0.131.0 (#78)
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.127.0 to 0.131.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.127.0...v0.131.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.131.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-22 09:39:27 +08:00
dependabot[bot] 0a4156269f 🐹 Bump the minor-patch group with 2 updates (#83)
Bumps the minor-patch group with 2 updates: [github.com/hashicorp/terraform-plugin-go](https://github.com/hashicorp/terraform-plugin-go) and [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing).


Updates `github.com/hashicorp/terraform-plugin-go` from 0.27.0 to 0.28.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.27.0...v0.28.0)

Updates `github.com/hashicorp/terraform-plugin-testing` from 1.13.1 to 1.13.2
- [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.1...v1.13.2)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-go
  dependency-version: 0.28.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.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-22 09:35:10 +08:00
dependabot[bot] 0adff101c6 🐹 Bump the minor-patch group across 1 directory with 4 updates (#80)
Bumps the minor-patch group with 3 updates in the / directory: [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework), [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators) and [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing).


Updates `github.com/hashicorp/terraform-plugin-framework` from 1.14.1 to 1.15.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.14.1...v1.15.0)

Updates `github.com/hashicorp/terraform-plugin-framework-validators` from 0.17.0 to 0.18.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.17.0...v0.18.0)

Updates `github.com/hashicorp/terraform-plugin-go` from 0.26.0 to 0.27.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.26.0...v0.27.0)

Updates `github.com/hashicorp/terraform-plugin-testing` from 1.12.0 to 1.13.1
- [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.12.0...v1.13.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-framework
  dependency-version: 1.15.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.18.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.27.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.13.1
  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>
2025-06-11 07:00:34 +08:00
61 changed files with 3805 additions and 2098 deletions
+11 -6
View File
@@ -6,7 +6,7 @@ name: Release
on:
push:
tags:
- 'v*'
- "v*"
# Releases need permissions to read and write the repository contents.
# GitHub considers creating releases and uploading assets as writing contents.
@@ -23,25 +23,30 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Allow goreleaser to access older tag information.
fetch-depth: 0
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"
cache: true
- 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
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
args: release --clean
env:
# GitHub sets the GITHUB_TOKEN secret automatically.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
- name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
run: gh release edit "${{ github.ref }}" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -23,11 +23,11 @@ jobs:
group: test
cancel-in-progress: true # would be nice if GH offered cancel-pending instead, https://github.com/orgs/community/discussions/63136
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- uses: hashicorp/setup-terraform@v3
- uses: hashicorp/setup-terraform@v4
with:
terraform_version: '1.5.*'
terraform_wrapper: false
@@ -54,8 +54,8 @@ jobs:
# name: Unit Tests
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-go@v5
# - uses: actions/checkout@v5
# - uses: actions/setup-go@v6
# with:
# go-version-file: 'go.mod'
# - run: go test -v -cover ./...
+3
View File
@@ -1,3 +1,6 @@
{
"go.testEnvFile": "${workspaceFolder}/.env",
"go.testEnvVars": {
"TF_LOG": "TRACE"
}
}
+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.
+15 -8
View File
@@ -20,9 +20,9 @@ Planned features:
- [x] Cloud Init
- [x] Updating server properties
- [x] "Advanced Features"
- [x] IPV6
- [ ] Customised Backups
- [ ] Software
- [ ] IPV6
- [ ] Alerts
- [x] SSH Keys
- [x] Virtual Private Cloud
@@ -36,9 +36,9 @@ Planned features:
- [x] Examples
- [x] Basic
- [x] Cloud Init
- [x] K3s
- [ ] NixOS
- [ ] Virtual Private Cloud
- [ ] Kubernetes
## Server parameters
@@ -57,6 +57,7 @@ jq '[ .regions[] | .slug ] | sort' tmp/regions.json
```json
[
"adl",
"bne",
"mel",
"per",
@@ -80,21 +81,24 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
```json
[
"alma-10",
"alma-8",
"alma-9",
"byo-os",
"byo-os-uefi",
"byo-os-virtio-disabled",
"cpanel-plus-whm",
"debian-11",
"debian-12",
"rocky-8",
"debian-13",
"rocky-10",
"rocky-9",
"ubuntu-20.04-neon-desktop",
"ubuntu-20.04.6",
"ubuntu-22.04",
"ubuntu-22.04-desktop",
"ubuntu-24.04",
"windows-2012-r2",
"ubuntu-24.04-neon-desktop",
"ubuntu-26.04",
"windows-2016",
"windows-2016-sql-2016-web",
"windows-2019",
@@ -102,7 +106,8 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
"windows-2019-sql-2017-web",
"windows-2022",
"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-6thr",
"cpu-8thr",
"ded-3900x-1600gb",
"ded-7900x-800gb",
"ded-e2136-400gb",
"ded-e2136-800gb",
"ded-e2288g-400gb",
"ded-e2288g-800gb",
"hdd-1000gb",
"hdd-2000gb",
"hdd-4000gb",
"hdd-500gb",
"hdd-6000gb",
"hdd-8000gb",
"std-1vcpu",
"std-2vcpu",
"std-4vcpu",
+8 -2
View File
@@ -29,16 +29,22 @@ data "binarylane_load_balancer" "example" {
- `forwarding_rules` (Attributes List) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
- `health_check` (Object) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
- `ip` (String) The IPv4 address of the load balancer.
- `name` (String) The hostname of the load balancer.
- `region` (String) Leave null to create an anycast load balancer.
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
- `server_ids` (Set of Number) A list of server IDs to assign to this load balancer.
<a id="nestedatt--forwarding_rules"></a>
### Nested Schema for `forwarding_rules`
Read-Only:
- `entry_protocol` (String) The protocol that traffic must match for the load balancer to forward it. Valid values are "http" and "https".
- `entry_protocol` (String) The protocol that traffic must match for this load balancer to forward traffic according to this rule.
| Value | Description |
| ----- | ----------- |
| http | The load balancer will forward HTTP traffic that matches this rule. |
| https | The load balancer will forward HTTPS traffic that matches this rule. |
<a id="nestedatt--health_check"></a>
+6 -1
View File
@@ -34,6 +34,7 @@ data "binarylane_server" "example" {
- \> 60 GB must be a multiple of 10
- \> 200 GB must be a multiple of 100
- `image` (String) The slug of the selected operating system, such as `debian-12`. You can fetch a full list of images from the BinaryLane API.
- `ipv6` (Boolean) If `true` this will add a public and private IPv6 address to the server. By default, IPv6 is disabled.
- `memory` (Number) The total memory in MB for this server. Leave null to accept the default size. Valid values:
- must be a multiple of 128
- \> 2048 MB must be a multiple of 1024
@@ -41,15 +42,19 @@ data "binarylane_server" "example" {
- \> 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.
- `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_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_ipv6_addresses` (List of String) The public IPv6 addresses assigned to the server.
- `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.
- `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.
- `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_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>
### 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.
- `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_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.
<a id="nestedatt--sizes--options--offsite_backup_frequency_cost"></a>
+1 -1
View File
@@ -30,4 +30,4 @@ data "binarylane_ssh_key" "example" {
- `default` (Boolean) If `true`, this SSH key will be included on all new server installations (if the operating system supports SSH key injection).
- `fingerprint` (String) The fingerprint of the SSH key.
- `name` (String) A name to help you identify the key.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format. This should be a valid public key, such as one generated by `ssh-keygen`.
-1
View File
@@ -1,7 +1,6 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane Provider"
subcategory: ""
description: |-
---
+11 -2
View File
@@ -40,19 +40,25 @@ resource "binarylane_load_balancer" "example" {
- `forwarding_rules` (Attributes List) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
- `health_check` (Attributes) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
- `region` (String) Leave null to create an anycast load balancer.
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
- `server_ids` (Set of Number) A list of server IDs to assign to this load balancer.
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
### Read-Only
- `id` (Number) The ID of the load balancer to fetch.
- `ip` (String) The IPv4 address of the load balancer.
<a id="nestedatt--forwarding_rules"></a>
### Nested Schema for `forwarding_rules`
Required:
- `entry_protocol` (String) The protocol that traffic must match for the load balancer to forward it. Valid values are "http" and "https".
- `entry_protocol` (String) The protocol that traffic must match for this load balancer to forward traffic according to this rule.
| Value | Description |
| ----- | ----------- |
| http | The load balancer will forward HTTP traffic that matches this rule. |
| https | The load balancer will forward HTTPS traffic that matches this rule. |
<a id="nestedatt--health_check"></a>
@@ -76,11 +82,14 @@ Optional:
Optional:
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
## 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_load_balancer.example "<name or id>"
```
+8 -1
View File
@@ -44,6 +44,7 @@ resource "binarylane_server" "example" {
- must be a multiple of 5
- \> 60 GB must be a multiple of 10
- \> 200 GB must be a multiple of 100
- `ipv6` (Boolean) If `true` this will add a public and private IPv6 address to the server. By default, IPv6 is disabled.
- `memory` (Number) The total memory in MB for this server. Leave null to accept the default size. Valid values:
- must be a multiple of 128
- \> 2048 MB must be a multiple of 1024
@@ -51,12 +52,14 @@ resource "binarylane_server" "example" {
- \> 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.
- `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.
- `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))
- `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_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
@@ -64,7 +67,9 @@ resource "binarylane_server" "example" {
- `password_change_supported` (Boolean) If this is true then the `password` attribute can be changed with Terraform. If this is false then the `password` attribute can only be replaced with a null/empty value, which will clear the root/administrator password allowing the password to be changed via the web console.
- `permalink` (String) A randomly generated two-word identifier assigned to servers in regions that support this feature
- `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.
- `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.
<a id="nestedatt--advanced_features"></a>
### Nested Schema for `advanced_features`
@@ -98,6 +103,8 @@ Optional:
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.example "<server name or id>"
```
+2
View File
@@ -71,6 +71,8 @@ Optional:
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_firewall_rules.example "<server name or id>"
```
+3 -1
View File
@@ -32,7 +32,7 @@ resource "binarylane_server" "example" {
### Required
- `name` (String) A name to help you identify the key.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format. This should be a valid public key, such as one generated by `ssh-keygen`.
### Optional
@@ -47,6 +47,8 @@ resource "binarylane_server" "example" {
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_ssh_key.example "<SSH Key ID or fingerprint>"
```
+2
View File
@@ -43,6 +43,8 @@ resource "binarylane_server" "example" {
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_vpc.example "<vpc_id>"
```
+2
View File
@@ -67,6 +67,8 @@ Optional:
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_vpc_route_entries.example "<vpc_id>"
```
+1 -1
View File
@@ -6,6 +6,6 @@ In order of increasing complexity, these examples show how to use the provider.
- [./offerings](./offerings/main.tf) - Use data sources to list available images, regions, and sizes.
- [./ssh](./ssh/main.tf) - Create a server and registers an SSH key as a global default.
- [./cloud-init](./cloud-init/main.tf) - Create a server with a cloud-init configuration.
- [./k3s](./k3s/main.tf) - Create Kubernetes cluster using k3s.
- [./nix](./nix/main.tf) - WIP - Create a server with a NixOS configuration.
- [./vpc](./vpc/main.tf) - WIP - Create a server with a Virtual Private Cloud.
- [./k3s](./k3s/main.tf) - WIP - Create Kubernetes cluster using k3s.
+2 -2
View File
@@ -3,10 +3,10 @@ bootcmd:
# If you have additional installation steps that need to be awaited, define
# these in the `bootcmd` section and they will also be awaited.
#
- [cloud-init-per, once, 'example-installing', sh, -c, 'sleep 60'] # pretend this is a slow installation step
- [cloud-init-per, instance, 'example-installing', sh, -c, 'sleep 60'] # pretend this is a slow installation step
- [
cloud-init-per,
once,
instance,
'example-done',
sh,
-c,
+1
View File
@@ -38,3 +38,4 @@ terraform.rc
# .kube/config contains credentials
.kube
.id_ed25519*
+11 -8
View File
@@ -1,15 +1,18 @@
# WIP - examples/k8s
# examples/k3s
This WIP example shows how to create a Kubernetes cluster on Binary Lane.
This example shows how to create a K3s cluster on Binary Lane.
After setting up the cluster by running `terraform apply`, the kubeconfig file will be downloaded to the `.kube/config` file in the current directory. You can then use `kubectl` to interact with the cluster, for example:
The SSH key used to access the servers is stored in the `.id_ed25519` file. You can use it to SSH into the servers:
```sh
ssh -o "IdentitiesOnly=yes" -i ./.id_ed25519 root@neptune-stand.bnr.la
```
## Prior art
- [MartinHodges/create_k8s](https://github.com/MartinHodges/create_k8s) repo and the [medium article](https://medium.com/@martin.hodges/creating-a-kubernetes-cluster-from-scratch-in-1-hour-using-automation-a25e387be547) that goes with it.
- https://github.com/inscapist/terraform-k3s-private-cloud
- https://github.com/schnerring/schnerring.github.io/blob/07d06fb40e3d01f2483a739f516ac4d711a5742c/content/blog/use-terraform-to-deploy-an-azure-kubernetes-service-aks-cluster-traefik-2-cert-manager-and-lets-encrypt-certificates/index.md
## Remaining TODOs
- [ ] Consider rewriting module to use `microk8s` instead of `k3s`
- [ ] Use `kustomize` instead of `helm`
- [ ] Use the `binarylane_load_balancer` resource instead of `kubectl port-forward` (or use the load balancer in another example)
- https://github.com/xunleii/terraform-module-k3s
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash -x
exec >/tmp/k3s-agent-join-debug.log 2>&1
export K3S_TOKEN="${cluster_token}"
export K3S_URL="https://${cluster_server}:6443"
curl -sfL https://get.k3s.io | sh -s - agent \
--node-name="$(hostname -f)"
unset K3S_TOKEN
unset K3S_URL
# Disable firewall recommended
ufw disable
echo "K3s Node Join Completed"
-25
View File
@@ -1,25 +0,0 @@
#!/bin/bash -x
exec >/tmp/k3s-server-install-debug.log 2>&1
export INSTALL_K3S_NAME="${cluster_id}"
export K3S_TOKEN="${cluster_token}"
export K3S_KUBECONFIG_MODE="644"
curl -sfL https://get.k3s.io | sh -s - server \
--node-name="$(hostname -f)"
# --disable-cloud-controller \
# --disable servicelb \
# --disable local-storage \
# --disable traefik
unset INSTALL_K3S_NAME
unset K3S_TOKEN
unset K3S_KUBECONFIG_MODE
echo "Installing Helm"
curl -sfL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# Disable firewall recommended
ufw disable
echo "K3s Setup Completed"
+50
View File
@@ -0,0 +1,50 @@
module "k3s" {
# source = "xunleii/k3s/module" # Uncomment after https://github.com/xunleii/terraform-module-k3s/pull/207 is merged
source = "github.com/dvdmuckle/terraform-module-k3s?ref=62dca7731b78f1120a141a2703e3ed14470276c0"
depends_on_ = binarylane_server.agents
k3s_version = "latest"
cluster_domain = "cluster.local"
managed_fields = ["label"]
// These subnets are used by the Flannel network interface in K3s, and are NOT the same as the VPC address space
cidr = {
pods = "10.42.0.0/16",
services = "10.43.0.0/16",
}
servers = {
for i in range(length(binarylane_server.servers)) :
binarylane_server.servers[i].name => {
ip = binarylane_server.servers[i].private_ipv4_addresses[0]
name = binarylane_server.servers[i].name
connection = {
host = binarylane_server.servers[i].permalink
private_key = trimspace(tls_private_key.ed25519_provisioning.private_key_pem)
}
labels = { "node.kubernetes.io/type" = "master" }
flags = [
"--tls-san ${binarylane_server.servers[0].private_ipv4_addresses[0]}",
]
}
}
agents = {
for i in range(length(binarylane_server.agents)) :
binarylane_server.agents[i].name => {
ip = binarylane_server.agents[i].private_ipv4_addresses[0]
name = binarylane_server.agents[i].name
connection = {
host = binarylane_server.agents[i].permalink
private_key = trimspace(tls_private_key.ed25519_provisioning.private_key_pem)
}
labels = { "node.kubernetes.io/pool" = "general" }
}
}
}
resource "local_sensitive_file" "kube_config" {
content = module.k3s.kube_config
filename = ".kube/config"
file_permission = "0644"
}
+28 -69
View File
@@ -1,100 +1,60 @@
locals {
cluster_id = "tf-example-k8s"
custom_domain = "tf-example-k8s.internal"
cluster_id = "tf-example-k3s"
custom_domain = "tf-example-k3s.internal"
}
resource "random_password" "binarylane" {
length = 18
}
resource "random_password" "cluster_token" {
length = 64
special = false
}
# SSH Key
# -------
resource "tls_private_key" "ed25519_provisioning" {
algorithm = "ED25519"
}
resource "local_sensitive_file" "ssh_private_key" {
content = tls_private_key.ed25519_provisioning.private_key_openssh
filename = ".id_ed25519"
file_permission = "0600"
}
resource "local_file" "ssh_public_key" {
content = tls_private_key.ed25519_provisioning.public_key_openssh
filename = ".id_ed25519.pub"
file_permission = "0644"
}
resource "binarylane_ssh_key" "example" {
name = "tf-example-k8s"
public_key = file("~/.ssh/id_ed25519.pub")
name = "tf-example-k3s"
public_key = tls_private_key.ed25519_provisioning.public_key_openssh
}
# Virtual Private Cloud
# ---------------------
resource "binarylane_vpc" "example" {
name = local.cluster_id
ip_range = "10.240.0.0/16"
ip_range = "10.0.0.0/8"
}
# k3s Servers
# -----------
data "cloudinit_config" "server" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-server-install.sh", {
cluster_id = local.cluster_id,
cluster_token = random_password.cluster_token.result,
})
}
}
resource "binarylane_server" "server" {
resource "binarylane_server" "servers" {
count = 1
name = "${local.cluster_id}-server-${count.index + 1}"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
size = "std-1vcpu" # 2GB memory, 1 vCPU
password = random_password.binarylane.result
ssh_keys = [binarylane_ssh_key.example.id]
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.server.rendered)
}
data "external" "kubeconfig" {
program = [
"/usr/bin/ssh",
"-o UserKnownHostsFile=/dev/null",
"-o StrictHostKeyChecking=no",
"root@${binarylane_server.server.0.permalink}",
"echo '{\"kubeconfig\":\"'$(cat /etc/rancher/k3s/k3s.yaml | base64)'\"}'",
]
}
resource "local_sensitive_file" "kubeconfig" {
depends_on = [binarylane_server.server]
content = replace(
base64decode(
replace(data.external.kubeconfig.result.kubeconfig, " ", "")
),
"server: https://127.0.0.1:6443",
"server: https://${binarylane_server.server.0.permalink}:6443",
)
filename = "${path.module}/.kube/config"
file_permission = "0600"
}
# k3s Agents
# ----------
data "cloudinit_config" "agent" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-agent-join.sh", {
cluster_token = random_password.cluster_token.result,
cluster_server = binarylane_server.server.0.private_ipv4_addresses.0
})
}
}
resource "binarylane_server" "agent" {
resource "binarylane_server" "agents" {
count = 2
name = "${local.cluster_id}-agent-${count.index + 1}"
@@ -105,12 +65,11 @@ resource "binarylane_server" "agent" {
ssh_keys = [binarylane_ssh_key.example.id]
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.agent.rendered)
}
# Virtual Private Cloud Routing
# -----------------------------
resource "binarylane_vpc_route_entries" "example" {
resource "binarylane_vpc_route_entries" "example" { # TODO
vpc_id = binarylane_vpc.example.id
route_entries = [
# {
@@ -122,12 +81,12 @@ resource "binarylane_vpc_route_entries" "example" {
}
locals {
agent_ips = flatten([for _, a in binarylane_server.agent : a.private_ipv4_addresses])
server_ips = flatten([for _, s in binarylane_server.server : s.private_ipv4_addresses])
agent_ips = flatten([for _, a in binarylane_server.agents : a.private_ipv4_addresses])
server_ips = flatten([for _, s in binarylane_server.servers : s.private_ipv4_addresses])
}
resource "binarylane_server_firewall_rules" "example" {
for_each = { for server in concat(binarylane_server.server, binarylane_server.agent) : server.name => server }
for_each = { for server in concat(binarylane_server.servers, binarylane_server.agents) : server.name => server }
server_id = each.value.id
-64
View File
@@ -1,64 +0,0 @@
resource "kubectl_manifest" "hello" {
yaml_body = <<YAML
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
selector:
matchLabels:
app: nginx-hello
template:
metadata:
labels:
app: nginx-hello
spec:
containers:
- image: nginxdemos/hello
name: nginx-hello
ports:
- name: http
containerPort: 8080
protocol: TCP
YAML
}
resource "kubectl_manifest" "hello_service" {
yaml_body = <<YAML
apiVersion: v1
kind: Service
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
type: ClusterIP
selector:
app: nginx-hello
ports:
- port: 80
YAML
}
resource "kubectl_manifest" "hello_ingress" {
yaml_body = <<YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-hello
spec:
rules:
- host: "*.bnr.la"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-hello
port:
number: 80
YAML
}
-18
View File
@@ -3,25 +3,7 @@ terraform {
binarylane = {
source = "oscarhermoso/binarylane"
}
kubectl = {
source = "alekc/kubectl"
version = ">= 2.0.0"
}
}
}
provider "binarylane" {}
provider "helm" {
kubernetes {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
}
provider "kubectl" {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
+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
+46 -45
View File
@@ -1,22 +1,20 @@
module terraform-provider-binarylane
go 1.23.0
toolchain go1.24.1
go 1.25.8
require (
github.com/deepmap/oapi-codegen v1.16.3
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-docs v0.21.0
github.com/hashicorp/terraform-plugin-framework v1.14.1
github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0
github.com/hashicorp/terraform-plugin-go v0.26.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.12.0
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
github.com/oapi-codegen/runtime v1.1.1
github.com/hashicorp/terraform-plugin-docs v0.24.0
github.com/hashicorp/terraform-plugin-framework v1.19.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.31.0
github.com/hashicorp/terraform-plugin-log v0.11.0
github.com/hashicorp/terraform-plugin-testing v1.16.0
github.com/oapi-codegen/oapi-codegen/v2 v2.6.0
github.com/oapi-codegen/runtime v1.6.0
)
require (
@@ -25,21 +23,21 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/circl v1.6.3 // 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/fsnotify/fsnotify v1.5.4 // indirect
github.com/getkin/kin-openapi v0.127.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
@@ -52,67 +50,70 @@ require (
github.com/hashicorp/go-cty v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-plugin v1.8.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hc-install v0.9.1 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/hc-install v0.9.4 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.22.0 // indirect
github.com/hashicorp/terraform-json v0.24.0 // indirect
github.com/hashicorp/terraform-exec v0.25.1 // 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-sdk/v2 v2.36.1 // indirect
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1 // indirect
github.com/hashicorp/terraform-registry-address v0.5.0 // indirect
github.com/hashicorp/terraform-svchost v0.2.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oklog/run v1.2.0 // indirect
github.com/onsi/gomega v1.33.1 // indirect
github.com/pb33f/libopenapi v0.15.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // 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/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yuin/goldmark v1.7.7 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zclconf/go-cty v1.18.1 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/tools v0.47.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/grpc v1.69.4 // indirect
google.golang.org/protobuf v1.36.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 // indirect
google.golang.org/grpc v1.83.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+138 -123
View File
@@ -11,13 +11,13 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
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/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
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/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
@@ -30,23 +30,26 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
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/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/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/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
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/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deepmap/oapi-codegen v1.16.3 h1:GT9G86SbQtT1r8ZB+4Cybi9VGdu1P5ieNvNdEoCSbrA=
github.com/deepmap/oapi-codegen v1.16.3/go.mod h1:JD6ErqeX0nYnhdciLc61Konj3NBASREMlkHOgHn8WAM=
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
@@ -55,24 +58,24 @@ 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/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
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.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
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/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
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/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@@ -82,8 +85,8 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -124,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.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs=
github.com/hashicorp/go-plugin v1.8.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
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-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ=
github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.4 h1:KKWOpUG0EqIV63Qk2GGFrZ0s275NVs5lKf9N5vjBNoc=
github.com/hashicorp/hc-install v0.9.4/go.mod h1:4LRYeEN2bMIFfIv57ldMWt9awfuZhvpbRt0vWmv51WU=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
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/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64=
github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ=
github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q=
github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
github.com/hashicorp/terraform-exec v0.25.1 h1:PRutYRGM8pixV3B8812NYoBK5O+yuf3qcB/70KFKGiU=
github.com/hashicorp/terraform-exec v0.25.1/go.mod h1:+izOYrs9sKMQK4OYvGDnrSSJHY/pm4e4eXFqSL2Q5mA=
github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=
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/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/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/go.mod h1:fywrEKpordQypmAjz/HIfm2LuNVmyJ6KDe8XT9GdJxQ=
github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8=
github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s=
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0/go.mod h1:t339KhmxnaF4SzdpxmqW8HnQBHVGYazwtfxU0qCs4eE=
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0=
github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M=
github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4=
github.com/hashicorp/terraform-plugin-testing v1.12.0 h1:tpIe+T5KBkA1EO6aT704SPLedHUo55RenguLHcaSBdI=
github.com/hashicorp/terraform-plugin-testing v1.12.0/go.mod h1:jbDQUkT9XRjAh1Bvyufq+PEH1Xs4RqIdpOQumSgSXBM=
github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/terraform-plugin-docs v0.24.0 h1:YNZYd+8cpYclQyXbl1EEngbld8w7/LPOm99GD5nikIU=
github.com/hashicorp/terraform-plugin-docs v0.24.0/go.mod h1:YLg+7LEwVmRuJc0EuCw0SPLxuQXw5mW8iJ5ml/kvi+o=
github.com/hashicorp/terraform-plugin-framework v1.19.0 h1:q0bwyhxAOR3vfdgbk9iplv3MlTv/dhBHTXjQOtQDoBA=
github.com/hashicorp/terraform-plugin-framework v1.19.0/go.mod h1:YRXOBu0jvs7xp4AThBbX4mAzYaMJ1JgtFH//oGKxwLc=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
github.com/hashicorp/terraform-plugin-go v0.31.0 h1:0Fz2r9DQ+kNNl6bx8HRxFd1TfMKUvnrOtvJPmp3Z0q8=
github.com/hashicorp/terraform-plugin-go v0.31.0/go.mod h1:A88bDhd/cW7FnwqxQRz3slT+QY6yzbHKc6AOTtmdeS8=
github.com/hashicorp/terraform-plugin-log v0.11.0 h1:WjhcpZIVqP8YRe83+dIZXncwSgtu4vh27i23G33PUQY=
github.com/hashicorp/terraform-plugin-log v0.11.0/go.mod h1:XygBz8+m5kgwTb73MMyrnUjeNQeVWECEfg+h2opMsj0=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1 h1:2yPUd7esMOpuTaG3y1iEla1iw+tla+3ZEkkBnmOAre4=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.1/go.mod h1:sq8qsxh+PwdvTQFcd17kfCoBgQo46ADNMvCpKE7t/gY=
github.com/hashicorp/terraform-plugin-testing v1.16.0 h1:GB97nGnJ1hESpDrCjqZig38RodSF0gdRzxlDupLXP38=
github.com/hashicorp/terraform-plugin-testing v1.16.0/go.mod h1:eQPYAy9xFMV7xtIFX8Y+wJGtUB++HBl329zCF6PBMZk=
github.com/hashicorp/terraform-registry-address v0.5.0 h1:FAlhWOLFgMvo/4f5DPhCTwRYfHYdF1DjiOtgxfGr4p0=
github.com/hashicorp/terraform-registry-address v0.5.0/go.mod h1:wOJYCN60i/gSQGPCcGdamatjxn65EZBMFVt7c/Suzis=
github.com/hashicorp/terraform-svchost v0.2.1 h1:ubvrTFw3Q7CsoEaX7V06PtCTKG3wu7GyyobAoN4eF3Q=
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/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -179,12 +182,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
@@ -201,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/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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@@ -214,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/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-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
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/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@@ -226,12 +227,18 @@ 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.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q=
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 h1:4i+F2cvwBFZeplxCssNdLy3MhNzUD87mI3HnayHZkAU=
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/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/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -249,28 +256,31 @@ github.com/pb33f/libopenapi v0.15.0 h1:AoBYIY3HXqDDF8O9kcudlqWaRFZZJmgtueE649oHz
github.com/pb33f/libopenapi v0.15.0/go.mod h1:m+4Pwri31UvcnZjuP8M7TlbR906DXJmMvYsbis234xg=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg=
github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
@@ -281,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.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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
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 v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
@@ -296,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/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/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/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -311,35 +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-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/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM=
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/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
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.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
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-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-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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
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/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -350,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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
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-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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -381,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-20220722155257-8c9f86f7a55f/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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -393,25 +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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
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-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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
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-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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 h1:mJiOtnGp0k/BcSgdu03G2NwnscCfCH+h2QKUBZr18KI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -420,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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
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 v1.0.0-20180628173108-788fd7840127/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{
Computed: true,
Description: "The maximum transfer in TB permitted for this size.",
MarkdownDescription: "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. If this is the same as `transfer` no additional transfer is supported.",
},
"weekly_backups": schema.Int64Attribute{
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))
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
break
}
@@ -7,6 +7,7 @@ import (
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
@@ -22,8 +23,9 @@ type loadBalancerDataSource struct {
bc *BinarylaneClient
}
type loadBalancerDataSourceModel struct {
type loadBalancerDataModel struct {
resources.LoadBalancerModel
Ip types.String `tfsdk:"ip"`
}
func (d *loadBalancerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
@@ -52,7 +54,7 @@ func (d *loadBalancerDataSource) Configure(
func (d *loadBalancerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(
resources.LoadBalancerResourceSchema(ctx),
loadBalancerSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"id"},
},
@@ -63,10 +65,11 @@ func (d *loadBalancerDataSource) Schema(ctx context.Context, req datasource.Sche
}
resp.Schema = *ds
resp.Schema.Description = "Retrieve details about a BinaryLane Load Balancer."
resp.Schema.MarkdownDescription = resp.Schema.Description
}
func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data loadBalancerDataSourceModel
var data loadBalancerDataModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
@@ -93,7 +96,7 @@ func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRe
return
}
diags := setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
+153 -95
View File
@@ -10,14 +10,17 @@ import (
"time"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"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/listdefault"
"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"
)
@@ -37,7 +40,7 @@ type loadBalancerResource struct {
}
type loadBalancerResourceModel struct {
resources.LoadBalancerModel
loadBalancerDataModel
Timeouts timeouts.Value `tfsdk:"timeouts"`
}
@@ -60,15 +63,15 @@ func (d *loadBalancerResource) Configure(_ context.Context, req resource.Configu
d.bc = &bc
}
func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.LoadBalancerResourceSchema(ctx)
resp.Schema.Description = "Provides a BinaryLane Load Balancer resource. This can be used to create, update, and delete load balancers."
resp.Schema.MarkdownDescription = resp.Schema.Description +
func loadBalancerSchema(ctx context.Context) schema.Schema {
s := resources.LoadBalancerResourceSchema(ctx)
s.Description = "Provides a BinaryLane Load Balancer resource. This can be used to create, update, and delete load balancers."
s.MarkdownDescription = s.Description +
" See [the docs](https://support.binarylane.com.au/support/solutions/articles/1000025661-load-balancer) for more information."
// Overrides
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
id := s.Attributes["id"]
s.Attributes["id"] = schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
@@ -80,8 +83,8 @@ func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRe
},
}
region := resp.Schema.Attributes["region"]
resp.Schema.Attributes["region"] = &schema.StringAttribute{
region := s.Attributes["region"]
s.Attributes["region"] = schema.StringAttribute{
Description: region.GetDescription(),
MarkdownDescription: region.GetMarkdownDescription(),
Optional: true,
@@ -91,9 +94,56 @@ func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRe
},
}
forwardingRules := s.Attributes["forwarding_rules"].(schema.ListNestedAttribute)
s.Attributes["forwarding_rules"] = schema.ListNestedAttribute{
NestedObject: forwardingRules.NestedObject,
Description: forwardingRules.GetDescription(),
MarkdownDescription: forwardingRules.GetMarkdownDescription(),
Required: false,
Optional: true,
Computed: true,
Default: listdefault.StaticValue(types.ListValueMust(
types.ObjectType{
AttrTypes: resources.ForwardingRulesValue{}.AttributeTypes(ctx),
},
[]attr.Value{
types.ObjectValueMust(
map[string]attr.Type{
"entry_protocol": types.StringType,
},
map[string]attr.Value{
"entry_protocol": types.StringValue("http"),
},
),
},
)),
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
}
// Additional attributes
s.Attributes["ip"] = schema.StringAttribute{
Description: "The IPv4 address of the load balancer.",
MarkdownDescription: "The IPv4 address of the load balancer.",
Required: false,
Optional: false,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
}
return s
}
func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = loadBalancerSchema(ctx)
resp.Schema.Attributes["timeouts"] =
timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
Update: true,
})
}
@@ -107,9 +157,14 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
return
}
timeout, diags := data.Timeouts.Create(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Create API call logic
forwardingRules := []binarylane.ForwardingRule{}
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, &forwardingRules, false)
forwardingRules := []binarylane.ForwardingRuleRequest{}
diags = data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, &forwardingRules, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -129,7 +184,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
}
if data.HealthCheck.Path.ValueStringPointer() != nil || data.HealthCheck.Protocol.ValueStringPointer() != nil {
body.HealthCheck = &binarylane.HealthCheck{
body.HealthCheck = &binarylane.HealthCheckRequest{
Path: data.HealthCheck.Path.ValueStringPointer(),
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
}
@@ -154,7 +209,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
return
}
diags = setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -163,60 +218,20 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
// Wait for server to be ready
var createActionId int64
for _, action := range *lbResp.JSON200.Links.Actions {
if *action.Rel == "create_load_balancer" {
createActionId = *action.Id
// Wait for load balancer to be ready
var actionId int64
for _, action := range lbResp.JSON200.Links.Actions {
if action.Rel == "create_load_balancer" {
actionId = action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for load balancer to be created, links.actions with rel=create_load_balancer missing from response",
fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body))
err = r.waitForAction(ctx, actionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for load balancer to be created", err.Error())
return
}
createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, createTimeout)
defer cancel()
var lastReadyResp *binarylane.GetActionsActionIdResponse
retryLoop:
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return
}
resp.Diagnostics.AddError(
"Timed out waiting for load balancer to be created",
fmt.Sprintf(
"Timed out waiting for load balancer %s to be created, last response was status=%s, body: %s",
data.Name.ValueString(),
lastReadyResp.Status(),
lastReadyResp.Body,
),
)
return
default:
readyResp, err := r.bc.client.GetActionsActionIdWithResponse(ctx, createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for load balancer to be created", err.Error())
return
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
break retryLoop
}
lastReadyResp = readyResp
tflog.Debug(ctx, fmt.Sprintf("Waiting for load balancer to be created: name=%s, status=%s, details: %s", data.Name.ValueString(), readyResp.Status(), readyResp.Body))
}
time.Sleep(time.Second * 5)
}
}
func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
@@ -253,7 +268,7 @@ func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadReques
return
}
diags := setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -274,7 +289,7 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
}
// Update API call logic
forwardingRules := &[]binarylane.ForwardingRule{}
forwardingRules := &[]binarylane.ForwardingRuleRequest{}
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, forwardingRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -293,13 +308,18 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
body := binarylane.UpdateLoadBalancerRequest{
Name: data.Name.ValueString(),
ForwardingRules: forwardingRules,
HealthCheck: &binarylane.HealthCheck{
HealthCheck: &binarylane.HealthCheckRequest{
Path: data.HealthCheck.Path.ValueStringPointer(),
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
},
ServerIds: serverIds,
}
timeout, diags := data.Timeouts.Update(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
lbResp, err := r.bc.client.PutLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64(), body)
if err != nil {
resp.Diagnostics.AddError(
@@ -317,12 +337,27 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
return
}
diags = setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Wait for load balancer to be ready
var actionId int64
for _, action := range lbResp.JSON200.Links.Actions {
if action.Rel == "update_load_balancer" {
actionId = action.Id
break
}
}
err = r.waitForAction(ctx, actionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for load balancer to be updated", err.Error())
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
@@ -394,15 +429,15 @@ func (r *loadBalancerResource) ImportState(
return
}
loadBalancers := *lbResp.JSON200.LoadBalancers
loadBalancers := lbResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if *lb.Name == req.ID {
if lb.Name == req.ID {
loadBalancer = lb
nextPage = false
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
break
}
@@ -410,44 +445,33 @@ func (r *loadBalancerResource) ImportState(
page++
}
if loadBalancer.Id == nil {
resp.Diagnostics.AddError(
"Could not find load balancer by name",
fmt.Sprintf("Error finding load balancer: name=%s", req.ID),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), *loadBalancer.Id)
diags := resp.State.SetAttribute(ctx, path.Root("id"), loadBalancer.Id)
resp.Diagnostics.Append(diags...)
}
func setLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancerModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
func setLoadBalancerModelState(ctx context.Context, data *loadBalancerDataModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
var diags, diag diag.Diagnostics
data.Id = types.Int64Value(*lb.Id)
data.Name = types.StringValue(*lb.Name)
data.Id = types.Int64Value(lb.Id)
data.Name = types.StringValue(lb.Name)
data.Ip = types.StringValue(lb.Ip)
if lb.Region == nil {
data.Region = types.StringNull()
} else {
data.Region = types.StringValue(*lb.Region.Slug)
data.Region = types.StringValue(lb.Region.Slug)
}
data.ServerIds, diags = types.ListValueFrom(ctx, types.Int64Type, lb.ServerIds)
data.ServerIds, diags = types.SetValueFrom(ctx, types.Int64Type, lb.ServerIds)
if lb.HealthCheck == nil {
data.HealthCheck = resources.NewHealthCheckValueNull()
} else {
data.HealthCheck, diag = resources.NewHealthCheckValue(
resources.HealthCheckValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"path": types.StringValue(*lb.HealthCheck.Path),
"protocol": types.StringValue(*lb.HealthCheck.Protocol),
},
)
diags.Append(diag...)
}
data.HealthCheck, diag = resources.NewHealthCheckValue(
resources.HealthCheckValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"path": types.StringValue(lb.HealthCheck.Path),
"protocol": types.StringValue(lb.HealthCheck.Protocol),
},
)
diags.Append(diag...)
data.ForwardingRules, diag = types.ListValueFrom(ctx,
types.ObjectType{AttrTypes: resources.ForwardingRulesValue{}.AttributeTypes(ctx)},
@@ -457,3 +481,37 @@ func setLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancer
return diags
}
func (r *loadBalancerResource) waitForAction(ctx context.Context, actionId int64) error {
var lastReadyResp *binarylane.GetActionsActionIdResponse
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return fmt.Errorf("timed out waiting for action: action_id=%d", actionId)
} else {
return fmt.Errorf("timed out waiting for action: action_id=%d, last response was status=%s, body: %s",
actionId, lastReadyResp.Status(), lastReadyResp.Body)
}
default:
readyResp, err := r.bc.client.GetActionsActionIdWithResponse(ctx, actionId)
if err != nil {
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 {
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 {
return nil
}
lastReadyResp = readyResp
tflog.Debug(ctx,
fmt.Sprintf("waiting for action for action_id=%d: last response was status=%s, details: %s",
actionId, readyResp.Status(), readyResp.Body,
),
)
}
time.Sleep(time.Second * 5)
}
}
+141 -12
View File
@@ -4,12 +4,15 @@ import (
"context"
"fmt"
"log"
"maps"
"net/http"
"slices"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestLoadBalancerResource(t *testing.T) {
@@ -35,7 +38,7 @@ resource "binarylane_server" "test" {
resource "binarylane_load_balancer" "test" {
name = "tf-test-lb"
server_ids = [binarylane_server.test.0.id, binarylane_server.test.1.id]
forwarding_rules = [{ entry_protocol = "http" }]
# initially, skip defining forwarding_rules, health_check, to test default values
}
data "binarylane_load_balancer" "test" {
@@ -50,20 +53,26 @@ data "binarylane_load_balancer" "test" {
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "server_ids.#", "2"),
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.path", "/"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.protocol", "http"),
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "ip"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "id", "binarylane_load_balancer.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("data.binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "server_ids.#", "2"),
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.path", "/"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.protocol", "http"),
resource.TestCheckResourceAttrSet("data.binarylane_load_balancer.test", "ip"),
),
},
// Test import by ID
@@ -79,6 +88,64 @@ data "binarylane_load_balancer" "test" {
ImportStateId: "tf-test-lb",
ImportStateVerify: true,
},
{
Taint: []string{"binarylane_server.test[0]"},
Config: providerConfig + `
resource "binarylane_server" "test" {
count = 2
name = "tf-test-lb-server-${count.index}"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
public_ipv4_count = 1
}
resource "binarylane_load_balancer" "test" {
name = "tf-test-lb"
server_ids = [binarylane_server.test.0.id, binarylane_server.test.1.id]
# updated
forwarding_rules = [{ entry_protocol = "https" }]
health_check = {
path = "/test-health"
protocol = "https"
}
}
data "binarylane_load_balancer" "test" {
depends_on = [binarylane_load_balancer.test]
id = binarylane_load_balancer.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "id"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "server_ids.#", "2"),
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "https"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.path", "/test-health"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.protocol", "https"),
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "ip"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "id", "binarylane_load_balancer.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("data.binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "server_ids.#", "2"),
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "https"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.path", "/test-health"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.protocol", "https"),
resource.TestCheckResourceAttrSet("data.binarylane_load_balancer.test", "ip"),
),
},
},
})
}
@@ -115,20 +182,20 @@ func init() {
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 {
if strings.HasPrefix(*lb.Name, "tf-test-") {
deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, *lb.Id)
if strings.HasPrefix(lb.Name, "tf-test-") {
deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, lb.Id)
if err != nil {
return fmt.Errorf("Error deleting load balancer %d for test sweep: %w", *lb.Id, err)
return fmt.Errorf("Error deleting load balancer %d for test sweep: %w", lb.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting load balancer %d in test sweep: %s", deleteResp.StatusCode(), *lb.Id, deleteResp.Body)
return fmt.Errorf("Unexpected status %d deleting load balancer %d in test sweep: %s", deleteResp.StatusCode(), lb.Id, deleteResp.Body)
}
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
break
}
@@ -139,3 +206,65 @@ func init() {
},
})
}
func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) {
ms := s.RootModule()
rs, ok := ms.Resources[name]
if !ok {
return nil, fmt.Errorf("Not found: %s in %s, resources: %v", name, ms.Path, slices.Collect(maps.Keys(ms.Resources)))
}
is := rs.Primary
if is == nil {
return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path)
}
return is, nil
}
func testCheckIfResourceAttrContainsAttr(collectionKey, collectionPath, otherResourceKey, otherAttrPath string) resource.TestCheckFunc {
return func(s *terraform.State) error {
collectionState, err := primaryInstanceState(s, collectionKey)
if err != nil {
return err
}
elementState, err := primaryInstanceState(s, otherResourceKey)
if err != nil {
return err
}
if collectionKey == otherResourceKey && collectionPath == otherAttrPath {
return fmt.Errorf(
"comparing self: resource %s attribute %s",
collectionKey,
collectionPath,
)
}
vElement, okElement := elementState.Attributes[otherAttrPath]
if !okElement {
return fmt.Errorf("%s: Attribute %q not set, cannot be contained in %q from %s", otherResourceKey, otherAttrPath, collectionPath, collectionKey)
}
vCollection, okCollection := collectionState.Attributes[collectionPath+".#"]
if !okCollection {
return fmt.Errorf("%s: Attribute %q not a collection, cannot contain %q from %s", collectionKey, collectionPath, otherAttrPath, otherResourceKey)
}
for attrKey, attrValue := range collectionState.Attributes {
if strings.HasPrefix(attrKey, collectionPath+".") &&
!(strings.HasSuffix(attrKey, ".%")) &&
!strings.HasSuffix(attrKey, ".#") &&
attrValue == vElement {
return nil
}
}
return fmt.Errorf(
"%s: Attribute '%s' expected to contain %#v, got %#v",
collectionKey,
collectionPath,
vElement,
vCollection)
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *regionsDataSource) Read(ctx context.Context, req datasource.ReadRequest
}
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
break
}
+51 -7
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"slices"
"strings"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -31,6 +32,8 @@ type serverDataModel struct {
resources.ServerModel
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
PublicIpv6Addresses types.List `tfsdk:"public_ipv6_addresses"`
PrivateIpv6Addresses types.List `tfsdk:"private_ipv6_addresses"`
Permalink types.String `tfsdk:"permalink"`
Memory types.Int32 `tfsdk:"memory"`
Disk types.Int32 `tfsdk:"disk"`
@@ -103,20 +106,34 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
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(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
@@ -162,6 +179,33 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
data.PrivateIPv4Addresses = tfPrivateIpv4Addresses
}
publicIpv6Addresses := []string{}
privateIpv6Addresses := []string{}
for _, v6address := range serverResp.JSON200.Server.Networks.V6 {
if v6address.Type == "public" {
publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress)
} else {
privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress)
}
}
tfPublicIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv6Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PublicIpv6Addresses = types.ListUnknown(data.PublicIpv6Addresses.ElementType(ctx))
} else {
data.PublicIpv6Addresses = tfPublicIpv6Addresses
}
tfPrivateIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv6Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PrivateIpv6Addresses = types.ListUnknown(data.PrivateIpv6Addresses.ElementType(ctx))
} else {
data.PrivateIpv6Addresses = tfPrivateIpv6Addresses
}
// Get user data script
userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
@@ -63,7 +63,7 @@ func (r *serverFirewallRulesResource) Schema(ctx context.Context, req resource.S
// Overrides
serverId := resp.Schema.Attributes["server_id"]
resp.Schema.Attributes["server_id"] = &schema.Int64Attribute{
resp.Schema.Attributes["server_id"] = schema.Int64Attribute{
Description: serverId.GetDescription(),
MarkdownDescription: serverId.GetMarkdownDescription(),
Required: true, // Server ID is required to define the firewall rules
@@ -251,7 +251,7 @@ func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.Adva
firewallRulesValue := resources.FirewallRulesValue{}
var firewallRulesValues []resources.FirewallRulesValue
if *firewallRules == nil || len(*firewallRules) == 0 {
if len(*firewallRules) == 0 {
firewallRulesValues = []resources.FirewallRulesValue{}
} else {
for _, rule := range *firewallRules {
+360 -67
View File
@@ -87,7 +87,7 @@ func serverSchema(ctx context.Context) schema.Schema {
// Overrides
id := s.Attributes["id"]
s.Attributes["id"] = &schema.Int64Attribute{
s.Attributes["id"] = schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
@@ -101,7 +101,7 @@ func serverSchema(ctx context.Context) schema.Schema {
imageDescription := "The slug of the selected operating system, such as `debian-12`. You can fetch a full list of images from the BinaryLane API."
image := s.Attributes["image"]
s.Attributes["image"] = &schema.StringAttribute{
s.Attributes["image"] = schema.StringAttribute{
Description: imageDescription,
MarkdownDescription: imageDescription,
Required: image.IsRequired(),
@@ -113,7 +113,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
backupsDescription := "If `true` this will enable two daily backups for the server. By default, backups are disabled."
s.Attributes["backups"] = &schema.BoolAttribute{
s.Attributes["backups"] = schema.BoolAttribute{
Description: backupsDescription,
MarkdownDescription: backupsDescription,
Optional: true,
@@ -122,7 +122,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
user_data := s.Attributes["user_data"]
s.Attributes["user_data"] = &schema.StringAttribute{
s.Attributes["user_data"] = schema.StringAttribute{
Description: user_data.GetDescription(),
MarkdownDescription: user_data.GetMarkdownDescription(),
Optional: true, // Optional as not all servers have an initialization script
@@ -130,7 +130,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
vpcId := s.Attributes["vpc_id"]
s.Attributes["vpc_id"] = &schema.Int64Attribute{
s.Attributes["vpc_id"] = schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Optional: vpcId.IsOptional(),
@@ -138,7 +138,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
portBlocking := s.Attributes["port_blocking"]
s.Attributes["port_blocking"] = &schema.BoolAttribute{
s.Attributes["port_blocking"] = schema.BoolAttribute{
Description: portBlocking.GetDescription(),
MarkdownDescription: portBlocking.GetMarkdownDescription(),
Optional: portBlocking.IsOptional(),
@@ -147,7 +147,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
region := s.Attributes["region"].(schema.StringAttribute)
s.Attributes["region"] = &schema.StringAttribute{
s.Attributes["region"] = schema.StringAttribute{
Description: region.GetDescription(),
MarkdownDescription: region.GetMarkdownDescription(),
Optional: region.IsOptional(),
@@ -160,7 +160,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
sshKeys := s.Attributes["ssh_keys"]
s.Attributes["ssh_keys"] = &schema.ListAttribute{
s.Attributes["ssh_keys"] = schema.ListAttribute{
ElementType: types.Int64Type,
Description: sshKeys.GetMarkdownDescription(),
MarkdownDescription: sshKeys.GetDescription(),
@@ -174,7 +174,7 @@ func serverSchema(ctx context.Context) schema.Schema {
userDataDescription := "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"
userData := s.Attributes["user_data"]
s.Attributes["user_data"] = &schema.StringAttribute{
s.Attributes["user_data"] = schema.StringAttribute{
Description: userDataDescription,
MarkdownDescription: userDataDescription,
Required: userData.IsRequired(),
@@ -186,11 +186,10 @@ func serverSchema(ctx context.Context) schema.Schema {
}
// Additional attributes
pwDescription :=
"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."
s.Attributes["password"] = &schema.StringAttribute{
pwDescription := "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."
s.Attributes["password"] = schema.StringAttribute{
Description: pwDescription,
MarkdownDescription: pwDescription,
Optional: true, // Password optional, if not set will be emailed to user
@@ -199,7 +198,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
publicIpv4CountDescription := "The number of public IPv4 addresses to assign to the server."
s.Attributes["public_ipv4_count"] = &schema.Int32Attribute{
s.Attributes["public_ipv4_count"] = schema.Int32Attribute{
Description: publicIpv4CountDescription,
MarkdownDescription: publicIpv4CountDescription,
Required: true,
@@ -212,7 +211,7 @@ func serverSchema(ctx context.Context) schema.Schema {
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
s.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
s.Attributes["public_ipv4_addresses"] = schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
ElementType: types.StringType,
@@ -229,7 +228,7 @@ func serverSchema(ctx context.Context) schema.Schema {
"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."
s.Attributes["source_and_destination_check"] = &schema.BoolAttribute{
s.Attributes["source_and_destination_check"] = schema.BoolAttribute{
Description: sourceDestCheckDescription,
MarkdownDescription: sourceDestCheckDescription,
Optional: true,
@@ -242,8 +241,22 @@ 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."
s.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
s.Attributes["private_ipv4_addresses"] = schema.ListAttribute{
Description: privateIpv4AddressesDescription,
MarkdownDescription: privateIpv4AddressesDescription,
ElementType: types.StringType,
@@ -256,7 +269,41 @@ func serverSchema(ctx context.Context) schema.Schema {
},
}
s.Attributes["permalink"] = &schema.StringAttribute{
ipv6Description := "If `true` this will add a public and private IPv6 address to the server. By default, IPv6 is disabled."
s.Attributes["ipv6"] = schema.BoolAttribute{
Description: ipv6Description,
MarkdownDescription: ipv6Description,
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false), // Add default to ipv6
}
publicIpv6AddressesDescription := "The public IPv6 addresses assigned to the server."
s.Attributes["public_ipv6_addresses"] = schema.ListAttribute{
Description: publicIpv6AddressesDescription,
MarkdownDescription: publicIpv6AddressesDescription,
ElementType: types.StringType,
// read only
Optional: false,
Required: false,
Computed: true,
}
privateIpv6AddressesDescription := "The private IPv6 addresses assigned to the server."
s.Attributes["private_ipv6_addresses"] = schema.ListAttribute{
Description: privateIpv6AddressesDescription,
MarkdownDescription: privateIpv6AddressesDescription,
ElementType: types.StringType,
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
}
s.Attributes["permalink"] = schema.StringAttribute{
Description: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
MarkdownDescription: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
// read only
@@ -271,7 +318,7 @@ func serverSchema(ctx context.Context) schema.Schema {
pwChangeDescription := "If this is true then the `password` attribute can be changed with Terraform. " +
"If this is false then the `password` attribute can only be replaced with a null/empty value, which will clear " +
"the root/administrator password allowing the password to be changed via the web console."
s.Attributes["password_change_supported"] = &schema.BoolAttribute{
s.Attributes["password_change_supported"] = schema.BoolAttribute{
Description: pwChangeDescription,
MarkdownDescription: pwChangeDescription,
// read only
@@ -293,7 +340,7 @@ func serverSchema(ctx context.Context) schema.Schema {
- \> 16384 MB must be a multiple of 2048
- \> 24576 MB must be a multiple of 4096`
s.Attributes["memory"] = &schema.Int32Attribute{
s.Attributes["memory"] = schema.Int32Attribute{
Description: memoryDescription + memoryValidValues,
MarkdownDescription: memoryDescription + memoryValidValuesMarkdown,
Optional: true,
@@ -316,7 +363,7 @@ func serverSchema(ctx context.Context) schema.Schema {
- \> 60 GB must be a multiple of 10
- \> 200 GB must be a multiple of 100`
s.Attributes["disk"] = &schema.Int32Attribute{
s.Attributes["disk"] = schema.Int32Attribute{
Description: diskDescription + diskValidValues,
MarkdownDescription: diskDescription + diskValidValuesMarkdown,
Optional: true,
@@ -330,11 +377,29 @@ func serverSchema(ctx context.Context) schema.Schema {
},
}
s.Attributes["timeouts"] =
timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
Update: true,
})
s.Attributes["timeouts"] = timeouts.Attributes(ctx, timeouts.Opts{
Create: 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
}
@@ -367,6 +432,24 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
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() {
// Creation plan, no further modification needed
return
@@ -378,7 +461,10 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
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))
}
@@ -414,6 +500,16 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
)
}
if !plan.Ipv6.Equal(state.Ipv6) {
if plan.Ipv6.ValueBool() {
plan.PublicIpv6Addresses = types.ListUnknown(state.PublicIpv6Addresses.ElementType(ctx))
plan.PrivateIpv6Addresses = types.ListUnknown(state.PrivateIpv6Addresses.ElementType(ctx))
} else {
plan.PublicIpv6Addresses = types.ListNull(state.PublicIpv6Addresses.ElementType(ctx))
plan.PrivateIpv6Addresses = types.ListNull(state.PrivateIpv6Addresses.ElementType(ctx))
}
}
// Use state for unknown disk/memory values, as long as server size is the same
if (plan.Memory.IsNull() || plan.Memory.IsUnknown()) && plan.Size.Equal(state.Size) {
plan.Memory = state.Memory
@@ -437,7 +533,7 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
debugAdvFeatResp, _ := json.Marshal(advFeatResp.JSON200)
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") {
resp.Diagnostics.AddAttributeError(
@@ -526,7 +622,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
Options: &binarylane.SizeOptionsRequest{
Ipv4Addresses: data.PublicIpv4Count.ValueInt32Pointer(),
},
Backups: data.Backups.ValueBoolPointer(),
Backups: data.Backups.ValueBoolPointer(),
Ipv6: data.Ipv6.ValueBoolPointer(),
SeparatePrivateNetworkInterface: data.SeparatePrivateNetworkInterface.ValueBoolPointer(),
}
if !data.Memory.IsNull() && !data.Memory.IsUnknown() {
@@ -535,7 +633,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
if !data.Disk.IsNull() && !data.Disk.IsUnknown() {
body.Options.Disk = data.Disk.ValueInt32Pointer()
}
if !data.VpcIpv4Address.IsNull() && !data.VpcIpv4Address.IsUnknown() {
body.VpcIpv4Address = data.VpcIpv4Address.ValueStringPointer()
}
if data.Password.IsNull() {
data.Password = types.StringNull()
} else {
@@ -561,9 +661,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
for _, action := range serverResp.JSON200.Links.Actions {
if action.Rel == "create" {
createActionId = action.Id
break
}
}
@@ -573,28 +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))
return
}
err = r.waitForServerAction(ctx, *serverResp.JSON200.Server.Id, createActionId)
err = r.waitForServerAction(ctx, serverResp.JSON200.Server.Id, createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be created", err.Error())
}
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
data.PasswordChangeSupported = types.BoolValue(serverResp.JSON200.Server.PasswordChangeSupported)
data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
plannedSourceDestCheck := data.SourceAndDestinationCheck
serverRespSourceDestCheck := types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
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(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
@@ -784,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))
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 {
resp.Diagnostics.AddError("Error waiting for server to change network", err.Error())
return
@@ -797,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
if !plan.Size.Equal(state.Size) ||
!plan.Memory.IsNull() && !plan.Memory.IsUnknown() && !plan.Memory.Equal(state.Memory) ||
@@ -873,7 +1036,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
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 {
resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error())
return
@@ -890,6 +1053,43 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
}
// Change IPv6
if !plan.Ipv6.Equal(state.Ipv6) {
ipv6Resp, err := r.bc.client.PostServersServerIdActionsChangeIpv6WithResponse(
ctx,
state.Id.ValueInt64(),
binarylane.PostServersServerIdActionsChangeIpv6JSONRequestBody{
Type: "change_ipv6",
Enabled: plan.Ipv6.ValueBool(),
},
)
if err != nil {
resp.Diagnostics.AddError("Error changing IPv6", err.Error())
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), ipv6Resp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for changing IPv6", err.Error())
return
}
state.Ipv6 = plan.Ipv6
state.PublicIpv6Addresses = plan.PublicIpv6Addresses
state.PrivateIpv6Addresses = plan.PrivateIpv6Addresses
if state.PublicIpv6Addresses.IsUnknown() || state.PrivateIpv6Addresses.IsUnknown() || listContainsUnknown(ctx, state.PublicIpv6Addresses) || listContainsUnknown(ctx, state.PrivateIpv6Addresses) {
refreshNeeded = true
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}
// Rebuild operation
if !plan.SshKeys.Equal(state.SshKeys) || !plan.UserData.IsNull() && !plan.UserData.Equal(state.UserData) {
var rebuildReq *binarylane.PostServersServerIdActionsRebuildJSONRequestBody
@@ -927,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))
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 {
resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error())
return
@@ -955,7 +1155,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error resetting password", err.Error())
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 {
resp.Diagnostics.AddError("Error waiting for password reset", err.Error())
return
@@ -999,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
if !plan.Backups.Equal(state.Backups) {
if plan.Backups.ValueBool() {
@@ -1009,7 +1228,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error enabling backups", err.Error())
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 {
resp.Diagnostics.AddError("Error waiting for backups to be enabled", err.Error())
return
@@ -1022,7 +1241,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error disabling backups", err.Error())
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 {
resp.Diagnostics.AddError("Error waiting for backups to be disabled", err.Error())
return
@@ -1049,7 +1268,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error changing \"port_blocking\" attribute", err.Error())
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 {
resp.Diagnostics.AddError("Error waiting for \"port_blocking\" attribute to change", err.Error())
return
@@ -1127,8 +1346,8 @@ func (r *serverResource) ImportState(
return
}
servers := *serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return *s.Name == name })
servers := serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return s.Name == name })
if idx == -1 {
resp.Diagnostics.AddError(
"Could not find server by hostname",
@@ -1138,7 +1357,7 @@ func (r *serverResource) ImportState(
}
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...)
}
@@ -1159,8 +1378,8 @@ func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64
if err != nil {
return fmt.Errorf("unexpected error waiting for server action: server_id=%d, action_id=%d, error: %w", serverId, actionId, err)
}
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored {
return fmt.Errorf("server action failed to with error: server_id=%d, action_id=%d, error: %s", serverId, actionId, *readyResp.JSON200.Action.ResultData)
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.Status == binarylane.Errored {
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
@@ -1215,7 +1434,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)
}
err = r.waitForServerAction(ctx, serverId, *sourceDestCheckResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, serverId, sourceDestCheckResp.JSON200.Action.Id)
if err != nil {
return fmt.Errorf("error changing source and destination check: %w", err)
}
@@ -1223,6 +1442,37 @@ func (r *serverResource) updateSourceDestCheck(
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 {
var diags diag.Diagnostics
@@ -1244,22 +1494,36 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
}
}
state.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
state.Name = types.StringValue(*serverResp.JSON200.Server.Name)
state.Id = types.Int64Value(serverResp.JSON200.Server.Id)
state.Name = types.StringValue(serverResp.JSON200.Server.Name)
state.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
state.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
state.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
state.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
state.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
state.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
state.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
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.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
state.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
state.SeparatePrivateNetworkInterface = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SeparatePrivateNetworkInterface)
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.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(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
@@ -1282,9 +1546,10 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
switch v4address.Type {
case "public":
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else if v4address.Type == "private" {
case "private":
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
@@ -1307,6 +1572,34 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
state.PrivateIPv4Addresses = tfPrivateIpv4Addresses
}
publicIpv6Addresses := []string{}
privateIpv6Addresses := []string{}
for _, v6address := range serverResp.JSON200.Server.Networks.V6 {
switch v6address.Type {
case "public":
publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress)
case "private":
privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress)
}
}
tfPublicIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv6Addresses)
diags.Append(diag...)
if diag.HasError() || tfPublicIpv6Addresses.IsNull() {
state.PublicIpv6Addresses = types.ListUnknown(state.PublicIpv6Addresses.ElementType(ctx))
} else {
state.PublicIpv6Addresses = tfPublicIpv6Addresses
}
tfPrivateIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv6Addresses)
diags.Append(diag...)
if diag.HasError() || tfPrivateIpv6Addresses.IsNull() {
state.PrivateIpv6Addresses = types.ListUnknown(state.PrivateIpv6Addresses.ElementType(ctx))
} else {
state.PrivateIpv6Addresses = tfPrivateIpv6Addresses
}
return diags
}
@@ -1395,7 +1688,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)
}
err = r.waitForServerAction(ctx, serverId, *resp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, serverId, resp.JSON200.Action.Id)
if err != nil {
return fmt.Errorf("failed to confirm advanced features for server was successful: %w", err)
}
+170 -8
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"log"
"net/http"
"regexp"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
@@ -57,6 +58,7 @@ resource "binarylane_server" "test" {
public_ipv4_count = 1
ssh_keys = [binarylane_ssh_key.initial.id]
source_and_destination_check = false
separate_private_network_interface = true
backups = true
port_blocking = false
user_data = <<EOT
@@ -81,6 +83,7 @@ data "binarylane_server" "test" {
resource.TestCheckResourceAttr("binarylane_server.test", "memory", "1152"),
resource.TestCheckResourceAttr("binarylane_server.test", "disk", "20"),
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", "password", password1),
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.TestCheckResourceAttrSet("binarylane_server.test", "permalink"),
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", "advanced_features.emulated_hyperv", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_devices", "false"),
@@ -104,6 +108,8 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.cloud_init", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.qemu_guest_agent", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.uefi_boot", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "ipv6", "false"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv6_addresses.#", "0"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "id", "binarylane_server.test", "id"),
@@ -112,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", "size", "std-min"),
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.TestCheckResourceAttr("data.binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
@@ -120,6 +127,8 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "disk", "20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "backups", "true"),
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_devices", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.nested_virt", "false"),
@@ -130,6 +139,8 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.cloud_init", "true"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.qemu_guest_agent", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.uefi_boot", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "ipv6", "false"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "public_ipv6_addresses.#", "0"),
),
},
// Test import by ID
@@ -180,6 +191,7 @@ resource "binarylane_server" "test" {
advanced_features = {
emulated_hyperv = true
}
ipv6 = true
# source_and_destination_check = null # defaults to null
# backups = false # defaults to false
@@ -206,6 +218,7 @@ EOT
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "true"),
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", "port_blocking", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
@@ -214,6 +227,9 @@ echo "Hello Whitespace" > /var/tmp/output.txt
`),
resource.TestCheckResourceAttr("binarylane_server.test", "ipv6", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv6_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv6_addresses.#", "1"),
),
},
// Change password testing (Cannot run at same time as Rebuild operation, so it has it's own test)
@@ -313,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 {
t.Helper()
pwBytes := make([]byte, 12)
@@ -328,7 +491,6 @@ func init() {
Name: "server",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
@@ -354,25 +516,25 @@ func init() {
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 {
if strings.HasPrefix(*s.Name, "tf-test-") {
if strings.HasPrefix(s.Name, "tf-test-") {
reason := "Terraform deletion"
params := binarylane.DeleteServersServerIdParams{
Reason: &reason,
}
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, &params)
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, s.Id, &params)
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 {
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
break
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *sizesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
}
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
break
}
+16 -5
View File
@@ -72,8 +72,17 @@ func (d *sshKeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequ
}
// Additional attributes
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
"such as one generated by `ssh-keygen`."
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
Description: publicKeyDescription,
MarkdownDescription: publicKeyDescription,
CustomType: TrimmedStringType{},
Computed: true,
}
fingerprintDescription := "The fingerprint of the SSH key."
resp.Schema.Attributes["fingerprint"] = &schema.StringAttribute{
resp.Schema.Attributes["fingerprint"] = schema.StringAttribute{
Description: fingerprintDescription,
MarkdownDescription: fingerprintDescription,
Optional: false,
@@ -109,11 +118,13 @@ func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = types.StringValue(*sshResp.JSON200.SshKey.PublicKey)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
}
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+56 -38
View File
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"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/types"
@@ -35,7 +36,8 @@ type sshKeyResource struct {
type sshKeyModel struct {
resources.SshKeyModel
Fingerprint types.String `tfsdk:"fingerprint"`
Fingerprint types.String `tfsdk:"fingerprint"`
PublicKey TrimmedStringValue `tfsdk:"public_key"`
}
func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -71,11 +73,15 @@ func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest,
Computed: true,
Description: idDescription,
MarkdownDescription: idDescription,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
}
defaultDescription := "If `true`, this SSH key will be included on all new server installations (if the operating " +
"system supports SSH key injection). Default is `false`."
resp.Schema.Attributes["default"] = schema.BoolAttribute{
Required: false,
Optional: true,
Computed: true,
Description: defaultDescription,
@@ -83,22 +89,31 @@ func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest,
Default: booldefault.StaticBool(false), // Add default to backups
}
public_key := resp.Schema.Attributes["public_key"]
// Additional attributes
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
"such as one generated by `ssh-keygen`."
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
Required: true,
Description: public_key.GetDescription(),
MarkdownDescription: public_key.GetMarkdownDescription(),
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
Optional: false,
Computed: false,
Description: publicKeyDescription,
MarkdownDescription: publicKeyDescription,
PlanModifiers: []planmodifier.String{
RequiresReplaceForTrimmedStringModifier{},
},
CustomType: TrimmedStringType{}, // Ignore leading/trailing whitespace
}
// Additional attributes
fingerprintDescription := "The fingerprint of the SSH key."
resp.Schema.Attributes["fingerprint"] = &schema.StringAttribute{
resp.Schema.Attributes["fingerprint"] = schema.StringAttribute{
Description: fingerprintDescription,
MarkdownDescription: fingerprintDescription,
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
}
}
@@ -138,8 +153,8 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
}
// Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
if resp.Diagnostics.HasError() {
return
@@ -154,9 +169,6 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
data.PublicKey = types.StringValue(data.PublicKey.ValueString())
if resp.Diagnostics.HasError() {
return
}
@@ -179,11 +191,13 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
}
// Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = types.StringValue(*sshResp.JSON200.SshKey.PublicKey)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
}
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -205,27 +219,31 @@ func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest,
}
// Update API call logic
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
Name: plan.Name.ValueString(),
Default: plan.Default.ValueBoolPointer(),
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating SSH Key: name=%s", plan.Name.ValueString()),
err.Error(),
)
return
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating SSH Key",
fmt.Sprintf("Received %s updating new SSH Key: name=%s. Details: %s", sshResp.Status(), plan.Name.ValueString(), sshResp.Body),
)
return
if !state.Name.Equal(plan.Name) || !state.Default.Equal(plan.Default) {
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
Name: plan.Name.ValueString(),
Default: plan.Default.ValueBoolPointer(),
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating SSH Key: name=%s", plan.Name.ValueString()),
err.Error(),
)
return
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating SSH Key",
fmt.Sprintf("Received %s updating new SSH Key: name=%s. Details: %s", sshResp.Status(), plan.Name.ValueString(), sshResp.Body),
)
return
}
state.Name = plan.Name
state.Default = plan.Default
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
@@ -294,13 +312,13 @@ func (r *sshKeyResource) ImportState(
sshKeys := sshResp.JSON200.SshKeys
for _, key := range sshKeys {
if *key.Fingerprint == req.ID {
if key.Fingerprint == req.ID {
sshKey = key
nextPage = false
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
break
}
@@ -308,7 +326,7 @@ func (r *sshKeyResource) ImportState(
page++
}
if sshKey.Id == nil {
if sshKey.Id == 0 {
resp.Diagnostics.AddError(
"Could not find SSH key by fingerprint",
fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID),
@@ -316,6 +334,6 @@ func (r *sshKeyResource) ImportState(
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...)
}
+25 -22
View File
@@ -12,6 +12,7 @@ import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
@@ -40,12 +41,14 @@ data "binarylane_ssh_key" "test" {
// Verify resource values
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "public_key", publicKey),
resource.TestCheckResourceAttrSet("binarylane_ssh_key.test", "fingerprint"),
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "default", "false"),
resource.TestCheckResourceAttrSet("binarylane_ssh_key.test", "id"),
// Verify data source values
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "public_key"), // Ideally would check this is identical, but whitespace is not preserved
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "public_key", publicKey),
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "fingerprint"),
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "false"),
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "id"),
),
@@ -64,22 +67,22 @@ data "binarylane_ssh_key" "test" {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{}, // nothing to ignore
},
// TODO: Update and Read testing
// {
// Config: providerConfig + `
// resource "binarylane_ssh_key" "test" {
// name = "tf-test-key-resource-test"
// public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal"
// default = true
// }
// `,
// Check: resource.ComposeAggregateTestCheckFunc(
// // Verify resource values
// resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
// resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "true"),
// ),
// },
// Delete testing automatically occurs in TestCase
// Whitespace changes should not recreate the resource
{
Config: `
resource "binarylane_ssh_key" "test" {
name = "tf-test-key-resource-test"
# Test public key with a newline to ensure it does not get recreated
public_key = <<EOT
` + publicKey + `
EOT
}`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
})
}
@@ -148,17 +151,17 @@ func init() {
for _, k := range keys {
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 {
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 {
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
break
}
@@ -0,0 +1,165 @@
package provider
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
// TrimmedStringType
// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringTypable = TrimmedStringType{}
type TrimmedStringType struct {
basetypes.StringType
}
func (t TrimmedStringType) Equal(o attr.Type) bool {
other, ok := o.(TrimmedStringType)
if !ok {
return false
}
return t.StringType.Equal(other.StringType)
}
func (t TrimmedStringType) String() string {
return "TrimmedStringType"
}
func (t TrimmedStringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
value := TrimmedStringValue{
StringValue: in,
}
return value, nil
}
func (t TrimmedStringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)
if err != nil {
return nil, err
}
stringValue, ok := attrValue.(basetypes.StringValue)
if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}
stringValuable, diags := t.ValueFromString(ctx, stringValue)
if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
}
return stringValuable, nil
}
func (t TrimmedStringType) ValueType(ctx context.Context) attr.Value {
return TrimmedStringValue{}
}
// TrimmedStringValue
var _ basetypes.StringValuable = TrimmedStringValue{}
var _ basetypes.StringValuableWithSemanticEquals = TrimmedStringValue{}
type TrimmedStringValue struct {
basetypes.StringValue
}
func (v TrimmedStringValue) Equal(o attr.Value) bool {
other, ok := o.(TrimmedStringValue)
if !ok {
return false
}
return v.StringValue.Equal(other.StringValue)
}
func (v TrimmedStringValue) Type(ctx context.Context) attr.Type {
return TrimmedStringType{}
}
func (v TrimmedStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
var diags diag.Diagnostics
// The framework should always pass the correct value type, but always check
newValue, ok := newValuable.(TrimmedStringValue)
if !ok {
diags.AddError(
"Semantic Equality Check Error",
"An unexpected value type was received while performing semantic equality checks. "+
"Please report this to the provider developers.\n\n"+
"Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
"Got Value Type: "+fmt.Sprintf("%T", newValuable),
)
return false, diags
}
priorString := strings.TrimSpace(v.StringValue.ValueString())
newString := strings.TrimSpace(newValue.ValueString())
// If the strings are equivalent, keep the prior value
return priorString == newString, diags
}
// PlanModifier
var _ planmodifier.String = RequiresReplaceForTrimmedStringModifier{}
type RequiresReplaceForTrimmedStringModifier struct{}
func (m RequiresReplaceForTrimmedStringModifier) Description(_ context.Context) string {
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
}
func (m RequiresReplaceForTrimmedStringModifier) MarkdownDescription(_ context.Context) string {
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
}
func (m RequiresReplaceForTrimmedStringModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
// Do not replace on resource creation.
if req.State.Raw.IsNull() {
return
}
// Do not replace on resource destroy.
if req.Plan.Raw.IsNull() {
return
}
// Do not replace if the plan and state values are equal.
if req.PlanValue.Equal(req.StateValue) {
return
}
// Create Normalized values for the state and plan.
stateNormalized := TrimmedStringValue{
StringValue: req.StateValue,
}
planNormalized := TrimmedStringValue{
StringValue: req.PlanValue,
}
// Perform semantic equality check.
semanticallyEqual, diags := stateNormalized.StringSemanticEquals(ctx, planNormalized)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
if semanticallyEqual {
tflog.Info(ctx, "Plan and state values are semantically equal. Suppressing differences.")
resp.PlanValue = req.StateValue
} else {
resp.RequiresReplace = true
}
}
+41 -2
View File
@@ -127,6 +127,45 @@ func convertResourceAttrToDataSourceAttr(name string, attribute r_schema.Attribu
}, nil
default:
if t, isSet := attribute.(r_schema.SetNestedAttribute); isSet {
nestedObjectAttrs := make(map[string]d_schema.Attribute)
for name, attribute := range t.NestedObject.Attributes {
nestedAttribute, err := convertResourceAttrToDataSourceAttr(name, attribute, required, optional)
if err != nil {
return nil, err
}
nestedObjectAttrs[name] = nestedAttribute
}
return d_schema.SetNestedAttribute{
NestedObject: d_schema.NestedAttributeObject{
Attributes: nestedObjectAttrs,
CustomType: t.NestedObject.CustomType,
Validators: t.NestedObject.Validators,
},
CustomType: t.CustomType,
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}, nil
}
if t, isSet := attribute.GetType().(types.SetType); isSet {
return d_schema.SetAttribute{
ElementType: t.ElemType,
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}, nil
}
if t, isList := attribute.(r_schema.ListNestedAttribute); isList {
nestedObjectAttrs := make(map[string]d_schema.Attribute)
for name, attribute := range t.NestedObject.Attributes {
@@ -136,13 +175,13 @@ func convertResourceAttrToDataSourceAttr(name string, attribute r_schema.Attribu
}
nestedObjectAttrs[name] = nestedAttribute
}
return d_schema.ListNestedAttribute{
NestedObject: d_schema.NestedAttributeObject{
Attributes: nestedObjectAttrs,
CustomType: t.NestedObject.CustomType,
Validators: t.NestedObject.Validators,
},
CustomType: t.CustomType,
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
@@ -165,6 +204,7 @@ func convertResourceAttrToDataSourceAttr(name string, attribute r_schema.Attribu
DeprecationMessage: attribute.GetDeprecationMessage(),
}, nil
}
if t, isObject := attribute.(r_schema.SingleNestedAttribute); isObject {
attributeTypes := make(map[string]attr.Type, len(t.GetAttributes()))
for name, attribute := range t.GetAttributes() {
@@ -183,7 +223,6 @@ func convertResourceAttrToDataSourceAttr(name string, attribute r_schema.Attribu
AttributeTypes: attributeTypes,
}, nil
}
if t, isObject := attribute.GetType().(types.ObjectType); isObject {
return d_schema.ObjectAttribute{
AttributeTypes: t.AttrTypes,
+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))
return
}
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+24 -8
View File
@@ -12,6 +12,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
@@ -60,7 +63,7 @@ func (r *vpcResource) Schema(ctx context.Context, req resource.SchemaRequest, re
// Overrides
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
resp.Schema.Attributes["id"] = schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
@@ -68,6 +71,19 @@ func (r *vpcResource) Schema(ctx context.Context, req resource.SchemaRequest, re
Required: false,
Computed: true,
}
ipRange := resp.Schema.Attributes["ip_range"]
resp.Schema.Attributes["ip_range"] = schema.StringAttribute{
Description: ipRange.GetDescription(),
MarkdownDescription: ipRange.GetMarkdownDescription(),
Required: false,
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Default: stringdefault.StaticString("10.240.0.0/16"),
}
}
func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -104,8 +120,8 @@ func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, re
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -141,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))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -187,8 +203,8 @@ func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, re
return
}
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
state.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+7 -7
View File
@@ -156,20 +156,20 @@ func init() {
}
vpcs := listResp.JSON200.Vpcs
for _, vpc := range *vpcs {
if strings.HasPrefix(*vpc.Name, "tf-test-") {
for _, vpc := range vpcs {
if strings.HasPrefix(vpc.Name, "tf-test-") {
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, *vpc.Id)
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, vpc.Id)
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 {
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
break
}
@@ -47,7 +47,7 @@ func (d *vpcRouteEntriesDataSource) Schema(ctx context.Context, req datasource.S
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
resp.Schema.Attributes["vpc_id"] = schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // vpc_id is required to define the route entries
@@ -55,7 +55,7 @@ func (d *vpcRouteEntriesDataSource) Schema(ctx context.Context, req datasource.S
vpcRouteEntries := resp.Schema.Attributes["route_entries"].(schema.ListNestedAttribute)
vpcRouteEntriesDescription := "The route entries that control how network traffic is directed through the VPC environment."
resp.Schema.Attributes["route_entries"] = &schema.ListNestedAttribute{
resp.Schema.Attributes["route_entries"] = schema.ListNestedAttribute{
Description: vpcRouteEntriesDescription,
NestedObject: schema.NestedAttributeObject{
CustomType: vpcRouteEntries.NestedObject.CustomType,
@@ -93,7 +93,7 @@ func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.Rea
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -48,7 +48,7 @@ func (r *vpcRouteEntriesResource) Schema(ctx context.Context, req resource.Schem
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
resp.Schema.Attributes["vpc_id"] = schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // vpc_id is required to define the route entries
@@ -94,7 +94,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -136,7 +136,7 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -183,7 +183,7 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -246,15 +246,15 @@ func GetRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteE
routeEntriesValue := resources.RouteEntriesValue{}
var routeEntriesValues []resources.RouteEntriesValue
if *routeEntries == nil || len(*routeEntries) == 0 {
if len(*routeEntries) == 0 {
routeEntriesValues = []resources.RouteEntriesValue{}
} else {
for _, route := range *routeEntries {
// TODO: This panics if invalid, should be handled better
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringValue(*route.Description),
"destination": types.StringValue(*route.Destination),
"router": types.StringValue(*route.Router),
"destination": types.StringValue(route.Destination),
"router": types.StringValue(route.Router),
})
routeEntriesValues = append(routeEntriesValues, r)
}
@@ -26,8 +26,8 @@ func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
Attributes: map[string]schema.Attribute{
"entry_protocol": schema.StringAttribute{
Required: true,
Description: "The protocol that traffic must match for the load balancer to forward it. Valid values are \"http\" and \"https\".",
MarkdownDescription: "The protocol that traffic must match for the load balancer to forward it. Valid values are \"http\" and \"https\".",
Description: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n",
MarkdownDescription: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n",
Validators: []validator.String{
stringvalidator.OneOf(
"http",
@@ -55,6 +55,7 @@ func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
Description: "Leave null to accept the default '/' path.",
MarkdownDescription: "Leave null to accept the default '/' path.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(regexp.MustCompile("/[A-Za-z0-9/.?=&+%_-]*"), ""),
},
},
@@ -95,7 +96,7 @@ func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
Description: "Leave null to create an anycast load balancer.",
MarkdownDescription: "Leave null to create an anycast load balancer.",
},
"server_ids": schema.ListAttribute{
"server_ids": schema.SetAttribute{
ElementType: types.Int64Type,
Optional: true,
Computed: true,
@@ -112,7 +113,7 @@ type LoadBalancerModel struct {
Id types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Region types.String `tfsdk:"region"`
ServerIds types.List `tfsdk:"server_ids"`
ServerIds types.Set `tfsdk:"server_ids"`
}
var _ basetypes.ObjectTypable = ForwardingRulesType{}
+34 -13
View File
@@ -105,6 +105,12 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "The slug of the selected operating system.",
MarkdownDescription: "The slug of the selected operating system.",
},
"ipv6": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "If true this will enable IPv6 for this server.",
MarkdownDescription: "If true this will enable IPv6 for this server.",
},
"name": schema.StringAttribute{
Optional: true,
Computed: true,
@@ -114,8 +120,8 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
"port_blocking": schema.BoolAttribute{
Optional: 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.",
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.",
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 verified accounts.",
},
"region": schema.StringAttribute{
Required: true,
@@ -125,6 +131,12 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
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{
Required: true,
Description: "The slug of the selected size.",
@@ -155,22 +167,31 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "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 {
AdvancedFeatures AdvancedFeaturesValue `tfsdk:"advanced_features"`
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Name types.String `tfsdk:"name"`
PortBlocking types.Bool `tfsdk:"port_blocking"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
SshKeys types.List `tfsdk:"ssh_keys"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
AdvancedFeatures AdvancedFeaturesValue `tfsdk:"advanced_features"`
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Ipv6 types.Bool `tfsdk:"ipv6"`
Name types.String `tfsdk:"name"`
PortBlocking types.Bool `tfsdk:"port_blocking"`
Region types.String `tfsdk:"region"`
SeparatePrivateNetworkInterface types.Bool `tfsdk:"separate_private_network_interface"`
Size types.String `tfsdk:"size"`
SshKeys types.List `tfsdk:"ssh_keys"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
VpcIpv4Address types.String `tfsdk:"vpc_ipv4_address"`
}
var _ basetypes.ObjectTypable = AdvancedFeaturesType{}
+3 -12
View File
@@ -34,21 +34,12 @@ func SshKeyResourceSchema(ctx context.Context) schema.Schema {
stringvalidator.LengthAtLeast(1),
},
},
"public_key": schema.StringAttribute{
Required: true,
Description: "The public key in OpenSSH \"authorized_keys\" format.",
MarkdownDescription: "The public key in OpenSSH \"authorized_keys\" format.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
},
}
}
type SshKeyModel struct {
Default types.Bool `tfsdk:"default"`
Id types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PublicKey types.String `tfsdk:"public_key"`
Default types.Bool `tfsdk:"default"`
Id types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
}
+515 -697
View File
File diff suppressed because it is too large Load Diff
+37 -24
View File
@@ -573,7 +573,7 @@
"name": "transfer_max",
"float64": {
"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."
}
},
{
@@ -712,7 +712,7 @@
"name": "entry_protocol",
"string": {
"computed_optional_required": "required",
"description": "The protocol that traffic must match for the load balancer to forward it. Valid values are \"http\" and \"https\".",
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n",
"validators": [
{
"custom": {
@@ -743,6 +743,16 @@
"computed_optional_required": "computed_optional",
"description": "Leave null to accept the default '/' path.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
},
{
"custom": {
"imports": [
@@ -798,7 +808,7 @@
},
{
"name": "server_ids",
"list": {
"set": {
"computed_optional_required": "computed_optional",
"element_type": {
"int64": {}
@@ -834,6 +844,13 @@
"description": "The slug of the selected operating system."
}
},
{
"name": "ipv6",
"bool": {
"computed_optional_required": "computed_optional",
"description": "If true this will enable IPv6 for this server."
}
},
{
"name": "name",
"string": {
@@ -845,7 +862,7 @@
"name": "port_blocking",
"bool": {
"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."
}
},
{
@@ -867,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",
"string": {
@@ -922,6 +946,13 @@
"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",
"int64": {
@@ -1176,25 +1207,6 @@
]
}
},
{
"name": "public_key",
"string": {
"computed_optional_required": "required",
"description": "The public key in OpenSSH \"authorized_keys\" format.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
},
{
"name": "id",
"int64": {
@@ -1328,5 +1340,6 @@
}
}
],
"version": "0.1"
"version": "0.1",
"$schema": "https://raw.githubusercontent.com/hashicorp/terraform-plugin-codegen-spec/main/spec/v0.1/schema.json"
}
+1 -1
View File
@@ -40,7 +40,6 @@ resources:
default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to
disable deployment of default keys.
ignores:
- ipv6
- licenses
- links
- server
@@ -99,6 +98,7 @@ resources:
key_id: id
ignores:
- ssh_key
- public_key
vpc:
create:
path: /vpcs
+20 -2
View File
@@ -2,6 +2,24 @@
GENERATOR_CONFIG=$(dirname "$0")/../provider_code_spec.json
# Advanced features configuration
ADVANCED_FEATURES_CONFIG=$(dirname "$0")/data/advanced_features.json
# Add JSON schema
cat <<<$(jq --tab '. + {"$schema": "https://raw.githubusercontent.com/hashicorp/terraform-plugin-codegen-spec/main/spec/v0.1/schema.json"}' $GENERATOR_CONFIG) >$GENERATOR_CONFIG
# Server configuration
ADVANCED_FEATURES_CONFIG=$(dirname "$0")/data/server_advanced_features.json
cat <<<$(jq --tab --slurpfile adv_feat_cfg $ADVANCED_FEATURES_CONFIG '.resources[1].schema.attributes |= . + $adv_feat_cfg' $GENERATOR_CONFIG) >$GENERATOR_CONFIG
# Use set instead of list for server_ids in load_balancer resource
jq --tab '
.resources |= map(
if .name == "load_balancer" then
.schema.attributes |= map(
if .name == "server_ids" then
with_entries(.key |= gsub("list"; "set"))
else .
end
)
else .
end
)
' "$GENERATOR_CONFIG" >"$GENERATOR_CONFIG.tmp" && mv "$GENERATOR_CONFIG.tmp" "$GENERATOR_CONFIG"
+2
View File
@@ -59,6 +59,7 @@ cat <<<$(jq '.components.schemas.AdvancedFirewallRuleRequest.properties.source_a
## Load Balancer
cat <<<$(jq '.components.schemas.CreateLoadBalancerRequest.properties.forwarding_rules += {"x-oapi-codegen-extra-tags": {"tfsdk": "forwarding_rules"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ForwardingRule.properties.entry_protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "entry_protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ForwardingRuleRequest.properties.entry_protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "entry_protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.LoadBalancer.properties.health_check += {"x-oapi-codegen-extra-tags": {"tfsdk": "health_check"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.HealthCheckProtocol |= del(.enum)' $OPENAPI_FILE) >$OPENAPI_FILE
@@ -143,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
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