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>
This commit is contained in:
Marcel Jacek 2025-05-23 13:25:30 +02:00 committed by GitHub
parent 29f9a01633
commit aaf29e4c19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 126 additions and 2 deletions

View file

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@ -237,6 +238,43 @@ func (r *loadBalancerResource) ModifyPlan(ctx context.Context, req resource.Modi
}
}
// ConfigValidators validates the resource configuration
func (r *loadBalancerResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var model Model
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}
// validation is done in extracted func so it's easier to unit-test it
validateConfig(ctx, &resp.Diagnostics, &model)
}
func validateConfig(ctx context.Context, diags *diag.Diagnostics, model *Model) {
externalAddressIsSet := !model.ExternalAddress.IsNull()
lbOptions, err := toOptionsPayload(ctx, model)
if err != nil || lbOptions == nil {
// private_network_only is not set and external_address is not set
if !externalAddressIsSet {
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))
}
return
}
if lbOptions.PrivateNetworkOnly == nil || !*lbOptions.PrivateNetworkOnly {
// private_network_only is not set or false and external_address is not set
if !externalAddressIsSet {
core.LogAndAddError(ctx, diags, "Error configuring load balancer", "You need to provide either the `options.private_network_only = true` or `external_address` field.")
}
return
}
// Both are set
if *lbOptions.PrivateNetworkOnly && externalAddressIsSet {
core.LogAndAddError(ctx, diags, "Error configuring load balancer", "You need to provide either the `options.private_network_only = true` or `external_address` field.")
}
}
// Configure adds the provider configured client to the resource.
func (r *loadBalancerResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
var ok bool
@ -327,7 +365,7 @@ The example below creates the supporting infrastructure using the STACKIT Terraf
},
"external_address": schema.StringAttribute{
Description: descriptions["external_address"],
Required: true,
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},

View file

@ -7,11 +7,16 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
)
const (
testExternalAddress = "95.46.74.109"
)
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
@ -688,3 +693,84 @@ func TestMapFields(t *testing.T) {
})
}
}
func Test_validateConfig(t *testing.T) {
type args struct {
ExternalAddress *string
PrivateNetworkOnly *bool
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "happy case 1: private_network_only is not set and external_address is set",
args: args{
ExternalAddress: utils.Ptr(testExternalAddress),
PrivateNetworkOnly: nil,
},
wantErr: false,
},
{
name: "happy case 2: private_network_only is set to false and external_address is set",
args: args{
ExternalAddress: utils.Ptr(testExternalAddress),
PrivateNetworkOnly: utils.Ptr(false),
},
wantErr: false,
},
{
name: "happy case 3: private_network_only is set to true and external_address is not set",
args: args{
ExternalAddress: nil,
PrivateNetworkOnly: utils.Ptr(true),
},
wantErr: false,
},
{
name: "error case 1: private_network_only and external_address are set",
args: args{
ExternalAddress: utils.Ptr(testExternalAddress),
PrivateNetworkOnly: utils.Ptr(true),
},
wantErr: true,
},
{
name: "error case 2: private_network_only is not set and external_address is not set",
args: args{
ExternalAddress: nil,
PrivateNetworkOnly: nil,
},
wantErr: true,
},
{
name: "error case 3: private_network_only is set to false and external_address is not set",
args: args{
ExternalAddress: nil,
PrivateNetworkOnly: utils.Ptr(false),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
diags := diag.Diagnostics{}
model := &Model{
ExternalAddress: types.StringPointerValue(tt.args.ExternalAddress),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
"acl": types.SetNull(types.StringType),
"observability": types.ObjectNull(observabilityTypes),
"private_network_only": types.BoolPointerValue(tt.args.PrivateNetworkOnly),
}),
}
validateConfig(ctx, &diags, model)
if diags.HasError() != tt.wantErr {
t.Errorf("validateConfig() = %v, want %v", diags.HasError(), tt.wantErr)
}
})
}
}