@@ -23,6 +23,7 @@ import (
2323 "net/netip"
2424 "regexp"
2525 "slices"
26+ "strconv"
2627 "strings"
2728
2829 "go4.org/netipx"
@@ -146,7 +147,11 @@ func (r *linodeVPCValidator) ValidateDelete(ctx context.Context, obj runtime.Obj
146147
147148func (r * linodeVPCValidator ) validateLinodeVPCSpec (ctx context.Context , linodeclient clients.LinodeClient , spec infrav1alpha2.LinodeVPCSpec , skipAPIValidation bool ) field.ErrorList {
148149 // TODO: instrument with tracing, might need refactor to preserve readibility
149- var errs field.ErrorList
150+ var (
151+ errs field.ErrorList
152+ ipv6Range = spec .IPv6Range
153+ ipv6RangePath = field .NewPath ("spec" ).Child ("ipv6Range" )
154+ )
150155
151156 if ! skipAPIValidation {
152157 if err := validateRegion (ctx , linodeclient , spec .Region , field .NewPath ("spec" ).Child ("region" ), LinodeVPCCapability ); err != nil {
@@ -157,6 +162,12 @@ func (r *linodeVPCValidator) validateLinodeVPCSpec(ctx context.Context, linodecl
157162 errs = slices .Concat (errs , err )
158163 }
159164
165+ // Validate VPC IPv6 Range
166+ rangeErr := validateIPv6Range (ipv6Range , ipv6RangePath )
167+ if rangeErr != nil {
168+ errs = append (errs , rangeErr )
169+ }
170+
160171 if len (errs ) == 0 {
161172 return nil
162173 }
@@ -173,10 +184,12 @@ func (r *linodeVPCValidator) validateLinodeVPCSubnets(spec infrav1alpha2.LinodeV
173184
174185 for i , subnet := range spec .Subnets {
175186 var (
176- label = subnet .Label
177- labelPath = field .NewPath ("spec" ).Child ("Subnets" ).Index (i ).Child ("Label" )
178- ip = subnet .IPv4
179- ipPath = field .NewPath ("spec" ).Child ("Subnets" ).Index (i ).Child ("IPv4" )
187+ label = subnet .Label
188+ labelPath = field .NewPath ("spec" ).Child ("Subnets" ).Index (i ).Child ("Label" )
189+ ip = subnet .IPv4
190+ ipPath = field .NewPath ("spec" ).Child ("Subnets" ).Index (i ).Child ("IPv4" )
191+ ipv6Range = subnet .IPv6Range
192+ ipv6RangePath = field .NewPath ("spec" ).Child ("Subnets" ).Index (i ).Child ("IPv6Range" )
180193 )
181194
182195 // Validate Subnet Label
@@ -202,6 +215,12 @@ func (r *linodeVPCValidator) validateLinodeVPCSubnets(spec infrav1alpha2.LinodeV
202215 if cidrs , err = builder .IPSet (); err != nil {
203216 return append (field.ErrorList {}, field .InternalError (ipPath , fmt .Errorf ("build ip set: %w" , err )))
204217 }
218+
219+ // Validate Subnet IPv6 Range
220+ rangeErr := validateIPv6Range (ipv6Range , ipv6RangePath )
221+ if rangeErr != nil {
222+ errs = append (errs , rangeErr )
223+ }
205224 }
206225
207226 if len (errs ) == 0 {
@@ -282,3 +301,23 @@ func validateSubnetIPv4CIDR(cidr string, path *field.Path) (*netipx.IPSet, *fiel
282301 }
283302 return set , nil
284303}
304+
305+ func validateIPv6Range (ipv6Range string , path * field.Path ) * field.Error {
306+ var errs = []error {
307+ errors .New ("IPv6 range must start with /. Example: /64" ),
308+ errors .New ("IPv6 range doesn't contain a valid number after /" ),
309+ errors .New ("IPv6 range must be between /0 and /128" ),
310+ }
311+ if ! strings .HasPrefix (ipv6Range , "/" ) {
312+ return field .Invalid (path , ipv6Range , errs [0 ].Error ())
313+ }
314+ numStr := strings .TrimPrefix (ipv6Range , "/" )
315+ num , err := strconv .Atoi (numStr )
316+ if err != nil {
317+ return field .Invalid (path , ipv6Range , errs [1 ].Error ())
318+ }
319+ if num < 0 || num > 128 {
320+ return field .Invalid (path , ipv6Range , errs [2 ].Error ())
321+ }
322+ return nil
323+ }
0 commit comments