diff --git a/CHANGELOG.md b/CHANGELOG.md index b27c6002..f00304b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,14 @@ All notable changes to this project will be documented in this file. - Internal operator refactoring: introduce a build() step in the reconciler that assembles all relevant Kubernetes resources before anything is applied ([#726]). +- The RBAC ServiceAccount and RoleBinding are now built with the operator-rs `v2::rbac` + functions and carry the full set of recommended labels ([#731]). +- BREAKING: The `metastore` role is now required by the CRD; a HiveCluster without it was + previously accepted by the API server but failed reconciliation ([#731]). - Bump stackable-operator to 0.114.0 ([#735]). [#726]: https://github.com/stackabletech/hive-operator/pull/726 +[#731]: https://github.com/stackabletech/hive-operator/pull/731 [#735]: https://github.com/stackabletech/hive-operator/pull/735 ## [26.7.0] - 2026-07-21 diff --git a/Cargo.lock b/Cargo.lock index 69a099bd..2bcf6097 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,6 +383,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1060,9 +1070,9 @@ dependencies = [ [[package]] name = "granit-parser" -version = "0.0.3" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50ba32164f9e098d5da618776a32afbb32270adcbe3d3d006107dae11e37c91" +checksum = "d03f81ad4732830d85cfd417a9f62cde6dadda4354d37d078a6084a19560aa2d" dependencies = [ "arraydeque", "smallvec", @@ -1508,6 +1518,55 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.118", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + [[package]] name = "jobserver" version = "0.1.35" @@ -1605,9 +1664,9 @@ checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" [[package]] name = "kube" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb9108095346a7096d11feeaff419c75dddcac1b2f59acb38d7bf3d13c3e146" +checksum = "208d7fe1380066abb194812a8a8e303cb896a33bb3d7b3177b71bd03ff39bf18" dependencies = [ "k8s-openapi", "kube-client", @@ -1618,9 +1677,9 @@ dependencies = [ [[package]] name = "kube-client" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f628e05bc2264c21fe10d3d675117dc9b43ea3bf4fb07262a222679757537b" +checksum = "31e940a73033a7c5c7918b5ece7851d84571b58be25e937198da37fa13f116d3" dependencies = [ "base64", "bytes", @@ -1639,6 +1698,7 @@ dependencies = [ "kube-core", "pem", "rustls", + "rustls-platform-verifier", "secrecy", "serde", "serde-saphyr", @@ -1653,9 +1713,9 @@ dependencies = [ [[package]] name = "kube-core" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b02f5933ba06140d58c7d6727f6c319f0962ec6a344aa5e21e475e891deaa8" +checksum = "a9d2353c118cf3462c352ee0b5bd5b0cf17990af456dfd8662d136ff9812eeb4" dependencies = [ "derive_more", "form_urlencoded", @@ -1672,9 +1732,9 @@ dependencies = [ [[package]] name = "kube-derive" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe171898707dadf1818ef94e81ef57f6beb7edf9ba87b9e814c045dad356c7aa" +checksum = "92141c19e1fa83bf91633c1234c66b03375c29aa8ca5486331ddb47e3a3da9c0" dependencies = [ "darling", "proc-macro2", @@ -1686,9 +1746,9 @@ dependencies = [ [[package]] name = "kube-runtime" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ddec66c540c7cf29a5b41fe4a657a53687f95c346e03bdf00585b70a1bab21" +checksum = "3eb064ef71c7bea55e934a443960da9e9ec31fbb2ba63e47e4aeca32603d5cc7" dependencies = [ "ahash", "async-broadcast", @@ -2565,6 +2625,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -2588,6 +2675,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -2693,9 +2789,9 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "0.0.27" +version = "0.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3" +checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4" dependencies = [ "ahash", "annotate-snippets", @@ -2705,7 +2801,7 @@ dependencies = [ "granit-parser", "nohash-hasher", "num-traits", - "serde", + "serde_core", "smallvec", "zmij", ] @@ -2863,6 +2959,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -3770,6 +3882,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3869,6 +3991,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.nix b/Cargo.nix index 9b753038..b727f34b 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1199,6 +1199,53 @@ rec { sha256 = "0w75k89hw39p0mnnhlrwr23q50rza1yjki44qvh2mgrnj065a1qx"; }; + "combine" = rec { + crateName = "combine"; + version = "4.6.7"; + edition = "2018"; + sha256 = "1z8rh8wp59gf8k23ar010phgs0wgf5i8cx4fg01gwcnzfn5k0nms"; + authors = [ + "Markus Westerlind " + ]; + dependencies = [ + { + name = "bytes"; + packageId = "bytes"; + optional = true; + } + { + name = "memchr"; + packageId = "memchr"; + usesDefaultFeatures = false; + } + ]; + devDependencies = [ + { + name = "bytes"; + packageId = "bytes"; + } + ]; + features = { + "bytes" = [ "dep:bytes" ]; + "bytes_05" = [ "dep:bytes_05" ]; + "default" = [ "std" ]; + "futures-03" = [ "pin-project" "std" "futures-core-03" "futures-io-03" "pin-project-lite" ]; + "futures-core-03" = [ "dep:futures-core-03" ]; + "futures-io-03" = [ "dep:futures-io-03" ]; + "pin-project" = [ "pin-project-lite" ]; + "pin-project-lite" = [ "dep:pin-project-lite" ]; + "regex" = [ "dep:regex" ]; + "std" = [ "memchr/std" "bytes" "alloc" ]; + "tokio" = [ "tokio-dep" "tokio-util/io" "futures-core-03" "pin-project-lite" ]; + "tokio-02" = [ "pin-project" "std" "tokio-02-dep" "futures-core-03" "pin-project-lite" "bytes_05" ]; + "tokio-02-dep" = [ "dep:tokio-02-dep" ]; + "tokio-03" = [ "pin-project" "std" "tokio-03-dep" "futures-core-03" "pin-project-lite" ]; + "tokio-03-dep" = [ "dep:tokio-03-dep" ]; + "tokio-dep" = [ "dep:tokio-dep" ]; + "tokio-util" = [ "dep:tokio-util" ]; + }; + resolvedDefaultFeatures = [ "alloc" "bytes" "default" "std" ]; + }; "concurrent-queue" = rec { crateName = "concurrent-queue"; version = "2.5.0"; @@ -3319,10 +3366,10 @@ rec { }; "granit-parser" = rec { crateName = "granit-parser"; - version = "0.0.3"; + version = "0.0.7"; edition = "2021"; crateBin = []; - sha256 = "14bwwc8swz8h0v8d7qybmmq25czv5aipd1v1vbariq7rchhs62zm"; + sha256 = "0bdac2as3130i83pvlsl8gdasvfy5kvaj5ylry2hv0rj8ynq2gyh"; libName = "granit_parser"; authors = [ "Ethiraric " @@ -4792,6 +4839,134 @@ rec { } ]; + }; + "jni" = rec { + crateName = "jni"; + version = "0.22.4"; + edition = "2024"; + sha256 = "161lza8gz071h22pgyqyx4n91ixd691z2dbb1pq2g97k5i49mzay"; + authors = [ + "jni team" + ]; + dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "combine"; + packageId = "combine"; + } + { + name = "jni-macros"; + packageId = "jni-macros"; + } + { + name = "jni-sys"; + packageId = "jni-sys"; + } + { + name = "log"; + packageId = "log"; + } + { + name = "simd_cesu8"; + packageId = "simd_cesu8"; + } + { + name = "thiserror"; + packageId = "thiserror 2.0.18"; + } + { + name = "windows-link"; + packageId = "windows-link"; + target = { target, features }: (target."windows" or false); + } + ]; + buildDependencies = [ + { + name = "walkdir"; + packageId = "walkdir"; + } + ]; + features = { + "invocation" = [ "dep:java-locator" "dep:libloading" ]; + }; + }; + "jni-macros" = rec { + crateName = "jni-macros"; + version = "0.22.4"; + edition = "2024"; + sha256 = "18v02mcn5c7mb2yw6r930xg6ynsn7hwkxv8z2kdhn3qprjn0j0d0"; + procMacro = true; + libName = "jni_macros"; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "simd_cesu8"; + packageId = "simd_cesu8"; + } + { + name = "syn"; + packageId = "syn 2.0.118"; + features = [ "full" ]; + } + ]; + buildDependencies = [ + { + name = "rustc_version"; + packageId = "rustc_version"; + } + ]; + + }; + "jni-sys" = rec { + crateName = "jni-sys"; + version = "0.4.1"; + edition = "2021"; + sha256 = "1wlahx6f2zhczdjqyn8mk7kshb8x5vsd927sn3lvw41rrf47ldy6"; + libName = "jni_sys"; + authors = [ + "Steven Fackler " + "Robert Bragg " + ]; + dependencies = [ + { + name = "jni-sys-macros"; + packageId = "jni-sys-macros"; + } + ]; + + }; + "jni-sys-macros" = rec { + crateName = "jni-sys-macros"; + version = "0.4.1"; + edition = "2021"; + sha256 = "0r32gbabrak15a7p487765b5wc0jcna2yv88mk6m1zjqyi1bkh1q"; + procMacro = true; + libName = "jni_sys_macros"; + authors = [ + "Robert Bragg " + ]; + dependencies = [ + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 2.0.118"; + features = [ "full" ]; + } + ]; + }; "jobserver" = rec { crateName = "jobserver"; @@ -5104,9 +5279,9 @@ rec { }; "kube" = rec { crateName = "kube"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "0ip1qc9kvgyp735rmxdjq75dspf737sazvhzs6b70siljn011fab"; + sha256 = "065z77zh7gbigcbv7mxk7firdf1w6278lal1jjqsnrh073hpz390"; authors = [ "clux " "Natalie Klestrup Röijezon " @@ -5179,9 +5354,9 @@ rec { }; "kube-client" = rec { crateName = "kube-client"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "0yskaybnf8m2c9rb0kxzlczb9jbx25sxdlqhzqhlq9n2bgh2ixnh"; + sha256 = "1lqny49zldysk1qr6pp2ifsp2ifqa5wcwplbj73wb9rk62kl1s9i"; libName = "kube_client"; authors = [ "clux " @@ -5281,6 +5456,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "rustls-platform-verifier"; + packageId = "rustls-platform-verifier"; + optional = true; + } { name = "secrecy"; packageId = "secrecy"; @@ -5391,7 +5571,7 @@ rec { "pem" = [ "dep:pem" ]; "ring" = [ "hyper-rustls?/ring" ]; "rustls" = [ "dep:rustls" ]; - "rustls-tls" = [ "rustls" "hyper-rustls" ]; + "rustls-tls" = [ "rustls" "hyper-rustls" "dep:rustls-platform-verifier" ]; "serde-saphyr" = [ "dep:serde-saphyr" ]; "socks5" = [ "hyper-util/client-proxy" ]; "tame-oauth" = [ "dep:tame-oauth" ]; @@ -5408,9 +5588,9 @@ rec { }; "kube-core" = rec { crateName = "kube-core"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "1a7a3n4mwiqywajlld3axii0k7ridizp5mn7b06i81ms6dcjzc61"; + sha256 = "1d7f2acgydnica3gsva5my87kw8cbfyvbq1f6ln4dwwc24y3blm9"; libName = "kube_core"; authors = [ "clux " @@ -5491,9 +5671,9 @@ rec { }; "kube-derive" = rec { crateName = "kube-derive"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "1an7av9xlif02klbk1xsz7nvggpnazpq2kpris0z3bbxf2c1h5zy"; + sha256 = "1h597lx7xd6x65ili9ccm8lmqdq3dg3384iwcf8vz0zsw4ciq54j"; procMacro = true; libName = "kube_derive"; authors = [ @@ -5540,9 +5720,9 @@ rec { }; "kube-runtime" = rec { crateName = "kube-runtime"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "08db3c5bg185y2yh6vilbkwqfdm5aykf87xllllwzis0qmkfrpcr"; + sha256 = "1isw7mh35jmfwi3kx9ibpcgw77lyv9h3ji2ajdgabgn7f7pn9c1y"; libName = "kube_runtime"; authors = [ "clux " @@ -8501,6 +8681,139 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "std" ]; }; + "rustls-platform-verifier" = rec { + crateName = "rustls-platform-verifier"; + version = "0.7.0"; + edition = "2021"; + sha256 = "181v4d0vl53vdh2wq56vghal1zyhdgqvy4xa8r45zwz4di9y5l96"; + libName = "rustls_platform_verifier"; + dependencies = [ + { + name = "core-foundation"; + packageId = "core-foundation"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "core-foundation-sys"; + packageId = "core-foundation-sys"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "jni"; + packageId = "jni"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "jni"; + packageId = "jni"; + usesDefaultFeatures = false; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "log"; + packageId = "log"; + } + { + name = "once_cell"; + packageId = "once_cell"; + optional = true; + } + { + name = "once_cell"; + packageId = "once_cell"; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "rustls"; + packageId = "rustls"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + { + name = "rustls-native-certs"; + packageId = "rustls-native-certs"; + target = { target, features }: ((target."unix" or false) && (!("android" == target."os" or null)) && (!("apple" == target."vendor" or null)) && (!("wasm32" == target."arch" or null))); + } + { + name = "rustls-platform-verifier-android"; + packageId = "rustls-platform-verifier-android"; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ((target."unix" or false) && (!("android" == target."os" or null)) && (!("apple" == target."vendor" or null)) && (!("wasm32" == target."arch" or null))); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ("wasm32" == target."arch" or null); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "security-framework"; + packageId = "security-framework"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "security-framework-sys"; + packageId = "security-framework-sys"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "webpki-root-certs"; + packageId = "webpki-root-certs"; + target = { target, features }: ("wasm32" == target."arch" or null); + } + { + name = "windows-sys"; + packageId = "windows-sys 0.61.2"; + usesDefaultFeatures = false; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Security_Cryptography" ]; + } + ]; + devDependencies = [ + { + name = "rustls"; + packageId = "rustls"; + usesDefaultFeatures = false; + features = [ "ring" ]; + } + { + name = "webpki-root-certs"; + packageId = "webpki-root-certs"; + } + ]; + features = { + "android_logger" = [ "dep:android_logger" ]; + "base64" = [ "dep:base64" ]; + "cert-logging" = [ "base64" ]; + "docsrs" = [ "jni" "once_cell" ]; + "ffi-testing" = [ "android_logger" "rustls/ring" ]; + "jni" = [ "dep:jni" ]; + "once_cell" = [ "dep:once_cell" ]; + }; + }; + "rustls-platform-verifier-android" = rec { + crateName = "rustls-platform-verifier-android"; + version = "0.1.1"; + edition = "2021"; + sha256 = "13vq6sxsgz9547xm2zbdxiw8x7ad1g8n8ax6xvxsjqszk7q6awgq"; + libName = "rustls_platform_verifier_android"; + + }; "rustls-webpki" = rec { crateName = "rustls-webpki"; version = "0.103.13"; @@ -8560,6 +8873,24 @@ rec { "no-panic" = [ "dep:no-panic" ]; }; }; + "same-file" = rec { + crateName = "same-file"; + version = "1.0.6"; + edition = "2018"; + sha256 = "00h5j1w87dmhnvbv9l8bic3y7xxsnjmssvifw2ayvgx9mb1ivz4k"; + libName = "same_file"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "winapi-util"; + packageId = "winapi-util"; + target = { target, features }: (target."windows" or false); + } + ]; + + }; "schannel" = rec { crateName = "schannel"; version = "0.1.29"; @@ -8857,6 +9188,7 @@ rec { features = { "default" = [ "OSX_10_13" ]; }; + resolvedDefaultFeatures = [ "OSX_10_13" "default" ]; }; "semver" = rec { crateName = "semver"; @@ -8907,10 +9239,10 @@ rec { }; "serde-saphyr" = rec { crateName = "serde-saphyr"; - version = "0.0.27"; + version = "0.0.29"; edition = "2024"; crateBin = []; - sha256 = "1crx4kmmm88y4dbaramdsn0w8fqzchaz12b6vdgx7bddzb1v95sq"; + sha256 = "196f2kjh4c9zayliykx9s5jyr2zir1rz15fsxdnwmq0xj60jglkv"; libName = "serde_saphyr"; dependencies = [ { @@ -8955,9 +9287,8 @@ rec { optional = true; } { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; + name = "serde_core"; + packageId = "serde_core"; } { name = "smallvec"; @@ -8970,16 +9301,9 @@ rec { optional = true; } ]; - devDependencies = [ - { - name = "serde"; - packageId = "serde"; - features = [ "derive" "rc" ]; - } - ]; features = { "default" = [ "serialize" "deserialize" ]; - "deserialize" = [ "dep:base64" "dep:num-traits" "dep:annotate-snippets" "dep:granit-parser" "dep:smallvec" "dep:encoding_rs_io" "dep:ahash" ]; + "deserialize" = [ "dep:num-traits" "dep:annotate-snippets" "dep:granit-parser" "dep:smallvec" "dep:encoding_rs_io" "dep:ahash" ]; "figment" = [ "dep:figment" "deserialize" ]; "figment2" = [ "dep:figment2" "deserialize" ]; "garde" = [ "dep:garde" "deserialize" ]; @@ -8988,6 +9312,7 @@ rec { "miette" = [ "dep:miette" "deserialize" ]; "properties" = [ "deserialize" ]; "robotics" = [ "deserialize" ]; + "serde_derived_types" = [ "dep:serde" ]; "serialize" = [ "dep:base64" "dep:num-traits" "dep:zmij" "dep:nohash-hasher" ]; "validator" = [ "dep:validator" "deserialize" ]; }; @@ -9431,6 +9756,46 @@ rec { "default" = [ "std" "const-generics" ]; }; }; + "simd_cesu8" = rec { + crateName = "simd_cesu8"; + version = "1.2.0"; + edition = "2021"; + sha256 = "0865mv3nmd35f1dccjcfj7dncjmmvvdij3j61z4131mz38jiw0qi"; + authors = [ + "Sean C. Roach " + ]; + dependencies = [ + { + name = "simdutf8"; + packageId = "simdutf8"; + usesDefaultFeatures = false; + } + ]; + buildDependencies = [ + { + name = "rustc_version"; + packageId = "rustc_version"; + } + ]; + features = { + "default" = [ "std" ]; + "std" = [ "simdutf8/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; + "simdutf8" = rec { + crateName = "simdutf8"; + version = "0.1.5"; + edition = "2018"; + sha256 = "0vmpf7xaa0dnaikib5jlx6y4dxd3hxqz6l830qb079g7wcsgxag3"; + authors = [ + "Hans Kratz " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "slab" = rec { crateName = "slab"; version = "0.4.12"; @@ -12681,6 +13046,27 @@ rec { "Sergio Benitez " ]; + }; + "walkdir" = rec { + crateName = "walkdir"; + version = "2.5.0"; + edition = "2018"; + sha256 = "0jsy7a710qv8gld5957ybrnc07gavppp963gs32xk4ag8130jy99"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "same-file"; + packageId = "same-file"; + } + { + name = "winapi-util"; + packageId = "winapi-util"; + target = { target, features }: (target."windows" or false); + } + ]; + }; "want" = rec { crateName = "want"; @@ -13411,6 +13797,41 @@ rec { "serde" = [ "dep:serde" ]; }; }; + "webpki-root-certs" = rec { + crateName = "webpki-root-certs"; + version = "1.0.9"; + edition = "2021"; + sha256 = "16qw59hxn1lln1615kb9rjy16pfxd1x8m9f9w6vwv36c5am58rdr"; + libName = "webpki_root_certs"; + dependencies = [ + { + name = "rustls-pki-types"; + packageId = "rustls-pki-types"; + rename = "pki-types"; + usesDefaultFeatures = false; + } + ]; + + }; + "winapi-util" = rec { + crateName = "winapi-util"; + version = "0.1.11"; + edition = "2021"; + sha256 = "08hdl7mkll7pz8whg869h58c1r9y7in0w0pk8fm24qc77k0b39y2"; + libName = "winapi_util"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "windows-sys"; + packageId = "windows-sys 0.61.2"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Storage_FileSystem" "Win32_System_Console" "Win32_System_SystemInformation" ]; + } + ]; + + }; "windows-core" = rec { crateName = "windows-core"; version = "0.62.2"; diff --git a/Cargo.toml b/Cargo.toml index 9423bb24..4459e967 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,5 +31,5 @@ tokio = { version = "1.52", features = ["full"] } tracing = "0.1" [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator" } +# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } diff --git a/extra/crds.yaml b/extra/crds.yaml index 09afc814..4a91c210 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -6,7 +6,6 @@ metadata: spec: group: hive.stackable.tech names: - categories: [] kind: HiveCluster plural: hiveclusters shortNames: @@ -14,8 +13,7 @@ spec: singular: hivecluster scope: Namespaced versions: - - additionalPrinterColumns: [] - name: v1alpha1 + - name: v1alpha1 schema: openAPIV3Schema: description: A Hive cluster stacklet. This resource is managed by the Stackable operator for Apache Hive. @@ -466,7 +464,6 @@ spec: at role level, the `roleConfig`. You can learn more about this in the [Roles and role group concept documentation](https://docs.stackable.tech/home/nightly/concepts/roles-and-role-groups). - nullable: true properties: cliOverrides: additionalProperties: @@ -1365,6 +1362,7 @@ spec: required: - clusterConfig - image + - metastore type: object status: nullable: true diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 775356bb..3cbdb783 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -16,18 +16,18 @@ use stackable_operator::{ commons::{ affinity::StackableAffinity, product_image_selection::ResolvedProductImage, - rbac::build_rbac_resources, resources::{NoRuntimeLimits, Resources}, }, crd::{listener::v1alpha1::Listener, s3}, database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, k8s_openapi::api::{ apps::v1::StatefulSet, - core::v1::{ConfigMap, Service}, + core::v1::{ConfigMap, Service, ServiceAccount}, policy::v1::PodDisruptionBudget, + rbac::v1::RoleBinding, }, kube::{ - Resource, ResourceExt, + Resource, api::ObjectMeta, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, @@ -44,6 +44,7 @@ use stackable_operator::{ cluster_resources::cluster_resources_new, kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, + role_utils, types::{ kubernetes::{ListenerClassName, ListenerName, SecretClassName}, operator::{ControllerName, OperatorName, ProductName, ProductVersion, RoleName}, @@ -54,7 +55,7 @@ use strum::EnumDiscriminants; use crate::{ OPERATOR_NAME, - controller::build::resource::discovery, + controller::build::{UNVERSIONED_PRODUCT_VERSION, resource::discovery}, crd::{APP_NAME, HdfsConnection, HiveClusterStatus, HiveRole, MetaStoreConfig, v1alpha1}, }; @@ -95,27 +96,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to patch service account"))] - ApplyServiceAccount { - source: stackable_operator::cluster_resources::Error, - }, - - #[snafu(display("failed to patch role binding"))] - ApplyRoleBinding { - source: stackable_operator::cluster_resources::Error, - }, - - #[snafu(display("failed to build RBAC resources"))] - BuildRbacResources { - source: stackable_operator::commons::rbac::Error, - }, - - #[snafu(display("failed to get required Labels"))] - GetRequiredLabels { - source: - stackable_operator::kvp::KeyValuePairError, - }, - #[snafu(display("HiveCluster object is invalid"))] InvalidHiveCluster { source: error_boundary::InvalidObject, @@ -210,7 +190,7 @@ pub struct ValidatedCluster { /// The product version as a valid label value, used for the recommended `app.kubernetes.io/version` /// label. Derived from the resolved image's app version label value. pub product_version: ProductVersion, - pub role_config: Option, + pub role_config: ValidatedRoleConfig, pub cluster_config: ValidatedClusterConfig, pub role_group_configs: BTreeMap>, } @@ -221,7 +201,7 @@ impl ValidatedCluster { namespace: stackable_operator::v2::types::kubernetes::NamespaceName, uid: stackable_operator::v2::types::kubernetes::Uid, image: ResolvedProductImage, - role_config: Option, + role_config: ValidatedRoleConfig, cluster_config: ValidatedClusterConfig, role_group_configs: BTreeMap>, ) -> Self { @@ -247,25 +227,52 @@ impl ValidatedCluster { } } - /// The single Hive role name (`metastore`). - pub fn role_name() -> RoleName { - RoleName::from_str(&HiveRole::MetaStore.to_string()) - .expect("the metastore role name is a valid role name") + /// Type-safe names for the per-cluster RBAC resources: the ServiceAccount shared by all + /// Pods, its (namespaced) RoleBinding, and the operator-deployed ClusterRole it binds. + pub fn cluster_resource_names(&self) -> role_utils::ResourceNames { + role_utils::ResourceNames { + cluster_name: self.name.clone(), + product_name: product_name(), + } } /// Type-safe names for the resources of a given role group. - pub(crate) fn resource_names(&self, role_group_name: &RoleGroupName) -> ResourceNames { + pub(crate) fn role_group_resource_names( + &self, + role_group_name: &RoleGroupName, + ) -> ResourceNames { ResourceNames { cluster_name: self.name.clone(), - role_name: Self::role_name(), + role_name: HiveRole::MetaStore.into(), role_group_name: role_group_name.clone(), } } + /// Recommended labels for a resource that is not tied to a concrete role, + /// using a free-form role/role-group label value. + pub fn recommended_labels_for( + &self, + role_name: &RoleName, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_with(&self.product_version, role_name, role_group_name) + } + + /// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates + /// that cannot be modified after deployment (keeps the labels stable across version upgrades). + pub fn unversioned_recommended_labels(&self, role_group_name: &RoleGroupName) -> Labels { + self.recommended_labels_with( + &UNVERSIONED_PRODUCT_VERSION, + &HiveRole::MetaStore.into(), + role_group_name, + ) + } + /// Recommended labels for a role-group resource, using the given product version. - fn recommended_labels_for( + fn recommended_labels_with( &self, product_version: &ProductVersion, + role_name: &RoleName, role_group_name: &RoleGroupName, ) -> Labels { recommended_labels( @@ -274,19 +281,24 @@ impl ValidatedCluster { product_version, &operator_name(), &controller_name(), - &Self::role_name(), + role_name, role_group_name, ) } /// Recommended labels for a role-group resource. pub fn recommended_labels(&self, role_group_name: &RoleGroupName) -> Labels { - self.recommended_labels_for(&self.product_version, role_group_name) + self.recommended_labels_for(&HiveRole::MetaStore.into(), role_group_name) } /// Selector labels matching the pods of a role group. pub fn role_group_selector(&self, role_group_name: &RoleGroupName) -> Labels { - role_group_selector(self, &product_name(), &Self::role_name(), role_group_name) + role_group_selector( + self, + &product_name(), + &HiveRole::MetaStore.into(), + role_group_name, + ) } /// Whether Kerberos is enabled for this cluster (a Kerberos `SecretClass` was configured). @@ -303,31 +315,6 @@ impl ValidatedCluster { )) .expect("the role listener name is a valid Listener name") } - - /// Returns an `ObjectMetaBuilder` pre-filled with the namespace, an owner reference back to - /// this cluster, and the recommended labels for a resource named `name` in `role_group_name`. - /// - /// Consolidates the metadata chain repeated by the child-resource builders. Call sites that - /// need extra labels/annotations chain them onto the returned builder. - pub(crate) fn object_meta( - &self, - name: impl Into, - role_group_name: &RoleGroupName, - ) -> stackable_operator::builder::meta::ObjectMetaBuilder { - let mut builder = stackable_operator::builder::meta::ObjectMetaBuilder::new(); - builder - .name_and_namespace(self) - .name(name) - .ownerreference( - stackable_operator::v2::builder::meta::ownerreference_from_resource( - self, - None, - Some(true), - ), - ) - .with_labels(self.recommended_labels(role_group_name)); - builder - } } /// Lets [`ValidatedCluster`] stand in for the raw [`v1alpha1::HiveCluster`] when building owner @@ -433,13 +420,15 @@ pub struct ValidatedRoleConfig { /// /// The role-level discovery `ConfigMap` is deliberately absent: it is built from the *applied* /// role [`Listener`]'s ingress addresses, so it is assembled in the reconcile step after the -/// Listener has been applied, not in the build step. +/// Listener has been applied, not in the build step. pub struct KubernetesResources { pub stateful_sets: Vec, pub services: Vec, pub listeners: Vec, pub config_maps: Vec, pub pod_disruption_budgets: Vec, + pub service_accounts: Vec, + pub role_bindings: Vec, } pub async fn reconcile_hive( @@ -476,41 +465,26 @@ pub async fn reconcile_hive( &hive.spec.object_overrides, ); - let (rbac_sa, rbac_rolebinding) = build_rbac_resources( - hive, - APP_NAME, - cluster_resources - .get_required_labels() - .context(GetRequiredLabelsSnafu)?, - ) - .context(BuildRbacResourcesSnafu)?; - - let rbac_sa = cluster_resources - .add(client, rbac_sa) - .await - .context(ApplyServiceAccountSnafu)?; - - cluster_resources - .add(client, rbac_rolebinding) - .await - .context(ApplyRoleBindingSnafu)?; - - // The ServiceAccount name is deterministic on the built object, so the client-free build step - // does not depend on the applied ServiceAccount. - let service_account_name = rbac_sa.name_any(); - - let resources = build::build( - &validated_cluster, - &client.kubernetes_cluster_info, - &service_account_name, - ) - .context(BuildResourcesSnafu)?; + let resources = build::build(&validated_cluster, &client.kubernetes_cluster_info) + .context(BuildResourcesSnafu)?; let mut ss_cond_builder = StatefulSetConditionBuilder::default(); // Apply order: everything before StatefulSets, StatefulSets last. A StatefulSet must only be // applied after all ConfigMaps and Secrets it mounts, to prevent unnecessary Pod restarts. // See https://github.com/stackabletech/commons-operator/issues/111 for details. + for service_account in resources.service_accounts { + cluster_resources + .add(client, service_account) + .await + .context(ApplyResourceSnafu)?; + } + for role_binding in resources.role_bindings { + cluster_resources + .add(client, role_binding) + .await + .context(ApplyResourceSnafu)?; + } for service in resources.services { cluster_resources .add(client, service) @@ -619,9 +593,23 @@ pub(crate) mod test_support { use super::{ValidatedCluster, dereference::DereferencedObjects, validate::validate_cluster}; use crate::crd::v1alpha1; + /// The expected `app.kubernetes.io/version` label value for the given product version. + /// + /// The `-stackable` suffix carries the operator's own version, which is `0.0.0-dev` on main + /// but rewritten by the release process — so tests must derive it rather than hardcode it, + /// or they fail on release branches. + pub fn app_version_label(product_version: &str) -> String { + format!( + "{product_version}-stackable{}", + crate::built_info::PKG_VERSION + ) + } + /// Minimal Derby-backed `HiveCluster` fixture shared across the crate's tests. /// - /// Includes a `uid` so owner references can be derived from it. + /// Includes a `uid` so owner references can be derived from it. The cluster name + /// (`simple-hive`) deliberately differs from the product name (`hive`), so tests asserting + /// recommended labels catch swapped `name`/`instance` values. pub const DERBY_YAML: &str = r#" apiVersion: hive.stackable.tech/v1alpha1 kind: HiveCluster @@ -661,21 +649,18 @@ pub(crate) mod test_support { #[cfg(test)] mod tests { - use std::str::FromStr; + use stackable_operator::v2::types::operator::RoleName; + use strum::IntoEnumIterator; - use super::{RoleGroupName, test_support::*}; + use crate::crd::HiveRole; + /// Locks the invariant behind the `expect` in the `From for RoleName` impls: + /// every `HiveRole` variant (present and future) must serialise to a valid `RoleName`. #[test] - fn object_meta_sets_namespace_owner_and_recommended_labels() { - let hive = minimal_hive(DERBY_YAML); - let cluster = validated_cluster(&hive); - let role_group_name = RoleGroupName::from_str("default").expect("valid role group name"); - - let meta = cluster.object_meta("test-name", &role_group_name).build(); - - assert_eq!(meta.name.as_deref(), Some("test-name")); - assert_eq!(meta.namespace.as_deref(), Some(cluster.namespace.as_ref())); - assert!(meta.owner_references.is_some()); - assert!(meta.labels.is_some()); + fn every_hive_role_serialises_to_a_valid_role_name() { + for role in HiveRole::iter() { + let _: RoleName = (&role).into(); + let _: RoleName = role.into(); + } } } diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 974a71f7..f6f79545 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -4,8 +4,12 @@ use std::str::FromStr; use snafu::{ResultExt, Snafu}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, utils::cluster_info::KubernetesClusterInfo, - v2::types::operator::{ProductVersion, RoleGroupName}, + v2::{ + builder::meta::ownerreference_from_resource, + types::operator::{ProductVersion, RoleGroupName}, + }, }; use crate::{ @@ -15,6 +19,7 @@ use crate::{ config_map::build_metastore_rolegroup_config_map, listener::build_role_listener, pdb::build_pdb, + rbac::{build_role_binding, build_service_account}, service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, statefulset::build_metastore_rolegroup_statefulset, }, @@ -69,13 +74,9 @@ pub enum Error { /// /// `cluster_info` carries the Kubernetes cluster domain (needed by the Kerberos config); it is /// static cluster metadata, not a live client, so the build step stays client-free. -/// -/// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under -/// (RBAC resources are built and applied separately, in the reconcile step). pub fn build( cluster: &ValidatedCluster, cluster_info: &KubernetesClusterInfo, - service_account_name: &str, ) -> Result { let mut stateful_sets = vec![]; let mut services = vec![]; @@ -85,14 +86,13 @@ pub fn build( // Role-level resources. Hive has the single `metastore` role; its PDB and Listener are built // here, but the discovery ConfigMap (which needs the applied Listener) is built in reconcile. - if let Some(role_config) = &cluster.role_config { - pod_disruption_budgets.extend(build_pdb(&role_config.pdb, cluster, &HiveRole::MetaStore)); - listeners.push(build_role_listener( - cluster, - &HiveRole::MetaStore, - &role_config.listener_class, - )); - } + let role_config = &cluster.role_config; + pod_disruption_budgets.extend(build_pdb(&role_config.pdb, cluster, &HiveRole::MetaStore)); + listeners.push(build_role_listener( + cluster, + &HiveRole::MetaStore, + &role_config.listener_class, + )); for (hive_role, role_group_configs) in &cluster.role_group_configs { for (role_group_name, rg) in role_group_configs { @@ -105,16 +105,10 @@ pub fn build( })?, ); stateful_sets.push( - build_metastore_rolegroup_statefulset( - hive_role, - cluster, - role_group_name, - rg, - service_account_name, - ) - .context(StatefulSetSnafu { - role_group: role_group_name.clone(), - })?, + build_metastore_rolegroup_statefulset(hive_role, cluster, role_group_name, rg) + .context(StatefulSetSnafu { + role_group: role_group_name.clone(), + })?, ); } } @@ -125,9 +119,30 @@ pub fn build( listeners, config_maps, pod_disruption_budgets, + service_accounts: vec![build_service_account(cluster)], + role_bindings: vec![build_role_binding(cluster)], }) } +/// Returns an [`ObjectMetaBuilder`] pre-filled with the namespace, an owner reference back to +/// the cluster, and the recommended labels for a resource named `name` in `role_group_name`. +/// +/// Consolidates the metadata chain repeated by the child-resource builders. Call sites that +/// need extra labels/annotations chain them onto the returned builder. +pub(crate) fn object_meta( + cluster: &ValidatedCluster, + name: impl Into, + role_group_name: &RoleGroupName, +) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .name_and_namespace(cluster) + .name(name) + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) + .with_labels(cluster.recommended_labels(role_group_name)); + builder +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -136,7 +151,7 @@ mod tests { commons::networking::DomainName, kube::Resource, utils::cluster_info::KubernetesClusterInfo, }; - use super::build; + use super::{RoleGroupName, build, object_meta}; use crate::controller::test_support::{DERBY_YAML, minimal_hive, validated_cluster}; fn test_cluster_info() -> KubernetesClusterInfo { @@ -159,15 +174,20 @@ mod tests { let hive = minimal_hive(DERBY_YAML); let cluster = validated_cluster(&hive); - let resources = build(&cluster, &test_cluster_info(), "simple-hive-serviceaccount") - .expect("build succeeds"); + let resources = build(&cluster, &test_cluster_info()).expect("build succeeds"); assert_eq!( sorted_names(&resources.stateful_sets), ["simple-hive-metastore-default"] ); // One headless and one metrics Service per role group. - assert_eq!(resources.services.len(), 2); + assert_eq!( + sorted_names(&resources.services), + [ + "simple-hive-metastore-default-headless", + "simple-hive-metastore-default-metrics", + ] + ); assert_eq!( sorted_names(&resources.config_maps), ["simple-hive-metastore-default"] @@ -182,5 +202,28 @@ mod tests { sorted_names(&resources.pod_disruption_budgets), ["simple-hive-metastore"] ); + // The cluster-shared RBAC pair. + assert_eq!( + sorted_names(&resources.service_accounts), + ["simple-hive-serviceaccount"] + ); + assert_eq!( + sorted_names(&resources.role_bindings), + ["simple-hive-rolebinding"] + ); + } + + #[test] + fn object_meta_sets_namespace_owner_and_recommended_labels() { + let hive = minimal_hive(DERBY_YAML); + let cluster = validated_cluster(&hive); + let role_group_name = RoleGroupName::from_str("default").expect("valid role group name"); + + let meta = object_meta(&cluster, "test-name", &role_group_name).build(); + + assert_eq!(meta.name.as_deref(), Some("test-name")); + assert_eq!(meta.namespace.as_deref(), Some(cluster.namespace.as_ref())); + assert!(meta.owner_references.is_some()); + assert!(meta.labels.is_some()); } } diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 2c466880..d57d748f 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -13,6 +13,7 @@ use crate::controller::{ HiveRoleGroupConfig, RoleGroupName, ValidatedCluster, build::{ kerberos::kerberos_config_properties, + object_meta, properties::{ConfigFileName, core_site, hive_site, product_logging, security_properties}, }, }; @@ -72,15 +73,15 @@ pub fn build_metastore_rolegroup_config_map( let mut cm_builder = ConfigMapBuilder::new(); cm_builder .metadata( - cluster - .object_meta( - cluster - .resource_names(role_group_name) - .role_group_config_map() - .to_string(), - role_group_name, - ) - .build(), + object_meta( + cluster, + cluster + .role_group_resource_names(role_group_name) + .role_group_config_map() + .to_string(), + role_group_name, + ) + .build(), ) .add_data( ConfigFileName::HiveSite.to_string(), diff --git a/rust/operator-binary/src/controller/build/resource/discovery.rs b/rust/operator-binary/src/controller/build/resource/discovery.rs index 7e544be2..1f4a2858 100644 --- a/rust/operator-binary/src/controller/build/resource/discovery.rs +++ b/rust/operator-binary/src/controller/build/resource/discovery.rs @@ -8,7 +8,8 @@ use crate::{ controller::{ ValidatedCluster, build::{ - PLACEHOLDER_DISCOVERY_ROLE_GROUP, resource::listener::build_listener_connection_string, + PLACEHOLDER_DISCOVERY_ROLE_GROUP, object_meta, + resource::listener::build_listener_connection_string, }, }, crd::{HiveRole, v1alpha1}, @@ -46,12 +47,15 @@ pub fn build_discovery_configmap( let mut discovery_configmap = ConfigMapBuilder::new(); discovery_configmap.metadata( - cluster - // Discovery is a role-level object; the cluster name is used as the resource name - // (matching `name_and_namespace`) and "discovery" as a placeholder role-group name - // for the recommended labels. - .object_meta(cluster.name.to_string(), &PLACEHOLDER_DISCOVERY_ROLE_GROUP) - .build(), + // Discovery is a role-level object; the cluster name is used as the resource name + // (matching `name_and_namespace`) and "discovery" as a placeholder role-group name + // for the recommended labels. + object_meta( + cluster, + cluster.name.to_string(), + &PLACEHOLDER_DISCOVERY_ROLE_GROUP, + ) + .build(), ); discovery_configmap.add_data( diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 68ef70e6..b6570495 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -5,7 +5,10 @@ use stackable_operator::{ }; use crate::{ - controller::{ValidatedCluster, build::PLACEHOLDER_LISTENER_ROLE_GROUP}, + controller::{ + ValidatedCluster, + build::{PLACEHOLDER_LISTENER_ROLE_GROUP, object_meta}, + }, crd::{HIVE_PORT, HIVE_PORT_NAME, HiveRole}, }; @@ -51,12 +54,12 @@ pub fn build_role_listener( ) -> Listener { // The role listener is a role-level (not role-group-level) object, so there is no real // role-group name; "none" is used as a placeholder for the recommended labels. - let metadata = cluster - .object_meta( - cluster.role_listener_name(hive_role), - &PLACEHOLDER_LISTENER_ROLE_GROUP, - ) - .build(); + let metadata = object_meta( + cluster, + cluster.role_listener_name(hive_role), + &PLACEHOLDER_LISTENER_ROLE_GROUP, + ) + .build(); let spec = ListenerSpec { class_name: Some(listener_class.to_string()), diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs index 1fef6abb..9c9ce3e3 100644 --- a/rust/operator-binary/src/controller/build/resource/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -4,5 +4,6 @@ pub mod config_map; pub mod discovery; pub mod listener; pub mod pdb; +pub mod rbac; pub mod service; pub mod statefulset; diff --git a/rust/operator-binary/src/controller/build/resource/pdb.rs b/rust/operator-binary/src/controller/build/resource/pdb.rs index c1b7be36..c3c0426c 100644 --- a/rust/operator-binary/src/controller/build/resource/pdb.rs +++ b/rust/operator-binary/src/controller/build/resource/pdb.rs @@ -23,7 +23,7 @@ pub fn build_pdb( let pdb = pod_disruption_budget_builder_with_role( cluster, &product_name(), - &ValidatedCluster::role_name(), + &role.into(), &operator_name(), &controller_name(), ) diff --git a/rust/operator-binary/src/controller/build/resource/rbac.rs b/rust/operator-binary/src/controller/build/resource/rbac.rs new file mode 100644 index 00000000..bce7548e --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/rbac.rs @@ -0,0 +1,139 @@ +//! Builds the RBAC resources (ServiceAccount + RoleBinding) shared by all role groups. + +use std::str::FromStr; + +use stackable_operator::{ + k8s_openapi::api::{core::v1::ServiceAccount, rbac::v1::RoleBinding}, + kvp::Labels, + v2::{ + rbac, + types::operator::{RoleGroupName, RoleName}, + }, +}; + +use crate::controller::ValidatedCluster; + +stackable_operator::constant!(NONE_ROLE_NAME: RoleName = "none"); +stackable_operator::constant!(NONE_ROLE_GROUP_NAME: RoleGroupName = "none"); + +/// Builds the [`ServiceAccount`] that the role-group Pods run under. +pub fn build_service_account(cluster: &ValidatedCluster) -> ServiceAccount { + rbac::build_service_account( + cluster, + &cluster.cluster_resource_names(), + rbac_labels(cluster), + ) +} + +/// Builds the [`RoleBinding`] that binds the [`ServiceAccount`] from [`build_service_account`] to +/// the operator-deployed ClusterRole. +pub fn build_role_binding(cluster: &ValidatedCluster) -> RoleBinding { + rbac::build_role_binding( + cluster, + &cluster.cluster_resource_names(), + rbac_labels(cluster), + ) +} + +/// Both resources are shared by the whole cluster rather than tied to a role or role group, so +/// the recommended labels carry `none` for both values. +fn rbac_labels(cluster: &ValidatedCluster) -> Labels { + cluster.recommended_labels_for(&NONE_ROLE_NAME, &NONE_ROLE_GROUP_NAME) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + use crate::controller::test_support::{ + DERBY_YAML, app_version_label, minimal_hive, validated_cluster, + }; + + // `simple-hive` vs `hive`: see the swap-guard note on `DERBY_YAML`. + + #[test] + fn test_service_account() { + let hive = minimal_hive(DERBY_YAML); + let service_account = build_service_account(&validated_cluster(&hive)); + + assert_eq!( + json!({ + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + // The RBAC resources are cluster-shared, so role and role group are `none`. + "labels": { + "app.kubernetes.io/component": "none", + "app.kubernetes.io/instance": "simple-hive", + "app.kubernetes.io/managed-by": "hive.stackable.tech_hivecluster", + "app.kubernetes.io/name": "hive", + "app.kubernetes.io/role-group": "none", + "app.kubernetes.io/version": app_version_label("4.0.0"), + "stackable.tech/vendor": "Stackable" + }, + "name": "simple-hive-serviceaccount", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "hive.stackable.tech/v1alpha1", + "controller": true, + "kind": "HiveCluster", + "name": "simple-hive", + "uid": "12345678-1234-1234-1234-123456789012" + } + ] + } + }), + serde_json::to_value(service_account).expect("must be serializable") + ); + } + + #[test] + fn test_role_binding() { + let hive = minimal_hive(DERBY_YAML); + let role_binding = build_role_binding(&validated_cluster(&hive)); + + assert_eq!( + json!({ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "labels": { + "app.kubernetes.io/component": "none", + "app.kubernetes.io/instance": "simple-hive", + "app.kubernetes.io/managed-by": "hive.stackable.tech_hivecluster", + "app.kubernetes.io/name": "hive", + "app.kubernetes.io/role-group": "none", + "app.kubernetes.io/version": app_version_label("4.0.0"), + "stackable.tech/vendor": "Stackable" + }, + "name": "simple-hive-rolebinding", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "hive.stackable.tech/v1alpha1", + "controller": true, + "kind": "HiveCluster", + "name": "simple-hive", + "uid": "12345678-1234-1234-1234-123456789012" + } + ] + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "hive-clusterrole" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "simple-hive-serviceaccount", + "namespace": "default" + } + ] + }), + serde_json::to_value(role_binding).expect("must be serializable") + ); + } +} diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 3ee05cc9..504f1bc0 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -4,7 +4,7 @@ use stackable_operator::{ }; use crate::{ - controller::{RoleGroupName, ValidatedCluster}, + controller::{RoleGroupName, ValidatedCluster, build::object_meta}, crd::{HIVE_PORT, HIVE_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME}, }; @@ -16,15 +16,15 @@ pub fn build_rolegroup_headless_service( role_group_name: &RoleGroupName, ) -> Service { Service { - metadata: cluster - .object_meta( - cluster - .resource_names(role_group_name) - .headless_service_name() - .to_string(), - role_group_name, - ) - .build(), + metadata: object_meta( + cluster, + cluster + .role_group_resource_names(role_group_name) + .headless_service_name() + .to_string(), + role_group_name, + ) + .build(), spec: Some(ServiceSpec { // Internal communication does not need to be exposed type_: Some("ClusterIP".to_string()), @@ -45,22 +45,22 @@ pub fn build_rolegroup_metrics_service( role_group_name: &RoleGroupName, ) -> Service { Service { - metadata: cluster - .object_meta( - cluster - .resource_names(role_group_name) - .metrics_service_name() - .to_string(), - role_group_name, - ) - .with_labels(prometheus_labels(&Scraping::Enabled)) - .with_annotations(prometheus_annotations( - &Scraping::Enabled, - &Scheme::Http, - "/metrics", - &METRICS_PORT, - )) - .build(), + metadata: object_meta( + cluster, + cluster + .role_group_resource_names(role_group_name) + .metrics_service_name() + .to_string(), + role_group_name, + ) + .with_labels(prometheus_labels(&Scraping::Enabled)) + .with_annotations(prometheus_annotations( + &Scraping::Enabled, + &Scheme::Http, + "/metrics", + &METRICS_PORT, + )) + .build(), spec: Some(ServiceSpec { // Internal communication does not need to be exposed type_: Some("ClusterIP".to_string()), @@ -91,3 +91,82 @@ fn service_ports() -> Vec { ..ServicePort::default() }] } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use serde_json::json; + + use super::*; + use crate::controller::test_support::{ + DERBY_YAML, app_version_label, minimal_hive, validated_cluster, + }; + + /// Every metrics Service must carry the Prometheus scrape label and the + /// `prometheus.io/path|port|scheme|scrape` annotations, or Prometheus stops discovering the + /// endpoints. + #[test] + fn test_rolegroup_metrics_service() { + let hive = minimal_hive(DERBY_YAML); + let cluster = validated_cluster(&hive); + let role_group_name = RoleGroupName::from_str("default").expect("valid role group name"); + + let service = build_rolegroup_metrics_service(&cluster, &role_group_name); + + assert_eq!( + json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "annotations": { + "prometheus.io/path": "/metrics", + "prometheus.io/port": "9084", + "prometheus.io/scheme": "http", + "prometheus.io/scrape": "true" + }, + "labels": { + "app.kubernetes.io/component": "metastore", + "app.kubernetes.io/instance": "simple-hive", + "app.kubernetes.io/managed-by": "hive.stackable.tech_hivecluster", + "app.kubernetes.io/name": "hive", + "app.kubernetes.io/role-group": "default", + "app.kubernetes.io/version": app_version_label("4.0.0"), + "prometheus.io/scrape": "true", + "stackable.tech/vendor": "Stackable" + }, + "name": "simple-hive-metastore-default-metrics", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "hive.stackable.tech/v1alpha1", + "controller": true, + "kind": "HiveCluster", + "name": "simple-hive", + "uid": "12345678-1234-1234-1234-123456789012" + } + ] + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "name": "metrics", + "port": 9084, + "protocol": "TCP" + } + ], + "publishNotReadyAddresses": true, + "selector": { + "app.kubernetes.io/component": "metastore", + "app.kubernetes.io/instance": "simple-hive", + "app.kubernetes.io/name": "hive", + "app.kubernetes.io/role-group": "default" + }, + "type": "ClusterIP" + } + }), + serde_json::to_value(service).expect("must be serializable") + ); + } +} diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 049c05ff..e79add71 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -48,11 +48,11 @@ use crate::{ controller::{ HiveRoleGroupConfig, RoleGroupName, ValidatedCluster, build::{ - UNVERSIONED_PRODUCT_VERSION, command::build_container_command_args, graceful_shutdown::add_graceful_shutdown_config, jvm::{construct_hadoop_heapsize_env, construct_non_heap_jvm_args}, kerberos::{add_kerberos_pod_config, kerberos_container_start_commands}, + object_meta, opa::{OPA_TLS_VOLUME_NAME, build_opa_tls_ca_cert_mount_path}, properties::product_logging::MAX_HIVE_LOG_FILES_SIZE, }, @@ -141,9 +141,8 @@ pub(crate) fn build_metastore_rolegroup_statefulset( cluster: &ValidatedCluster, role_group_name: &RoleGroupName, rg: &HiveRoleGroupConfig, - sa_name: &str, ) -> Result { - let resource_names = cluster.resource_names(role_group_name); + let resource_names = cluster.role_group_resource_names(role_group_name); let resolved_product_image = &cluster.image; let database_connection_details = &cluster.cluster_config.metadata_database_connection_details; let s3_connection = cluster.cluster_config.s3_connection_spec.as_ref(); @@ -325,8 +324,7 @@ pub(crate) fn build_metastore_rolegroup_statefulset( let recommended_object_labels = cluster.recommended_labels(role_group_name); // Used for PVC templates that cannot be modified once they are deployed. A version value is // required, so a constant "none" is used to keep the labels stable across version upgrades. - let unversioned_recommended_labels = - cluster.recommended_labels_for(&UNVERSIONED_PRODUCT_VERSION, role_group_name); + let unversioned_recommended_labels = cluster.unversioned_recommended_labels(role_group_name); let metadata = ObjectMetaBuilder::new() .with_labels(recommended_object_labels) @@ -372,7 +370,12 @@ pub(crate) fn build_metastore_rolegroup_statefulset( ) .context(AddVolumeSnafu)? .affinity(&merged_config.affinity) - .service_account_name(sa_name) + .service_account_name( + cluster + .cluster_resource_names() + .service_account_name() + .to_string(), + ) .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); // The Hive container's log config ConfigMap: either the operator-generated one (the rolegroup @@ -424,13 +427,13 @@ pub(crate) fn build_metastore_rolegroup_statefulset( pod_template.merge_from(rg.pod_overrides.clone()); Ok(StatefulSet { - metadata: cluster - .object_meta( - resource_names.stateful_set_name().to_string(), - role_group_name, - ) - .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) - .build(), + metadata: object_meta( + cluster, + resource_names.stateful_set_name().to_string(), + role_group_name, + ) + .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) + .build(), spec: Some(StatefulSetSpec { pod_management_policy: Some("Parallel".to_string()), // `None` (no replica count specified) leaves `.spec.replicas` unset so a diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 51ca71aa..73ea52ea 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -39,9 +39,6 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("object defines no metastore role"))] - NoMetaStoreRole, - #[snafu(display("failed to resolve cluster name"))] ResolveClusterName { source: stackable_operator::v2::controller_utils::Error, @@ -152,21 +149,17 @@ pub fn validate_cluster( .context(ResolveProductImageSnafu)?; let hive_role = HiveRole::MetaStore; - let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; + let role = &hive.spec.metastore; - let role_config = if let Some(HiveMetastoreRoleConfig { + let HiveMetastoreRoleConfig { common: GenericRoleConfig { pod_disruption_budget: pdb, }, listener_class, - }) = hive.role_config(&hive_role) - { - Some(ValidatedRoleConfig { - pdb: pdb.clone(), - listener_class: listener_class.clone(), - }) - } else { - None + } = hive.role_config(&hive_role); + let role_config = ValidatedRoleConfig { + pdb: pdb.clone(), + listener_class: listener_class.clone(), }; let default_config = MetaStoreConfig::default_config(name.as_ref(), &hive_role); @@ -292,6 +285,161 @@ fn validate_role_group_config( #[cfg(test)] mod tests { use super::*; + use crate::controller::test_support::{DERBY_YAML, app_version_label, minimal_hive}; + + /// Runs the real validate step over the given fixture YAML, without unwrapping, so error + /// cases can assert the specific [`Error`] variant. + fn validate_yaml(yaml: &str) -> Result { + validate_cluster( + &minimal_hive(yaml), + "oci.example.org", + DereferencedObjects { + s3_connection_spec: None, + hive_opa_config: None, + }, + ) + } + + /// Unwraps the error of a failed validation ([`ValidatedCluster`] has no `Debug` impl, so + /// `expect_err` is unavailable). + fn expect_validate_err(yaml: &str) -> Error { + match validate_yaml(yaml) { + Ok(_) => panic!("expected validation to fail"), + Err(error) => error, + } + } + + /// Locks every value the validate step itself derives from the minimal fixture — so a + /// validation regression fails here, with a validate-shaped message, instead of surfacing as + /// a confusing build-test failure downstream. + /// + /// The merged per-role-group config (resources, affinity, logging defaults, …) is produced by + /// `with_validated_config` and the config defaults, whose contracts are tested in operator-rs + /// and the properties tests; only the values this module derives on top are re-asserted here. + #[test] + fn validate_ok_derives_expected_values() { + let cluster = validate_yaml(DERBY_YAML).expect("the minimal fixture validates"); + + assert_eq!(cluster.name.to_string(), "simple-hive"); + assert_eq!(cluster.namespace.to_string(), "default"); + assert_eq!( + cluster.uid.to_string(), + "12345678-1234-1234-1234-123456789012" + ); + assert_eq!( + cluster.image.image, + format!("oci.example.org/hive:{}", app_version_label("4.0.0")) + ); + assert_eq!(cluster.image.product_version, "4.0.0"); + assert_eq!( + cluster.product_version.to_string(), + app_version_label("4.0.0") + ); + + // The role config falls back to its defaults: PDBs enabled, cluster-internal listener. + assert!(cluster.role_config.pdb.enabled); + assert_eq!(cluster.role_config.pdb.max_unavailable, None); + assert_eq!( + cluster.role_config.listener_class.to_string(), + "cluster-internal" + ); + + // The Derby metadata database: embedded driver (per product version), default on-disk + // location, no credentials. + let cluster_config = &cluster.cluster_config; + assert_eq!( + cluster_config.connection_driver, + "org.apache.derby.jdbc.EmbeddedDriver" + ); + assert_eq!(cluster_config.db_type, "derby"); + let details = &cluster_config.metadata_database_connection_details; + assert_eq!( + details.connection_url.to_string(), + "jdbc:derby:/tmp/derby/METADATA/derby.db;create=true" + ); + assert_eq!(details.username_env, None); + assert_eq!(details.password_env, None); + + // The minimal fixture configures no HDFS, S3, OPA or Kerberos. + assert_eq!(cluster_config.hdfs, None); + assert_eq!(cluster_config.s3_connection_spec, None); + assert!(cluster_config.hive_opa_config.is_none()); + assert_eq!(cluster_config.kerberos_secret_class, None); + + // A single metastore role with the single `default` role group. + assert_eq!(cluster.role_group_configs.len(), 1); + let role_groups = &cluster.role_group_configs[&HiveRole::MetaStore]; + let role_group_names: Vec = role_groups.keys().map(ToString::to_string).collect(); + assert_eq!(role_group_names, ["default"]); + let role_group = &role_groups[&RoleGroupName::from_str("default").expect("valid name")]; + assert_eq!(role_group.replicas, Some(1)); + assert_eq!(role_group.env_overrides, EnvVarSet::new()); + assert!(!role_group.config.logging.enable_vector_agent); + assert_eq!(role_group.config.logging.vector_container, None); + } + + #[test] + fn validate_rejects_invalid_role_group_name() { + // A copy of `DERBY_YAML` whose role-group name violates the RFC 1123 label rules + // (uppercase and underscore). + let yaml = r#" + apiVersion: hive.stackable.tech/v1alpha1 + kind: HiveCluster + metadata: + name: simple-hive + namespace: default + uid: 12345678-1234-1234-1234-123456789012 + spec: + image: + productVersion: "4.0.0" + clusterConfig: + metadataDatabase: + derby: {} + metastore: + roleGroups: + Invalid_RG: + replicas: 1 + "#; + + let error = expect_validate_err(yaml); + assert!( + matches!(&error, Error::ParseRoleGroupName { role_group, .. } if role_group == "Invalid_RG"), + "unexpected error: {error:?}" + ); + } + + #[test] + fn validate_rejects_invalid_env_var_override_name() { + // A copy of `DERBY_YAML` with an invalid `envOverrides` name: `EnvVarName` allows any + // printable ASCII except `=` (the Kubernetes rule), so a leading digit is fine; the + // embedded `=` is what gets rejected. + let yaml = r#" + apiVersion: hive.stackable.tech/v1alpha1 + kind: HiveCluster + metadata: + name: simple-hive + namespace: default + uid: 12345678-1234-1234-1234-123456789012 + spec: + image: + productVersion: "4.0.0" + clusterConfig: + metadataDatabase: + derby: {} + metastore: + roleGroups: + default: + replicas: 1 + envOverrides: + "BAD=NAME": value + "#; + + let error = expect_validate_err(yaml); + assert!( + matches!(&error, Error::ParseEnvVarName { role_group, .. } if role_group.as_ref() == "default"), + "unexpected error: {error:?}" + ); + } #[test] fn validate_logging_rejects_invalid_custom_config_map_name() { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 6d7a497b..20a9d837 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -38,6 +38,7 @@ use stackable_operator::{ kubernetes::{ ConfigMapName, ContainerName, ListenerClassName, SecretClassName, VolumeName, }, + operator::RoleName, }, }, versioned::versioned, @@ -143,8 +144,7 @@ pub mod versioned { pub image: ProductImage, // no doc - docs in Role struct. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub metastore: Option, + pub metastore: HiveRoleType, } // TODO: move generic version to op-rs? @@ -220,9 +220,9 @@ impl HasStatusCondition for v1alpha1::HiveCluster { } impl v1alpha1::HiveCluster { - pub fn role_config(&self, role: &HiveRole) -> Option<&HiveMetastoreRoleConfig> { + pub fn role_config(&self, role: &HiveRole) -> &HiveMetastoreRoleConfig { match role { - HiveRole::MetaStore => self.spec.metastore.as_ref().map(|m| &m.role_config), + HiveRole::MetaStore => &self.spec.metastore.role_config, } } @@ -261,6 +261,18 @@ pub enum HiveRole { MetaStore, } +impl From for RoleName { + fn from(value: HiveRole) -> Self { + RoleName::from_str(&value.to_string()).expect("a HiveRole is a valid role name") + } +} + +impl From<&HiveRole> for RoleName { + fn from(value: &HiveRole) -> Self { + RoleName::from_str(&value.to_string()).expect("a HiveRole is a valid role name") + } +} + impl HiveRole { /// A Kerberos principal has three parts, with the form username/fully.qualified.domain.name@YOUR-REALM.COM. /// We only have one role and will use "hive" everywhere (which e.g. differs from the current hdfs implementation).