From dd3aaa2d75662ebaede8de586ed14d69753fde63 Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sat, 31 Aug 2024 14:39:31 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20allow=20importing=20of=20binarylane?= =?UTF-8?q?=5Fserver=20resources=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/k3s/main.tf | 22 ++++- internal/provider/server_data_source.go | 6 +- .../server_firewall_rules_resource.go | 1 - internal/provider/server_resource.go | 84 +++++++++++++------ internal/provider/server_resource_test.go | 6 ++ 5 files changed, 87 insertions(+), 32 deletions(-) diff --git a/examples/k3s/main.tf b/examples/k3s/main.tf index b424f18..f68ea7d 100644 --- a/examples/k3s/main.tf +++ b/examples/k3s/main.tf @@ -12,6 +12,13 @@ resource "random_password" "cluster_token" { special = false } +# SSH Key +# ------- +resource "binarylane_ssh_key" "example" { + name = "tf-example-k8s" + public_key = file("~/.ssh/id_ed25519.pub") +} + # Virtual Private Cloud # --------------------- resource "binarylane_vpc" "example" { @@ -42,9 +49,11 @@ resource "binarylane_server" "server" { image = "ubuntu-24.04" size = "std-min" password = random_password.binarylane.result + ssh_keys = [binarylane_ssh_key.example.id] vpc_id = binarylane_vpc.example.id public_ipv4_count = 1 user_data = sensitive(data.cloudinit_config.server.rendered) + wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules } # k3s Agents @@ -70,9 +79,11 @@ resource "binarylane_server" "agent" { image = "ubuntu-24.04" size = "std-min" password = random_password.binarylane.result + ssh_keys = [binarylane_ssh_key.example.id] vpc_id = binarylane_vpc.example.id public_ipv4_count = 1 user_data = sensitive(data.cloudinit_config.agent.rendered) + wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules } # Virtual Private Cloud Routing @@ -88,8 +99,13 @@ resource "binarylane_vpc_route_entries" "example" { ] } +locals { + agent_ips = flatten([for _, a in binarylane_server.agent : a.private_ipv4_addresses]) + server_ips = flatten([for _, s in binarylane_server.server : s.private_ipv4_addresses]) +} + resource "binarylane_server_firewall_rules" "example" { - for_each = { for server in flatten([binarylane_server.server, binarylane_server.agent]) : server.id => server } + for_each = { for server in concat(binarylane_server.server, binarylane_server.agent) : server.name => server } server_id = each.value.id @@ -97,8 +113,8 @@ resource "binarylane_server_firewall_rules" "example" { { description = "K3s supervisor and Kubernetes API Server" protocol = "tcp" - source_addresses = flatten([for _, a in binarylane_server.agent : a.private_ipv4_addresses]) - destination_addresses = flatten([for _, s in binarylane_server.server : s.private_ipv4_addresses]) + source_addresses = local.agent_ips + destination_addresses = local.server_ips destination_ports = ["6443"] action = "accept" }, diff --git a/internal/provider/server_data_source.go b/internal/provider/server_data_source.go index 995ba22..bd67a0a 100644 --- a/internal/provider/server_data_source.go +++ b/internal/provider/server_data_source.go @@ -81,15 +81,15 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, serverResp, err := d.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64()) if err != nil { resp.Diagnostics.AddError( - fmt.Sprintf("Error reading server: name=%s", data.Id.String()), + fmt.Sprintf("Error reading server: name=%s, id=%s", data.Name.ValueString(), data.Id.String()), err.Error(), ) return } if serverResp.StatusCode() != http.StatusOK { resp.Diagnostics.AddError( - fmt.Sprintf("Unexpected HTTP status code reading server: name=%s", data.Id.String()), - fmt.Sprintf("Unexpected HTTP status code reading server: %s", serverResp.Body), + fmt.Sprintf("Unexpected HTTP status code reading server: name=%s, id=%s", data.Name.ValueString(), data.Id.String()), + string(serverResp.Body), ) return } diff --git a/internal/provider/server_firewall_rules_resource.go b/internal/provider/server_firewall_rules_resource.go index c6a0b9f..36a0fd6 100644 --- a/internal/provider/server_firewall_rules_resource.go +++ b/internal/provider/server_firewall_rules_resource.go @@ -78,7 +78,6 @@ func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.C // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } diff --git a/internal/provider/server_resource.go b/internal/provider/server_resource.go index 866dfce..b33cfb2 100644 --- a/internal/provider/server_resource.go +++ b/internal/provider/server_resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strconv" "terraform-provider-binarylane/internal/binarylane" "terraform-provider-binarylane/internal/resources" "time" @@ -11,10 +12,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -41,7 +44,7 @@ type serverResource struct { type serverModel struct { resources.ServerModel - WaitForCreateSeconds int32 `tfsdk:"wait_for_create"` + WaitForCreateSeconds types.Int32 `tfsdk:"wait_for_create"` PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"` PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"` PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"` @@ -82,6 +85,9 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Optional: false, Required: false, Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, } pw := resp.Schema.Attributes["password"] @@ -151,8 +157,7 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Description: waitDescription, MarkdownDescription: waitDescription, Optional: true, - Computed: true, - Default: int32default.StaticInt32(0), + Computed: false, } publicIpv4CountDescription := "The number of public IPv4 addresses to assign to the server. If this is not provided, the " + @@ -162,9 +167,13 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r MarkdownDescription: publicIpv4CountDescription, Optional: true, Computed: true, + Default: int32default.StaticInt32(1), Validators: []validator.Int32{ int32validator.AtLeast(0), }, + // PlanModifiers: []planmodifier.Int32{ + // int32planmodifier.UseStateForUnknown(), + // }, } publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server." @@ -176,6 +185,9 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Optional: false, Required: false, Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, } privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server." @@ -187,6 +199,9 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Optional: false, Required: false, Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, } } @@ -251,20 +266,19 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug) data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug) data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) + data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking) + data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId) - publicV4AddressCount := 0 var publicIpv4Addresses []string var privateIpv4Addresses []string for _, v4address := range serverResp.JSON200.Server.Networks.V4 { if v4address.Type == "public" { - publicV4AddressCount++ publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress) } else { privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress) } } - data.PublicIpv4Count = types.Int32Value(int32(publicV4AddressCount)) data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses) resp.Diagnostics.Append(diags...) data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses) @@ -273,7 +287,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) - if data.WaitForCreateSeconds <= 0 { + if data.WaitForCreateSeconds.ValueInt32() <= 0 { return } @@ -292,7 +306,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, return } - timeLimit := time.Now().Add(time.Duration(data.WaitForCreateSeconds) * time.Second) + timeLimit := time.Now().Add(time.Duration(data.WaitForCreateSeconds.ValueInt32()) * time.Second) for { tflog.Info(ctx, "Waiting for server to be ready...") @@ -325,7 +339,8 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res var data serverModel // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } @@ -344,8 +359,9 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res if serverResp.StatusCode() != http.StatusOK { resp.Diagnostics.AddError( - "Unexpected HTTP status code reading server", - fmt.Sprintf("Received %s reading server: id=%s, name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), data.Id.String(), serverResp.Body)) + fmt.Sprintf("Unexpected HTTP status code %s reading server: name=%s, id=%s", serverResp.Status(), data.Name.ValueString(), data.Id.String()), + string(serverResp.Body), + ) return } @@ -354,6 +370,24 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug) data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug) data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug) + data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil) + data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking) + data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId) + + var publicIpv4Addresses []string + var privateIpv4Addresses []string + + for _, v4address := range serverResp.JSON200.Server.Networks.V4 { + if v4address.Type == "public" { + publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress) + } else { + privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress) + } + } + data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses) + resp.Diagnostics.Append(diags...) + data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -415,19 +449,19 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, } } -// func (r *serverResource) ImportState( -// ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, -// ) { -// id, err := strconv.ParseInt(req.ID, 10, 32) +func (r *serverResource) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + id, err := strconv.ParseInt(req.ID, 10, 32) -// if err != nil { -// resp.Diagnostics.AddError( -// "Error importing server", -// "Could not import server, unexpected error (ID should be an integer): "+err.Error(), -// ) -// return -// } + if err != nil { + resp.Diagnostics.AddError( + "Error importing server", + "Could not import server, unexpected error (ID should be an integer): "+err.Error(), + ) + return + } -// diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id)) -// resp.Diagnostics.Append(diags...) -// } + diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id)) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/server_resource_test.go b/internal/provider/server_resource_test.go index 367a08d..f77b8b7 100644 --- a/internal/provider/server_resource_test.go +++ b/internal/provider/server_resource_test.go @@ -89,6 +89,12 @@ echo "Hello World" > /var/tmp/output.txt resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"), ), }, + { + ResourceName: "binarylane_server.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password", "ssh_keys", "user_data", "wait_for_create", "public_ipv4_count"}, + }, }, }) }