mirror of
https://github.com/oscarhermoso/terraform-provider-binarylane
synced 2026-10-20 05:56:05 +11:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c50a470d | |||
| 2bc281d177 |
@@ -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,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"terraform-provider-binarylane/internal/resources"
|
||||
"time"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"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"
|
||||
@@ -107,7 +109,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 +127,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 +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)),
|
||||
},
|
||||
@@ -266,23 +263,38 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
|
||||
// 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...)
|
||||
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 {
|
||||
plan.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, append(currentIps, make([]*string, ipv4CountPlan-ipv4CountState)...))
|
||||
resp.Diagnostics.Append(diags...)
|
||||
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 +308,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,35 +360,6 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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)
|
||||
|
||||
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...)
|
||||
|
||||
// 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 {
|
||||
@@ -386,46 +374,38 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
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)
|
||||
err = r.waitForServerAction(ctx, *serverResp.JSON200.Server.Id, createActionId, "create")
|
||||
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)
|
||||
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)
|
||||
|
||||
publicIpv4Addresses := []string{}
|
||||
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...)
|
||||
|
||||
// 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 +429,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()),
|
||||
@@ -469,9 +448,8 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
|
||||
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
|
||||
|
||||
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 +461,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 +499,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 +542,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 +598,90 @@ 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, "resize")
|
||||
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, "rebuild")
|
||||
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)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
@@ -674,7 +689,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 +721,91 @@ 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),
|
||||
)
|
||||
return
|
||||
}
|
||||
server := servers[idx]
|
||||
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
|
||||
}
|
||||
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id))
|
||||
resp.Diagnostics.Append(diags...)
|
||||
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, actionType string) error {
|
||||
var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if lastReadyResp == nil {
|
||||
return fmt.Errorf("timed out waiting for server %d to %s", serverId, actionType)
|
||||
} else {
|
||||
return fmt.Errorf("timed out waiting for server %d to %s, last response was status=%s, body: %s", serverId, actionType, 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 %d to %s: %w", serverId, actionType, err)
|
||||
}
|
||||
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored {
|
||||
return fmt.Errorf("server %d failed to %s with error: %s", serverId, actionType, *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 %d to %s: last response was status=%s, details: %s", serverId, actionType, readyResp.Status(), readyResp.Body))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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,27 @@ 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]
|
||||
user_data = <<EOT
|
||||
#cloud-config
|
||||
echo "Hello World" > /var/tmp/output.txt
|
||||
@@ -75,7 +77,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 +88,20 @@ 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"),
|
||||
|
||||
// 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 +109,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 +117,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 +127,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 +147,12 @@ 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]
|
||||
user_data = <<EOT
|
||||
#cloud-config
|
||||
echo "Hello World" > /var/tmp/output.txt
|
||||
echo "Hello Whitespace" > /var/tmp/output.txt
|
||||
|
||||
|
||||
EOT
|
||||
}
|
||||
`,
|
||||
@@ -153,6 +160,16 @@ 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", "user_data", // test extra whitespace
|
||||
`#cloud-config
|
||||
echo "Hello Whitespace" > /var/tmp/output.txt
|
||||
|
||||
|
||||
`),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -163,16 +180,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 +198,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 +215,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,26 @@ 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
|
||||
}
|
||||
|
||||
@@ -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