-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #424 from Juniper/rt-validator
Improve validation of RT attribute strings
- Loading branch information
Showing
4 changed files
with
212 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package apstravalidator | ||
|
||
import ( | ||
"context" | ||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
"math" | ||
"net" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
const ( | ||
rtSep = ":" | ||
rtFormatErr = `A Route Target must take one of the following forms (leading zeros not permitted): | ||
- <2-byte-value>:<4-byte-value> | ||
- <4-byte-value>:<2-byte-value> | ||
- <IPv4-address>:<2-byte-value> | ||
` | ||
) | ||
|
||
var _ validator.String = ParseRtValidator{} | ||
|
||
type ParseRtValidator struct{} | ||
|
||
func (o ParseRtValidator) Description(_ context.Context) string { | ||
return "Ensures that the supplied can be parsed as a Route Target" | ||
} | ||
|
||
func (o ParseRtValidator) MarkdownDescription(ctx context.Context) string { | ||
return o.Description(ctx) | ||
} | ||
|
||
func (o ParseRtValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { | ||
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { | ||
return | ||
} | ||
|
||
// split the RT string | ||
parts := strings.Split(req.ConfigValue.ValueString(), rtSep) | ||
if len(parts) != 2 { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
|
||
var ipFound bool | ||
// check to see if part 1 is an IPv4 address | ||
ip := net.ParseIP(parts[0]) | ||
if ip != nil { | ||
ipFound = true // we got an IP! | ||
|
||
// is it IPv4? | ||
if len(ip.To4()) != net.IPv4len { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
} | ||
|
||
// make sure "part 1" has length and no prepended zeros (if we haven't already decided it's an IPv4 address) | ||
if !ipFound && (len(parts[0]) == 0 || (len(parts[0]) >= 2 && strings.HasPrefix(parts[0], "0"))) { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
|
||
// make sure "part 2" has length and no prepended zeros | ||
if len(parts[1]) == 0 || (len(parts[1]) >= 2 && strings.HasPrefix(parts[1], "0")) { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
|
||
// determine whether we've got a 32-bit "first part", and thus require a 16-bit "second part" | ||
var firstPartIs32bits bool | ||
if ipFound { | ||
firstPartIs32bits = true | ||
} else { | ||
// try parsing p1 as a 32-bit value | ||
p1, err := strconv.ParseUint(parts[0], 10, 32) | ||
if err != nil { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
|
||
// does p1 require 32 bits? | ||
if p1 > math.MaxUint16 { | ||
firstPartIs32bits = true | ||
} | ||
} | ||
|
||
// try parsing p2 as a 32-bit value | ||
p2, err := strconv.ParseUint(parts[1], 10, 32) | ||
if err != nil { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
|
||
// p1 and p2 can't both be 32 bits | ||
if firstPartIs32bits && p2 > math.MaxUint16 { | ||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( | ||
req.Path, rtFormatErr, req.ConfigValue.ValueString())) | ||
return | ||
} | ||
} | ||
|
||
func ParseRT() validator.String { | ||
return ParseRtValidator{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package apstravalidator | ||
|
||
import ( | ||
"context" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"testing" | ||
) | ||
|
||
func TestParseRtValidator(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
validRTs := []string{ | ||
"0:0", | ||
"1:1", | ||
"65535:65535", | ||
"65536:65535", | ||
"65535:65536", | ||
"0.0.0.0:0", | ||
"255.255.255.255:0", | ||
"0.0.0.0:65535", | ||
"255.255.255.255:65535", | ||
"4294967295:65535", | ||
"65535:4294967295", | ||
} | ||
|
||
invalidRTs := []string{ | ||
"", | ||
":", | ||
"1:", | ||
":1", | ||
"1", | ||
"4294967296:65535", | ||
"4294967295:65536", | ||
"65536:4294967295", | ||
"65535:4294967296", | ||
"-1:1", | ||
"1:-1", | ||
"256.1.2.3:1", | ||
"bogus", | ||
} | ||
|
||
type testCase struct { | ||
rt string | ||
expectErr bool | ||
} | ||
|
||
var testCases []testCase | ||
for _, rt := range validRTs { // load valid test cases | ||
testCases = append(testCases, testCase{rt: rt, expectErr: false}) | ||
} | ||
for _, rt := range invalidRTs { // load invalid test cases | ||
testCases = append(testCases, testCase{rt: rt, expectErr: true}) | ||
} | ||
|
||
for _, tCase := range testCases { | ||
tCase := tCase | ||
t.Run(tCase.rt, func(t *testing.T) { | ||
t.Parallel() | ||
request := validator.StringRequest{ | ||
Path: path.Root("test"), | ||
PathExpression: path.MatchRoot("test"), | ||
ConfigValue: types.StringValue(tCase.rt), | ||
} | ||
response := validator.StringResponse{} | ||
|
||
ParseRtValidator{}.ValidateString(ctx, request, &response) | ||
if response.Diagnostics.HasError() && !tCase.expectErr { | ||
t.Fail() // error where none expected | ||
} | ||
if !response.Diagnostics.HasError() && tCase.expectErr { | ||
t.Fail() // expected error not found | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters