Skip to content

Commit af7d789

Browse files
Adjust mapping of LB API response when private network only field is … (#477)
* Adjust mapping of LB API response when private network only field is null * Improve comment Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com> --------- Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com>
1 parent 40ce909 commit af7d789

3 files changed

Lines changed: 160 additions & 16 deletions

File tree

stackit/internal/services/loadbalancer/loadbalancer/datasource.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ func (r *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRe
330330
}
331331

332332
// Map response body to schema
333-
err = mapFields(lbResp, &model)
333+
err = mapFields(ctx, lbResp, &model)
334334
if err != nil {
335335
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading load balancer", fmt.Sprintf("Processing API payload: %v", err))
336336
return

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
591591
}
592592

593593
// Map response body to schema
594-
err = mapFields(waitResp, &model)
594+
err = mapFields(ctx, waitResp, &model)
595595
if err != nil {
596596
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating load balancer", fmt.Sprintf("Processing API payload: %v", err))
597597
return
@@ -632,7 +632,7 @@ func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadReques
632632
}
633633

634634
// Map response body to schema
635-
err = mapFields(lbResp, &model)
635+
err = mapFields(ctx, lbResp, &model)
636636
if err != nil {
637637
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading load balancer", fmt.Sprintf("Processing API payload: %v", err))
638638
return
@@ -696,7 +696,7 @@ func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRe
696696
}
697697

698698
// Map response body to schema
699-
err = mapFields(getResp, &model)
699+
err = mapFields(ctx, getResp, &model)
700700
if err != nil {
701701
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating load balancer", fmt.Sprintf("Processing API payload: %v", err))
702702
return
@@ -1039,7 +1039,7 @@ func toTargetsPayload(ctx context.Context, tp *targetPool) (*[]loadbalancer.Targ
10391039
return &payload, nil
10401040
}
10411041

1042-
func mapFields(lb *loadbalancer.LoadBalancer, m *Model) error {
1042+
func mapFields(ctx context.Context, lb *loadbalancer.LoadBalancer, m *Model) error {
10431043
if lb == nil {
10441044
return fmt.Errorf("response input is nil")
10451045
}
@@ -1075,7 +1075,7 @@ func mapFields(lb *loadbalancer.LoadBalancer, m *Model) error {
10751075
if err != nil {
10761076
return fmt.Errorf("mapping network: %w", err)
10771077
}
1078-
err = mapOptions(lb, m)
1078+
err = mapOptions(ctx, lb, m)
10791079
if err != nil {
10801080
return fmt.Errorf("mapping options: %w", err)
10811081
}
@@ -1192,14 +1192,29 @@ func mapNetworks(loadBalancerResp *loadbalancer.LoadBalancer, m *Model) error {
11921192
return nil
11931193
}
11941194

1195-
func mapOptions(loadBalancerResp *loadbalancer.LoadBalancer, m *Model) error {
1195+
func mapOptions(ctx context.Context, loadBalancerResp *loadbalancer.LoadBalancer, m *Model) error {
11961196
if loadBalancerResp.Options == nil {
11971197
m.Options = types.ObjectNull(optionsTypes)
11981198
return nil
11991199
}
12001200

1201+
privateNetworkOnlyTF := types.BoolPointerValue(loadBalancerResp.Options.PrivateNetworkOnly)
1202+
1203+
// If the private_network_only field is nil in the response but is explicitly set to false in the model,
1204+
// we set it to false in the TF state to prevent an inconsistent result after apply error
1205+
if !m.Options.IsNull() && !m.Options.IsUnknown() {
1206+
optionsModel := options{}
1207+
diags := m.Options.As(ctx, &optionsModel, basetypes.ObjectAsOptions{})
1208+
if diags.HasError() {
1209+
return fmt.Errorf("convert options: %w", core.DiagsToError(diags))
1210+
}
1211+
if loadBalancerResp.Options.PrivateNetworkOnly == nil && !optionsModel.PrivateNetworkOnly.IsNull() && !optionsModel.PrivateNetworkOnly.IsUnknown() && !optionsModel.PrivateNetworkOnly.ValueBool() {
1212+
privateNetworkOnlyTF = types.BoolValue(false)
1213+
}
1214+
}
1215+
12011216
optionsMap := map[string]attr.Value{
1202-
"private_network_only": types.BoolPointerValue(loadBalancerResp.Options.PrivateNetworkOnly),
1217+
"private_network_only": privateNetworkOnlyTF,
12031218
}
12041219

12051220
err := mapACL(loadBalancerResp.Options.AccessControl, optionsMap)

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

Lines changed: 137 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,11 @@ func TestToTargetPoolUpdatePayload(t *testing.T) {
268268

269269
func TestMapFields(t *testing.T) {
270270
tests := []struct {
271-
description string
272-
input *loadbalancer.LoadBalancer
273-
expected *Model
274-
isValid bool
271+
description string
272+
input *loadbalancer.LoadBalancer
273+
modelPrivateNetworkOnly *bool
274+
expected *Model
275+
isValid bool
275276
}{
276277
{
277278
"default_values_ok",
@@ -288,6 +289,7 @@ func TestMapFields(t *testing.T) {
288289
},
289290
TargetPools: nil,
290291
},
292+
nil,
291293
&Model{
292294
Id: types.StringValue("pid,name"),
293295
ProjectId: types.StringValue("pid"),
@@ -304,9 +306,127 @@ func TestMapFields(t *testing.T) {
304306
},
305307
true,
306308
},
307-
308309
{
309310
"simple_values_ok",
311+
&loadbalancer.LoadBalancer{
312+
ExternalAddress: utils.Ptr("external_address"),
313+
Listeners: utils.Ptr([]loadbalancer.Listener{
314+
{
315+
DisplayName: utils.Ptr("display_name"),
316+
Port: utils.Ptr(int64(80)),
317+
Protocol: utils.Ptr("protocol"),
318+
ServerNameIndicators: &[]loadbalancer.ServerNameIndicator{
319+
{
320+
Name: utils.Ptr("domain.com"),
321+
},
322+
},
323+
TargetPool: utils.Ptr("target_pool"),
324+
},
325+
}),
326+
Name: utils.Ptr("name"),
327+
Networks: utils.Ptr([]loadbalancer.Network{
328+
{
329+
NetworkId: utils.Ptr("network_id"),
330+
Role: utils.Ptr("role"),
331+
},
332+
{
333+
NetworkId: utils.Ptr("network_id_2"),
334+
Role: utils.Ptr("role_2"),
335+
},
336+
}),
337+
Options: utils.Ptr(loadbalancer.LoadBalancerOptions{
338+
PrivateNetworkOnly: utils.Ptr(true),
339+
}),
340+
TargetPools: utils.Ptr([]loadbalancer.TargetPool{
341+
{
342+
ActiveHealthCheck: utils.Ptr(loadbalancer.ActiveHealthCheck{
343+
HealthyThreshold: utils.Ptr(int64(1)),
344+
Interval: utils.Ptr("2s"),
345+
IntervalJitter: utils.Ptr("3s"),
346+
Timeout: utils.Ptr("4s"),
347+
UnhealthyThreshold: utils.Ptr(int64(5)),
348+
}),
349+
Name: utils.Ptr("name"),
350+
TargetPort: utils.Ptr(int64(80)),
351+
Targets: utils.Ptr([]loadbalancer.Target{
352+
{
353+
DisplayName: utils.Ptr("display_name"),
354+
Ip: utils.Ptr("ip"),
355+
},
356+
}),
357+
SessionPersistence: utils.Ptr(loadbalancer.SessionPersistence{
358+
UseSourceIpAddress: utils.Ptr(true),
359+
}),
360+
},
361+
}),
362+
},
363+
nil,
364+
&Model{
365+
Id: types.StringValue("pid,name"),
366+
ProjectId: types.StringValue("pid"),
367+
ExternalAddress: types.StringValue("external_address"),
368+
Listeners: types.ListValueMust(types.ObjectType{AttrTypes: listenerTypes}, []attr.Value{
369+
types.ObjectValueMust(listenerTypes, map[string]attr.Value{
370+
"display_name": types.StringValue("display_name"),
371+
"port": types.Int64Value(80),
372+
"protocol": types.StringValue("protocol"),
373+
"server_name_indicators": types.ListValueMust(types.ObjectType{AttrTypes: serverNameIndicatorTypes}, []attr.Value{
374+
types.ObjectValueMust(
375+
serverNameIndicatorTypes,
376+
map[string]attr.Value{
377+
"name": types.StringValue("domain.com"),
378+
},
379+
),
380+
},
381+
),
382+
"target_pool": types.StringValue("target_pool"),
383+
}),
384+
}),
385+
Name: types.StringValue("name"),
386+
Networks: types.ListValueMust(types.ObjectType{AttrTypes: networkTypes}, []attr.Value{
387+
types.ObjectValueMust(networkTypes, map[string]attr.Value{
388+
"network_id": types.StringValue("network_id"),
389+
"role": types.StringValue("role"),
390+
}),
391+
types.ObjectValueMust(networkTypes, map[string]attr.Value{
392+
"network_id": types.StringValue("network_id_2"),
393+
"role": types.StringValue("role_2"),
394+
}),
395+
}),
396+
Options: types.ObjectValueMust(
397+
optionsTypes,
398+
map[string]attr.Value{
399+
"private_network_only": types.BoolValue(true),
400+
"acl": types.SetNull(types.StringType),
401+
},
402+
),
403+
TargetPools: types.ListValueMust(types.ObjectType{AttrTypes: targetPoolTypes}, []attr.Value{
404+
types.ObjectValueMust(targetPoolTypes, map[string]attr.Value{
405+
"active_health_check": types.ObjectValueMust(activeHealthCheckTypes, map[string]attr.Value{
406+
"healthy_threshold": types.Int64Value(1),
407+
"interval": types.StringValue("2s"),
408+
"interval_jitter": types.StringValue("3s"),
409+
"timeout": types.StringValue("4s"),
410+
"unhealthy_threshold": types.Int64Value(5),
411+
}),
412+
"name": types.StringValue("name"),
413+
"target_port": types.Int64Value(80),
414+
"targets": types.ListValueMust(types.ObjectType{AttrTypes: targetTypes}, []attr.Value{
415+
types.ObjectValueMust(targetTypes, map[string]attr.Value{
416+
"display_name": types.StringValue("display_name"),
417+
"ip": types.StringValue("ip"),
418+
}),
419+
}),
420+
"session_persistence": types.ObjectValueMust(sessionPersistenceTypes, map[string]attr.Value{
421+
"use_source_ip_address": types.BoolValue(true),
422+
}),
423+
}),
424+
}),
425+
},
426+
true,
427+
},
428+
{
429+
"simple_values_ok_with_null_private_network_only_response",
310430
&loadbalancer.LoadBalancer{
311431
ExternalAddress: utils.Ptr("external_address"),
312432
Listeners: utils.Ptr([]loadbalancer.Listener{
@@ -337,7 +457,7 @@ func TestMapFields(t *testing.T) {
337457
AccessControl: &loadbalancer.LoadbalancerOptionAccessControl{
338458
AllowedSourceRanges: utils.Ptr([]string{"cidr"}),
339459
},
340-
PrivateNetworkOnly: utils.Ptr(true),
460+
PrivateNetworkOnly: nil, // API sets this to nil if it's false in the request
341461
}),
342462
TargetPools: utils.Ptr([]loadbalancer.TargetPool{
343463
{
@@ -362,6 +482,7 @@ func TestMapFields(t *testing.T) {
362482
},
363483
}),
364484
},
485+
utils.Ptr(false),
365486
&Model{
366487
Id: types.StringValue("pid,name"),
367488
ProjectId: types.StringValue("pid"),
@@ -400,7 +521,7 @@ func TestMapFields(t *testing.T) {
400521
"acl": types.SetValueMust(
401522
types.StringType,
402523
[]attr.Value{types.StringValue("cidr")}),
403-
"private_network_only": types.BoolValue(true),
524+
"private_network_only": types.BoolValue(false),
404525
},
405526
),
406527
TargetPools: types.ListValueMust(types.ObjectType{AttrTypes: targetPoolTypes}, []attr.Value{
@@ -431,12 +552,14 @@ func TestMapFields(t *testing.T) {
431552
{
432553
"nil_response",
433554
nil,
555+
nil,
434556
&Model{},
435557
false,
436558
},
437559
{
438560
"no_name",
439561
&loadbalancer.LoadBalancer{},
562+
nil,
440563
&Model{},
441564
false,
442565
},
@@ -446,7 +569,13 @@ func TestMapFields(t *testing.T) {
446569
model := &Model{
447570
ProjectId: tt.expected.ProjectId,
448571
}
449-
err := mapFields(tt.input, model)
572+
if tt.modelPrivateNetworkOnly != nil {
573+
model.Options = types.ObjectValueMust(optionsTypes, map[string]attr.Value{
574+
"private_network_only": types.BoolValue(*tt.modelPrivateNetworkOnly),
575+
"acl": types.SetNull(types.StringType),
576+
})
577+
}
578+
err := mapFields(context.Background(), tt.input, model)
450579
if !tt.isValid && err == nil {
451580
t.Fatalf("Should have failed")
452581
}

0 commit comments

Comments
 (0)