Skip to content

Commit 49b360c

Browse files
committed
fix(networkinterface): Recognize the removal of allowed_addresses and security_group_ids from terraform config
Signed-off-by: Alexander Dahmen <[email protected]>
1 parent e627296 commit 49b360c

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

stackit/internal/services/iaas/networkinterface/resource.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var (
3333
_ resource.Resource = &networkInterfaceResource{}
3434
_ resource.ResourceWithConfigure = &networkInterfaceResource{}
3535
_ resource.ResourceWithImportState = &networkInterfaceResource{}
36+
_ resource.ResourceWithModifyPlan = &networkInterfaceResource{}
3637
)
3738

3839
type Model struct {
@@ -61,6 +62,38 @@ type networkInterfaceResource struct {
6162
client *iaas.APIClient
6263
}
6364

65+
// ModifyPlan implements resource.ResourceWithModifyPlan.
66+
func (r *networkInterfaceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
67+
// skip initial empty configuration to avoid follow-up errors
68+
if req.Config.Raw.IsNull() {
69+
return
70+
}
71+
var configModel Model
72+
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
73+
if resp.Diagnostics.HasError() {
74+
return
75+
}
76+
77+
var planModel Model
78+
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
79+
if resp.Diagnostics.HasError() {
80+
return
81+
}
82+
// If allowed_addresses were completly removed from the config this is not recognized by terraform
83+
// since this field is optional and computed therefore this plan modifier is needed.
84+
utils.CheckListRemoval(ctx, configModel.AllowedAddresses, planModel.AllowedAddresses, path.Root("allowed_addresses"), types.StringType, false, resp)
85+
if resp.Diagnostics.HasError() {
86+
return
87+
}
88+
89+
// If security_group_ids were completly removed from the config this is not recognized by terraform
90+
// since this field is optional and computed therefore this plan modifier is needed.
91+
utils.CheckListRemoval(ctx, configModel.SecurityGroupIds, planModel.SecurityGroupIds, path.Root("security_group_ids"), types.StringType, true, resp)
92+
if resp.Diagnostics.HasError() {
93+
return
94+
}
95+
}
96+
6497
// Metadata returns the resource type name.
6598
func (r *networkInterfaceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
6699
resp.TypeName = req.ProviderTypeName + "_network_interface"

stackit/internal/utils/utils.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import (
77
"regexp"
88
"strings"
99

10+
"github.com/hashicorp/terraform-plugin-framework/attr"
1011
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/hashicorp/terraform-plugin-framework/path"
13+
"github.com/hashicorp/terraform-plugin-framework/resource"
1114
"github.com/hashicorp/terraform-plugin-framework/types"
1215
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1316
"github.com/hashicorp/terraform-plugin-log/tflog"
@@ -157,3 +160,16 @@ func FormatPossibleValues(values ...string) string {
157160
func BuildInternalTerraformId(idParts ...string) types.String {
158161
return types.StringValue(strings.Join(idParts, core.Separator))
159162
}
163+
164+
// If a List was completely removed from the terraform config this is not recognized by terraform.
165+
// This helper function checks if that is the case and adjusts the plan accordingly.
166+
func CheckListRemoval(ctx context.Context, configModelList, planModelList types.List, destination path.Path, listType attr.Type, createEmptyList bool, resp *resource.ModifyPlanResponse) {
167+
if configModelList.IsNull() && !planModelList.IsNull() {
168+
if createEmptyList {
169+
emptyList, _ := types.ListValueFrom(ctx, listType, []string{})
170+
resp.Diagnostics.Append(resp.Plan.SetAttribute(ctx, destination, emptyList)...)
171+
} else {
172+
resp.Diagnostics.Append(resp.Plan.SetAttribute(ctx, destination, types.ListNull(listType))...)
173+
}
174+
}
175+
}

stackit/internal/utils/utils_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package utils
22

33
import (
4+
"context"
45
"fmt"
56
"reflect"
67
"testing"
78

89
"github.com/google/go-cmp/cmp"
910
"github.com/hashicorp/terraform-plugin-framework/attr"
11+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/path"
13+
"github.com/hashicorp/terraform-plugin-framework/resource"
14+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1015
"github.com/hashicorp/terraform-plugin-framework/types"
1116
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1217
)
@@ -378,3 +383,121 @@ func TestBuildInternalTerraformId(t *testing.T) {
378383
})
379384
}
380385
}
386+
387+
func TestCheckListRemoval(t *testing.T) {
388+
type model struct {
389+
AllowedAddresses types.List `tfsdk:"allowed_addresses"`
390+
}
391+
tests := []struct {
392+
description string
393+
configModelList types.List
394+
planModelList types.List
395+
path path.Path
396+
listType attr.Type
397+
createEmptyList bool
398+
expectedAdjustedResp bool
399+
}{
400+
{
401+
"config and plan are the same - no change",
402+
types.ListValueMust(types.StringType, []attr.Value{
403+
types.StringValue("value1"),
404+
}),
405+
types.ListValueMust(types.StringType, []attr.Value{
406+
types.StringValue("value1"),
407+
}),
408+
path.Root("allowed_addresses"),
409+
types.StringType,
410+
false,
411+
false,
412+
},
413+
{
414+
"list was removed from config",
415+
types.ListNull(types.StringType),
416+
types.ListValueMust(types.StringType, []attr.Value{
417+
types.StringValue("value1"),
418+
}),
419+
path.Root("allowed_addresses"),
420+
types.StringType,
421+
false,
422+
true,
423+
},
424+
{
425+
"list was added to config",
426+
types.ListValueMust(types.StringType, []attr.Value{
427+
types.StringValue("value1"),
428+
}),
429+
types.ListNull(types.StringType),
430+
path.Root("allowed_addresses"),
431+
types.StringType,
432+
false,
433+
false,
434+
},
435+
{
436+
"no list provided at all",
437+
types.ListNull(types.StringType),
438+
types.ListNull(types.StringType),
439+
path.Root("allowed_addresses"),
440+
types.StringType,
441+
false,
442+
false,
443+
},
444+
{
445+
"create empty list test - list was removed from config",
446+
types.ListNull(types.StringType),
447+
types.ListValueMust(types.StringType, []attr.Value{
448+
types.StringValue("value1"),
449+
}),
450+
path.Root("allowed_addresses"),
451+
types.StringType,
452+
true,
453+
true,
454+
},
455+
}
456+
for _, tt := range tests {
457+
t.Run(tt.description, func(t *testing.T) {
458+
// create resp
459+
plan := tfsdk.Plan{
460+
Schema: schema.Schema{
461+
Attributes: map[string]schema.Attribute{
462+
"allowed_addresses": schema.ListAttribute{
463+
ElementType: basetypes.StringType{},
464+
},
465+
},
466+
},
467+
}
468+
469+
// set input planModelList to plan
470+
if diags := plan.Set(context.Background(), model{tt.planModelList}); diags.HasError() {
471+
t.Fatalf("cannot create test model: %v", diags)
472+
}
473+
resp := resource.ModifyPlanResponse{
474+
Plan: plan,
475+
}
476+
477+
CheckListRemoval(context.Background(), tt.configModelList, tt.planModelList, tt.path, tt.listType, tt.createEmptyList, &resp)
478+
// check targetList
479+
var respList types.List
480+
resp.Plan.GetAttribute(context.Background(), tt.path, &respList)
481+
482+
if tt.createEmptyList {
483+
emptyList, _ := types.ListValueFrom(context.Background(), tt.listType, []string{})
484+
diffEmptyList := cmp.Diff(emptyList, respList)
485+
if diffEmptyList != "" {
486+
t.Fatalf("an empty list should have been created but was not: %s", diffEmptyList)
487+
}
488+
}
489+
490+
// compare planModelList and resp list
491+
diff := cmp.Diff(tt.planModelList, respList)
492+
if tt.expectedAdjustedResp {
493+
if diff == "" {
494+
t.Fatalf("plan should be adjusted but was not")
495+
}
496+
} else {
497+
if diff != "" {
498+
t.Fatalf("plan should not be adjusted but diff is: %s", diff)
499+
}
500+
}
501+
})
502+
}
503+
}

0 commit comments

Comments
 (0)