From 63632d3abbb7ec28f534b1bc267e8dcd0a53c2db Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Tue, 27 Aug 2024 20:27:24 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20Fix=20some=20resource=20attribut?= =?UTF-8?q?es=20to=20be=20read=20only=20or=20required=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/resources/server.md | 2 +- docs/resources/server_firewall_rules.md | 3 - docs/resources/vpc.md | 5 +- examples/k3s/main.tf | 16 ++--- .../server_firewall_rules_resource.go | 48 +++++++++------ .../server_firewall_rules_resource_test.go | 59 +++++++++++++++++++ internal/provider/server_resource.go | 42 ++++++++++--- internal/provider/vpc_resource.go | 12 ++++ 8 files changed, 146 insertions(+), 41 deletions(-) diff --git a/docs/resources/server.md b/docs/resources/server.md index 4e5f096..c494a3b 100644 --- a/docs/resources/server.md +++ b/docs/resources/server.md @@ -24,7 +24,6 @@ Provides a Binary Lane Server resource. This can be used to create and delete se ### Optional - `backups` (Boolean) If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect. -- `id` (Number) The ID of the server to fetch. - `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name. - `password` (String, Sensitive) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address. - `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts. @@ -35,5 +34,6 @@ Provides a Binary Lane Server resource. This can be used to create and delete se ### Read-Only +- `id` (Number) The ID of the server to fetch. - `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server. - `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server. diff --git a/docs/resources/server_firewall_rules.md b/docs/resources/server_firewall_rules.md index 87e3763..96c8b96 100644 --- a/docs/resources/server_firewall_rules.md +++ b/docs/resources/server_firewall_rules.md @@ -18,9 +18,6 @@ TODO ### Required - `firewall_rules` (Attributes List) A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules. (see [below for nested schema](#nestedatt--firewall_rules)) - -### Optional - - `server_id` (Number) The ID of the server for which firewall rules should be listed. diff --git a/docs/resources/vpc.md b/docs/resources/vpc.md index 572c8e7..5b743c1 100644 --- a/docs/resources/vpc.md +++ b/docs/resources/vpc.md @@ -21,5 +21,8 @@ TODO ### Optional -- `id` (Number) The target vpc id. - `ip_range` (String) A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like. + +### Read-Only + +- `id` (Number) The target vpc id. diff --git a/examples/k3s/main.tf b/examples/k3s/main.tf index 56c3612..b424f18 100644 --- a/examples/k3s/main.tf +++ b/examples/k3s/main.tf @@ -95,16 +95,12 @@ resource "binarylane_server_firewall_rules" "example" { firewall_rules = [ { - description = "K3s supervisor and Kubernetes API Server" - protocol = "tcp" - source_addresses = flatten([ # agents - for _, agent in binarylane_server.agent : agent.private_ipv4_addresses - ]) - destination_addresses = flatten([ # server_ips - for _, server in binarylane_server.server : server.private_ipv4_addresses - ]) - destination_ports = ["6443"] - action = "accept" + 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]) + destination_ports = ["6443"] + action = "accept" }, { description = "Flannel VXLAN" diff --git a/internal/provider/server_firewall_rules_resource.go b/internal/provider/server_firewall_rules_resource.go index 7304f0f..c6a0b9f 100644 --- a/internal/provider/server_firewall_rules_resource.go +++ b/internal/provider/server_firewall_rules_resource.go @@ -8,10 +8,13 @@ import ( "terraform-provider-binarylane/internal/binarylane" "terraform-provider-binarylane/internal/resources" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -57,6 +60,17 @@ func (r *serverFirewallRulesResource) Metadata(ctx context.Context, req resource func (r *serverFirewallRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resources.ServerFirewallRulesResourceSchema(ctx) resp.Schema.Description = "TODO" + + // Overrides + serverId := resp.Schema.Attributes["server_id"] + resp.Schema.Attributes["server_id"] = &schema.Int64Attribute{ + Description: serverId.GetDescription(), + MarkdownDescription: serverId.GetMarkdownDescription(), + Required: true, // Server ID is required to define the firewall rules + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + } } func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -227,23 +241,6 @@ func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.D } } -func (r *serverFirewallRulesResource) ImportState( - ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, -) { - id, err := strconv.ParseInt(req.ID, 10, 64) - - if err != nil { - resp.Diagnostics.AddError( - "Error importing VPC route entries", - "Could not import VPC route entries, unexpected error (ID should be an integer): "+err.Error(), - ) - return - } - - diags := resp.State.SetAttribute(ctx, path.Root("server_id"), id) - resp.Diagnostics.Append(diags...) -} - func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.AdvancedFirewallRule) (basetypes.ListValue, diag.Diagnostics) { var diags diag.Diagnostics @@ -290,3 +287,20 @@ func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.Adva return firewallRulesListValue, diags } + +func (r *serverFirewallRulesResource) 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 firewall rules", + "Could not import server firewall rules, unexpected error (ID should be an integer): "+err.Error(), + ) + return + } + + diags := resp.State.SetAttribute(ctx, path.Root("server_id"), id) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/server_firewall_rules_resource_test.go b/internal/provider/server_firewall_rules_resource_test.go index 9aa281f..9eb3ade 100644 --- a/internal/provider/server_firewall_rules_resource_test.go +++ b/internal/provider/server_firewall_rules_resource_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestServerFirewallRulesResource(t *testing.T) { @@ -75,6 +76,64 @@ data "binarylane_server_firewall_rules" "test" { resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"), ), }, + // ImportState testing + // { + // ResourceName: "binarylane_server.test", + // ImportState: true, + // ImportStateVerify: true, + // }, + { + ResourceName: "binarylane_server_firewall_rules.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "server_id", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + resourceState := s.RootModule().Resources["binarylane_server_firewall_rules.test"] + return resourceState.Primary.Attributes["server_id"], nil + }, + }, + // Update and Read testing + { + Config: providerConfig + ` +resource "binarylane_server" "test" { + name = "tf-test-server-fw-rules" + region = "per" + image = "debian-12" + size = "std-min" + password = "` + password + `" + wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules +} + +resource "binarylane_server_firewall_rules" "test" { + server_id = binarylane_server.test.id + firewall_rules = [ + { + description = "Allow HTTP" # Updated description + protocol = "tcp" + source_addresses = ["0.0.0.0/0"] + destination_addresses = [binarylane_server.test.private_ipv4_addresses.0] + destination_ports = ["80"] # Updated port + action = "accept" + }, + ] +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + // Verify resource values updated + resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "server_id"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.#", "1"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow HTTP"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"), + resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "80"), + resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"), + ), + }, + // Delete testing automatically occurs in TestCase }, }) } diff --git a/internal/provider/server_resource.go b/internal/provider/server_resource.go index 0132b60..da7966e 100644 --- a/internal/provider/server_resource.go +++ b/internal/provider/server_resource.go @@ -71,6 +71,16 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r resp.Schema.Description = "Provides a Binary Lane Server resource. This can be used to create and delete servers." // Overrides + id := resp.Schema.Attributes["id"] + resp.Schema.Attributes["id"] = &schema.Int64Attribute{ + Description: id.GetDescription(), + MarkdownDescription: id.GetMarkdownDescription(), + // read only + Optional: false, + Required: false, + Computed: true, + } + pw := resp.Schema.Attributes["password"] resp.Schema.Attributes["password"] = &schema.StringAttribute{ Description: pw.GetDescription(), @@ -146,9 +156,10 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Description: publicIpv4AddressesDescription, MarkdownDescription: publicIpv4AddressesDescription, ElementType: types.StringType, - Optional: false, - Required: false, - Computed: true, // "read only" with computed=true and required/optional=false + // read only + Optional: false, + Required: false, + Computed: true, } privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server." @@ -156,9 +167,10 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r Description: privateIpv4AddressesDescription, MarkdownDescription: privateIpv4AddressesDescription, ElementType: types.StringType, - Optional: false, - Required: false, - Computed: true, // "read only" with computed=true and required/optional=false + // read only + Optional: false, + Required: false, + Computed: true, } } @@ -379,7 +391,19 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, } } -// func (r *serverResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { -// // Retrieve import ID and save to id attribute -// resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +// 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 +// } + +// diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id)) +// resp.Diagnostics.Append(diags...) // } diff --git a/internal/provider/vpc_resource.go b/internal/provider/vpc_resource.go index 73554b4..8bd4f27 100644 --- a/internal/provider/vpc_resource.go +++ b/internal/provider/vpc_resource.go @@ -11,6 +11,7 @@ import ( "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/types" "github.com/hashicorp/terraform-plugin-log/tflog" ) @@ -56,6 +57,17 @@ func (r *vpcResource) Metadata(ctx context.Context, req resource.MetadataRequest func (r *vpcResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resources.VpcResourceSchema(ctx) resp.Schema.Description = "TODO" + + // Overrides + id := resp.Schema.Attributes["id"] + resp.Schema.Attributes["id"] = &schema.Int64Attribute{ + Description: id.GetDescription(), + MarkdownDescription: id.GetMarkdownDescription(), + // read only + Optional: false, + Required: false, + Computed: true, + } } func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {