From 8c9b1b37ddf2acdfa53d330a92b0664066d717cc Mon Sep 17 00:00:00 2001 From: xeniape Date: Wed, 17 Sep 2025 09:52:51 +0200 Subject: [PATCH 01/10] fix: separate between official and kubernetes-native RFC 1123 definition --- .../src/builder/pod/container.rs | 8 +-- crates/stackable-operator/src/validation.rs | 56 ++++++++++--------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/container.rs b/crates/stackable-operator/src/builder/pod/container.rs index 3f1620a2f..4e7b42547 100644 --- a/crates/stackable-operator/src/builder/pod/container.rs +++ b/crates/stackable-operator/src/builder/pod/container.rs @@ -441,7 +441,7 @@ mod tests { resources::ResourceRequirementsBuilder, }, commons::resources::ResourceRequirementsType, - validation::RFC_1123_LABEL_FMT, + validation::LOWERCASE_RFC_1123_LABEL_FMT, }; #[test] @@ -604,11 +604,11 @@ mod tests { assert!(ContainerBuilder::new("name-with-hyphen").is_ok()); assert_container_builder_err( ContainerBuilder::new("ends-with-hyphen-"), - &format!(r#"regex used for validation is "{RFC_1123_LABEL_FMT}""#), + &format!(r#"regex used for validation is "{LOWERCASE_RFC_1123_LABEL_FMT}""#), ); assert_container_builder_err( ContainerBuilder::new("-starts-with-hyphen"), - &format!(r#"regex used for validation is "{RFC_1123_LABEL_FMT}""#), + &format!(r#"regex used for validation is "{LOWERCASE_RFC_1123_LABEL_FMT}""#), ); } @@ -623,7 +623,7 @@ mod tests { assert_container_builder_err( ContainerBuilder::new("name_name"), &format!( - r#"(e.g. "example-label", or "1-label-1", regex used for validation is "{RFC_1123_LABEL_FMT}""# + r#"(e.g. "example-label", or "1-label-1", regex used for validation is "{LOWERCASE_RFC_1123_LABEL_FMT}""# ), ); } diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index d9b895715..9605078de 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -17,13 +17,18 @@ use snafu::Snafu; /// Minimal length required by RFC 1123 is 63. Up to 255 allowed, unsupported by k8s. const RFC_1123_LABEL_MAX_LENGTH: usize = 63; -pub const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; -const RFC_1123_LABEL_ERROR_MSG: &str = "a RFC 1123 label must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; +// This is a modified RFC 1123 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names +pub const LOWERCASE_RFC_1123_LABEL_FMT: &str = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"; +const LOWERCASE_RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; + +// This is a RFC 1123 format, see https://www.rfc-editor.org/rfc/rfc1123 +const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; /// This is a subdomain's max length in DNS (RFC 1123) const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253; const RFC_1123_SUBDOMAIN_FMT: &str = concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*"); +const RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; const DOMAIN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH; /// Same as [`RFC_1123_SUBDOMAIN_FMT`], but allows a trailing dot @@ -54,8 +59,14 @@ pub(crate) static DOMAIN_REGEX: LazyLock = LazyLock::new(|| { Regex::new(&format!("^{DOMAIN_FMT}$")).expect("failed to compile domain regex") }); -static RFC_1123_LABEL_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(&format!("^{RFC_1123_LABEL_FMT}$")).expect("failed to compile RFC 1123 label regex") +static LOWERCASE_RFC_1123_LABEL_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(&format!("^{LOWERCASE_RFC_1123_LABEL_FMT}$")) + .expect("failed to compile RFC 1123 label regex") +}); + +static RFC_1123_SUBDOMAIN_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(&format!("^{RFC_1123_SUBDOMAIN_FMT}$")) + .expect("failed to compile RFC 1123 subdomain regex") }); static RFC_1035_LABEL_REGEX: LazyLock = LazyLock::new(|| { @@ -205,13 +216,26 @@ pub fn is_rfc_1123_label(value: &str) -> Result { validate_str_length(value, RFC_1123_LABEL_MAX_LENGTH), validate_str_regex( value, - &RFC_1123_LABEL_REGEX, - RFC_1123_LABEL_ERROR_MSG, + &LOWERCASE_RFC_1123_LABEL_REGEX, + LOWERCASE_RFC_1123_LABEL_ERROR_MSG, &["example-label", "1-label-1"], ), ]) } +/// Tests for a string that conforms to the definition of a subdomain in DNS (RFC 1123). +pub fn is_rfc_1123_subdomain(value: &str) -> Result { + validate_all([ + validate_str_length(value, RFC_1123_SUBDOMAIN_MAX_LENGTH), + validate_str_regex( + value, + &RFC_1123_SUBDOMAIN_REGEX, + RFC_1123_SUBDOMAIN_ERROR_MSG, + &["example.com"], + ), + ]) +} + /// Tests for a string that conforms to the definition of a label in DNS (RFC 1035). pub fn is_rfc_1035_label(value: &str) -> Result { validate_all([ @@ -277,26 +301,6 @@ mod tests { use super::*; - const RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; - - static RFC_1123_SUBDOMAIN_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(&format!("^{RFC_1123_SUBDOMAIN_FMT}$")) - .expect("failed to compile RFC 1123 subdomain regex") - }); - - /// Tests for a string that conforms to the definition of a subdomain in DNS (RFC 1123). - fn is_rfc_1123_subdomain(value: &str) -> Result { - validate_all([ - validate_str_length(value, RFC_1123_SUBDOMAIN_MAX_LENGTH), - validate_str_regex( - value, - &RFC_1123_SUBDOMAIN_REGEX, - RFC_1123_SUBDOMAIN_ERROR_MSG, - &["example.com"], - ), - ]) - } - #[rstest] #[case("")] #[case("-")] From be30a23d7d2be35bbe36518994f2e98b385637a1 Mon Sep 17 00:00:00 2001 From: xeniape Date: Wed, 17 Sep 2025 10:01:14 +0200 Subject: [PATCH 02/10] add changelog entry --- crates/stackable-operator/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 9e0ec53b7..104f9f198 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -25,10 +25,12 @@ All notable changes to this project will be documented in this file. ### Fixed - Don't default the `termination_grace_period` of the `ProbeBuilder` to 0, as this is an invalid value ([#1090]). +- Don't allow uppercase characters in Kubernetes object names ([#1095]). [#1085]: https://github.com/stackabletech/operator-rs/pull/1085 [#1087]: https://github.com/stackabletech/operator-rs/pull/1087 [#1090]: https://github.com/stackabletech/operator-rs/pull/1090 +[#1095]: https://github.com/stackabletech/operator-rs/pull/1095 ## [0.96.0] - 2025-08-25 From abd70439f0a45fd27e0a405137216ea760d5489c Mon Sep 17 00:00:00 2001 From: xeniape Date: Wed, 17 Sep 2025 10:53:48 +0200 Subject: [PATCH 03/10] subdomain format also kubernetes-specific --- crates/stackable-operator/src/validation.rs | 61 +++++++++++---------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 9605078de..dab87356c 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -26,13 +26,18 @@ const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; /// This is a subdomain's max length in DNS (RFC 1123) const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253; -const RFC_1123_SUBDOMAIN_FMT: &str = - concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*"); +const RFC_1123_SUBDOMAIN_FMT: &str = concatcp!( + LOWERCASE_RFC_1123_LABEL_FMT, + "(\\.", + LOWERCASE_RFC_1123_LABEL_FMT, + ")*" +); const RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; const DOMAIN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH; -/// Same as [`RFC_1123_SUBDOMAIN_FMT`], but allows a trailing dot -const DOMAIN_FMT: &str = concatcp!(RFC_1123_SUBDOMAIN_FMT, "\\.?"); + +/// Same as [`RFC_1123_LABEL_FMT`], but allows a trailing dot +const DOMAIN_FMT: &str = concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*\\.?"); const DOMAIN_ERROR_MSG: &str = "a domain must consist of alphanumeric characters, '-' or '.', and must start with an alphanumeric character and end with an alphanumeric character or '.'"; // FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter @@ -303,6 +308,12 @@ mod tests { #[rstest] #[case("")] + #[case("A")] + #[case("aBc")] + #[case("ABC")] + #[case("A1")] + #[case("A-1")] + #[case("1-A")] #[case("-")] #[case("a-")] #[case("-a")] @@ -329,6 +340,24 @@ mod tests { #[case("1 ")] #[case(" 1")] #[case("1 2")] + #[case("A.a")] + #[case("aB.a")] + #[case("ab.A")] + #[case("A1.a")] + #[case("a1.A")] + #[case("A.1")] + #[case("aB.1")] + #[case("A1.1")] + #[case("0.A")] + #[case("01.A")] + #[case("012.A")] + #[case("1A.a")] + #[case("1a.A")] + #[case("1A.1")] + #[case("a.B.c.d.e")] + #[case("A.B.C.D.E")] + #[case("aa.bB.cc.dd.ee")] + #[case("AA.BB.CC.DD.EE")] #[case("a@b")] #[case("a,b")] #[case("a_b")] @@ -344,67 +373,43 @@ mod tests { #[rstest] #[case("a")] - #[case("A")] #[case("ab")] #[case("abc")] - #[case("aBc")] - #[case("ABC")] #[case("a1")] - #[case("A1")] #[case("a-1")] - #[case("A-1")] #[case("a--1--2--b")] #[case("0")] #[case("01")] #[case("012")] #[case("1a")] #[case("1-a")] - #[case("1-A")] #[case("1--a--b--2")] #[case("a.a")] - #[case("A.a")] #[case("ab.a")] - #[case("aB.a")] - #[case("ab.A")] #[case("abc.a")] #[case("a1.a")] - #[case("A1.a")] - #[case("a1.A")] #[case("a-1.a")] #[case("a--1--2--b.a")] #[case("a.1")] - #[case("A.1")] #[case("ab.1")] - #[case("aB.1")] #[case("abc.1")] #[case("a1.1")] - #[case("A1.1")] #[case("a-1.1")] #[case("a--1--2--b.1")] #[case("0.a")] - #[case("0.A")] #[case("01.a")] - #[case("01.A")] #[case("012.a")] - #[case("012.A")] #[case("1a.a")] - #[case("1A.a")] - #[case("1a.A")] #[case("1-a.a")] #[case("1--a--b--2")] #[case("0.1")] #[case("01.1")] #[case("012.1")] #[case("1a.1")] - #[case("1A.1")] #[case("1-a.1")] #[case("1--a--b--2.1")] #[case("a.b.c.d.e")] - #[case("a.B.c.d.e")] - #[case("A.B.C.D.E")] #[case("aa.bb.cc.dd.ee")] - #[case("aa.bB.cc.dd.ee")] - #[case("AA.BB.CC.DD.EE")] #[case("1.2.3.4.5")] #[case("11.22.33.44.55")] #[case(&"a".repeat(253))] From 897b75165e27996b09ef14152aa04124f5885a17 Mon Sep 17 00:00:00 2001 From: xeniape Date: Fri, 19 Sep 2025 10:31:55 +0200 Subject: [PATCH 04/10] adjust variable names and add docs, fix changelog --- crates/stackable-operator/CHANGELOG.md | 7 +++- crates/stackable-operator/src/validation.rs | 43 ++++++++++++--------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 104f9f198..e48d64a09 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -8,7 +8,12 @@ All notable changes to this project will be documented in this file. - Extend `ObjectMetaBuilder` with `finalizers` ([#1094]). +### Fixed + +- BREAKING: Don't allow uppercase characters in Kubernetes object names ([#1095]). + [#1094]: https://github.com/stackabletech/operator-rs/pull/1094 +[#1095]: https://github.com/stackabletech/operator-rs/pull/1095 ## [0.97.0] - 2025-09-09 @@ -25,12 +30,10 @@ All notable changes to this project will be documented in this file. ### Fixed - Don't default the `termination_grace_period` of the `ProbeBuilder` to 0, as this is an invalid value ([#1090]). -- Don't allow uppercase characters in Kubernetes object names ([#1095]). [#1085]: https://github.com/stackabletech/operator-rs/pull/1085 [#1087]: https://github.com/stackabletech/operator-rs/pull/1087 [#1090]: https://github.com/stackabletech/operator-rs/pull/1090 -[#1095]: https://github.com/stackabletech/operator-rs/pull/1095 ## [0.96.0] - 2025-08-25 diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index dab87356c..0479f67bf 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -16,7 +16,7 @@ use regex::Regex; use snafu::Snafu; /// Minimal length required by RFC 1123 is 63. Up to 255 allowed, unsupported by k8s. -const RFC_1123_LABEL_MAX_LENGTH: usize = 63; +pub const RFC_1123_LABEL_MAX_LENGTH: usize = 63; // This is a modified RFC 1123 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names pub const LOWERCASE_RFC_1123_LABEL_FMT: &str = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"; const LOWERCASE_RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; @@ -25,16 +25,16 @@ const LOWERCASE_RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label mus const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; /// This is a subdomain's max length in DNS (RFC 1123) -const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253; -const RFC_1123_SUBDOMAIN_FMT: &str = concatcp!( +pub const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253; +const LOWERCASE_RFC_1123_SUBDOMAIN_FMT: &str = concatcp!( LOWERCASE_RFC_1123_LABEL_FMT, "(\\.", LOWERCASE_RFC_1123_LABEL_FMT, ")*" ); -const RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; +const LOWERCASE_RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; -const DOMAIN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH; +pub const DOMAIN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH; /// Same as [`RFC_1123_LABEL_FMT`], but allows a trailing dot const DOMAIN_FMT: &str = concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*\\.?"); @@ -42,11 +42,12 @@ const DOMAIN_ERROR_MSG: &str = "a domain must consist of alphanumeric characters // FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter // (and not a number). -const RFC_1035_LABEL_FMT: &str = "[a-z]([-a-z0-9]*[a-z0-9])?"; -const RFC_1035_LABEL_ERROR_MSG: &str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"; +// This is a modified RFC 1035 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names +const LOWERCASE_RFC_1035_LABEL_FMT: &str = "[a-z]([-a-z0-9]*[a-z0-9])?"; +const LOWERCASE_RFC_1035_LABEL_ERROR_MSG: &str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"; // This is a label's max length in DNS (RFC 1035) -const RFC_1035_LABEL_MAX_LENGTH: usize = 63; +pub const RFC_1035_LABEL_MAX_LENGTH: usize = 63; // Technically Kerberos allows more realm names // (https://web.mit.edu/kerberos/krb5-1.21/doc/admin/realm_config.html#realm-name), @@ -69,13 +70,14 @@ static LOWERCASE_RFC_1123_LABEL_REGEX: LazyLock = LazyLock::new(|| { .expect("failed to compile RFC 1123 label regex") }); -static RFC_1123_SUBDOMAIN_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(&format!("^{RFC_1123_SUBDOMAIN_FMT}$")) +static LOWERCASE_RFC_1123_SUBDOMAIN_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(&format!("^{LOWERCASE_RFC_1123_SUBDOMAIN_FMT}$")) .expect("failed to compile RFC 1123 subdomain regex") }); -static RFC_1035_LABEL_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(&format!("^{RFC_1035_LABEL_FMT}$")).expect("failed to compile RFC 1035 label regex") +static LOWERCASE_RFC_1035_LABEL_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(&format!("^{LOWERCASE_RFC_1035_LABEL_FMT}$")) + .expect("failed to compile RFC 1035 label regex") }); pub(crate) static KERBEROS_REALM_NAME_REGEX: LazyLock = LazyLock::new(|| { @@ -214,7 +216,8 @@ pub fn is_domain(value: &str) -> Result { ]) } -/// Tests for a string that conforms to the definition of a label in DNS (RFC 1123). +/// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1123) +/// used in Namespace names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names /// Maximum label length supported by k8s is 63 characters (minimum required). pub fn is_rfc_1123_label(value: &str) -> Result { validate_all([ @@ -228,27 +231,29 @@ pub fn is_rfc_1123_label(value: &str) -> Result { ]) } -/// Tests for a string that conforms to the definition of a subdomain in DNS (RFC 1123). +/// Tests for a string that conforms to the kubernetes-specific definition of a subdomain in DNS (RFC 1123) +/// used in ConfigMap names, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names pub fn is_rfc_1123_subdomain(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1123_SUBDOMAIN_MAX_LENGTH), validate_str_regex( value, - &RFC_1123_SUBDOMAIN_REGEX, - RFC_1123_SUBDOMAIN_ERROR_MSG, + &LOWERCASE_RFC_1123_SUBDOMAIN_REGEX, + LOWERCASE_RFC_1123_SUBDOMAIN_ERROR_MSG, &["example.com"], ), ]) } -/// Tests for a string that conforms to the definition of a label in DNS (RFC 1035). +/// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1035) +/// used in Service names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names pub fn is_rfc_1035_label(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1035_LABEL_MAX_LENGTH), validate_str_regex( value, - &RFC_1035_LABEL_REGEX, - RFC_1035_LABEL_ERROR_MSG, + &LOWERCASE_RFC_1035_LABEL_REGEX, + LOWERCASE_RFC_1035_LABEL_ERROR_MSG, &["my-name", "abc-123"], ), ]) From c541accbabe4d688540d6b65ae70d0f392a9138d Mon Sep 17 00:00:00 2001 From: xeniape Date: Fri, 19 Sep 2025 10:39:25 +0200 Subject: [PATCH 05/10] rename functions --- .../src/builder/pod/container.rs | 7 ++++--- crates/stackable-operator/src/validation.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/container.rs b/crates/stackable-operator/src/builder/pod/container.rs index 4e7b42547..62c6115b5 100644 --- a/crates/stackable-operator/src/builder/pod/container.rs +++ b/crates/stackable-operator/src/builder/pod/container.rs @@ -12,7 +12,7 @@ use {k8s_openapi::api::core::v1::PodSpec, std::collections::BTreeMap}; use crate::{ commons::product_image_selection::ResolvedProductImage, - validation::{self, is_rfc_1123_label}, + validation::{self, is_lowercase_rfc_1123_label}, }; type Result = std::result::Result; @@ -351,10 +351,11 @@ impl ContainerBuilder { } } - /// Validates a container name is according to the [RFC 1123](https://www.ietf.org/rfc/rfc1123.txt) standard. + /// Validates a container name is according to the kubernetes-specific [RFC 1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names) standard. /// Returns [Ok] if the name is according to the standard, and [Err] if not. fn validate_container_name(container_name: &str) -> Result<()> { - is_rfc_1123_label(container_name).context(InvalidContainerNameSnafu { container_name }) + is_lowercase_rfc_1123_label(container_name) + .context(InvalidContainerNameSnafu { container_name }) } } diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 0479f67bf..726ea538c 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -219,7 +219,7 @@ pub fn is_domain(value: &str) -> Result { /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1123) /// used in Namespace names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names /// Maximum label length supported by k8s is 63 characters (minimum required). -pub fn is_rfc_1123_label(value: &str) -> Result { +pub fn is_lowercase_rfc_1123_label(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1123_LABEL_MAX_LENGTH), validate_str_regex( @@ -233,7 +233,7 @@ pub fn is_rfc_1123_label(value: &str) -> Result { /// Tests for a string that conforms to the kubernetes-specific definition of a subdomain in DNS (RFC 1123) /// used in ConfigMap names, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names -pub fn is_rfc_1123_subdomain(value: &str) -> Result { +pub fn is_lowercase_rfc_1123_subdomain(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1123_SUBDOMAIN_MAX_LENGTH), validate_str_regex( @@ -247,7 +247,7 @@ pub fn is_rfc_1123_subdomain(value: &str) -> Result { /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1035) /// used in Service names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names -pub fn is_rfc_1035_label(value: &str) -> Result { +pub fn is_lowercase_rfc_1035_label(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1035_LABEL_MAX_LENGTH), validate_str_regex( @@ -295,7 +295,7 @@ pub fn name_is_dns_label(name: &str, prefix: bool) -> Result { name = mask_trailing_dash(name); } - is_rfc_1035_label(&name) + is_lowercase_rfc_1035_label(&name) } /// Validates a namespace name. @@ -373,7 +373,7 @@ mod tests { #[case("a$b")] #[case(&"a".repeat(254))] fn is_rfc_1123_subdomain_fail(#[case] value: &str) { - assert!(is_rfc_1123_subdomain(value).is_err()); + assert!(is_lowercase_rfc_1123_subdomain(value).is_err()); } #[rstest] @@ -419,7 +419,7 @@ mod tests { #[case("11.22.33.44.55")] #[case(&"a".repeat(253))] fn is_rfc_1123_subdomain_pass(#[case] value: &str) { - assert!(is_rfc_1123_subdomain(value).is_ok()); + assert!(is_lowercase_rfc_1123_subdomain(value).is_ok()); // Every valid RFC1123 is also a valid domain assert!(is_domain(value).is_ok()); } @@ -483,7 +483,7 @@ mod tests { #[case("1 2")] #[case(&"a".repeat(64))] fn is_rfc_1035_label_fail(#[case] value: &str) { - assert!(is_rfc_1035_label(value).is_err()); + assert!(is_lowercase_rfc_1035_label(value).is_err()); } #[rstest] @@ -495,6 +495,6 @@ mod tests { #[case("a--1--2--b")] #[case(&"a".repeat(63))] fn is_rfc_1035_label_pass(#[case] value: &str) { - assert!(is_rfc_1035_label(value).is_ok()); + assert!(is_lowercase_rfc_1035_label(value).is_ok()); } } From 5e6ec66bd17d55aea70a618f2beafd755278bb7b Mon Sep 17 00:00:00 2001 From: xeniape Date: Fri, 19 Sep 2025 10:45:04 +0200 Subject: [PATCH 06/10] fix links in docs --- crates/stackable-operator/src/validation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 726ea538c..9adbb6ffb 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -217,7 +217,7 @@ pub fn is_domain(value: &str) -> Result { } /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1123) -/// used in Namespace names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names +/// used in Namespace names, see: [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names) /// Maximum label length supported by k8s is 63 characters (minimum required). pub fn is_lowercase_rfc_1123_label(value: &str) -> Result { validate_all([ @@ -232,7 +232,7 @@ pub fn is_lowercase_rfc_1123_label(value: &str) -> Result { } /// Tests for a string that conforms to the kubernetes-specific definition of a subdomain in DNS (RFC 1123) -/// used in ConfigMap names, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names +/// used in ConfigMap names, see [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) pub fn is_lowercase_rfc_1123_subdomain(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1123_SUBDOMAIN_MAX_LENGTH), @@ -246,7 +246,7 @@ pub fn is_lowercase_rfc_1123_subdomain(value: &str) -> Result { } /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1035) -/// used in Service names, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names +/// used in Service names, see: [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) pub fn is_lowercase_rfc_1035_label(value: &str) -> Result { validate_all([ validate_str_length(value, RFC_1035_LABEL_MAX_LENGTH), From 91108ce418feadda176d79f86392ad18b6543ad3 Mon Sep 17 00:00:00 2001 From: xeniape Date: Fri, 19 Sep 2025 10:50:56 +0200 Subject: [PATCH 07/10] make const public --- crates/stackable-operator/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 9adbb6ffb..695e0df5d 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -26,7 +26,7 @@ const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; /// This is a subdomain's max length in DNS (RFC 1123) pub const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253; -const LOWERCASE_RFC_1123_SUBDOMAIN_FMT: &str = concatcp!( +pub const LOWERCASE_RFC_1123_SUBDOMAIN_FMT: &str = concatcp!( LOWERCASE_RFC_1123_LABEL_FMT, "(\\.", LOWERCASE_RFC_1123_LABEL_FMT, From 147513110f3467c4ef18c607cb41b2b1216faf9c Mon Sep 17 00:00:00 2001 From: Xenia Date: Sat, 20 Sep 2025 11:03:23 +0200 Subject: [PATCH 08/10] Update crates/stackable-operator/src/validation.rs Co-authored-by: Siegfried Weber --- crates/stackable-operator/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 695e0df5d..17021fe88 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -19,7 +19,7 @@ use snafu::Snafu; pub const RFC_1123_LABEL_MAX_LENGTH: usize = 63; // This is a modified RFC 1123 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names pub const LOWERCASE_RFC_1123_LABEL_FMT: &str = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"; -const LOWERCASE_RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"; +const LOWERCASE_RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"; // This is a RFC 1123 format, see https://www.rfc-editor.org/rfc/rfc1123 const RFC_1123_LABEL_FMT: &str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?"; From 43be1be6a3ac1a483e5ec57e3429ea75efe2fac1 Mon Sep 17 00:00:00 2001 From: Xenia Date: Sat, 20 Sep 2025 11:04:03 +0200 Subject: [PATCH 09/10] Update crates/stackable-operator/src/validation.rs Co-authored-by: Siegfried Weber --- crates/stackable-operator/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 17021fe88..8e4169f2d 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -43,7 +43,7 @@ const DOMAIN_ERROR_MSG: &str = "a domain must consist of alphanumeric characters // FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter // (and not a number). // This is a modified RFC 1035 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names -const LOWERCASE_RFC_1035_LABEL_FMT: &str = "[a-z]([-a-z0-9]*[a-z0-9])?"; +pub const LOWERCASE_RFC_1035_LABEL_FMT: &str = "[a-z]([-a-z0-9]*[a-z0-9])?"; const LOWERCASE_RFC_1035_LABEL_ERROR_MSG: &str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"; // This is a label's max length in DNS (RFC 1035) From 6d845e530d650090b9c9bdc510fffe842a3d8a22 Mon Sep 17 00:00:00 2001 From: xeniape Date: Mon, 22 Sep 2025 09:10:52 +0200 Subject: [PATCH 10/10] clarify comment --- crates/stackable-operator/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/validation.rs b/crates/stackable-operator/src/validation.rs index 8e4169f2d..dbc5beb9e 100644 --- a/crates/stackable-operator/src/validation.rs +++ b/crates/stackable-operator/src/validation.rs @@ -36,7 +36,7 @@ const LOWERCASE_RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a lowercase RFC 1123 subdo pub const DOMAIN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH; -/// Same as [`RFC_1123_LABEL_FMT`], but allows a trailing dot +/// String of one or multiple [`RFC_1123_LABEL_FMT`] separated by dots but also allowing a trailing dot const DOMAIN_FMT: &str = concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*\\.?"); const DOMAIN_ERROR_MSG: &str = "a domain must consist of alphanumeric characters, '-' or '.', and must start with an alphanumeric character and end with an alphanumeric character or '.'";