Skip to content

Commit aaf29e4

Browse files
fix(loadbalancer): set external_address as optional (#854)
* fix: set external_address for lb to optional * add unit tests --------- Co-authored-by: Ruben Hoenle <Ruben.Hoenle@stackit.cloud>
1 parent 29f9a01 commit aaf29e4

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

docs/resources/loadbalancer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ resource "stackit_loadbalancer" "example" {
119119

120120
### Required
121121

122-
- `external_address` (String) External Load Balancer IP address where this Load Balancer is exposed.
123122
- `listeners` (Attributes List) List of all listeners which will accept traffic. Limited to 20. (see [below for nested schema](#nestedatt--listeners))
124123
- `name` (String) Load balancer name.
125124
- `networks` (Attributes List) List of networks that listeners and targets reside in. (see [below for nested schema](#nestedatt--networks))
@@ -128,6 +127,7 @@ resource "stackit_loadbalancer" "example" {
128127

129128
### Optional
130129

130+
- `external_address` (String) External Load Balancer IP address where this Load Balancer is exposed.
131131
- `options` (Attributes) Defines any optional functionality you want to have enabled on your load balancer. (see [below for nested schema](#nestedatt--options))
132132
- `region` (String) The resource region. If not defined, the provider region is used.
133133

stackit/internal/services/loadbalancer/loadbalancer/resource.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
1515
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1616
"github.com/hashicorp/terraform-plugin-framework/attr"
17+
"github.com/hashicorp/terraform-plugin-framework/diag"
1718
"github.com/hashicorp/terraform-plugin-framework/path"
1819
"github.com/hashicorp/terraform-plugin-framework/resource"
1920
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@@ -237,6 +238,43 @@ func (r *loadBalancerResource) ModifyPlan(ctx context.Context, req resource.Modi
237238
}
238239
}
239240

241+
// ConfigValidators validates the resource configuration
242+
func (r *loadBalancerResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
243+
var model Model
244+
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
245+
if resp.Diagnostics.HasError() {
246+
return
247+
}
248+
249+
// validation is done in extracted func so it's easier to unit-test it
250+
validateConfig(ctx, &resp.Diagnostics, &model)
251+
}
252+
253+
func validateConfig(ctx context.Context, diags *diag.Diagnostics, model *Model) {
254+
externalAddressIsSet := !model.ExternalAddress.IsNull()
255+
256+
lbOptions, err := toOptionsPayload(ctx, model)
257+
if err != nil || lbOptions == nil {
258+
// private_network_only is not set and external_address is not set
259+
if !externalAddressIsSet {
260+
core.LogAndAddError(ctx, diags, "Error configuring load balancer", fmt.Sprintf("You need to provide either the `options.private_network_only = true` or `external_address` field. %v", err))
261+
}
262+
return
263+
}
264+
if lbOptions.PrivateNetworkOnly == nil || !*lbOptions.PrivateNetworkOnly {
265+
// private_network_only is not set or false and external_address is not set
266+
if !externalAddressIsSet {
267+
core.LogAndAddError(ctx, diags, "Error configuring load balancer", "You need to provide either the `options.private_network_only = true` or `external_address` field.")
268+
}
269+
return
270+
}
271+
272+
// Both are set
273+
if *lbOptions.PrivateNetworkOnly && externalAddressIsSet {
274+
core.LogAndAddError(ctx, diags, "Error configuring load balancer", "You need to provide either the `options.private_network_only = true` or `external_address` field.")
275+
}
276+
}
277+
240278
// Configure adds the provider configured client to the resource.
241279
func (r *loadBalancerResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
242280
var ok bool
@@ -327,7 +365,7 @@ The example below creates the supporting infrastructure using the STACKIT Terraf
327365
},
328366
"external_address": schema.StringAttribute{
329367
Description: descriptions["external_address"],
330-
Required: true,
368+
Optional: true,
331369
PlanModifiers: []planmodifier.String{
332370
stringplanmodifier.RequiresReplace(),
333371
},

stackit/internal/services/loadbalancer/loadbalancer/resource_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ import (
77

88
"github.com/google/go-cmp/cmp"
99
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
1011
"github.com/hashicorp/terraform-plugin-framework/types"
1112
"github.com/stackitcloud/stackit-sdk-go/core/utils"
1213
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
1314
)
1415

16+
const (
17+
testExternalAddress = "95.46.74.109"
18+
)
19+
1520
func TestToCreatePayload(t *testing.T) {
1621
tests := []struct {
1722
description string
@@ -688,3 +693,84 @@ func TestMapFields(t *testing.T) {
688693
})
689694
}
690695
}
696+
697+
func Test_validateConfig(t *testing.T) {
698+
type args struct {
699+
ExternalAddress *string
700+
PrivateNetworkOnly *bool
701+
}
702+
tests := []struct {
703+
name string
704+
args args
705+
wantErr bool
706+
}{
707+
{
708+
name: "happy case 1: private_network_only is not set and external_address is set",
709+
args: args{
710+
ExternalAddress: utils.Ptr(testExternalAddress),
711+
PrivateNetworkOnly: nil,
712+
},
713+
wantErr: false,
714+
},
715+
{
716+
name: "happy case 2: private_network_only is set to false and external_address is set",
717+
args: args{
718+
ExternalAddress: utils.Ptr(testExternalAddress),
719+
PrivateNetworkOnly: utils.Ptr(false),
720+
},
721+
wantErr: false,
722+
},
723+
{
724+
name: "happy case 3: private_network_only is set to true and external_address is not set",
725+
args: args{
726+
ExternalAddress: nil,
727+
PrivateNetworkOnly: utils.Ptr(true),
728+
},
729+
wantErr: false,
730+
},
731+
{
732+
name: "error case 1: private_network_only and external_address are set",
733+
args: args{
734+
ExternalAddress: utils.Ptr(testExternalAddress),
735+
PrivateNetworkOnly: utils.Ptr(true),
736+
},
737+
wantErr: true,
738+
},
739+
{
740+
name: "error case 2: private_network_only is not set and external_address is not set",
741+
args: args{
742+
ExternalAddress: nil,
743+
PrivateNetworkOnly: nil,
744+
},
745+
wantErr: true,
746+
},
747+
{
748+
name: "error case 3: private_network_only is set to false and external_address is not set",
749+
args: args{
750+
ExternalAddress: nil,
751+
PrivateNetworkOnly: utils.Ptr(false),
752+
},
753+
wantErr: true,
754+
},
755+
}
756+
for _, tt := range tests {
757+
t.Run(tt.name, func(t *testing.T) {
758+
ctx := context.Background()
759+
diags := diag.Diagnostics{}
760+
model := &Model{
761+
ExternalAddress: types.StringPointerValue(tt.args.ExternalAddress),
762+
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
763+
"acl": types.SetNull(types.StringType),
764+
"observability": types.ObjectNull(observabilityTypes),
765+
"private_network_only": types.BoolPointerValue(tt.args.PrivateNetworkOnly),
766+
}),
767+
}
768+
769+
validateConfig(ctx, &diags, model)
770+
771+
if diags.HasError() != tt.wantErr {
772+
t.Errorf("validateConfig() = %v, want %v", diags.HasError(), tt.wantErr)
773+
}
774+
})
775+
}
776+
}

0 commit comments

Comments
 (0)