From 71d84903e9984a4b4ab8c10f17edea6545658cf3 Mon Sep 17 00:00:00 2001 From: Dolapo Toki Date: Fri, 31 Jul 2020 17:46:10 -0700 Subject: [PATCH 1/5] user prompt input validation --- internal/init/init.go | 2 +- internal/init/prompts.go | 46 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/internal/init/init.go b/internal/init/init.go index d5884b1bb..3a9c410c0 100644 --- a/internal/init/init.go +++ b/internal/init/init.go @@ -125,7 +125,7 @@ func getProjectNamePrompt() PromptHandler { Default: "", }, Condition: NoCondition, - Validate: NoValidation, + Validate: ValidatePname, } } diff --git a/internal/init/prompts.go b/internal/init/prompts.go index a5fb56103..10a6b0438 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -88,6 +88,36 @@ func ValidateSAK(input string) error { return nil } +// ValidatePname validates Project Name field string input. +func ValidatePname(input string) error { + // the first 62 char out of base64 and - + var pName = regexp.MustCompile(`^[A-Za-z0-9-]{1,15}$`) + if !pName.MatchString(input) { + return errors.New("Invalid project-name (cannot contain special chars except '-' & max len of 15)") + } + return nil +} + +func validateRootDomain(input string) error { + // FIXME: this regex would still accept subdomains a.co.uk a permanent solution matching all TLDs + // is not pretty https://publicsuffix.org/list/public_suffix_list.dat another solution would be shortlisting + // TLDs to the most commonly used. + var rootDomain = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.{1})+[a-z]{2,}$`) + if !rootDomain.MatchString(input) { + return errors.New("Invalid root domain name") + } + return nil +} + +func validateSubDomain(input string) error { + // match all char a-z and 0-9 can contain a - must end with a . + var subDomainName = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)$`) + if !subDomainName.MatchString(input) { + return errors.New("Invalid subdomain (cannot contain special chars & must end with a '.')") + } + return nil +} + // TODO: validation / allow prompt retry ...etc func (p PromptHandler) GetParam(projectParams map[string]string) string { var err error @@ -178,10 +208,24 @@ func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[s continue } + // map module field names to corresponding validate functions. + // not optimal solution as changing a field name in the EKS stack would render the map invalid. + m := map[string]func(string) error{ + "productionHostRoot": validateRootDomain, + "stagingHostRoot": validateRootDomain, + + "productionFrontendSubdomain": validateSubDomain, + "productionBackendSubdomain": validateSubDomain, + "stagingFrontendSubdomain": validateSubDomain, + "stagingBackendSubdomain": validateSubDomain, + } + + evalFunc := m[promptConfig.Field] + promptHandler := PromptHandler{ Parameter: promptConfig, Condition: NoCondition, - Validate: NoValidation, + Validate: evalFunc, } // merging the context of param and credentals // this treats credentialEnvs as throwaway, parameters is shared between modules From 863178b33097e2a411fdaf9045d144d0a903d434 Mon Sep 17 00:00:00 2001 From: Dolapo Toki Date: Sun, 2 Aug 2020 10:25:28 -0700 Subject: [PATCH 2/5] refactor --- internal/init/init.go | 2 +- internal/init/prompts.go | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/init/init.go b/internal/init/init.go index 3a9c410c0..c69552948 100644 --- a/internal/init/init.go +++ b/internal/init/init.go @@ -125,7 +125,7 @@ func getProjectNamePrompt() PromptHandler { Default: "", }, Condition: NoCondition, - Validate: ValidatePname, + Validate: ValidateProjectName, } } diff --git a/internal/init/prompts.go b/internal/init/prompts.go index 10a6b0438..7320165c0 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -88,8 +88,8 @@ func ValidateSAK(input string) error { return nil } -// ValidatePname validates Project Name field string input. -func ValidatePname(input string) error { +// ValidateProjectName validates Project Name field user input. +func ValidateProjectName(input string) error { // the first 62 char out of base64 and - var pName = regexp.MustCompile(`^[A-Za-z0-9-]{1,15}$`) if !pName.MatchString(input) { @@ -200,6 +200,17 @@ func sanitizeParameterValue(str string) string { // PromptParams renders series of prompt UI based on the config func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string, projectCredentials globalconfig.ProjectCredential) (map[string]string, error) { + // map module field names to corresponding validate functions. + // not optimal solution as changing a field name in the EKS stack would render the map invalid. + m := map[string]func(string) error{ + "productionHostRoot": validateRootDomain, + "stagingHostRoot": validateRootDomain, + + "productionFrontendSubdomain": validateSubDomain, + "productionBackendSubdomain": validateSubDomain, + "stagingFrontendSubdomain": validateSubDomain, + "stagingBackendSubdomain": validateSubDomain, + } credentialEnvs := projectCredentials.SelectedVendorsCredentialsAsEnv(moduleConfig.RequiredCredentials) for _, promptConfig := range moduleConfig.Parameters { @@ -208,18 +219,7 @@ func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[s continue } - // map module field names to corresponding validate functions. - // not optimal solution as changing a field name in the EKS stack would render the map invalid. - m := map[string]func(string) error{ - "productionHostRoot": validateRootDomain, - "stagingHostRoot": validateRootDomain, - - "productionFrontendSubdomain": validateSubDomain, - "productionBackendSubdomain": validateSubDomain, - "stagingFrontendSubdomain": validateSubDomain, - "stagingBackendSubdomain": validateSubDomain, - } - + // evaluate which validation method to use evalFunc := m[promptConfig.Field] promptHandler := PromptHandler{ From 8109f6a70ab9eb6dfbe9c824d27edfa4adb6f095 Mon Sep 17 00:00:00 2001 From: Dolapo Toki Date: Wed, 5 Aug 2020 10:11:50 -0700 Subject: [PATCH 3/5] add support for regex validation from modules --- internal/config/moduleconfig/module_config.go | 21 ++++-- internal/constants/constants.go | 2 + internal/init/prompts.go | 72 +++++++++---------- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/internal/config/moduleconfig/module_config.go b/internal/config/moduleconfig/module_config.go index 2368c75d8..132ac3466 100644 --- a/internal/config/moduleconfig/module_config.go +++ b/internal/config/moduleconfig/module_config.go @@ -17,13 +17,20 @@ type ModuleConfig struct { } type Parameter struct { - Field string - Label string `yaml:"label,omitempty"` - Options []string `yaml:"options,omitempty"` - Execute string `yaml:"execute,omitempty"` - Value string `yaml:"value,omitempty"` - Default string `yaml:"default,omitempty"` - Info string `yaml:"info,omitempty"` + Field string + Label string `yaml:"label,omitempty"` + Options []string `yaml:"options,omitempty"` + Execute string `yaml:"execute,omitempty"` + Value string `yaml:"value,omitempty"` + Default string `yaml:"default,omitempty"` + Info string `yaml:"info,omitempty"` + FieldValidation Validate `yaml:"fieldValidation,omitempty"` +} + +type Validate struct { + Type string `yaml:"type,omitempty"` + Value string `yaml:"value,omitempty"` + ErrorMessage string `yaml:"errorMessage,omitempty"` } type TemplateConfig struct { diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 907ad9f60..456fbff17 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -8,4 +8,6 @@ const ( ZeroHomeDirectory = ".zero" IgnoredPaths = "(?i)zero.module.yml|.git/" TemplateExtn = ".tmpl" + RegexValidation = "regex" + FunctionValidation = "function" ) diff --git a/internal/init/prompts.go b/internal/init/prompts.go index 7320165c0..b12bfa829 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -11,6 +11,7 @@ import ( "github.com/commitdev/zero/internal/config/globalconfig" "github.com/commitdev/zero/internal/config/moduleconfig" + "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/credentials" "github.com/commitdev/zero/pkg/util/exit" @@ -91,34 +92,13 @@ func ValidateSAK(input string) error { // ValidateProjectName validates Project Name field user input. func ValidateProjectName(input string) error { // the first 62 char out of base64 and - - var pName = regexp.MustCompile(`^[A-Za-z0-9-]{1,15}$`) + var pName = regexp.MustCompile(`^[A-Za-z0-9-]{1,16}$`) if !pName.MatchString(input) { return errors.New("Invalid project-name (cannot contain special chars except '-' & max len of 15)") } return nil } -func validateRootDomain(input string) error { - // FIXME: this regex would still accept subdomains a.co.uk a permanent solution matching all TLDs - // is not pretty https://publicsuffix.org/list/public_suffix_list.dat another solution would be shortlisting - // TLDs to the most commonly used. - var rootDomain = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.{1})+[a-z]{2,}$`) - if !rootDomain.MatchString(input) { - return errors.New("Invalid root domain name") - } - return nil -} - -func validateSubDomain(input string) error { - // match all char a-z and 0-9 can contain a - must end with a . - var subDomainName = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)$`) - if !subDomainName.MatchString(input) { - return errors.New("Invalid subdomain (cannot contain special chars & must end with a '.')") - } - return nil -} - -// TODO: validation / allow prompt retry ...etc func (p PromptHandler) GetParam(projectParams map[string]string) string { var err error var result string @@ -200,18 +180,6 @@ func sanitizeParameterValue(str string) string { // PromptParams renders series of prompt UI based on the config func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string, projectCredentials globalconfig.ProjectCredential) (map[string]string, error) { - // map module field names to corresponding validate functions. - // not optimal solution as changing a field name in the EKS stack would render the map invalid. - m := map[string]func(string) error{ - "productionHostRoot": validateRootDomain, - "stagingHostRoot": validateRootDomain, - - "productionFrontendSubdomain": validateSubDomain, - "productionBackendSubdomain": validateSubDomain, - "stagingFrontendSubdomain": validateSubDomain, - "stagingBackendSubdomain": validateSubDomain, - } - credentialEnvs := projectCredentials.SelectedVendorsCredentialsAsEnv(moduleConfig.RequiredCredentials) for _, promptConfig := range moduleConfig.Parameters { // deduplicate fields already prompted and received @@ -219,13 +187,24 @@ func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[s continue } - // evaluate which validation method to use - evalFunc := m[promptConfig.Field] + var validateFunc func(input string) error = nil + + // type:regex field validation for zero-module.yaml + if promptConfig.FieldValidation.Type == constants.RegexValidation { + validateFunc = func(input string) error { + var regexRule = regexp.MustCompile(promptConfig.FieldValidation.Value) + if !regexRule.MatchString(input) { + return errors.New(promptConfig.FieldValidation.ErrorMessage) + } + return nil + } + } + // TODO: type:fuction field validation for zero-module.yaml promptHandler := PromptHandler{ Parameter: promptConfig, Condition: NoCondition, - Validate: evalFunc, + Validate: validateFunc, } // merging the context of param and credentals // this treats credentialEnvs as throwaway, parameters is shared between modules @@ -276,3 +255,22 @@ func appendToSet(set []string, toAppend []string) []string { } return set } + +// validation fuctions that field validation of type:fuction in zero-module.yaml can select +// need to implement type:function flow in PromptModuleParams before use +func validateRootDomain(input string) error { + var rootDomain = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.{1})+[a-z]{2,}$`) + if !rootDomain.MatchString(input) { + return errors.New("Invalid root domain name") + } + return nil +} + +func validateSubDomain(input string) error { + // match all char a-z and 0-9 can contain a - must end with a . + var subDomainName = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)$`) + if !subDomainName.MatchString(input) { + return errors.New("Invalid subdomain (cannot contain special chars & must end with a '.')") + } + return nil +} From 90460b133c5b12827da230f1bcab1f0e28dd26c7 Mon Sep 17 00:00:00 2001 From: Dolapo Toki Date: Wed, 5 Aug 2020 14:46:44 -0700 Subject: [PATCH 4/5] update env; add constants; better error validation --- internal/apply/apply.go | 6 +++--- internal/constants/constants.go | 4 ++++ internal/init/prompts.go | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/apply/apply.go b/internal/apply/apply.go index 33cc2c778..382b14b46 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -101,9 +101,9 @@ func applyAll(dir string, projectConfig projectconfig.ZeroProjectConfig, applyEn // promptEnvironments Prompts the user for the environments to apply against and returns a slice of strings representing the environments func promptEnvironments() []string { items := map[string][]string{ - "Staging ": {"staging"}, - "Production": {"production"}, - "Both Staging and Production": {"staging", "production"}, + "Staging ": {"stage"}, + "Production": {"prod"}, + "Both Staging and Production": {"stage", "prod"}, } var labels []string diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 456fbff17..988474359 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -8,6 +8,10 @@ const ( ZeroHomeDirectory = ".zero" IgnoredPaths = "(?i)zero.module.yml|.git/" TemplateExtn = ".tmpl" + + // prompt constants + + MaxPnameLength = 16 RegexValidation = "regex" FunctionValidation = "function" ) diff --git a/internal/init/prompts.go b/internal/init/prompts.go index b12bfa829..affc0a0d2 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -94,7 +94,11 @@ func ValidateProjectName(input string) error { // the first 62 char out of base64 and - var pName = regexp.MustCompile(`^[A-Za-z0-9-]{1,16}$`) if !pName.MatchString(input) { - return errors.New("Invalid project-name (cannot contain special chars except '-' & max len of 15)") + // error if char len is greater than 16 + if len(input) > constants.MaxPnameLength { + return errors.New("Invalid, Project Name: (cannot exceed a max length of 16)") + } + return errors.New("Invalid, Project Name: (can only contain alphanumeric chars & '-')") } return nil } From 9b18bb267298d19d96fe1f7db2ef430dca605e97 Mon Sep 17 00:00:00 2001 From: Dolapo Toki Date: Wed, 5 Aug 2020 16:30:40 -0700 Subject: [PATCH 5/5] remove unused validation functions --- internal/init/prompts.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/internal/init/prompts.go b/internal/init/prompts.go index affc0a0d2..018375c87 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -259,22 +259,3 @@ func appendToSet(set []string, toAppend []string) []string { } return set } - -// validation fuctions that field validation of type:fuction in zero-module.yaml can select -// need to implement type:function flow in PromptModuleParams before use -func validateRootDomain(input string) error { - var rootDomain = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.{1})+[a-z]{2,}$`) - if !rootDomain.MatchString(input) { - return errors.New("Invalid root domain name") - } - return nil -} - -func validateSubDomain(input string) error { - // match all char a-z and 0-9 can contain a - must end with a . - var subDomainName = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)$`) - if !subDomainName.MatchString(input) { - return errors.New("Invalid subdomain (cannot contain special chars & must end with a '.')") - } - return nil -}