Compare commits

...

7 Commits

Author SHA1 Message Date
Oscar Hermoso 4ef9df6edf 🧁 allow update of server vpc_id without recreating resource, update api (#51) 2024-12-08 08:20:21 +08:00
dependabot[bot] 547763d639 🐹 Bump github.com/hashicorp/terraform-plugin-docs (#49)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs).


Updates `github.com/hashicorp/terraform-plugin-docs` from 0.20.0 to 0.20.1
- [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.20.0...v0.20.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  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>
2024-11-29 07:22:41 +08:00
Oscar Hermoso f5fecdd69c 👯 consistent headings/curl summary for parameters in README 2024-11-22 10:40:17 +08:00
dependabot[bot] cd85f67e28 🐹 Bump github.com/hashicorp/terraform-plugin-testing (#47)
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.10.0 to 1.11.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.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-testing
  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>
2024-11-22 10:12:43 +08:00
Oscar Hermoso 50d06b967b 🐣 source_and_destination_check for server resource (#48)
* 🐣 source_and_destination_check for server resource

* Ensure default values are handled correctly
2024-11-22 10:12:22 +08:00
Oscar Hermoso e6c50a470d 🔧 rebuild server when ssh_keys, image or user_data changes (#44) 2024-11-17 15:28:41 +08:00
Oscar Hermoso 2bc281d177 🐙 fix server import for public_ipv4_count (#43) 2024-11-13 22:22:10 +08:00
21 changed files with 834 additions and 1486 deletions
+30 -22
View File
@@ -39,9 +39,35 @@ Planned features:
- [ ] Virtual Private Cloud
- [ ] Kubernetes
## Parameters
## Server parameters
Images:
### Regions
<details>
<summary>curl for regions</summary>
```sh
curl -X GET "https://api.binarylane.com.au/v2/regions" \
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/regions.json
jq '[ .regions[] | .slug ] | sort' tmp/regions.json
```
</details>
```json
[
"bne",
"mel",
"per",
"sin",
"syd"
]
```
### Images
<details>
<summary>curl for images</summary>
```sh
curl -X GET "https://api.binarylane.com.au/v2/images?type=distribution&&page=1&per_page=200" \
@@ -49,6 +75,7 @@ curl -X GET "https://api.binarylane.com.au/v2/images?type=distribution&&page=1&p
jq '[ .images[] | .slug ] | sort' tmp/images.json
```
</details>
```json
[
@@ -78,29 +105,10 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
]
```
Regions:
```sh
curl -X GET "https://api.binarylane.com.au/v2/regions" \
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/regions.json
jq '[ .regions[] | .slug ] | sort' tmp/regions.json
```
```json
[
"bne",
"mel",
"per",
"sin",
"syd"
]
```
### Sizes
<details>
<summary>VM size</summary>
<summary>curl for sizes</summary>
```sh
curl -X GET "https://api.binarylane.com.au/v2/sizes" \
+1
View File
@@ -28,6 +28,7 @@ Provides a Binary Lane Server resource. This can be used to create and delete se
- `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.
- `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) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
+7 -6
View File
@@ -35,12 +35,13 @@ resource "binarylane_server" "db" {
# VPN server
resource "binarylane_server" "vpn" {
name = "tf-example-vpc-vpn"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
name = "tf-example-vpc-vpn"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
source_and_destination_check = false
}
resource "binarylane_vpc_route_entries" "example" {
+8 -8
View File
@@ -6,13 +6,13 @@ 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.20.0
github.com/hashicorp/terraform-plugin-docs v0.20.1
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
github.com/hashicorp/terraform-plugin-framework-validators v0.15.0
github.com/hashicorp/terraform-plugin-go v0.25.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.10.0
github.com/hashicorp/terraform-plugin-testing v1.11.0
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
github.com/oapi-codegen/runtime v1.1.1
)
@@ -55,12 +55,12 @@ require (
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.0 // indirect
github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
github.com/hashicorp/terraform-json v0.23.0 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
@@ -99,13 +99,13 @@ require (
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
+16 -16
View File
@@ -131,8 +131,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE=
github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
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/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.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
@@ -145,8 +145,8 @@ github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0 h1:IKpc337XKk50QyQP
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.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o=
github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y=
github.com/hashicorp/terraform-plugin-docs v0.20.1 h1:Fq7E/HrU8kuZu3hNliZGwloFWSYfWEOWnylFhYQIoys=
github.com/hashicorp/terraform-plugin-docs v0.20.1/go.mod h1:Yz6HoK7/EgzSrHPB9J/lWFzwl9/xep2OPnc5jaJDV90=
github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw=
github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E=
@@ -157,10 +157,10 @@ github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974r
github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw=
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.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw=
github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A=
github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
@@ -319,8 +319,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
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=
@@ -343,8 +343,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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=
@@ -370,8 +370,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@@ -382,8 +382,8 @@ 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
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=
+20 -5
View File
@@ -6,17 +6,32 @@ import (
"fmt"
"net/http"
"net/http/httputil"
"os"
"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
func NewClientWithAuth(endpoint string, token string) (*ClientWithResponses, error) {
func NewClientWithDefaultConfig() (*ClientWithResponses, error) {
return NewClientWithConfig("", "")
}
func NewClientWithConfig(endpoint string, token string) (*ClientWithResponses, error) {
if endpoint == "" {
endpoint = os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
}
if token == "" {
return nil, errors.New("missing or empty value for the Binary Lane API " +
"token. Set the `api_token` value in the configuration or use the " +
"BINARYLANE_API_TOKEN environment variable. If either is already set, " +
"ensure the value is not empty")
token = os.Getenv("BINARYLANE_API_TOKEN")
if token == "" {
return nil, errors.New("missing or empty value for the Binary Lane API " +
"token. Set the `api_token` value in the configuration or use the " +
"BINARYLANE_API_TOKEN environment variable. If either is already set, " +
"ensure the value is not empty")
}
}
auth, err := securityprovider.NewSecurityProviderBearerToken(token)
+8 -472
View File
@@ -197,17 +197,6 @@ type ClientInterface interface {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetFailoverIpsServerId request
GetFailoverIpsServerId(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// PostFailoverIpsServerIdWithBody request with any body
PostFailoverIpsServerIdWithBody(ctx context.Context, serverId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
PostFailoverIpsServerId(ctx context.Context, serverId int64, body PostFailoverIpsServerIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetFailoverIpsServerIdAvailable request
GetFailoverIpsServerIdAvailable(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdAvailableParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetImages request
GetImages(ctx context.Context, params *GetImagesParams, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -987,54 +976,6 @@ func (c *Client) PutDomainsDomainNameRecordsRecordId(ctx context.Context, domain
return c.Client.Do(req)
}
func (c *Client) GetFailoverIpsServerId(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetFailoverIpsServerIdRequest(c.Server, serverId, params)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) PostFailoverIpsServerIdWithBody(ctx context.Context, serverId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPostFailoverIpsServerIdRequestWithBody(c.Server, serverId, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) PostFailoverIpsServerId(ctx context.Context, serverId int64, body PostFailoverIpsServerIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPostFailoverIpsServerIdRequest(c.Server, serverId, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) GetFailoverIpsServerIdAvailable(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdAvailableParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetFailoverIpsServerIdAvailableRequest(c.Server, serverId, params)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) GetImages(ctx context.Context, params *GetImagesParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetImagesRequest(c.Server, params)
if err != nil {
@@ -3920,197 +3861,6 @@ func NewPutDomainsDomainNameRecordsRecordIdRequestWithBody(server string, domain
return req, nil
}
// NewGetFailoverIpsServerIdRequest generates requests for GetFailoverIpsServerId
func NewGetFailoverIpsServerIdRequest(server string, serverId int64, params *GetFailoverIpsServerIdParams) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "server_id", runtime.ParamLocationPath, serverId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/failover_ips/%s", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
if params != nil {
queryValues := queryURL.Query()
if params.Page != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, *params.Page); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
}
if params.PerPage != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "per_page", runtime.ParamLocationQuery, *params.PerPage); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
}
queryURL.RawQuery = queryValues.Encode()
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewPostFailoverIpsServerIdRequest calls the generic PostFailoverIpsServerId builder with application/json body
func NewPostFailoverIpsServerIdRequest(server string, serverId int64, body PostFailoverIpsServerIdJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewPostFailoverIpsServerIdRequestWithBody(server, serverId, "application/json", bodyReader)
}
// NewPostFailoverIpsServerIdRequestWithBody generates requests for PostFailoverIpsServerId with any type of body
func NewPostFailoverIpsServerIdRequestWithBody(server string, serverId int64, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "server_id", runtime.ParamLocationPath, serverId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/failover_ips/%s", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewGetFailoverIpsServerIdAvailableRequest generates requests for GetFailoverIpsServerIdAvailable
func NewGetFailoverIpsServerIdAvailableRequest(server string, serverId int64, params *GetFailoverIpsServerIdAvailableParams) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "server_id", runtime.ParamLocationPath, serverId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/failover_ips/%s/available", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
if params != nil {
queryValues := queryURL.Query()
if params.Page != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, *params.Page); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
}
if params.PerPage != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "per_page", runtime.ParamLocationQuery, *params.PerPage); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
}
queryURL.RawQuery = queryValues.Encode()
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewGetImagesRequest generates requests for GetImages
func NewGetImagesRequest(server string, params *GetImagesParams) (*http.Request, error) {
var err error
@@ -8665,17 +8415,6 @@ type ClientWithResponsesInterface interface {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error)
// GetFailoverIpsServerIdWithResponse request
GetFailoverIpsServerIdWithResponse(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*GetFailoverIpsServerIdResponse, error)
// PostFailoverIpsServerIdWithBodyWithResponse request with any body
PostFailoverIpsServerIdWithBodyWithResponse(ctx context.Context, serverId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFailoverIpsServerIdResponse, error)
PostFailoverIpsServerIdWithResponse(ctx context.Context, serverId int64, body PostFailoverIpsServerIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PostFailoverIpsServerIdResponse, error)
// GetFailoverIpsServerIdAvailableWithResponse request
GetFailoverIpsServerIdAvailableWithResponse(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdAvailableParams, reqEditors ...RequestEditorFn) (*GetFailoverIpsServerIdAvailableResponse, error)
// GetImagesWithResponse request
GetImagesWithResponse(ctx context.Context, params *GetImagesParams, reqEditors ...RequestEditorFn) (*GetImagesResponse, error)
@@ -9641,76 +9380,6 @@ func (r PutDomainsDomainNameRecordsRecordIdResponse) StatusCode() int {
return 0
}
type GetFailoverIpsServerIdResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *FailoverIpsResponse
JSON404 *ProblemDetails
}
// Status returns HTTPResponse.Status
func (r GetFailoverIpsServerIdResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r GetFailoverIpsServerIdResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type PostFailoverIpsServerIdResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ActionResponse
JSON400 *ValidationProblemDetails
JSON404 *ProblemDetails
}
// Status returns HTTPResponse.Status
func (r PostFailoverIpsServerIdResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r PostFailoverIpsServerIdResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type GetFailoverIpsServerIdAvailableResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *FailoverIpsResponse
JSON404 *ProblemDetails
}
// Status returns HTTPResponse.Status
func (r GetFailoverIpsServerIdAvailableResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r GetFailoverIpsServerIdAvailableResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type GetImagesResponse struct {
Body []byte
HTTPResponse *http.Response
@@ -9761,6 +9430,7 @@ type GetImagesImageIdDownloadResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ImageDownloadResponse
JSON400 *ValidationProblemDetails
JSON404 *ProblemDetails
}
@@ -12123,41 +11793,6 @@ func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithResponse(ct
return ParsePutDomainsDomainNameRecordsRecordIdResponse(rsp)
}
// GetFailoverIpsServerIdWithResponse request returning *GetFailoverIpsServerIdResponse
func (c *ClientWithResponses) GetFailoverIpsServerIdWithResponse(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*GetFailoverIpsServerIdResponse, error) {
rsp, err := c.GetFailoverIpsServerId(ctx, serverId, params, reqEditors...)
if err != nil {
return nil, err
}
return ParseGetFailoverIpsServerIdResponse(rsp)
}
// PostFailoverIpsServerIdWithBodyWithResponse request with arbitrary body returning *PostFailoverIpsServerIdResponse
func (c *ClientWithResponses) PostFailoverIpsServerIdWithBodyWithResponse(ctx context.Context, serverId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFailoverIpsServerIdResponse, error) {
rsp, err := c.PostFailoverIpsServerIdWithBody(ctx, serverId, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParsePostFailoverIpsServerIdResponse(rsp)
}
func (c *ClientWithResponses) PostFailoverIpsServerIdWithResponse(ctx context.Context, serverId int64, body PostFailoverIpsServerIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PostFailoverIpsServerIdResponse, error) {
rsp, err := c.PostFailoverIpsServerId(ctx, serverId, body, reqEditors...)
if err != nil {
return nil, err
}
return ParsePostFailoverIpsServerIdResponse(rsp)
}
// GetFailoverIpsServerIdAvailableWithResponse request returning *GetFailoverIpsServerIdAvailableResponse
func (c *ClientWithResponses) GetFailoverIpsServerIdAvailableWithResponse(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdAvailableParams, reqEditors ...RequestEditorFn) (*GetFailoverIpsServerIdAvailableResponse, error) {
rsp, err := c.GetFailoverIpsServerIdAvailable(ctx, serverId, params, reqEditors...)
if err != nil {
return nil, err
}
return ParseGetFailoverIpsServerIdAvailableResponse(rsp)
}
// GetImagesWithResponse request returning *GetImagesResponse
func (c *ClientWithResponses) GetImagesWithResponse(ctx context.Context, params *GetImagesParams, reqEditors ...RequestEditorFn) (*GetImagesResponse, error) {
rsp, err := c.GetImages(ctx, params, reqEditors...)
@@ -14171,112 +13806,6 @@ func ParsePutDomainsDomainNameRecordsRecordIdResponse(rsp *http.Response) (*PutD
return response, nil
}
// ParseGetFailoverIpsServerIdResponse parses an HTTP response from a GetFailoverIpsServerIdWithResponse call
func ParseGetFailoverIpsServerIdResponse(rsp *http.Response) (*GetFailoverIpsServerIdResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &GetFailoverIpsServerIdResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest FailoverIpsResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON404 = &dest
}
return response, nil
}
// ParsePostFailoverIpsServerIdResponse parses an HTTP response from a PostFailoverIpsServerIdWithResponse call
func ParsePostFailoverIpsServerIdResponse(rsp *http.Response) (*PostFailoverIpsServerIdResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &PostFailoverIpsServerIdResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest ActionResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ValidationProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON404 = &dest
}
return response, nil
}
// ParseGetFailoverIpsServerIdAvailableResponse parses an HTTP response from a GetFailoverIpsServerIdAvailableWithResponse call
func ParseGetFailoverIpsServerIdAvailableResponse(rsp *http.Response) (*GetFailoverIpsServerIdAvailableResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &GetFailoverIpsServerIdAvailableResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest FailoverIpsResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON404 = &dest
}
return response, nil
}
// ParseGetImagesResponse parses an HTTP response from a GetImagesWithResponse call
func ParseGetImagesResponse(rsp *http.Response) (*GetImagesResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
@@ -14364,6 +13893,13 @@ func ParseGetImagesImageIdDownloadResponse(rsp *http.Response) (*GetImagesImageI
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ValidationProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ProblemDetails
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+3
View File
@@ -12,6 +12,9 @@ cat <<<$(jq '.paths |= with_entries(.key |= sub("/v2/"; "/"))' $OPENAPI_FILE) >$
# Terraform can't handle oneOf types, so we need to replace them with basic types
cat <<<$(jq '.components.schemas.CreateServerRequest.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.CreateServerRequest.properties.ssh_keys.items |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.Rebuild.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ImageOptions.properties.ssh_keys.items |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ChangeImage.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].put.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].get.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
+18 -313
View File
@@ -8,7 +8,7 @@
"url": "https://support.binarylane.com.au",
"email": "support@binarylane.com.au"
},
"version": "0.25.0",
"version": "0.26.0",
"x-logo": {
"url": "https://www.binarylane.com.au/res/images/binarylane/logo.png",
"backgroundColor": "#000000",
@@ -1200,242 +1200,6 @@
"x-cli-command": "domain record delete"
}
},
"/failover_ips/{server_id}": {
"get": {
"tags": [
"FailoverIps"
],
"summary": "Fetch the Failover IPs for a Server",
"parameters": [
{
"name": "server_id",
"in": "path",
"description": "The target server id.",
"required": true,
"schema": {
"type": "integer",
"description": "The target server id.",
"format": "int64"
}
},
{
"name": "page",
"in": "query",
"description": "The selected page. Page numbering starts at 1",
"schema": {
"maximum": 2147483647,
"minimum": 1,
"type": "integer",
"description": "The selected page. Page numbering starts at 1",
"format": "int32",
"default": 1
}
},
{
"name": "per_page",
"in": "query",
"description": "The number of results to show per page.",
"schema": {
"maximum": 200,
"minimum": 1,
"type": "integer",
"description": "The number of results to show per page.",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FailoverIpsResponse"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"x-codeSamples": [
{
"lang": "Curl",
"source": "curl -X GET \"https://api.binarylane.com.au/v2/failover_ips/${server_id}?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\""
}
],
"x-cli-command": "server failover-ip get"
},
"post": {
"tags": [
"FailoverIps"
],
"summary": "Sets the List of Failover IPs that are Assigned to a Server",
"description": "This overwrites the current list, i.e. submit the entire list, not just updates.\nIf NoContent is returned no changes were made.\nIf an action is returned the changes have been saved but the effect may not be complete until the action is complete. If the action does not complete the network changes can be forced by rebooting the server.",
"parameters": [
{
"name": "server_id",
"in": "path",
"description": "The target server id.",
"required": true,
"schema": {
"type": "integer",
"description": "The target server id.",
"format": "int64"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/FailoverIpsRequest"
}
]
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ActionResponse"
}
}
}
},
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"x-codeSamples": [
{
"lang": "Curl",
"source": "curl -X POST \"https://api.binarylane.com.au/v2/failover_ips/${server_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\""
}
],
"x-cli-command": "server failover-ip update"
}
},
"/failover_ips/{server_id}/available": {
"get": {
"tags": [
"FailoverIps"
],
"summary": "Fetch a List of all Failover IPs that are Available to be Assigned to a Server",
"parameters": [
{
"name": "server_id",
"in": "path",
"description": "The target server id.",
"required": true,
"schema": {
"type": "integer",
"description": "The target server id.",
"format": "int64"
}
},
{
"name": "page",
"in": "query",
"description": "The selected page. Page numbering starts at 1",
"schema": {
"maximum": 2147483647,
"minimum": 1,
"type": "integer",
"description": "The selected page. Page numbering starts at 1",
"format": "int32",
"default": 1
}
},
{
"name": "per_page",
"in": "query",
"description": "The number of results to show per page.",
"schema": {
"maximum": 200,
"minimum": 1,
"type": "integer",
"description": "The number of results to show per page.",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FailoverIpsResponse"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"x-codeSamples": [
{
"lang": "Curl",
"source": "curl -X GET \"https://api.binarylane.com.au/v2/failover_ips/${server_id}/available?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\""
}
],
"x-cli-command": "server failover-ip available"
}
},
"/images": {
"get": {
"tags": [
@@ -1613,6 +1377,16 @@
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
@@ -9199,7 +8973,7 @@
},
"iso": {
"type": "boolean",
"description": "If this is true the backup is an ISO image and cannot be restored. ISO images may only be attached for use as a boot disk or an additional disk.",
"description": "If this is true the backup is an ISO image and cannot be restored or downloaded. ISO images may only be attached for use as a boot disk or an additional disk.",
"readOnly": true
},
"backup_disks": {
@@ -9514,17 +9288,10 @@
"type": "object",
"properties": {
"image": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "The slug or ID of the selected image. What type of image is permitted here varies based on the server action.",
"nullable": true,
"example": 5
"example": 5,
"type": "string"
},
"options": {
"allOf": [
@@ -10942,53 +10709,6 @@
},
"description": "Enable IPv6 for a Server"
},
"FailoverIpsRequest": {
"required": [
"failover_ips"
],
"type": "object",
"properties": {
"failover_ips": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of IP Failover addresses to assign to this server. This overwrites the current list, so any current IP Failover addresses that are omitted will be removed from the server."
}
}
},
"FailoverIpsResponse": {
"required": [
"failover_ips",
"meta"
],
"type": "object",
"properties": {
"meta": {
"allOf": [
{
"$ref": "#/components/schemas/Meta"
}
],
"description": "Contains metadata about the response, currently this includes the total number of items."
},
"links": {
"allOf": [
{
"$ref": "#/components/schemas/Links"
}
],
"nullable": true
},
"failover_ips": {
"type": "array",
"items": {
"type": "string"
},
"readOnly": true
}
}
},
"ForwardingRule": {
"required": [
"entry_protocol"
@@ -11306,15 +11026,8 @@
"ssh_keys": {
"type": "array",
"items": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
"example": 5,
"type": "integer"
},
"description": "This may be either the existing SSH Keys IDs or fingerprints.\nIf this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH Keys).\nSubmit an empty array to disable deployment of default keys.",
"nullable": true
@@ -12491,17 +12204,10 @@
"type": "string"
},
"image": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "The Operating System ID or slug or Backup image ID to use as a base for the rebuild.",
"nullable": true,
"example": 5
"example": 5,
"type": "string"
},
"options": {
"allOf": [
@@ -15029,7 +14735,6 @@
"ServerActions",
"Actions",
"DataUsages",
"FailoverIps",
"Images",
"Keys",
"ReverseNames",
+4 -262
View File
@@ -714,7 +714,7 @@ type BackupInfo struct {
// BackupDisks A list of the individual disks that make up this backup.
BackupDisks *[]BackupDisk `json:"backup_disks,omitempty"`
// Iso If this is true the backup is an ISO image and cannot be restored. ISO images may only be attached for use as a boot disk or an additional disk.
// Iso If this is true the backup is an ISO image and cannot be restored or downloaded. ISO images may only be attached for use as a boot disk or an additional disk.
Iso *bool `json:"iso,omitempty"`
// Locked If this is true the backup is locked and cannot be replaced.
@@ -854,23 +854,12 @@ type ChangeBackupScheduleType string
// ChangeImage defines model for ChangeImage.
type ChangeImage struct {
// Image The slug or ID of the selected image. What type of image is permitted here varies based on the server action.
Image *ChangeImage_Image `json:"image"`
Image *string `json:"image"`
// Options Additional options for the server configuration after the image has been changed.
Options *ImageOptions `json:"options"`
}
// ChangeImageImage0 defines model for .
type ChangeImageImage0 = int
// ChangeImageImage1 defines model for .
type ChangeImageImage1 = string
// ChangeImage_Image The slug or ID of the selected image. What type of image is permitted here varies based on the server action.
type ChangeImage_Image struct {
union json.RawMessage
}
// ChangeIpv6 Enable or Disable IPv6 for a Server
type ChangeIpv6 struct {
// Enabled The desired enabled status of IPv6.
@@ -1513,21 +1502,6 @@ type EnableIpv6 struct {
// EnableIpv6Type defines model for EnableIpv6.Type.
type EnableIpv6Type string
// FailoverIpsRequest defines model for FailoverIpsRequest.
type FailoverIpsRequest struct {
// FailoverIps The list of IP Failover addresses to assign to this server. This overwrites the current list, so any current IP Failover addresses that are omitted will be removed from the server.
FailoverIps []string `json:"failover_ips"`
}
// FailoverIpsResponse defines model for FailoverIpsResponse.
type FailoverIpsResponse struct {
FailoverIps *[]string `json:"failover_ips,omitempty"`
Links *Links `json:"links"`
// Meta Contains metadata about the response, currently this includes the total number of items.
Meta Meta `json:"meta"`
}
// ForwardingRule defines model for ForwardingRule.
type ForwardingRule struct {
// EntryProtocol The protocol that traffic must match for this load balancer to forward traffic according to this rule.
@@ -1662,23 +1636,12 @@ type ImageOptions struct {
// SshKeys This may be either the existing SSH Keys IDs or fingerprints.
// If this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH Keys).
// Submit an empty array to disable deployment of default keys.
SshKeys *[]ImageOptions_SshKeys_Item `json:"ssh_keys"`
SshKeys *[]int `json:"ssh_keys"`
// UserData If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
UserData *string `json:"user_data"`
}
// ImageOptionsSshKeys0 defines model for .
type ImageOptionsSshKeys0 = int
// ImageOptionsSshKeys1 defines model for .
type ImageOptionsSshKeys1 = string
// ImageOptions_SshKeys_Item defines model for ImageOptions.ssh_keys.Item.
type ImageOptions_SshKeys_Item struct {
union json.RawMessage
}
// ImageQueryType
// | Value | Description |
// | ----- | ----------- |
@@ -2176,24 +2139,13 @@ type RebootType string
// Rebuild Rebuild an Existing Server
type Rebuild struct {
// Image The Operating System ID or slug or Backup image ID to use as a base for the rebuild.
Image *Rebuild_Image `json:"image"`
Image *string `json:"image"`
// Options Additional options. Leaving this entirely null or any of the properties included null will use the defaults from the existing server.
Options *ImageOptions `json:"options"`
Type RebuildType `json:"type"`
}
// RebuildImage0 defines model for .
type RebuildImage0 = int
// RebuildImage1 defines model for .
type RebuildImage1 = string
// Rebuild_Image The Operating System ID or slug or Backup image ID to use as a base for the rebuild.
type Rebuild_Image struct {
union json.RawMessage
}
// RebuildType defines model for Rebuild.Type.
type RebuildType string
@@ -3247,27 +3199,6 @@ type PutDomainsDomainNameRecordsRecordIdParamsDomainName0 = int
// PutDomainsDomainNameRecordsRecordIdParamsDomainName1 defines parameters for PutDomainsDomainNameRecordsRecordId.
type PutDomainsDomainNameRecordsRecordIdParamsDomainName1 = string
// GetFailoverIpsServerIdParams defines parameters for GetFailoverIpsServerId.
type GetFailoverIpsServerIdParams struct {
// Page The selected page. Page numbering starts at 1
Page *int32 `form:"page,omitempty" json:"page,omitempty"`
// PerPage The number of results to show per page.
PerPage *int32 `form:"per_page,omitempty" json:"per_page,omitempty"`
}
// PostFailoverIpsServerIdJSONBody defines parameters for PostFailoverIpsServerId.
type PostFailoverIpsServerIdJSONBody = FailoverIpsRequest
// GetFailoverIpsServerIdAvailableParams defines parameters for GetFailoverIpsServerIdAvailable.
type GetFailoverIpsServerIdAvailableParams struct {
// Page The selected page. Page numbering starts at 1
Page *int32 `form:"page,omitempty" json:"page,omitempty"`
// PerPage The number of results to show per page.
PerPage *int32 `form:"per_page,omitempty" json:"per_page,omitempty"`
}
// GetImagesParams defines parameters for GetImages.
type GetImagesParams struct {
// Type Queries for distribution will include images that have pre-installed applications.
@@ -3555,9 +3486,6 @@ type PostDomainsDomainNameRecordsJSONRequestBody = PostDomainsDomainNameRecordsJ
// PutDomainsDomainNameRecordsRecordIdJSONRequestBody defines body for PutDomainsDomainNameRecordsRecordId for application/json ContentType.
type PutDomainsDomainNameRecordsRecordIdJSONRequestBody = PutDomainsDomainNameRecordsRecordIdJSONBody
// PostFailoverIpsServerIdJSONRequestBody defines body for PostFailoverIpsServerId for application/json ContentType.
type PostFailoverIpsServerIdJSONRequestBody = PostFailoverIpsServerIdJSONBody
// PostLoadBalancersJSONRequestBody defines body for PostLoadBalancers for application/json ContentType.
type PostLoadBalancersJSONRequestBody = PostLoadBalancersJSONBody
@@ -3985,130 +3913,6 @@ func (a ValidationProblemDetails) MarshalJSON() ([]byte, error) {
return json.Marshal(object)
}
// AsChangeImageImage0 returns the union data inside the ChangeImage_Image as a ChangeImageImage0
func (t ChangeImage_Image) AsChangeImageImage0() (ChangeImageImage0, error) {
var body ChangeImageImage0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromChangeImageImage0 overwrites any union data inside the ChangeImage_Image as the provided ChangeImageImage0
func (t *ChangeImage_Image) FromChangeImageImage0(v ChangeImageImage0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeChangeImageImage0 performs a merge with any union data inside the ChangeImage_Image, using the provided ChangeImageImage0
func (t *ChangeImage_Image) MergeChangeImageImage0(v ChangeImageImage0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsChangeImageImage1 returns the union data inside the ChangeImage_Image as a ChangeImageImage1
func (t ChangeImage_Image) AsChangeImageImage1() (ChangeImageImage1, error) {
var body ChangeImageImage1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromChangeImageImage1 overwrites any union data inside the ChangeImage_Image as the provided ChangeImageImage1
func (t *ChangeImage_Image) FromChangeImageImage1(v ChangeImageImage1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeChangeImageImage1 performs a merge with any union data inside the ChangeImage_Image, using the provided ChangeImageImage1
func (t *ChangeImage_Image) MergeChangeImageImage1(v ChangeImageImage1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ChangeImage_Image) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ChangeImage_Image) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsImageOptionsSshKeys0 returns the union data inside the ImageOptions_SshKeys_Item as a ImageOptionsSshKeys0
func (t ImageOptions_SshKeys_Item) AsImageOptionsSshKeys0() (ImageOptionsSshKeys0, error) {
var body ImageOptionsSshKeys0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromImageOptionsSshKeys0 overwrites any union data inside the ImageOptions_SshKeys_Item as the provided ImageOptionsSshKeys0
func (t *ImageOptions_SshKeys_Item) FromImageOptionsSshKeys0(v ImageOptionsSshKeys0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeImageOptionsSshKeys0 performs a merge with any union data inside the ImageOptions_SshKeys_Item, using the provided ImageOptionsSshKeys0
func (t *ImageOptions_SshKeys_Item) MergeImageOptionsSshKeys0(v ImageOptionsSshKeys0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsImageOptionsSshKeys1 returns the union data inside the ImageOptions_SshKeys_Item as a ImageOptionsSshKeys1
func (t ImageOptions_SshKeys_Item) AsImageOptionsSshKeys1() (ImageOptionsSshKeys1, error) {
var body ImageOptionsSshKeys1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromImageOptionsSshKeys1 overwrites any union data inside the ImageOptions_SshKeys_Item as the provided ImageOptionsSshKeys1
func (t *ImageOptions_SshKeys_Item) FromImageOptionsSshKeys1(v ImageOptionsSshKeys1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeImageOptionsSshKeys1 performs a merge with any union data inside the ImageOptions_SshKeys_Item, using the provided ImageOptionsSshKeys1
func (t *ImageOptions_SshKeys_Item) MergeImageOptionsSshKeys1(v ImageOptionsSshKeys1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ImageOptions_SshKeys_Item) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ImageOptions_SshKeys_Item) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsNetworkNetmask0 returns the union data inside the Network_Netmask as a NetworkNetmask0
func (t Network_Netmask) AsNetworkNetmask0() (NetworkNetmask0, error) {
var body NetworkNetmask0
@@ -4171,68 +3975,6 @@ func (t *Network_Netmask) UnmarshalJSON(b []byte) error {
return err
}
// AsRebuildImage0 returns the union data inside the Rebuild_Image as a RebuildImage0
func (t Rebuild_Image) AsRebuildImage0() (RebuildImage0, error) {
var body RebuildImage0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromRebuildImage0 overwrites any union data inside the Rebuild_Image as the provided RebuildImage0
func (t *Rebuild_Image) FromRebuildImage0(v RebuildImage0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeRebuildImage0 performs a merge with any union data inside the Rebuild_Image, using the provided RebuildImage0
func (t *Rebuild_Image) MergeRebuildImage0(v RebuildImage0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsRebuildImage1 returns the union data inside the Rebuild_Image as a RebuildImage1
func (t Rebuild_Image) AsRebuildImage1() (RebuildImage1, error) {
var body RebuildImage1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromRebuildImage1 overwrites any union data inside the Rebuild_Image as the provided RebuildImage1
func (t *Rebuild_Image) FromRebuildImage1(v RebuildImage1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeRebuildImage1 performs a merge with any union data inside the Rebuild_Image, using the provided RebuildImage1
func (t *Rebuild_Image) MergeRebuildImage1(v RebuildImage1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t Rebuild_Image) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *Rebuild_Image) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsRestoreImage0 returns the union data inside the Restore_Image as a RestoreImage0
func (t Restore_Image) AsRestoreImage0() (RestoreImage0, error) {
var body RestoreImage0
@@ -93,7 +93,7 @@ func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRe
return
}
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
+4 -4
View File
@@ -152,7 +152,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
return
}
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -245,7 +245,7 @@ func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadReques
return
}
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -309,7 +309,7 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
return
}
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -414,7 +414,7 @@ func (r *loadBalancerResource) ImportState(
resp.Diagnostics.Append(diags...)
}
func SetLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancerModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
func setLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancerModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
var diags, diag diag.Diagnostics
data.Id = types.Int64Value(*lb.Id)
@@ -7,7 +7,6 @@ import (
"fmt"
"log"
"net/http"
"os"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
@@ -97,16 +96,7 @@ func init() {
Name: "load_balancer",
Dependencies: []string{"server"},
F: func(_ string) error {
endpoint := os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
token := os.Getenv("BINARYLANE_API_TOKEN")
client, err := binarylane.NewClientWithAuth(
endpoint,
token,
)
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
@@ -124,29 +114,29 @@ func init() {
PerPage: &perPage,
}
lbResp, err := client.GetLoadBalancersWithResponse(ctx, &params)
listResp, err := client.GetLoadBalancersWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting load balancers for test sweep: %w", err)
}
if lbResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", lbResp.Body)
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", listResp.Body)
}
loadBalancers := *lbResp.JSON200.LoadBalancers
loadBalancers := *listResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if strings.HasPrefix(*lb.Name, "tf-test-") {
lbResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, *lb.Id)
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)
}
if lbResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting load balancer %d in test sweep: %s", lbResp.StatusCode(), *lb.Id, lbResp.Body)
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)
}
log.Println("Deleted load balancer during test sweep:", *lb.Id)
}
}
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+5 -22
View File
@@ -2,7 +2,6 @@ package provider
import (
"context"
"os"
"terraform-provider-binarylane/internal/binarylane"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@@ -23,8 +22,7 @@ func New(version string) func() provider.Provider {
}
type BinarylaneClient struct {
endpoint string
client *binarylane.ClientWithResponses
client *binarylane.ClientWithResponses
}
type binarylaneProvider struct {
@@ -65,32 +63,17 @@ func (p *binarylaneProvider) Configure(ctx context.Context, req provider.Configu
return
}
endpoint := config.Endpoint.ValueString()
if endpoint == "" {
endpoint = os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
}
token := config.Token.ValueString()
if token == "" {
token = os.Getenv("BINARYLANE_API_TOKEN")
}
client, err := binarylane.NewClientWithAuth(
endpoint,
token,
client, err := binarylane.NewClientWithConfig(
config.Endpoint.ValueString(),
config.Token.ValueString(),
)
if err != nil {
resp.Diagnostics.AddError("Failed to create Binary Lane API client", err.Error())
return
}
binarylaneClient := BinarylaneClient{
endpoint: endpoint,
client: client,
client: client,
}
resp.DataSourceData = binarylaneClient
+38 -10
View File
@@ -128,16 +128,17 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
// Set data values
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.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
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)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
publicIpv4Addresses := []string{}
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
@@ -146,13 +147,40 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
tfPublicIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PublicIpv4Addresses = types.ListUnknown(data.PublicIpv4Addresses.ElementType(ctx))
} else {
data.PublicIpv4Addresses = tfPublicIpv4Addresses
}
tfPrivateIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PrivateIPv4Addresses = types.ListUnknown(data.PrivateIPv4Addresses.ElementType(ctx))
} else {
data.PrivateIPv4Addresses = tfPrivateIpv4Addresses
}
// Get user data script
userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server user data: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
}
if userDataResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status %d reading server user data: name=%s, id=%s", userDataResp.StatusCode(), data.Name.ValueString(), data.Id.String()),
string(userDataResp.Body),
)
return
}
data.UserData = types.StringPointerValue(userDataResp.JSON200.UserData)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+485 -250
View File
@@ -6,15 +6,19 @@ import (
"net/http"
"slices"
"strconv"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"time"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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"
@@ -46,15 +50,14 @@ type serverResource struct {
bc *BinarylaneClient
}
type serverModel struct {
resources.ServerModel
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
Permalink types.String `tfsdk:"permalink"`
Password types.String `tfsdk:"password"`
PasswordChangeSupported types.Bool `tfsdk:"password_change_supported"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
type serverResourceModel struct {
serverDataModel
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
SourceAndDestinationCheck types.Bool `tfsdk:"source_and_destination_check"`
Password types.String `tfsdk:"password"`
PasswordChangeSupported types.Bool `tfsdk:"password_change_supported"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
}
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -107,7 +110,6 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, // Requires replacement if the image changes, TODO (#37)
}
backups := resp.Schema.Attributes["backups"]
@@ -126,9 +128,6 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
MarkdownDescription: user_data.GetMarkdownDescription(),
Optional: true, // Optional as not all servers have an initialization script
Computed: false, // User defined
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(), // If user changes init script, assume they want to replace server
},
}
vpcId := resp.Schema.Attributes["vpc_id"]
@@ -137,7 +136,6 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
MarkdownDescription: vpcId.GetMarkdownDescription(),
Optional: vpcId.IsOptional(),
Computed: false, // VPC ID is not computed, defined at creation
PlanModifiers: []planmodifier.Int64{int64planmodifier.RequiresReplace()},
}
portBlocking := resp.Schema.Attributes["port_blocking"]
@@ -156,8 +154,7 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
Description: sshKeys.GetMarkdownDescription(),
MarkdownDescription: sshKeys.GetDescription(),
Optional: sshKeys.IsOptional(),
Computed: false, // SSH keys are not computed, defined at creation
PlanModifiers: []planmodifier.List{listplanmodifier.RequiresReplace()}, // Cannot update SSH keys with API
Computed: false, // SSH keys are not computed, defined at creation
Validators: []validator.List{
listvalidator.ValueInt64sAre(int64validator.AtLeast(1)),
},
@@ -202,6 +199,26 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
Computed: true,
}
sourceDestCheckDescription := "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."
resp.Schema.Attributes["source_and_destination_check"] = &schema.BoolAttribute{
Description: sourceDestCheckDescription,
MarkdownDescription: sourceDestCheckDescription,
Optional: true,
Required: false,
Computed: true,
Validators: []validator.Bool{
boolvalidator.AlsoRequires(path.Expressions{
path.MatchRoot("vpc_id"),
}...),
},
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
resp.Schema.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
Description: privateIpv4AddressesDescription,
@@ -251,43 +268,79 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
}
func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
// Creation or destruction plan
if req.Plan.Raw.IsNull() || req.State.Raw.IsNull() {
var plan, state serverResourceModel
if req.Plan.Raw.IsNull() {
// Destruction plan, no modification needed
return
}
var plan, state serverModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// When IP count is changed, plan should show addition/removal of public IPs
ipv4CountPlan := plan.PublicIpv4Count.ValueInt32()
ipv4CountState := state.PublicIpv4Count.ValueInt32()
if ipv4CountPlan != ipv4CountState {
currentIps := []*string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &currentIps, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if ipv4CountPlan < ipv4CountState {
plan.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, currentIps[:ipv4CountPlan+1])
resp.Diagnostics.Append(diags...)
if plan.SourceAndDestinationCheck.IsUnknown() {
if plan.VpcId.IsNull() {
plan.SourceAndDestinationCheck = types.BoolNull()
} else {
plan.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, append(currentIps, make([]*string, ipv4CountPlan-ipv4CountState)...))
resp.Diagnostics.Append(diags...)
plan.SourceAndDestinationCheck = types.BoolPointerValue(Pointer(true))
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
if req.State.Raw.IsNull() {
// Creation plan, no further modification needed
return
}
// Read Terraform state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if !plan.VpcId.Equal(state.VpcId) {
plan.PrivateIPv4Addresses = types.ListUnknown(plan.PrivateIPv4Addresses.ElementType(ctx))
}
// When IP count is changed, plan should show addition/removal of public IPs
plannedPublicIpV4Count := int(plan.PublicIpv4Count.ValueInt32())
plannedPublicIpAddresses := make([]attr.Value, plannedPublicIpV4Count)
stateIpV4Addresses := []*string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &stateIpV4Addresses, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
for i := range plannedPublicIpAddresses {
if i < len(stateIpV4Addresses) {
plannedPublicIpAddresses[i] = types.StringValue(*stateIpV4Addresses[i])
} else {
plannedPublicIpAddresses[i] = types.StringUnknown()
}
}
plan.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, plannedPublicIpAddresses)
resp.Diagnostics.Append(diags...)
// Add warning if rebuild is required
attrsRequiringRebuild := attrsRequiringRebuild(&plan, &state)
if len(attrsRequiringRebuild) > 0 {
resp.Diagnostics.AddWarning(
"Server Rebuild Required",
fmt.Sprintf(
"Server %d will lose all data if this Terraform plan is applied, because of modified attribute(s): %s",
state.Id.ValueInt64(),
strings.Join(attrsRequiringRebuild, ", "),
),
)
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data serverModel
var data serverResourceModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
@@ -296,6 +349,11 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
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
tflog.Debug(ctx, fmt.Sprintf("Creating server: name=%s", data.Name.ValueString()))
@@ -343,6 +401,25 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
return
}
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for server to be created, links.actions with rel=create missing from response",
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)
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.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
@@ -353,10 +430,12 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
plannedSourceDestCheck := data.SourceAndDestinationCheck
serverRespSourceDestCheck := types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
data.SourceAndDestinationCheck = serverRespSourceDestCheck
var publicIpv4Addresses []string
var privateIpv4Addresses []string
publicIpv4Addresses := []string{}
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
@@ -372,64 +451,22 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// 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 *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for server to be created, links.actions with rel=create missing from response",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
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.GetServersServerIdActionsActionIdResponse
retryLoop:
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return
}
resp.Diagnostics.AddError(
"Timed out waiting for server to be created",
fmt.Sprintf(
"Timed out waiting for server %s to be created, last response was status=%s, body: %s",
data.Name.ValueString(),
lastReadyResp.Status(),
lastReadyResp.Body,
),
)
// Update source_and_destination_check if needed
if plannedSourceDestCheck.Equal(types.BoolPointerValue(Pointer(false))) {
err := r.updateSourceDestCheck(ctx, data.Id.ValueInt64(), false)
if err != nil {
resp.Diagnostics.AddError("Error updating source and destination check", err.Error())
return
default:
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, data.Id.ValueInt64(), createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server 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 server to to be created: name=%s, status=%s, details: %s", data.Name.ValueString(), readyResp.Status(), readyResp.Body))
}
time.Sleep(time.Second * 5)
data.SourceAndDestinationCheck = plannedSourceDestCheck
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data serverModel
var data serverResourceModel
// Read Terraform prior state data into the model
diags := req.State.Get(ctx, &data)
@@ -449,7 +486,6 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status code %s reading server: name=%s, id=%s", serverResp.Status(), data.Name.ValueString(), data.Id.String()),
@@ -458,42 +494,33 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
return
}
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.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
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)
diag := setServerResourceState(ctx, &data, serverResp.JSON200)
resp.Diagnostics.Append(diag...)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
// Get user data script
userDataResp, err := r.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server user data: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
}
if userDataResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status %d reading server user data: name=%s, id=%s", userDataResp.StatusCode(), data.Name.ValueString(), data.Id.String()),
string(userDataResp.Body),
)
return
}
data.UserData = types.StringPointerValue(userDataResp.JSON200.UserData)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state serverModel
var plan, state serverResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
@@ -502,13 +529,44 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
createTimeout, diags := plan.Timeouts.Update(ctx, 20*time.Minute)
timeout, diags := plan.Timeouts.Update(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, createTimeout)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Rename operation
if !plan.Name.Equal(state.Name) {
rebuildNeeded := len(attrsRequiringRebuild(&plan, &state)) > 0
refreshNeeded := false
defer (func() {
if !refreshNeeded {
return
}
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, state.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server: id=%s, name=%s", state.Id.String(), state.Name.ValueString()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status code %s reading server: name=%s, id=%s", serverResp.Status(), state.Name.ValueString(), state.Id.String()),
string(serverResp.Body),
)
return
}
diag := setServerResourceState(ctx, &state, serverResp.JSON200)
resp.Diagnostics.Append(diag...)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
})()
// Rename
if !plan.Name.Equal(state.Name) && !rebuildNeeded {
renameResp, err := r.bc.client.PostServersServerIdActionsRenameWithResponse(
ctx,
state.Id.ValueInt64(),
@@ -542,45 +600,80 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
state.Name = types.StringValue(plan.Name.ValueString())
}
// Resize operation
var resizeReq *binarylane.PostServersServerIdActionsResizeJSONRequestBody
// Change network
if !plan.VpcId.Equal(state.VpcId) {
networkResp, err := r.bc.client.PostServersServerIdActionsChangeNetworkWithResponse(
ctx,
state.Id.ValueInt64(),
binarylane.PostServersServerIdActionsChangeNetworkJSONRequestBody{
Type: "change_network",
VpcId: plan.VpcId.ValueInt64Pointer(),
},
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error changing network for server: server_id=%s", state.Id.String()),
err.Error())
return
}
if networkResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code changing network for server",
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)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to change network", err.Error())
return
}
state.VpcId = plan.VpcId
refreshNeeded = true // Refresh to get new private IP addresses
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}
if !plan.Size.Equal(state.Size) {
resizeReq = &binarylane.PostServersServerIdActionsResizeJSONRequestBody{
// Resize operation
if !plan.Size.Equal(state.Size) || !plan.PublicIpv4Count.Equal(state.PublicIpv4Count) || !plan.Image.Equal(state.Image) {
resizeReq := &binarylane.PostServersServerIdActionsResizeJSONRequestBody{
Type: "resize",
}
resizeReq.Size = plan.Size.ValueStringPointer()
}
if !plan.PublicIpv4Count.Equal(state.PublicIpv4Count) {
if resizeReq == nil {
resizeReq = &binarylane.PostServersServerIdActionsResizeJSONRequestBody{
Type: "resize",
if !plan.Size.Equal(state.Size) {
resizeReq.Size = plan.Size.ValueStringPointer()
state.Size = plan.Size
}
if !plan.PublicIpv4Count.Equal(state.PublicIpv4Count) {
resizeReq.Options = &binarylane.ChangeSizeOptionsRequest{
Ipv4Addresses: plan.PublicIpv4Count.ValueInt32Pointer(),
}
if plan.PublicIpv4Count.ValueInt32() < state.PublicIpv4Count.ValueInt32() {
currentIps := []string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &currentIps, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
removedIps := currentIps[plan.PublicIpv4Count.ValueInt32():state.PublicIpv4Count.ValueInt32()]
resizeReq.Options.Ipv4AddressesToRemove = &removedIps
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
state.PublicIpv4Count = plan.PublicIpv4Count
state.PublicIpv4Addresses = plan.PublicIpv4Addresses
}
if !plan.Image.Equal(state.Image) {
resizeReq.ChangeImage = &binarylane.ChangeImage{
Image: plan.Image.ValueStringPointer(),
}
state.Image = plan.Image
}
resizeReq.Options = &binarylane.ChangeSizeOptionsRequest{
Ipv4Addresses: plan.PublicIpv4Count.ValueInt32Pointer(),
}
if plan.PublicIpv4Count.ValueInt32() < state.PublicIpv4Count.ValueInt32() {
currentIps := []string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &currentIps, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
removedIps := currentIps[plan.PublicIpv4Count.ValueInt32():state.PublicIpv4Count.ValueInt32()]
resizeReq.Options.Ipv4AddressesToRemove = &removedIps
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
}
if resizeReq != nil {
tflog.Info(ctx, fmt.Sprintf("Resizing server: server_id=%s", state.Id.String()))
resizeResp, err := r.bc.client.PostServersServerIdActionsResizeWithResponse(
ctx,
state.Id.ValueInt64(),
@@ -600,81 +693,91 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse
retryLoop:
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return
}
resp.Diagnostics.AddError(
"Timed out waiting for server to be resized",
fmt.Sprintf(
"Timed out waiting for server %s to be resized, last response was status=%s, body: %s",
plan.Name.ValueString(),
lastReadyResp.Status(),
lastReadyResp.Body,
),
)
return
default:
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, state.Id.ValueInt64(), *resizeResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error())
return
}
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
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
// success
state.Size = types.StringValue(plan.Size.ValueString())
state.PublicIpv4Count = types.Int32Value(plan.PublicIpv4Count.ValueInt32())
state.PublicIpv4Addresses, diags = plan.PublicIpv4Addresses.ToListValue(ctx)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if state.PublicIpv4Addresses.IsUnknown() {
// New IPs may have been allocated, so we need to check the server again
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, state.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError("Error checking IPs for server after resize", err.Error())
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError("Unexpected HTTP status code checking IPs for server after resize",
fmt.Sprintf("Received %s checking IPs for server after resize: name=%s. Details: %s", serverResp.Status(), state.Name.ValueString(), serverResp.Body))
return
}
var publicIpv4Addresses []string
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
}
}
state.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
}
break retryLoop
}
lastReadyResp = readyResp
tflog.Debug(ctx, fmt.Sprintf("Waiting for server to be resized: name=%s, status=%s, details: %s", state.Name.ValueString(), readyResp.Status(), readyResp.Body))
}
time.Sleep(time.Second * 5)
if state.PublicIpv4Addresses.IsUnknown() || listContainsUnknown(ctx, state.PublicIpv4Addresses) {
// New IPs may have been allocated, so we need to check the server again
refreshNeeded = true
}
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
// Rebuild operation
if !plan.SshKeys.Equal(state.SshKeys) || !plan.UserData.IsNull() && !plan.UserData.Equal(state.UserData) {
var rebuildReq *binarylane.PostServersServerIdActionsRebuildJSONRequestBody
sshKeys := []int{}
diags := plan.SshKeys.ElementsAs(ctx, &sshKeys, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
rebuildReq = &binarylane.PostServersServerIdActionsRebuildJSONRequestBody{
Type: "rebuild",
Options: &binarylane.ImageOptions{
Name: plan.Name.ValueStringPointer(),
Password: plan.Password.ValueStringPointer(),
UserData: plan.UserData.ValueStringPointer(),
SshKeys: &sshKeys,
},
}
rebuildResp, err := r.bc.client.PostServersServerIdActionsRebuildWithResponse(
ctx,
state.Id.ValueInt64(),
*rebuildReq,
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error rebuilding server: server_id=%s", state.Id.String()),
err.Error(),
)
return
}
if rebuildResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code rebuilding server",
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)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error())
return
}
state.Name = plan.Name
state.Password = plan.Password
state.UserData = plan.UserData
state.SshKeys = plan.SshKeys
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
// Check source_and_destination_check
if !plan.SourceAndDestinationCheck.Equal(state.SourceAndDestinationCheck) {
if !plan.SourceAndDestinationCheck.IsNull() {
err := r.updateSourceDestCheck(ctx, state.Id.ValueInt64(), plan.SourceAndDestinationCheck.ValueBool())
if err != nil {
resp.Diagnostics.AddError("Error updating source and destination check", err.Error())
return
}
}
state.SourceAndDestinationCheck = plan.SourceAndDestinationCheck
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
}
func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data serverModel
var data serverResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
@@ -707,42 +810,174 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
func (r *serverResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
// Import by ID
id, err := strconv.ParseInt(req.ID, 10, 32)
if err == nil {
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
resp.Diagnostics.Append(diags...)
} else {
name := req.ID
params := binarylane.GetServersParams{
Hostname: &name,
}
return
}
serverResp, err := r.bc.client.GetServersWithResponse(ctx, &params)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error getting server: hostname=%s", name), err.Error())
return
}
// Import by name
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code getting server",
fmt.Sprintf("Received %s reading server: hostname=%s. Details: %s", serverResp.Status(), name,
serverResp.Body))
return
}
name := req.ID
params := binarylane.GetServersParams{
Hostname: &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",
fmt.Sprintf("Error finding server: hostname=%s", name),
serverResp, err := r.bc.client.GetServersWithResponse(ctx, &params)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error getting server: hostname=%s", name), err.Error())
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code getting server",
fmt.Sprintf("Received %s reading server: hostname=%s. Details: %s", serverResp.Status(), name,
serverResp.Body))
return
}
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",
fmt.Sprintf("Error finding server: hostname=%s", name),
)
return
}
server := servers[idx]
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id))
resp.Diagnostics.Append(diags...)
}
func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64, actionId int64) error {
var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d", serverId, actionId)
} else {
return fmt.Errorf("timed out waiting for server action: server_id=%d, action_id=%d, last response was status=%s, body: %s",
serverId, actionId, lastReadyResp.Status(), lastReadyResp.Body)
}
default:
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, serverId, actionId)
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.CompletedAt != nil {
return nil
}
lastReadyResp = readyResp
tflog.Debug(ctx,
fmt.Sprintf("waiting for server action for server_id=%d, action_id=%d: last response was status=%s, details: %s",
serverId, actionId, readyResp.Status(), readyResp.Body,
),
)
return
}
server := servers[idx]
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id))
resp.Diagnostics.Append(diags...)
time.Sleep(time.Second * 5)
}
}
func attrsRequiringRebuild(plan *serverResourceModel, state *serverResourceModel) []string {
attrs := []string{}
if !plan.SshKeys.Equal(state.SshKeys) {
attrs = append(attrs, "ssh_keys")
}
if !plan.Image.Equal(state.Image) {
attrs = append(attrs, "image")
}
if !plan.UserData.IsNull() && !plan.UserData.Equal(state.UserData) {
attrs = append(attrs, "user_data")
}
return attrs
}
func (r *serverResource) updateSourceDestCheck(
ctx context.Context,
serverId int64,
sourceDestCheckEnabled bool,
) error {
tflog.Info(ctx, fmt.Sprintf("Changing source and destination check for server: server_id=%d, enabled=%t",
serverId, sourceDestCheckEnabled))
sourceDestCheckResp, err := r.bc.client.PostServersServerIdActionsChangeSourceAndDestinationCheckWithResponse(
ctx,
serverId,
binarylane.PostServersServerIdActionsChangeSourceAndDestinationCheckJSONRequestBody{
Type: "change_source_and_destination_check",
Enabled: sourceDestCheckEnabled,
},
)
if err != nil {
return fmt.Errorf("error changing source and destination check for server: server_id=%d, error: %w", serverId, err)
}
if sourceDestCheckResp.StatusCode() != http.StatusOK {
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)
if err != nil {
return fmt.Errorf("error changing source and destination check: %w", err)
}
return nil
}
func setServerResourceState(ctx context.Context, data *serverResourceModel, serverResp *binarylane.ServerResponse) diag.Diagnostics {
var diags diag.Diagnostics
data.Id = types.Int64Value(*serverResp.Server.Id)
data.Name = types.StringValue(*serverResp.Server.Name)
data.Image = types.StringValue(*serverResp.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.Server.NextBackupWindow != nil)
data.PortBlocking = types.BoolValue(serverResp.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.Server.PasswordChangeSupported)
data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.Server.Networks.SourceAndDestinationCheck)
publicIpv4Addresses := []string{}
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
tfPublicIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PublicIpv4Addresses = types.ListUnknown(data.PublicIpv4Addresses.ElementType(ctx))
data.PublicIpv4Count = types.Int32Unknown()
} else {
data.PublicIpv4Addresses = tfPublicIpv4Addresses
data.PublicIpv4Count = types.Int32Value(int32(len(publicIpv4Addresses)))
}
tfPrivateIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
diags.Append(diag...)
if diag.HasError() {
data.PrivateIPv4Addresses = types.ListUnknown(data.PrivateIPv4Addresses.ElementType(ctx))
} else {
data.PrivateIPv4Addresses = tfPrivateIpv4Addresses
}
return diags
}
+56 -42
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"log"
"net/http"
"os"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
@@ -25,6 +24,9 @@ func TestServerResource(t *testing.T) {
}
password := base64.URLEncoding.EncodeToString(pw_bytes)
sshPublicKeyInitial := GeneratePublicKey(t)
sshPublicKeyUpdated := GeneratePublicKey(t)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
@@ -37,27 +39,28 @@ resource "binarylane_vpc" "test" {
ip_range = "10.240.0.0/16"
}
resource "binarylane_ssh_key" "test" {
name = "tf-test-server-resource"
public_key = "` + GeneratePublicKey(t) + `"
resource "binarylane_ssh_key" "initial" {
name = "tf-test-server-resource-initial"
public_key = "` + sshPublicKeyInitial + `"
default = true
}
resource "binarylane_ssh_key" "unused" {
name = "tf-test-server-resource-unused"
public_key = "` + GeneratePublicKey(t) + `"
resource "binarylane_ssh_key" "updated" {
name = "tf-test-server-resource-updated"
public_key = "` + sshPublicKeyUpdated + `"
default = true
}
resource "binarylane_server" "test" {
name = "tf-test-server-resource"
region = "per"
image = "debian-12"
image = "debian-11"
size = "std-min"
password = "` + password + `"
vpc_id = binarylane_vpc.test.id
public_ipv4_count = 1
ssh_keys = [binarylane_ssh_key.test.id]
ssh_keys = [binarylane_ssh_key.initial.id]
source_and_destination_check = false
user_data = <<EOT
#cloud-config
echo "Hello World" > /var/tmp/output.txt
@@ -75,7 +78,7 @@ data "binarylane_server" "test" {
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-11"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"),
@@ -86,17 +89,22 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "private_ipv4_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"), // Only the test SSH key should be registered
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"),
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"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "id", "binarylane_server.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
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.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
`),
),
},
// Test import by ID
@@ -104,7 +112,7 @@ echo "Hello World" > /var/tmp/output.txt
ResourceName: "binarylane_server.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "user_data", "public_ipv4_count", "timeouts"},
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "timeouts"},
},
// Test import by name
{
@@ -112,7 +120,7 @@ echo "Hello World" > /var/tmp/output.txt
ImportState: true,
ImportStateId: "tf-test-server-resource",
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "user_data", "public_ipv4_count", "timeouts"},
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "timeouts"},
},
// Update and Read testing
{
@@ -122,15 +130,15 @@ resource "binarylane_vpc" "test" {
ip_range = "10.240.0.0/16"
}
resource "binarylane_ssh_key" "test" {
name = "tf-test-server-resource"
public_key = "` + GeneratePublicKey(t) + `"
resource "binarylane_ssh_key" "initial" {
name = "tf-test-server-resource-initial"
public_key = "` + sshPublicKeyInitial + `"
default = true
}
resource "binarylane_ssh_key" "unused" {
name = "tf-test-server-resource-unused"
public_key = "` + GeneratePublicKey(t) + `"
resource "binarylane_ssh_key" "updated" {
name = "tf-test-server-resource-updated"
public_key = "` + sshPublicKeyUpdated + `"
default = true
}
@@ -140,12 +148,15 @@ resource "binarylane_server" "test" {
image = "debian-12"
size = "std-1vcpu"
password = "` + password + `"
vpc_id = binarylane_vpc.test.id
vpc_id = null
public_ipv4_count = 0
ssh_keys = [binarylane_ssh_key.test.id]
ssh_keys = [binarylane_ssh_key.updated.id]
# source_and_destination_check = null # defaults to null when vpc_id is null
user_data = <<EOT
#cloud-config
echo "Hello World" > /var/tmp/output.txt
echo "Hello Whitespace" > /var/tmp/output.txt
EOT
}
`,
@@ -153,6 +164,18 @@ EOT
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-test-server-resource-2"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-1vcpu"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "0"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "0"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckNoResourceAttr("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"),
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"),
resource.TestCheckNoResourceAttr("binarylane_server.test", "source_and_destination_check"),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
`#cloud-config
echo "Hello Whitespace" > /var/tmp/output.txt
`),
),
},
},
@@ -163,16 +186,7 @@ func init() {
resource.AddTestSweepers("server", &resource.Sweeper{
Name: "server",
F: func(_ string) error {
endpoint := os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
token := os.Getenv("BINARYLANE_API_TOKEN")
client, err := binarylane.NewClientWithAuth(
endpoint,
token,
)
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
@@ -190,16 +204,16 @@ func init() {
PerPage: &perPage,
}
serverResp, err := client.GetServersWithResponse(ctx, &params)
listResp, err := client.GetServersWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting servers for test sweep: %w", err)
}
if serverResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", serverResp.Body)
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", listResp.Body)
}
servers := *serverResp.JSON200.Servers
servers := *listResp.JSON200.Servers
for _, s := range servers {
if strings.HasPrefix(*s.Name, "tf-test-") {
reason := "Terraform deletion"
@@ -207,17 +221,17 @@ func init() {
Reason: &reason,
}
serverResp, 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)
}
if serverResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", serverResp.StatusCode(), *s.Id, serverResp.Body)
if deleteResp.StatusCode() != http.StatusNoContent {
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)
}
}
if serverResp.JSON200.Links == nil || serverResp.JSON200.Links.Pages == nil || serverResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+20 -15
View File
@@ -175,22 +175,28 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
}
func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data sshKeyModel
var plan, state sshKeyModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(data.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
Name: data.Name.ValueString(),
Default: data.Default.ValueBoolPointer(),
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", data.Name.ValueString()),
fmt.Sprintf("Error updating SSH Key: name=%s", plan.Name.ValueString()),
err.Error(),
)
return
@@ -198,30 +204,29 @@ func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest,
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(), data.Name.ValueString(), sshResp.Body),
fmt.Sprintf("Received %s updating new SSH Key: name=%s. Details: %s", sshResp.Status(), plan.Name.ValueString(), sshResp.Body),
)
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data sshKeyModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
var state sshKeyModel
// Read Terraform prior state into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
sshResp, err := r.bc.client.DeleteAccountKeysKeyIdWithResponse(ctx, int(data.Id.ValueInt64()))
sshResp, err := r.bc.client.DeleteAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()))
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting SSH Key: name=%s", data.Name.ValueString()),
fmt.Sprintf("Error deleting SSH Key: name=%s", state.Name.ValueString()),
err.Error(),
)
return
@@ -229,7 +234,7 @@ func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest,
if sshResp.StatusCode() != http.StatusNoContent {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting SSH Key",
fmt.Sprintf("Received %s deleting SSH Key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
fmt.Sprintf("Received %s deleting SSH Key: name=%s. Details: %s", sshResp.Status(), state.Name.ValueString(), sshResp.Body),
)
return
}
+9 -19
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"log"
"net/http"
"os"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
@@ -118,16 +117,7 @@ func init() {
resource.AddTestSweepers("ssh_key", &resource.Sweeper{
Name: "ssh_key",
F: func(_ string) error {
endpoint := os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
token := os.Getenv("BINARYLANE_API_TOKEN")
client, err := binarylane.NewClientWithAuth(
endpoint,
token,
)
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
@@ -145,30 +135,30 @@ func init() {
PerPage: &perPage,
}
keyResp, err := client.GetAccountKeysWithResponse(ctx, &params)
listResp, err := client.GetAccountKeysWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting SSH keys for test sweep: %w", err)
}
if keyResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting SSH keys for test sweep: %s", keyResp.Body)
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting SSH keys for test sweep: %s", listResp.Body)
}
keys := keyResp.JSON200.SshKeys
keys := listResp.JSON200.SshKeys
for _, k := range keys {
if strings.HasPrefix(*k.Name, "tf-test-") {
keyResp, 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)
}
if keyResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting SSH key %d for test sweep: %s", keyResp.StatusCode(), *k.Id, keyResp.Body)
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)
}
log.Println("Deleted SSH key for test sweep:", *k.Id)
}
}
if keyResp.JSON200.Links == nil || keyResp.JSON200.Links.Pages == nil || keyResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+28
View File
@@ -1,6 +1,7 @@
package provider
import (
"context"
"fmt"
"slices"
@@ -153,3 +154,30 @@ func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema, cfg AttributeCo
return &ds, nil
}
func listContainsUnknown(ctx context.Context, list types.List) bool {
// If the whole list is unknown, return true
if list.IsUnknown() {
return true
}
// Get elements as generic attr.Value to check individual unknown status
var elements []attr.Value
diags := list.ElementsAs(ctx, &elements, false)
if diags.HasError() {
return true // Assume unknown in case of errors
}
// Check if any element is unknown
for _, elem := range elements {
if elem.IsUnknown() {
return true
}
}
return false
}
func Pointer[T any](d T) *T {
return &d
}
+64
View File
@@ -1,6 +1,12 @@
package provider
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -117,3 +123,61 @@ resource "binarylane_vpc_route_entries" "test" {
},
})
}
func init() {
resource.AddTestSweepers("vpc", &resource.Sweeper{
Name: "vpc",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
ctx := context.Background()
var page int32 = 1
perPage := int32(200)
nextPage := true
for nextPage {
params := binarylane.GetVpcsParams{
Page: &page,
PerPage: &perPage,
}
listResp, err := client.GetVpcsWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting VPCs for test sweep: %w", err)
}
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting VPCs for test sweep: %s", listResp.Body)
}
vpcs := listResp.JSON200.Vpcs
for _, vpc := range *vpcs {
if strings.HasPrefix(*vpc.Name, "tf-test-") {
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, *vpc.Id)
if err != nil {
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)
}
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 {
nextPage = false
break
}
page++
}
return nil
},
})
}