forked from mirrors/terraform-provider-binarylane
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd85f67e28 | |||
| 50d06b967b | |||
| e6c50a470d | |||
| 2bc281d177 |
@@ -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.
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
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
|
||||
|
||||
@@ -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=
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9514,17 +9514,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": [
|
||||
@@ -11306,15 +11299,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 +12477,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": [
|
||||
|
||||
@@ -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.
|
||||
@@ -1662,23 +1651,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 +2154,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
|
||||
|
||||
@@ -3985,130 +3952,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 +4014,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
|
||||
|
||||
@@ -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, ¶ms)
|
||||
listResp, err := client.GetLoadBalancersWithResponse(ctx, ¶ms)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -151,8 +151,23 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
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)...)
|
||||
// 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)...)
|
||||
|
||||
@@ -6,15 +6,18 @@ 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/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
@@ -48,13 +51,14 @@ type serverResource struct {
|
||||
|
||||
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"`
|
||||
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
|
||||
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
|
||||
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
|
||||
SourceAndDestinationCheck types.Bool `tfsdk:"source_and_destination_check"`
|
||||
Permalink types.String `tfsdk:"permalink"`
|
||||
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 +111,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 +129,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"]
|
||||
@@ -156,8 +156,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 +201,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,38 +270,70 @@ 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() {
|
||||
return
|
||||
}
|
||||
|
||||
var plan, state serverModel
|
||||
|
||||
if req.Plan.Raw.IsNull() {
|
||||
// Destruction plan, no modification needed
|
||||
return
|
||||
}
|
||||
// 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, ¤tIps, 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
|
||||
}
|
||||
|
||||
// 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)...)
|
||||
}
|
||||
|
||||
@@ -296,6 +347,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 +399,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 +428,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,60 +449,18 @@ 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) {
|
||||
@@ -449,7 +484,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()),
|
||||
@@ -468,10 +502,10 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
|
||||
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
|
||||
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
|
||||
data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
|
||||
|
||||
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)
|
||||
@@ -483,11 +517,30 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
resp.Diagnostics.Append(diags...)
|
||||
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
data.PublicIpv4Count = types.Int32Value(int32(len(publicIpv4Addresses)))
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// 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)...)
|
||||
}
|
||||
@@ -502,13 +555,15 @@ 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
|
||||
|
||||
// Rename
|
||||
if !plan.Name.Equal(state.Name) && !rebuildNeeded {
|
||||
renameResp, err := r.bc.client.PostServersServerIdActionsRenameWithResponse(
|
||||
ctx,
|
||||
state.Id.ValueInt64(),
|
||||
@@ -543,44 +598,44 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
|
||||
// Resize operation
|
||||
var resizeReq *binarylane.PostServersServerIdActionsResizeJSONRequestBody
|
||||
|
||||
if !plan.Size.Equal(state.Size) {
|
||||
resizeReq = &binarylane.PostServersServerIdActionsResizeJSONRequestBody{
|
||||
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, ¤tIps, 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, ¤tIps, 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(),
|
||||
@@ -599,74 +654,106 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
fmt.Sprintf("Received %s resizing server: server_id=%s. Details: %s", resizeResp.Status(), state.Id.String(), resizeResp.Body))
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// Success
|
||||
|
||||
if state.PublicIpv4Addresses.IsUnknown() || listContainsUnknown(ctx, state.PublicIpv4Addresses) {
|
||||
// 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
|
||||
}
|
||||
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...)
|
||||
}
|
||||
|
||||
// Save updated data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -674,7 +761,6 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
|
||||
// Read Terraform prior state data into the model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
@@ -707,42 +793,127 @@ 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, ¶ms)
|
||||
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, ¶ms)
|
||||
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 *serverModel, state *serverModel) []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
|
||||
}
|
||||
|
||||
@@ -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,21 @@ 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.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 +111,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 +119,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 +129,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
|
||||
}
|
||||
|
||||
@@ -142,10 +149,13 @@ resource "binarylane_server" "test" {
|
||||
password = "` + password + `"
|
||||
vpc_id = binarylane_vpc.test.id
|
||||
public_ipv4_count = 0
|
||||
ssh_keys = [binarylane_ssh_key.test.id]
|
||||
ssh_keys = [binarylane_ssh_key.updated.id]
|
||||
# source_and_destination_check = true # defaults to true
|
||||
user_data = <<EOT
|
||||
#cloud-config
|
||||
echo "Hello World" > /var/tmp/output.txt
|
||||
echo "Hello Whitespace" > /var/tmp/output.txt
|
||||
|
||||
|
||||
EOT
|
||||
}
|
||||
`,
|
||||
@@ -153,6 +163,17 @@ 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.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"),
|
||||
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "source_and_destination_check", "true"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
|
||||
`#cloud-config
|
||||
echo "Hello Whitespace" > /var/tmp/output.txt
|
||||
|
||||
|
||||
`),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -163,16 +184,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 +202,16 @@ func init() {
|
||||
PerPage: &perPage,
|
||||
}
|
||||
|
||||
serverResp, err := client.GetServersWithResponse(ctx, ¶ms)
|
||||
listResp, err := client.GetServersWithResponse(ctx, ¶ms)
|
||||
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 +219,17 @@ func init() {
|
||||
Reason: &reason,
|
||||
}
|
||||
|
||||
serverResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, ¶ms)
|
||||
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, ¶ms)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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, ¶ms)
|
||||
listResp, err := client.GetAccountKeysWithResponse(ctx, ¶ms)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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, ¶ms)
|
||||
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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user