Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add missing validations in the database #389

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions resources/types/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func NewDatabase(r *Database, resourceId string, db *resourcespb.DatabaseArgs, o

var MysqlVersions = []string{"5.6", "5.7", "8.0"}
var PostgresVersions = []string{"10", "11", "12", "13", "14"}
var MariaDBVersions = []string{"10.2", "10.3"} // https://docs.microsoft.com/bs-latn-ba/azure/mariadb/concepts-supported-versions

func (r *Database) Validate(ctx resources.MultyContext) (errs []validate.ValidationError) {
errs = append(errs, r.ResourceWithId.Validate()...)
Expand All @@ -83,10 +84,24 @@ func (r *Database) Validate(ctx resources.MultyContext) (errs []validate.Validat
errs = append(errs, r.NewValidationError(fmt.Errorf("'%s' is an unsupported engine version for postgres, must be one of %+q", r.Args.EngineVersion, PostgresVersions), "engine_version"))
}
}
if r.Args.Engine == resourcespb.DatabaseEngine_MARIADB && r.GetCloud() == commonpb.CloudProvider_GCP {
errs = append(errs, r.NewValidationError(fmt.Errorf("mariadb is not supported in gcp"), "engine"))
if r.Args.Engine == resourcespb.DatabaseEngine_MARIADB {
if r.GetCloud() == commonpb.CloudProvider_GCP {
errs = append(errs, r.NewValidationError(fmt.Errorf("mariadb is not supported in gcp"), "engine"))
}
if !slices.Contains(MariaDBVersions, r.Args.EngineVersion) {
errs = append(errs, r.NewValidationError(fmt.Errorf("'%s' is an unsupported engine version for mariadb, must be one of %+q", r.Args.EngineVersion, MariaDBVersions), "engine_version"))
}
}
if r.Args.Size == commonpb.DatabaseSize_UNKNOWN_VM_SIZE {
errs = append(errs, r.NewValidationError(fmt.Errorf("unknown database size"), "size"))
}
usernameValidator := validate.NewDbUsernameValidator(r.Args.Engine, r.Args.EngineVersion)
passwordValidator := validate.NewDbPasswordValidator(r.Args.Engine)
if err := usernameValidator.Check(r.Args.Username, r.ResourceId); err != nil {
errs = append(errs, r.NewValidationError(err, "username"))
}
if err := passwordValidator.Check(r.Args.Password, r.ResourceId); err != nil {
errs = append(errs, r.NewValidationError(err, "password"))
}
// TODO regex validate r username && password
// TODO validate DB Size
return errs
}
65 changes: 62 additions & 3 deletions validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import (
"bufio"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/multycloud/multy/api/proto/resourcespb"
"golang.org/x/exp/constraints"
"io/ioutil"
"regexp"
)

type Validator interface {
Check(value string, valueType interface{}) error
}

type RegexpValidator struct {
pattern string
errorTemplate string
Expand All @@ -30,7 +35,7 @@ func (r *RegexpValidator) Check(value string, valueType interface{}) error {
const wordWithDotHyphenUnder80Pattern = string(`^[a-zA-Z\d]$|^[a-zA-Z\d][\w\-.]{0,78}\w$`)

//NewWordWithDotHyphenUnder80Validator creates new RegexpValidator validating with wordWithDotHyphenUnder80Pattern.
func NewWordWithDotHyphenUnder80Validator() *RegexpValidator {
func NewWordWithDotHyphenUnder80Validator() Validator {
return &RegexpValidator{wordWithDotHyphenUnder80Pattern, "%s can contain only alphanumerics, underscores, periods, and hyphens;" +
" must start with alphanumeric and end with alphanumeric or underscore and have 1-80 length", nil}
}
Expand All @@ -39,7 +44,7 @@ func NewWordWithDotHyphenUnder80Validator() *RegexpValidator {
const cidrIPv4Pattern = string(`^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)([\/][0-3][0-2]?|[\/][1-2][0-9]|[\/][0-9])?$`)

//NewCIDRIPv4Check creates new RegexpValidator validating CIDR IPv4
func NewCIDRIPv4Check() *RegexpValidator {
func NewCIDRIPv4Check() Validator {
return &RegexpValidator{cidrIPv4Pattern, "%s not valid CIDR IPv4 value", nil}
}

Expand All @@ -60,7 +65,7 @@ func matchWholeWordsPattern(words []string) string {
}

// NewProtocolCheck checks if provided protocol value is allowed in every deployment environment.
func NewProtocolCheck() *RegexpValidator {
func NewProtocolCheck() Validator {
return &RegexpValidator{matchWholeWordsPattern([]string{"tcp", "udp", "icmp", "\\*"}),
"%s didn't match any protocol allowed value", nil}
}
Expand Down Expand Up @@ -95,6 +100,60 @@ func NewPriorityCheck() InRangeIncludingCheck[int64] {
return newInRangeExcludingCheck[int64]("%v priority value %v cannot be %v than %v", 100, 4096)
}

type maxLengthValidator struct {
maxLength int
}

func (m maxLengthValidator) Check(value string, valueType interface{}) error {
if len(value) < 1 {
return fmt.Errorf("%v cannot be empty", valueType)
} else if len(value) > m.maxLength {
return fmt.Errorf("%v maximum length is %v", valueType, m.maxLength)
}
return nil
}

type dummyValidator struct {
}

func (d dummyValidator) Check(value string, valueType interface{}) error {
return nil
}

func NewDbUsernameValidator(engine resourcespb.DatabaseEngine, version string) Validator {
switch engine {
case resourcespb.DatabaseEngine_MYSQL:
switch version {
case "5.6":
return &maxLengthValidator{16}
default:
return &maxLengthValidator{32}
}
case resourcespb.DatabaseEngine_POSTGRES:
return &maxLengthValidator{63}
case resourcespb.DatabaseEngine_MARIADB:
return &maxLengthValidator{80}
default:
return &dummyValidator{}
}
}

func NewDbPasswordValidator(engine resourcespb.DatabaseEngine) Validator {
switch engine {
case resourcespb.DatabaseEngine_MYSQL:
// https://stackoverflow.com/a/31634299
return &maxLengthValidator{32}
case resourcespb.DatabaseEngine_POSTGRES:
// https://stackoverflow.com/a/19499303
return &maxLengthValidator{99}
case resourcespb.DatabaseEngine_MARIADB:
// https://github.com/MariaDB/server/blob/7c58e97bf6f80a251046c5b3e7bce826fe058bd6/mysys/get_password.c#L65
return &maxLengthValidator{79}
default:
return &dummyValidator{}
}
}

type ResourceValidationInfo struct {
SourceRanges map[string]hcl.Range
BlockDefRange hcl.Range
Expand Down
2 changes: 1 addition & 1 deletion validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"testing"
)

func testRegexp(validator *validate.RegexpValidator, shouldMatch, shouldntMatch []string, t *testing.T) {
func testRegexp(validator validate.Validator, shouldMatch, shouldntMatch []string, t *testing.T) {
for _, name := range shouldMatch {
if err := validator.Check(name, "some_val"); err != nil {
t.Errorf("%v should match %s, but didn't", validator, name)
Expand Down