Skip to content

Commit bb328a3

Browse files
authored
chore(network): ipv_prefix and ipv_prefix_length validation in network creation (#1498)
STACKITTPR-656 Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
1 parent cc8d710 commit bb328a3

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,47 @@ func (r *networkResource) ConfigValidators(_ context.Context) []resource.ConfigV
174174
}
175175
}
176176

177+
func (r *networkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
178+
var model Model
179+
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
180+
if resp.Diagnostics.HasError() {
181+
return
182+
}
183+
184+
// validation is done in extracted func so it's easier to unit-test it
185+
validateConfig(ctx, &resp.Diagnostics, &model)
186+
}
187+
188+
func validateConfig(ctx context.Context, diags *diag.Diagnostics, model *Model) {
189+
// IPv4 is used when any of those attributes is set
190+
ipv4IsActive := !model.IPv4Prefix.IsNull() ||
191+
!model.IPv4PrefixLength.IsNull() ||
192+
!model.IPv4Gateway.IsNull() ||
193+
!model.NoIPv4Gateway.IsNull() ||
194+
!model.IPv4Nameservers.IsNull()
195+
196+
if ipv4IsActive {
197+
if model.IPv4Prefix.IsNull() && model.IPv4PrefixLength.IsNull() {
198+
core.LogAndAddError(ctx, diags, "Invalid IPv4 configuration",
199+
"When IPv4 is configured, you must provide either 'ipv4_prefix' or 'ipv4_prefix_length'.")
200+
}
201+
}
202+
203+
// IPv6 is used when any of those attributes is set
204+
ipv6IsActive := !model.IPv6Prefix.IsNull() ||
205+
!model.IPv6PrefixLength.IsNull() ||
206+
!model.IPv6Gateway.IsNull() ||
207+
!model.NoIPv6Gateway.IsNull() ||
208+
!model.IPv6Nameservers.IsNull()
209+
210+
if ipv6IsActive {
211+
if model.IPv6Prefix.IsNull() && model.IPv6PrefixLength.IsNull() {
212+
core.LogAndAddError(ctx, diags, "Invalid IPv6 configuration",
213+
"When IPv6 is configured, you must provide either 'ipv6_prefix' or 'ipv6_prefix_length'.")
214+
}
215+
}
216+
}
217+
177218
// Schema defines the schema for the resource.
178219
func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
179220
description := "Network resource schema. Must have a `region` specified in the provider configuration."

stackit/internal/services/iaas/network/resource_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/google/go-cmp/cmp"
88
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/types"
1011
iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api"
1112
)
@@ -943,3 +944,135 @@ func TestModelIsIPv4ConfigSet(t *testing.T) {
943944
})
944945
}
945946
}
947+
948+
func TestValidateConfig(t *testing.T) {
949+
tests := []struct {
950+
name string
951+
model Model
952+
wantErr bool
953+
}{
954+
{
955+
name: "happy case: IPv4 only with prefix",
956+
model: Model{
957+
IPv4Prefix: types.StringValue("192.168.0.0/24"),
958+
IPv4Gateway: types.StringValue("192.168.0.1"),
959+
},
960+
wantErr: false,
961+
},
962+
{
963+
name: "happy case: IPv4 only with prefix_length",
964+
model: Model{
965+
IPv4PrefixLength: types.Int64Value(24),
966+
NoIPv4Gateway: types.BoolValue(true),
967+
},
968+
wantErr: false,
969+
},
970+
{
971+
name: "happy case: IPv6 only with prefix",
972+
model: Model{
973+
IPv6Prefix: types.StringValue("2001:db8::/64"),
974+
IPv6Gateway: types.StringValue("2001:db8::1"),
975+
},
976+
wantErr: false,
977+
},
978+
{
979+
name: "happy case: IPv6 only with prefix_length",
980+
model: Model{
981+
IPv6PrefixLength: types.Int64Value(64),
982+
NoIPv6Gateway: types.BoolValue(true),
983+
},
984+
wantErr: false,
985+
},
986+
{
987+
name: "happy case: both IPv4 and IPv6 configured correctly",
988+
model: Model{
989+
IPv4Prefix: types.StringValue("192.168.0.0/24"),
990+
NoIPv4Gateway: types.BoolValue(true),
991+
IPv6Prefix: types.StringValue("2001:db8::/64"),
992+
NoIPv6Gateway: types.BoolValue(true),
993+
},
994+
wantErr: false,
995+
},
996+
{
997+
name: "happy case: only IPv4 prefix",
998+
model: Model{
999+
IPv4Prefix: types.StringValue("192.168.0.0/24"),
1000+
},
1001+
wantErr: false,
1002+
},
1003+
{
1004+
name: "happy case: only IPv6 prefix",
1005+
model: Model{
1006+
IPv6Prefix: types.StringValue("2001:db8::/64"),
1007+
},
1008+
wantErr: false,
1009+
},
1010+
{
1011+
name: "happy case: only IPv4 prefix_length",
1012+
model: Model{
1013+
IPv4PrefixLength: types.Int64Value(24),
1014+
},
1015+
wantErr: false,
1016+
},
1017+
{
1018+
name: "happy case: only IPv6 prefix_length",
1019+
model: Model{
1020+
IPv6PrefixLength: types.Int64Value(64),
1021+
},
1022+
wantErr: false,
1023+
},
1024+
{
1025+
name: "error case: IPv4 active via gateway but missing prefix and prefix_length",
1026+
model: Model{
1027+
IPv4Gateway: types.StringValue("192.168.0.1"),
1028+
},
1029+
wantErr: true,
1030+
},
1031+
{
1032+
name: "error case: IPv6 active via no_gateway but missing prefix and prefix_length",
1033+
model: Model{
1034+
NoIPv6Gateway: types.BoolValue(true),
1035+
},
1036+
wantErr: true,
1037+
},
1038+
{
1039+
name: "error case: IPv4 active via nameservers but missing prefix and prefix_length",
1040+
model: Model{
1041+
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
1042+
types.StringValue("1.1.1.1"),
1043+
}),
1044+
},
1045+
wantErr: true,
1046+
},
1047+
{
1048+
name: "error case: IPv6 active via nameservers but missing prefix and prefix_length",
1049+
model: Model{
1050+
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
1051+
types.StringValue("2606:4700:4700::1111"),
1052+
}),
1053+
},
1054+
wantErr: true,
1055+
},
1056+
{
1057+
name: "error case: dual-stack failure (both active via nameservers, both missing prefixes)",
1058+
model: Model{
1059+
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("8.8.8.8")}),
1060+
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("2001:4860:4860::8888")}),
1061+
},
1062+
wantErr: true,
1063+
},
1064+
}
1065+
1066+
for _, tt := range tests {
1067+
t.Run(tt.name, func(t *testing.T) {
1068+
ctx := context.Background()
1069+
diags := diag.Diagnostics{}
1070+
1071+
validateConfig(ctx, &diags, &tt.model)
1072+
1073+
if diags.HasError() != tt.wantErr {
1074+
t.Errorf("validateConfig() error = %v, wantErr %v. Diagnostics: %v", diags.HasError(), tt.wantErr, diags)
1075+
}
1076+
})
1077+
}
1078+
}

0 commit comments

Comments
 (0)