From 853b610725d4668972a0aafbd255bc1336785853 Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Thu, 23 Oct 2025 01:12:41 +0530 Subject: [PATCH 1/7] Add namespace validation to catch invalid names before build/deploy Signed-off-by: RayyanSeliya --- cmd/deploy.go | 26 +++++++++++++++++++++ pkg/functions/errors.go | 3 +++ pkg/utils/names.go | 13 +++++++++++ pkg/utils/names_test.go | 51 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/cmd/deploy.go b/cmd/deploy.go index e053004b0f..c018143325 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -347,6 +347,23 @@ Valid examples: Note: Domain must be configured on your Knative cluster, or it will be ignored. +For more options, run 'func deploy --help'`, err) + } + if errors.Is(err, fn.ErrInvalidNamespace) { + return fmt.Errorf(`%w + +Invalid namespace name. Kubernetes namespaces must: + - Contain only lowercase letters, numbers, and hyphens (-) + - Start and end with a letter or number + - Be 63 characters or less + +Valid examples: + func deploy --namespace myapp + func deploy --namespace my-app-123 + +For more options, run 'func deploy --help'`, err) + } + For more options, run 'func deploy --help'`, err) } if errors.Is(err, fn.ErrConflictingImageAndRegistry) { @@ -826,6 +843,15 @@ func (c deployConfig) Validate(cmd *cobra.Command) (err error) { return fn.ErrInvalidDomain } } + // Validate namespace format if provided + if c.Namespace != "" { + if err = utils.ValidateNamespace(c.Namespace); err != nil { + // Wrap the validation error as fn.ErrInvalidNamespace for layer consistency + return fn.ErrInvalidNamespace + } + } + } + // Check Image Digest was included var digest bool diff --git a/pkg/functions/errors.go b/pkg/functions/errors.go index 71f6e69956..6fe163b846 100644 --- a/pkg/functions/errors.go +++ b/pkg/functions/errors.go @@ -44,6 +44,9 @@ var ( // ErrClusterNotAccessible is returned when cluster connection fails (network, auth, etc) ErrClusterNotAccessible = errors.New("cluster not accessible") + + // ErrInvalidNamespace is returned when a namespace name doesn't meet Kubernetes naming requirements + ErrInvalidNamespace = errors.New("invalid namespace") ) // ErrNotInitialized indicates that a function is uninitialized diff --git a/pkg/utils/names.go b/pkg/utils/names.go index 525d770562..50632f98a1 100644 --- a/pkg/utils/names.go +++ b/pkg/utils/names.go @@ -25,6 +25,8 @@ type ErrInvalidLabel error // ErrInvalidDomain indicates the domain name did not pass DNS subdomain validation. type ErrInvalidDomain error +// ErrInvalidNamespace indicates the namespace name did not pass Kubernetes namespace validation. +type ErrInvalidNamespace error // ValidateFunctionName validates that the input name is a valid function name, ie. valid DNS-1035 label. // It must consist of lower case alphanumeric characters or '-' and start with an alphabetic character and end with an alphanumeric character. @@ -123,5 +125,16 @@ func ValidateDomain(domain string) error { return ErrInvalidDomain(errors.New(errMsg)) } +// ValidateNamespace validates that the input name is a valid Kubernetes namespace name, ie. valid DNS-1123 label. +// It must consist of lower case alphanumeric characters or '-', +// and must start and end with an alphanumeric character +// (e.g. 'my-namespace', 'abc-123', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?') +func ValidateNamespace(namespace string) error { + if errs := validation.IsDNS1123Label(namespace); len(errs) > 0 { + // Reuse the error message from Kubernetes validation + // Replace "a DNS-1123 label" with more user-friendly context + errMsg := strings.Replace(strings.Join(errs, ""), "a DNS-1123 label", fmt.Sprintf("Namespace '%v'", namespace), 1) + return ErrInvalidNamespace(errors.New(errMsg)) + } return nil } diff --git a/pkg/utils/names_test.go b/pkg/utils/names_test.go index 4ebf9fbbe9..e9d7a940ab 100644 --- a/pkg/utils/names_test.go +++ b/pkg/utils/names_test.go @@ -212,6 +212,8 @@ func TestValidateLabelValue(t *testing.T) { // TestValidateDomain tests that only correct DNS subdomain names are accepted func TestValidateDomain(t *testing.T) { +// TestValidateNamespace tests that only correct Kubernetes namespace names are accepted +func TestValidateNamespace(t *testing.T) { cases := []struct { In string Valid bool @@ -262,6 +264,47 @@ func TestValidateDomain(t *testing.T) { } if err == nil && !c.Valid { t.Fatalf("Expected error for invalid domain: '%v'", c.In) + // Valid namespaces + {"default", true}, + {"kube-system", true}, + {"my-namespace", true}, + {"myapp", true}, + {"my-app-123", true}, + {"prod", true}, + {"test-123", true}, + {"a", true}, + {"a-b", true}, + {"abc-123-xyz", true}, + + // Invalid namespaces + {"", false}, // empty + {"My-App", false}, // uppercase not allowed + {"MY-APP", false}, // uppercase not allowed + {"123app", false}, // cannot start with number + {"123invalid", false}, // cannot start with number + {"my_app", false}, // underscore not allowed + {"my app", false}, // spaces not allowed + {"invalid namespace", false}, // spaces not allowed + {"my@app", false}, // @ not allowed + {"invalid@namespace", false}, // @ not allowed + {"-myapp", false}, // cannot start with hyphen + {"myapp-", false}, // cannot end with hyphen + {"my..app", false}, // dots not allowed + {"my/app", false}, // slash not allowed + {"my:app", false}, // colon not allowed + {"my;app", false}, // semicolon not allowed + {"my,app", false}, // comma not allowed + {"my*app", false}, // asterisk not allowed + {"my!app", false}, // exclamation not allowed + } + + for _, c := range cases { + err := ValidateNamespace(c.In) + if err != nil && c.Valid { + t.Fatalf("Unexpected error for valid namespace: %v, namespace: '%v'", err, c.In) + } + if err == nil && !c.Valid { + t.Fatalf("Expected error for invalid namespace: '%v'", c.In) } } } @@ -271,6 +314,11 @@ func TestValidateDomainErrMsg(t *testing.T) { errMsgPrefix := fmt.Sprintf("Domain '%v'", invalidDomain) err := ValidateDomain(invalidDomain) +func TestValidateNamespaceErrMsg(t *testing.T) { + invalidNamespace := "123invalid" + errMsgPrefix := fmt.Sprintf("Namespace '%v'", invalidNamespace) + + err := ValidateNamespace(invalidNamespace) if err != nil { if !strings.HasPrefix(err.Error(), errMsgPrefix) { t.Fatalf("Unexpected error message: %v, the message should start with '%v' string", err.Error(), errMsgPrefix) @@ -293,4 +341,7 @@ func TestValidateDomainEmptyString(t *testing.T) { if err == nil { t.Fatal("String with only whitespace should be invalid") } +} + t.Fatalf("Expected error for invalid namespace: %v", invalidNamespace) + } } From 781cf912d56af745595b9ec2714b518521ee1917 Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Thu, 23 Oct 2025 01:25:33 +0530 Subject: [PATCH 2/7] fix the linter errors and refactor such that test not fail Signed-off-by: RayyanSeliya --- pkg/utils/names.go | 4 ++-- pkg/utils/names_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/utils/names.go b/pkg/utils/names.go index 50632f98a1..0236e718fc 100644 --- a/pkg/utils/names.go +++ b/pkg/utils/names.go @@ -132,8 +132,8 @@ func ValidateDomain(domain string) error { func ValidateNamespace(namespace string) error { if errs := validation.IsDNS1123Label(namespace); len(errs) > 0 { // Reuse the error message from Kubernetes validation - // Replace "a DNS-1123 label" with more user-friendly context - errMsg := strings.Replace(strings.Join(errs, ""), "a DNS-1123 label", fmt.Sprintf("Namespace '%v'", namespace), 1) + // Replace "a lowercase RFC 1123 label" with more user-friendly context + errMsg := strings.Replace(strings.Join(errs, ""), "a lowercase RFC 1123 label", fmt.Sprintf("Namespace '%v'", namespace), 1) return ErrInvalidNamespace(errors.New(errMsg)) } return nil diff --git a/pkg/utils/names_test.go b/pkg/utils/names_test.go index e9d7a940ab..2fd1b56c30 100644 --- a/pkg/utils/names_test.go +++ b/pkg/utils/names_test.go @@ -275,13 +275,13 @@ func TestValidateNamespace(t *testing.T) { {"a", true}, {"a-b", true}, {"abc-123-xyz", true}, + {"123app", true}, // DNS-1123 allows starting with number + {"123invalid", true}, // DNS-1123 allows starting with number + {"1", true}, // single number is valid // Invalid namespaces - {"", false}, // empty {"My-App", false}, // uppercase not allowed {"MY-APP", false}, // uppercase not allowed - {"123app", false}, // cannot start with number - {"123invalid", false}, // cannot start with number {"my_app", false}, // underscore not allowed {"my app", false}, // spaces not allowed {"invalid namespace", false}, // spaces not allowed @@ -315,7 +315,7 @@ func TestValidateDomainErrMsg(t *testing.T) { err := ValidateDomain(invalidDomain) func TestValidateNamespaceErrMsg(t *testing.T) { - invalidNamespace := "123invalid" + invalidNamespace := "my@app" errMsgPrefix := fmt.Sprintf("Namespace '%v'", invalidNamespace) err := ValidateNamespace(invalidNamespace) From 682b8343a045aec7361cda3935b4ca9ffc0992fc Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Thu, 23 Oct 2025 01:34:43 +0530 Subject: [PATCH 3/7] fix the goimports Signed-off-by: RayyanSeliya --- pkg/utils/names_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/utils/names_test.go b/pkg/utils/names_test.go index 2fd1b56c30..2165d9b38f 100644 --- a/pkg/utils/names_test.go +++ b/pkg/utils/names_test.go @@ -275,9 +275,9 @@ func TestValidateNamespace(t *testing.T) { {"a", true}, {"a-b", true}, {"abc-123-xyz", true}, - {"123app", true}, // DNS-1123 allows starting with number - {"123invalid", true}, // DNS-1123 allows starting with number - {"1", true}, // single number is valid + {"123app", true}, // DNS-1123 allows starting with number + {"123invalid", true}, // DNS-1123 allows starting with number + {"1", true}, // single number is valid // Invalid namespaces {"My-App", false}, // uppercase not allowed From 39ba7308e19e41dd586302bbbf46f451a6e7fece Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Sun, 2 Nov 2025 16:21:08 +0530 Subject: [PATCH 4/7] refactor based on the rfc-1123 dns ruling Signed-off-by: RayyanSeliya --- cmd/deploy.go | 2 +- pkg/utils/names.go | 4 ++-- pkg/utils/names_test.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index c018143325..089e9c5e1f 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -354,7 +354,7 @@ For more options, run 'func deploy --help'`, err) Invalid namespace name. Kubernetes namespaces must: - Contain only lowercase letters, numbers, and hyphens (-) - - Start and end with a letter or number + - Start with a letter and end with a letter or number - Be 63 characters or less Valid examples: diff --git a/pkg/utils/names.go b/pkg/utils/names.go index 0236e718fc..e873221d21 100644 --- a/pkg/utils/names.go +++ b/pkg/utils/names.go @@ -127,8 +127,8 @@ func ValidateDomain(domain string) error { // ValidateNamespace validates that the input name is a valid Kubernetes namespace name, ie. valid DNS-1123 label. // It must consist of lower case alphanumeric characters or '-', -// and must start and end with an alphanumeric character -// (e.g. 'my-namespace', 'abc-123', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?') +// start with an alphabetic character, and end with an alphanumeric character +// (e.g. 'my-namespace', 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') func ValidateNamespace(namespace string) error { if errs := validation.IsDNS1123Label(namespace); len(errs) > 0 { // Reuse the error message from Kubernetes validation diff --git a/pkg/utils/names_test.go b/pkg/utils/names_test.go index 2165d9b38f..7b3b250c0f 100644 --- a/pkg/utils/names_test.go +++ b/pkg/utils/names_test.go @@ -275,11 +275,11 @@ func TestValidateNamespace(t *testing.T) { {"a", true}, {"a-b", true}, {"abc-123-xyz", true}, - {"123app", true}, // DNS-1123 allows starting with number - {"123invalid", true}, // DNS-1123 allows starting with number - {"1", true}, // single number is valid // Invalid namespaces + {"123app", false}, // cannot start with number (K8s requirement) + {"123invalid", false}, // cannot start with number (K8s requirement) + {"1", false}, // cannot start with number (K8s requirement) {"My-App", false}, // uppercase not allowed {"MY-APP", false}, // uppercase not allowed {"my_app", false}, // underscore not allowed From b896336aaddf14b90329e10972eee51ee32abed3 Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Sun, 2 Nov 2025 19:01:15 +0530 Subject: [PATCH 5/7] use dns-1035 validation to enforce alphabetic start for namespace Signed-off-by: RayyanSeliya --- pkg/utils/names.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/utils/names.go b/pkg/utils/names.go index e873221d21..9e8f2d8e0e 100644 --- a/pkg/utils/names.go +++ b/pkg/utils/names.go @@ -130,10 +130,10 @@ func ValidateDomain(domain string) error { // start with an alphabetic character, and end with an alphanumeric character // (e.g. 'my-namespace', 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') func ValidateNamespace(namespace string) error { - if errs := validation.IsDNS1123Label(namespace); len(errs) > 0 { + if errs := validation.IsDNS1035Label(namespace); len(errs) > 0 { // Reuse the error message from Kubernetes validation - // Replace "a lowercase RFC 1123 label" with more user-friendly context - errMsg := strings.Replace(strings.Join(errs, ""), "a lowercase RFC 1123 label", fmt.Sprintf("Namespace '%v'", namespace), 1) + // Replace "a DNS-1035 label" with more user-friendly context + errMsg := strings.Replace(strings.Join(errs, ""), "a DNS-1035 label", fmt.Sprintf("Namespace '%v'", namespace), 1) return ErrInvalidNamespace(errors.New(errMsg)) } return nil From ead7e3320889be0243f4fbd5d0a3eccd3a35ac37 Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Tue, 11 Nov 2025 13:19:48 +0530 Subject: [PATCH 6/7] fixed some inconcistencies due to merge conflicts Signed-off-by: RayyanSeliya --- cmd/deploy.go | 5 ---- pkg/utils/names.go | 4 +++ pkg/utils/names_test.go | 65 +++++++++++++++++++++++++---------------- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index 089e9c5e1f..9c8a87c621 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -361,9 +361,6 @@ Valid examples: func deploy --namespace myapp func deploy --namespace my-app-123 -For more options, run 'func deploy --help'`, err) - } - For more options, run 'func deploy --help'`, err) } if errors.Is(err, fn.ErrConflictingImageAndRegistry) { @@ -850,8 +847,6 @@ func (c deployConfig) Validate(cmd *cobra.Command) (err error) { return fn.ErrInvalidNamespace } } - } - // Check Image Digest was included var digest bool diff --git a/pkg/utils/names.go b/pkg/utils/names.go index 9e8f2d8e0e..40bfa04183 100644 --- a/pkg/utils/names.go +++ b/pkg/utils/names.go @@ -25,6 +25,7 @@ type ErrInvalidLabel error // ErrInvalidDomain indicates the domain name did not pass DNS subdomain validation. type ErrInvalidDomain error + // ErrInvalidNamespace indicates the namespace name did not pass Kubernetes namespace validation. type ErrInvalidNamespace error @@ -125,6 +126,9 @@ func ValidateDomain(domain string) error { return ErrInvalidDomain(errors.New(errMsg)) } + return nil +} + // ValidateNamespace validates that the input name is a valid Kubernetes namespace name, ie. valid DNS-1123 label. // It must consist of lower case alphanumeric characters or '-', // start with an alphabetic character, and end with an alphanumeric character diff --git a/pkg/utils/names_test.go b/pkg/utils/names_test.go index 7b3b250c0f..82ef98d801 100644 --- a/pkg/utils/names_test.go +++ b/pkg/utils/names_test.go @@ -212,8 +212,6 @@ func TestValidateLabelValue(t *testing.T) { // TestValidateDomain tests that only correct DNS subdomain names are accepted func TestValidateDomain(t *testing.T) { -// TestValidateNamespace tests that only correct Kubernetes namespace names are accepted -func TestValidateNamespace(t *testing.T) { cases := []struct { In string Valid bool @@ -233,6 +231,7 @@ func TestValidateNamespace(t *testing.T) { {"example-app.com", true}, // hyphen in domain {"a.co", true}, // short domain {"123app.example.com", true}, // label starting with number + // Invalid domains {"Example.Com", false}, // uppercase not allowed {"MY-APP.COM", false}, // uppercase not allowed @@ -264,6 +263,45 @@ func TestValidateNamespace(t *testing.T) { } if err == nil && !c.Valid { t.Fatalf("Expected error for invalid domain: '%v'", c.In) + } + } +} + +func TestValidateDomainErrMsg(t *testing.T) { + invalidDomain := "my@app.com" + errMsgPrefix := fmt.Sprintf("Domain '%v'", invalidDomain) + + err := ValidateDomain(invalidDomain) + if err != nil { + if !strings.HasPrefix(err.Error(), errMsgPrefix) { + t.Fatalf("Unexpected error message: %v, the message should start with '%v' string", err.Error(), errMsgPrefix) + } + } else { + t.Fatalf("Expected error for invalid domain: %v", invalidDomain) + } +} + +// TestValidateDomainEmptyString ensures empty string is handled specially +func TestValidateDomainEmptyString(t *testing.T) { + // Empty string should be valid (means use cluster default) + err := ValidateDomain("") + if err != nil { + t.Fatalf("Empty string should be valid (means use default): %v", err) + } + + // String with only whitespace should error + err = ValidateDomain(" ") + if err == nil { + t.Fatal("String with only whitespace should be invalid") + } +} + +// TestValidateNamespace tests that only correct Kubernetes namespace names are accepted +func TestValidateNamespace(t *testing.T) { + cases := []struct { + In string + Valid bool + }{ // Valid namespaces {"default", true}, {"kube-system", true}, @@ -309,11 +347,6 @@ func TestValidateNamespace(t *testing.T) { } } -func TestValidateDomainErrMsg(t *testing.T) { - invalidDomain := "my@app.com" - errMsgPrefix := fmt.Sprintf("Domain '%v'", invalidDomain) - - err := ValidateDomain(invalidDomain) func TestValidateNamespaceErrMsg(t *testing.T) { invalidNamespace := "my@app" errMsgPrefix := fmt.Sprintf("Namespace '%v'", invalidNamespace) @@ -324,24 +357,6 @@ func TestValidateNamespaceErrMsg(t *testing.T) { t.Fatalf("Unexpected error message: %v, the message should start with '%v' string", err.Error(), errMsgPrefix) } } else { - t.Fatalf("Expected error for invalid domain: %v", invalidDomain) - } -} - -// TestValidateDomainEmptyString ensures empty string is handled specially -func TestValidateDomainEmptyString(t *testing.T) { - // Empty string should be valid (means use cluster default) - err := ValidateDomain("") - if err != nil { - t.Fatalf("Empty string should be valid (means use default): %v", err) - } - - // String with only whitespace should error - err = ValidateDomain(" ") - if err == nil { - t.Fatal("String with only whitespace should be invalid") - } -} t.Fatalf("Expected error for invalid namespace: %v", invalidNamespace) } } From a8a0ec9fa4a037cbc25505f259d7a8397e44e210 Mon Sep 17 00:00:00 2001 From: RayyanSeliya Date: Wed, 12 Nov 2025 18:49:28 +0530 Subject: [PATCH 7/7] goimports after merge conflict Signed-off-by: RayyanSeliya --- pkg/functions/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/functions/errors.go b/pkg/functions/errors.go index 6fe163b846..3c3e8b8f08 100644 --- a/pkg/functions/errors.go +++ b/pkg/functions/errors.go @@ -44,7 +44,7 @@ var ( // ErrClusterNotAccessible is returned when cluster connection fails (network, auth, etc) ErrClusterNotAccessible = errors.New("cluster not accessible") - + // ErrInvalidNamespace is returned when a namespace name doesn't meet Kubernetes naming requirements ErrInvalidNamespace = errors.New("invalid namespace") )