Alpha (#4)
* chore: initial push to be able to work together * chore: add missing wait folder * chore: add missing folders * chore: cleanup alpha branch * feat: mssql alpha instance (#2) * fix: remove unused attribute types and functions from backup models * fix: update API client references to use sqlserverflexalpha package * fix: update package references to use sqlserverflexalpha and modify user data source model * fix: add sqlserverflexalpha user data source to provider * fix: add sqlserverflexalpha user resource and update related functionality * chore: add stackit_sqlserverflexalpha_user resource and instance_id variable * fix: refactor sqlserverflexalpha user resource and enhance schema with status and default_database --------- Co-authored-by: Andre Harms <andre.harms@stackit.cloud> Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud> * feat: add sqlserver instance * chore: fixing tests * chore: update docs --------- Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud> Co-authored-by: Andre Harms <andre.harms@stackit.cloud>
This commit is contained in:
parent
45073a716b
commit
2733834fc9
351 changed files with 62744 additions and 3 deletions
373
stackit/internal/validate/validate.go
Normal file
373
stackit/internal/validate/validate.go
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
// Copyright (c) STACKIT
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/teambition/rrule-go"
|
||||
)
|
||||
|
||||
const (
|
||||
MajorMinorVersionRegex = `^\d+\.\d+?$`
|
||||
FullVersionRegex = `^\d+\.\d+.\d+?$`
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
description string
|
||||
markdownDescription string
|
||||
validate ValidationFn
|
||||
}
|
||||
|
||||
type ValidationFn func(context.Context, validator.StringRequest, *validator.StringResponse)
|
||||
|
||||
var _ = validator.String(&Validator{})
|
||||
|
||||
func (v *Validator) Description(_ context.Context) string {
|
||||
return v.description
|
||||
}
|
||||
|
||||
func (v *Validator) MarkdownDescription(_ context.Context) string {
|
||||
return v.markdownDescription
|
||||
}
|
||||
|
||||
func (v *Validator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
|
||||
return
|
||||
}
|
||||
v.validate(ctx, req, resp)
|
||||
}
|
||||
|
||||
func UUID() *Validator {
|
||||
description := "value must be an UUID"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
if _, err := uuid.Parse(req.ConfigValue.ValueString()); err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NoUUID() *Validator {
|
||||
description := "value must not be an UUID"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
if _, err := uuid.Parse(req.ConfigValue.ValueString()); err == nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IP returns a validator that checks, if the given string is a valid IP address.
|
||||
// The allowZeroAddress parameter defines, if 0.0.0.0, resp. [::] should be considered valid.
|
||||
func IP(allowZeroAddress bool) *Validator {
|
||||
description := "value must be an IP address"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
ip := net.ParseIP(req.ConfigValue.ValueString())
|
||||
invalidZeroAddress := !allowZeroAddress && (net.IPv4zero.Equal(ip) || net.IPv6zero.Equal(ip))
|
||||
if ip == nil || invalidZeroAddress {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RecordSet() *Validator {
|
||||
const typePath = "type"
|
||||
return &Validator{
|
||||
description: "value must be a valid record set",
|
||||
validate: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
recordType := basetypes.StringValue{}
|
||||
req.Config.GetAttribute(ctx, path.Root(typePath), &recordType)
|
||||
switch recordType.ValueString() {
|
||||
case "A":
|
||||
ip := net.ParseIP(req.ConfigValue.ValueString())
|
||||
if ip == nil || ip.To4() == nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
"value must be an IPv4 address",
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
case "AAAA":
|
||||
ip := net.ParseIP(req.ConfigValue.ValueString())
|
||||
if ip == nil || ip.To4() != nil || ip.To16() == nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
"value must be an IPv6 address",
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
case "CNAME":
|
||||
name := req.ConfigValue.ValueString()
|
||||
if name == "" || name[len(name)-1] != '.' {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
"value must be a Fully Qualified Domain Name (FQDN) and end with dot '.'",
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
case "NS":
|
||||
case "MX":
|
||||
case "TXT":
|
||||
case "ALIAS":
|
||||
case "DNAME":
|
||||
case "CAA":
|
||||
default:
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NoSeparator() *Validator {
|
||||
description := fmt.Sprintf("value must not contain identifier separator '%s'", core.Separator)
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
if strings.Contains(req.ConfigValue.ValueString(), core.Separator) {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NonLegacyProjectRole() *Validator {
|
||||
description := "legacy roles are not supported"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
if utils.IsLegacyProjectRole(req.ConfigValue.ValueString()) {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MinorVersionNumber() *Validator {
|
||||
description := "value must be a minor version number, without a leading 'v': '[MAJOR].[MINOR]'"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
exp := MajorMinorVersionRegex
|
||||
r := regexp.MustCompile(exp)
|
||||
version := req.ConfigValue.ValueString()
|
||||
if !r.MatchString(version) {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func VersionNumber() *Validator {
|
||||
description := "value must be a version number, without a leading 'v': '[MAJOR].[MINOR]' or '[MAJOR].[MINOR].[PATCH]'"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
minorVersionExp := MajorMinorVersionRegex
|
||||
minorVersionRegex := regexp.MustCompile(minorVersionExp)
|
||||
|
||||
versionExp := FullVersionRegex
|
||||
versionRegex := regexp.MustCompile(versionExp)
|
||||
|
||||
version := req.ConfigValue.ValueString()
|
||||
if !minorVersionRegex.MatchString(version) && !versionRegex.MatchString(version) {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RFC3339SecondsOnly() *Validator {
|
||||
description := "value must be in RFC339 format (seconds only)"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
t, err := time.Parse(time.RFC3339, req.ConfigValue.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it failed because it has nanoseconds
|
||||
if t.Nanosecond() != 0 {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
"value can't have fractional seconds",
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CIDR() *Validator {
|
||||
description := "value must be in CIDR notation"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
_, _, err := net.ParseCIDR(req.ConfigValue.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
"parsing value in CIDR notation: invalid CIDR address",
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Rrule() *Validator {
|
||||
description := "value must be in a valid RRULE format"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
// The go library rrule-go expects \n before RRULE (to be a newline and not a space)
|
||||
// for example: "DTSTART;TZID=America/New_York:19970902T090000\nRRULE:FREQ=DAILY;COUNT=10"
|
||||
// whereas a valid rrule according to the API docs is:
|
||||
// for example: "DTSTART;TZID=America/New_York:19970902T090000 RRULE:FREQ=DAILY;COUNT=10"
|
||||
//
|
||||
// So we will accept a ' ' (which is valid per API docs),
|
||||
// but replace it with a '\n' for the rrule-go validations
|
||||
value := req.ConfigValue.ValueString()
|
||||
value = strings.ReplaceAll(value, " ", "\n")
|
||||
|
||||
if _, err := rrule.StrToRRuleSet(value); err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func FileExists() *Validator {
|
||||
description := "file must exist"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
_, err := os.Stat(req.ConfigValue.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ValidDurationString() *Validator {
|
||||
description := "value must be in a valid duration string. Such as \"300ms\", \"-1.5h\" or \"2h45m\".\nValid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"."
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
_, err := time.ParseDuration(req.ConfigValue.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidNoTrailingNewline returns a Validator that checks if the input string has no trailing newline
|
||||
// character ("\n" or "\r\n"). If a trailing newline is present, a diagnostic error will be appended.
|
||||
func ValidNoTrailingNewline() *Validator {
|
||||
description := `The value must not have a trailing newline character ("\n" or "\r\n"). You can remove a trailing newline by using Terraform's built-in chomp() function.`
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
val := req.ConfigValue.ValueString()
|
||||
if val == "" {
|
||||
return
|
||||
}
|
||||
if len(val) >= 2 && val[len(val)-2:] == "\r\n" {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
val,
|
||||
))
|
||||
return
|
||||
}
|
||||
if val[len(val)-1] == '\n' {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
val,
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
970
stackit/internal/validate/validate_test.go
Normal file
970
stackit/internal/validate/validate_test.go
Normal file
|
|
@ -0,0 +1,970 @@
|
|||
// Copyright (c) STACKIT
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
)
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"cae27bba-c43d-498a-861e-d11d241c4ff8",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"too short",
|
||||
"a-b-c-d",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not UUID",
|
||||
"www-541-%",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
UUID().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoUUID(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"UUID",
|
||||
"cae27bba-c43d-498a-861e-d11d241c4ff8",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no UUID",
|
||||
"a-b-c-d",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"domain name",
|
||||
"www.test.de",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
NoUUID().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
invalidZero bool
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok IP4",
|
||||
false,
|
||||
"111.222.111.222",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok IP6",
|
||||
false,
|
||||
"2001:0db8:85a3:08d3::0370:7344",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"too short",
|
||||
false,
|
||||
"0.1.2",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
false,
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Not an IP",
|
||||
false,
|
||||
"for-sure-not-an-IP",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid ipv4 zero",
|
||||
true,
|
||||
"0.0.0.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid ipv4 zero",
|
||||
false,
|
||||
"0.0.0.0",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid ipv6 zero",
|
||||
true,
|
||||
"::",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid ipv6 zero short notation",
|
||||
true,
|
||||
"::0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid ipv6 zero long notation",
|
||||
true,
|
||||
"0000:0000:0000:0000:0000:0000:0000:0000",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid ipv6 zero short notation",
|
||||
false,
|
||||
"::",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid ipv6 zero long notation",
|
||||
false,
|
||||
"0000:0000:0000:0000:0000:0000:0000:0000",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
IP(tt.invalidZero).ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
record string
|
||||
recordType string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"A record ok IP4",
|
||||
"111.222.111.222",
|
||||
"A",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"A record fail IP6",
|
||||
"2001:0db8:85a3:08d3::0370:7344",
|
||||
"A",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"A record too short",
|
||||
"0.1.2",
|
||||
"A",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"A record Empty",
|
||||
"",
|
||||
"A",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"A record Not an IP",
|
||||
"for-sure-not-an-IP",
|
||||
"A",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"AAAA record fail IP4",
|
||||
"111.222.111.222",
|
||||
"AAAA",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"AAAA record ok IP6",
|
||||
"2001:0db8:85a3:08d3::0370:7344",
|
||||
"AAAA",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"AAAA record too short",
|
||||
"0.1.2",
|
||||
"AAAA",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"AAAA record Empty",
|
||||
"",
|
||||
"AAAA",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"AAAA record Not an IP",
|
||||
"for-sure-not-an-IP",
|
||||
"AAAA",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"CNAME record Not a Fully Qualified Domain Name",
|
||||
"stackit.de",
|
||||
"CNAME",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"CNAME record ok Fully Qualified Domain Name",
|
||||
"stackit.de.",
|
||||
"CNAME",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"NS record",
|
||||
"some-record",
|
||||
"NS",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"MX record",
|
||||
"some-record",
|
||||
"MX",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"TXT record",
|
||||
"some-record",
|
||||
"TXT",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ALIAS record",
|
||||
"some-record",
|
||||
"ALIAS",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"DNAME record",
|
||||
"some-record",
|
||||
"DNAME",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"CAA record",
|
||||
"some-record",
|
||||
"CAA",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"random record",
|
||||
"some-record",
|
||||
"random",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
scheme := tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"type": tftypes.String,
|
||||
},
|
||||
}
|
||||
value := map[string]tftypes.Value{
|
||||
"type": tftypes.NewValue(tftypes.String, tt.recordType),
|
||||
}
|
||||
record := tftypes.NewValue(scheme, value)
|
||||
|
||||
RecordSet().ValidateString(context.Background(), validator.StringRequest{
|
||||
Config: tfsdk.Config{
|
||||
Schema: schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"type": schema.StringAttribute{},
|
||||
},
|
||||
},
|
||||
Raw: record,
|
||||
},
|
||||
ConfigValue: types.StringValue(tt.record),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSeparator(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"ABCD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"#$%&/()=.;-",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"ab,",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
NoSeparator().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonLegacyProjectRole(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"owner",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"reader",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"leagcy-role",
|
||||
"project.owner",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"leagcy-role-2",
|
||||
"project.admin",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"leagcy-role-3",
|
||||
"project.member",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"leagcy-role-4",
|
||||
"project.auditor",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
NonLegacyProjectRole().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinorVersionNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"1.20",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"1.3",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-3",
|
||||
"10.1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"afssfdfs",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-major-version",
|
||||
"1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-patch-version",
|
||||
"1.20.1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-version",
|
||||
"v1.20.1",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
MinorVersionNumber().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"1.20",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"1.3",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-3",
|
||||
"10.1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version",
|
||||
"1.20.1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version-2",
|
||||
"1.20.10",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version-3",
|
||||
"10.20.10",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"afssfdfs",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-major-version",
|
||||
"1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-version",
|
||||
"v1.20.1",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
VersionNumber().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC3339SecondsOnly(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"9999-01-02T03:04:05Z",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok_2",
|
||||
"9999-01-02T03:04:05+06:00",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not_ok",
|
||||
"foo-bar",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"with_sub_seconds",
|
||||
"9999-01-02T03:04:05.678Z",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"with_sub_seconds_2",
|
||||
"9999-01-02T03:04:05.678+06:00",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
RFC3339SecondsOnly().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDR(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"IPv4_block",
|
||||
"198.51.100.14/24",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv4_block_2",
|
||||
"111.222.111.222/22",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv4_single",
|
||||
"198.51.100.14/32",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv4_entire_internet",
|
||||
"0.0.0.0/0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv4_block_invalid",
|
||||
"198.51.100.14/33",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"IPv4_no_block",
|
||||
"111.222.111.222",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"IPv6_block",
|
||||
"2001:db8::/48",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv6_single",
|
||||
"2001:0db8:85a3:08d3::0370:7344/128",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv6_all",
|
||||
"::/0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IPv6_block_invalid",
|
||||
"2001:0db8:85a3:08d3::0370:7344/129",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"IPv6_no_block",
|
||||
"2001:0db8:85a3:08d3::0370:7344",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
CIDR().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRrule(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"DTSTART;TZID=Europe/Sofia:20200803T023000\nRULE:FREQ=DAILY;INTERVAL=1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"afssfdfs",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-missing-space-before-rrule",
|
||||
"DTSTART;TZID=Europe/Sofia:20200803T023000RRULE:FREQ=DAILY;INTERVAL=1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-missing-interval",
|
||||
"DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
Rrule().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"testdata/file.txt",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"testdata/non-existing-file.txt",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
FileExists().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidTtlDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"valid duration with hours, minutes, and seconds",
|
||||
"5h30m40s",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid duration with hours only",
|
||||
"5h",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid duration with hours and minutes",
|
||||
"5h30m",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid duration with minutes only",
|
||||
"30m",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid duration with seconds only",
|
||||
"30s",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid duration with incorrect unit",
|
||||
"30o",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid duration without unit",
|
||||
"30",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid duration with invalid letters",
|
||||
"30e",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid duration with letters in middle",
|
||||
"1h30x",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty string",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
va := ValidDurationString()
|
||||
va.ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Expected validation to fail for input: %v", tt.input)
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Expected validation to succeed for input: %v, but got errors: %v", tt.input, r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidNoTrailingNewline(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"string with no trailing newline",
|
||||
"abc",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"string with trailing \\n",
|
||||
"abc\n",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"string with trailing \\r\\n",
|
||||
"abc\r\n",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"string with internal newlines but not trailing",
|
||||
"abc\ndef\nghi",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"empty string",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"string that is just \\n",
|
||||
"\n",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"string that is just \\r\\n",
|
||||
"\r\n",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"string with multiple newlines, trailing",
|
||||
"abc\n\n",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"string with newlines but ends with character",
|
||||
"abc\ndef\n",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
va := ValidNoTrailingNewline()
|
||||
va.ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Expected validation to fail for input: %q", tt.input)
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Expected validation to succeed for input: %q, but got errors: %v", tt.input, r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue