mirror of
https://github.com/oscarhermoso/terraform-provider-binarylane
synced 2026-10-19 21:49:15 +11:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a58a8112c | |||
| 08a37caf51 |
@@ -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 }}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
+262
-259
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, ¶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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)...)
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)...)
|
||||
|
||||
@@ -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)...)
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user