diff --git a/docs/data-sources/loadbalancer.md b/docs/data-sources/loadbalancer.md index c59fc8428..553f1d7e7 100644 --- a/docs/data-sources/loadbalancer.md +++ b/docs/data-sources/loadbalancer.md @@ -33,6 +33,7 @@ data "stackit_loadbalancer" "example" { ### Read-Only +- `disable_security_group_assignment` (Boolean) If set to true, this will disable the automatic assignment of a security group to the load balancer's targets. This option is primarily used to allow targets that are not within the load balancer's own network or SNA (STACKIT Network area). When this is enabled, you are fully responsible for ensuring network connectivity to the targets, including managing all routing and security group rules manually. This setting cannot be changed after the load balancer is created. - `external_address` (String) External Load Balancer IP address where this Load Balancer is exposed. - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`","region","`name`". - `listeners` (Attributes List) List of all listeners which will accept traffic. Limited to 20. (see [below for nested schema](#nestedatt--listeners)) @@ -40,6 +41,7 @@ data "stackit_loadbalancer" "example" { - `options` (Attributes) Defines any optional functionality you want to have enabled on your load balancer. (see [below for nested schema](#nestedatt--options)) - `plan_id` (String) The service plan ID. If not defined, the default service plan is `p10`. Possible values are: `p10`, `p50`, `p250`, `p750`. - `private_address` (String) Transient private Load Balancer IP address. It can change any time. +- `security_group_id` (String) The ID of the egress security group assigned to the Load Balancer's internal machines. This ID is essential for allowing traffic from the Load Balancer to targets in different networks or STACKIT Network areas (SNA). To enable this, create a security group rule for your target VMs and set the `remote_security_group_id` of that rule to this value. This is typically used when `disable_security_group_assignment` is set to `true`. - `target_pools` (Attributes List) List of all target pools which will be used in the Load Balancer. Limited to 20. (see [below for nested schema](#nestedatt--target_pools)) diff --git a/docs/resources/loadbalancer.md b/docs/resources/loadbalancer.md index 3511f2d3b..cec3b5b4b 100644 --- a/docs/resources/loadbalancer.md +++ b/docs/resources/loadbalancer.md @@ -110,6 +110,109 @@ resource "stackit_loadbalancer" "example" { } } +# This example demonstrates an advanced setup where the Load Balancer is in one +# network and the target server is in another. This requires manual +# security group configuration using the `disable_security_group_assignment` +# and `security_group_id` attributes. + +# We create two separate networks: one for the load balancer and one for the target. +resource "stackit_network" "lb_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "lb-network-example" + ipv4_prefix = "192.168.1.0/24" + ipv4_nameservers = ["8.8.8.8"] +} + +resource "stackit_network" "target_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "target-network-example" + ipv4_prefix = "192.168.2.0/24" + ipv4_nameservers = ["8.8.8.8"] +} + +resource "stackit_public_ip" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +resource "stackit_loadbalancer" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-advanced-lb" + external_address = stackit_public_ip.example.ip + + # Key setting for manual mode: disables automatic security group handling. + disable_security_group_assignment = true + + networks = [{ + network_id = stackit_network.lb_network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + }] + + listeners = [{ + port = 80 + protocol = "PROTOCOL_TCP" + target_pool = "cross-network-pool" + }] + + target_pools = [{ + name = "cross-network-pool" + target_port = 80 + targets = [{ + display_name = stackit_server.example.name + ip = stackit_network_interface.nic.ipv4 + }] + }] +} + +# Create a new security group to be assigned to the target server. +resource "stackit_security_group" "target_sg" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "target-sg-for-lb-access" + description = "Allows ingress traffic from the example load balancer." +} + +# Create a rule to allow traffic FROM the load balancer. +# This rule uses the computed `security_group_id` of the load balancer. +resource "stackit_security_group_rule" "allow_lb_ingress" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + security_group_id = stackit_security_group.target_sg.security_group_id + direction = "ingress" + protocol = { + name = "tcp" + } + + # This is the crucial link: it allows traffic from the LB's security group. + remote_security_group_id = stackit_loadbalancer.example.security_group_id + + port_range = { + min = 80 + max = 80 + } +} + +resource "stackit_server" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-remote-target" + machine_type = "g2i.2" + availability_zone = "eu01-1" + + boot_volume = { + source_type = "image" + source_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + size = 10 + } + + network_interfaces = [ + stackit_network_interface.nic.network_interface_id + ] +} + +resource "stackit_network_interface" "nic" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + network_id = stackit_network.target_network.network_id + security_group_ids = [stackit_security_group.target_sg.security_group_id] +} +# End of advanced example + # Only use the import statement, if you want to import an existing loadbalancer import { to = stackit_loadbalancer.import-example @@ -130,6 +233,7 @@ import { ### Optional +- `disable_security_group_assignment` (Boolean) If set to true, this will disable the automatic assignment of a security group to the load balancer's targets. This option is primarily used to allow targets that are not within the load balancer's own network or SNA (STACKIT network area). When this is enabled, you are fully responsible for ensuring network connectivity to the targets, including managing all routing and security group rules manually. This setting cannot be changed after the load balancer is created. - `external_address` (String) External Load Balancer IP address where this Load Balancer is exposed. - `options` (Attributes) Defines any optional functionality you want to have enabled on your load balancer. (see [below for nested schema](#nestedatt--options)) - `plan_id` (String) The service plan ID. If not defined, the default service plan is `p10`. Possible values are: `p10`, `p50`, `p250`, `p750`. @@ -139,6 +243,7 @@ import { - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`","region","`name`". - `private_address` (String) Transient private Load Balancer IP address. It can change any time. +- `security_group_id` (String) The ID of the egress security group assigned to the Load Balancer's internal machines. This ID is essential for allowing traffic from the Load Balancer to targets in different networks or STACKIT network areas (SNA). To enable this, create a security group rule for your target VMs and set the `remote_security_group_id` of that rule to this value. This is typically used when `disable_security_group_assignment` is set to `true`. ### Nested Schema for `listeners` diff --git a/examples/resources/stackit_loadbalancer/resource.tf b/examples/resources/stackit_loadbalancer/resource.tf index 5560fe79a..372c922da 100644 --- a/examples/resources/stackit_loadbalancer/resource.tf +++ b/examples/resources/stackit_loadbalancer/resource.tf @@ -91,6 +91,109 @@ resource "stackit_loadbalancer" "example" { } } +# This example demonstrates an advanced setup where the Load Balancer is in one +# network and the target server is in another. This requires manual +# security group configuration using the `disable_security_group_assignment` +# and `security_group_id` attributes. + +# We create two separate networks: one for the load balancer and one for the target. +resource "stackit_network" "lb_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "lb-network-example" + ipv4_prefix = "192.168.1.0/24" + ipv4_nameservers = ["8.8.8.8"] +} + +resource "stackit_network" "target_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "target-network-example" + ipv4_prefix = "192.168.2.0/24" + ipv4_nameservers = ["8.8.8.8"] +} + +resource "stackit_public_ip" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +resource "stackit_loadbalancer" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-advanced-lb" + external_address = stackit_public_ip.example.ip + + # Key setting for manual mode: disables automatic security group handling. + disable_security_group_assignment = true + + networks = [{ + network_id = stackit_network.lb_network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + }] + + listeners = [{ + port = 80 + protocol = "PROTOCOL_TCP" + target_pool = "cross-network-pool" + }] + + target_pools = [{ + name = "cross-network-pool" + target_port = 80 + targets = [{ + display_name = stackit_server.example.name + ip = stackit_network_interface.nic.ipv4 + }] + }] +} + +# Create a new security group to be assigned to the target server. +resource "stackit_security_group" "target_sg" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "target-sg-for-lb-access" + description = "Allows ingress traffic from the example load balancer." +} + +# Create a rule to allow traffic FROM the load balancer. +# This rule uses the computed `security_group_id` of the load balancer. +resource "stackit_security_group_rule" "allow_lb_ingress" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + security_group_id = stackit_security_group.target_sg.security_group_id + direction = "ingress" + protocol = { + name = "tcp" + } + + # This is the crucial link: it allows traffic from the LB's security group. + remote_security_group_id = stackit_loadbalancer.example.security_group_id + + port_range = { + min = 80 + max = 80 + } +} + +resource "stackit_server" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-remote-target" + machine_type = "g2i.2" + availability_zone = "eu01-1" + + boot_volume = { + source_type = "image" + source_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + size = 10 + } + + network_interfaces = [ + stackit_network_interface.nic.network_interface_id + ] +} + +resource "stackit_network_interface" "nic" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + network_id = stackit_network.target_network.network_id + security_group_ids = [stackit_security_group.target_sg.security_group_id] +} +# End of advanced example + # Only use the import statement, if you want to import an existing loadbalancer import { to = stackit_loadbalancer.import-example diff --git a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go index d7f3a2157..e51cc1e04 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go @@ -69,6 +69,8 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", "project_id": "STACKIT project ID to which the Load Balancer is associated.", "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "disable_security_group_assignment": "If set to true, this will disable the automatic assignment of a security group to the load balancer's targets. This option is primarily used to allow targets that are not within the load balancer's own network or SNA (STACKIT Network area). When this is enabled, you are fully responsible for ensuring network connectivity to the targets, including managing all routing and security group rules manually. This setting cannot be changed after the load balancer is created.", + "security_group_id": "The ID of the egress security group assigned to the Load Balancer's internal machines. This ID is essential for allowing traffic from the Load Balancer to targets in different networks or STACKIT Network areas (SNA). To enable this, create a security group rule for your target VMs and set the `remote_security_group_id` of that rule to this value. This is typically used when `disable_security_group_assignment` is set to `true`.", "listeners": "List of all listeners which will accept traffic. Limited to 20.", "port": "Port number where we listen for traffic.", "protocol": "Protocol is the highest network protocol we understand to load balance.", @@ -125,6 +127,10 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe Description: descriptions["external_address"], Computed: true, }, + "disable_security_group_assignment": schema.BoolAttribute{ + Description: descriptions["disable_security_group_assignment"], + Computed: true, + }, "plan_id": schema.StringAttribute{ Description: descriptions["plan_id"], Computed: true, @@ -339,6 +345,10 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe Optional: true, Description: descriptions["region"], }, + "security_group_id": schema.StringAttribute{ + Description: descriptions["security_group_id"], + Computed: true, + }, }, } } diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource.go b/stackit/internal/services/loadbalancer/loadbalancer/resource.go index 36e0e1f74..698286703 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource.go @@ -7,6 +7,8 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + loadbalancerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/utils" "github.com/google/uuid" @@ -48,17 +50,19 @@ var ( ) type Model struct { - Id types.String `tfsdk:"id"` // needed by TF - ProjectId types.String `tfsdk:"project_id"` - ExternalAddress types.String `tfsdk:"external_address"` - Listeners types.List `tfsdk:"listeners"` - Name types.String `tfsdk:"name"` - PlanId types.String `tfsdk:"plan_id"` - Networks types.List `tfsdk:"networks"` - Options types.Object `tfsdk:"options"` - PrivateAddress types.String `tfsdk:"private_address"` - TargetPools types.List `tfsdk:"target_pools"` - Region types.String `tfsdk:"region"` + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + ExternalAddress types.String `tfsdk:"external_address"` + DisableSecurityGroupAssignment types.Bool `tfsdk:"disable_security_group_assignment"` + Listeners types.List `tfsdk:"listeners"` + Name types.String `tfsdk:"name"` + PlanId types.String `tfsdk:"plan_id"` + Networks types.List `tfsdk:"networks"` + Options types.Object `tfsdk:"options"` + PrivateAddress types.String `tfsdk:"private_address"` + TargetPools types.List `tfsdk:"target_pools"` + Region types.String `tfsdk:"region"` + SecurityGroupId types.String `tfsdk:"security_group_id"` } // Struct corresponding to Model.Listeners[i] @@ -303,6 +307,7 @@ func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaReques "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", "project_id": "STACKIT project ID to which the Load Balancer is associated.", "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "disable_security_group_assignment": "If set to true, this will disable the automatic assignment of a security group to the load balancer's targets. This option is primarily used to allow targets that are not within the load balancer's own network or SNA (STACKIT network area). When this is enabled, you are fully responsible for ensuring network connectivity to the targets, including managing all routing and security group rules manually. This setting cannot be changed after the load balancer is created.", "listeners": "List of all listeners which will accept traffic. Limited to 20.", "port": "Port number where we listen for traffic.", "protocol": "Protocol is the highest network protocol we understand to load balance. " + utils.SupportedValuesDocumentation(protocolOptions), @@ -339,6 +344,7 @@ func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaReques "targets.display_name": "Target display name", "ip": "Target IP", "region": "The resource region. If not defined, the provider region is used.", + "security_group_id": "The ID of the egress security group assigned to the Load Balancer's internal machines. This ID is essential for allowing traffic from the Load Balancer to targets in different networks or STACKIT network areas (SNA). To enable this, create a security group rule for your target VMs and set the `remote_security_group_id` of that rule to this value. This is typically used when `disable_security_group_assignment` is set to `true`.", } resp.Schema = schema.Schema{ @@ -373,6 +379,16 @@ The example below creates the supporting infrastructure using the STACKIT Terraf stringplanmodifier.RequiresReplace(), }, }, + "disable_security_group_assignment": schema.BoolAttribute{ + Description: descriptions["disable_security_group_assignment"], + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + boolplanmodifier.UseStateForUnknown(), + }, + }, "plan_id": schema.StringAttribute{ Description: descriptions["plan_id"], Optional: true, @@ -669,6 +685,13 @@ The example below creates the supporting infrastructure using the STACKIT Terraf stringplanmodifier.RequiresReplace(), }, }, + "security_group_id": schema.StringAttribute{ + Description: descriptions["security_group_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, }, } } @@ -907,13 +930,14 @@ func toCreatePayload(ctx context.Context, model *Model) (*loadbalancer.CreateLoa } return &loadbalancer.CreateLoadBalancerPayload{ - ExternalAddress: conversion.StringValueToPointer(model.ExternalAddress), - Listeners: listenersPayload, - Name: conversion.StringValueToPointer(model.Name), - PlanId: conversion.StringValueToPointer(model.PlanId), - Networks: networksPayload, - Options: optionsPayload, - TargetPools: targetPoolsPayload, + ExternalAddress: conversion.StringValueToPointer(model.ExternalAddress), + DisableTargetSecurityGroupAssignment: conversion.BoolValueToPointer(model.DisableSecurityGroupAssignment), + Listeners: listenersPayload, + Name: conversion.StringValueToPointer(model.Name), + PlanId: conversion.StringValueToPointer(model.PlanId), + Networks: networksPayload, + Options: optionsPayload, + TargetPools: targetPoolsPayload, }, nil } @@ -1221,7 +1245,13 @@ func mapFields(ctx context.Context, lb *loadbalancer.LoadBalancer, m *Model, reg m.PlanId = types.StringPointerValue(lb.PlanId) m.ExternalAddress = types.StringPointerValue(lb.ExternalAddress) m.PrivateAddress = types.StringPointerValue(lb.PrivateAddress) + m.DisableSecurityGroupAssignment = types.BoolPointerValue(lb.DisableTargetSecurityGroupAssignment) + if lb.TargetSecurityGroup != nil { + m.SecurityGroupId = types.StringPointerValue(lb.TargetSecurityGroup.Id) + } else { + m.SecurityGroupId = types.StringNull() + } err := mapListeners(lb, m) if err != nil { return fmt.Errorf("mapping listeners: %w", err) diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go index 2a1a85b79..810e9c395 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go @@ -489,9 +489,10 @@ func TestMapFields(t *testing.T) { }), }), }), - PrivateAddress: types.StringNull(), - TargetPools: types.ListNull(types.ObjectType{AttrTypes: targetPoolTypes}), - Region: types.StringValue(testRegion), + PrivateAddress: types.StringNull(), + SecurityGroupId: types.StringNull(), + TargetPools: types.ListNull(types.ObjectType{AttrTypes: targetPoolTypes}), + Region: types.StringValue(testRegion), }, true, }, @@ -536,6 +537,10 @@ func TestMapFields(t *testing.T) { }, }, }), + TargetSecurityGroup: loadbalancer.LoadBalancerGetTargetSecurityGroupAttributeType(&loadbalancer.SecurityGroup{ + Id: utils.Ptr("sg-id-12345"), + Name: utils.Ptr("sg-name-abcde"), + }), TargetPools: utils.Ptr([]loadbalancer.TargetPool{ { ActiveHealthCheck: utils.Ptr(loadbalancer.ActiveHealthCheck{ @@ -565,6 +570,7 @@ func TestMapFields(t *testing.T) { Id: types.StringValue(id), ProjectId: types.StringValue("pid"), ExternalAddress: types.StringValue("external_address"), + SecurityGroupId: types.StringValue("sg-id-12345"), Listeners: types.ListValueMust(types.ObjectType{AttrTypes: listenerTypes}, []attr.Value{ types.ObjectValueMust(listenerTypes, map[string]attr.Value{ "display_name": types.StringValue("display_name"), diff --git a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go index 2d3c974b9..fe96460ce 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go @@ -29,17 +29,18 @@ var resourceMinConfig string var resourceMaxConfig string var testConfigVarsMin = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "plan_id": config.StringVariable("p10"), - "network_name": config.StringVariable(fmt.Sprintf("tf-acc-n%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "server_name": config.StringVariable(fmt.Sprintf("tf-acc-s%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "loadbalancer_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "target_pool_name": config.StringVariable("example-target-pool"), - "target_port": config.StringVariable("5432"), - "target_display_name": config.StringVariable("example-target"), - "listener_port": config.StringVariable("5432"), - "listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), - "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), + "project_id": config.StringVariable(testutil.ProjectId), + "plan_id": config.StringVariable("p10"), + "disable_security_group_assignment": config.BoolVariable(false), + "network_name": config.StringVariable(fmt.Sprintf("tf-acc-n%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "server_name": config.StringVariable(fmt.Sprintf("tf-acc-s%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "loadbalancer_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "target_pool_name": config.StringVariable("example-target-pool"), + "target_port": config.StringVariable("5432"), + "target_display_name": config.StringVariable("example-target"), + "listener_port": config.StringVariable("5432"), + "listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), + "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), "obs_display_name": config.StringVariable("obs-user"), "obs_username": config.StringVariable("obs-username"), @@ -47,17 +48,18 @@ var testConfigVarsMin = config.Variables{ } var testConfigVarsMax = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "plan_id": config.StringVariable("p10"), - "network_name": config.StringVariable(fmt.Sprintf("tf-acc-n%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "server_name": config.StringVariable(fmt.Sprintf("tf-acc-s%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "loadbalancer_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "target_pool_name": config.StringVariable("example-target-pool"), - "target_port": config.StringVariable("5432"), - "target_display_name": config.StringVariable("example-target"), - "listener_port": config.StringVariable("5432"), - "listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), - "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), + "project_id": config.StringVariable(testutil.ProjectId), + "plan_id": config.StringVariable("p10"), + "disable_security_group_assignment": config.BoolVariable(true), + "network_name": config.StringVariable(fmt.Sprintf("tf-acc-n%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "server_name": config.StringVariable(fmt.Sprintf("tf-acc-s%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "loadbalancer_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "target_pool_name": config.StringVariable("example-target-pool"), + "target_port": config.StringVariable("5432"), + "target_display_name": config.StringVariable("example-target"), + "listener_port": config.StringVariable("5432"), + "listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), + "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), "listener_display_name": config.StringVariable("example-listener"), "listener_server_name_indicators": config.StringVariable("acc-test.runs.onstackit.cloud"), @@ -118,10 +120,12 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMin["network_role"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "external_address"), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", "false"), resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url"), resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url"), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "security_group_id"), // Loadbalancer observability credentials resource resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.obs_credential", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), @@ -168,10 +172,16 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMin["network_role"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "external_address"), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", "false"), resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url"), resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url"), + resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "security_group_id"), + resource.TestCheckResourceAttrPair( + "stackit_loadbalancer.loadbalancer", "security_group_id", + "data.stackit_loadbalancer.loadbalancer", "security_group_id", + ), )}, // Import { @@ -186,8 +196,11 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find attribute name") } - - return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, name), nil + region, ok := r.Primary.Attributes["region"] + if !ok { + return "", fmt.Errorf("couldn't find attribute region") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, region, name), nil }, ImportState: true, ImportStateVerify: true, @@ -234,7 +247,8 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMax["network_role"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "external_address"), - + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", testutil.ConvertConfigVariable(testConfigVarsMax["disable_security_group_assignment"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "security_group_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["healthy_threshold"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval_jitter"])), @@ -304,22 +318,25 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMax["network_role"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "external_address"), - + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", testutil.ConvertConfigVariable(testConfigVarsMax["disable_security_group_assignment"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "security_group_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["healthy_threshold"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval_jitter"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.timeout", testutil.ConvertConfigVariable(testConfigVarsMax["health_timeout"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.unhealthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["unhealthy_threshold"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["use_source_ip_address"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), - resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.logs", "credentials_ref", "data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_logs_push_url"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.metrics", "credentials_ref", "data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_metrics_push_url"])), + resource.TestCheckResourceAttrPair( + "stackit_loadbalancer.loadbalancer", "security_group_id", + "data.stackit_loadbalancer.loadbalancer", "security_group_id", + ), )}, // Import { @@ -334,8 +351,11 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find attribute name") } - - return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, name), nil + region, ok := r.Primary.Attributes["region"] + if !ok { + return "", fmt.Errorf("couldn't find attribute region") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, region, name), nil }, ImportState: true, ImportStateVerify: true, diff --git a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf index aabd752d5..11691e8e2 100644 --- a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf +++ b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf @@ -11,6 +11,7 @@ variable "target_display_name" {} variable "listener_port" {} variable "listener_protocol" {} variable "network_role" {} +variable "disable_security_group_assignment" {} variable "listener_display_name" {} variable "listener_server_name_indicators" {} @@ -44,6 +45,11 @@ resource "stackit_network_interface" "network_interface" { project_id = stackit_network.network.project_id network_id = stackit_network.network.network_id name = "name" + lifecycle { + ignore_changes = [ + security_group_ids, + ] + } } resource "stackit_public_ip" "public_ip" { @@ -72,9 +78,10 @@ resource "stackit_server" "server" { } resource "stackit_loadbalancer" "loadbalancer" { - project_id = var.project_id - name = var.loadbalancer_name - plan_id = var.plan_id + project_id = var.project_id + name = var.loadbalancer_name + plan_id = var.plan_id + disable_security_group_assignment = var.disable_security_group_assignment target_pools = [ { name = var.target_pool_name @@ -120,14 +127,14 @@ resource "stackit_loadbalancer" "loadbalancer" { private_network_only = var.private_network_only acl = [var.acl] observability = { - logs = { - credentials_ref = stackit_loadbalancer_observability_credential.logs.credentials_ref - push_url = var.observability_logs_push_url - } - metrics = { - credentials_ref = stackit_loadbalancer_observability_credential.metrics.credentials_ref - push_url = var.observability_metrics_push_url - } + logs = { + credentials_ref = stackit_loadbalancer_observability_credential.logs.credentials_ref + push_url = var.observability_logs_push_url + } + metrics = { + credentials_ref = stackit_loadbalancer_observability_credential.metrics.credentials_ref + push_url = var.observability_metrics_push_url + } } } external_address = stackit_public_ip.public_ip.ip diff --git a/stackit/internal/services/loadbalancer/testfiles/resource-min.tf b/stackit/internal/services/loadbalancer/testfiles/resource-min.tf index 2b6d65793..f2692c35d 100644 --- a/stackit/internal/services/loadbalancer/testfiles/resource-min.tf +++ b/stackit/internal/services/loadbalancer/testfiles/resource-min.tf @@ -27,6 +27,11 @@ resource "stackit_network_interface" "network_interface" { project_id = stackit_network.network.project_id network_id = stackit_network.network.network_id name = "name" + lifecycle { + ignore_changes = [ + security_group_ids, + ] + } } resource "stackit_public_ip" "public_ip" {