Compare commits

..

2 Commits

Author SHA1 Message Date
Oscar Hermoso 9a58a8112c 👍 Support vpc_ipv4_address, bump BinaryLane OpenAPI schema to v0.34.0 (#114)
* 👍 Bump BinaryLane OpenAPI schema to v0.34.0

* 💄 Support change of IPv4 addresses
2026-02-08 16:58:30 +08:00
Oscar Hermoso 08a37caf51 😹 Fix gh release edit number of args 2025-10-05 13:45:19 +08:00
25 changed files with 946 additions and 967 deletions
+3 -3
View File
@@ -6,7 +6,7 @@ name: Release
on:
push:
tags:
- 'v*'
- "v*"
# Releases need permissions to read and write the repository contents.
# GitHub considers creating releases and uploading assets as writing contents.
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"
cache: true
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
@@ -47,6 +47,6 @@ jobs:
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
- name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
run: gh release edit "${{ github.ref }}" --draft false
run: gh release edit "${{ github.ref }}" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -53,6 +53,7 @@ data "binarylane_server" "example" {
- `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.
- `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
<a id="nestedatt--advanced_features"></a>
### Nested Schema for `advanced_features`
+1
View File
@@ -58,6 +58,7 @@ resource "binarylane_server" "example" {
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `user_data` (String) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
### Read-Only
+41
View File
@@ -0,0 +1,41 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore SSH keys
.id_ed25519*
.kube
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -82,9 +82,9 @@ func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
resp.Diagnostics.AddError("Unexpected status code listing images", string(listResp.Body))
return
}
imgResults = append(imgResults, *listResp.JSON200.Images...)
imgResults = append(imgResults, listResp.JSON200.Images...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
@@ -96,7 +96,7 @@ func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRe
return
}
diags := setLoadBalancerModelState(ctx, &data, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
+13 -13
View File
@@ -209,7 +209,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
return
}
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -220,9 +220,9 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
// Wait for load balancer to be ready
var actionId int64
for _, action := range *lbResp.JSON200.Links.Actions {
if *action.Rel == "create_load_balancer" {
actionId = *action.Id
for _, action := range lbResp.JSON200.Links.Actions {
if action.Rel == "create_load_balancer" {
actionId = action.Id
break
}
}
@@ -268,7 +268,7 @@ func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadReques
return
}
diags := setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer)
diags := setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -337,7 +337,7 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
return
}
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, lbResp.JSON200.LoadBalancer)
diags = setLoadBalancerModelState(ctx, &data.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -345,9 +345,9 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
// Wait for load balancer to be ready
var actionId int64
for _, action := range *lbResp.JSON200.Links.Actions {
if *action.Rel == "update_load_balancer" {
actionId = *action.Id
for _, action := range lbResp.JSON200.Links.Actions {
if action.Rel == "update_load_balancer" {
actionId = action.Id
break
}
}
@@ -429,7 +429,7 @@ func (r *loadBalancerResource) ImportState(
return
}
loadBalancers := *lbResp.JSON200.LoadBalancers
loadBalancers := lbResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if lb.Name == req.ID {
loadBalancer = lb
@@ -437,7 +437,7 @@ func (r *loadBalancerResource) ImportState(
break
}
}
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil {
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
@@ -459,7 +459,7 @@ func setLoadBalancerModelState(ctx context.Context, data *loadBalancerDataModel,
if lb.Region == nil {
data.Region = types.StringNull()
} else {
data.Region = types.StringValue(*lb.Region.Slug)
data.Region = types.StringValue(lb.Region.Slug)
}
data.ServerIds, diags = types.SetValueFrom(ctx, types.Int64Type, lb.ServerIds)
@@ -499,7 +499,7 @@ func (r *loadBalancerResource) waitForAction(ctx context.Context, actionId int64
if err != nil {
return fmt.Errorf("unexpected error waiting for action: action_id=%d, error: %w", actionId, err)
}
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored {
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.Status == binarylane.Errored {
return fmt.Errorf("action failed with error: action_id=%d, error: %s", actionId, readyResp.Body)
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
@@ -182,7 +182,7 @@ func init() {
return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", listResp.Body)
}
loadBalancers := *listResp.JSON200.LoadBalancers
loadBalancers := listResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if strings.HasPrefix(lb.Name, "tf-test-") {
deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, lb.Id)
@@ -195,7 +195,7 @@ func init() {
log.Println("Deleted load balancer during test sweep:", lb.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *regionsDataSource) Read(ctx context.Context, req datasource.ReadRequest
}
regResults = append(regResults, listResp.JSON200.Regions...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+20 -7
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"slices"
"strings"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -105,21 +106,33 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
if serverResp.JSON200.Server.VpcId == nil {
data.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
data.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
+157 -51
View File
@@ -186,10 +186,9 @@ func serverSchema(ctx context.Context) schema.Schema {
}
// Additional attributes
pwDescription :=
"If this is provided the specified or default remote user's account password will be set to this value. " +
"Only valid if the server supports password change actions. If omitted and the server supports password " +
"change actions a random password will be generated and emailed to the account email address."
pwDescription := "If this is provided the specified or default remote user's account password will be set to this value. " +
"Only valid if the server supports password change actions. If omitted and the server supports password " +
"change actions a random password will be generated and emailed to the account email address."
s.Attributes["password"] = schema.StringAttribute{
Description: pwDescription,
MarkdownDescription: pwDescription,
@@ -364,11 +363,29 @@ func serverSchema(ctx context.Context) schema.Schema {
},
}
s.Attributes["timeouts"] =
timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
Update: true,
})
s.Attributes["timeouts"] = timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
Update: true,
})
vpcIpv4Addr := s.Attributes["vpc_ipv4_address"]
vpcIpv4AddrDescription := "If provided this will be the IPv4 address for the server's private VPC network adapter." +
" If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id`" +
" is provided."
s.Attributes["vpc_ipv4_address"] = schema.StringAttribute{
Description: vpcIpv4AddrDescription,
MarkdownDescription: vpcIpv4AddrDescription,
Optional: vpcIpv4Addr.IsOptional(),
Computed: vpcIpv4Addr.IsComputed(),
Validators: []validator.String{
stringvalidator.AlsoRequires(path.Expressions{
path.MatchRoot("vpc_id"),
}...),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
}
return s
}
@@ -401,6 +418,15 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
if plan.VpcIpv4Address.IsUnknown() {
if plan.VpcId.IsNull() {
plan.VpcIpv4Address = types.StringNull()
} else {
plan.VpcIpv4Address = types.StringUnknown()
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
if req.State.Raw.IsNull() {
// Creation plan, no further modification needed
return
@@ -412,7 +438,10 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
return
}
if !plan.VpcId.Equal(state.VpcId) {
if !plan.VpcId.Equal(state.VpcId) || !plan.VpcIpv4Address.Equal(state.VpcIpv4Address) {
if config.VpcIpv4Address.IsNull() {
plan.VpcIpv4Address = types.StringUnknown()
}
plan.PrivateIPv4Addresses = types.ListUnknown(plan.PrivateIPv4Addresses.ElementType(ctx))
}
@@ -481,7 +510,7 @@ func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
debugAdvFeatResp, _ := json.Marshal(advFeatResp.JSON200)
tflog.Debug(ctx, fmt.Sprintf("Recieved response for available advanced features: %s", debugAdvFeatResp))
availableAdvFeat := *advFeatResp.JSON200.AvailableAdvancedServerFeatures.AdvancedFeatures
availableAdvFeat := advFeatResp.JSON200.AvailableAdvancedServerFeatures.AdvancedFeatures
if plan.AdvancedFeatures.EmulatedHyperv.ValueBool() && !slices.Contains(availableAdvFeat, "emulated-hyperv") {
resp.Diagnostics.AddAttributeError(
@@ -580,7 +609,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
if !data.Disk.IsNull() && !data.Disk.IsUnknown() {
body.Options.Disk = data.Disk.ValueInt32Pointer()
}
if !data.VpcIpv4Address.IsNull() && !data.VpcIpv4Address.IsUnknown() {
body.VpcIpv4Address = data.VpcIpv4Address.ValueStringPointer()
}
if data.Password.IsNull() {
data.Password = types.StringNull()
} else {
@@ -606,9 +637,9 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
for _, action := range serverResp.JSON200.Links.Actions {
if action.Rel == "create" {
createActionId = action.Id
break
}
}
@@ -618,29 +649,41 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
return
}
err = r.waitForServerAction(ctx, *serverResp.JSON200.Server.Id, createActionId)
err = r.waitForServerAction(ctx, serverResp.JSON200.Server.Id, createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be created", err.Error())
}
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Id = types.Int64Value(serverResp.JSON200.Server.Id)
data.Name = types.StringValue(serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
data.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
data.PasswordChangeSupported = types.BoolValue(serverResp.JSON200.Server.PasswordChangeSupported)
data.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
plannedSourceDestCheck := data.SourceAndDestinationCheck
serverRespSourceDestCheck := types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
data.SourceAndDestinationCheck = serverRespSourceDestCheck
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
if serverResp.JSON200.Server.VpcId == nil {
data.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
data.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
@@ -830,7 +873,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
fmt.Sprintf("Received %s changing network for server: server_id=%s. Details: %s", networkResp.Status(), state.Id.String(), networkResp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *networkResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), networkResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to change network", err.Error())
return
@@ -843,6 +886,55 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
}
// Change VPC IPv4 address
if !plan.VpcIpv4Address.Equal(state.VpcIpv4Address) && !plan.VpcIpv4Address.IsNull() && !plan.VpcIpv4Address.IsUnknown() {
// If VPC was just changed, IP address needs to be fetched so it can be sent in the request
if refreshNeeded {
diag := r.fetchServerResourceState(ctx, &state)
resp.Diagnostics.Append(diag...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}
// We might have got lucky - the new randomly assigned IP address might be the one we want, so check before making API call
if state.VpcIpv4Address.ValueString() != plan.VpcIpv4Address.ValueString() {
vpcIpv4Resp, err := r.bc.client.PostServersServerIdActionsChangeVpcIpv4WithResponse(
ctx,
state.Id.ValueInt64(),
binarylane.PostServersServerIdActionsChangeVpcIpv4JSONRequestBody{
Type: "change_vpc_ipv4",
CurrentIpv4Address: state.VpcIpv4Address.ValueString(),
NewIpv4Address: plan.VpcIpv4Address.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error changing VPC IPv4 address for server: server_id=%s", state.Id.String()),
err.Error())
return
}
if vpcIpv4Resp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code changing VPC IPv4 address for server",
fmt.Sprintf("Received %s changing VPC IPv4 address for server: server_id=%s. Details: %s", vpcIpv4Resp.Status(), state.Id.String(), vpcIpv4Resp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), vpcIpv4Resp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for VPC IPv4 address to change", err.Error())
return
}
state.VpcIpv4Address = plan.VpcIpv4Address
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
refreshNeeded = true // Refresh to populate private_ipv4_addresses
if resp.Diagnostics.HasError() {
return
}
}
}
// Resize operation
if !plan.Size.Equal(state.Size) ||
!plan.Memory.IsNull() && !plan.Memory.IsUnknown() && !plan.Memory.Equal(state.Memory) ||
@@ -919,7 +1011,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *resizeResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), resizeResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error())
return
@@ -936,7 +1028,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
}
// Change ipv6
// Change IPv6
if !plan.Ipv6.Equal(state.Ipv6) {
ipv6Resp, err := r.bc.client.PostServersServerIdActionsChangeIpv6WithResponse(
ctx,
@@ -951,7 +1043,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *ipv6Resp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), ipv6Resp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for changing IPv6", err.Error())
return
@@ -1010,7 +1102,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
fmt.Sprintf("Received %s rebuilding server: server_id=%s. Details: %s", rebuildResp.Status(), state.Id.String(), rebuildResp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *rebuildResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), rebuildResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error())
return
@@ -1038,7 +1130,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error resetting password", err.Error())
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *passwordResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), passwordResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for password reset", err.Error())
return
@@ -1092,7 +1184,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error enabling backups", err.Error())
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *backupResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), backupResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for backups to be enabled", err.Error())
return
@@ -1105,7 +1197,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error disabling backups", err.Error())
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *backupResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), backupResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for backups to be disabled", err.Error())
return
@@ -1132,7 +1224,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Error changing \"port_blocking\" attribute", err.Error())
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *portBlockingResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), portBlockingResp.JSON200.Action.Id)
if err != nil {
resp.Diagnostics.AddError("Error waiting for \"port_blocking\" attribute to change", err.Error())
return
@@ -1210,8 +1302,8 @@ func (r *serverResource) ImportState(
return
}
servers := *serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return *s.Name == name })
servers := serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return s.Name == name })
if idx == -1 {
resp.Diagnostics.AddError(
"Could not find server by hostname",
@@ -1221,7 +1313,7 @@ func (r *serverResource) ImportState(
}
server := servers[idx]
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id))
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(server.Id))
resp.Diagnostics.Append(diags...)
}
@@ -1242,7 +1334,7 @@ func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64
if err != nil {
return fmt.Errorf("unexpected error waiting for server action: server_id=%d, action_id=%d, error: %w", serverId, actionId, err)
}
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored {
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.Status == binarylane.Errored {
return fmt.Errorf("server action failed to with error: server_id=%d, action_id=%d, error: %s", serverId, actionId, readyResp.Body)
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
@@ -1298,7 +1390,7 @@ func (r *serverResource) updateSourceDestCheck(
return fmt.Errorf("unexpected HTTP status code changing source and destination check for server: server_id=%d, details: %s", serverId, sourceDestCheckResp.Body)
}
err = r.waitForServerAction(ctx, serverId, *sourceDestCheckResp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, serverId, sourceDestCheckResp.JSON200.Action.Id)
if err != nil {
return fmt.Errorf("error changing source and destination check: %w", err)
}
@@ -1327,23 +1419,35 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
}
}
state.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
state.Name = types.StringValue(*serverResp.JSON200.Server.Name)
state.Id = types.Int64Value(serverResp.JSON200.Server.Id)
state.Name = types.StringValue(serverResp.JSON200.Server.Name)
state.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
state.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
state.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
state.Region = types.StringValue(serverResp.JSON200.Server.Region.Slug)
state.Size = types.StringValue(serverResp.JSON200.Server.Size.Slug)
state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
state.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
state.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
state.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
state.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
state.PasswordChangeSupported = types.BoolValue(serverResp.JSON200.Server.PasswordChangeSupported)
state.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
state.Memory = types.Int32Value(*serverResp.JSON200.Server.Memory)
state.Disk = types.Int32Value(*serverResp.JSON200.Server.Disk)
state.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
state.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
state.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
state.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
advFeat := *serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
if serverResp.JSON200.Server.VpcId == nil {
state.VpcIpv4Address = types.StringNull()
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
state.VpcIpv4Address = types.StringValue(v4address.IpAddress)
break
}
}
}
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
state.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
@@ -1366,9 +1470,10 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
switch v4address.Type {
case "public":
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else if v4address.Type == "private" {
case "private":
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
@@ -1395,9 +1500,10 @@ func (r *serverResource) fetchServerResourceState(ctx context.Context, state *se
privateIpv6Addresses := []string{}
for _, v6address := range serverResp.JSON200.Server.Networks.V6 {
if v6address.Type == "public" {
switch v6address.Type {
case "public":
publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress)
} else {
case "private":
privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress)
}
}
@@ -1506,7 +1612,7 @@ func (r *serverResource) updateAdvancedFeatures(
return fmt.Errorf("unexpected HTTP status code updating advanced features for server: server_id=%d, details: %s", serverId, resp.Body)
}
err = r.waitForServerAction(ctx, serverId, *resp.JSON200.Action.Id)
err = r.waitForServerAction(ctx, serverId, resp.JSON200.Action.Id)
if err != nil {
return fmt.Errorf("failed to confirm advanced features for server was successful: %w", err)
}
+157 -8
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"log"
"net/http"
"regexp"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
@@ -81,6 +82,7 @@ data "binarylane_server" "test" {
resource.TestCheckResourceAttr("binarylane_server.test", "memory", "1152"),
resource.TestCheckResourceAttr("binarylane_server.test", "disk", "20"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_ipv4_address"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "password", password1),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
@@ -114,6 +116,7 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-11"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_ipv4_address"),
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
@@ -321,6 +324,153 @@ resource "binarylane_server" "test" {
})
}
func TestServerVpcIpv4Change(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
password := GenerateTestPassword(t)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Plan should fail if vpc_ipv4_address is set without vpc_id
{
Config: providerConfig + `
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_ipv4_address = "10.241.69.69"
}
`,
PlanOnly: true,
ExpectError: regexp.MustCompile("Attribute \"vpc_id\" must be specified when \"vpc_ipv4_address\" is specified"),
},
// Setup
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test1.id
vpc_ipv4_address = "10.240.0.69"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
),
},
// Change IP without changing VPC
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test1.id
vpc_ipv4_address = "10.240.4.20"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
),
},
// Change VPC (should also change IP)
{
Config: providerConfig + `
resource "binarylane_vpc" "test1" {
name = "tf-test-server-vpcipv4-1"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc" "test2" {
name = "tf-test-server-vpcipv4-2"
ip_range = "10.241.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-test-server-vpcipv4-1"
region = "per"
image = "debian-11"
size = "std-min"
public_ipv4_count = 0
password = "` + password + `"
vpc_id = binarylane_vpc.test2.id
vpc_ipv4_address = "10.241.69.69"
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test2", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
),
},
},
})
}
func GenerateTestPassword(t *testing.T) string {
t.Helper()
pwBytes := make([]byte, 12)
@@ -336,7 +486,6 @@ func init() {
Name: "server",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
@@ -362,25 +511,25 @@ func init() {
return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", listResp.Body)
}
servers := *listResp.JSON200.Servers
servers := listResp.JSON200.Servers
for _, s := range servers {
if strings.HasPrefix(*s.Name, "tf-test-") {
if strings.HasPrefix(s.Name, "tf-test-") {
reason := "Terraform deletion"
params := binarylane.DeleteServersServerIdParams{
Reason: &reason,
}
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, &params)
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, s.Id, &params)
if err != nil {
return fmt.Errorf("Error deleting server %d during test sweep: %w", *s.Id, err)
return fmt.Errorf("Error deleting server %d during test sweep: %w", s.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", deleteResp.StatusCode(), *s.Id, deleteResp.Body)
return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", deleteResp.StatusCode(), s.Id, deleteResp.Body)
}
log.Println("Deleted server during test sweep:", *s.Id)
log.Println("Deleted server during test sweep:", s.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *sizesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
}
regResults = append(regResults, listResp.JSON200.Sizes...)
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+4 -4
View File
@@ -118,13 +118,13 @@ func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(*sshResp.JSON200.SshKey.PublicKey),
StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
}
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+10 -10
View File
@@ -153,8 +153,8 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
}
// Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
if resp.Diagnostics.HasError() {
return
@@ -191,13 +191,13 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
}
// Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = TrimmedStringValue{
StringValue: types.StringValue(*sshResp.JSON200.SshKey.PublicKey),
StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
}
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -312,13 +312,13 @@ func (r *sshKeyResource) ImportState(
sshKeys := sshResp.JSON200.SshKeys
for _, key := range sshKeys {
if *key.Fingerprint == req.ID {
if key.Fingerprint == req.ID {
sshKey = key
nextPage = false
break
}
}
if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages == nil || sshResp.JSON200.Links.Pages.Next == nil {
if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
@@ -326,7 +326,7 @@ func (r *sshKeyResource) ImportState(
page++
}
if sshKey.Id == nil {
if sshKey.Id == 0 {
resp.Diagnostics.AddError(
"Could not find SSH key by fingerprint",
fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID),
@@ -334,6 +334,6 @@ func (r *sshKeyResource) ImportState(
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), *sshKey.Id)
diags := resp.State.SetAttribute(ctx, path.Root("id"), sshKey.Id)
resp.Diagnostics.Append(diags...)
}
+9 -9
View File
@@ -151,17 +151,17 @@ func init() {
for _, k := range keys {
if strings.HasPrefix(*k.Name, "tf-test-") {
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 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)
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 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 listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
+2 -2
View File
@@ -90,8 +90,8 @@ func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+7 -7
View File
@@ -120,8 +120,8 @@ func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, re
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -157,9 +157,9 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
data.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -203,8 +203,8 @@ func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, re
return
}
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
state.Id = types.Int64Value(vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+11 -11
View File
@@ -156,20 +156,20 @@ func init() {
}
vpcs := listResp.JSON200.Vpcs
for _, vpc := range *vpcs {
if strings.HasPrefix(*vpc.Name, "tf-test-") {
for _, vpc := range vpcs {
if strings.HasPrefix(vpc.Name, "tf-test-") {
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, *vpc.Id)
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)
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 {
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
@@ -93,7 +93,7 @@ func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.Rea
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -94,7 +94,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -136,7 +136,7 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -183,7 +183,7 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, &vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -253,8 +253,8 @@ func GetRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteE
// TODO: This panics if invalid, should be handled better
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringValue(*route.Description),
"destination": types.StringValue(*route.Destination),
"router": types.StringValue(*route.Router),
"destination": types.StringValue(route.Destination),
"router": types.StringValue(route.Router),
})
routeEntriesValues = append(routeEntriesValues, r)
}
@@ -161,6 +161,12 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "Leave null to use default (public) network for the selected region.",
MarkdownDescription: "Leave null to use default (public) network for the selected region.",
},
"vpc_ipv4_address": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided.",
MarkdownDescription: "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided.",
},
},
}
}
@@ -178,6 +184,7 @@ type ServerModel struct {
SshKeys types.List `tfsdk:"ssh_keys"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
VpcIpv4Address types.String `tfsdk:"vpc_ipv4_address"`
}
var _ basetypes.ObjectTypable = AdvancedFeaturesType{}
+221 -570
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -929,6 +929,13 @@
"description": "Leave null to use default (public) network for the selected region."
}
},
{
"name": "vpc_ipv4_address",
"string": {
"computed_optional_required": "computed_optional",
"description": "If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided."
}
},
{
"name": "id",
"int64": {