From 517fe43d267d1a01f56fc4b8854033f07b21c567 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:55:38 +0200 Subject: [PATCH 01/50] feat: remove product-config & clean up --- Cargo.lock | 692 ++++--- Cargo.nix | 1694 +++++++---------- Cargo.toml | 6 +- crate-hashes.json | 9 - deploy/config-spec/properties.yaml | 48 +- .../configs/properties.yaml | 48 +- rust/operator-binary/Cargo.toml | 2 +- rust/operator-binary/src/config/mod.rs | 1 + rust/operator-binary/src/config/writer.rs | 46 + rust/operator-binary/src/connect/common.rs | 28 +- rust/operator-binary/src/connect/s3.rs | 2 +- rust/operator-binary/src/crd/constants.rs | 6 + rust/operator-binary/src/crd/history.rs | 49 +- rust/operator-binary/src/crd/mod.rs | 199 +- .../operator-binary/src/history/controller.rs | 200 +- .../src/history/controller/validate.rs | 14 +- rust/operator-binary/src/main.rs | 11 +- .../src/spark_k8s_controller.rs | 167 +- .../src/spark_k8s_controller/validate.rs | 15 +- 19 files changed, 1298 insertions(+), 1939 deletions(-) create mode 100644 rust/operator-binary/src/config/writer.rs diff --git a/Cargo.lock b/Cargo.lock index 6af9820b..9306281d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -62,15 +62,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -103,9 +103,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -163,15 +163,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -265,9 +265,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" [[package]] name = "block-buffer" @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" dependencies = [ "chrono", "git2", @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -302,9 +302,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,9 +320,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -365,15 +365,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concurrent-queue" @@ -392,11 +392,12 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -612,9 +613,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -675,9 +676,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -787,9 +788,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" @@ -923,9 +924,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" @@ -982,15 +983,14 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ "bitflags", "libc", "libgit2-sys", "log", - "url", ] [[package]] @@ -1024,9 +1024,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1052,6 +1052,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" @@ -1080,9 +1086,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1131,9 +1137,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.8.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1146,7 +1152,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1154,9 +1159,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", @@ -1164,7 +1169,6 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1232,12 +1236,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1245,9 +1250,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1258,9 +1263,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1272,15 +1277,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1292,15 +1297,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1330,9 +1335,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1340,12 +1345,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -1363,16 +1368,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1390,9 +1385,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "java-properties" @@ -1407,9 +1402,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.22" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1417,14 +1412,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.22" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1433,9 +1428,9 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" @@ -1458,24 +1453,27 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] name = "json-patch" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", + "schemars", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -1503,9 +1501,9 @@ dependencies = [ [[package]] name = "k8s-openapi" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a6d6f3611ad1d21732adbd7a2e921f598af6c92d71ae6e2620da4b67ee1f0d" +checksum = "51b326f5219dd55872a72c1b6ddd1b830b8334996c667449c29391d657d78d5e" dependencies = [ "base64", "jiff", @@ -1517,13 +1515,27 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "darling", "regex", - "snafu 0.9.0", + "snafu 0.9.1", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", ] +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kube" version = "3.1.0" @@ -1617,7 +1629,7 @@ dependencies = [ "backon", "educe", "futures 0.3.32", - "hashbrown", + "hashbrown 0.16.1", "hostname", "json-patch", "k8s-openapi", @@ -1643,15 +1655,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -1667,9 +1679,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.24" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -1679,9 +1691,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -1694,9 +1706,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "matchers" @@ -1715,9 +1727,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -1737,9 +1749,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1766,16 +1778,16 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1809,9 +1821,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1827,9 +1839,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -1841,9 +1853,9 @@ dependencies = [ [[package]] name = "opentelemetry-appender-tracing" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +checksum = "2c0080f0dc1d7c786f467cd85a4e395fcab11ee852004f39a29a18ab7c25d837" dependencies = [ "opentelemetry", "tracing", @@ -1853,9 +1865,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ "async-trait", "bytes", @@ -1866,9 +1878,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http", "opentelemetry", @@ -1880,14 +1892,14 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tonic", - "tracing", + "tonic-types", ] [[package]] name = "opentelemetry-proto" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -1898,22 +1910,23 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" +checksum = "6ca2f98a0437b427b4b08f19f1caa3c44db885a202bc12cfea13d6c702243d68" [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.2", + "portable-atomic", + "rand 0.9.4", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -2039,18 +2052,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -2063,12 +2076,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs1" version = "0.7.5" @@ -2092,9 +2099,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -2104,18 +2111,18 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2146,9 +2153,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -2201,11 +2208,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2218,9 +2234,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -2228,9 +2244,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2340,9 +2356,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -2358,9 +2374,6 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", @@ -2457,9 +2470,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -2472,9 +2485,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2484,9 +2497,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -2516,9 +2529,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -2603,9 +2616,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2660,9 +2673,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2740,9 +2753,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -2766,9 +2779,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -2803,11 +2816,11 @@ dependencies = [ [[package]] name = "snafu" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d4bced6a69f90b2056c03dcff2c4737f98d6fb9e0853493996e1d253ca29c6" +checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522" dependencies = [ - "snafu-derive 0.9.0", + "snafu-derive 0.9.1", ] [[package]] @@ -2835,9 +2848,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54254b8531cafa275c5e096f62d48c81435d1015405a91198ddb11e967301d40" +checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" dependencies = [ "heck", "proc-macro2", @@ -2847,12 +2860,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2880,19 +2893,18 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "const-oid", "ecdsa", "k8s-openapi", "kube", "p256", - "rand 0.9.2", + "rand 0.9.4", "rand_core 0.6.4", "rsa", "sha2", "signature", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-shared", "tokio", "tokio-rustls", @@ -2904,7 +2916,6 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "base64", "clap", @@ -2921,14 +2932,14 @@ dependencies = [ "k8s-openapi", "kube", "product-config", - "rand 0.9.2", + "rand 0.9.4", "regex", "schemars", "semver", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", "stackable-telemetry", @@ -2940,12 +2951,12 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "url", + "uuid", ] [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "darling", "proc-macro2", @@ -2955,8 +2966,7 @@ dependencies = [ [[package]] name = "stackable-shared" -version = "0.1.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +version = "0.1.1" dependencies = [ "jiff", "k8s-openapi", @@ -2965,7 +2975,7 @@ dependencies = [ "semver", "serde", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "time", ] @@ -2980,14 +2990,14 @@ dependencies = [ "const_format", "futures 0.3.32", "indoc", - "product-config", + "java-properties", "regex", "rstest", "semver", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator", "strum", "tokio", @@ -2997,8 +3007,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" -version = "0.6.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +version = "0.6.4" dependencies = [ "axum", "clap", @@ -3009,7 +3018,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "tokio", "tower", @@ -3022,21 +3031,19 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "kube", "schemars", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-versioned-macros", ] [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "convert_case", "convert_case_extras", @@ -3054,7 +3061,6 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" dependencies = [ "arc-swap", "async-trait", @@ -3067,10 +3073,10 @@ dependencies = [ "kube", "opentelemetry", "opentelemetry-semantic-conventions", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_json", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-certs", "stackable-shared", "stackable-telemetry", @@ -3116,6 +3122,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -3240,9 +3252,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3271,9 +3283,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3288,9 +3300,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3334,18 +3346,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime", @@ -3355,18 +3367,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "base64", @@ -3391,15 +3403,26 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", "tonic", ] +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -3421,9 +3444,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "base64", "bitflags", @@ -3431,13 +3454,13 @@ dependencies = [ "futures-util", "http", "http-body", - "iri-string", "mime", "pin-project-lite", "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3466,11 +3489,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber", @@ -3522,9 +3546,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -3548,9 +3572,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -3575,9 +3599,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -3593,9 +3617,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3640,6 +3664,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3675,18 +3709,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3697,23 +3731,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3721,9 +3751,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3734,18 +3764,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3826,16 +3856,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -3853,31 +3874,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -3886,116 +3890,68 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-cert" @@ -4013,15 +3969,15 @@ dependencies = [ [[package]] name = "xml" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" +checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4030,9 +3986,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4042,18 +3998,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", @@ -4062,18 +4018,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4103,9 +4059,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4114,9 +4070,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4125,9 +4081,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.nix b/Cargo.nix index b7cf9ecb..5edb7bb9 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -216,9 +216,9 @@ rec { }; "anstream" = rec { crateName = "anstream"; - version = "0.6.21"; + version = "1.0.0"; edition = "2021"; - sha256 = "0jjgixms4qjj58dzr846h2s29p8w7ynwr9b9x6246m1pwy0v5ma3"; + sha256 = "13d2bj0xfg012s4rmq44zc8zgy1q8k9yp7yhvfnarscnmwpj2jl2"; dependencies = [ { name = "anstyle"; @@ -261,9 +261,9 @@ rec { }; "anstyle" = rec { crateName = "anstyle"; - version = "1.0.13"; + version = "1.0.14"; edition = "2021"; - sha256 = "0y2ynjqajpny6q0amvfzzgw0gfw3l47z85km4gvx87vg02lcr4ji"; + sha256 = "0030szmgj51fxkic1hpakxxgappxzwm6m154a3gfml83lq63l2wl"; features = { "default" = [ "std" ]; }; @@ -271,9 +271,9 @@ rec { }; "anstyle-parse" = rec { crateName = "anstyle-parse"; - version = "0.2.7"; + version = "1.0.0"; edition = "2021"; - sha256 = "1hhmkkfr95d462b3zf6yl2vfzdqfy5726ya572wwg8ha9y148xjf"; + sha256 = "03hkv2690s0crssbnmfkr76kw1k7ah2i6s5amdy9yca2n8w7zkjj"; libName = "anstyle_parse"; dependencies = [ { @@ -345,9 +345,9 @@ rec { }; "arc-swap" = rec { crateName = "arc-swap"; - version = "1.8.2"; + version = "1.9.1"; edition = "2018"; - sha256 = "19aas8y3kz0v6jr6yijvw6cad9grpl3lw1a25k0cws2m2iy69wzr"; + sha256 = "01xjlahcya8igdalxmda375lnlhjqwjz0cdqhy0bc1jkyzb1yfka"; libName = "arc_swap"; authors = [ "Michal 'vorner' Vaner " @@ -489,9 +489,9 @@ rec { }; "autocfg" = rec { crateName = "autocfg"; - version = "1.5.0"; + version = "1.5.1"; edition = "2015"; - sha256 = "1s77f98id9l4af4alklmzq46f21c980v13z2r1pcxx6bqgw0d1n0"; + sha256 = "0lqasy5i30flcgih1b50kvsk6z32g09r1q4ql7q81pj6228jy0zj"; authors = [ "Josh Stone " ]; @@ -499,9 +499,9 @@ rec { }; "axum" = rec { crateName = "axum"; - version = "0.8.8"; + version = "0.8.9"; edition = "2021"; - sha256 = "1f4p0m04mgwpn8b40i9r5mgqxk6w11sv4yri6xfqk305nhyayllb"; + sha256 = "146df5x8dhczm1sp939gr3839220wl6rxc1k65bzc450z72ridii"; dependencies = [ { name = "axum-core"; @@ -866,9 +866,9 @@ rec { }; "bitflags" = rec { crateName = "bitflags"; - version = "2.11.0"; + version = "2.12.1"; edition = "2021"; - sha256 = "1bwjibwry5nfwsfm9kjg2dqx5n5nja9xymwbfl6svnn8jsz6ff44"; + sha256 = "02phhjm7w380zdh8928zf13cfi1bw2qz2ay36ml2jmwmmv8cxmw4"; authors = [ "The Rust Project Developers" ]; @@ -898,9 +898,9 @@ rec { }; "built" = rec { crateName = "built"; - version = "0.8.0"; - edition = "2021"; - sha256 = "0r5f08lpjsr6j5ajkbmd0ymfmajpq8ddbfvi8ji8rx48y88qzbgl"; + version = "0.8.1"; + edition = "2024"; + sha256 = "1saq332pd6g3svvc9ah8myjpfvgqlzl2ksb1ypp3976kjcfm63jw"; authors = [ "Lukas Lueg " ]; @@ -924,15 +924,16 @@ rec { "chrono" = [ "dep:chrono" ]; "dependency-tree" = [ "cargo-lock/dependency-tree" ]; "git2" = [ "dep:git2" ]; + "gix" = [ "dep:gix" ]; "semver" = [ "dep:semver" ]; }; resolvedDefaultFeatures = [ "chrono" "git2" ]; }; "bumpalo" = rec { crateName = "bumpalo"; - version = "3.20.2"; + version = "3.20.3"; edition = "2021"; - sha256 = "1jrgxlff76k9glam0akhwpil2fr1w32gbjdf5hpipc7ld2c7h82x"; + sha256 = "0jc6va3nwcqikm7chnpdv1s87my3gs2j7g1sc7g3k91brg3arxbj"; authors = [ "Nick Fitzgerald " ]; @@ -961,9 +962,9 @@ rec { }; "cc" = rec { crateName = "cc"; - version = "1.2.56"; + version = "1.2.63"; edition = "2018"; - sha256 = "1chvh9g2izhqad7vzy4cc7xpdljdvqpsr6x6hv1hmyqv3mlkbgxf"; + sha256 = "0zy2bqc4nvj6bv2cipx4h4bn65wf1zqf1fw1hsh64mmvg1hh2vjm"; authors = [ "Alex Crichton " ]; @@ -1011,9 +1012,9 @@ rec { }; "chrono" = rec { crateName = "chrono"; - version = "0.4.44"; + version = "0.4.45"; edition = "2021"; - sha256 = "1c64mk9a235271j5g3v4zrzqqmd43vp9vki7vqfllpqf5rd0fwy6"; + sha256 = "09rkcgk6is2sdhqs9142zv8xqnj8ryx8m9hknllqwyv9wxi9x9qs"; dependencies = [ { name = "iana-time-zone"; @@ -1060,10 +1061,10 @@ rec { }; "clap" = rec { crateName = "clap"; - version = "4.5.60"; - edition = "2021"; + version = "4.6.1"; + edition = "2024"; crateBin = []; - sha256 = "02h3nzznssjgp815nnbzk0r62y2iw03kdli75c233kirld6z75r7"; + sha256 = "0lcf88l7vlg796rrqr7wipbbmfa5sgsgx4211b7xmxxv8dz13nqx"; dependencies = [ { name = "clap_builder"; @@ -1102,9 +1103,9 @@ rec { }; "clap_builder" = rec { crateName = "clap_builder"; - version = "4.5.60"; - edition = "2021"; - sha256 = "0xk8mdizvmmn6w5ij5cwhy5pbgyac4w9pfvl6nqmjl7a5hql38i4"; + version = "4.6.0"; + edition = "2024"; + sha256 = "17q6np22yxhh5y5v53y4l31ps3hlaz45mvz2n2nicr7n3c056jki"; dependencies = [ { name = "anstream"; @@ -1141,9 +1142,9 @@ rec { }; "clap_derive" = rec { crateName = "clap_derive"; - version = "4.5.55"; - edition = "2021"; - sha256 = "1r949xis3jmhzh387smd70vc8a3b9734ck3g5ahg59a63bd969x9"; + version = "4.6.1"; + edition = "2024"; + sha256 = "1acpz49hi00iv9jkapixjzcv7s51x8qkfaqscjm36rqgf428dkpj"; procMacro = true; dependencies = [ { @@ -1173,16 +1174,16 @@ rec { }; "clap_lex" = rec { crateName = "clap_lex"; - version = "1.0.0"; - edition = "2021"; - sha256 = "0c8888qi1l9sayqlv666h8s0yxn2qc6jr88v1zagk43mpjjjx0is"; + version = "1.1.0"; + edition = "2024"; + sha256 = "1ycqkpygnlqnndghhcxjb44lzl0nmgsia64x9581030yifxs7m68"; }; "colorchoice" = rec { crateName = "colorchoice"; - version = "1.0.4"; + version = "1.0.5"; edition = "2021"; - sha256 = "0x8ymkz1xr77rcj1cfanhf416pc4v681gmkc9dzb3jqja7f62nxh"; + sha256 = "0w75k89hw39p0mnnhlrwr23q50rza1yjki44qvh2mgrnj065a1qx"; }; "concurrent-queue" = rec { @@ -1226,9 +1227,9 @@ rec { }; "const_format" = rec { crateName = "const_format"; - version = "0.2.35"; + version = "0.2.36"; edition = "2021"; - sha256 = "1b9h03z3k76ail1ldqxcqmsc4raa7dwgwwqwrjf6wmism5lp9akz"; + sha256 = "07ncczs8yndga2f8p4386c827l4fxwzl0pbwp7ijnhcsmlbsd0a4"; authors = [ "rodrimati1992 " ]; @@ -1237,6 +1238,12 @@ rec { name = "const_format_proc_macros"; packageId = "const_format_proc_macros"; } + { + name = "konst"; + packageId = "konst"; + usesDefaultFeatures = false; + features = [ "rust_1_64" ]; + } ]; features = { "__debug" = [ "const_format_proc_macros/debug" ]; @@ -1250,10 +1257,9 @@ rec { "constant_time_as_str" = [ "fmt" ]; "derive" = [ "fmt" "const_format_proc_macros/derive" ]; "fmt" = [ "rust_1_83" ]; - "konst" = [ "dep:konst" ]; "more_str_macros" = [ "rust_1_64" ]; "nightly_const_generics" = [ "const_generics" ]; - "rust_1_64" = [ "rust_1_51" "konst" "konst/rust_1_64" ]; + "rust_1_64" = [ "rust_1_51" ]; "rust_1_83" = [ "rust_1_64" ]; }; resolvedDefaultFeatures = [ "default" ]; @@ -1901,9 +1907,9 @@ rec { }; "displaydoc" = rec { crateName = "displaydoc"; - version = "0.2.5"; + version = "0.2.6"; edition = "2021"; - sha256 = "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp"; + sha256 = "0kyxwfbdmagd8afzb2pzja7wj8dhah7smxdsgw00iq8pa2jhmiqs"; procMacro = true; authors = [ "Jane Lusby " @@ -2103,12 +2109,9 @@ rec { }; "either" = rec { crateName = "either"; - version = "1.15.0"; + version = "1.16.0"; edition = "2021"; - sha256 = "069p1fknsmzn9llaizh77kip0pqmcwpdsykv2x30xpjyija5gis8"; - authors = [ - "bluss" - ]; + sha256 = "17k7jfbdz7k440h6lws9baz8p9zlxgb41sig3w81h80nwzsjyqli"; features = { "default" = [ "std" ]; "serde" = [ "dep:serde" ]; @@ -2452,9 +2455,9 @@ rec { }; "fastrand" = rec { crateName = "fastrand"; - version = "2.3.0"; + version = "2.4.1"; edition = "2018"; - sha256 = "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p"; + sha256 = "1mnqxxnxvd69ma9mczabpbbsgwlhd6l78yv3vd681453a9s247wz"; authors = [ "Stjepan Glavina " ]; @@ -2825,9 +2828,9 @@ rec { }; "futures-timer" = rec { crateName = "futures-timer"; - version = "3.0.3"; + version = "3.0.4"; edition = "2018"; - sha256 = "094vw8k37djpbwv74bwf2qb7n6v6ghif4myss6smd6hgyajb127j"; + sha256 = "0s39in8ivw7g4d37pf31q02y44zd1hpfkd1pgra2slcqibdzlhxg"; libName = "futures_timer"; authors = [ "Alex Crichton " @@ -3085,9 +3088,9 @@ rec { }; "git2" = rec { crateName = "git2"; - version = "0.20.4"; - edition = "2018"; - sha256 = "0azykjpk3j6s354z23jkyq3r3pbmlw9ha1zsxkw5cnnpi1h2b23v"; + version = "0.21.0"; + edition = "2021"; + sha256 = "0bmqga9vlyx5sdlr0i28z0362s89xv9i4qcv20vvx9j54y9vzpfx"; authors = [ "Josh Triplett " "Alex Crichton " @@ -3109,17 +3112,14 @@ rec { name = "log"; packageId = "log"; } - { - name = "url"; - packageId = "url"; - } ]; features = { - "default" = [ "ssh" "https" ]; - "https" = [ "libgit2-sys/https" "openssl-sys" "openssl-probe" ]; + "cred" = [ "dep:url" ]; + "https" = [ "libgit2-sys/https" "openssl-sys" "openssl-probe" "cred" ]; "openssl-probe" = [ "dep:openssl-probe" ]; "openssl-sys" = [ "dep:openssl-sys" ]; - "ssh" = [ "libgit2-sys/ssh" ]; + "ssh" = [ "libgit2-sys/ssh" "cred" ]; + "unstable-sha256" = [ "libgit2-sys/unstable-sha256" ]; "vendored-libgit2" = [ "libgit2-sys/vendored" ]; "vendored-openssl" = [ "openssl-sys/vendored" "libgit2-sys/vendored-openssl" ]; "zlib-ng-compat" = [ "libgit2-sys/zlib-ng-compat" ]; @@ -3209,9 +3209,9 @@ rec { }; "h2" = rec { crateName = "h2"; - version = "0.4.13"; + version = "0.4.14"; edition = "2021"; - sha256 = "0m6w5gg0n0m1m5915bxrv8n4rlazhx5icknkslz719jhh4xdli1g"; + sha256 = "0cw7jk7kn2vn6f8w8ssh6gis1mljnfjxd606gvi4sjpyjayfy7qp"; authors = [ "Carl Lerche " "Sean McArthur " @@ -3279,7 +3279,7 @@ rec { features = { }; }; - "hashbrown" = rec { + "hashbrown 0.16.1" = rec { crateName = "hashbrown"; version = "0.16.1"; edition = "2021"; @@ -3322,6 +3322,24 @@ rec { }; resolvedDefaultFeatures = [ "allocator-api2" "default" "default-hasher" "equivalent" "inline-more" "raw-entry" ]; }; + "hashbrown 0.17.1" = rec { + crateName = "hashbrown"; + version = "0.17.1"; + edition = "2024"; + sha256 = "0jmqz7i4yl6cm7rbn0i2ffkfrmwi6xkmzkaldr2v8bcsx2v0jngd"; + features = { + "alloc" = [ "dep:alloc" ]; + "allocator-api2" = [ "dep:allocator-api2" ]; + "core" = [ "dep:core" ]; + "default" = [ "default-hasher" "inline-more" "allocator-api2" "equivalent" "raw-entry" ]; + "default-hasher" = [ "dep:foldhash" ]; + "equivalent" = [ "dep:equivalent" ]; + "nightly" = [ "foldhash?/nightly" "bumpalo/allocator_api" ]; + "rayon" = [ "dep:rayon" ]; + "rustc-dep-of-std" = [ "nightly" "core" "alloc" "rustc-internal-api" ]; + "serde" = [ "dep:serde_core" "dep:serde" ]; + }; + }; "heck" = rec { crateName = "heck"; version = "0.5.0"; @@ -3383,9 +3401,9 @@ rec { }; "http" = rec { crateName = "http"; - version = "1.4.0"; + version = "1.4.1"; edition = "2021"; - sha256 = "06iind4cwsj1d6q8c2xgq8i2wka4ps74kmws24gsi1bzdlw2mfp3"; + sha256 = "1l7k2ia57z3q7q3ka497krzps795kd3fymm2k12lr623y4nldrwb"; authors = [ "Alex Crichton " "Carl Lerche " @@ -3502,9 +3520,9 @@ rec { }; "hyper" = rec { crateName = "hyper"; - version = "1.8.1"; + version = "1.10.1"; edition = "2021"; - sha256 = "04cxr8j5y86bhxxlyqb8xkxjskpajk7cxwfzzk4v3my3a3rd9cia"; + sha256 = "1624nwrh1ci34psqcl3q8q266kha8kd6fmqjj14qck49l59iqa2m"; authors = [ "Sean McArthur " ]; @@ -3561,11 +3579,6 @@ rec { packageId = "pin-project-lite"; optional = true; } - { - name = "pin-utils"; - packageId = "pin-utils"; - optional = true; - } { name = "smallvec"; packageId = "smallvec"; @@ -3603,7 +3616,7 @@ rec { "client" = [ "dep:want" "dep:pin-project-lite" "dep:smallvec" ]; "ffi" = [ "dep:http-body-util" "dep:futures-util" ]; "full" = [ "client" "http1" "http2" "server" ]; - "http1" = [ "dep:atomic-waker" "dep:futures-channel" "dep:futures-core" "dep:httparse" "dep:itoa" "dep:pin-utils" ]; + "http1" = [ "dep:atomic-waker" "dep:futures-channel" "dep:futures-core" "dep:httparse" "dep:itoa" ]; "http2" = [ "dep:futures-channel" "dep:futures-core" "dep:h2" ]; "server" = [ "dep:httpdate" "dep:pin-project-lite" "dep:smallvec" ]; "tracing" = [ "dep:tracing" ]; @@ -3612,9 +3625,9 @@ rec { }; "hyper-rustls" = rec { crateName = "hyper-rustls"; - version = "0.27.7"; + version = "0.27.9"; edition = "2021"; - sha256 = "0n6g8998szbzhnvcs1b7ibn745grxiqmlpg53xz206v826v3xjg3"; + sha256 = "03vfnsm873wsp1dk0q85nxvk7w6syp8c2m5bcdjcyfgg4786ijik"; libName = "hyper_rustls"; dependencies = [ { @@ -3647,11 +3660,6 @@ rec { packageId = "rustls-native-certs"; optional = true; } - { - name = "rustls-pki-types"; - packageId = "rustls-pki-types"; - rename = "pki-types"; - } { name = "tokio"; packageId = "tokio"; @@ -3951,9 +3959,9 @@ rec { }; "icu_collections" = rec { crateName = "icu_collections"; - version = "2.1.1"; + version = "2.2.0"; edition = "2021"; - sha256 = "0hsblchsdl64q21qwrs4hvc2672jrf466zivbj1bwyv606bn8ssc"; + sha256 = "070r7xd0pynm0hnc1v2jzlbxka6wf50f81wybf9xg0y82v6x3119"; authors = [ "The ICU4X Project Developers" ]; @@ -3969,6 +3977,11 @@ rec { usesDefaultFeatures = false; features = [ "zerovec" ]; } + { + name = "utf8_iter"; + packageId = "utf8_iter"; + usesDefaultFeatures = false; + } { name = "yoke"; packageId = "yoke"; @@ -3996,9 +4009,9 @@ rec { }; "icu_locale_core" = rec { crateName = "icu_locale_core"; - version = "2.1.1"; + version = "2.2.0"; edition = "2021"; - sha256 = "1djvdc2f5ylmp1ymzv4gcnmq1s4hqfim9nxlcm173lsd01hpifpd"; + sha256 = "0a9cmin5w1x3bg941dlmgszn33qgq428k7qiqn5did72ndi9n8cj"; authors = [ "The ICU4X Project Developers" ]; @@ -4030,6 +4043,14 @@ rec { usesDefaultFeatures = false; } ]; + devDependencies = [ + { + name = "litemap"; + packageId = "litemap"; + usesDefaultFeatures = false; + features = [ "testing" ]; + } + ]; features = { "alloc" = [ "litemap/alloc" "tinystr/alloc" "writeable/alloc" "serde?/alloc" ]; "databake" = [ "dep:databake" "alloc" ]; @@ -4040,9 +4061,9 @@ rec { }; "icu_normalizer" = rec { crateName = "icu_normalizer"; - version = "2.1.1"; + version = "2.2.0"; edition = "2021"; - sha256 = "16dmn5596la2qm0r3vih0bzjfi0vx9a20yqjha6r1y3vnql8hv2z"; + sha256 = "1d7krxr0xpc4x9635k1100a24nh0nrc59n65j6yk6gbfkplmwvn5"; authors = [ "The ICU4X Project Developers" ]; @@ -4084,6 +4105,7 @@ rec { "compiled_data" = [ "dep:icu_normalizer_data" "icu_properties?/compiled_data" "icu_provider/baked" ]; "datagen" = [ "serde" "dep:databake" "icu_properties" "icu_collections/databake" "zerovec/databake" "icu_properties?/datagen" "icu_provider/export" ]; "default" = [ "compiled_data" "utf8_iter" "utf16_iter" ]; + "harfbuzz_traits" = [ "dep:harfbuzz-traits" ]; "icu_properties" = [ "dep:icu_properties" ]; "serde" = [ "dep:serde" "icu_collections/serde" "zerovec/serde" "icu_properties?/serde" "icu_provider/serde" ]; "utf16_iter" = [ "dep:utf16_iter" "dep:write16" ]; @@ -4093,9 +4115,9 @@ rec { }; "icu_normalizer_data" = rec { crateName = "icu_normalizer_data"; - version = "2.1.1"; + version = "2.2.0"; edition = "2021"; - sha256 = "02jnzizg6q75m41l6c13xc7nkc5q8yr1b728dcgfhpzw076wrvbs"; + sha256 = "0f5d5d5fhhr9937m2z6z38fzh6agf14z24kwlr6lyczafypf0fys"; authors = [ "The ICU4X Project Developers" ]; @@ -4103,9 +4125,9 @@ rec { }; "icu_properties" = rec { crateName = "icu_properties"; - version = "2.1.2"; + version = "2.2.0"; edition = "2021"; - sha256 = "1v3lbmhhi7i6jgw51ikjb1p50qh5rb67grlkdnkc63l7zq1gq2q2"; + sha256 = "1pkh3s837808cbwxvfagwc28cvwrz2d9h5rl02jwrhm51ryvdqxy"; authors = [ "The ICU4X Project Developers" ]; @@ -4150,6 +4172,7 @@ rec { "compiled_data" = [ "dep:icu_properties_data" "icu_provider/baked" ]; "datagen" = [ "serde" "dep:databake" "zerovec/databake" "icu_collections/databake" "icu_locale_core/databake" "zerotrie/databake" "icu_provider/export" ]; "default" = [ "compiled_data" ]; + "harfbuzz_traits" = [ "dep:harfbuzz-traits" ]; "serde" = [ "dep:serde" "icu_locale_core/serde" "zerovec/serde" "icu_collections/serde" "icu_provider/serde" "zerotrie/serde" ]; "unicode_bidi" = [ "dep:unicode-bidi" ]; }; @@ -4157,9 +4180,9 @@ rec { }; "icu_properties_data" = rec { crateName = "icu_properties_data"; - version = "2.1.2"; + version = "2.2.0"; edition = "2021"; - sha256 = "1bvpkh939rgzrjfdb7hz47v4wijngk0snmcgrnpwc9fpz162jv31"; + sha256 = "052awny0qwkbcbpd5jg2cd7vl5ry26pq4hz1nfsgf10c3qhbnawf"; authors = [ "The ICU4X Project Developers" ]; @@ -4167,9 +4190,9 @@ rec { }; "icu_provider" = rec { crateName = "icu_provider"; - version = "2.1.1"; + version = "2.2.0"; edition = "2021"; - sha256 = "0576b7dizgyhpfa74kacv86y4g1p7v5ffd6c56kf1q82rvq2r5l5"; + sha256 = "08dl8pxbwr8zsz4c5vphqb7xw0hykkznwi4rw7bk6pwb3krlr70k"; authors = [ "The ICU4X Project Developers" ]; @@ -4270,9 +4293,9 @@ rec { }; "idna_adapter" = rec { crateName = "idna_adapter"; - version = "1.2.1"; - edition = "2021"; - sha256 = "0i0339pxig6mv786nkqcxnwqa87v4m94b2653f6k3aj0jmhfkjis"; + version = "1.2.2"; + edition = "2024"; + sha256 = "0557p76l8hj35r9zn1yv7c6x1c0qbrsffmg80n0yy8361ly3fs6b"; authors = [ "The rust-url developers" ]; @@ -4295,9 +4318,9 @@ rec { }; "indexmap" = rec { crateName = "indexmap"; - version = "2.13.0"; - edition = "2021"; - sha256 = "05qh5c4h2hrnyypphxpwflk45syqbzvqsvvyxg43mp576w2ff53p"; + version = "2.14.0"; + edition = "2024"; + sha256 = "1na9z6f0d5pkjr1lgsni470v98gv2r7c41j8w48skr089x2yjrnl"; dependencies = [ { name = "equivalent"; @@ -4306,7 +4329,7 @@ rec { } { name = "hashbrown"; - packageId = "hashbrown"; + packageId = "hashbrown 0.17.1"; usesDefaultFeatures = false; } ]; @@ -4364,39 +4387,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "iri-string" = rec { - crateName = "iri-string"; - version = "0.7.10"; - edition = "2021"; - sha256 = "06kk3a5jz576p7vrpf7zz9jv3lrgcyp7pczcblcxdnryg3q3h4y9"; - libName = "iri_string"; - authors = [ - "YOSHIOKA Takuma " - ]; - dependencies = [ - { - name = "memchr"; - packageId = "memchr"; - optional = true; - usesDefaultFeatures = false; - } - { - name = "serde"; - packageId = "serde"; - optional = true; - usesDefaultFeatures = false; - features = [ "derive" ]; - } - ]; - features = { - "alloc" = [ "serde?/alloc" ]; - "default" = [ "std" ]; - "memchr" = [ "dep:memchr" ]; - "serde" = [ "dep:serde" ]; - "std" = [ "alloc" "memchr?/std" "serde?/std" ]; - }; - resolvedDefaultFeatures = [ "alloc" "default" "std" ]; - }; "is_terminal_polyfill" = rec { crateName = "is_terminal_polyfill"; version = "1.70.2"; @@ -4429,9 +4419,9 @@ rec { }; "itoa" = rec { crateName = "itoa"; - version = "1.0.17"; + version = "1.0.18"; edition = "2021"; - sha256 = "1lh93xydrdn1g9x547bd05g0d3hra7pd1k4jfd2z1pl1h5hwdv4j"; + sha256 = "10jnd1vpfkb8kj38rlkn2a6k02afvj3qmw054dfpzagrpl6achlg"; authors = [ "David Tolnay " ]; @@ -4466,9 +4456,9 @@ rec { }; "jiff" = rec { crateName = "jiff"; - version = "0.2.22"; + version = "0.2.28"; edition = "2021"; - sha256 = "1hni7qv2j2kbjisw84r7y6gxdb8qx534vw92nmz13nc7gjy496w1"; + sha256 = "00lixngcc7amh2fcsxfr0z38j06lllhapz192biv1qj97q1x60s6"; authors = [ "Andrew Gallant " ]; @@ -4514,12 +4504,10 @@ rec { usesDefaultFeatures = false; } { - name = "windows-sys"; - packageId = "windows-sys 0.61.2"; + name = "windows-link"; + packageId = "windows-link"; optional = true; - usesDefaultFeatures = false; target = { target, features }: (target."windows" or false); - features = [ "Win32_Foundation" "Win32_System_Time" ]; } ]; devDependencies = [ @@ -4538,7 +4526,7 @@ rec { "static-tz" = [ "dep:jiff-static" ]; "std" = [ "alloc" "log?/std" "serde_core?/std" ]; "tz-fat" = [ "jiff-static?/tz-fat" ]; - "tz-system" = [ "std" "dep:windows-sys" ]; + "tz-system" = [ "std" "dep:windows-link" ]; "tzdb-bundle-always" = [ "dep:jiff-tzdb" "alloc" ]; "tzdb-bundle-platform" = [ "dep:jiff-tzdb-platform" "alloc" ]; "tzdb-concatenated" = [ "std" ]; @@ -4548,9 +4536,9 @@ rec { }; "jiff-static" = rec { crateName = "jiff-static"; - version = "0.2.22"; + version = "0.2.28"; edition = "2021"; - sha256 = "18fljj75vxqvq1v55s074pbbrjy67qg1p2f0cvbmzhzc33dm40j7"; + sha256 = "0irbhfh2f4i9w5l53jcmh6ssnhdd92wfy76978chgwnxilvk4bbq"; procMacro = true; libName = "jiff_static"; authors = [ @@ -4577,9 +4565,9 @@ rec { }; "jiff-tzdb" = rec { crateName = "jiff-tzdb"; - version = "0.1.5"; + version = "0.1.6"; edition = "2021"; - sha256 = "1hm5xn3q092zac6apjy4492ddid473mwa0d64z5f5f95yyzix5v8"; + sha256 = "0xihzlnnyk0xnrzpq4xcyjdcmy8xc3ychzb9ayjkh4vgha2fy069"; libName = "jiff_tzdb"; libPath = "lib.rs"; authors = [ @@ -4630,14 +4618,25 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.91"; + version = "0.3.99"; edition = "2021"; - sha256 = "171rzgq33wc1nxkgnvhlqqwwnrifs13mg3jjpjj5nf1z0yvib5xl"; + sha256 = "04azrzsz91gr5s3z0ij36lz0kj9ry4lw3jz0mmbiwb251rsc8aql"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" ]; dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "futures-util"; + packageId = "futures-util"; + optional = true; + usesDefaultFeatures = false; + features = [ "std" ]; + } { name = "once_cell"; packageId = "once_cell"; @@ -4651,15 +4650,16 @@ rec { ]; features = { "default" = [ "std" "unsafe-eval" ]; - "std" = [ "wasm-bindgen/std" ]; + "futures-core-03-stream" = [ "dep:futures-util" "dep:futures-core" ]; + "std" = [ "wasm-bindgen/std" "dep:futures-util" ]; }; resolvedDefaultFeatures = [ "default" "std" "unsafe-eval" ]; }; "json-patch" = rec { crateName = "json-patch"; - version = "4.1.0"; + version = "4.2.0"; edition = "2021"; - sha256 = "147yaxmv3i4s0bdna86rgwpmqh2507fn4ighfpplaiqkw8ay807k"; + sha256 = "0wkv896d0pzq56i2kkl0giqpv117fwvhbpgs8iz85805w66l68bl"; libName = "json_patch"; authors = [ "Ivan Dubrov " @@ -4669,6 +4669,11 @@ rec { name = "jsonptr"; packageId = "jsonptr"; } + { + name = "schemars"; + packageId = "schemars"; + optional = true; + } { name = "serde"; packageId = "serde"; @@ -4680,10 +4685,14 @@ rec { } { name = "thiserror"; - packageId = "thiserror 1.0.69"; + packageId = "thiserror 2.0.18"; } ]; devDependencies = [ + { + name = "schemars"; + packageId = "schemars"; + } { name = "serde_json"; packageId = "serde_json"; @@ -4695,7 +4704,7 @@ rec { "schemars" = [ "dep:schemars" ]; "utoipa" = [ "dep:utoipa" ]; }; - resolvedDefaultFeatures = [ "default" "diff" ]; + resolvedDefaultFeatures = [ "default" "diff" "schemars" ]; }; "jsonpath-rust" = rec { crateName = "jsonpath-rust"; @@ -4768,10 +4777,10 @@ rec { }; "k8s-openapi" = rec { crateName = "k8s-openapi"; - version = "0.27.0"; + version = "0.27.1"; edition = "2021"; - links = "k8s-openapi-0.27.0"; - sha256 = "038zxrklpni04rpaww9dr7v8ln8zj8p7mgdd68bx5l8sc7rxd9h5"; + links = "k8s-openapi-0.27.1"; + sha256 = "0pldsxbxd4ckq94p8rkck4s862w33gfns6rclxr5imcx47sjdcsi"; libName = "k8s_openapi"; authors = [ "Arnav Singh " @@ -4819,12 +4828,7 @@ rec { crateName = "k8s-version"; version = "0.1.3"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/k8s-version; }; libName = "k8s_version"; authors = [ "Stackable GmbH " @@ -4841,7 +4845,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } ]; features = { @@ -4850,6 +4854,53 @@ rec { }; resolvedDefaultFeatures = [ "darling" ]; }; + "konst" = rec { + crateName = "konst"; + version = "0.2.20"; + edition = "2018"; + sha256 = "1yyf1fhk28wbf1lqrga9as4cpfmpbry9a5vvdqyxgz14g3nk708j"; + authors = [ + "rodrimati1992 " + ]; + dependencies = [ + { + name = "konst_macro_rules"; + packageId = "konst_macro_rules"; + } + ]; + features = { + "__ui" = [ "__test" "trybuild" "rust_latest_stable" ]; + "const_generics" = [ "rust_1_51" ]; + "constant_time_slice" = [ "rust_latest_stable" ]; + "default" = [ "cmp" "parsing" ]; + "deref_raw_in_fn" = [ "rust_1_56" ]; + "konst_proc_macros" = [ "dep:konst_proc_macros" ]; + "mut_refs" = [ "rust_latest_stable" "konst_macro_rules/mut_refs" ]; + "nightly_mut_refs" = [ "mut_refs" "konst_macro_rules/nightly_mut_refs" ]; + "parsing" = [ "parsing_no_proc" "konst_proc_macros" ]; + "rust_1_51" = [ "konst_macro_rules/rust_1_51" ]; + "rust_1_55" = [ "rust_1_51" "konst_macro_rules/rust_1_55" ]; + "rust_1_56" = [ "rust_1_55" "konst_macro_rules/rust_1_56" ]; + "rust_1_57" = [ "rust_1_56" "konst_macro_rules/rust_1_57" ]; + "rust_1_61" = [ "rust_1_57" "konst_macro_rules/rust_1_61" ]; + "rust_1_64" = [ "rust_1_61" ]; + "rust_latest_stable" = [ "rust_1_64" ]; + "trybuild" = [ "dep:trybuild" ]; + }; + resolvedDefaultFeatures = [ "rust_1_51" "rust_1_55" "rust_1_56" "rust_1_57" "rust_1_61" "rust_1_64" ]; + }; + "konst_macro_rules" = rec { + crateName = "konst_macro_rules"; + version = "0.2.19"; + edition = "2018"; + sha256 = "0dswja0dqcww4x3fwjnirc0azv2n6cazn8yv0kddksd8awzkz4x4"; + authors = [ + "rodrimati1992 " + ]; + features = { + }; + resolvedDefaultFeatures = [ "rust_1_51" "rust_1_55" "rust_1_56" "rust_1_57" "rust_1_61" ]; + }; "kube" = rec { crateName = "kube"; version = "3.1.0"; @@ -5324,7 +5375,7 @@ rec { } { name = "hashbrown"; - packageId = "hashbrown"; + packageId = "hashbrown 0.16.1"; } { name = "hostname"; @@ -5426,9 +5477,9 @@ rec { }; "libc" = rec { crateName = "libc"; - version = "0.2.182"; + version = "0.2.186"; edition = "2021"; - sha256 = "04k1w1mq9f4cxv520dbr5xw1i7xkbc9fcrvaggyjy25jdkdvl038"; + sha256 = "0rnyhzjyqq9x56skkllbjzzzwym3r61lq3l4hqj64v71gw0r3av8"; authors = [ "The Rust Project Developers" ]; @@ -5442,10 +5493,10 @@ rec { }; "libgit2-sys" = rec { crateName = "libgit2-sys"; - version = "0.18.3+1.9.2"; + version = "0.18.5+1.9.4"; edition = "2021"; links = "git2"; - sha256 = "11rlbyihj3k35mnkxxz4yvsnlx33a4r9srl66c5vp08pp72arcy9"; + sha256 = "18lwqnhy7qxg4iw24s1a0n7aj7qbnryry1iy0w32k4f1xbk6lp80"; libName = "libgit2_sys"; libPath = "lib.rs"; authors = [ @@ -5503,10 +5554,10 @@ rec { }; "libz-sys" = rec { crateName = "libz-sys"; - version = "1.1.24"; + version = "1.1.29"; edition = "2018"; links = "z"; - sha256 = "0f8879301wxgljw8snkcix90p6qbm4inp3sqrsjq9b2svv5yjda7"; + sha256 = "1n98kqya7a7a0cxf5n5z3g13rj7a1vqxynk2xc7bja1qfxbrdg45"; libName = "libz_sys"; authors = [ "Alex Crichton " @@ -5545,9 +5596,9 @@ rec { }; "litemap" = rec { crateName = "litemap"; - version = "0.8.1"; + version = "0.8.2"; edition = "2021"; - sha256 = "0xsy8pfp9s802rsj1bq2ys2kbk1g36w5dr3gkfip7gphb5x60wv3"; + sha256 = "1w7628bc7wwcxc4n4s5kw0610xk06710nh2hn5kwwk2wa91z9nlj"; authors = [ "The ICU4X Project Developers" ]; @@ -5583,9 +5634,9 @@ rec { }; "log" = rec { crateName = "log"; - version = "0.4.29"; + version = "0.4.32"; edition = "2021"; - sha256 = "15q8j9c8g5zpkcw0hnd6cf2z7fxqnvsjh3rw5mv5q10r83i34l2y"; + sha256 = "0fmdg0cxig7i4fwf1sw7fmg4d1gdbfzniawcfpwydy1q7320fgwm"; authors = [ "The Rust Project Developers" ]; @@ -5639,9 +5690,9 @@ rec { }; "memchr" = rec { crateName = "memchr"; - version = "2.8.0"; + version = "2.8.1"; edition = "2021"; - sha256 = "0y9zzxcqxvdqg6wyag7vc3h0blhdn7hkq164bxyx2vph8zs5ijpq"; + sha256 = "1n448jx01h5z2xknj6x2dhxgr8s8fb717cf6vfqj5lmhkpj7m53b"; authors = [ "Andrew Gallant " "bluss" @@ -5702,9 +5753,9 @@ rec { }; "mio" = rec { crateName = "mio"; - version = "1.1.1"; + version = "1.2.1"; edition = "2021"; - sha256 = "1z2phpalqbdgihrcjp8y09l3kgq6309jnhnr6h11l9s7mnqcm6x6"; + sha256 = "1nkggmrlnjs93w8rja4lvjj4aml1xqahgimv1h0p7d373kvhmg82"; authors = [ "Carl Lerche " "Thomas de Zeeuw " @@ -5714,17 +5765,7 @@ rec { { name = "libc"; packageId = "libc"; - target = { target, features }: ("hermit" == target."os" or null); - } - { - name = "libc"; - packageId = "libc"; - target = { target, features }: ("wasi" == target."os" or null); - } - { - name = "libc"; - packageId = "libc"; - target = { target, features }: (target."unix" or false); + target = { target, features }: ((target."unix" or false) || ("hermit" == target."os" or null) || ("wasi" == target."os" or null)); } { name = "wasi"; @@ -5813,7 +5854,7 @@ rec { } { name = "rand"; - packageId = "rand 0.8.5"; + packageId = "rand 0.8.6"; optional = true; usesDefaultFeatures = false; } @@ -5832,7 +5873,7 @@ rec { devDependencies = [ { name = "rand"; - packageId = "rand 0.8.5"; + packageId = "rand 0.8.6"; features = [ "small_rng" ]; } ]; @@ -5850,9 +5891,9 @@ rec { }; "num-conv" = rec { crateName = "num-conv"; - version = "0.2.0"; + version = "0.2.2"; edition = "2021"; - sha256 = "0l4hj7lp8zbb9am4j3p7vlcv47y9bbazinvnxx9zjhiwkibyr5yg"; + sha256 = "0hg4f9bwmy7cwpxdkm165dmkfc8jhkkayci234jsmi5ssb33j5sj"; libName = "num_conv"; authors = [ "Jacob Pratt " @@ -5946,9 +5987,9 @@ rec { }; "once_cell" = rec { crateName = "once_cell"; - version = "1.21.3"; + version = "1.21.4"; edition = "2021"; - sha256 = "0b9x77lb9f1j6nqgf5aka4s2qj0nly176bpbrv6f9iakk5ff3xa2"; + sha256 = "0l1v676wf71kjg2khch4dphwh1jp3291ffiymr2mvy1kxd5kwz4z"; authors = [ "Aleksey Kladov " ]; @@ -5985,9 +6026,9 @@ rec { }; "opentelemetry" = rec { crateName = "opentelemetry"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "18629xsj4rsyiby9aj511q6wcw6s9m09gx3ymw1yjcvix1mcsjxq"; + sha256 = "10ln14d1jgc8rvw97mblc9blzcgpg1bimim4d170b7ia4mijq55h"; dependencies = [ { name = "futures-core"; @@ -6024,24 +6065,24 @@ rec { ]; features = { "default" = [ "trace" "metrics" "logs" "internal-logs" "futures" ]; + "experimental_metrics_bound_instruments" = [ "metrics" ]; "futures" = [ "futures-core" "futures-sink" "pin-project-lite" ]; "futures-core" = [ "dep:futures-core" ]; "futures-sink" = [ "dep:futures-sink" ]; "internal-logs" = [ "tracing" ]; "pin-project-lite" = [ "dep:pin-project-lite" ]; - "spec_unstable_logs_enabled" = [ "logs" ]; "testing" = [ "trace" ]; "thiserror" = [ "dep:thiserror" ]; "trace" = [ "futures" "thiserror" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "default" "futures" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "spec_unstable_logs_enabled" "thiserror" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "futures" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "thiserror" "trace" "tracing" ]; }; "opentelemetry-appender-tracing" = rec { crateName = "opentelemetry-appender-tracing"; - version = "0.31.1"; + version = "0.32.0"; edition = "2021"; - sha256 = "1hnwizzgfhpjfnvml638yy846py8hf2gl1n3p1igbk1srb2ilspg"; + sha256 = "0dyq4myan64sl8wly02jx0gb3jjz7575mn3w8rpphz0xvkq8001c"; libName = "opentelemetry_appender_tracing"; dependencies = [ { @@ -6084,18 +6125,15 @@ rec { ]; features = { "experimental_metadata_attributes" = [ "dep:tracing-log" ]; - "experimental_use_tracing_span_context" = [ "tracing-opentelemetry" ]; "log" = [ "dep:log" ]; - "spec_unstable_logs_enabled" = [ "opentelemetry/spec_unstable_logs_enabled" ]; - "tracing-opentelemetry" = [ "dep:tracing-opentelemetry" ]; }; resolvedDefaultFeatures = [ "default" ]; }; "opentelemetry-http" = rec { crateName = "opentelemetry-http"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "0pc5nw1ds8v8w0nvyall39m92v8m1xl1p3vwvxk6nkhrffdd19np"; + sha256 = "0ca3drvm4fx5nskl7yn42dimy3bg35ppzc85y1p27pz215fh30sn"; libName = "opentelemetry_http"; dependencies = [ { @@ -6131,16 +6169,16 @@ rec { "internal-logs" = [ "opentelemetry/internal-logs" ]; "reqwest" = [ "dep:reqwest" ]; "reqwest-blocking" = [ "dep:reqwest" "reqwest/blocking" ]; - "reqwest-rustls" = [ "dep:reqwest" "reqwest/rustls-tls-native-roots" ]; - "reqwest-rustls-webpki-roots" = [ "dep:reqwest" "reqwest/rustls-tls-webpki-roots" ]; + "reqwest-rustls" = [ "dep:reqwest" "reqwest/default-tls" ]; + "reqwest-rustls-webpki-roots" = [ "dep:reqwest" "reqwest/default-tls" "reqwest/webpki-roots" ]; }; - resolvedDefaultFeatures = [ "internal-logs" "reqwest" "reqwest-blocking" ]; + resolvedDefaultFeatures = [ "reqwest" "reqwest-blocking" ]; }; "opentelemetry-otlp" = rec { crateName = "opentelemetry-otlp"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "1gv3h75z8c0p9b85mbq7f1rgsi18wip1xlfa6g82lkfa5pdnc8vs"; + sha256 = "0d9cys2flpidfxbr6h1103hjc633cax47ihnqgbj0xnicscr4rlr"; libName = "opentelemetry_otlp"; dependencies = [ { @@ -6201,10 +6239,9 @@ rec { usesDefaultFeatures = false; } { - name = "tracing"; - packageId = "tracing"; + name = "tonic-types"; + packageId = "tonic-types"; optional = true; - usesDefaultFeatures = false; } ]; devDependencies = [ @@ -6229,16 +6266,19 @@ rec { ]; features = { "default" = [ "http-proto" "reqwest-blocking-client" "trace" "metrics" "logs" "internal-logs" ]; + "experimental-grpc-retry" = [ "grpc-tonic" "opentelemetry_sdk/experimental_async_runtime" "opentelemetry_sdk/rt-tokio" ]; + "experimental-http-retry" = [ "opentelemetry_sdk/experimental_async_runtime" "opentelemetry_sdk/rt-tokio" "tokio" "httpdate" ]; "flate2" = [ "dep:flate2" ]; - "grpc-tonic" = [ "tonic" "prost" "http" "tokio" "opentelemetry-proto/gen-tonic" ]; + "grpc-tonic" = [ "tonic" "tonic-types" "prost" "http" "tokio" "opentelemetry-proto/gen-tonic" ]; "gzip-http" = [ "flate2" ]; "gzip-tonic" = [ "tonic/gzip" ]; "http" = [ "dep:http" ]; "http-json" = [ "serde_json" "prost" "opentelemetry-http" "opentelemetry-proto/gen-tonic-messages" "opentelemetry-proto/with-serde" "http" "trace" "metrics" ]; "http-proto" = [ "prost" "opentelemetry-http" "opentelemetry-proto/gen-tonic-messages" "http" "trace" "metrics" ]; + "httpdate" = [ "dep:httpdate" ]; "hyper-client" = [ "opentelemetry-http/hyper" ]; "integration-testing" = [ "tonic" "prost" "tokio/full" "trace" "logs" ]; - "internal-logs" = [ "tracing" "opentelemetry_sdk/internal-logs" "opentelemetry-http/internal-logs" ]; + "internal-logs" = [ "opentelemetry_sdk/internal-logs" "opentelemetry/internal-logs" ]; "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" "opentelemetry-proto/logs" ]; "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" "opentelemetry-proto/metrics" ]; "opentelemetry-http" = [ "dep:opentelemetry-http" ]; @@ -6251,24 +6291,27 @@ rec { "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; "serialize" = [ "serde" "serde_json" ]; - "tls" = [ "tonic/tls-ring" ]; - "tls-roots" = [ "tls" "tonic/tls-native-roots" ]; - "tls-webpki-roots" = [ "tls" "tonic/tls-webpki-roots" ]; + "tls" = [ "tls-ring" ]; + "tls-aws-lc" = [ "tonic/tls-aws-lc" ]; + "tls-provider-agnostic" = [ "tonic/_tls-any" ]; + "tls-ring" = [ "tonic/tls-ring" ]; + "tls-roots" = [ "tonic/tls-native-roots" ]; + "tls-webpki-roots" = [ "tonic/tls-webpki-roots" ]; "tokio" = [ "dep:tokio" ]; "tonic" = [ "dep:tonic" ]; + "tonic-types" = [ "dep:tonic-types" ]; "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" "opentelemetry-proto/trace" ]; - "tracing" = [ "dep:tracing" ]; "zstd" = [ "dep:zstd" ]; "zstd-http" = [ "zstd" ]; "zstd-tonic" = [ "tonic/zstd" ]; }; - resolvedDefaultFeatures = [ "default" "grpc-tonic" "gzip-tonic" "http" "http-proto" "internal-logs" "logs" "metrics" "opentelemetry-http" "prost" "reqwest" "reqwest-blocking-client" "tokio" "tonic" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "grpc-tonic" "gzip-tonic" "http" "http-proto" "internal-logs" "logs" "metrics" "opentelemetry-http" "prost" "reqwest" "reqwest-blocking-client" "tokio" "tonic" "tonic-types" "trace" ]; }; "opentelemetry-proto" = rec { crateName = "opentelemetry-proto"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "03xkjsjrsm7zkkx5gascqd9bg2z20wymm06l16cyxsp5dpq5s5x7"; + sha256 = "0f5ny4rpnpq6q5q34b8k2q548rf31rpbxkwjqjwzfqxg3yx5imjn"; libName = "opentelemetry_proto"; dependencies = [ { @@ -6312,30 +6355,29 @@ rec { "const-hex" = [ "dep:const-hex" ]; "default" = [ "full" ]; "full" = [ "gen-tonic" "trace" "logs" "metrics" "zpages" "with-serde" "internal-logs" ]; - "gen-tonic" = [ "gen-tonic-messages" "tonic/channel" ]; - "gen-tonic-messages" = [ "tonic" "tonic-prost" "prost" ]; + "gen-tonic" = [ "gen-tonic-messages" "tonic" "tonic-prost" "tonic/channel" ]; + "gen-tonic-messages" = [ "prost" ]; "internal-logs" = [ "opentelemetry/internal-logs" ]; "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" ]; "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" ]; "prost" = [ "dep:prost" ]; "schemars" = [ "dep:schemars" ]; "serde" = [ "dep:serde" ]; - "serde_json" = [ "dep:serde_json" ]; "testing" = [ "opentelemetry/testing" ]; "tonic" = [ "dep:tonic" ]; "tonic-prost" = [ "dep:tonic-prost" ]; "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" ]; "with-schemars" = [ "schemars" ]; - "with-serde" = [ "serde" "const-hex" "base64" "serde_json" ]; + "with-serde" = [ "serde" "const-hex" "base64" ]; "zpages" = [ "trace" ]; }; resolvedDefaultFeatures = [ "gen-tonic" "gen-tonic-messages" "logs" "metrics" "prost" "tonic" "tonic-prost" "trace" ]; }; "opentelemetry-semantic-conventions" = rec { crateName = "opentelemetry-semantic-conventions"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "0in8plv2l2ar7anzi7lrbll0fjfvaymkg5vc5bnvibs1w3gjjbp6"; + sha256 = "0s1x4h1cgmhkxb7i5g02la2vhkf4lg5g26cgn2s2gd1p0j5gk8kc"; libName = "opentelemetry_semantic_conventions"; features = { }; @@ -6343,9 +6385,9 @@ rec { }; "opentelemetry_sdk" = rec { crateName = "opentelemetry_sdk"; - version = "0.31.0"; + version = "0.32.1"; edition = "2021"; - sha256 = "1gbjsggdxfpjbanjvaxa3nq32vfa37i3v13dvx4gsxhrk7sy8jp1"; + sha256 = "1ycl11syranrinhgn4c2hlzhyzyvpa06ryxq5mxgzmf4387ghncv"; dependencies = [ { name = "futures-channel"; @@ -6371,9 +6413,16 @@ rec { packageId = "percent-encoding"; optional = true; } + { + name = "portable-atomic"; + packageId = "portable-atomic"; + usesDefaultFeatures = false; + target = { target, features }: (!("64" == target."has_atomic" or null)); + features = [ "fallback" ]; + } { name = "rand"; - packageId = "rand 0.9.2"; + packageId = "rand 0.9.4"; optional = true; usesDefaultFeatures = false; features = [ "std" "std_rng" "small_rng" "os_rng" "thread_rng" ]; @@ -6395,10 +6444,18 @@ rec { optional = true; } ]; + devDependencies = [ + { + name = "tokio"; + packageId = "tokio"; + usesDefaultFeatures = false; + features = [ "macros" "rt-multi-thread" ]; + } + ]; features = { "default" = [ "trace" "metrics" "logs" "internal-logs" ]; "experimental_logs_batch_log_processor_with_async_runtime" = [ "logs" "experimental_async_runtime" ]; - "experimental_logs_concurrent_log_processor" = [ "logs" ]; + "experimental_metrics_bound_instruments" = [ "metrics" "opentelemetry/experimental_metrics_bound_instruments" ]; "experimental_metrics_custom_reader" = [ "metrics" ]; "experimental_metrics_disable_name_validation" = [ "metrics" ]; "experimental_metrics_periodicreader_with_async_runtime" = [ "metrics" "experimental_async_runtime" ]; @@ -6415,15 +6472,14 @@ rec { "rt-tokio-current-thread" = [ "tokio/rt" "tokio/time" "tokio-stream" "experimental_async_runtime" ]; "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; - "spec_unstable_logs_enabled" = [ "logs" "opentelemetry/spec_unstable_logs_enabled" ]; "spec_unstable_metrics_views" = [ "metrics" ]; - "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "rt-tokio" "rt-tokio-current-thread" "tokio/macros" "tokio/rt-multi-thread" ]; + "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "tokio/sync" ]; "tokio" = [ "dep:tokio" ]; "tokio-stream" = [ "dep:tokio-stream" ]; "trace" = [ "opentelemetry/trace" "rand" "percent-encoding" ]; "url" = [ "dep:url" ]; }; - resolvedDefaultFeatures = [ "default" "experimental_async_runtime" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "spec_unstable_logs_enabled" "tokio" "tokio-stream" "trace" ]; + resolvedDefaultFeatures = [ "default" "experimental_async_runtime" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "tokio" "tokio-stream" "trace" ]; }; "ordered-float" = rec { crateName = "ordered-float"; @@ -6797,9 +6853,9 @@ rec { }; "pin-project" = rec { crateName = "pin-project"; - version = "1.1.11"; + version = "1.1.13"; edition = "2021"; - sha256 = "05zm3y3bl83ypsr6favxvny2kys4i19jiz1y18ylrbxwsiz9qx7i"; + sha256 = "09091qp946lpmjz4yp0xil1r5v4hgc91fi19dg5csayhdqrv4ri4"; libName = "pin_project"; dependencies = [ { @@ -6811,9 +6867,9 @@ rec { }; "pin-project-internal" = rec { crateName = "pin-project-internal"; - version = "1.1.11"; + version = "1.1.13"; edition = "2021"; - sha256 = "1ik4mpb92da75inmjvxf2qm61vrnwml3x24wddvrjlqh1z9hxcnr"; + sha256 = "12rzlh07i1sdgrvzj6wgkka5bjqyvbfsl8knq6qi7g16m7q9aqy9"; procMacro = true; libName = "pin_project_internal"; dependencies = [ @@ -6841,17 +6897,6 @@ rec { sha256 = "1kfmwvs271si96zay4mm8887v5khw0c27jc9srw1a75ykvgj54x8"; libName = "pin_project_lite"; - }; - "pin-utils" = rec { - crateName = "pin-utils"; - version = "0.1.0"; - edition = "2018"; - sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb"; - libName = "pin_utils"; - authors = [ - "Josef Brandl " - ]; - }; "pkcs1" = rec { crateName = "pkcs1"; @@ -6923,9 +6968,9 @@ rec { }; "pkg-config" = rec { crateName = "pkg-config"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "0k4h3gnzs94sjb2ix6jyksacs52cf1fanpwsmlhjnwrdnp8dppby"; + sha256 = "17jnqmcbxsnwhg9gjf0nh6dj5k0x3hgwi3mb9krjnmfa9v435w8r"; libName = "pkg_config"; authors = [ "Alex Crichton " @@ -6943,13 +6988,13 @@ rec { "default" = [ "fallback" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "require-cas" ]; + resolvedDefaultFeatures = [ "fallback" "require-cas" ]; }; "portable-atomic-util" = rec { crateName = "portable-atomic-util"; - version = "0.2.5"; + version = "0.2.7"; edition = "2018"; - sha256 = "1xcm0ia8756k6hdgafx4g3lx3fw0hvz2zqswq7c2sy58gxnvk7bs"; + sha256 = "0616j0fhy6y71hyxg3n86f6hng0fmsc269s3wp4gl8ww4p8hd8f2"; libName = "portable_atomic_util"; dependencies = [ { @@ -6960,15 +7005,16 @@ rec { } ]; features = { + "serde" = [ "dep:serde" ]; "std" = [ "alloc" ]; }; resolvedDefaultFeatures = [ "alloc" ]; }; "potential_utf" = rec { crateName = "potential_utf"; - version = "0.1.4"; + version = "0.1.5"; edition = "2021"; - sha256 = "0xxg0pkfpq299wvwln409z4fk80rbv55phh3f1jhjajy5x1ljfdp"; + sha256 = "0r0518fr32xbkgzqap509s3r60cr0iancsg9j1jgf37cyz7b20q1"; authors = [ "The ICU4X Project Developers" ]; @@ -7050,9 +7096,9 @@ rec { }; "proc-macro-crate" = rec { crateName = "proc-macro-crate"; - version = "3.4.0"; + version = "3.5.0"; edition = "2021"; - sha256 = "10v9qi51n4phn1lrj5r94kjq7yhci9jrkqnn6wpan05yjsgb3711"; + sha256 = "0kv1g1d1zjwxlgcaba2qlshzyy32j03xic8rskqlcr5mnblsfyz6"; libName = "proc_macro_crate"; authors = [ "Bastian Köcher " @@ -7210,11 +7256,39 @@ rec { ]; }; + "prost-types" = rec { + crateName = "prost-types"; + version = "0.14.3"; + edition = "2021"; + sha256 = "1mrxrciryfgi6a0vmrgyj3g27r9hdhlgwkq71cgv3icbvg5w94c9"; + libName = "prost_types"; + authors = [ + "Dan Burkert " + "Lucio Franco " + "Casper Meijn " + "Tokio Contributors " + ]; + dependencies = [ + { + name = "prost"; + packageId = "prost"; + usesDefaultFeatures = false; + features = [ "derive" ]; + } + ]; + features = { + "arbitrary" = [ "dep:arbitrary" ]; + "chrono" = [ "dep:chrono" ]; + "default" = [ "std" ]; + "std" = [ "prost/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "quote" = rec { crateName = "quote"; - version = "1.0.44"; + version = "1.0.45"; edition = "2021"; - sha256 = "1r7c7hxl66vz3q9qizgjhy77pdrrypqgk4ghc7260xvvfb7ypci1"; + sha256 = "095rb5rg7pbnwdp6v8w5jw93wndwyijgci1b5lw8j1h5cscn3wj1"; authors = [ "David Tolnay " ]; @@ -7243,11 +7317,11 @@ rec { "rustc-dep-of-std" = [ "core" ]; }; }; - "rand 0.8.5" = rec { + "rand 0.8.6" = rec { crateName = "rand"; - version = "0.8.5"; + version = "0.8.6"; edition = "2018"; - sha256 = "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl"; + sha256 = "12kd4rljn86m00rcaz4c1rcya4mb4gk5ig6i8xq00a8wjgxfr82w"; authors = [ "The Rand Project Developers" "The Rust Project Developers" @@ -7269,22 +7343,19 @@ rec { "default" = [ "std" "std_rng" ]; "getrandom" = [ "rand_core/getrandom" ]; "libc" = [ "dep:libc" ]; - "log" = [ "dep:log" ]; - "packed_simd" = [ "dep:packed_simd" ]; "rand_chacha" = [ "dep:rand_chacha" ]; "serde" = [ "dep:serde" ]; "serde1" = [ "serde" "rand_core/serde1" ]; - "simd_support" = [ "packed_simd" ]; "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ]; "std_rng" = [ "rand_chacha" ]; }; resolvedDefaultFeatures = [ "rand_chacha" "std_rng" ]; }; - "rand 0.9.2" = rec { + "rand 0.9.4" = rec { crateName = "rand"; - version = "0.9.2"; + version = "0.9.4"; edition = "2021"; - sha256 = "1lah73ainvrgl7brcxx0pwhpnqa3sm3qaj672034jz8i0q7pgckd"; + sha256 = "1sknbxgs6nfg0nxdd7689lwbyr2i4vaswchrv4b34z8vpc3azia4"; authors = [ "The Rand Project Developers" "The Rust Project Developers" @@ -7304,7 +7375,6 @@ rec { ]; features = { "default" = [ "std" "std_rng" "os_rng" "small_rng" "thread_rng" ]; - "log" = [ "dep:log" ]; "os_rng" = [ "rand_core/os_rng" ]; "serde" = [ "dep:serde" "rand_core/serde" ]; "std" = [ "rand_core/std" "rand_chacha?/std" "alloc" ]; @@ -7643,9 +7713,9 @@ rec { }; "reqwest" = rec { crateName = "reqwest"; - version = "0.12.28"; + version = "0.13.4"; edition = "2021"; - sha256 = "0iqidijghgqbzl3bjg5hb4zmigwa4r612bgi0yiq0c90b6jkrpgd"; + sha256 = "1hy1plns9krbh3h1dy2sdjygsfkdcnxm6pbxdi0ya9b5vq8mi711"; authors = [ "Sean McArthur " ]; @@ -7662,7 +7732,7 @@ rec { name = "futures-channel"; packageId = "futures-channel"; optional = true; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "futures-core"; @@ -7682,62 +7752,44 @@ rec { { name = "http-body"; packageId = "http-body"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "http-body-util"; packageId = "http-body-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "hyper"; packageId = "hyper"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "client" ]; } { name = "hyper-util"; packageId = "hyper-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "client" "client-legacy" "client-proxy" "tokio" ]; } { name = "js-sys"; packageId = "js-sys"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "log"; packageId = "log"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "percent-encoding"; packageId = "percent-encoding"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "pin-project-lite"; packageId = "pin-project-lite"; - target = { target, features }: (!("wasm32" == target."arch" or null)); - } - { - name = "serde"; - packageId = "serde"; - } - { - name = "serde_json"; - packageId = "serde_json"; - optional = true; - } - { - name = "serde_json"; - packageId = "serde_json"; - target = { target, features }: ("wasm32" == target."arch" or null); - } - { - name = "serde_urlencoded"; - packageId = "serde_urlencoded"; + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "sync_wrapper"; @@ -7748,27 +7800,27 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "net" "time" ]; } { name = "tower"; packageId = "tower"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "retry" "timeout" "util" ]; } { name = "tower-http"; packageId = "tower-http"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "follow-redirect" ]; } { name = "tower-service"; packageId = "tower-service"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "url"; @@ -7777,17 +7829,17 @@ rec { { name = "wasm-bindgen"; packageId = "wasm-bindgen"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "wasm-bindgen-futures"; packageId = "wasm-bindgen-futures"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "web-sys"; packageId = "web-sys"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); features = [ "AbortController" "AbortSignal" "Headers" "Request" "RequestInit" "RequestMode" "Response" "Window" "FormData" "Blob" "BlobPropertyBag" "ServiceWorkerGlobalScope" "RequestCredentials" "File" "ReadableStream" "RequestCache" ]; } ]; @@ -7796,33 +7848,27 @@ rec { name = "futures-util"; packageId = "futures-util"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "std" "alloc" ]; } { name = "hyper"; packageId = "hyper"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "http2" "client" "server" ]; } { name = "hyper-util"; packageId = "hyper-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "http2" "client" "client-legacy" "server-auto" "server-graceful" "tokio" ]; } - { - name = "serde"; - packageId = "serde"; - target = { target, features }: (!("wasm32" == target."arch" or null)); - features = [ "derive" ]; - } { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "macros" "rt-multi-thread" ]; } { @@ -7834,40 +7880,37 @@ rec { { name = "wasm-bindgen"; packageId = "wasm-bindgen"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); features = [ "serde-serialize" ]; } ]; features = { + "__native-tls" = [ "dep:hyper-tls" "dep:native-tls-crate" "__tls" "dep:tokio-native-tls" ]; + "__native-tls-alpn" = [ "native-tls-crate?/alpn" "hyper-tls?/alpn" ]; "__rustls" = [ "dep:hyper-rustls" "dep:tokio-rustls" "dep:rustls" "__tls" ]; - "__rustls-ring" = [ "hyper-rustls?/ring" "tokio-rustls?/ring" "rustls?/ring" "quinn?/ring" ]; + "__rustls-aws-lc-rs" = [ "hyper-rustls?/aws-lc-rs" "tokio-rustls?/aws-lc-rs" "rustls?/aws-lc-rs" "quinn?/rustls-aws-lc-rs" ]; "__tls" = [ "dep:rustls-pki-types" "tokio/io-util" ]; "blocking" = [ "dep:futures-channel" "futures-channel?/sink" "dep:futures-util" "futures-util?/io" "futures-util?/sink" "tokio/sync" ]; "brotli" = [ "tower-http/decompression-br" ]; "charset" = [ "dep:encoding_rs" "dep:mime" ]; "cookies" = [ "dep:cookie_crate" "dep:cookie_store" ]; "default" = [ "default-tls" "charset" "http2" "system-proxy" ]; - "default-tls" = [ "dep:hyper-tls" "dep:native-tls-crate" "__tls" "dep:tokio-native-tls" ]; + "default-tls" = [ "rustls" ]; "deflate" = [ "tower-http/decompression-deflate" ]; + "form" = [ "dep:serde" "dep:serde_urlencoded" ]; "gzip" = [ "tower-http/decompression-gzip" ]; - "h2" = [ "dep:h2" ]; "hickory-dns" = [ "dep:hickory-resolver" "dep:once_cell" ]; - "http2" = [ "h2" "hyper/http2" "hyper-util/http2" "hyper-rustls?/http2" ]; - "http3" = [ "rustls-tls-manual-roots" "dep:h3" "dep:h3-quinn" "dep:quinn" "tokio/macros" ]; - "json" = [ "dep:serde_json" ]; - "macos-system-configuration" = [ "system-proxy" ]; + "http2" = [ "dep:h2" "hyper/http2" "hyper-util/http2" "hyper-rustls?/http2" ]; + "http3" = [ "rustls" "dep:h3" "dep:h3-quinn" "dep:quinn" "tokio/macros" ]; + "json" = [ "dep:serde" "dep:serde_json" ]; "multipart" = [ "dep:mime_guess" "dep:futures-util" ]; - "native-tls" = [ "default-tls" ]; - "native-tls-alpn" = [ "native-tls" "native-tls-crate?/alpn" "hyper-tls?/alpn" ]; - "native-tls-vendored" = [ "native-tls" "native-tls-crate?/vendored" ]; - "rustls-tls" = [ "rustls-tls-webpki-roots" ]; - "rustls-tls-manual-roots" = [ "rustls-tls-manual-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-manual-roots-no-provider" = [ "__rustls" ]; - "rustls-tls-native-roots" = [ "rustls-tls-native-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-native-roots-no-provider" = [ "dep:rustls-native-certs" "hyper-rustls?/native-tokio" "__rustls" ]; - "rustls-tls-no-provider" = [ "rustls-tls-manual-roots-no-provider" ]; - "rustls-tls-webpki-roots" = [ "rustls-tls-webpki-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-webpki-roots-no-provider" = [ "dep:webpki-roots" "hyper-rustls?/webpki-tokio" "__rustls" ]; + "native-tls" = [ "__native-tls" "__native-tls-alpn" ]; + "native-tls-no-alpn" = [ "__native-tls" ]; + "native-tls-vendored" = [ "__native-tls" "native-tls-crate?/vendored" "__native-tls-alpn" ]; + "native-tls-vendored-no-alpn" = [ "__native-tls" "native-tls-crate?/vendored" ]; + "query" = [ "dep:serde" "dep:serde_urlencoded" ]; + "rustls" = [ "__rustls-aws-lc-rs" "dep:rustls-platform-verifier" "__rustls" ]; + "rustls-no-provider" = [ "dep:rustls-platform-verifier" "__rustls" ]; "stream" = [ "tokio/fs" "dep:futures-util" "dep:tokio-util" "dep:wasm-streams" ]; "system-proxy" = [ "hyper-util/client-proxy-system" ]; "zstd" = [ "tower-http/decompression-zstd" ]; @@ -8179,9 +8222,9 @@ rec { }; "rustls" = rec { crateName = "rustls"; - version = "0.23.37"; + version = "0.23.40"; edition = "2021"; - sha256 = "193k5h0wcih6ghvkrxyzwncivr1bd3a8yw3lzp13pzfcbz5jb03m"; + sha256 = "12qnv3ag4wrw7aj8jng74kgrilpjm2b1rfcjaac8h691frccv1pg"; dependencies = [ { name = "log"; @@ -8248,9 +8291,9 @@ rec { }; "rustls-native-certs" = rec { crateName = "rustls-native-certs"; - version = "0.8.3"; + version = "0.8.4"; edition = "2021"; - sha256 = "0qrajg2n90bcr3bcq6j95gjm7a9lirfkkdmjj32419dyyzan0931"; + sha256 = "0kgazl8zc1sv63qg179bz96ilzh56lzfa5k92ji7d265f4kibdfs"; libName = "rustls_native_certs"; dependencies = [ { @@ -8279,9 +8322,9 @@ rec { }; "rustls-pki-types" = rec { crateName = "rustls-pki-types"; - version = "1.14.0"; + version = "1.14.1"; edition = "2021"; - sha256 = "1p9zsgslvwzzkzhm6bqicffqndr4jpx67992b0vl0pi21a5hy15y"; + sha256 = "1a9pr54y0f3qr97bxpd3ahjldq0gqdld0h799xbnwdzbwxx1k9rh"; libName = "rustls_pki_types"; dependencies = [ { @@ -8360,9 +8403,9 @@ rec { }; "schannel" = rec { crateName = "schannel"; - version = "0.1.28"; + version = "0.1.29"; edition = "2018"; - sha256 = "1qb6s5gyxfz2inz753a4z3mc1d266mwvz0c5w7ppd3h44swq27c9"; + sha256 = "0ffrzz5vf2s3gnzvphgb5gg8fqifvryl07qcf7q3x1scj3jbghci"; authors = [ "Steven Fackler " "Steffen Butzer " @@ -8658,9 +8701,9 @@ rec { }; "semver" = rec { crateName = "semver"; - version = "1.0.27"; - edition = "2018"; - sha256 = "1qmi3akfrnqc2hfkdgcxhld5bv961wbk8my3ascv5068mc5fnryp"; + version = "1.0.28"; + edition = "2021"; + sha256 = "1kaimrpy876bcgi8bfj0qqfxk77zm9iz2zhn1hp9hj685z854y4a"; authors = [ "David Tolnay " ]; @@ -8817,9 +8860,9 @@ rec { }; "serde_json" = rec { crateName = "serde_json"; - version = "1.0.149"; + version = "1.0.150"; edition = "2021"; - sha256 = "11jdx4vilzrjjd1dpgy67x5lgzr0laplz30dhv75lnf5ffa07z43"; + sha256 = "1ffgfhy9kndjnrz8lmy95pr758p2zk8dxv6yi99x0vkkni24w0g8"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -9060,9 +9103,9 @@ rec { }; "shlex" = rec { crateName = "shlex"; - version = "1.3.0"; - edition = "2015"; - sha256 = "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg"; + version = "2.0.1"; + edition = "2018"; + sha256 = "1fjsll1cd7d2bcpdij9kd6w62rpbc7qqzvydvs021vsmr1cxvypq"; authors = [ "comex " "Fenhl " @@ -9130,9 +9173,9 @@ rec { }; "simd-adler32" = rec { crateName = "simd-adler32"; - version = "0.3.8"; + version = "0.3.9"; edition = "2018"; - sha256 = "18lx2gdgislabbvlgw5q3j5ssrr77v8kmkrxaanp3liimp2sc873"; + sha256 = "0532ysdwcvzyp2bwpk8qz0hijplcdwpssr5gy5r7qwqqy5z5qgbh"; libName = "simd_adler32"; authors = [ "Marvin Countryman " @@ -9241,29 +9284,25 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "rust_1_61" "rust_1_65" "std" ]; }; - "snafu 0.9.0" = rec { + "snafu 0.9.1" = rec { crateName = "snafu"; - version = "0.9.0"; + version = "0.9.1"; edition = "2018"; - sha256 = "1ii9r99x5qcn754m624yzgb9hzvkqkrcygf0aqh0pyb9dbnvrm6i"; + sha256 = "08k5yfydxdlshivfhrdq9km8qn02r93q28gkyvazbqz2icr1586i"; authors = [ "Jake Goulding " ]; dependencies = [ { name = "snafu-derive"; - packageId = "snafu-derive 0.9.0"; + packageId = "snafu-derive 0.9.1"; } ]; features = { - "backtrace" = [ "dep:backtrace" ]; - "backtraces-impl-backtrace-crate" = [ "backtrace" ]; + "backtraces-impl-backtrace-crate" = [ "dep:backtrace" ]; "default" = [ "std" "rust_1_81" ]; - "futures" = [ "futures-core-crate" "pin-project" ]; - "futures-core-crate" = [ "dep:futures-core-crate" ]; - "futures-crate" = [ "dep:futures-crate" ]; - "internal-dev-dependencies" = [ "futures-crate" ]; - "pin-project" = [ "dep:pin-project" ]; + "futures" = [ "dep:futures-core" "dep:pin-project" ]; + "internal-dev-dependencies" = [ "dep:futures" ]; "std" = [ "alloc" ]; "unstable-provider-api" = [ "snafu-derive/unstable-provider-api" ]; }; @@ -9331,11 +9370,11 @@ rec { }; resolvedDefaultFeatures = [ "rust_1_61" ]; }; - "snafu-derive 0.9.0" = rec { + "snafu-derive 0.9.1" = rec { crateName = "snafu-derive"; - version = "0.9.0"; + version = "0.9.1"; edition = "2018"; - sha256 = "0h0x61kyj4fvilcr2nj02l85shw1ika64vq9brf2gyna662ln9al"; + sha256 = "1nkfi7bis72pz3w7vb64m79w49qsv20sbf19jkd471vbhr83q42z"; procMacro = true; libName = "snafu_derive"; authors = [ @@ -9361,7 +9400,7 @@ rec { name = "syn"; packageId = "syn 2.0.117"; usesDefaultFeatures = false; - features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" ]; + features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } ]; features = { @@ -9369,9 +9408,9 @@ rec { }; "socket2" = rec { crateName = "socket2"; - version = "0.6.2"; + version = "0.6.4"; edition = "2021"; - sha256 = "1q073zkvz96h216mfz6niqk2kjqrgqv2va6zj34qh84zv4xamx46"; + sha256 = "0ldyp5rhba15spwxj1n94xh7sjks1398c3vwpwkxkd1087nwzlaj"; authors = [ "Alex Crichton " "Thomas de Zeeuw " @@ -9380,11 +9419,11 @@ rec { { name = "libc"; packageId = "libc"; - target = { target, features }: (target."unix" or false); + target = { target, features }: ((target."unix" or false) || ("wasi" == target."os" or null)); } { name = "windows-sys"; - packageId = "windows-sys 0.60.2"; + packageId = "windows-sys 0.61.2"; target = { target, features }: (target."windows" or false); features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ]; } @@ -9467,12 +9506,7 @@ rec { crateName = "stackable-certs"; version = "0.4.0"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-certs; }; libName = "stackable_certs"; authors = [ "Stackable GmbH " @@ -9507,7 +9541,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.2"; + packageId = "rand 0.9.4"; } { name = "rand_core"; @@ -9529,7 +9563,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-shared"; @@ -9570,12 +9604,7 @@ rec { crateName = "stackable-operator"; version = "0.111.1"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-operator; }; libName = "stackable_operator"; authors = [ "Stackable GmbH " @@ -9631,6 +9660,7 @@ rec { { name = "json-patch"; packageId = "json-patch"; + features = [ "schemars" ]; } { name = "k8s-openapi"; @@ -9650,7 +9680,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.2"; + packageId = "rand 0.9.4"; } { name = "regex"; @@ -9680,7 +9710,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator-derive"; @@ -9734,12 +9764,17 @@ rec { packageId = "url"; features = [ "serde" ]; } + { + name = "uuid"; + packageId = "uuid"; + } ]; features = { "certs" = [ "dep:stackable-certs" ]; + "client-feature-gates" = [ "dep:winnow" ]; "crds" = [ "dep:stackable-versioned" ]; "default" = [ "crds" ]; - "full" = [ "crds" "certs" "time" "webhook" "kube-ws" ]; + "full" = [ "client-feature-gates" "crds" "certs" "time" "webhook" "kube-ws" ]; "kube-ws" = [ "kube/ws" ]; "time" = [ "stackable-shared/time" ]; "webhook" = [ "dep:stackable-webhook" ]; @@ -9750,12 +9785,7 @@ rec { crateName = "stackable-operator-derive"; version = "0.3.1"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-operator-derive; }; procMacro = true; libName = "stackable_operator_derive"; authors = [ @@ -9783,14 +9813,9 @@ rec { }; "stackable-shared" = rec { crateName = "stackable-shared"; - version = "0.1.0"; + version = "0.1.1"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-shared; }; libName = "stackable_shared"; authors = [ "Stackable GmbH " @@ -9833,7 +9858,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -9900,8 +9925,8 @@ rec { packageId = "indoc"; } { - name = "product-config"; - packageId = "product-config"; + name = "java-properties"; + packageId = "java-properties"; } { name = "regex"; @@ -9926,7 +9951,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator"; @@ -9974,14 +9999,9 @@ rec { }; "stackable-telemetry" = rec { crateName = "stackable-telemetry"; - version = "0.6.3"; + version = "0.6.4"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-telemetry; }; libName = "stackable_telemetry"; authors = [ "Stackable GmbH " @@ -10023,7 +10043,7 @@ rec { { name = "opentelemetry_sdk"; packageId = "opentelemetry_sdk"; - features = [ "rt-tokio" "logs" "rt-tokio" "spec_unstable_logs_enabled" ]; + features = [ "rt-tokio" "logs" "rt-tokio" ]; } { name = "pin-project"; @@ -10031,7 +10051,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -10086,12 +10106,7 @@ rec { crateName = "stackable-versioned"; version = "0.10.0"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-versioned; }; libName = "stackable_versioned"; authors = [ "Stackable GmbH " @@ -10123,7 +10138,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-versioned-macros"; @@ -10136,12 +10151,7 @@ rec { crateName = "stackable-versioned-macros"; version = "0.10.0"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-versioned-macros; }; procMacro = true; libName = "stackable_versioned_macros"; authors = [ @@ -10204,12 +10214,7 @@ rec { crateName = "stackable-webhook"; version = "0.9.1"; edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; - }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-webhook; }; libName = "stackable_webhook"; authors = [ "Stackable GmbH " @@ -10267,7 +10272,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.2"; + packageId = "rand 0.9.4"; } { name = "serde"; @@ -10280,7 +10285,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-certs"; @@ -10410,6 +10415,16 @@ rec { }; resolvedDefaultFeatures = [ "i128" ]; }; + "symlink" = rec { + crateName = "symlink"; + version = "0.1.0"; + edition = "2015"; + sha256 = "02h1i0b81mxb4vns4xrvrfibpcvs7jqqav8p3yilwik8cv73r5x7"; + authors = [ + "Chris Morgan " + ]; + + }; "syn 1.0.109" = rec { crateName = "syn"; version = "1.0.109"; @@ -10750,9 +10765,9 @@ rec { }; "tinystr" = rec { crateName = "tinystr"; - version = "0.8.2"; + version = "0.8.3"; edition = "2021"; - sha256 = "0sa8z88axdsf088hgw5p4xcyi6g3w3sgbb6qdp81bph9bk2fkls2"; + sha256 = "0vfr8x285w6zsqhna0a9jyhylwiafb2kc8pj2qaqaahw48236cn8"; authors = [ "The ICU4X Project Developers" ]; @@ -10842,9 +10857,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.50.0"; + version = "1.52.3"; edition = "2021"; - sha256 = "0bc2c5kd57p2xd4l6hagb0bkrp798k5vw0f3xzzwy0sf6ws5xb97"; + sha256 = "1zpzazypkg61sw91na1m85x5s4rsjym335fwwhwm1hcs70dz1iwg"; authors = [ "Tokio Contributors " ]; @@ -10860,6 +10875,12 @@ rec { optional = true; target = { target, features }: ((target."tokio_unstable" or false) && ("linux" == target."os" or null)); } + { + name = "libc"; + packageId = "libc"; + optional = true; + target = { target, features }: ("wasi" == target."os" or null); + } { name = "libc"; packageId = "libc"; @@ -10899,7 +10920,7 @@ rec { name = "socket2"; packageId = "socket2"; optional = true; - target = { target, features }: (!(builtins.elem "wasm" target."family")); + target = { target, features }: ((!(builtins.elem "wasm" target."family")) || (("wasi" == target."os" or null) && (!("p1" == target."env" or null)))); features = [ "all" ]; } { @@ -10957,9 +10978,9 @@ rec { }; "tokio-macros" = rec { crateName = "tokio-macros"; - version = "2.6.1"; + version = "2.7.0"; edition = "2021"; - sha256 = "172nwz3s7mmh266hb8l5xdnc7v9kqahisppqhinfd75nz3ps4maw"; + sha256 = "15m4f37mdafs0gg36sh0rskm1i768lb7zmp8bw67kaxr3avnqniq"; procMacro = true; libName = "tokio_macros"; authors = [ @@ -11127,9 +11148,9 @@ rec { }; "toml_datetime" = rec { crateName = "toml_datetime"; - version = "0.7.5+spec-1.1.0"; - edition = "2021"; - sha256 = "0iqkgvgsxmszpai53dbip7sf2igic39s4dby29dbqf1h9bnwzqcj"; + version = "1.1.1+spec-1.1.0"; + edition = "2024"; + sha256 = "1mws2mkkf46l7inn77azhm0vdwxngv9vsbhbl0ah33p2c9gzcr9i"; dependencies = [ { name = "serde_core"; @@ -11148,9 +11169,9 @@ rec { }; "toml_edit" = rec { crateName = "toml_edit"; - version = "0.23.10+spec-1.0.0"; - edition = "2021"; - sha256 = "0saj5c676j8a3sqaj9akkp09wambg8aflji4zblwwa70azvvkj44"; + version = "0.25.12+spec-1.1.0"; + edition = "2024"; + sha256 = "1mx5paq837rjw7w51zprrjynk1vaig9yzxfqz9ac79jmd7f3w5fj"; dependencies = [ { name = "indexmap"; @@ -11183,9 +11204,9 @@ rec { }; "toml_parser" = rec { crateName = "toml_parser"; - version = "1.0.9+spec-1.1.0"; - edition = "2021"; - sha256 = "1i54qpvvcppy8ybdn9gssas81vfzq0kmgkcnxzhyf8w9w0al8bbh"; + version = "1.1.2+spec-1.1.0"; + edition = "2024"; + sha256 = "09kmzc55a0j21whm290wlf5a8b18a0qc87a1s8sncrckc6wfkax2"; dependencies = [ { name = "winnow"; @@ -11203,9 +11224,9 @@ rec { }; "tonic" = rec { crateName = "tonic"; - version = "0.14.5"; - edition = "2021"; - sha256 = "1v4k7aa28m7722gz9qak2jiy7lis1ycm4fdmq63iip4m0qdcdizy"; + version = "0.14.6"; + edition = "2024"; + sha256 = "1vs5ci6z6b9xhfsnx4s8qx6bqi1zzcrxncjp71147a0gqwc5aamc"; authors = [ "Lucio Franco " ]; @@ -11332,9 +11353,9 @@ rec { }; "tonic-prost" = rec { crateName = "tonic-prost"; - version = "0.14.5"; - edition = "2021"; - sha256 = "02fkg2bv87q0yds2wz3w0s7i1x6qcgbrl00dy6ipajdapfh7clx5"; + version = "0.14.6"; + edition = "2024"; + sha256 = "184y40nf0iyzc5rg32ivgd88snv68sqy1kchynn55r1vhml9z12h"; libName = "tonic_prost"; authors = [ "Lucio Franco " @@ -11355,6 +11376,33 @@ rec { } ]; + }; + "tonic-types" = rec { + crateName = "tonic-types"; + version = "0.14.6"; + edition = "2024"; + sha256 = "1s286gg71pjajny8xar0azq1w9lgz1ks3jm3pccxb0qz0q11pavk"; + libName = "tonic_types"; + authors = [ + "Lucio Franco " + "Rafael Lemos " + ]; + dependencies = [ + { + name = "prost"; + packageId = "prost"; + } + { + name = "prost-types"; + packageId = "prost-types"; + } + { + name = "tonic"; + packageId = "tonic"; + usesDefaultFeatures = false; + } + ]; + }; "tower" = rec { crateName = "tower"; @@ -11476,9 +11524,9 @@ rec { }; "tower-http" = rec { crateName = "tower-http"; - version = "0.6.8"; + version = "0.6.11"; edition = "2018"; - sha256 = "1y514jwzbyrmrkbaajpwmss4rg0mak82k16d6588w9ncaffmbrnl"; + sha256 = "0h08wjgs3hwnq11iwwzlmnabn1h4cl0fzd48svaccvqffkiggz2c"; libName = "tower_http"; authors = [ "Tower Maintainers " @@ -11512,11 +11560,6 @@ rec { packageId = "http-body"; optional = true; } - { - name = "iri-string"; - packageId = "iri-string"; - optional = true; - } { name = "mime"; packageId = "mime"; @@ -11546,6 +11589,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "url"; + packageId = "url"; + optional = true; + } ]; devDependencies = [ { @@ -11567,35 +11615,33 @@ rec { } ]; features = { - "async-compression" = [ "dep:async-compression" ]; "auth" = [ "base64" "validate-request" ]; "base64" = [ "dep:base64" ]; "catch-panic" = [ "tracing" "futures-util/std" "dep:http-body" "dep:http-body-util" ]; - "compression-br" = [ "async-compression/brotli" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "compression-deflate" = [ "async-compression/zlib" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; + "compression-br" = [ "dep:async-compression" "async-compression?/brotli" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "compression-deflate" = [ "dep:async-compression" "async-compression?/zlib" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; "compression-full" = [ "compression-br" "compression-deflate" "compression-gzip" "compression-zstd" ]; - "compression-gzip" = [ "async-compression/gzip" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "compression-zstd" = [ "async-compression/zstd" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "decompression-br" = [ "async-compression/brotli" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "decompression-deflate" = [ "async-compression/zlib" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; + "compression-gzip" = [ "dep:async-compression" "async-compression?/gzip" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "compression-zstd" = [ "dep:async-compression" "async-compression?/zstd" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "decompression-br" = [ "dep:async-compression" "async-compression?/brotli" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "decompression-deflate" = [ "dep:async-compression" "async-compression?/zlib" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; "decompression-full" = [ "decompression-br" "decompression-deflate" "decompression-gzip" "decompression-zstd" ]; - "decompression-gzip" = [ "async-compression/gzip" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "decompression-zstd" = [ "async-compression/zstd" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "follow-redirect" = [ "futures-util" "dep:http-body" "iri-string" "tower/util" ]; - "fs" = [ "futures-core" "futures-util" "dep:http-body" "dep:http-body-util" "tokio/fs" "tokio-util/io" "tokio/io-util" "dep:http-range-header" "mime_guess" "mime" "percent-encoding" "httpdate" "set-status" "futures-util/alloc" "tracing" ]; - "full" = [ "add-extension" "auth" "catch-panic" "compression-full" "cors" "decompression-full" "follow-redirect" "fs" "limit" "map-request-body" "map-response-body" "metrics" "normalize-path" "propagate-header" "redirect" "request-id" "sensitive-headers" "set-header" "set-status" "timeout" "trace" "util" "validate-request" ]; + "decompression-gzip" = [ "dep:async-compression" "async-compression?/gzip" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "decompression-zstd" = [ "dep:async-compression" "async-compression?/zstd" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "follow-redirect" = [ "futures-util" "dep:http-body" "dep:url" "tower/util" ]; + "fs" = [ "dep:tokio" "tokio?/fs" "tokio?/io-util" "futures-core" "futures-util" "dep:http-body" "dep:http-body-util" "tokio-util/io" "dep:http-range-header" "mime_guess" "mime" "percent-encoding" "httpdate" "set-status" "futures-util/alloc" ]; + "full" = [ "add-extension" "auth" "catch-panic" "compression-full" "cors" "decompression-full" "follow-redirect" "fs" "limit" "map-request-body" "map-response-body" "metrics" "normalize-path" "on-early-drop" "propagate-header" "redirect" "request-id" "sensitive-headers" "set-header" "set-status" "timeout" "trace" "util" "validate-request" ]; "futures-core" = [ "dep:futures-core" ]; "futures-util" = [ "dep:futures-util" ]; "httpdate" = [ "dep:httpdate" ]; - "iri-string" = [ "dep:iri-string" ]; "limit" = [ "dep:http-body" "dep:http-body-util" ]; - "metrics" = [ "dep:http-body" "tokio/time" ]; + "metrics" = [ "dep:http-body" "dep:tokio" "tokio?/time" ]; "mime" = [ "dep:mime" ]; "mime_guess" = [ "dep:mime_guess" ]; + "on-early-drop" = [ "dep:http-body" ]; "percent-encoding" = [ "dep:percent-encoding" ]; "request-id" = [ "uuid" ]; - "timeout" = [ "dep:http-body" "tokio/time" ]; - "tokio" = [ "dep:tokio" ]; + "timeout" = [ "dep:http-body" "dep:tokio" "tokio?/time" ]; "tokio-util" = [ "dep:tokio-util" ]; "tower" = [ "dep:tower" ]; "trace" = [ "dep:http-body" "tracing" ]; @@ -11604,7 +11650,7 @@ rec { "uuid" = [ "dep:uuid" ]; "validate-request" = [ "mime" ]; }; - resolvedDefaultFeatures = [ "auth" "base64" "default" "follow-redirect" "futures-util" "iri-string" "map-response-body" "mime" "tower" "trace" "tracing" "util" "validate-request" ]; + resolvedDefaultFeatures = [ "auth" "base64" "default" "follow-redirect" "futures-util" "map-response-body" "mime" "tower" "trace" "tracing" "util" "validate-request" ]; }; "tower-layer" = rec { crateName = "tower-layer"; @@ -11677,9 +11723,9 @@ rec { }; "tracing-appender" = rec { crateName = "tracing-appender"; - version = "0.2.4"; + version = "0.2.5"; edition = "2018"; - sha256 = "1bxf7xvsr89glbq174cx0b9pinaacbhlmc85y1ssniv2rq5lhvbq"; + sha256 = "0g4a6q5s3wafid5lqw1ljzvh1nhk3a4zmb627fxv96dr7qcqc1h5"; libName = "tracing_appender"; authors = [ "Zeki Sherif " @@ -11690,6 +11736,10 @@ rec { name = "crossbeam-channel"; packageId = "crossbeam-channel"; } + { + name = "symlink"; + packageId = "symlink"; + } { name = "thiserror"; packageId = "thiserror 2.0.18"; @@ -11854,9 +11904,9 @@ rec { }; "tracing-opentelemetry" = rec { crateName = "tracing-opentelemetry"; - version = "0.32.1"; + version = "0.33.0"; edition = "2021"; - sha256 = "1z2jjmxbkm1qawlb3bm99x8xwf4g8wjkbcknm9z4fv1w14nqzhhs"; + sha256 = "09nvxy5m7nxmifz4b6szdcyczapp2jcgxcac0jw4ax8klz5n9g5d"; libName = "tracing_opentelemetry"; dependencies = [ { @@ -11958,9 +12008,9 @@ rec { }; "tracing-subscriber" = rec { crateName = "tracing-subscriber"; - version = "0.3.22"; + version = "0.3.23"; edition = "2018"; - sha256 = "07hz575a0p1c2i4xw3gs3hkrykhndnkbfhyqdwjhvayx4ww18c1g"; + sha256 = "06fkr0qhggvrs861d7f74pn3i3a10h5jsp4n70jj9ys5b675fzyb"; libName = "tracing_subscriber"; authors = [ "Eliza Weisman " @@ -12091,13 +12141,9 @@ rec { }; "typenum" = rec { crateName = "typenum"; - version = "1.19.0"; + version = "1.20.1"; edition = "2018"; - sha256 = "1fw2mpbn2vmqan56j1b3fbpcdg80mz26fm53fs16bq5xcq84hban"; - authors = [ - "Paho Lurie-Gregg " - "Andre Bogus " - ]; + sha256 = "086s9ly0906kw5yw41249fba97w5zfxf03pyfwdkffvcprqfixdn"; features = { "scale-info" = [ "dep:scale-info" ]; "scale_info" = [ "scale-info/derive" ]; @@ -12130,9 +12176,9 @@ rec { }; "unicode-segmentation" = rec { crateName = "unicode-segmentation"; - version = "1.12.0"; + version = "1.13.3"; edition = "2018"; - sha256 = "14qla2jfx74yyb9ds3d2mpwpa4l4lzb9z57c6d2ba511458z5k7n"; + sha256 = "1a47zaq83p386r3baq4m018xd5q4q0grdg56i1x042dzn71x7xf6"; libName = "unicode_segmentation"; authors = [ "kwantam " @@ -12258,6 +12304,66 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "uuid" = rec { + crateName = "uuid"; + version = "1.23.2"; + edition = "2021"; + sha256 = "1xy942s4z0bi8p3441wvd4ry3hx6ry1c7s6fgrr38462xqybhn6j"; + authors = [ + "Ashley Mannix" + "Dylan DPC" + "Hunar Roop Kahlon" + ]; + dependencies = [ + { + name = "js-sys"; + packageId = "js-sys"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)) && (builtins.elem "atomics" targetFeatures)); + } + { + name = "wasm-bindgen"; + packageId = "wasm-bindgen"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); + } + ]; + devDependencies = [ + { + name = "wasm-bindgen"; + packageId = "wasm-bindgen"; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); + } + ]; + features = { + "arbitrary" = [ "dep:arbitrary" ]; + "atomic" = [ "dep:atomic" ]; + "borsh" = [ "dep:borsh" "dep:borsh-derive" ]; + "bytemuck" = [ "dep:bytemuck" ]; + "default" = [ "std" ]; + "fast-rng" = [ "rng" "dep:rand" ]; + "js" = [ "dep:wasm-bindgen" "dep:js-sys" ]; + "md5" = [ "dep:md-5" ]; + "rng" = [ "dep:getrandom" ]; + "rng-getrandom" = [ "rng" "dep:getrandom" "uuid-rng-internal-lib" "uuid-rng-internal-lib/getrandom" ]; + "rng-rand" = [ "rng" "dep:rand" "uuid-rng-internal-lib" "uuid-rng-internal-lib/rand" ]; + "serde" = [ "dep:serde_core" ]; + "sha1" = [ "dep:sha1_smol" ]; + "slog" = [ "dep:slog" ]; + "std" = [ "wasm-bindgen?/std" "js-sys?/std" ]; + "uuid-rng-internal-lib" = [ "dep:uuid-rng-internal-lib" ]; + "v1" = [ "atomic" ]; + "v3" = [ "md5" ]; + "v4" = [ "rng" ]; + "v5" = [ "sha1" ]; + "v6" = [ "atomic" ]; + "v7" = [ "rng" ]; + "zerocopy" = [ "dep:zerocopy" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "valuable" = rec { crateName = "valuable"; version = "0.1.1"; @@ -12325,9 +12431,9 @@ rec { }; "wasip2" = rec { crateName = "wasip2"; - version = "1.0.2+wasi-0.2.9"; + version = "1.0.3+wasi-0.2.9"; edition = "2021"; - sha256 = "1xdw7v08jpfjdg94sp4lbdgzwa587m5ifpz6fpdnkh02kwizj5wm"; + sha256 = "1mi3w855dz99xzjqc4aa8c9q5b6z1y5c963pkk4cvmr6vdr4c1i0"; dependencies = [ { name = "wit-bindgen"; @@ -12345,9 +12451,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.114"; + version = "0.2.122"; edition = "2021"; - sha256 = "13nkhw552hpllrrmkd2x9y4bmcxr82kdpky2n667kqzcq6jzjck5"; + sha256 = "02flix96brsb2r1i3grnikii302iqpdm337kl3xv5lklz5v4bl1y"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12396,62 +12502,37 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.64"; + version = "0.4.72"; edition = "2021"; - sha256 = "1f3xnr40wwims4zhvh119dhwmffz4h4x82cffi118ri878mm5ig9"; + sha256 = "03qb24gfr072rk8hb69glfdc8yhqqqq2rhy3j5i0ps8sk79dnwwl"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" ]; dependencies = [ - { - name = "cfg-if"; - packageId = "cfg-if"; - } - { - name = "futures-util"; - packageId = "futures-util"; - optional = true; - usesDefaultFeatures = false; - features = [ "std" ]; - } { name = "js-sys"; packageId = "js-sys"; usesDefaultFeatures = false; } - { - name = "once_cell"; - packageId = "once_cell"; - usesDefaultFeatures = false; - } { name = "wasm-bindgen"; packageId = "wasm-bindgen"; usesDefaultFeatures = false; } - { - name = "web-sys"; - packageId = "web-sys"; - usesDefaultFeatures = false; - target = { target, features }: (builtins.elem "atomics" targetFeatures); - features = [ "MessageEvent" "Worker" ]; - } ]; features = { "default" = [ "std" ]; - "futures-core" = [ "dep:futures-core" ]; - "futures-core-03-stream" = [ "futures-core" ]; - "futures-util" = [ "dep:futures-util" ]; - "std" = [ "wasm-bindgen/std" "js-sys/std" "web-sys/std" "futures-util" ]; + "futures-core-03-stream" = [ "js-sys/futures-core-03-stream" ]; + "std" = [ "wasm-bindgen/std" "js-sys/std" ]; }; - resolvedDefaultFeatures = [ "default" "futures-util" "std" ]; + resolvedDefaultFeatures = [ "default" "std" ]; }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.114"; + version = "0.2.122"; edition = "2021"; - sha256 = "1rhq9kkl7n0zjrag9p25xsi4aabpgfkyf02zn4xv6pqhrw7xb8hq"; + sha256 = "1inyl55bvdifx7l60q9wl0ivmw7236jg7jqmcqpxhsx3knq52qci"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12473,9 +12554,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.114"; + version = "0.2.122"; edition = "2021"; - sha256 = "1qriqqjpn922kv5c7f7627fj823k5aifv06j2gvwsiy5map4rkh3"; + sha256 = "0pjw5kc2mbfz59agk5l21kh4hxzp94rygdvsnr4f3z6b5hv4g419"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12509,10 +12590,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.114"; + version = "0.2.122"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "05lc6w64jxlk4wk8rjci4z61lhx2ams90la27a41gvi3qaw2d8vm"; + sha256 = "0ds4mmfqvxwc5fp33hn0jblf0f6b4lghrd9mpkls66zic4n9p4ls"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12527,9 +12608,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.91"; + version = "0.3.99"; edition = "2021"; - sha256 = "1y91r8f4dy4iqgrr03swdzqffz6wmllrgninp8kgpaq4n5xs2jw5"; + sha256 = "0dilfvl9jnyhi4skl6cry9wc300r693j0w82jjbq8yy3rx0i8qkd"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -12613,6 +12694,7 @@ rec { "CssStyleSheet" = [ "StyleSheet" ]; "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ]; "CssTransition" = [ "Animation" "EventTarget" ]; + "CssViewTransitionRule" = [ "CssRule" ]; "CustomEvent" = [ "Event" ]; "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ]; "DelayNode" = [ "AudioNode" "EventTarget" ]; @@ -13019,7 +13101,7 @@ rec { "default" = [ "std" ]; "std" = [ "wasm-bindgen/std" "js-sys/std" ]; }; - resolvedDefaultFeatures = [ "AbortController" "AbortSignal" "Blob" "BlobPropertyBag" "Event" "EventTarget" "File" "FormData" "Headers" "MessageEvent" "ReadableStream" "Request" "RequestCache" "RequestCredentials" "RequestInit" "RequestMode" "Response" "ServiceWorkerGlobalScope" "Window" "Worker" "WorkerGlobalScope" "default" "std" ]; + resolvedDefaultFeatures = [ "AbortController" "AbortSignal" "Blob" "BlobPropertyBag" "EventTarget" "File" "FormData" "Headers" "ReadableStream" "Request" "RequestCache" "RequestCredentials" "RequestInit" "RequestMode" "Response" "ServiceWorkerGlobalScope" "Window" "WorkerGlobalScope" "default" "std" ]; }; "web-time" = rec { crateName = "web-time"; @@ -13193,7 +13275,7 @@ rec { dependencies = [ { name = "windows-targets"; - packageId = "windows-targets 0.52.6"; + packageId = "windows-targets"; } ]; features = { @@ -13429,19 +13511,16 @@ rec { }; resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_System" "Win32_System_Threading" "default" ]; }; - "windows-sys 0.60.2" = rec { + "windows-sys 0.61.2" = rec { crateName = "windows-sys"; - version = "0.60.2"; + version = "0.61.2"; edition = "2021"; - sha256 = "1jrbc615ihqnhjhxplr2kw7rasrskv9wj3lr80hgfd42sbj01xgj"; + sha256 = "1z7k3y9b6b5h52kid57lvmvm05362zv1v8w0gc7xyv5xphlp44xf"; libName = "windows_sys"; - authors = [ - "Microsoft" - ]; dependencies = [ { - name = "windows-targets"; - packageId = "windows-targets 0.53.5"; + name = "windows-link"; + packageId = "windows-link"; usesDefaultFeatures = false; } ]; @@ -13692,380 +13771,62 @@ rec { "Win32_Web" = [ "Win32" ]; "Win32_Web_InternetExplorer" = [ "Win32_Web" ]; }; - resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_System" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" "default" ]; + resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "default" ]; }; - "windows-sys 0.61.2" = rec { - crateName = "windows-sys"; - version = "0.61.2"; + "windows-targets" = rec { + crateName = "windows-targets"; + version = "0.52.6"; edition = "2021"; - sha256 = "1z7k3y9b6b5h52kid57lvmvm05362zv1v8w0gc7xyv5xphlp44xf"; - libName = "windows_sys"; + sha256 = "0wwrx625nwlfp7k93r2rra568gad1mwd888h1jwnl0vfg5r4ywlv"; + libName = "windows_targets"; + authors = [ + "Microsoft" + ]; dependencies = [ { - name = "windows-link"; - packageId = "windows-link"; - usesDefaultFeatures = false; - } - ]; - features = { - "Wdk" = [ "Win32_Foundation" ]; - "Wdk_Devices" = [ "Wdk" ]; - "Wdk_Devices_Bluetooth" = [ "Wdk_Devices" ]; - "Wdk_Devices_HumanInterfaceDevice" = [ "Wdk_Devices" ]; - "Wdk_Foundation" = [ "Wdk" ]; - "Wdk_Graphics" = [ "Wdk" ]; - "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ]; - "Wdk_NetworkManagement" = [ "Wdk" ]; - "Wdk_NetworkManagement_Ndis" = [ "Wdk_NetworkManagement" ]; - "Wdk_NetworkManagement_WindowsFilteringPlatform" = [ "Wdk_NetworkManagement" ]; - "Wdk_Storage" = [ "Wdk" ]; - "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ]; - "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ]; - "Wdk_System" = [ "Wdk" ]; - "Wdk_System_IO" = [ "Wdk_System" ]; - "Wdk_System_Memory" = [ "Wdk_System" ]; - "Wdk_System_OfflineRegistry" = [ "Wdk_System" ]; - "Wdk_System_Registry" = [ "Wdk_System" ]; - "Wdk_System_SystemInformation" = [ "Wdk_System" ]; - "Wdk_System_SystemServices" = [ "Wdk_System" ]; - "Wdk_System_Threading" = [ "Wdk_System" ]; - "Win32" = [ "Win32_Foundation" ]; - "Win32_Data" = [ "Win32" ]; - "Win32_Data_HtmlHelp" = [ "Win32_Data" ]; - "Win32_Data_RightsManagement" = [ "Win32_Data" ]; - "Win32_Devices" = [ "Win32" ]; - "Win32_Devices_AllJoyn" = [ "Win32_Devices" ]; - "Win32_Devices_Beep" = [ "Win32_Devices" ]; - "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ]; - "Win32_Devices_Bluetooth" = [ "Win32_Devices" ]; - "Win32_Devices_Cdrom" = [ "Win32_Devices" ]; - "Win32_Devices_Communication" = [ "Win32_Devices" ]; - "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ]; - "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ]; - "Win32_Devices_Display" = [ "Win32_Devices" ]; - "Win32_Devices_Dvd" = [ "Win32_Devices" ]; - "Win32_Devices_Enumeration" = [ "Win32_Devices" ]; - "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ]; - "Win32_Devices_Fax" = [ "Win32_Devices" ]; - "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ]; - "Win32_Devices_Nfc" = [ "Win32_Devices" ]; - "Win32_Devices_Nfp" = [ "Win32_Devices" ]; - "Win32_Devices_PortableDevices" = [ "Win32_Devices" ]; - "Win32_Devices_Properties" = [ "Win32_Devices" ]; - "Win32_Devices_Pwm" = [ "Win32_Devices" ]; - "Win32_Devices_Sensors" = [ "Win32_Devices" ]; - "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ]; - "Win32_Devices_Tapi" = [ "Win32_Devices" ]; - "Win32_Devices_Usb" = [ "Win32_Devices" ]; - "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ]; - "Win32_Foundation" = [ "Win32" ]; - "Win32_Gaming" = [ "Win32" ]; - "Win32_Globalization" = [ "Win32" ]; - "Win32_Graphics" = [ "Win32" ]; - "Win32_Graphics_Dwm" = [ "Win32_Graphics" ]; - "Win32_Graphics_Gdi" = [ "Win32_Graphics" ]; - "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ]; - "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ]; - "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ]; - "Win32_Graphics_Printing" = [ "Win32_Graphics" ]; - "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ]; - "Win32_Management" = [ "Win32" ]; - "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ]; - "Win32_Media" = [ "Win32" ]; - "Win32_Media_Audio" = [ "Win32_Media" ]; - "Win32_Media_DxMediaObjects" = [ "Win32_Media" ]; - "Win32_Media_KernelStreaming" = [ "Win32_Media" ]; - "Win32_Media_Multimedia" = [ "Win32_Media" ]; - "Win32_Media_Streaming" = [ "Win32_Media" ]; - "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ]; - "Win32_NetworkManagement" = [ "Win32" ]; - "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ]; - "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ]; - "Win32_Networking" = [ "Win32" ]; - "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ]; - "Win32_Networking_Clustering" = [ "Win32_Networking" ]; - "Win32_Networking_HttpServer" = [ "Win32_Networking" ]; - "Win32_Networking_Ldap" = [ "Win32_Networking" ]; - "Win32_Networking_WebSocket" = [ "Win32_Networking" ]; - "Win32_Networking_WinHttp" = [ "Win32_Networking" ]; - "Win32_Networking_WinInet" = [ "Win32_Networking" ]; - "Win32_Networking_WinSock" = [ "Win32_Networking" ]; - "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ]; - "Win32_Security" = [ "Win32" ]; - "Win32_Security_AppLocker" = [ "Win32_Security" ]; - "Win32_Security_Authentication" = [ "Win32_Security" ]; - "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ]; - "Win32_Security_Authorization" = [ "Win32_Security" ]; - "Win32_Security_Credentials" = [ "Win32_Security" ]; - "Win32_Security_Cryptography" = [ "Win32_Security" ]; - "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ]; - "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ]; - "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ]; - "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ]; - "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ]; - "Win32_Security_DirectoryServices" = [ "Win32_Security" ]; - "Win32_Security_EnterpriseData" = [ "Win32_Security" ]; - "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ]; - "Win32_Security_Isolation" = [ "Win32_Security" ]; - "Win32_Security_LicenseProtection" = [ "Win32_Security" ]; - "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ]; - "Win32_Security_WinTrust" = [ "Win32_Security" ]; - "Win32_Security_WinWlx" = [ "Win32_Security" ]; - "Win32_Storage" = [ "Win32" ]; - "Win32_Storage_Cabinets" = [ "Win32_Storage" ]; - "Win32_Storage_CloudFilters" = [ "Win32_Storage" ]; - "Win32_Storage_Compression" = [ "Win32_Storage" ]; - "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ]; - "Win32_Storage_FileHistory" = [ "Win32_Storage" ]; - "Win32_Storage_FileSystem" = [ "Win32_Storage" ]; - "Win32_Storage_Imapi" = [ "Win32_Storage" ]; - "Win32_Storage_IndexServer" = [ "Win32_Storage" ]; - "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ]; - "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ]; - "Win32_Storage_Jet" = [ "Win32_Storage" ]; - "Win32_Storage_Nvme" = [ "Win32_Storage" ]; - "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ]; - "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ]; - "Win32_Storage_Packaging" = [ "Win32_Storage" ]; - "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ]; - "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ]; - "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ]; - "Win32_Storage_Vhd" = [ "Win32_Storage" ]; - "Win32_Storage_Xps" = [ "Win32_Storage" ]; - "Win32_System" = [ "Win32" ]; - "Win32_System_AddressBook" = [ "Win32_System" ]; - "Win32_System_Antimalware" = [ "Win32_System" ]; - "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ]; - "Win32_System_ApplicationVerifier" = [ "Win32_System" ]; - "Win32_System_ClrHosting" = [ "Win32_System" ]; - "Win32_System_Com" = [ "Win32_System" ]; - "Win32_System_Com_Marshal" = [ "Win32_System_Com" ]; - "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ]; - "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ]; - "Win32_System_ComponentServices" = [ "Win32_System" ]; - "Win32_System_Console" = [ "Win32_System" ]; - "Win32_System_CorrelationVector" = [ "Win32_System" ]; - "Win32_System_DataExchange" = [ "Win32_System" ]; - "Win32_System_DeploymentServices" = [ "Win32_System" ]; - "Win32_System_DeveloperLicensing" = [ "Win32_System" ]; - "Win32_System_Diagnostics" = [ "Win32_System" ]; - "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ]; - "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ]; - "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ]; - "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ]; - "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ]; - "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ]; - "Win32_System_Diagnostics_TraceLogging" = [ "Win32_System_Diagnostics" ]; - "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ]; - "Win32_System_Environment" = [ "Win32_System" ]; - "Win32_System_ErrorReporting" = [ "Win32_System" ]; - "Win32_System_EventCollector" = [ "Win32_System" ]; - "Win32_System_EventLog" = [ "Win32_System" ]; - "Win32_System_EventNotificationService" = [ "Win32_System" ]; - "Win32_System_GroupPolicy" = [ "Win32_System" ]; - "Win32_System_HostCompute" = [ "Win32_System" ]; - "Win32_System_HostComputeNetwork" = [ "Win32_System" ]; - "Win32_System_HostComputeSystem" = [ "Win32_System" ]; - "Win32_System_Hypervisor" = [ "Win32_System" ]; - "Win32_System_IO" = [ "Win32_System" ]; - "Win32_System_Iis" = [ "Win32_System" ]; - "Win32_System_Ioctl" = [ "Win32_System" ]; - "Win32_System_JobObjects" = [ "Win32_System" ]; - "Win32_System_Js" = [ "Win32_System" ]; - "Win32_System_Kernel" = [ "Win32_System" ]; - "Win32_System_LibraryLoader" = [ "Win32_System" ]; - "Win32_System_Mailslots" = [ "Win32_System" ]; - "Win32_System_Mapi" = [ "Win32_System" ]; - "Win32_System_Memory" = [ "Win32_System" ]; - "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ]; - "Win32_System_MessageQueuing" = [ "Win32_System" ]; - "Win32_System_MixedReality" = [ "Win32_System" ]; - "Win32_System_Ole" = [ "Win32_System" ]; - "Win32_System_PasswordManagement" = [ "Win32_System" ]; - "Win32_System_Performance" = [ "Win32_System" ]; - "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ]; - "Win32_System_Pipes" = [ "Win32_System" ]; - "Win32_System_Power" = [ "Win32_System" ]; - "Win32_System_ProcessStatus" = [ "Win32_System" ]; - "Win32_System_Recovery" = [ "Win32_System" ]; - "Win32_System_Registry" = [ "Win32_System" ]; - "Win32_System_RemoteDesktop" = [ "Win32_System" ]; - "Win32_System_RemoteManagement" = [ "Win32_System" ]; - "Win32_System_RestartManager" = [ "Win32_System" ]; - "Win32_System_Restore" = [ "Win32_System" ]; - "Win32_System_Rpc" = [ "Win32_System" ]; - "Win32_System_Search" = [ "Win32_System" ]; - "Win32_System_Search_Common" = [ "Win32_System_Search" ]; - "Win32_System_SecurityCenter" = [ "Win32_System" ]; - "Win32_System_Services" = [ "Win32_System" ]; - "Win32_System_SetupAndMigration" = [ "Win32_System" ]; - "Win32_System_Shutdown" = [ "Win32_System" ]; - "Win32_System_StationsAndDesktops" = [ "Win32_System" ]; - "Win32_System_SubsystemForLinux" = [ "Win32_System" ]; - "Win32_System_SystemInformation" = [ "Win32_System" ]; - "Win32_System_SystemServices" = [ "Win32_System" ]; - "Win32_System_Threading" = [ "Win32_System" ]; - "Win32_System_Time" = [ "Win32_System" ]; - "Win32_System_TpmBaseServices" = [ "Win32_System" ]; - "Win32_System_UserAccessLogging" = [ "Win32_System" ]; - "Win32_System_Variant" = [ "Win32_System" ]; - "Win32_System_VirtualDosMachines" = [ "Win32_System" ]; - "Win32_System_WindowsProgramming" = [ "Win32_System" ]; - "Win32_System_Wmi" = [ "Win32_System" ]; - "Win32_UI" = [ "Win32" ]; - "Win32_UI_Accessibility" = [ "Win32_UI" ]; - "Win32_UI_ColorSystem" = [ "Win32_UI" ]; - "Win32_UI_Controls" = [ "Win32_UI" ]; - "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ]; - "Win32_UI_HiDpi" = [ "Win32_UI" ]; - "Win32_UI_Input" = [ "Win32_UI" ]; - "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ]; - "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ]; - "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ]; - "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ]; - "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ]; - "Win32_UI_InteractionContext" = [ "Win32_UI" ]; - "Win32_UI_Magnification" = [ "Win32_UI" ]; - "Win32_UI_Shell" = [ "Win32_UI" ]; - "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ]; - "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ]; - "Win32_UI_TabletPC" = [ "Win32_UI" ]; - "Win32_UI_TextServices" = [ "Win32_UI" ]; - "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ]; - "Win32_Web" = [ "Win32" ]; - "Win32_Web_InternetExplorer" = [ "Win32_Web" ]; - }; - resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Time" "Win32_System_WindowsProgramming" "default" ]; - }; - "windows-targets 0.52.6" = rec { - crateName = "windows-targets"; - version = "0.52.6"; - edition = "2021"; - sha256 = "0wwrx625nwlfp7k93r2rra568gad1mwd888h1jwnl0vfg5r4ywlv"; - libName = "windows_targets"; - authors = [ - "Microsoft" - ]; - dependencies = [ - { - name = "windows_aarch64_gnullvm"; - packageId = "windows_aarch64_gnullvm 0.52.6"; - target = { target, features }: (target.name == "aarch64-pc-windows-gnullvm"); - } - { - name = "windows_aarch64_msvc"; - packageId = "windows_aarch64_msvc 0.52.6"; - target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); - } - { - name = "windows_i686_gnu"; - packageId = "windows_i686_gnu 0.52.6"; - target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false))); - } - { - name = "windows_i686_gnullvm"; - packageId = "windows_i686_gnullvm 0.52.6"; - target = { target, features }: (target.name == "i686-pc-windows-gnullvm"); - } - { - name = "windows_i686_msvc"; - packageId = "windows_i686_msvc 0.52.6"; - target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); - } - { - name = "windows_x86_64_gnu"; - packageId = "windows_x86_64_gnu 0.52.6"; - target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false))); - } - { - name = "windows_x86_64_gnullvm"; - packageId = "windows_x86_64_gnullvm 0.52.6"; - target = { target, features }: (target.name == "x86_64-pc-windows-gnullvm"); - } - { - name = "windows_x86_64_msvc"; - packageId = "windows_x86_64_msvc 0.52.6"; - target = { target, features }: ((("x86_64" == target."arch" or null) || ("arm64ec" == target."arch" or null)) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); - } - ]; - - }; - "windows-targets 0.53.5" = rec { - crateName = "windows-targets"; - version = "0.53.5"; - edition = "2021"; - sha256 = "1wv9j2gv3l6wj3gkw5j1kr6ymb5q6dfc42yvydjhv3mqa7szjia9"; - libName = "windows_targets"; - dependencies = [ - { - name = "windows-link"; - packageId = "windows-link"; - usesDefaultFeatures = false; - target = { target, features }: (target."windows_raw_dylib" or false); - } - { - name = "windows_aarch64_gnullvm"; - packageId = "windows_aarch64_gnullvm 0.53.1"; - target = { target, features }: (target.name == "aarch64-pc-windows-gnullvm"); + name = "windows_aarch64_gnullvm"; + packageId = "windows_aarch64_gnullvm"; + target = { target, features }: (target.name == "aarch64-pc-windows-gnullvm"); } { name = "windows_aarch64_msvc"; - packageId = "windows_aarch64_msvc 0.53.1"; + packageId = "windows_aarch64_msvc"; target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); } { name = "windows_i686_gnu"; - packageId = "windows_i686_gnu 0.53.1"; + packageId = "windows_i686_gnu"; target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false))); } { name = "windows_i686_gnullvm"; - packageId = "windows_i686_gnullvm 0.53.1"; + packageId = "windows_i686_gnullvm"; target = { target, features }: (target.name == "i686-pc-windows-gnullvm"); } { name = "windows_i686_msvc"; - packageId = "windows_i686_msvc 0.53.1"; + packageId = "windows_i686_msvc"; target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); } { name = "windows_x86_64_gnu"; - packageId = "windows_x86_64_gnu 0.53.1"; + packageId = "windows_x86_64_gnu"; target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false))); } { name = "windows_x86_64_gnullvm"; - packageId = "windows_x86_64_gnullvm 0.53.1"; + packageId = "windows_x86_64_gnullvm"; target = { target, features }: (target.name == "x86_64-pc-windows-gnullvm"); } { name = "windows_x86_64_msvc"; - packageId = "windows_x86_64_msvc 0.53.1"; + packageId = "windows_x86_64_msvc"; target = { target, features }: ((("x86_64" == target."arch" or null) || ("arm64ec" == target."arch" or null)) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false))); } ]; }; - "windows_aarch64_gnullvm 0.52.6" = rec { + "windows_aarch64_gnullvm" = rec { crateName = "windows_aarch64_gnullvm"; version = "0.52.6"; edition = "2021"; @@ -14075,14 +13836,7 @@ rec { ]; }; - "windows_aarch64_gnullvm 0.53.1" = rec { - crateName = "windows_aarch64_gnullvm"; - version = "0.53.1"; - edition = "2021"; - sha256 = "0lqvdm510mka9w26vmga7hbkmrw9glzc90l4gya5qbxlm1pl3n59"; - - }; - "windows_aarch64_msvc 0.52.6" = rec { + "windows_aarch64_msvc" = rec { crateName = "windows_aarch64_msvc"; version = "0.52.6"; edition = "2021"; @@ -14092,14 +13846,7 @@ rec { ]; }; - "windows_aarch64_msvc 0.53.1" = rec { - crateName = "windows_aarch64_msvc"; - version = "0.53.1"; - edition = "2021"; - sha256 = "01jh2adlwx043rji888b22whx4bm8alrk3khjpik5xn20kl85mxr"; - - }; - "windows_i686_gnu 0.52.6" = rec { + "windows_i686_gnu" = rec { crateName = "windows_i686_gnu"; version = "0.52.6"; edition = "2021"; @@ -14109,14 +13856,7 @@ rec { ]; }; - "windows_i686_gnu 0.53.1" = rec { - crateName = "windows_i686_gnu"; - version = "0.53.1"; - edition = "2021"; - sha256 = "18wkcm82ldyg4figcsidzwbg1pqd49jpm98crfz0j7nqd6h6s3ln"; - - }; - "windows_i686_gnullvm 0.52.6" = rec { + "windows_i686_gnullvm" = rec { crateName = "windows_i686_gnullvm"; version = "0.52.6"; edition = "2021"; @@ -14126,14 +13866,7 @@ rec { ]; }; - "windows_i686_gnullvm 0.53.1" = rec { - crateName = "windows_i686_gnullvm"; - version = "0.53.1"; - edition = "2021"; - sha256 = "030qaxqc4salz6l4immfb6sykc6gmhyir9wzn2w8mxj8038mjwzs"; - - }; - "windows_i686_msvc 0.52.6" = rec { + "windows_i686_msvc" = rec { crateName = "windows_i686_msvc"; version = "0.52.6"; edition = "2021"; @@ -14143,14 +13876,7 @@ rec { ]; }; - "windows_i686_msvc 0.53.1" = rec { - crateName = "windows_i686_msvc"; - version = "0.53.1"; - edition = "2021"; - sha256 = "1hi6scw3mn2pbdl30ji5i4y8vvspb9b66l98kkz350pig58wfyhy"; - - }; - "windows_x86_64_gnu 0.52.6" = rec { + "windows_x86_64_gnu" = rec { crateName = "windows_x86_64_gnu"; version = "0.52.6"; edition = "2021"; @@ -14160,14 +13886,7 @@ rec { ]; }; - "windows_x86_64_gnu 0.53.1" = rec { - crateName = "windows_x86_64_gnu"; - version = "0.53.1"; - edition = "2021"; - sha256 = "16d4yiysmfdlsrghndr97y57gh3kljkwhfdbcs05m1jasz6l4f4w"; - - }; - "windows_x86_64_gnullvm 0.52.6" = rec { + "windows_x86_64_gnullvm" = rec { crateName = "windows_x86_64_gnullvm"; version = "0.52.6"; edition = "2021"; @@ -14177,14 +13896,7 @@ rec { ]; }; - "windows_x86_64_gnullvm 0.53.1" = rec { - crateName = "windows_x86_64_gnullvm"; - version = "0.53.1"; - edition = "2021"; - sha256 = "1qbspgv4g3q0vygkg8rnql5c6z3caqv38japiynyivh75ng1gyhg"; - - }; - "windows_x86_64_msvc 0.52.6" = rec { + "windows_x86_64_msvc" = rec { crateName = "windows_x86_64_msvc"; version = "0.52.6"; edition = "2021"; @@ -14193,19 +13905,12 @@ rec { "Microsoft" ]; - }; - "windows_x86_64_msvc 0.53.1" = rec { - crateName = "windows_x86_64_msvc"; - version = "0.53.1"; - edition = "2021"; - sha256 = "0l6npq76vlq4ksn4bwsncpr8508mk0gmznm6wnhjg95d19gzzfyn"; - }; "winnow" = rec { crateName = "winnow"; - version = "0.7.14"; + version = "1.0.3"; edition = "2021"; - sha256 = "0a88ahjqhyn2ln1yplq2xsigm09kxqkdkkk2c2mfxkbzszln8lss"; + sha256 = "1wajycd3krn6h699vydjv7hm0ll5l31p899qzpk59y2is74y34h5"; dependencies = [ { name = "memchr"; @@ -14215,38 +13920,42 @@ rec { } ]; features = { + "ascii" = [ "parser" ]; + "binary" = [ "parser" ]; "debug" = [ "std" "dep:anstream" "dep:anstyle" "dep:is_terminal_polyfill" "dep:terminal_size" ]; - "default" = [ "std" ]; + "default" = [ "std" "ascii" "binary" ]; "simd" = [ "dep:memchr" ]; "std" = [ "alloc" "memchr?/std" ]; - "unstable-doc" = [ "alloc" "std" "simd" "unstable-recover" ]; + "unstable-doc" = [ "alloc" "std" "ascii" "binary" "simd" "unstable-recover" ]; + "unstable-recover" = [ "parser" ]; }; - resolvedDefaultFeatures = [ "alloc" "default" "std" ]; + resolvedDefaultFeatures = [ "alloc" "ascii" "binary" "default" "parser" "std" ]; }; "wit-bindgen" = rec { crateName = "wit-bindgen"; - version = "0.51.0"; + version = "0.57.1"; edition = "2024"; - sha256 = "19fazgch8sq5cvjv3ynhhfh5d5x08jq2pkw8jfb05vbcyqcr496p"; + sha256 = "0vjk2jb593ri9k1aq4iqs2si9mrw5q46wxnn78im7hm7hx799gqy"; libName = "wit_bindgen"; authors = [ "Alex Crichton " ]; features = { - "async" = [ "std" "wit-bindgen-rust-macro?/async" ]; - "async-spawn" = [ "async" "dep:futures" ]; + "async-spawn" = [ "async" "dep:futures" "std" ]; "bitflags" = [ "dep:bitflags" ]; - "default" = [ "macros" "realloc" "async" "std" "bitflags" ]; + "default" = [ "macros" "realloc" "async" "std" "bitflags" "macro-string" ]; + "futures-stream" = [ "async" "dep:futures" ]; "inter-task-wakeup" = [ "async" ]; + "macro-string" = [ "wit-bindgen-rust-macro?/macro-string" ]; "macros" = [ "dep:wit-bindgen-rust-macro" ]; "rustc-dep-of-std" = [ "dep:core" "dep:alloc" ]; }; }; "writeable" = rec { crateName = "writeable"; - version = "0.6.2"; + version = "0.6.3"; edition = "2021"; - sha256 = "1fg08y97n6vk7l0rnjggw3xyrii6dcqg54wqaxldrlk98zdy1pcy"; + sha256 = "1i54d13h9bpap2hf13xcry1s4lxh7ap3923g8f3c0grd7c9fbyhz"; authors = [ "The ICU4X Project Developers" ]; @@ -14313,9 +14022,9 @@ rec { }; "xml" = rec { crateName = "xml"; - version = "1.2.1"; + version = "1.3.0"; edition = "2021"; - sha256 = "0ak4k990faralbli5a0rb8kvwihccb2rp0r94d4azfy94a6lkamq"; + sha256 = "128s58qhq8whrx90zbw8r5algr7lakgbf7mn05jfk234rbjqavv3"; authors = [ "Vladimir Matveev " "Kornel (https://github.com/kornelski)" @@ -14324,9 +14033,9 @@ rec { }; "yoke" = rec { crateName = "yoke"; - version = "0.8.1"; + version = "0.8.3"; edition = "2021"; - sha256 = "0m29dm0bf5iakxgma0bj6dbmc3b8qi9b1vaw9sa76kdqmz3fbmkj"; + sha256 = "1xgyj6c2lxj2bp891ynmhws87c6z7yyv2li1v0ss9di40hxf57vh"; authors = [ "Manish Goregaokar " ]; @@ -14359,9 +14068,9 @@ rec { }; "yoke-derive" = rec { crateName = "yoke-derive"; - version = "0.8.1"; + version = "0.8.2"; edition = "2021"; - sha256 = "0pbyja133jnng4mrhimzdq4a0y26421g734ybgz8wsgbfhl0andn"; + sha256 = "13l5y5sz4lqm7rmyakjbh6vwgikxiql51xfff9hq2j485hk4r16y"; procMacro = true; libName = "yoke_derive"; authors = [ @@ -14390,9 +14099,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.40"; + version = "0.8.50"; edition = "2021"; - sha256 = "1r9j2mlb54q1l9pgall3mk0gg6cprhdncvbbgsgxnxmmj3jcd2d7"; + sha256 = "1laahnfxs4qyfb1fdf5nbb2qfshi72b1hbi0ffp2zy2m1r7ms1iv"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -14426,9 +14135,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.40"; + version = "0.8.50"; edition = "2021"; - sha256 = "0lsrhg5nvf0c40z644a014l2nrvh7xw0ff3i9744k9vif2d4hp7n"; + sha256 = "0fdnr9qslx1hbn2i9rsvy9s95mychfy2vj90ajsjm2basccinqqb"; procMacro = true; libName = "zerocopy_derive"; authors = [ @@ -14461,11 +14170,11 @@ rec { }; "zerofrom" = rec { crateName = "zerofrom"; - version = "0.1.6"; + version = "0.1.8"; edition = "2021"; - sha256 = "19dyky67zkjichsb7ykhv0aqws3q0jfvzww76l66c19y6gh45k2h"; + sha256 = "0wjjdj7gdmd0iq91gzkxl7dlv0nhkk80l4bmdpzh3a1yh48mmh0f"; authors = [ - "Manish Goregaokar " + "The ICU4X Project Developers" ]; dependencies = [ { @@ -14483,9 +14192,9 @@ rec { }; "zerofrom-derive" = rec { crateName = "zerofrom-derive"; - version = "0.1.6"; + version = "0.1.7"; edition = "2021"; - sha256 = "00l5niw7c1b0lf1vhvajpjmcnbdp2vn96jg4nmkhq2db0rp5s7np"; + sha256 = "18c4wsnznhdxx6m80piil1lbyszdiwsshgjrybqcm4b6qic22lqi"; procMacro = true; libName = "zerofrom_derive"; authors = [ @@ -14564,9 +14273,9 @@ rec { }; "zerotrie" = rec { crateName = "zerotrie"; - version = "0.2.3"; + version = "0.2.4"; edition = "2021"; - sha256 = "0lbqznlqazmrwwzslw0ci7p3pqxykrbfhq29npj0gmb2amxc2n9a"; + sha256 = "1gr0pkcn3qsr6in6iixqyp0vbzwf2j1jzyvh7yl2yydh3p9m548g"; authors = [ "The ICU4X Project Developers" ]; @@ -14591,7 +14300,9 @@ rec { } ]; features = { + "alloc" = [ "zerovec?/alloc" ]; "databake" = [ "dep:databake" "zerovec?/databake" ]; + "dense" = [ "dep:zerovec" ]; "litemap" = [ "dep:litemap" "alloc" ]; "serde" = [ "dep:serde_core" "dep:litemap" "alloc" "litemap/serde" "zerovec?/serde" ]; "yoke" = [ "dep:yoke" ]; @@ -14602,9 +14313,9 @@ rec { }; "zerovec" = rec { crateName = "zerovec"; - version = "0.11.5"; + version = "0.11.6"; edition = "2021"; - sha256 = "00m0p47k2g9mkv505hky5xh3r6ps7v8qc0dy4pspg542jj972a3c"; + sha256 = "0fdjsy6b31q9i0d73sl7xjd12xadbwi45lkpfgqnmasrqg5i3ych"; authors = [ "The ICU4X Project Developers" ]; @@ -14627,11 +14338,20 @@ rec { usesDefaultFeatures = false; } ]; + devDependencies = [ + { + name = "yoke"; + packageId = "yoke"; + usesDefaultFeatures = false; + features = [ "derive" ]; + } + ]; features = { "alloc" = [ "serde?/alloc" ]; "databake" = [ "dep:databake" ]; "derive" = [ "dep:zerovec-derive" ]; "hashmap" = [ "dep:twox-hash" "alloc" ]; + "schemars" = [ "dep:schemars" "alloc" ]; "serde" = [ "dep:serde" ]; "yoke" = [ "dep:yoke" ]; }; @@ -14639,9 +14359,9 @@ rec { }; "zerovec-derive" = rec { crateName = "zerovec-derive"; - version = "0.11.2"; + version = "0.11.3"; edition = "2021"; - sha256 = "1wsig4h5j7a1scd5hrlnragnazjny9qjc44hancb6p6a76ay7p7a"; + sha256 = "0m85qj92mmfvhjra6ziqky5b1p4kcmp5069k7kfadp5hr8jw8pb2"; procMacro = true; libName = "zerovec_derive"; authors = [ diff --git a/Cargo.toml b/Cargo.toml index 8e405087..e49f57f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ edition = "2021" repository = "https://github.com/stackabletech/spark-k8s-operator" [workspace.dependencies] -product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.8.0" } stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.111.1", features = ["webhook"] } anyhow = "1.0" @@ -18,6 +17,7 @@ built = { version = "0.8", features = ["chrono", "git2"] } clap = "4.5" const_format = "0.2" futures = { version = "0.3", features = ["compat"] } +java-properties = "2.0" rstest = "0.26" semver = "1.0" serde = { version = "1.0", features = ["derive"] } @@ -32,5 +32,5 @@ indoc = "2" regex = "1" [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } -# stackable-operator = { path = "../operator-rs/crates/stackable-operator" } +# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch="smooth-operator" } +stackable-operator = { path = "../operator-rs/crates/stackable-operator" } diff --git a/crate-hashes.json b/crate-hashes.json index 86f2b840..ceeb77ce 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,3 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#k8s-version@0.1.3": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-certs@0.4.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-operator-derive@0.3.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-operator@0.111.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-shared@0.1.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-telemetry@0.6.3": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-versioned-macros@0.10.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-versioned@0.10.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-webhook@0.9.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/deploy/config-spec/properties.yaml b/deploy/config-spec/properties.yaml index f25e830e..739b9ee5 100644 --- a/deploy/config-spec/properties.yaml +++ b/deploy/config-spec/properties.yaml @@ -1,51 +1,5 @@ --- -# Not used. Kept for compatibilty with Dockerfile. +# Not used at runtime. Kept as a compatibility stub for external references. version: 0.1.0 spec: units: [] -properties: - - property: &jvmDnsCacheTtl - propertyNames: - - name: "networkaddress.cache.ttl" - kind: - type: "file" - file: "security.properties" - datatype: - type: "integer" - min: "0" - recommendedValues: - - fromVersion: "0.0.0" - value: "30" - roles: - - name: "submit" - required: true - - name: "driver" - required: true - - name: "executor" - required: true - asOfVersion: "0.0.0" - comment: "History server - TTL for successfully resolved domain names." - description: "History server - TTL for successfully resolved domain names." - - - property: &jvmDnsCacheNegativeTtl - propertyNames: - - name: "networkaddress.cache.negative.ttl" - kind: - type: "file" - file: "security.properties" - datatype: - type: "integer" - min: "0" - recommendedValues: - - fromVersion: "0.0.0" - value: "0" - roles: - - name: "submit" - required: true - - name: "driver" - required: true - - name: "executor" - required: true - asOfVersion: "0.0.0" - comment: "History server - TTL for domain names that cannot be resolved." - description: "History server - TTL for domain names that cannot be resolved." diff --git a/deploy/helm/spark-k8s-operator/configs/properties.yaml b/deploy/helm/spark-k8s-operator/configs/properties.yaml index f25e830e..739b9ee5 100644 --- a/deploy/helm/spark-k8s-operator/configs/properties.yaml +++ b/deploy/helm/spark-k8s-operator/configs/properties.yaml @@ -1,51 +1,5 @@ --- -# Not used. Kept for compatibilty with Dockerfile. +# Not used at runtime. Kept as a compatibility stub for external references. version: 0.1.0 spec: units: [] -properties: - - property: &jvmDnsCacheTtl - propertyNames: - - name: "networkaddress.cache.ttl" - kind: - type: "file" - file: "security.properties" - datatype: - type: "integer" - min: "0" - recommendedValues: - - fromVersion: "0.0.0" - value: "30" - roles: - - name: "submit" - required: true - - name: "driver" - required: true - - name: "executor" - required: true - asOfVersion: "0.0.0" - comment: "History server - TTL for successfully resolved domain names." - description: "History server - TTL for successfully resolved domain names." - - - property: &jvmDnsCacheNegativeTtl - propertyNames: - - name: "networkaddress.cache.negative.ttl" - kind: - type: "file" - file: "security.properties" - datatype: - type: "integer" - min: "0" - recommendedValues: - - fromVersion: "0.0.0" - value: "0" - roles: - - name: "submit" - required: true - - name: "driver" - required: true - - name: "executor" - required: true - asOfVersion: "0.0.0" - comment: "History server - TTL for domain names that cannot be resolved." - description: "History server - TTL for domain names that cannot be resolved." diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 608ac35b..667520c9 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true publish = false [dependencies] -product-config.workspace = true stackable-operator.workspace = true anyhow.workspace = true @@ -17,6 +16,7 @@ const_format.workspace = true semver.workspace = true serde.workspace = true serde_json.workspace = true +java-properties.workspace = true serde_yaml.workspace = true snafu.workspace = true strum.workspace = true diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 271c6d99..cb7d7920 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -1 +1,2 @@ pub mod jvm; +pub mod writer; diff --git a/rust/operator-binary/src/config/writer.rs b/rust/operator-binary/src/config/writer.rs new file mode 100644 index 00000000..3591a1b2 --- /dev/null +++ b/rust/operator-binary/src/config/writer.rs @@ -0,0 +1,46 @@ +//! Writers for Java `.properties` files. +//! +//! This is adapted from the `product-config` writer so we can render +//! configuration files without depending on the `product-config` crate. + +use std::io::Write; + +use java_properties::{PropertiesError, PropertiesWriter}; +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum PropertiesWriterError { + #[snafu(display("failed to create properties file"))] + Properties { source: PropertiesError }, + + #[snafu(display("failed to convert properties file byte array to UTF-8"))] + FromUtf8 { source: std::string::FromUtf8Error }, +} + +/// Creates a common Java properties file string in the format: +/// `property_1=value_1\nproperty_2=value_2\n`. +pub fn to_java_properties_string<'a, T>(properties: T) -> Result +where + T: Iterator)>, +{ + let mut output = Vec::new(); + write_java_properties(&mut output, properties)?; + String::from_utf8(output).context(FromUtf8Snafu) +} + +/// Writes Java properties to the given writer. A `None` value is written as an +/// empty value (`key=`). +fn write_java_properties<'a, W, T>(writer: W, properties: T) -> Result<(), PropertiesWriterError> +where + W: Write, + T: Iterator)>, +{ + let mut writer = PropertiesWriter::new(writer); + for (key, value) in properties { + let property_value = value.as_deref().unwrap_or_default(); + writer.write(key, property_value).context(PropertiesSnafu)?; + } + writer.flush().context(PropertiesSnafu)?; + + Ok(()) +} diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index bc190fde..40b14a8a 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; -use product_config::writer::to_java_properties_string; use snafu::{ResultExt, Snafu}; use stackable_operator::{ kvp::ObjectLabels, @@ -10,11 +9,16 @@ use strum::Display; use super::crd::CONNECT_EXECUTOR_ROLE_NAME; use crate::{ + config::writer::{PropertiesWriterError, to_java_properties_string}, connect::crd::{ CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, CONNECT_SERVER_ROLE_NAME, DEFAULT_SPARK_CONNECT_GROUP_NAME, }, - crd::constants::OPERATOR_NAME, + crd::constants::{ + DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, + OPERATOR_NAME, + }, }; #[derive(Snafu, Debug)] @@ -26,19 +30,13 @@ pub enum Error { }, #[snafu(display("failed to serialize spark properties"))] - SparkProperties { - source: product_config::writer::PropertiesWriterError, - }, + SparkProperties { source: PropertiesWriterError }, #[snafu(display("failed to serialize jvm security properties",))] - JvmSecurityProperties { - source: product_config::writer::PropertiesWriterError, - }, + JvmSecurityProperties { source: PropertiesWriterError }, #[snafu(display("failed to serialize metrics properties",))] - MetricsProperties { - source: product_config::writer::PropertiesWriterError, - }, + MetricsProperties { source: PropertiesWriterError }, } pub(crate) fn labels<'a, T>( @@ -110,12 +108,12 @@ pub(crate) fn security_properties( ) -> Result { let mut result: BTreeMap> = [ ( - "networkaddress.cache.ttl".to_string(), - Some("30".to_string()), + JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), ), ( - "networkaddress.cache.negative.ttl".to_string(), - Some("0".to_string()), + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), ), ] .into(); diff --git a/rust/operator-binary/src/connect/s3.rs b/rust/operator-binary/src/connect/s3.rs index 13488aa8..75d29428 100644 --- a/rust/operator-binary/src/connect/s3.rs +++ b/rust/operator-binary/src/connect/s3.rs @@ -375,7 +375,6 @@ impl ResolvedS3 { #[cfg(test)] mod tests { - use product_config::writer::to_java_properties_string; use rstest::*; use stackable_operator::commons::{ secret_class::SecretClassVolume, @@ -383,6 +382,7 @@ mod tests { }; use super::*; + use crate::config::writer::to_java_properties_string; fn connection_fixture( credentials_secret_class: Option<&str>, diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index 0b2aab32..3bd2db24 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -33,6 +33,12 @@ pub const VOLUME_MOUNT_PATH_LOG: &str = "/stackable/log"; pub const LOG4J2_CONFIG_FILE: &str = "log4j2.properties"; pub const JVM_SECURITY_PROPERTIES_FILE: &str = "security.properties"; +// These defaults used to come from deploy/config-spec/properties.yaml via product-config. +// They are now defined in code and can still be overridden through configOverrides.security.properties. +pub const JVM_SECURITY_PROPERTY_DNS_CACHE_TTL: &str = "networkaddress.cache.ttl"; +pub const JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL: &str = "networkaddress.cache.negative.ttl"; +pub const DEFAULT_JVM_SECURITY_DNS_CACHE_TTL: &str = "30"; +pub const DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL: &str = "0"; pub const METRICS_PROPERTIES_FILE: &str = "metrics.properties"; pub const ACCESS_KEY_ID: &str = "accessKey"; diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 04e68b2b..a331e115 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -1,12 +1,11 @@ use std::collections::{BTreeMap, HashMap}; -use product_config::{ProductConfigManager, types::PropertyNameKind}; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ commons::{ affinity::StackableAffinity, - product_image_selection::{ProductImage, ResolvedProductImage}, + product_image_selection::ProductImage, resources::{ CpuLimitsFragment, MemoryLimitsFragment, NoRuntimeLimits, NoRuntimeLimitsFragment, Resources, ResourcesFragment, @@ -21,10 +20,7 @@ use stackable_operator::{ deep_merger::ObjectOverrides, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef}, - product_config_utils::{ - Configuration, ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config, - validate_all_roles_and_groups_config, - }, + product_config_utils::Configuration, product_logging::{self, spec::Logging}, role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef}, schemars::{self, JsonSchema}, @@ -43,16 +39,6 @@ use crate::{ #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("failed to transform configs"))] - ProductConfigTransform { - source: stackable_operator::product_config_utils::Error, - }, - - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - #[snafu(display("fragment validation failure"))] FragmentValidationFailure { source: ValidationError }, @@ -245,37 +231,6 @@ impl v1alpha1::SparkHistoryServer { rgs } - pub fn validated_role_config( - &self, - resolved_product_image: &ResolvedProductImage, - product_config: &ProductConfigManager, - ) -> Result { - let roles_to_validate = vec![( - HISTORY_ROLE_NAME.to_string(), - ( - vec![ - PropertyNameKind::File(SPARK_DEFAULTS_FILE_NAME.to_string()), - PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), - ], - self.spec.nodes.clone(), - ), - )] - .into_iter() - .collect::>(); - - let role_config = transform_all_roles_to_config(self, &roles_to_validate); - - validate_all_roles_and_groups_config( - &resolved_product_image.product_version, - &role_config.context(ProductConfigTransformSnafu)?, - product_config, - false, - false, - ) - .context(InvalidProductConfigSnafu) - } - pub fn merged_env( &self, role_group: &str, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index e2048dea..67fa5768 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -8,7 +8,6 @@ use std::{ use constants::*; use history::LogFileDirectorySpec; use logdir::ResolvedLogDir; -use product_config::{ProductConfigManager, types::PropertyNameKind}; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -17,7 +16,7 @@ use stackable_operator::{ VolumeBuilder, }, commons::{ - product_image_selection::{ProductImage, ResolvedProductImage}, + product_image_selection::ProductImage, resources::{CpuLimits, MemoryLimits, Resources}, secret_class::SecretClassVolumeProvisionParts, }, @@ -34,12 +33,8 @@ use stackable_operator::{ kube::{CustomResource, ResourceExt}, kvp::ObjectLabels, memory::{BinaryMultiple, MemoryQuantity}, - product_config_utils::{ - ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config, - validate_all_roles_and_groups_config, - }, product_logging, - role_utils::{CommonConfiguration, GenericRoleConfig, JavaCommonConfig, Role, RoleGroup}, + role_utils::{CommonConfiguration, JavaCommonConfig, RoleGroup}, schemars::{self, JsonSchema}, shared::time::Duration, utils::crds::raw_object_list_schema, @@ -97,16 +92,6 @@ pub enum Error { #[snafu(display("fragment validation failure"))] FragmentValidationFailure { source: ValidationError }, - #[snafu(display("failed to transform configs"))] - ProductConfigTransform { - source: stackable_operator::product_config_utils::Error, - }, - - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - #[snafu(display("failed to build TLS certificate SecretClass Volume"))] TlsCertSecretClassVolumeBuild { source: SecretOperatorVolumeSourceBuilderError, @@ -215,7 +200,7 @@ pub mod versioned { /// supported for spark-submit processes. // // IMPORTANT: Please note that the jvmArgumentOverrides have no effect here! - // However, due to product-config things I wasn't able to remove them. + // This field is currently kept for API compatibility. #[serde(default, skip_serializing_if = "Option::is_none")] pub job: Option, @@ -948,114 +933,6 @@ impl v1alpha1::SparkApplication { env.into_values().collect() } - pub fn validated_role_config( - &self, - resolved_product_image: &ResolvedProductImage, - product_config: &ProductConfigManager, - ) -> Result { - let submit_conf = match self.spec.job.as_ref() { - Some(job) => job.clone(), - None => CommonConfiguration { - config: SubmitConfig::default_config(), - ..CommonConfiguration::default() - }, - }; - - let driver_conf = match self.spec.driver.as_ref() { - Some(driver) => driver.clone(), - None => CommonConfiguration { - config: RoleConfig::default_config(), - ..CommonConfiguration::default() - }, - }; - - let executor_conf = match self.spec.executor.as_ref() { - Some(executor) => executor.clone(), - None => RoleGroup { - replicas: Some(1), - config: CommonConfiguration { - config: RoleConfig::default_config(), - ..CommonConfiguration::default() - }, - }, - }; - - let mut roles_to_validate = HashMap::new(); - roles_to_validate.insert( - SparkApplicationRole::Submit.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), - ], - Role { - config: submit_conf.clone(), - role_config: GenericRoleConfig::default(), - role_groups: [( - "default".to_string(), - RoleGroup { - config: submit_conf, - replicas: Some(1), - }, - )] - .into(), - } - .erase(), - ), - ); - roles_to_validate.insert( - SparkApplicationRole::Driver.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), - ], - Role { - config: driver_conf.clone(), - role_config: GenericRoleConfig::default(), - role_groups: [( - "default".to_string(), - RoleGroup { - config: driver_conf, - replicas: Some(1), - }, - )] - .into(), - } - .erase(), - ), - ); - roles_to_validate.insert( - SparkApplicationRole::Executor.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), - ], - Role { - config: executor_conf.config.clone(), - role_config: GenericRoleConfig::default(), - role_groups: [("default".to_string(), executor_conf)].into(), - } - .erase(), - ), - ); - - let role_config = transform_all_roles_to_config(self, &roles_to_validate); - - validate_all_roles_and_groups_config( - &resolved_product_image.product_version, - &role_config.context(ProductConfigTransformSnafu)?, - product_config, - false, - false, - ) - .context(InvalidProductConfigSnafu) - } - pub fn retry_on_failure_count(&self) -> i32 { let effective_retry_on_failure_count = self .spec @@ -1239,17 +1116,15 @@ where #[cfg(test)] mod tests { - use std::collections::{BTreeMap, HashMap}; + use std::collections::BTreeMap; use indoc::indoc; - use product_config::{ProductConfigManager, types::PropertyNameKind}; use rstest::rstest; use stackable_operator::{ commons::{ affinity::StackableAffinity, resources::{CpuLimits, MemoryLimits, NoRuntimeLimits, Resources}, }, - product_config_utils::ValidatedRoleConfigByPropertyKind, product_logging::spec::Logging, versioned::test_utils::RoundtripTestData, }; @@ -1700,72 +1575,6 @@ spec: assert_eq!(expected, props); } - #[test] - fn test_validated_config() { - let spark_application = serde_yaml::from_str::(indoc! {r#" - --- - apiVersion: spark.stackable.tech/v1alpha1 - kind: SparkApplication - metadata: - name: spark-examples - spec: - mode: cluster - mainApplicationFile: test.py - sparkImage: - productVersion: 1.2.3 - "#}) - .unwrap(); - - let resolved_product_image = spark_application - .spec - .spark_image - .resolve("spark-k8s", "oci.example.org", "0.0.0-dev") - .expect("test: resolved product image is always valid"); - - let product_config = - ProductConfigManager::from_yaml_file("../../deploy/config-spec/properties.yaml") - .unwrap(); - let validated_config = spark_application - .validated_role_config(&resolved_product_image, &product_config) - .unwrap(); - - let expected_role_groups: HashMap< - String, - HashMap>, - > = vec![( - "default".into(), - vec![ - (PropertyNameKind::Env, BTreeMap::new()), - ( - PropertyNameKind::File("spark-env.sh".into()), - BTreeMap::new(), - ), - ( - PropertyNameKind::File("security.properties".into()), - vec![ - ("networkaddress.cache.negative.ttl".into(), "0".into()), - ("networkaddress.cache.ttl".into(), "30".into()), - ] - .into_iter() - .collect(), - ), - ] - .into_iter() - .collect(), - )] - .into_iter() - .collect(); - let expected: ValidatedRoleConfigByPropertyKind = vec![ - ("submit".into(), expected_role_groups.clone()), - ("driver".into(), expected_role_groups.clone()), - ("executor".into(), expected_role_groups), - ] - .into_iter() - .collect(); - - assert_eq!(expected, validated_config); - } - #[test] fn test_job_volume_mounts() { let spark_application = serde_yaml::from_str::(indoc! {r#" diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 957e60da..1891fabc 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -1,9 +1,5 @@ -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, -}; +use std::{collections::BTreeMap, sync::Arc}; -use product_config::{types::PropertyNameKind, writer::to_java_properties_string}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ @@ -22,6 +18,7 @@ use stackable_operator::{ }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, + config_overrides::KeyValueOverridesProvider, crd::listener, k8s_openapi::{ DeepMerge, @@ -52,14 +49,18 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ Ctx, + config::writer::{PropertiesWriterError, to_java_properties_string}, crd::{ constants::{ - ACCESS_KEY_ID, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, HISTORY_UI_PORT, - JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, - MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, OPERATOR_NAME, SECRET_ACCESS_KEY, - SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, STACKABLE_TRUST_STORE, - VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, - VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, + ACCESS_KEY_ID, DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, + HISTORY_ROLE_NAME, HISTORY_UI_PORT, JVM_SECURITY_PROPERTIES_FILE, + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, + OPERATOR_NAME, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, + STACKABLE_TRUST_STORE, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, + VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, + VOLUME_MOUNT_PATH_LOG_CONFIG, }, history::{self, HistoryConfig, SparkHistoryServerContainer, v1alpha1}, listener_ext, @@ -188,7 +189,7 @@ pub enum Error { rolegroup ))] JvmSecurityProperties { - source: product_config::writer::PropertiesWriterError, + source: PropertiesWriterError, rolegroup: String, }, @@ -265,13 +266,8 @@ pub async fn reconcile( .await .context(DereferenceSparkHistoryServerSnafu)?; - let validated = validate::validate( - shs, - dereferenced, - &ctx.operator_environment, - &ctx.product_config, - ) - .context(ValidateSparkHistoryServerSnafu)?; + let validated = validate::validate(shs, dereferenced, &ctx.operator_environment) + .context(ValidateSparkHistoryServerSnafu)?; let mut cluster_resources = ClusterResources::new( HISTORY_APP_NAME, @@ -304,77 +300,75 @@ pub async fn reconcile( .await .context(ApplyRoleBindingSnafu)?; - // The role_name is always HISTORY_ROLE_NAME - for (role_name, role_config) in validated.product_config.iter() { - for (rolegroup_name, rolegroup_config) in role_config.iter() { - let rgr = RoleGroupRef { - cluster: ObjectRef::from_obj(shs), - role: role_name.into(), - role_group: rolegroup_name.into(), - }; - - let merged_config = shs - .merged_config(&rgr) - .context(FailedToResolveConfigSnafu)?; - - let config_map = build_config_map( - shs, - rolegroup_config, - &merged_config, - &resolved_product_image.app_version_label_value, - &rgr, - log_dir, - )?; - - let metrics_service = - build_rolegroup_metrics_service(shs, resolved_product_image, &rgr) - .context(BuildMetricsServiceSnafu)?; - - let sts = build_stateful_set( - shs, - resolved_product_image, - &rgr, - log_dir, - &merged_config, - &service_account, - )?; - - cluster_resources - .add(client, config_map) - .await - .context(ApplyConfigMapSnafu)?; - cluster_resources - .add(client, metrics_service) - .await - .context(ApplyMetricsServiceSnafu)?; - cluster_resources - .add(client, sts) - .await - .context(ApplyStatefulSetSnafu)?; - } + for rolegroup_name in shs.spec.nodes.role_groups.keys() { + let rgr = RoleGroupRef { + cluster: ObjectRef::from_obj(shs), + role: HISTORY_ROLE_NAME.to_string(), + role_group: rolegroup_name.to_string(), + }; + + let merged_config = shs + .merged_config(&rgr) + .context(FailedToResolveConfigSnafu)?; + + let role_group = shs.rolegroup(&rgr).context(CannotRetrieveRoleGroupSnafu)?; + + let config_map = build_config_map( + shs, + &role_group.config.config_overrides, + &merged_config, + &resolved_product_image.app_version_label_value, + &rgr, + log_dir, + )?; - let rg_group_listener = build_group_listener( + let metrics_service = build_rolegroup_metrics_service(shs, resolved_product_image, &rgr) + .context(BuildMetricsServiceSnafu)?; + + let sts = build_stateful_set( shs, resolved_product_image, - role_name, - shs.node_listener_class().to_string(), + &rgr, + log_dir, + &merged_config, + &service_account, )?; cluster_resources - .add(client, rg_group_listener) + .add(client, config_map) + .await + .context(ApplyConfigMapSnafu)?; + cluster_resources + .add(client, metrics_service) + .await + .context(ApplyMetricsServiceSnafu)?; + cluster_resources + .add(client, sts) .await - .context(ApplyGroupListenerSnafu)?; + .context(ApplyStatefulSetSnafu)?; + } - let role_config = &shs.spec.nodes.role_config; - add_pdbs( - &role_config.common.pod_disruption_budget, - shs, - client, - &mut cluster_resources, - ) + let rg_group_listener = build_group_listener( + shs, + resolved_product_image, + HISTORY_ROLE_NAME, + shs.node_listener_class().to_string(), + )?; + + cluster_resources + .add(client, rg_group_listener) .await - .context(FailedToCreatePdbSnafu)?; - } + .context(ApplyGroupListenerSnafu)?; + + let role_config = &shs.spec.nodes.role_config; + add_pdbs( + &role_config.common.pod_disruption_budget, + shs, + client, + &mut cluster_resources, + ) + .await + .context(FailedToCreatePdbSnafu)?; cluster_resources .delete_orphaned_resources(client) @@ -430,7 +424,7 @@ pub fn error_policy( #[allow(clippy::result_large_err)] fn build_config_map( shs: &v1alpha1::SparkHistoryServer, - config: &HashMap>, + config_overrides: &v1alpha1::ConfigOverrides, merged_config: &HistoryConfig, app_version_label_value: &str, rolegroupref: &RoleGroupRef, @@ -440,15 +434,8 @@ fn build_config_map( let spark_defaults = spark_defaults(shs, log_dir, rolegroupref)?; - let jvm_sec_props: BTreeMap> = config - .get(&PropertyNameKind::File( - JVM_SECURITY_PROPERTIES_FILE.to_string(), - )) - .cloned() - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect(); + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); let mut cm_builder = ConfigMapBuilder::new(); @@ -471,11 +458,7 @@ fn build_config_map( .add_data( SPARK_ENV_SH_FILE_NAME, to_spark_env_sh_string( - config - .get(&PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string())) - .cloned() - .unwrap_or_default() - .iter(), + defined_key_value_overrides(config_overrides, SPARK_ENV_SH_FILE_NAME).iter(), ), ) .add_data( @@ -746,6 +729,31 @@ fn command_args(logdir: &ResolvedLogDir) -> Vec { vec![command.join("\n")] } +fn default_jvm_security_properties() -> BTreeMap> { + [ + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), + ), + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), + ), + ] + .into() +} + +fn defined_key_value_overrides( + config_overrides: &v1alpha1::ConfigOverrides, + file_name: &str, +) -> BTreeMap { + config_overrides + .get_key_value_overrides(file_name) + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) + .collect() +} + /// Return the Spark properties for the cleaner role group (if any). /// There should be only one role group with "cleaner=true" and this /// group should have a replica count of 0 or 1. diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index bd57bd8b..e8a39e23 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -1,14 +1,12 @@ //! The validate step in the SparkHistoryServer controller. //! -//! Resolves the product image and runs role/role-group config validation. +//! Resolves the product image. //! Does not touch the Kubernetes API. -use product_config::ProductConfigManager; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - product_config_utils::ValidatedRoleConfigByPropertyKind, }; use crate::{ @@ -22,9 +20,6 @@ pub enum Error { ResolveProductImage { source: product_image_selection::Error, }, - - #[snafu(display("invalid product config"))] - InvalidProductConfig { source: crate::crd::history::Error }, } type Result = std::result::Result; @@ -32,14 +27,12 @@ type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { pub log_dir: ResolvedLogDir, pub resolved_product_image: ResolvedProductImage, - pub product_config: ValidatedRoleConfigByPropertyKind, } pub fn validate( shs: &v1alpha1::SparkHistoryServer, dereferenced: DereferencedSparkHistoryServer, operator_environment: &OperatorEnvironmentOptions, - product_config: &ProductConfigManager, ) -> Result { let resolved_product_image = shs .spec @@ -51,13 +44,8 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let product_config_validated = shs - .validated_role_config(&resolved_product_image, product_config) - .context(InvalidProductConfigSnafu)?; - Ok(ValidatedSparkHistoryServer { log_dir: dereferenced.log_dir, resolved_product_image, - product_config: product_config_validated, }) } diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 6ae07183..9f14b776 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -8,7 +8,6 @@ use clap::Parser; use connect::crd::{CONNECT_FULL_CONTROLLER_NAME, SparkConnectServer}; use futures::{FutureExt, StreamExt, TryFutureExt}; use history::controller; -use product_config::ProductConfigManager; use stackable_operator::{ YamlSchema, cli::{Command, OperatorEnvironmentOptions, RunArguments}, @@ -68,13 +67,8 @@ struct Opts { cmd: Command, } -const PRODUCT_CONFIG_PATHS: [&str; 2] = [ - "deploy/config-spec/properties.yaml", - "/etc/stackable/spark-k8s-operator/config-spec/properties.yaml", -]; pub struct Ctx { pub client: stackable_operator::client::Client, - pub product_config: ProductConfigManager, pub operator_environment: OperatorEnvironmentOptions, } @@ -95,7 +89,7 @@ async fn main() -> anyhow::Result<()> { Command::Run(RunArguments { operator_environment, watch_namespace, - product_config, + product_config: _, maintenance, common, }) => { @@ -144,7 +138,6 @@ async fn main() -> anyhow::Result<()> { let ctx = Ctx { client: client.clone(), - product_config: product_config.load(&PRODUCT_CONFIG_PATHS)?, operator_environment: operator_environment.clone(), }; @@ -234,7 +227,6 @@ async fn main() -> anyhow::Result<()> { // Create new object because Ctx cannot be cloned let ctx = Ctx { client: client.clone(), - product_config: product_config.load(&PRODUCT_CONFIG_PATHS)?, operator_environment: operator_environment.clone(), }; let history_event_recorder = Arc::new(Recorder::new( @@ -300,7 +292,6 @@ async fn main() -> anyhow::Result<()> { // Create new object because Ctx cannot be cloned let ctx = Ctx { client: client.clone(), - product_config: product_config.load(&PRODUCT_CONFIG_PATHS)?, operator_environment, }; let connect_event_recorder = Arc::new(Recorder::new( diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 3b4a70be..7bb83a30 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -1,10 +1,5 @@ -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, - vec, -}; +use std::{collections::BTreeMap, sync::Arc, vec}; -use product_config::{types::PropertyNameKind, writer::to_java_properties_string}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ @@ -17,6 +12,7 @@ use stackable_operator::{ }, }, commons::product_image_selection::ResolvedProductImage, + config_overrides::KeyValueOverridesProvider, crd::s3, k8s_openapi::{ DeepMerge, Resource, @@ -53,6 +49,7 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ Ctx, + config::writer::{PropertiesWriterError, to_java_properties_string}, crd::{ constants::*, logdir::ResolvedLogDir, @@ -131,7 +128,7 @@ pub enum Error { #[snafu(display("failed to serialize [{JVM_SECURITY_PROPERTIES_FILE}] for {}", role))] JvmSecurityProperties { - source: product_config::writer::PropertiesWriterError, + source: PropertiesWriterError, role: SparkApplicationRole, }, @@ -207,16 +204,13 @@ pub async fn reconcile( .await .context(DereferenceSparkApplicationSnafu)?; - let validated = - validate::validate(dereferenced, &ctx.operator_environment, &ctx.product_config) - .context(ValidateSparkApplicationSnafu)?; + let validated = validate::validate(dereferenced, &ctx.operator_environment) + .context(ValidateSparkApplicationSnafu)?; let spark_application = &validated.spark_application; let opt_s3conn = &validated.s3_connection; let logdir = &validated.log_dir; let resolved_product_image = &validated.resolved_product_image; - let validated_product_config = &validated.product_config; - // This is the final version of the spark app to reconcile. // No more mutating operations after this point (except for status). tracing::debug!("reconciling spark application [{spark_application:?}]"); @@ -238,16 +232,18 @@ pub async fn reconcile( .driver_config() .context(FailedToResolveConfigSnafu)?; - let driver_product_config: Option<&HashMap>> = - validated_product_config - .get(&SparkApplicationRole::Driver.to_string()) - .and_then(|r| r.get(&"default".to_string())); + let driver_config_overrides = spark_application + .spec + .driver + .as_ref() + .map(|driver| driver.config_overrides.clone()) + .unwrap_or_default(); let driver_pod_template_config_map = pod_template_config_map( spark_application, SparkApplicationRole::Driver, &driver_config, - driver_product_config, + &driver_config_overrides, &env_vars, opt_s3conn, logdir, @@ -267,16 +263,18 @@ pub async fn reconcile( .executor_config() .context(FailedToResolveConfigSnafu)?; - let executor_product_config: Option<&HashMap>> = - validated_product_config - .get(&SparkApplicationRole::Executor.to_string()) - .and_then(|r| r.get(&"default".to_string())); + let executor_config_overrides = spark_application + .spec + .executor + .as_ref() + .map(|executor| executor.config.config_overrides.clone()) + .unwrap_or_default(); let executor_pod_template_config_map = pod_template_config_map( spark_application, SparkApplicationRole::Executor, &executor_config, - executor_product_config, + &executor_config_overrides, &env_vars, opt_s3conn, logdir, @@ -300,14 +298,16 @@ pub async fn reconcile( .submit_config() .context(SubmitConfigSnafu)?; - let submit_product_config: Option<&HashMap>> = - validated_product_config - .get(&SparkApplicationRole::Submit.to_string()) - .and_then(|r| r.get(&"default".to_string())); + let submit_config_overrides = spark_application + .spec + .job + .as_ref() + .map(|job| job.config_overrides.clone()) + .unwrap_or_default(); let submit_job_config_map = submit_job_config_map( spark_application, - submit_product_config, + &submit_config_overrides, resolved_product_image, )?; client @@ -636,7 +636,7 @@ fn pod_template_config_map( spark_application: &v1alpha1::SparkApplication, role: SparkApplicationRole, merged_config: &RoleConfig, - product_config: Option<&HashMap>>, + config_overrides: &v1alpha1::ConfigOverrides, env: &[EnvVar], s3conn: &Option, logdir: &Option, @@ -720,40 +720,26 @@ fn pod_template_config_map( ) .context(InvalidLoggingConfigSnafu { cm_name })?; - if let Some(product_config) = product_config { - cm_builder.add_data( - SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string( - product_config - .get(&PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string())) - .cloned() - .unwrap_or_default() - .iter(), - ), - ); + cm_builder.add_data( + SPARK_ENV_SH_FILE_NAME, + to_spark_env_sh_string(defined_key_value_overrides(config_overrides).iter()), + ); + + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); + + cm_builder.add_data( + JVM_SECURITY_PROPERTIES_FILE, + to_java_properties_string(jvm_sec_props.iter()) + .with_context(|_| JvmSecurityPropertiesSnafu { role })?, + ); - let jvm_sec_props: BTreeMap> = product_config - .get(&PropertyNameKind::File( - JVM_SECURITY_PROPERTIES_FILE.to_string(), - )) - .cloned() - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect(); - - cm_builder.add_data( - JVM_SECURITY_PROPERTIES_FILE, - to_java_properties_string(jvm_sec_props.iter()) - .with_context(|_| JvmSecurityPropertiesSnafu { role })?, - ); - } cm_builder.build().context(PodTemplateConfigMapSnafu) } fn submit_job_config_map( spark_application: &v1alpha1::SparkApplication, - product_config: Option<&HashMap>>, + config_overrides: &v1alpha1::ConfigOverrides, spark_image: &ResolvedProductImage, ) -> Result { let cm_name = spark_application.submit_job_config_map_name(); @@ -774,41 +760,50 @@ fn submit_job_config_map( .build(), ); - if let Some(product_config) = product_config { - cm_builder.add_data( - SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string( - product_config - .get(&PropertyNameKind::File(SPARK_ENV_SH_FILE_NAME.to_string())) - .cloned() - .unwrap_or_default() - .iter(), - ), - ); + cm_builder.add_data( + SPARK_ENV_SH_FILE_NAME, + to_spark_env_sh_string(defined_key_value_overrides(config_overrides).iter()), + ); - let jvm_sec_props: BTreeMap> = product_config - .get(&PropertyNameKind::File( - JVM_SECURITY_PROPERTIES_FILE.to_string(), - )) - .cloned() - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect(); - - cm_builder.add_data( - JVM_SECURITY_PROPERTIES_FILE, - to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { - JvmSecurityPropertiesSnafu { - role: SparkApplicationRole::Submit, - } - })?, - ); - } + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); + + cm_builder.add_data( + JVM_SECURITY_PROPERTIES_FILE, + to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { + JvmSecurityPropertiesSnafu { + role: SparkApplicationRole::Submit, + } + })?, + ); cm_builder.build().context(PodTemplateConfigMapSnafu) } +fn default_jvm_security_properties() -> BTreeMap> { + [ + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), + ), + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), + Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), + ), + ] + .into() +} + +fn defined_key_value_overrides( + config_overrides: &v1alpha1::ConfigOverrides, +) -> BTreeMap { + config_overrides + .get_key_value_overrides(SPARK_ENV_SH_FILE_NAME) + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) + .collect() +} + #[allow(clippy::too_many_arguments)] fn spark_job( spark_application: &v1alpha1::SparkApplication, diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index a00b15b1..3e29e2d2 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -1,9 +1,8 @@ //! The validate step in the SparkApplication controller. //! //! Synchronously validates the [`super::dereference::DereferencedSparkApplication`] and -//! resolves the product image and product config. Does not touch the Kubernetes API. +//! resolves the product image. Does not touch the Kubernetes API. -use product_config::ProductConfigManager; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, @@ -12,7 +11,6 @@ use stackable_operator::{ tls_verification::TlsVerification, }, crd::s3, - product_config_utils::ValidatedRoleConfigByPropertyKind, }; use crate::{ @@ -27,9 +25,6 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("invalid product config"))] - InvalidProductConfig { source: crate::crd::Error }, - #[snafu(display("S3 TLS with verification disabled is not supported ({context})"))] S3TlsNoVerificationNotSupported { context: String }, } @@ -43,13 +38,11 @@ pub struct ValidatedSparkApplication { pub s3_connection: Option, pub log_dir: Option, pub resolved_product_image: ResolvedProductImage, - pub product_config: ValidatedRoleConfigByPropertyKind, } pub fn validate( dereferenced: DereferencedSparkApplication, operator_environment: &OperatorEnvironmentOptions, - product_config: &ProductConfigManager, ) -> Result { if let Some(conn) = &dereferenced.s3_connection { reject_tls_no_verification(conn, "S3 connection")?; @@ -69,18 +62,12 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let product_config = dereferenced - .spark_application - .validated_role_config(&resolved_product_image, product_config) - .context(InvalidProductConfigSnafu)?; - Ok(ValidatedSparkApplication { spark_application: dereferenced.spark_application, resolved_template_refs: dereferenced.resolved_template_refs, s3_connection: dereferenced.s3_connection, log_dir: dereferenced.log_dir, resolved_product_image, - product_config, }) } From dfa3307688be49fb2e950b8d214906b416369819 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:04:10 +0200 Subject: [PATCH 02/50] update op-rs patch --- Cargo.lock | 11 ++++++++ Cargo.nix | 71 +++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 4 +-- crate-hashes.json | 9 ++++++ 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9306281d..5c81e5a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,6 +1515,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "regex", @@ -2893,6 +2894,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "const-oid", "ecdsa", @@ -2916,6 +2918,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "base64", "clap", @@ -2927,6 +2930,7 @@ dependencies = [ "futures 0.3.32", "http", "indexmap", + "java-properties", "jiff", "json-patch", "k8s-openapi", @@ -2952,11 +2956,13 @@ dependencies = [ "tracing-subscriber", "url", "uuid", + "xml", ] [[package]] name = "stackable-operator-derive" version = "0.3.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "proc-macro2", @@ -2967,6 +2973,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "jiff", "k8s-openapi", @@ -3008,6 +3015,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "axum", "clap", @@ -3031,6 +3039,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "kube", "schemars", @@ -3044,6 +3053,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "convert_case", "convert_case_extras", @@ -3061,6 +3071,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.nix b/Cargo.nix index 5edb7bb9..47f55b9f 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4828,7 +4828,12 @@ rec { crateName = "k8s-version"; version = "0.1.3"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/k8s-version; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "k8s_version"; authors = [ "Stackable GmbH " @@ -9506,7 +9511,12 @@ rec { crateName = "stackable-certs"; version = "0.4.0"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-certs; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_certs"; authors = [ "Stackable GmbH " @@ -9604,7 +9614,12 @@ rec { crateName = "stackable-operator"; version = "0.111.1"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-operator; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_operator"; authors = [ "Stackable GmbH " @@ -9653,6 +9668,10 @@ rec { name = "indexmap"; packageId = "indexmap"; } + { + name = "java-properties"; + packageId = "java-properties"; + } { name = "jiff"; packageId = "jiff"; @@ -9768,6 +9787,10 @@ rec { name = "uuid"; packageId = "uuid"; } + { + name = "xml"; + packageId = "xml"; + } ]; features = { "certs" = [ "dep:stackable-certs" ]; @@ -9785,7 +9808,12 @@ rec { crateName = "stackable-operator-derive"; version = "0.3.1"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-operator-derive; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; procMacro = true; libName = "stackable_operator_derive"; authors = [ @@ -9815,7 +9843,12 @@ rec { crateName = "stackable-shared"; version = "0.1.1"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-shared; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_shared"; authors = [ "Stackable GmbH " @@ -10001,7 +10034,12 @@ rec { crateName = "stackable-telemetry"; version = "0.6.4"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-telemetry; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_telemetry"; authors = [ "Stackable GmbH " @@ -10106,7 +10144,12 @@ rec { crateName = "stackable-versioned"; version = "0.10.0"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-versioned; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_versioned"; authors = [ "Stackable GmbH " @@ -10151,7 +10194,12 @@ rec { crateName = "stackable-versioned-macros"; version = "0.10.0"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-versioned-macros; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; procMacro = true; libName = "stackable_versioned_macros"; authors = [ @@ -10214,7 +10262,12 @@ rec { crateName = "stackable-webhook"; version = "0.9.1"; edition = "2024"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ../operator-rs/crates/stackable-webhook; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + }; libName = "stackable_webhook"; authors = [ "Stackable GmbH " diff --git a/Cargo.toml b/Cargo.toml index e49f57f1..b2b4da2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,5 +32,5 @@ indoc = "2" regex = "1" [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch="smooth-operator" } -stackable-operator = { path = "../operator-rs/crates/stackable-operator" } +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch="smooth-operator" } +# stackable-operator = { path = "../operator-rs/crates/stackable-operator" } diff --git a/crate-hashes.json b/crate-hashes.json index ceeb77ce..deac3bf4 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,3 +1,12 @@ { + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file From af9492a8825d09ffb9666801c8c040ad80a3ed6e Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:04:02 +0200 Subject: [PATCH 03/50] add regression test --- .../product-config-compat/00-assert.yaml | 9 ++ .../product-config-compat/00-patch-ns.yaml.j2 | 9 ++ .../00-serviceaccount.yaml.j2 | 29 +++++ .../10-deploy-spark-app.yaml.j2 | 122 ++++++++++++++++++ .../product-config-compat/11-assert.yaml | 19 +++ .../product-config-compat/12-assert.yaml | 50 +++++++ .../kuttl/product-config-compat/README.md | 26 ++++ .../pyspark-pi-driver-pod-template-data.json | 6 + ...pyspark-pi-executor-pod-template-data.json | 6 + .../pyspark-pi-job-template-spec.json | 102 +++++++++++++++ .../fixtures/pyspark-pi-submit-job-data.json | 4 + .../fixtures/spark-connect-executor-data.json | 4 + .../fixtures/spark-connect-server-data.json | 6 + .../spark-history-node-default-data.json | 5 + tests/test-definition.yaml | 7 + 15 files changed, 404 insertions(+) create mode 100644 tests/templates/kuttl/product-config-compat/00-assert.yaml create mode 100644 tests/templates/kuttl/product-config-compat/00-patch-ns.yaml.j2 create mode 100644 tests/templates/kuttl/product-config-compat/00-serviceaccount.yaml.j2 create mode 100644 tests/templates/kuttl/product-config-compat/10-deploy-spark-app.yaml.j2 create mode 100644 tests/templates/kuttl/product-config-compat/11-assert.yaml create mode 100644 tests/templates/kuttl/product-config-compat/12-assert.yaml create mode 100644 tests/templates/kuttl/product-config-compat/README.md create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-submit-job-data.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/spark-connect-executor-data.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json create mode 100644 tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json diff --git a/tests/templates/kuttl/product-config-compat/00-assert.yaml b/tests/templates/kuttl/product-config-compat/00-assert.yaml new file mode 100644 index 00000000..5baf8caa --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/00-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 900 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: integration-tests-sa diff --git a/tests/templates/kuttl/product-config-compat/00-patch-ns.yaml.j2 b/tests/templates/kuttl/product-config-compat/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/product-config-compat/00-serviceaccount.yaml.j2 b/tests/templates/kuttl/product-config-compat/00-serviceaccount.yaml.j2 new file mode 100644 index 00000000..9cbf0351 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/00-serviceaccount.yaml.j2 @@ -0,0 +1,29 @@ +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-integration-tests-scc +rules: +{% if test_scenario['values']['openshift'] == "true" %} + - apiGroups: ["security.openshift.io"] + resources: ["securitycontextconstraints"] + resourceNames: ["privileged"] + verbs: ["use"] +{% endif %} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: integration-tests-sa +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-integration-tests-scc +subjects: + - kind: ServiceAccount + name: integration-tests-sa +roleRef: + kind: Role + name: use-integration-tests-scc + apiGroup: rbac.authorization.k8s.io diff --git a/tests/templates/kuttl/product-config-compat/10-deploy-spark-app.yaml.j2 b/tests/templates/kuttl/product-config-compat/10-deploy-spark-app.yaml.j2 new file mode 100644 index 00000000..b2b9104d --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/10-deploy-spark-app.yaml.j2 @@ -0,0 +1,122 @@ +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkApplication +metadata: + name: pyspark-pi +spec: + sparkImage: +{% if test_scenario['values']['spark-regression'].find(",") > 0 %} + custom: "{{ test_scenario['values']['spark-regression'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['spark-regression'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['spark-regression'] }}" +{% endif %} + mode: cluster + mainApplicationFile: local:///stackable/spark/examples/src/main/python/pi.py + job: + config: + resources: + cpu: + min: "1" + max: "2" + memory: + limit: "1Gi" + driver: + config: + resources: + cpu: + min: "1" + max: "2" + memory: + limit: "1Gi" + executor: + replicas: 1 + config: + resources: + cpu: + min: "1" + max: "2" + memory: + limit: "1Gi" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: spark-history-log-config +data: + log4j2.properties: |- + rootLogger.level=INFO +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkHistoryServer +metadata: + name: spark-history +spec: + image: +{% if test_scenario['values']['spark-regression'].find(",") > 0 %} + custom: "{{ test_scenario['values']['spark-regression'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['spark-regression'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['spark-regression'] }}" +{% endif %} + pullPolicy: IfNotPresent + logFileDirectory: + customLogDirectory: file:///tmp/ + nodes: + roleGroups: + default: + replicas: 1 + config: + cleaner: true + config: + logging: + containers: + spark-history: + custom: + configMap: spark-history-log-config +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: spark-connect-log-config +data: + log4j2.properties: |- + rootLogger.level=INFO +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkConnectServer +metadata: + name: spark-connect +spec: + image: +{% if test_scenario['values']['spark-regression'].find(",") > 0 %} + custom: "{{ test_scenario['values']['spark-regression'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['spark-regression'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['spark-regression'] }}" +{% endif %} + pullPolicy: IfNotPresent + server: + jvmArgumentOverrides: + add: + - -Dmy.custom.jvm.arg=customValue + config: + logging: + containers: + spark: + custom: + configMap: spark-connect-log-config + configOverrides: + spark-defaults.conf: + spark.driver.cores: "3" + executor: + configOverrides: + spark-defaults.conf: + spark.executor.memoryOverhead: "1m" + spark.executor.instances: "3" + config: + logging: + containers: + spark: + custom: + configMap: spark-connect-log-config diff --git a/tests/templates/kuttl/product-config-compat/11-assert.yaml b/tests/templates/kuttl/product-config-compat/11-assert.yaml new file mode 100644 index 00000000..be8058d9 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/11-assert.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 900 +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkApplication +metadata: + name: pyspark-pi +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkHistoryServer +metadata: + name: spark-history +--- +apiVersion: spark.stackable.tech/v1alpha1 +kind: SparkConnectServer +metadata: + name: spark-connect diff --git a/tests/templates/kuttl/product-config-compat/12-assert.yaml b/tests/templates/kuttl/product-config-compat/12-assert.yaml new file mode 100644 index 00000000..9a6333de --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/12-assert.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 120 +commands: + - script: | + set -euo pipefail + + compare_fixture() { + fixture_path="$1" + kind="$2" + name="$3" + jq_expr="$4" + + expected=$(sed "s|__NAMESPACE__|$NAMESPACE|g" "$fixture_path" | jq -S .) + actual=$(kubectl -n "$NAMESPACE" get "$kind" "$name" -o json | jq "$jq_expr" | jq -S .) + + if ! diff <(echo "$expected") <(echo "$actual"); then + echo "ERROR: $kind/$name drifted from baseline fixture $fixture_path" + exit 1 + fi + } + + wait_for_configmap() { + name="$1" + for _ in $(seq 1 60); do + if kubectl -n "$NAMESPACE" get configmap "$name" >/dev/null 2>&1; then + return 0 + fi + sleep 2 + done + + echo "ERROR: Timed out waiting for configmap/$name" + exit 1 + } + + wait_for_configmap pyspark-pi-driver-pod-template + wait_for_configmap pyspark-pi-executor-pod-template + wait_for_configmap pyspark-pi-submit-job + wait_for_configmap spark-history-node-default + wait_for_configmap spark-connect-server + wait_for_configmap spark-connect-executor + + compare_fixture fixtures/pyspark-pi-driver-pod-template-data.json configmap pyspark-pi-driver-pod-template '.data' + compare_fixture fixtures/pyspark-pi-executor-pod-template-data.json configmap pyspark-pi-executor-pod-template '.data' + compare_fixture fixtures/pyspark-pi-submit-job-data.json configmap pyspark-pi-submit-job '.data' + compare_fixture fixtures/pyspark-pi-job-template-spec.json job pyspark-pi '.spec.template.spec' + compare_fixture fixtures/spark-history-node-default-data.json configmap spark-history-node-default '.data' + compare_fixture fixtures/spark-connect-server-data.json configmap spark-connect-server '.data' + compare_fixture fixtures/spark-connect-executor-data.json configmap spark-connect-executor '.data' diff --git a/tests/templates/kuttl/product-config-compat/README.md b/tests/templates/kuttl/product-config-compat/README.md new file mode 100644 index 00000000..c830d9fb --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/README.md @@ -0,0 +1,26 @@ +# product-config-compat fixtures + +The fixtures in `fixtures/` are the expected baseline for operator-generated Spark objects +created by a getting-started style `SparkApplication` (`pyspark-pi`). + +Compared objects: + +- `pyspark-pi-driver-pod-template` ConfigMap `.data` +- `pyspark-pi-executor-pod-template` ConfigMap `.data` +- `pyspark-pi-submit-job` ConfigMap `.data` +- `pyspark-pi` Job `.spec.template.spec` +- `spark-history-node-default` ConfigMap `.data` +- `spark-connect-server` ConfigMap `.data` +- `spark-connect-executor` ConfigMap `.data` + +## Refresh baseline fixtures + +Refresh these files from the `main` branch when an intentional behavior change is accepted. + +Example workflow: + +1. Check out `main`. +2. Ensure the spark-k8s operator from `main` is running in your local cluster. +3. Apply a getting-started style `SparkApplication`, plus `SparkHistoryServer` and `SparkConnectServer`, in a temporary namespace. +4. Export the objects listed above and replace namespace-specific values with `__NAMESPACE__` in fixtures that embed it. +5. Commit fixture updates together with the intentional behavior change. diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json new file mode 100644 index 00000000..c65e0c89 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json @@ -0,0 +1,6 @@ +{ + "log4j2.properties": "appenders = FILE, CONSOLE\n\nappender.CONSOLE.type = Console\nappender.CONSOLE.name = CONSOLE\nappender.CONSOLE.target = SYSTEM_ERR\nappender.CONSOLE.layout.type = PatternLayout\nappender.CONSOLE.layout.pattern = %d{ISO8601} %p [%t] %c - %m%n\nappender.CONSOLE.filter.threshold.type = ThresholdFilter\nappender.CONSOLE.filter.threshold.level = INFO\n\nappender.FILE.type = RollingFile\nappender.FILE.name = FILE\nappender.FILE.fileName = /stackable/log/spark/spark.log4j2.xml\nappender.FILE.filePattern = /stackable/log/spark/spark.log4j2.xml.%i\nappender.FILE.layout.type = XMLLayout\nappender.FILE.policies.type = Policies\nappender.FILE.policies.size.type = SizeBasedTriggeringPolicy\nappender.FILE.policies.size.size = 5MB\nappender.FILE.strategy.type = DefaultRolloverStrategy\nappender.FILE.strategy.max = 1\nappender.FILE.filter.threshold.type = ThresholdFilter\nappender.FILE.filter.threshold.level = INFO\n\n\nrootLogger.level=INFO\nrootLogger.appenderRefs = CONSOLE, FILE\nrootLogger.appenderRef.CONSOLE.ref = CONSOLE\nrootLogger.appenderRef.FILE.ref = FILE", + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", + "spark-env.sh": "", + "template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n prometheus.io/scrape: 'true'\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: Always\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: config\n" +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json new file mode 100644 index 00000000..03c53b63 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json @@ -0,0 +1,6 @@ +{ + "log4j2.properties": "appenders = FILE, CONSOLE\n\nappender.CONSOLE.type = Console\nappender.CONSOLE.name = CONSOLE\nappender.CONSOLE.target = SYSTEM_ERR\nappender.CONSOLE.layout.type = PatternLayout\nappender.CONSOLE.layout.pattern = %d{ISO8601} %p [%t] %c - %m%n\nappender.CONSOLE.filter.threshold.type = ThresholdFilter\nappender.CONSOLE.filter.threshold.level = INFO\n\nappender.FILE.type = RollingFile\nappender.FILE.name = FILE\nappender.FILE.fileName = /stackable/log/spark/spark.log4j2.xml\nappender.FILE.filePattern = /stackable/log/spark/spark.log4j2.xml.%i\nappender.FILE.layout.type = XMLLayout\nappender.FILE.policies.type = Policies\nappender.FILE.policies.size.type = SizeBasedTriggeringPolicy\nappender.FILE.policies.size.size = 5MB\nappender.FILE.strategy.type = DefaultRolloverStrategy\nappender.FILE.strategy.max = 1\nappender.FILE.filter.threshold.type = ThresholdFilter\nappender.FILE.filter.threshold.level = INFO\n\n\nrootLogger.level=INFO\nrootLogger.appenderRefs = CONSOLE, FILE\nrootLogger.appenderRef.CONSOLE.ref = CONSOLE\nrootLogger.appenderRef.FILE.ref = FILE", + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", + "spark-env.sh": "", + "template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: Always\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: config\n" +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json new file mode 100644 index 00000000..dc9ada45 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json @@ -0,0 +1,102 @@ +{ + "affinity": {}, + "containers": [ + { + "args": [ + "containerdebug --output=/stackable/log/containerdebug-state.json --loop & /stackable/spark/bin/spark-submit --verbose --master k8s://https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS} --deploy-mode cluster --name pyspark-pi --conf spark.kubernetes.driver.podTemplateFile=/stackable/spark/driver-pod-templates/template.yaml --conf spark.kubernetes.executor.podTemplateFile=/stackable/spark/executor-pod-templates/template.yaml --conf spark.kubernetes.driver.podTemplateContainerName=spark --conf spark.kubernetes.executor.podTemplateContainerName=spark --conf spark.kubernetes.namespace=__NAMESPACE__ --conf spark.kubernetes.driver.container.image=oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev --conf spark.kubernetes.executor.container.image=oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev --conf spark.driver.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.driver.extraClassPath=/stackable/spark/extra-jars/* --conf spark.executor.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.executor.extraClassPath=/stackable/spark/extra-jars/* --conf spark.driver.extraJavaOptions=\"-Djava.security.properties=/stackable/log_config/security.properties\" --conf spark.executor.extraJavaOptions=\"-Djava.security.properties=/stackable/log_config/security.properties\" --conf spark.metrics.conf.\\*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet --conf spark.metrics.conf.\\*.sink.prometheusServlet.path=/metrics/prometheus --conf spark.ui.prometheus.enabled=true --conf spark.sql.streaming.metricsEnabled=true --conf \"spark.driver.cores=2\" --conf \"spark.driver.memory=640m\" --conf \"spark.executor.cores=2\" --conf \"spark.executor.instances=1\" --conf \"spark.executor.memory=640m\" --conf \"spark.kubernetes.driver.limit.cores=2\" --conf \"spark.kubernetes.driver.request.cores=1\" --conf \"spark.kubernetes.executor.limit.cores=2\" --conf \"spark.kubernetes.executor.request.cores=1\" --conf \"spark.kubernetes.memoryOverheadFactor=0.0\" local:///stackable/spark/examples/src/main/python/pi.py" + ], + "command": [ + "/bin/bash", + "-x", + "-euo", + "pipefail", + "-c" + ], + "env": [ + { + "name": "CONTAINERDEBUG_LOG_DIRECTORY", + "value": "/stackable/log/containerdebug" + }, + { + "name": "_STACKABLE_PRE_HOOK", + "value": "containerdebug --output=/stackable/log/containerdebug-state.json --loop &" + }, + { + "name": "SPARK_SUBMIT_OPTS", + "value": "-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties" + }, + { + "name": "SPARK_CONF_DIR", + "value": "/stackable/spark/conf" + } + ], + "image": "oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev", + "imagePullPolicy": "Always", + "name": "spark-submit", + "resources": { + "limits": { + "cpu": "2", + "memory": "1Gi" + }, + "requests": { + "cpu": "1", + "memory": "1Gi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/stackable/spark/driver-pod-templates", + "name": "driver-pod-template" + }, + { + "mountPath": "/stackable/spark/executor-pod-templates", + "name": "executor-pod-template" + }, + { + "mountPath": "/stackable/log", + "name": "log" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Never", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1000 + }, + "serviceAccount": "pyspark-pi", + "serviceAccountName": "pyspark-pi", + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "configMap": { + "defaultMode": 420, + "name": "pyspark-pi-submit-job" + }, + "name": "config" + }, + { + "configMap": { + "defaultMode": 420, + "name": "pyspark-pi-driver-pod-template" + }, + "name": "driver-pod-template" + }, + { + "configMap": { + "defaultMode": 420, + "name": "pyspark-pi-executor-pod-template" + }, + "name": "executor-pod-template" + }, + { + "emptyDir": { + "sizeLimit": "39Mi" + }, + "name": "log" + } + ] +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-submit-job-data.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-submit-job-data.json new file mode 100644 index 00000000..665476de --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-submit-job-data.json @@ -0,0 +1,4 @@ +{ + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", + "spark-env.sh": "" +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-executor-data.json b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-executor-data.json new file mode 100644 index 00000000..791c1931 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-executor-data.json @@ -0,0 +1,4 @@ +{ + "metrics.properties": "*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet\n*.sink.prometheusServlet.path=/metrics/prometheus\n", + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n" +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json new file mode 100644 index 00000000..038159aa --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json @@ -0,0 +1,6 @@ +{ + "metrics.properties": "*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet\n*.sink.prometheusServlet.path=/metrics/prometheus\n", + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", + "spark-defaults.conf": "spark.driver.cores=3\nspark.driver.defaultJavaOptions=-Djava.security.properties\\=/stackable/spark/conf/security.properties\\ -Dlog4j.configurationFile\\=/stackable/log_config/log4j2.properties\\ -Dmy.custom.jvm.arg\\=customValue\nspark.driver.extraClassPath=/stackable/spark/extra-jars/*\\:/stackable/spark/connect/spark-connect-3.5.8.jar\nspark.driver.host=spark-connect-server-headless\nspark.executor.defaultJavaOptions=-Djava.security.properties\\=/stackable/spark/conf/security.properties\\ -Dlog4j.configurationFile\\=/stackable/log_config/log4j2.properties\nspark.executor.instances=3\nspark.executor.memory=1024M\nspark.executor.memoryOverhead=1m\nspark.kubernetes.authenticate.driver.serviceAccountName=spark-connect-serviceaccount\nspark.kubernetes.driver.container.image=oci.stackable.tech/sdp/spark-k8s\\:3.5.8-stackable0.0.0-dev\nspark.kubernetes.driver.pod.name=${env\\:HOSTNAME}\nspark.kubernetes.executor.container.image=oci.stackable.tech/sdp/spark-k8s\\:3.5.8-stackable0.0.0-dev\nspark.kubernetes.executor.limit.cores=1\nspark.kubernetes.executor.podTemplateContainerName=spark\nspark.kubernetes.executor.podTemplateFile=/stackable/spark/conf/template.yaml\nspark.kubernetes.executor.request.cores=1\nspark.kubernetes.namespace=__NAMESPACE__\nspark.metrics.conf=/stackable/spark/conf/metrics.properties\nspark.ui.prometheus.enabled=true\n", + "template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/managed-by: spark.stackable.tech_connect\n app.kubernetes.io/name: spark-connect\n app.kubernetes.io/role-group: default\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\nspec:\n affinity:\n podAntiAffinity:\n preferredDuringSchedulingIgnoredDuringExecution:\n - podAffinityTerm:\n labelSelector:\n matchLabels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/name: spark-connect\n topologyKey: kubernetes.io/hostname\n weight: 70\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n name: spark\n volumeMounts:\n - mountPath: /stackable/spark/conf\n name: config\n - mountPath: /stackable/log\n name: log\n - mountPath: /stackable/truststore\n name: stackable-truststore\n - mountPath: /stackable/log_config\n name: log-config\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n volumes:\n - emptyDir:\n sizeLimit: 30Mi\n name: log\n - configMap:\n name: spark-connect-executor\n name: config\n - emptyDir: {}\n name: stackable-truststore\n - configMap:\n name: spark-connect-log-config\n name: log-config\n" +} diff --git a/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json b/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json new file mode 100644 index 00000000..75db8909 --- /dev/null +++ b/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json @@ -0,0 +1,5 @@ +{ + "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", + "spark-defaults.conf": "spark.history.fs.cleaner.enabled true\nspark.history.fs.logDirectory file:///tmp/", + "spark-env.sh": "" +} diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 341a3da9..e4313ce3 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -47,6 +47,9 @@ dimensions: - 4.0.1 - 4.1.1 # - 3.5.6,oci.stackable.tech/sandbox/spark-k8s:3.5.6-stackable0.0.0-dev + - name: spark-regression + values: + - 3.5.8 - name: hbase values: - 2.6.4 @@ -135,6 +138,10 @@ tests: - spark-connect - openshift - s3-use-tls + - name: product-config-compat + dimensions: + - spark-regression + - openshift suites: - name: nightly From b6b14527b8ccacebb32bb6147626953a1006fad7 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Fri, 5 Jun 2026 18:37:56 +0200 Subject: [PATCH 04/50] fix merging properties for the history controller --- rust/operator-binary/src/crd/history.rs | 24 +++++++++++++++++++ .../operator-binary/src/history/controller.rs | 8 ++++++- .../templates/kuttl/overrides/06-assert.yaml | 3 +++ tests/templates/kuttl/smoke/43-assert.yaml.j2 | 2 +- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index a331e115..3465f82f 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -156,6 +156,30 @@ impl KeyValueOverridesProvider for v1alpha1::ConfigOverrides { } } +impl Merge for v1alpha1::ConfigOverrides { + fn merge(&mut self, defaults: &Self) { + merge_key_value_config_overrides( + &mut self.spark_defaults_conf, + &defaults.spark_defaults_conf, + ); + merge_key_value_config_overrides(&mut self.spark_env_sh, &defaults.spark_env_sh); + merge_key_value_config_overrides( + &mut self.security_properties, + &defaults.security_properties, + ); + } +} + +fn merge_key_value_config_overrides( + base: &mut Option, + overlay: &Option, +) { + let base = base.get_or_insert_default(); + if let Some(overlay) = overlay { + base.overrides.extend(overlay.overrides.clone()); + } +} + impl v1alpha1::SparkHistoryServer { /// Returns a reference to the role. Raises an error if the role is not defined. pub fn role(&self) -> &SparkHistoryRoleType { diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 1891fabc..15569333 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -18,6 +18,7 @@ use stackable_operator::{ }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, + config::merge::Merge, config_overrides::KeyValueOverridesProvider, crd::listener, k8s_openapi::{ @@ -313,9 +314,14 @@ pub async fn reconcile( let role_group = shs.rolegroup(&rgr).context(CannotRetrieveRoleGroupSnafu)?; + // Merge config_overrides from both nodes and role group levels + // Role group level overrides nodes (role) level, so start with nodes and merge role group into it + let mut merged_config_overrides = shs.spec.nodes.config.config_overrides.clone(); + merged_config_overrides.merge(&role_group.config.config_overrides); + let config_map = build_config_map( shs, - &role_group.config.config_overrides, + &merged_config_overrides, &merged_config, &resolved_product_image.app_version_label_value, &rgr, diff --git a/tests/templates/kuttl/overrides/06-assert.yaml b/tests/templates/kuttl/overrides/06-assert.yaml index 6d0f090a..5257dfb3 100644 --- a/tests/templates/kuttl/overrides/06-assert.yaml +++ b/tests/templates/kuttl/overrides/06-assert.yaml @@ -28,9 +28,12 @@ metadata: name: spark-history-node-default data: security.properties: | + networkaddress.cache.negative.ttl=0 + networkaddress.cache.ttl=30 test.securityProperties.fromRg=rolegroup test.securityProperties.rg=rolegroup test.securityProperties.role=role + spark-defaults.conf: |- spark.hadoop.fs.s3a.endpoint https://eventlog-minio:9000/ spark.hadoop.fs.s3a.endpoint.region us-east-1 diff --git a/tests/templates/kuttl/smoke/43-assert.yaml.j2 b/tests/templates/kuttl/smoke/43-assert.yaml.j2 index d0c49cd3..fdd123e7 100644 --- a/tests/templates/kuttl/smoke/43-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/43-assert.yaml.j2 @@ -17,7 +17,7 @@ timeout: 60 commands: - script: | expected=$(cat <<'YAMLEOF' | sed "s|__NAMESPACE__|$NAMESPACE|g" | yq -o=json - security.properties: "" + security.properties: "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n" spark-defaults.conf: |- {% if test_scenario['values']['s3-use-tls'] == 'true' %} spark.hadoop.fs.s3a.endpoint https://eventlog-minio:9000/ From f2108462f2b17091a00a8ece689a2be7c93234d0 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Fri, 5 Jun 2026 18:38:48 +0200 Subject: [PATCH 05/50] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e002d38d..91c1ab5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ All notable changes to this project will be documented in this file. ### Fixed - Application template merging should not ignore "*Overrides" fields ([#680]). +- SparkHistoryServer security.properties now contain the correct JVM DNS settings used across all Java products in the SDP platform ([#692]). ### Changed - Document Helm deployed RBAC permissions and remove unnecessary permissions ([#674]). +- Remove runtime usage of `product-config`. Default values that previously came from `deploy/config-spec/properties.yaml` are now defined in operator code and merged with CRD `configOverrides` ([#692]). - BREAKING: Each custom resource accepts now only the known config files in `configOverrides`: - `SparkApplication`: `spark-env.sh` and `security.properties` - `SparkHistoryServer`: `spark-defaults.conf`, `spark-env.sh` and `security.properties` @@ -30,6 +32,7 @@ All notable changes to this project will be documented in this file. [#684]: https://github.com/stackabletech/spark-k8s-operator/pull/684 [#687]: https://github.com/stackabletech/spark-k8s-operator/pull/687 [#689]: https://github.com/stackabletech/spark-k8s-operator/pull/689 +[#692]: https://github.com/stackabletech/spark-k8s-operator/pull/692 ## [26.3.0] - 2026-03-16 From 15f0295a7ec4253f61c35d64d0b1d121f897fe20 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:04:26 +0200 Subject: [PATCH 06/50] implement review feedback --- CHANGELOG.md | 1 + Cargo.lock | 1 - Cargo.nix | 4 -- Cargo.toml | 1 - extra/crds.yaml | 6 +++ rust/operator-binary/Cargo.toml | 1 - rust/operator-binary/src/config/mod.rs | 1 - rust/operator-binary/src/config/writer.rs | 46 ------------------- rust/operator-binary/src/connect/common.rs | 2 +- rust/operator-binary/src/connect/s3.rs | 12 +++-- rust/operator-binary/src/crd/history.rs | 39 +++++++--------- .../operator-binary/src/history/controller.rs | 2 +- .../src/spark_k8s_controller.rs | 2 +- 13 files changed, 34 insertions(+), 84 deletions(-) delete mode 100644 rust/operator-binary/src/config/writer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 91c1ab5f..ef281f5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. - Document Helm deployed RBAC permissions and remove unnecessary permissions ([#674]). - Remove runtime usage of `product-config`. Default values that previously came from `deploy/config-spec/properties.yaml` are now defined in operator code and merged with CRD `configOverrides` ([#692]). +- BREAKING: Values previously configured via `properties.yaml` (the `product-config` mechanism) are no longer read at runtime. Users who relied on this file to set configuration defaults must migrate those settings to the `configOverrides` field in their custom resources ([#692]). - BREAKING: Each custom resource accepts now only the known config files in `configOverrides`: - `SparkApplication`: `spark-env.sh` and `security.properties` - `SparkHistoryServer`: `spark-defaults.conf`, `spark-env.sh` and `security.properties` diff --git a/Cargo.lock b/Cargo.lock index 5c81e5a5..697deb75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2997,7 +2997,6 @@ dependencies = [ "const_format", "futures 0.3.32", "indoc", - "java-properties", "regex", "rstest", "semver", diff --git a/Cargo.nix b/Cargo.nix index 47f55b9f..30ce602a 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -9957,10 +9957,6 @@ rec { name = "indoc"; packageId = "indoc"; } - { - name = "java-properties"; - packageId = "java-properties"; - } { name = "regex"; packageId = "regex"; diff --git a/Cargo.toml b/Cargo.toml index b2b4da2c..7a677dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ built = { version = "0.8", features = ["chrono", "git2"] } clap = "4.5" const_format = "0.2" futures = { version = "0.3", features = ["compat"] } -java-properties = "2.0" rstest = "0.26" semver = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/extra/crds.yaml b/extra/crds.yaml index 04dc601e..83bbab56 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -2825,6 +2825,7 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -2835,6 +2836,7 @@ spec: type: object spark-defaults.conf: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -2845,6 +2847,7 @@ spec: type: object spark-env.sh: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -3244,6 +3247,7 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -3254,6 +3258,7 @@ spec: type: object spark-defaults.conf: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -3264,6 +3269,7 @@ spec: type: object spark-env.sh: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 667520c9..ecce5188 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -16,7 +16,6 @@ const_format.workspace = true semver.workspace = true serde.workspace = true serde_json.workspace = true -java-properties.workspace = true serde_yaml.workspace = true snafu.workspace = true strum.workspace = true diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index cb7d7920..271c6d99 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -1,2 +1 @@ pub mod jvm; -pub mod writer; diff --git a/rust/operator-binary/src/config/writer.rs b/rust/operator-binary/src/config/writer.rs deleted file mode 100644 index 3591a1b2..00000000 --- a/rust/operator-binary/src/config/writer.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Writers for Java `.properties` files. -//! -//! This is adapted from the `product-config` writer so we can render -//! configuration files without depending on the `product-config` crate. - -use std::io::Write; - -use java_properties::{PropertiesError, PropertiesWriter}; -use snafu::{ResultExt, Snafu}; - -#[derive(Debug, Snafu)] -pub enum PropertiesWriterError { - #[snafu(display("failed to create properties file"))] - Properties { source: PropertiesError }, - - #[snafu(display("failed to convert properties file byte array to UTF-8"))] - FromUtf8 { source: std::string::FromUtf8Error }, -} - -/// Creates a common Java properties file string in the format: -/// `property_1=value_1\nproperty_2=value_2\n`. -pub fn to_java_properties_string<'a, T>(properties: T) -> Result -where - T: Iterator)>, -{ - let mut output = Vec::new(); - write_java_properties(&mut output, properties)?; - String::from_utf8(output).context(FromUtf8Snafu) -} - -/// Writes Java properties to the given writer. A `None` value is written as an -/// empty value (`key=`). -fn write_java_properties<'a, W, T>(writer: W, properties: T) -> Result<(), PropertiesWriterError> -where - W: Write, - T: Iterator)>, -{ - let mut writer = PropertiesWriter::new(writer); - for (key, value) in properties { - let property_value = value.as_deref().unwrap_or_default(); - writer.write(key, property_value).context(PropertiesSnafu)?; - } - writer.flush().context(PropertiesSnafu)?; - - Ok(()) -} diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index 40b14a8a..eac282e9 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -4,12 +4,12 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ kvp::ObjectLabels, role_utils::{JavaCommonConfig, JvmArgumentOverrides}, + v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, }; use strum::Display; use super::crd::CONNECT_EXECUTOR_ROLE_NAME; use crate::{ - config::writer::{PropertiesWriterError, to_java_properties_string}, connect::crd::{ CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, CONNECT_SERVER_ROLE_NAME, DEFAULT_SPARK_CONNECT_GROUP_NAME, diff --git a/rust/operator-binary/src/connect/s3.rs b/rust/operator-binary/src/connect/s3.rs index 75d29428..146c68db 100644 --- a/rust/operator-binary/src/connect/s3.rs +++ b/rust/operator-binary/src/connect/s3.rs @@ -376,13 +376,17 @@ impl ResolvedS3 { #[cfg(test)] mod tests { use rstest::*; - use stackable_operator::commons::{ - secret_class::SecretClassVolume, - tls_verification::{CaCert, Tls, TlsClientDetails, TlsServerVerification, TlsVerification}, + use stackable_operator::{ + commons::{ + secret_class::SecretClassVolume, + tls_verification::{ + CaCert, Tls, TlsClientDetails, TlsServerVerification, TlsVerification, + }, + }, + v2::config_file_writer::to_java_properties_string, }; use super::*; - use crate::config::writer::to_java_properties_string; fn connection_fixture( credentials_secret_class: Option<&str>, diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 3465f82f..169daabd 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -15,7 +15,7 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, - config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, + config_overrides::KeyValueOverridesProvider, crd::s3, deep_merger::ObjectOverrides, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, @@ -25,6 +25,7 @@ use stackable_operator::{ role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef}, schemars::{self, JsonSchema}, shared::time::Duration, + v2::config_overrides::KeyValueConfigOverrides, versioned::versioned, }; use strum::{Display, EnumIter}; @@ -150,33 +151,25 @@ impl KeyValueOverridesProvider for v1alpha1::ConfigOverrides { JVM_SECURITY_PROPERTIES_FILE => self.security_properties.as_ref(), _ => None, }; - field - .map(KeyValueConfigOverrides::as_product_config_overrides) - .unwrap_or_default() + field.map(|f| f.overrides.clone()).unwrap_or_default() } } impl Merge for v1alpha1::ConfigOverrides { fn merge(&mut self, defaults: &Self) { - merge_key_value_config_overrides( - &mut self.spark_defaults_conf, - &defaults.spark_defaults_conf, - ); - merge_key_value_config_overrides(&mut self.spark_env_sh, &defaults.spark_env_sh); - merge_key_value_config_overrides( - &mut self.security_properties, - &defaults.security_properties, - ); - } -} - -fn merge_key_value_config_overrides( - base: &mut Option, - overlay: &Option, -) { - let base = base.get_or_insert_default(); - if let Some(overlay) = overlay { - base.overrides.extend(overlay.overrides.clone()); + if let Some(defaults) = &defaults.spark_defaults_conf { + self.spark_defaults_conf + .get_or_insert_default() + .merge(defaults); + } + if let Some(defaults) = &defaults.spark_env_sh { + self.spark_env_sh.get_or_insert_default().merge(defaults); + } + if let Some(defaults) = &defaults.security_properties { + self.security_properties + .get_or_insert_default() + .merge(defaults); + } } } diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 15569333..912ca021 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -45,12 +45,12 @@ use stackable_operator::{ }, role_utils::RoleGroupRef, shared::time::Duration, + v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, }; use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ Ctx, - config::writer::{PropertiesWriterError, to_java_properties_string}, crd::{ constants::{ ACCESS_KEY_ID, DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 7bb83a30..49e87a5c 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -44,12 +44,12 @@ use stackable_operator::{ }, role_utils::RoleGroupRef, shared::time::Duration, + v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, }; use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ Ctx, - config::writer::{PropertiesWriterError, to_java_properties_string}, crd::{ constants::*, logdir::ResolvedLogDir, From 3e009b80d98ab7ebe6e230482fdb7602917f811e Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:42:45 +0200 Subject: [PATCH 07/50] update pull policy in some tests --- .../kuttl/spark-history-server/10-deploy-spark-app.yaml.j2 | 1 + .../kuttl/spark-history-server/12-deploy-spark-app.yaml.j2 | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/templates/kuttl/spark-history-server/10-deploy-spark-app.yaml.j2 b/tests/templates/kuttl/spark-history-server/10-deploy-spark-app.yaml.j2 index b1c93d44..d8798d56 100644 --- a/tests/templates/kuttl/spark-history-server/10-deploy-spark-app.yaml.j2 +++ b/tests/templates/kuttl/spark-history-server/10-deploy-spark-app.yaml.j2 @@ -19,6 +19,7 @@ spec: {% else %} productVersion: "{{ test_scenario['values']['spark'] }}" {% endif %} + pullPolicy: IfNotPresent mode: cluster mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: "/stackable/spark/examples/jars/spark-examples.jar" diff --git a/tests/templates/kuttl/spark-history-server/12-deploy-spark-app.yaml.j2 b/tests/templates/kuttl/spark-history-server/12-deploy-spark-app.yaml.j2 index 2ea61a61..8aa1ab9c 100644 --- a/tests/templates/kuttl/spark-history-server/12-deploy-spark-app.yaml.j2 +++ b/tests/templates/kuttl/spark-history-server/12-deploy-spark-app.yaml.j2 @@ -19,6 +19,7 @@ spec: {% else %} productVersion: "{{ test_scenario['values']['spark'] }}" {% endif %} + pullPolicy: IfNotPresent mode: cluster mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: "/stackable/spark/examples/jars/spark-examples.jar" From d75ec312911bad26e3691dc9a005d6b38cb867d9 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:08:50 +0200 Subject: [PATCH 08/50] remove shs argument from build_configmap() --- rust/operator-binary/src/crd/history.rs | 54 +++++++-- .../operator-binary/src/history/controller.rs | 104 ++++++------------ .../src/history/controller/validate.rs | 48 +++++++- 3 files changed, 125 insertions(+), 81 deletions(-) diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 169daabd..7cf99aa9 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -19,7 +19,7 @@ use stackable_operator::{ crd::s3, deep_merger::ObjectOverrides, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, - kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef}, + kube::{CustomResource, ResourceExt}, product_config_utils::Configuration, product_logging::{self, spec::Logging}, role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef}, @@ -50,6 +50,12 @@ pub enum Error { ConstructJvmArguments { source: crate::history::config::jvm::Error, }, + + #[snafu(display("too many cleaner replicas"))] + TooManyCleanerReplicas, + + #[snafu(display("too many cleaner role groups: {role_groups}"))] + TooManyCleanerRoleGroups { role_groups: String }, } pub type SparkHistoryRoleType = Role< @@ -234,18 +240,44 @@ impl v1alpha1::SparkHistoryServer { .map(i32::from) } - pub fn cleaner_rolegroups(&self) -> Vec> { - let mut rgs = vec![]; - for (rg_name, rg_config) in &self.spec.nodes.role_groups { - if let Some(true) = rg_config.config.config.cleaner { - rgs.push(RoleGroupRef { - cluster: ObjectRef::from_obj(self), - role: HISTORY_ROLE_NAME.into(), - role_group: rg_name.into(), - }); + // Returns the name of the cleaner role group if any. + // Raises an error when: + // * there are multiple cleaner role groups + // * the cleaner role group has more than one replica. + pub fn cleaner_rolegroup_name(&self) -> Result, Error> { + let rgs = self + .spec + .nodes + .role_groups + .keys() + .filter(|rg_name| { + self.spec + .nodes + .role_groups + .get(*rg_name) + .and_then(|rg| rg.config.config.cleaner) + .unwrap_or(false) + }) + .cloned() + .collect::>(); + + match rgs.len() { + 0 => Ok(None), + 1 => match self + .spec + .nodes + .role_groups + .get(&rgs[0]) + .and_then(|rg| rg.replicas) + { + Some(replicas) if replicas > 1 => Err(TooManyCleanerReplicasSnafu.build()), + _ => Ok(Some(rgs[0].clone())), + }, + _ => Err(TooManyCleanerRoleGroupsSnafu { + role_groups: rgs.join(","), } + .build()), } - rgs } pub fn merged_env( diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 912ca021..64304aea 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -151,15 +151,6 @@ pub enum Error { #[snafu(display("failed to resolve and merge config for role and role group"))] FailedToResolveConfig { source: crate::crd::history::Error }, - #[snafu(display("number of cleaner rolegroups exceeds 1"))] - TooManyCleanerRoleGroups, - - #[snafu(display("number of cleaner replicas exceeds 1"))] - TooManyCleanerReplicas, - - #[snafu(display("failed to resolve the log dir configuration"))] - LogDir { source: crate::crd::logdir::Error }, - #[snafu(display("failed to create cluster resources"))] CreateClusterResources { source: stackable_operator::cluster_resources::Error, @@ -241,6 +232,9 @@ pub enum Error { #[snafu(display("failed to build metrics service"))] BuildMetricsService { source: service::Error }, + + #[snafu(display("failed to serialize Spark default properties"))] + InvalidSparkDefaults { source: PropertiesWriterError }, } impl ReconcilerError for Error { @@ -320,12 +314,16 @@ pub async fn reconcile( merged_config_overrides.merge(&role_group.config.config_overrides); let config_map = build_config_map( - shs, + &validated, &merged_config_overrides, &merged_config, - &resolved_product_image.app_version_label_value, &rgr, - log_dir, + &Labels::recommended(&recommended_labels( + shs, + &resolved_product_image.app_version_label_value, + &rgr.role_group, + )) + .context(LabelBuildSnafu)?, )?; let metrics_service = build_rolegroup_metrics_service(shs, resolved_product_image, &rgr) @@ -429,16 +427,16 @@ pub fn error_policy( #[allow(clippy::result_large_err)] fn build_config_map( - shs: &v1alpha1::SparkHistoryServer, + validated: &validate::ValidatedSparkHistoryServer, config_overrides: &v1alpha1::ConfigOverrides, merged_config: &HistoryConfig, - app_version_label_value: &str, rolegroupref: &RoleGroupRef, - log_dir: &ResolvedLogDir, + recommended_labels: &Labels, ) -> Result { let cm_name = rolegroupref.object_name(); - let spark_defaults = spark_defaults(shs, log_dir, rolegroupref)?; + let spark_defaults = to_java_properties_string(spark_defaults(validated, rolegroupref).iter()) + .context(InvalidSparkDefaultsSnafu)?; let mut jvm_sec_props = default_jvm_security_properties(); jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); @@ -448,16 +446,10 @@ fn build_config_map( cm_builder .metadata( ObjectMetaBuilder::new() - .name_and_namespace(shs) + .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference_from_resource(shs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&recommended_labels( - shs, - app_version_label_value, - &rolegroupref.role_group, - )) - .context(MetadataBuildSnafu)? + .ownerreference(validated.owner_reference.clone()) + .labels(recommended_labels.clone()) .build(), ) .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) @@ -690,26 +682,22 @@ fn build_stateful_set( }) } -#[allow(clippy::result_large_err)] fn spark_defaults( - shs: &v1alpha1::SparkHistoryServer, - log_dir: &ResolvedLogDir, + validated: &validate::ValidatedSparkHistoryServer, rolegroupref: &RoleGroupRef, -) -> Result { - let mut log_dir_settings = log_dir.history_server_spark_config().context(LogDirSnafu)?; +) -> BTreeMap> { + let mut default_properties = validated.log_dir_settings.clone(); // add cleaner spark settings if requested - log_dir_settings.extend(cleaner_config(shs, rolegroupref)?); + default_properties.extend(cleaner_config(validated, rolegroupref)); // add user provided configuration. These can overwrite everything. - log_dir_settings.extend(shs.spec.spark_conf.clone()); - - // stringify the spark configuration for the ConfigMap - Ok(log_dir_settings - .iter() - .map(|(k, v)| format!("{k} {v}")) - .collect::>() - .join("\n")) + default_properties.extend(validated.spark_conf.clone()); + + default_properties + .into_iter() + .map(|(key, value)| (key, Some(value))) + .collect() } fn command_args(logdir: &ResolvedLogDir) -> Vec { @@ -761,37 +749,17 @@ fn defined_key_value_overrides( } /// Return the Spark properties for the cleaner role group (if any). -/// There should be only one role group with "cleaner=true" and this -/// group should have a replica count of 0 or 1. -#[allow(clippy::result_large_err)] fn cleaner_config( - shs: &v1alpha1::SparkHistoryServer, + validated: &validate::ValidatedSparkHistoryServer, rolegroup_ref: &RoleGroupRef, -) -> Result, Error> { - let mut result = BTreeMap::new(); - - // all role groups with "cleaner=true" - let cleaner_rolegroups = shs.cleaner_rolegroups(); - - // should have max of one - if cleaner_rolegroups.len() > 1 { - return TooManyCleanerRoleGroupsSnafu.fail(); - } - - // check if cleaner is set for this rolegroup ref - if cleaner_rolegroups.len() == 1 && cleaner_rolegroups[0].role_group == rolegroup_ref.role_group - { - if let Some(replicas) = shs.replicas(rolegroup_ref) { - if replicas > 1 { - return TooManyCleanerReplicasSnafu.fail(); - } else { - result.insert( - "spark.history.fs.cleaner.enabled".to_string(), - "true".to_string(), - ); - } +) -> BTreeMap { + match validated.cleaner_rolegroup_name.as_ref() { + Some(cleaner_rolegroup) if cleaner_rolegroup == &rolegroup_ref.role_group => { + BTreeMap::from([( + "spark.history.fs.cleaner.enabled".to_string(), + "true".to_string(), + )]) } + _ => BTreeMap::new(), } - - Ok(result) } diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index e8a39e23..33857459 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,10 +3,14 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use snafu::{ResultExt, Snafu}; +use std::collections::BTreeMap; + +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, + k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference, + kube::{Resource, ResourceExt}, }; use crate::{ @@ -20,13 +24,34 @@ pub enum Error { ResolveProductImage { source: product_image_selection::Error, }, + + #[snafu(display("object is missing metadata to build owner reference"))] + MissingOwnerReference, + + #[snafu(display("object is missing namespace"))] + MissingNamespace, + + #[snafu(display("invalid cleaner configuration"))] + InvalidCleanerConfiguration { source: crate::crd::history::Error }, + + #[snafu(display("invalid log directory settings"))] + InvalidLogDirSettings { source: crate::crd::logdir::Error }, } type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { - pub log_dir: ResolvedLogDir, + pub namespace: String, + pub owner_reference: OwnerReference, + pub cleaner_rolegroup_name: Option, + pub spark_conf: BTreeMap, pub resolved_product_image: ResolvedProductImage, + // These two are a bit redundant right now. + // This is a temporary situation until we remove all v1alpha1::SparkHistoryServer usages after validation. + // Currently log_dir_settings is needed for history::controller::build_configmap() function whereas log_dir + // is needed for command args and volume mounts. + pub log_dir: ResolvedLogDir, + pub log_dir_settings: BTreeMap, } pub fn validate( @@ -44,8 +69,27 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; + let owner_reference = shs + .controller_owner_ref(&()) + .context(MissingOwnerReferenceSnafu)?; + let namespace = shs.namespace().context(MissingNamespaceSnafu)?; + + let cleaner_rolegroup_name = shs + .cleaner_rolegroup_name() + .context(InvalidCleanerConfigurationSnafu)?; + + let log_dir_settings = dereferenced + .log_dir + .history_server_spark_config() + .context(InvalidLogDirSettingsSnafu)?; + Ok(ValidatedSparkHistoryServer { + namespace, + owner_reference, + cleaner_rolegroup_name, + spark_conf: shs.spec.spark_conf.clone(), log_dir: dereferenced.log_dir, + log_dir_settings, resolved_product_image, }) } From 76931d7a13259132bc22744ed0a4d40b910d9c33 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:10:02 +0200 Subject: [PATCH 09/50] remove Merge impl for history ConfigOverrides. --- extra/crds.yaml | 12 ++--- rust/operator-binary/src/crd/history.rs | 59 ++++++------------------- 2 files changed, 20 insertions(+), 51 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index 83bbab56..f4327c74 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -2827,34 +2827,34 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-defaults.conf: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -3249,34 +3249,34 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-defaults.conf: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 7cf99aa9..4b23750c 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -124,57 +124,26 @@ pub mod versioned { pub listener_class: String, } - #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] + #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize, Merge)] pub struct ConfigOverrides { - #[serde( - default, - rename = "spark-defaults.conf", - skip_serializing_if = "Option::is_none" - )] - pub spark_defaults_conf: Option, - - #[serde( - default, - rename = "spark-env.sh", - skip_serializing_if = "Option::is_none" - )] - pub spark_env_sh: Option, - - #[serde( - default, - rename = "security.properties", - skip_serializing_if = "Option::is_none" - )] - pub security_properties: Option, + #[serde(default, rename = "spark-defaults.conf")] + pub spark_defaults_conf: KeyValueConfigOverrides, + + #[serde(default, rename = "spark-env.sh")] + pub spark_env_sh: KeyValueConfigOverrides, + + #[serde(default, rename = "security.properties")] + pub security_properties: KeyValueConfigOverrides, } } impl KeyValueOverridesProvider for v1alpha1::ConfigOverrides { fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - let field = match file { - SPARK_DEFAULTS_FILE_NAME => self.spark_defaults_conf.as_ref(), - SPARK_ENV_SH_FILE_NAME => self.spark_env_sh.as_ref(), - JVM_SECURITY_PROPERTIES_FILE => self.security_properties.as_ref(), - _ => None, - }; - field.map(|f| f.overrides.clone()).unwrap_or_default() - } -} - -impl Merge for v1alpha1::ConfigOverrides { - fn merge(&mut self, defaults: &Self) { - if let Some(defaults) = &defaults.spark_defaults_conf { - self.spark_defaults_conf - .get_or_insert_default() - .merge(defaults); - } - if let Some(defaults) = &defaults.spark_env_sh { - self.spark_env_sh.get_or_insert_default().merge(defaults); - } - if let Some(defaults) = &defaults.security_properties { - self.security_properties - .get_or_insert_default() - .merge(defaults); + match file { + SPARK_DEFAULTS_FILE_NAME => self.spark_defaults_conf.overrides.clone(), + SPARK_ENV_SH_FILE_NAME => self.spark_env_sh.overrides.clone(), + JVM_SECURITY_PROPERTIES_FILE => self.security_properties.overrides.clone(), + _ => BTreeMap::new(), } } } From 9bba56aa8ab848d0c7dec3ec0d5053ffbdf4f5db Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:21:51 +0200 Subject: [PATCH 10/50] update tests --- .../fixtures/spark-history-node-default-data.json | 2 +- tests/templates/kuttl/smoke/43-assert.yaml.j2 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json b/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json index 75db8909..1c5d126b 100644 --- a/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json +++ b/tests/templates/kuttl/product-config-compat/fixtures/spark-history-node-default-data.json @@ -1,5 +1,5 @@ { "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", - "spark-defaults.conf": "spark.history.fs.cleaner.enabled true\nspark.history.fs.logDirectory file:///tmp/", + "spark-defaults.conf": "spark.history.fs.cleaner.enabled=true\nspark.history.fs.logDirectory=file\\:///tmp/\n", "spark-env.sh": "" } diff --git a/tests/templates/kuttl/smoke/43-assert.yaml.j2 b/tests/templates/kuttl/smoke/43-assert.yaml.j2 index fdd123e7..e3c94693 100644 --- a/tests/templates/kuttl/smoke/43-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/43-assert.yaml.j2 @@ -18,16 +18,16 @@ commands: - script: | expected=$(cat <<'YAMLEOF' | sed "s|__NAMESPACE__|$NAMESPACE|g" | yq -o=json security.properties: "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n" - spark-defaults.conf: |- + spark-defaults.conf: | {% if test_scenario['values']['s3-use-tls'] == 'true' %} - spark.hadoop.fs.s3a.endpoint https://eventlog-minio:9000/ + spark.hadoop.fs.s3a.endpoint=https\://eventlog-minio\:9000/ {% else %} - spark.hadoop.fs.s3a.endpoint http://eventlog-minio:9000/ + spark.hadoop.fs.s3a.endpoint=http\://eventlog-minio\:9000/ {% endif %} - spark.hadoop.fs.s3a.endpoint.region us-east-1 - spark.hadoop.fs.s3a.path.style.access true - spark.history.fs.cleaner.enabled true - spark.history.fs.logDirectory s3a://spark-logs/eventlogs/ + spark.hadoop.fs.s3a.endpoint.region=us-east-1 + spark.hadoop.fs.s3a.path.style.access=true + spark.history.fs.cleaner.enabled=true + spark.history.fs.logDirectory=s3a\://spark-logs/eventlogs/ spark-env.sh: "" {% if lookup('env', 'VECTOR_AGGREGATOR') %} vector.yaml: | From 1f64bf2c0aa0b28171f325ab6b0cd3e928b72096 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:31:48 +0200 Subject: [PATCH 11/50] fix tests and config overrides merging --- rust/operator-binary/src/history/controller.rs | 5 ++--- tests/templates/kuttl/overrides/06-assert.yaml | 17 +++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 64304aea..27f13958 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -309,9 +309,8 @@ pub async fn reconcile( let role_group = shs.rolegroup(&rgr).context(CannotRetrieveRoleGroupSnafu)?; // Merge config_overrides from both nodes and role group levels - // Role group level overrides nodes (role) level, so start with nodes and merge role group into it - let mut merged_config_overrides = shs.spec.nodes.config.config_overrides.clone(); - merged_config_overrides.merge(&role_group.config.config_overrides); + let mut merged_config_overrides = role_group.config.config_overrides; + merged_config_overrides.merge(&shs.spec.nodes.config.config_overrides); let config_map = build_config_map( &validated, diff --git a/tests/templates/kuttl/overrides/06-assert.yaml b/tests/templates/kuttl/overrides/06-assert.yaml index 5257dfb3..04415b86 100644 --- a/tests/templates/kuttl/overrides/06-assert.yaml +++ b/tests/templates/kuttl/overrides/06-assert.yaml @@ -1,7 +1,7 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -timeout: 900 +timeout: 60 --- apiVersion: apps/v1 kind: StatefulSet @@ -34,13 +34,14 @@ data: test.securityProperties.rg=rolegroup test.securityProperties.role=role - spark-defaults.conf: |- - spark.hadoop.fs.s3a.endpoint https://eventlog-minio:9000/ - spark.hadoop.fs.s3a.endpoint.region us-east-1 - spark.hadoop.fs.s3a.path.style.access true - spark.history.fs.cleaner.enabled true - spark.history.fs.logDirectory s3a://spark-logs/eventlogs/ - test.sparkConf true + spark-defaults.conf: | + spark.hadoop.fs.s3a.endpoint=https\://eventlog-minio\:9000/ + spark.hadoop.fs.s3a.endpoint.region=us-east-1 + spark.hadoop.fs.s3a.path.style.access=true + spark.history.fs.cleaner.enabled=true + spark.history.fs.logDirectory=s3a\://spark-logs/eventlogs/ + test.sparkConf=true + spark-env.sh: |- export TEST_SPARK-ENV-SH_FROM_RG="ROLEGROUP" export TEST_SPARK-ENV-SH_RG="ROLEGROUP" From a24fc24c353beb9744058f56de8c0c1795360029 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:11:51 +0200 Subject: [PATCH 12/50] more cleanups --- extra/crds.yaml | 36 ++++++++---- rust/operator-binary/src/crd/history.rs | 12 ---- rust/operator-binary/src/crd/mod.rs | 57 +++--------------- .../src/crd/template_merger.rs | 58 ++++++++++--------- .../operator-binary/src/history/controller.rs | 18 +----- .../src/spark_k8s_controller.rs | 19 ++---- 6 files changed, 70 insertions(+), 130 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index f4327c74..125e2fde 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -691,23 +691,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -1488,23 +1490,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -1704,23 +1708,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -5321,23 +5327,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -6118,23 +6126,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -6334,23 +6344,25 @@ spec: properties: security.properties: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-env.sh: additionalProperties: + nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 4b23750c..33be5b99 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -15,7 +15,6 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, - config_overrides::KeyValueOverridesProvider, crd::s3, deep_merger::ObjectOverrides, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, @@ -137,17 +136,6 @@ pub mod versioned { } } -impl KeyValueOverridesProvider for v1alpha1::ConfigOverrides { - fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - match file { - SPARK_DEFAULTS_FILE_NAME => self.spark_defaults_conf.overrides.clone(), - SPARK_ENV_SH_FILE_NAME => self.spark_env_sh.overrides.clone(), - JVM_SECURITY_PROPERTIES_FILE => self.security_properties.overrides.clone(), - _ => BTreeMap::new(), - } - } -} - impl v1alpha1::SparkHistoryServer { /// Returns a reference to the role. Raises an error if the role is not defined. pub fn role(&self) -> &SparkHistoryRoleType { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index c4289e2f..4c65b43f 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -24,7 +24,6 @@ use stackable_operator::{ fragment::{self, ValidationError}, merge::Merge, }, - config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, crd::s3, k8s_openapi::{ api::core::v1::{EmptyDirVolumeSource, EnvVar, PodTemplateSpec, Volume, VolumeMount}, @@ -38,6 +37,7 @@ use stackable_operator::{ schemars::{self, JsonSchema}, shared::time::Duration, utils::crds::raw_object_list_schema, + v2::config_overrides::KeyValueConfigOverrides, versioned::versioned, }; @@ -250,54 +250,13 @@ pub mod versioned { pub log_file_directory: Option, } - #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] + #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize, Merge)] pub struct ConfigOverrides { - #[serde( - default, - rename = "spark-env.sh", - skip_serializing_if = "Option::is_none" - )] - pub spark_env_sh: Option, - - #[serde( - default, - rename = "security.properties", - skip_serializing_if = "Option::is_none" - )] - pub security_properties: Option, - } -} - -impl KeyValueOverridesProvider for v1alpha1::ConfigOverrides { - fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - let field = match file { - SPARK_ENV_SH_FILE_NAME => self.spark_env_sh.as_ref(), - JVM_SECURITY_PROPERTIES_FILE => self.security_properties.as_ref(), - _ => None, - }; - field - .map(KeyValueConfigOverrides::as_product_config_overrides) - .unwrap_or_default() - } -} - -impl Merge for v1alpha1::ConfigOverrides { - fn merge(&mut self, defaults: &Self) { - merge_key_value_config_overrides(&mut self.spark_env_sh, &defaults.spark_env_sh); - merge_key_value_config_overrides( - &mut self.security_properties, - &defaults.security_properties, - ); - } -} + #[serde(default, rename = "spark-env.sh")] + pub spark_env_sh: KeyValueConfigOverrides, -fn merge_key_value_config_overrides( - base: &mut Option, - overlay: &Option, -) { - let base = base.get_or_insert_default(); - if let Some(overlay) = overlay { - base.overrides.extend(overlay.overrides.clone()); + #[serde(default, rename = "security.properties")] + pub security_properties: KeyValueConfigOverrides, } } @@ -1105,10 +1064,10 @@ fn resources_to_executor_props( /// providing escaped values. pub fn to_spark_env_sh_string<'a, T>(properties: T) -> String where - T: Iterator, + T: Iterator)>, { properties - .map(|(k, v)| format!("export {k}=\"{v}\"")) + .filter_map(|(k, v)| v.as_ref().map(|v| format!("export {k}=\"{v}\""))) .collect::>() .join("\n") } diff --git a/rust/operator-binary/src/crd/template_merger.rs b/rust/operator-binary/src/crd/template_merger.rs index 667e5128..c11d2cb9 100644 --- a/rust/operator-binary/src/crd/template_merger.rs +++ b/rust/operator-binary/src/crd/template_merger.rs @@ -192,7 +192,11 @@ where // Clone the base and merge the overlay config into it let mut merged = b.clone(); merged.config.merge(&o.config); - merged.config_overrides.merge(&o.config_overrides); + // Merge with overlay precedence: keep overlay values for conflicts, + // fill missing keys from base. + let mut config_overrides = o.config_overrides.clone(); + config_overrides.merge(&b.config_overrides); + merged.config_overrides = config_overrides; merged.env_overrides = merge_hashmap(&b.env_overrides, &o.env_overrides); merged.pod_overrides = merge_pod_template_spec(&b.pod_overrides, &o.pod_overrides); Some(merged) @@ -218,10 +222,11 @@ where // Clone the base and merge overlay let mut merged = b.clone(); merged.config.config.merge(&o.config.config); - merged - .config - .config_overrides - .merge(&o.config.config_overrides); + // Merge with overlay precedence: keep overlay values for conflicts, + // fill missing keys from base. + let mut config_overrides = o.config.config_overrides.clone(); + config_overrides.merge(&b.config.config_overrides); + merged.config.config_overrides = config_overrides; merged.config.env_overrides = merge_hashmap(&b.config.env_overrides, &o.config.env_overrides); merged.config.pod_overrides = @@ -482,58 +487,61 @@ mod tests { let submit_security_props = merged .spec .job - .and_then(|config| config.config_overrides.security_properties) - .map(|security_properties| security_properties.overrides) + .map(|config| config.config_overrides.security_properties.overrides) .unwrap(); assert_eq!( submit_security_props.get("test.base.only"), - Some(&"base".to_string()) + Some(&Some("base".to_string())) ); assert_eq!( submit_security_props.get("test.overridden"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); assert_eq!( submit_security_props.get("test.overlay.only"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); let driver_security_props = merged .spec .driver - .and_then(|config| config.config_overrides.security_properties) - .map(|security_properties| security_properties.overrides) + .map(|config| config.config_overrides.security_properties.overrides) .unwrap(); assert_eq!( driver_security_props.get("test.base.only"), - Some(&"base".to_string()) + Some(&Some("base".to_string())) ); assert_eq!( driver_security_props.get("test.overridden"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); assert_eq!( driver_security_props.get("test.overlay.only"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); let executor_security_props = merged .spec .executor - .and_then(|role_group| role_group.config.config_overrides.security_properties) - .map(|security_properties| security_properties.overrides) + .map(|role_group| { + role_group + .config + .config_overrides + .security_properties + .overrides + }) .unwrap(); assert_eq!( executor_security_props.get("test.base.only"), - Some(&"base".to_string()) + Some(&Some("base".to_string())) ); assert_eq!( executor_security_props.get("test.overridden"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); assert_eq!( executor_security_props.get("test.overlay.only"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); } @@ -580,13 +588,12 @@ mod tests { let submit_security_props = merged .spec .job - .and_then(|config| config.config_overrides.security_properties) - .map(|security_properties| security_properties.overrides) + .map(|config| config.config_overrides.security_properties.overrides) .unwrap(); assert_eq!( submit_security_props.get("test.base.only"), - Some(&"base".to_string()) + Some(&Some("base".to_string())) ); } @@ -633,13 +640,12 @@ mod tests { let submit_security_props = merged .spec .job - .and_then(|config| config.config_overrides.security_properties) - .map(|security_properties| security_properties.overrides) + .map(|config| config.config_overrides.security_properties.overrides) .unwrap(); assert_eq!( submit_security_props.get("test.overlay.only"), - Some(&"overlay".to_string()) + Some(&Some("overlay".to_string())) ); } diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 27f13958..1584f4ad 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -19,7 +19,6 @@ use stackable_operator::{ cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, config::merge::Merge, - config_overrides::KeyValueOverridesProvider, crd::listener, k8s_openapi::{ DeepMerge, @@ -438,7 +437,7 @@ fn build_config_map( .context(InvalidSparkDefaultsSnafu)?; let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); + jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); let mut cm_builder = ConfigMapBuilder::new(); @@ -454,9 +453,7 @@ fn build_config_map( .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) .add_data( SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string( - defined_key_value_overrides(config_overrides, SPARK_ENV_SH_FILE_NAME).iter(), - ), + to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), ) .add_data( JVM_SECURITY_PROPERTIES_FILE, @@ -736,17 +733,6 @@ fn default_jvm_security_properties() -> BTreeMap> { .into() } -fn defined_key_value_overrides( - config_overrides: &v1alpha1::ConfigOverrides, - file_name: &str, -) -> BTreeMap { - config_overrides - .get_key_value_overrides(file_name) - .into_iter() - .filter_map(|(key, value)| value.map(|value| (key, value))) - .collect() -} - /// Return the Spark properties for the cleaner role group (if any). fn cleaner_config( validated: &validate::ValidatedSparkHistoryServer, diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 49e87a5c..a06b41f3 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -12,7 +12,6 @@ use stackable_operator::{ }, }, commons::product_image_selection::ResolvedProductImage, - config_overrides::KeyValueOverridesProvider, crd::s3, k8s_openapi::{ DeepMerge, Resource, @@ -722,11 +721,11 @@ fn pod_template_config_map( cm_builder.add_data( SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(defined_key_value_overrides(config_overrides).iter()), + to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), ); let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); + jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); cm_builder.add_data( JVM_SECURITY_PROPERTIES_FILE, @@ -762,11 +761,11 @@ fn submit_job_config_map( cm_builder.add_data( SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(defined_key_value_overrides(config_overrides).iter()), + to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), ); let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.get_key_value_overrides(JVM_SECURITY_PROPERTIES_FILE)); + jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); cm_builder.add_data( JVM_SECURITY_PROPERTIES_FILE, @@ -794,16 +793,6 @@ fn default_jvm_security_properties() -> BTreeMap> { .into() } -fn defined_key_value_overrides( - config_overrides: &v1alpha1::ConfigOverrides, -) -> BTreeMap { - config_overrides - .get_key_value_overrides(SPARK_ENV_SH_FILE_NAME) - .into_iter() - .filter_map(|(key, value)| value.map(|value| (key, value))) - .collect() -} - #[allow(clippy::too_many_arguments)] fn spark_job( spark_application: &v1alpha1::SparkApplication, From b410525d7e75b98ea27707fa57574ec532a72a3c Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:45:46 +0200 Subject: [PATCH 13/50] change namespace type --- .../src/history/controller/validate.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 33857459..d87f86d4 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,7 +3,7 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -11,6 +11,7 @@ use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference, kube::{Resource, ResourceExt}, + v2::types::kubernetes::NamespaceName, }; use crate::{ @@ -31,6 +32,11 @@ pub enum Error { #[snafu(display("object is missing namespace"))] MissingNamespace, + #[snafu(display("failed to parse namespace"))] + ParseNamespace { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + #[snafu(display("invalid cleaner configuration"))] InvalidCleanerConfiguration { source: crate::crd::history::Error }, @@ -41,7 +47,7 @@ pub enum Error { type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { - pub namespace: String, + pub namespace: NamespaceName, pub owner_reference: OwnerReference, pub cleaner_rolegroup_name: Option, pub spark_conf: BTreeMap, @@ -72,7 +78,8 @@ pub fn validate( let owner_reference = shs .controller_owner_ref(&()) .context(MissingOwnerReferenceSnafu)?; - let namespace = shs.namespace().context(MissingNamespaceSnafu)?; + let namespace = NamespaceName::from_str(&shs.namespace().context(MissingNamespaceSnafu)?) + .context(ParseNamespaceSnafu)?; let cleaner_rolegroup_name = shs .cleaner_rolegroup_name() From 791044e73808d982351e602ba776d5385a9740f3 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:12:14 +0200 Subject: [PATCH 14/50] replace owner reference with name and uid --- .../operator-binary/src/history/controller.rs | 2 +- .../src/history/controller/validate.rs | 48 +++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 1584f4ad..b7797bd0 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -446,7 +446,7 @@ fn build_config_map( ObjectMetaBuilder::new() .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference(validated.owner_reference.clone()) + .ownerreference(validated.owner_reference()) .labels(recommended_labels.clone()) .build(), ) diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index d87f86d4..90dcc6f9 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -11,7 +11,10 @@ use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference, kube::{Resource, ResourceExt}, - v2::types::kubernetes::NamespaceName, + v2::types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, }; use crate::{ @@ -26,17 +29,30 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("object is missing metadata to build owner reference"))] - MissingOwnerReference, + #[snafu(display("object is missing name"))] + MissingName, #[snafu(display("object is missing namespace"))] MissingNamespace, + #[snafu(display("object is missing UID"))] + MissingUid, + + #[snafu(display("failed to parse cluster name"))] + ParseName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + #[snafu(display("failed to parse namespace"))] ParseNamespace { source: stackable_operator::v2::macros::attributed_string_type::Error, }, + #[snafu(display("failed to parse UID"))] + ParseUid { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + #[snafu(display("invalid cleaner configuration"))] InvalidCleanerConfiguration { source: crate::crd::history::Error }, @@ -47,8 +63,9 @@ pub enum Error { type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { + pub name: ClusterName, pub namespace: NamespaceName, - pub owner_reference: OwnerReference, + pub uid: Uid, pub cleaner_rolegroup_name: Option, pub spark_conf: BTreeMap, pub resolved_product_image: ResolvedProductImage, @@ -60,6 +77,19 @@ pub struct ValidatedSparkHistoryServer { pub log_dir_settings: BTreeMap, } +impl ValidatedSparkHistoryServer { + pub fn owner_reference(&self) -> OwnerReference { + OwnerReference { + api_version: v1alpha1::SparkHistoryServer::api_version(&()).to_string(), + block_owner_deletion: Some(true), + controller: Some(true), + kind: v1alpha1::SparkHistoryServer::kind(&()).to_string(), + name: String::from(&self.name), + uid: String::from(&self.uid), + } + } +} + pub fn validate( shs: &v1alpha1::SparkHistoryServer, dereferenced: DereferencedSparkHistoryServer, @@ -75,11 +105,12 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let owner_reference = shs - .controller_owner_ref(&()) - .context(MissingOwnerReferenceSnafu)?; + let name = ClusterName::from_str(&shs.meta().name.clone().context(MissingNameSnafu)?) + .context(ParseNameSnafu)?; let namespace = NamespaceName::from_str(&shs.namespace().context(MissingNamespaceSnafu)?) .context(ParseNamespaceSnafu)?; + let uid = + Uid::from_str(&shs.meta().uid.clone().context(MissingUidSnafu)?).context(ParseUidSnafu)?; let cleaner_rolegroup_name = shs .cleaner_rolegroup_name() @@ -91,8 +122,9 @@ pub fn validate( .context(InvalidLogDirSettingsSnafu)?; Ok(ValidatedSparkHistoryServer { + name, namespace, - owner_reference, + uid, cleaner_rolegroup_name, spark_conf: shs.spec.spark_conf.clone(), log_dir: dereferenced.log_dir, From 70c7d7845776e3e23eb24daf1354b55fe22b15ae Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:50:45 +0200 Subject: [PATCH 15/50] validated history: add metadata and impl Resource --- .../operator-binary/src/history/controller.rs | 2 +- .../src/history/controller/validate.rs | 41 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index b7797bd0..0e922ae5 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -267,7 +267,7 @@ pub async fn reconcile( HISTORY_APP_NAME, OPERATOR_NAME, HISTORY_CONTROLLER_NAME, - &shs.object_ref(&()), + &validated.object_ref(&()), ClusterResourceApplyStrategy::Default, &shs.spec.object_overrides, ) diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 90dcc6f9..73a8e84e 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,13 +3,13 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use std::{collections::BTreeMap, str::FromStr}; +use std::{borrow::Cow, collections::BTreeMap, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference, + k8s_openapi::apimachinery::pkg::apis::meta::v1::{ObjectMeta, OwnerReference}, kube::{Resource, ResourceExt}, v2::types::{ kubernetes::{NamespaceName, Uid}, @@ -63,6 +63,7 @@ pub enum Error { type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { + pub metadata: ObjectMeta, pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, @@ -79,14 +80,45 @@ pub struct ValidatedSparkHistoryServer { impl ValidatedSparkHistoryServer { pub fn owner_reference(&self) -> OwnerReference { - OwnerReference { + let mut owner_reference = self.controller_owner_ref(&()).unwrap_or(OwnerReference { api_version: v1alpha1::SparkHistoryServer::api_version(&()).to_string(), block_owner_deletion: Some(true), controller: Some(true), kind: v1alpha1::SparkHistoryServer::kind(&()).to_string(), name: String::from(&self.name), uid: String::from(&self.uid), - } + }); + owner_reference.block_owner_deletion = Some(true); + owner_reference + } +} + +impl Resource for ValidatedSparkHistoryServer { + type DynamicType = (); + type Scope = ::Scope; + + fn kind(_: &Self::DynamicType) -> Cow<'_, str> { + ::kind(&()) + } + + fn group(_: &Self::DynamicType) -> Cow<'_, str> { + ::group(&()) + } + + fn version(_: &Self::DynamicType) -> Cow<'_, str> { + ::version(&()) + } + + fn plural(_: &Self::DynamicType) -> Cow<'_, str> { + ::plural(&()) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata } } @@ -122,6 +154,7 @@ pub fn validate( .context(InvalidLogDirSettingsSnafu)?; Ok(ValidatedSparkHistoryServer { + metadata: shs.meta().clone(), name, namespace, uid, From 10efca57c07eb1531feefbc4d0f5a3be1d5b94d1 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:05:41 +0200 Subject: [PATCH 16/50] connect validated: make it in line with the history structure --- .../operator-binary/src/connect/controller.rs | 48 ++++++++--- .../src/connect/controller/validate.rs | 80 ++++++++++++++++++- rust/operator-binary/src/connect/executor.rs | 18 ++--- rust/operator-binary/src/connect/server.rs | 26 +++--- 4 files changed, 136 insertions(+), 36 deletions(-) diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index a662cd60..f4c240d3 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -175,6 +175,13 @@ pub async fn reconcile( let validated = validate::validate(scs, dereferenced, &ctx.operator_environment) .context(ValidateSparkConnectServerSnafu)?; + tracing::debug!( + name = %validated.name, + namespace = %validated.namespace, + uid = %validated.uid, + "Validated SparkConnectServer identity" + ); + let server_config = &validated.server_config; let executor_config = &validated.executor_config; let resolved_product_image = &validated.resolved_product_image; @@ -186,7 +193,7 @@ pub async fn reconcile( CONNECT_APP_NAME, OPERATOR_NAME, CONNECT_CONTROLLER_NAME, - &scs.object_ref(&()), + &validated.object_ref(&()), ClusterResourceApplyStrategy::from(&scs.spec.cluster_operation), &scs.spec.object_overrides, ) @@ -251,19 +258,28 @@ pub async fn reconcile( ]) .context(SerializePropertiesSnafu)?; + let executor_config_overrides = scs + .spec + .executor + .as_ref() + .map(|s| s.config_overrides.clone()); + // ======================================== // Executor config map and pod template - let executor_config_map = - executor::executor_config_map(scs, executor_config, resolved_product_image).context( - BuildExecutorConfigMapSnafu { - name: scs.name_unchecked(), - }, - )?; + let executor_config_map = executor::executor_config_map( + &validated, + executor_config, + resolved_product_image, + executor_config_overrides.as_ref(), + ) + .context(BuildExecutorConfigMapSnafu { + name: validated.name_any(), + })?; cluster_resources .add(client, executor_config_map.clone()) .await .context(ApplyExecutorConfigMapSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), })?; let executor_pod_template = serde_yaml::to_string( @@ -278,23 +294,31 @@ pub async fn reconcile( ) .context(ExecutorPodTemplateSerdeSnafu)?; + let server_config_overrides = scs + .spec + .server + .config + .as_ref() + .map(|s| s.config_overrides.clone()); + // ======================================== // Server config map let server_config_map = server::server_config_map( - scs, + &validated, server_config, resolved_product_image, &spark_props, &executor_pod_template, + server_config_overrides.as_ref(), ) .context(BuildServerConfigMapSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), })?; cluster_resources .add(client, server_config_map.clone()) .await .context(ApplyServerConfigMapSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), })?; // ======================================== @@ -354,7 +378,7 @@ pub async fn reconcile( .apply_patch_status(OPERATOR_NAME, scs, &status) .await .context(ApplyStatusSnafu { - name: scs.name_any(), + name: validated.name_any(), })?; Ok(Action::await_change()) diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index e77a2e49..e7517ada 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -1,11 +1,20 @@ //! The validate step in the SparkConnectServer controller. //! -//! Resolves the product image and the server/executor configs. Does not touch K8s. +//! Resolves the product image and the server/executor configs. +//! Does not touch the Kubernetes API. -use snafu::{ResultExt, Snafu}; +use std::{borrow::Cow, str::FromStr}; + +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, + k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, + kube::{Resource, ResourceExt}, + v2::types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, }; use crate::{ @@ -29,17 +38,74 @@ pub enum Error { #[snafu(display("failed to resolve executor config"))] ExecutorConfig { source: crd::Error }, + + #[snafu(display("object is missing name"))] + MissingName, + + #[snafu(display("object is missing namespace"))] + MissingNamespace, + + #[snafu(display("object is missing UID"))] + MissingUid, + + #[snafu(display("failed to parse cluster name"))] + ParseName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + + #[snafu(display("failed to parse namespace"))] + ParseNamespace { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + + #[snafu(display("failed to parse UID"))] + ParseUid { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, } type Result = std::result::Result; pub struct ValidatedSparkConnectServer { + pub metadata: ObjectMeta, + pub name: ClusterName, + pub namespace: NamespaceName, + pub uid: Uid, pub resolved_s3: ResolvedS3, pub resolved_product_image: ResolvedProductImage, pub server_config: v1alpha1::ServerConfig, pub executor_config: v1alpha1::ExecutorConfig, } +impl Resource for ValidatedSparkConnectServer { + type DynamicType = (); + type Scope = ::Scope; + + fn kind(_: &Self::DynamicType) -> Cow<'_, str> { + ::kind(&()) + } + + fn group(_: &Self::DynamicType) -> Cow<'_, str> { + ::group(&()) + } + + fn version(_: &Self::DynamicType) -> Cow<'_, str> { + ::version(&()) + } + + fn plural(_: &Self::DynamicType) -> Cow<'_, str> { + ::plural(&()) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} + pub fn validate( scs: &v1alpha1::SparkConnectServer, dereferenced: DereferencedSparkConnectServer, @@ -57,8 +123,18 @@ pub fn validate( let server_config = scs.server_config().context(ServerConfigSnafu)?; let executor_config = scs.executor_config().context(ExecutorConfigSnafu)?; + let name = ClusterName::from_str(&scs.meta().name.clone().context(MissingNameSnafu)?) + .context(ParseNameSnafu)?; + let namespace = NamespaceName::from_str(&scs.namespace().context(MissingNamespaceSnafu)?) + .context(ParseNamespaceSnafu)?; + let uid = + Uid::from_str(&scs.meta().uid.clone().context(MissingUidSnafu)?).context(ParseUidSnafu)?; Ok(ValidatedSparkConnectServer { + metadata: scs.meta().clone(), + name, + namespace, + uid, resolved_s3: dereferenced.resolved_s3, resolved_product_image, server_config, diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index fb4414a8..fb6fd625 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -24,6 +24,7 @@ use stackable_operator::{ use super::{ common::{SparkConnectRole, object_name}, + controller::validate::ValidatedSparkConnectServer, crd::{DEFAULT_SPARK_CONNECT_GROUP_NAME, SparkConnectContainer}, }; use crate::{ @@ -338,13 +339,12 @@ fn executor_jvm_args( // - log4j2.properties : with logging configuration (if configured) // pub(crate) fn executor_config_map( - scs: &v1alpha1::SparkConnectServer, + validated: &ValidatedSparkConnectServer, config: &v1alpha1::ExecutorConfig, resolved_product_image: &ResolvedProductImage, + config_overrides: Option<&v1alpha1::ConfigOverrides>, ) -> Result { - let cm_name = object_name(&scs.name_any(), SparkConnectRole::Executor); - - let config_overrides = scs.spec.executor.as_ref().map(|s| &s.config_overrides); + let cm_name = object_name(&validated.name_any(), SparkConnectRole::Executor); let security_properties_overrides = config_overrides .and_then(|config_overrides| config_overrides.security_properties.as_ref()) @@ -361,7 +361,7 @@ pub(crate) fn executor_config_map( let metrics_props = common::metrics_properties(metrics_properties_overrides).context( MetricsPropertiesSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), }, )?; @@ -370,12 +370,12 @@ pub(crate) fn executor_config_map( cm_builder .metadata( ObjectMetaBuilder::new() - .name_and_namespace(scs) + .name_and_namespace(validated) .name(&cm_name) - .ownerreference_from_resource(scs, None, Some(true)) + .ownerreference_from_resource(validated, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&common::labels( - scs, + validated, &resolved_product_image.app_version_label_value, &SparkConnectRole::Executor.to_string(), )) @@ -386,7 +386,7 @@ pub(crate) fn executor_config_map( .add_data(METRICS_PROPERTIES_FILE, metrics_props); let role_group_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(scs), + cluster: ObjectRef::from_obj(validated), role: SparkConnectRole::Executor.to_string(), role_group: DEFAULT_SPARK_CONNECT_GROUP_NAME.to_string(), }; diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 10a81e79..d3793ed0 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -42,6 +42,7 @@ use crate::{ connect::{ GRPC, HTTP, common::{self, SparkConnectRole, object_name}, + controller::validate::ValidatedSparkConnectServer, crd::{ CONNECT_GRPC_PORT, CONNECT_UI_PORT, DEFAULT_SPARK_CONNECT_GROUP_NAME, SparkConnectContainer, v1alpha1, @@ -148,15 +149,14 @@ pub enum Error { // to the container environment. #[allow(clippy::result_large_err)] pub(crate) fn server_config_map( - scs: &v1alpha1::SparkConnectServer, + validated: &ValidatedSparkConnectServer, config: &v1alpha1::ServerConfig, resolved_product_image: &ResolvedProductImage, spark_properties: &str, executor_pod_template_spec: &str, + config_overrides: Option<&v1alpha1::ConfigOverrides>, ) -> Result { - let cm_name = object_name(&scs.name_any(), SparkConnectRole::Server); - - let config_overrides = scs.spec.server.config.as_ref().map(|s| &s.config_overrides); + let cm_name = object_name(&validated.name_any(), SparkConnectRole::Server); let security_properties_overrides = config_overrides .and_then(|config_overrides| config_overrides.security_properties.as_ref()) @@ -165,7 +165,7 @@ pub(crate) fn server_config_map( let jvm_sec_props = common::security_properties(security_properties_overrides).context( ServerJvmSecurityPropertiesSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), }, )?; @@ -176,7 +176,7 @@ pub(crate) fn server_config_map( let metrics_props = common::metrics_properties(metrics_properties_overrides).context( MetricsPropertiesSnafu { - name: scs.name_unchecked(), + name: validated.name_any(), }, )?; @@ -185,12 +185,12 @@ pub(crate) fn server_config_map( cm_builder .metadata( ObjectMetaBuilder::new() - .name_and_namespace(scs) + .name_and_namespace(validated) .name(&cm_name) - .ownerreference_from_resource(scs, None, Some(true)) + .ownerreference_from_resource(validated, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&common::labels( - scs, + validated, &resolved_product_image.app_version_label_value, &SparkConnectRole::Server.to_string(), )) @@ -202,7 +202,7 @@ pub(crate) fn server_config_map( .add_data(JVM_SECURITY_PROPERTIES_FILE, jvm_sec_props) .add_data(METRICS_PROPERTIES_FILE, metrics_props); - let role_group_ref = default_role_group_ref(scs); + let role_group_ref = default_role_group_ref(validated); product_logging::extend_config_map( &role_group_ref, &config.logging, @@ -595,10 +595,10 @@ fn probe() -> Probe { } fn default_role_group_ref( - scs: &v1alpha1::SparkConnectServer, -) -> RoleGroupRef { + validated: &ValidatedSparkConnectServer, +) -> RoleGroupRef { RoleGroupRef { - cluster: ObjectRef::from_obj(scs), + cluster: ObjectRef::from_obj(validated), role: SparkConnectRole::Server.to_string(), role_group: DEFAULT_SPARK_CONNECT_GROUP_NAME.to_string(), } From 8fa3170d8fd96d80ee78dbeeec948a2e645ce1f5 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 11 Jun 2026 15:00:19 +0200 Subject: [PATCH 17/50] fix: use v2 get name/namespace/uid helpers. --- .../src/connect/controller/validate.rs | 53 ++++++++----------- .../src/history/controller/validate.rs | 53 ++++++++----------- 2 files changed, 44 insertions(+), 62 deletions(-) diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index e7517ada..1c75882f 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -3,17 +3,20 @@ //! Resolves the product image and the server/executor configs. //! Does not touch the Kubernetes API. -use std::{borrow::Cow, str::FromStr}; +use std::borrow::Cow; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, - kube::{Resource, ResourceExt}, - v2::types::{ - kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + kube::Resource, + v2::{ + controller_utils::{get_cluster_name, get_namespace, get_uid}, + types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, }, }; @@ -39,35 +42,26 @@ pub enum Error { #[snafu(display("failed to resolve executor config"))] ExecutorConfig { source: crd::Error }, - #[snafu(display("object is missing name"))] - MissingName, - - #[snafu(display("object is missing namespace"))] - MissingNamespace, - - #[snafu(display("object is missing UID"))] - MissingUid, - - #[snafu(display("failed to parse cluster name"))] - ParseName { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve cluster name"))] + ResolveClusterName { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("failed to parse namespace"))] - ParseNamespace { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve namespace"))] + ResolveNamespace { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("failed to parse UID"))] - ParseUid { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve uid"))] + ResolveUid { + source: stackable_operator::v2::controller_utils::Error, }, } type Result = std::result::Result; pub struct ValidatedSparkConnectServer { - pub metadata: ObjectMeta, + metadata: ObjectMeta, pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, @@ -123,12 +117,9 @@ pub fn validate( let server_config = scs.server_config().context(ServerConfigSnafu)?; let executor_config = scs.executor_config().context(ExecutorConfigSnafu)?; - let name = ClusterName::from_str(&scs.meta().name.clone().context(MissingNameSnafu)?) - .context(ParseNameSnafu)?; - let namespace = NamespaceName::from_str(&scs.namespace().context(MissingNamespaceSnafu)?) - .context(ParseNamespaceSnafu)?; - let uid = - Uid::from_str(&scs.meta().uid.clone().context(MissingUidSnafu)?).context(ParseUidSnafu)?; + let name = get_cluster_name(scs).context(ResolveClusterNameSnafu)?; + let namespace = get_namespace(scs).context(ResolveNamespaceSnafu)?; + let uid = get_uid(scs).context(ResolveUidSnafu)?; Ok(ValidatedSparkConnectServer { metadata: scs.meta().clone(), diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 73a8e84e..748b9e96 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,17 +3,20 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use std::{borrow::Cow, collections::BTreeMap, str::FromStr}; +use std::{borrow::Cow, collections::BTreeMap}; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::{ObjectMeta, OwnerReference}, - kube::{Resource, ResourceExt}, - v2::types::{ - kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + kube::Resource, + v2::{ + controller_utils::{get_cluster_name, get_namespace, get_uid}, + types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, }, }; @@ -29,28 +32,19 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("object is missing name"))] - MissingName, - - #[snafu(display("object is missing namespace"))] - MissingNamespace, - - #[snafu(display("object is missing UID"))] - MissingUid, - - #[snafu(display("failed to parse cluster name"))] - ParseName { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve cluster name"))] + ResolveClusterName { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("failed to parse namespace"))] - ParseNamespace { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve namespace"))] + ResolveNamespace { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("failed to parse UID"))] - ParseUid { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to resolve uid"))] + ResolveUid { + source: stackable_operator::v2::controller_utils::Error, }, #[snafu(display("invalid cleaner configuration"))] @@ -63,7 +57,7 @@ pub enum Error { type Result = std::result::Result; pub struct ValidatedSparkHistoryServer { - pub metadata: ObjectMeta, + metadata: ObjectMeta, pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, @@ -137,12 +131,9 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let name = ClusterName::from_str(&shs.meta().name.clone().context(MissingNameSnafu)?) - .context(ParseNameSnafu)?; - let namespace = NamespaceName::from_str(&shs.namespace().context(MissingNamespaceSnafu)?) - .context(ParseNamespaceSnafu)?; - let uid = - Uid::from_str(&shs.meta().uid.clone().context(MissingUidSnafu)?).context(ParseUidSnafu)?; + let name = get_cluster_name(shs).context(ResolveClusterNameSnafu)?; + let namespace = get_namespace(shs).context(ResolveNamespaceSnafu)?; + let uid = get_uid(shs).context(ResolveUidSnafu)?; let cleaner_rolegroup_name = shs .cleaner_rolegroup_name() From 59411fa8baf351fa9fa9146fd7192d9aac8e9846 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 11 Jun 2026 15:13:30 +0200 Subject: [PATCH 18/50] fix: cleanup product-config left overs & remove allow dead_code --- rust/operator-binary/src/connect/common.rs | 3 - rust/operator-binary/src/crd/constants.rs | 2 - rust/operator-binary/src/crd/history.rs | 33 ----------- rust/operator-binary/src/crd/roles.rs | 67 +--------------------- 4 files changed, 1 insertion(+), 104 deletions(-) diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index eac282e9..d8ca69b3 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -55,9 +55,6 @@ pub(crate) fn labels<'a, T>( } } -// The dead code annotation is to shut up complains about missing Executor instantiations -// These will come in the future. -#[allow(dead_code)] #[derive(Clone, Debug, Display)] #[strum(serialize_all = "lowercase")] pub(crate) enum SparkConnectRole { diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index 3bd2db24..3a447a23 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -33,8 +33,6 @@ pub const VOLUME_MOUNT_PATH_LOG: &str = "/stackable/log"; pub const LOG4J2_CONFIG_FILE: &str = "log4j2.properties"; pub const JVM_SECURITY_PROPERTIES_FILE: &str = "security.properties"; -// These defaults used to come from deploy/config-spec/properties.yaml via product-config. -// They are now defined in code and can still be overridden through configOverrides.security.properties. pub const JVM_SECURITY_PROPERTY_DNS_CACHE_TTL: &str = "networkaddress.cache.ttl"; pub const JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL: &str = "networkaddress.cache.negative.ttl"; pub const DEFAULT_JVM_SECURITY_DNS_CACHE_TTL: &str = "30"; diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 33be5b99..76873cb4 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -19,7 +19,6 @@ use stackable_operator::{ deep_merger::ObjectOverrides, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, kube::{CustomResource, ResourceExt}, - product_config_utils::Configuration, product_logging::{self, spec::Logging}, role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef}, schemars::{self, JsonSchema}, @@ -388,38 +387,6 @@ impl HistoryConfig { } } -impl Configuration for HistoryConfigFragment { - type Configurable = v1alpha1::SparkHistoryServer; - - fn compute_env( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_cli( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - _resource: &Self::Configurable, - _role_name: &str, - _file: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } -} - impl Default for v1alpha1::SparkHistoryServerRoleConfig { fn default() -> Self { v1alpha1::SparkHistoryServerRoleConfig { diff --git a/rust/operator-binary/src/crd/roles.rs b/rust/operator-binary/src/crd/roles.rs index 52e95af8..0961537f 100644 --- a/rust/operator-binary/src/crd/roles.rs +++ b/rust/operator-binary/src/crd/roles.rs @@ -13,7 +13,7 @@ //! each role is named "default". These roles are transparent to the user. //! //! The history server has its own role completely unrelated to this module. -use std::{collections::BTreeMap, slice}; +use std::slice; use serde::{Deserialize, Serialize}; use stackable_operator::{ @@ -30,7 +30,6 @@ use stackable_operator::{ }, crd::s3, k8s_openapi::{api::core::v1::VolumeMount, apimachinery::pkg::api::resource::Quantity}, - product_config_utils::Configuration, product_logging::{self, spec::Logging}, schemars::{self, JsonSchema}, shared::time::Duration, @@ -165,38 +164,6 @@ impl RoleConfig { } } -impl Configuration for RoleConfigFragment { - type Configurable = v1alpha1::SparkApplication; - - fn compute_env( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_cli( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - _resource: &Self::Configurable, - _role_name: &str, - _file: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } -} - #[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)] #[fragment_attrs( derive( @@ -256,38 +223,6 @@ impl SubmitConfig { } } -impl Configuration for SubmitConfigFragment { - type Configurable = v1alpha1::SparkApplication; - - fn compute_env( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_cli( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - _resource: &Self::Configurable, - _role_name: &str, - _file: &str, - ) -> Result>, stackable_operator::product_config_utils::Error> - { - Ok(BTreeMap::new()) - } -} - // TODO: remove this when switch to pod overrides ??? #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] From ffd814e6eec21407aa9bb5eed0954fd9ae815c7f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 11 Jun 2026 17:04:02 +0200 Subject: [PATCH 19/50] bump op-rs --- Cargo.lock | 18 +-- Cargo.nix | 36 ++--- crate-hashes.json | 18 +-- extra/crds.yaml | 126 +++--------------- rust/operator-binary/src/connect/common.rs | 21 ++- rust/operator-binary/src/connect/s3.rs | 7 +- rust/operator-binary/src/crd/mod.rs | 4 +- .../src/crd/template_merger.rs | 22 +-- .../operator-binary/src/history/controller.rs | 14 +- .../src/spark_k8s_controller.rs | 6 +- 10 files changed, 103 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 697deb75..5f1c6153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "regex", @@ -2894,7 +2894,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "const-oid", "ecdsa", @@ -2918,7 +2918,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "base64", "clap", @@ -2962,7 +2962,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "proc-macro2", @@ -2973,7 +2973,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "jiff", "k8s-openapi", @@ -3014,7 +3014,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "axum", "clap", @@ -3038,7 +3038,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "kube", "schemars", @@ -3052,7 +3052,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "convert_case", "convert_case_extras", @@ -3070,7 +3070,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.nix b/Cargo.nix index 4e8848f4..6b6caf4e 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4831,8 +4831,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "k8s_version"; authors = [ @@ -9514,8 +9514,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_certs"; authors = [ @@ -9617,8 +9617,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_operator"; authors = [ @@ -9811,8 +9811,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9846,8 +9846,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_shared"; authors = [ @@ -10033,8 +10033,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_telemetry"; authors = [ @@ -10143,8 +10143,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_versioned"; authors = [ @@ -10193,8 +10193,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10261,8 +10261,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_webhook"; authors = [ diff --git a/crate-hashes.json b/crate-hashes.json index deac3bf4..c9a6e6a9 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/extra/crds.yaml b/extra/crds.yaml index 125e2fde..56063f37 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -691,25 +691,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -1490,25 +1480,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -1708,25 +1688,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -2831,36 +2801,21 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-defaults.conf: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -3253,36 +3208,21 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-defaults.conf: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -5327,25 +5267,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -6126,25 +6056,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -6344,25 +6264,15 @@ spec: properties: security.properties: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object spark-env.sh: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index d8ca69b3..53bac899 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -97,7 +97,12 @@ pub(crate) fn spark_properties( for p in props { result.extend(p); } - to_java_properties_string(result.into_iter()).context(SparkPropertiesSnafu) + to_java_properties_string( + result + .into_iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), + ) + .context(SparkPropertiesSnafu) } pub(crate) fn security_properties( @@ -117,7 +122,12 @@ pub(crate) fn security_properties( result.extend(config_overrides); - to_java_properties_string(result.iter()).context(JvmSecurityPropertiesSnafu) + to_java_properties_string( + result + .iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), + ) + .context(JvmSecurityPropertiesSnafu) } pub(crate) fn metrics_properties( @@ -137,5 +147,10 @@ pub(crate) fn metrics_properties( result.extend(config_overrides); - to_java_properties_string(result.iter()).context(MetricsPropertiesSnafu) + to_java_properties_string( + result + .iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), + ) + .context(MetricsPropertiesSnafu) } diff --git a/rust/operator-binary/src/connect/s3.rs b/rust/operator-binary/src/connect/s3.rs index 146c68db..624720dc 100644 --- a/rust/operator-binary/src/connect/s3.rs +++ b/rust/operator-binary/src/connect/s3.rs @@ -626,7 +626,12 @@ mod tests { let properties = resolved_s3.spark_properties().unwrap(); assert_eq!( - to_java_properties_string(properties.iter()).unwrap(), + to_java_properties_string( + properties + .iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))) + ) + .unwrap(), spark_properties_string, "Case failed for spark properties: {}", case_name diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 4c65b43f..e881fd24 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -1064,10 +1064,10 @@ fn resources_to_executor_props( /// providing escaped values. pub fn to_spark_env_sh_string<'a, T>(properties: T) -> String where - T: Iterator)>, + T: Iterator, { properties - .filter_map(|(k, v)| v.as_ref().map(|v| format!("export {k}=\"{v}\""))) + .map(|(k, v)| format!("export {k}=\"{v}\"")) .collect::>() .join("\n") } diff --git a/rust/operator-binary/src/crd/template_merger.rs b/rust/operator-binary/src/crd/template_merger.rs index c11d2cb9..1ee7f21c 100644 --- a/rust/operator-binary/src/crd/template_merger.rs +++ b/rust/operator-binary/src/crd/template_merger.rs @@ -491,15 +491,15 @@ mod tests { .unwrap(); assert_eq!( submit_security_props.get("test.base.only"), - Some(&Some("base".to_string())) + Some(&"base".to_string()) ); assert_eq!( submit_security_props.get("test.overridden"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); assert_eq!( submit_security_props.get("test.overlay.only"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); let driver_security_props = merged @@ -509,15 +509,15 @@ mod tests { .unwrap(); assert_eq!( driver_security_props.get("test.base.only"), - Some(&Some("base".to_string())) + Some(&"base".to_string()) ); assert_eq!( driver_security_props.get("test.overridden"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); assert_eq!( driver_security_props.get("test.overlay.only"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); let executor_security_props = merged @@ -533,15 +533,15 @@ mod tests { .unwrap(); assert_eq!( executor_security_props.get("test.base.only"), - Some(&Some("base".to_string())) + Some(&"base".to_string()) ); assert_eq!( executor_security_props.get("test.overridden"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); assert_eq!( executor_security_props.get("test.overlay.only"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); } @@ -593,7 +593,7 @@ mod tests { assert_eq!( submit_security_props.get("test.base.only"), - Some(&Some("base".to_string())) + Some(&"base".to_string()) ); } @@ -645,7 +645,7 @@ mod tests { assert_eq!( submit_security_props.get("test.overlay.only"), - Some(&Some("overlay".to_string())) + Some(&"overlay".to_string()) ); } diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 0e922ae5..f51e7b61 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -433,8 +433,12 @@ fn build_config_map( ) -> Result { let cm_name = rolegroupref.object_name(); - let spark_defaults = to_java_properties_string(spark_defaults(validated, rolegroupref).iter()) - .context(InvalidSparkDefaultsSnafu)?; + let spark_defaults = to_java_properties_string( + spark_defaults(validated, rolegroupref) + .iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), + ) + .context(InvalidSparkDefaultsSnafu)?; let mut jvm_sec_props = default_jvm_security_properties(); jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); @@ -719,15 +723,15 @@ fn command_args(logdir: &ResolvedLogDir) -> Vec { vec![command.join("\n")] } -fn default_jvm_security_properties() -> BTreeMap> { +fn default_jvm_security_properties() -> BTreeMap { [ ( JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), ), ( JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), ), ] .into() diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index a06b41f3..385ba4dd 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -779,15 +779,15 @@ fn submit_job_config_map( cm_builder.build().context(PodTemplateConfigMapSnafu) } -fn default_jvm_security_properties() -> BTreeMap> { +fn default_jvm_security_properties() -> BTreeMap { [ ( JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), ), ( JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), ), ] .into() From 77a6aa5797861efe20cc27732860c442264f31b5 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 11 Jun 2026 20:54:31 +0200 Subject: [PATCH 20/50] Option -> KeyValueConfigOverrides --- extra/crds.yaml | 12 ++++---- rust/operator-binary/src/connect/common.rs | 30 +++++++------------- rust/operator-binary/src/connect/crd.rs | 28 ++++++------------ rust/operator-binary/src/connect/executor.rs | 12 +++----- rust/operator-binary/src/connect/server.rs | 12 +++----- 5 files changed, 32 insertions(+), 62 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index 56063f37..9364fc9b 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -4006,32 +4006,32 @@ spec: metrics.properties: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object security.properties: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-defaults.conf: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -4425,32 +4425,32 @@ spec: metrics.properties: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object security.properties: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object spark-defaults.conf: additionalProperties: type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index 53bac899..468bb16f 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -106,51 +106,41 @@ pub(crate) fn spark_properties( } pub(crate) fn security_properties( - config_overrides: BTreeMap>, + config_overrides: BTreeMap, ) -> Result { - let mut result: BTreeMap> = [ + let mut result: BTreeMap = [ ( JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), ), ( JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), - Some(DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string()), + DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), ), ] .into(); result.extend(config_overrides); - to_java_properties_string( - result - .iter() - .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), - ) - .context(JvmSecurityPropertiesSnafu) + to_java_properties_string(result.iter()).context(JvmSecurityPropertiesSnafu) } pub(crate) fn metrics_properties( - config_overrides: BTreeMap>, + config_overrides: BTreeMap, ) -> Result { - let mut result: BTreeMap> = [ + let mut result: BTreeMap = [ ( "*.sink.prometheusServlet.class".to_string(), - Some("org.apache.spark.metrics.sink.PrometheusServlet".to_string()), + "org.apache.spark.metrics.sink.PrometheusServlet".to_string(), ), ( "*.sink.prometheusServlet.path".to_string(), - Some("/metrics/prometheus".to_string()), + "/metrics/prometheus".to_string(), ), ] .into(); result.extend(config_overrides); - to_java_properties_string( - result - .iter() - .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), - ) - .context(MetricsPropertiesSnafu) + to_java_properties_string(result.iter()).context(MetricsPropertiesSnafu) } diff --git a/rust/operator-binary/src/connect/crd.rs b/rust/operator-binary/src/connect/crd.rs index b919f7c1..77db139c 100644 --- a/rust/operator-binary/src/connect/crd.rs +++ b/rust/operator-binary/src/connect/crd.rs @@ -213,26 +213,14 @@ pub mod versioned { #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] pub struct ConfigOverrides { - #[serde( - default, - rename = "spark-defaults.conf", - skip_serializing_if = "Option::is_none" - )] - pub spark_defaults_conf: Option, - - #[serde( - default, - rename = "metrics.properties", - skip_serializing_if = "Option::is_none" - )] - pub metrics_properties: Option, - - #[serde( - default, - rename = "security.properties", - skip_serializing_if = "Option::is_none" - )] - pub security_properties: Option, + #[serde(default, rename = "spark-defaults.conf")] + pub spark_defaults_conf: KeyValueConfigOverrides, + + #[serde(default, rename = "metrics.properties")] + pub metrics_properties: KeyValueConfigOverrides, + + #[serde(default, rename = "security.properties")] + pub security_properties: KeyValueConfigOverrides, } } diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index fb6fd625..f5072977 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -12,7 +12,6 @@ use stackable_operator::{ product_image_selection::ResolvedProductImage, resources::{CpuLimits, MemoryLimits, Resources}, }, - config_overrides::KeyValueConfigOverrides, k8s_openapi::{ DeepMerge, api::core::v1::{ConfigMap, EnvVar, PodSecurityContext, PodTemplateSpec}, @@ -298,11 +297,10 @@ pub(crate) fn executor_properties( .spec .executor .as_ref() - .and_then(|s| s.config_overrides.spark_defaults_conf.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|s| s.config_overrides.spark_defaults_conf.overrides.clone()) .unwrap_or_default(); - result.extend(config_overrides); + result.extend(config_overrides.into_iter().map(|(k, v)| (k, Some(v)))); Ok(result) } @@ -347,16 +345,14 @@ pub(crate) fn executor_config_map( let cm_name = object_name(&validated.name_any(), SparkConnectRole::Executor); let security_properties_overrides = config_overrides - .and_then(|config_overrides| config_overrides.security_properties.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|config_overrides| config_overrides.security_properties.overrides.clone()) .unwrap_or_default(); let jvm_sec_props = common::security_properties(security_properties_overrides) .context(ExecutorJvmSecurityPropertiesSnafu)?; let metrics_properties_overrides = config_overrides - .and_then(|config_overrides| config_overrides.metrics_properties.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|config_overrides| config_overrides.metrics_properties.overrides.clone()) .unwrap_or_default(); let metrics_props = common::metrics_properties(metrics_properties_overrides).context( diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index d3793ed0..dc5ac61e 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -18,7 +18,6 @@ use stackable_operator::{ }, }, commons::product_image_selection::ResolvedProductImage, - config_overrides::KeyValueConfigOverrides, crd::listener, k8s_openapi::{ DeepMerge, @@ -159,8 +158,7 @@ pub(crate) fn server_config_map( let cm_name = object_name(&validated.name_any(), SparkConnectRole::Server); let security_properties_overrides = config_overrides - .and_then(|config_overrides| config_overrides.security_properties.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|config_overrides| config_overrides.security_properties.overrides.clone()) .unwrap_or_default(); let jvm_sec_props = common::security_properties(security_properties_overrides).context( @@ -170,8 +168,7 @@ pub(crate) fn server_config_map( )?; let metrics_properties_overrides = config_overrides - .and_then(|config_overrides| config_overrides.metrics_properties.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|config_overrides| config_overrides.metrics_properties.overrides.clone()) .unwrap_or_default(); let metrics_props = common::metrics_properties(metrics_properties_overrides).context( @@ -501,8 +498,7 @@ pub(crate) fn server_properties( .server .config .as_ref() - .and_then(|s| s.config_overrides.spark_defaults_conf.as_ref()) - .map(KeyValueConfigOverrides::as_product_config_overrides) + .map(|s| s.config_overrides.spark_defaults_conf.overrides.clone()) .unwrap_or_default(); let mut result: BTreeMap> = [ @@ -549,7 +545,7 @@ pub(crate) fn server_properties( ] .into(); - result.extend(config_overrides); + result.extend(config_overrides.into_iter().map(|(k, v)| (k, Some(v)))); Ok(result) } From ad14fb13d15c10178eb5af04b10cdd4d102158e8 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 11 Jun 2026 21:21:44 +0200 Subject: [PATCH 21/50] thread metadata through via validated object --- .../src/spark_k8s_controller.rs | 42 +++++++------ .../src/spark_k8s_controller/validate.rs | 62 +++++++++++++++++++ 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 385ba4dd..02fac4a2 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -239,7 +239,7 @@ pub async fn reconcile( .unwrap_or_default(); let driver_pod_template_config_map = pod_template_config_map( - spark_application, + &validated, SparkApplicationRole::Driver, &driver_config, &driver_config_overrides, @@ -270,7 +270,7 @@ pub async fn reconcile( .unwrap_or_default(); let executor_pod_template_config_map = pod_template_config_map( - spark_application, + &validated, SparkApplicationRole::Executor, &executor_config, &executor_config_overrides, @@ -304,11 +304,8 @@ pub async fn reconcile( .map(|job| job.config_overrides.clone()) .unwrap_or_default(); - let submit_job_config_map = submit_job_config_map( - spark_application, - &submit_config_overrides, - resolved_product_image, - )?; + let submit_job_config_map = + submit_job_config_map(&validated, &submit_config_overrides, resolved_product_image)?; client .apply_patch( SPARK_CONTROLLER_NAME, @@ -319,7 +316,7 @@ pub async fn reconcile( .context(ApplyApplicationSnafu)?; let job = spark_job( - spark_application, + &validated, resolved_product_image, &serviceaccount, &env_vars, @@ -509,7 +506,7 @@ fn init_containers( #[allow(clippy::too_many_arguments)] fn pod_template( - spark_application: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, role: SparkApplicationRole, config: &RoleConfig, volumes: &[Volume], @@ -519,6 +516,7 @@ fn pod_template( spark_image: &ResolvedProductImage, service_account: &ServiceAccount, ) -> Result { + let spark_application = &validated.spark_application; let container_name = SparkContainer::Spark.to_string(); let mut cb = ContainerBuilder::new(&container_name).context(IllegalContainerNameSnafu)?; let merged_env = spark_application.merged_env(role.clone(), env); @@ -545,7 +543,7 @@ fn pod_template( omb.name(&container_name) // this reference is not pointing to a controller but only provides a UID that can used to clean up resources // cleanly (specifically driver pods and related config maps) when the spark application is deleted. - .ownerreference_from_resource(spark_application, None, None) + .ownerreference_from_resource(validated, None, None) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels( &spark_application @@ -632,7 +630,7 @@ fn pod_template( #[allow(clippy::too_many_arguments)] fn pod_template_config_map( - spark_application: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, role: SparkApplicationRole, merged_config: &RoleConfig, config_overrides: &v1alpha1::ConfigOverrides, @@ -642,6 +640,7 @@ fn pod_template_config_map( spark_image: &ResolvedProductImage, service_account: &ServiceAccount, ) -> Result { + let spark_application = &validated.spark_application; let cm_name = spark_application.pod_template_config_map_name(role.clone()); let log_config_map = if let Some(ContainerLogConfig { @@ -674,7 +673,7 @@ fn pod_template_config_map( ); let template = pod_template( - spark_application, + validated, role.clone(), merged_config, volumes.as_ref(), @@ -690,9 +689,9 @@ fn pod_template_config_map( cm_builder .metadata( ObjectMetaBuilder::new() - .name_and_namespace(spark_application) + .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference_from_resource(spark_application, None, Some(true)) + .ownerreference_from_resource(validated, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&spark_application.build_recommended_labels( &spark_image.app_version_label_value, @@ -737,19 +736,20 @@ fn pod_template_config_map( } fn submit_job_config_map( - spark_application: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, config_overrides: &v1alpha1::ConfigOverrides, spark_image: &ResolvedProductImage, ) -> Result { + let spark_application = &validated.spark_application; let cm_name = spark_application.submit_job_config_map_name(); let mut cm_builder = ConfigMapBuilder::new(); cm_builder.metadata( ObjectMetaBuilder::new() - .name_and_namespace(spark_application) + .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference_from_resource(spark_application, None, Some(true)) + .ownerreference_from_resource(validated, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels( &spark_application @@ -795,7 +795,7 @@ fn default_jvm_security_properties() -> BTreeMap { #[allow(clippy::too_many_arguments)] fn spark_job( - spark_application: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, spark_image: &ResolvedProductImage, serviceaccount: &ServiceAccount, env: &[EnvVar], @@ -804,6 +804,7 @@ fn spark_job( logdir: &Option, job_config: &SubmitConfig, ) -> Result { + let spark_application = &validated.spark_application; let mut cb = ContainerBuilder::new(&SparkContainer::SparkSubmit.to_string()) .context(IllegalContainerNameSnafu)?; @@ -902,8 +903,9 @@ fn spark_job( let job = Job { metadata: ObjectMetaBuilder::new() - .name_and_namespace(spark_application) - .ownerreference_from_resource(spark_application, None, Some(true)) + .name(validated.name.to_string()) + .namespace(validated.namespace.clone()) + .ownerreference_from_resource(validated, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels( &spark_application diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index f5151ac4..41579be4 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -3,6 +3,8 @@ //! Synchronously validates the [`super::dereference::DereferencedSparkApplication`] and //! resolves the product image. Does not touch the Kubernetes API. +use std::borrow::Cow; + use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, @@ -11,6 +13,12 @@ use stackable_operator::{ tls_verification::TlsVerification, }, crd::s3, + k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, + kube::Resource, + v2::{ + controller_utils::{get_cluster_name, get_namespace}, + types::{kubernetes::NamespaceName, operator::ClusterName}, + }, }; use crate::{ @@ -25,6 +33,16 @@ pub enum Error { source: product_image_selection::Error, }, + #[snafu(display("failed to resolve cluster name"))] + ResolveClusterName { + source: stackable_operator::v2::controller_utils::Error, + }, + + #[snafu(display("failed to resolve namespace"))] + ResolveNamespace { + source: stackable_operator::v2::controller_utils::Error, + }, + #[snafu(display("S3 TLS with verification disabled is not supported ({context})"))] S3TlsNoVerificationNotSupported { context: String }, } @@ -33,6 +51,12 @@ type Result = std::result::Result; /// Inputs the rest of `reconcile` needs after dereferencing. pub struct ValidatedSparkApplication { + /// Metadata mirroring the source [`v1alpha1::SparkApplication`] (name, namespace and UID), so + /// this struct can be used as the owner of generated objects via its [`Resource`] impl. + metadata: ObjectMeta, + pub name: ClusterName, + pub namespace: NamespaceName, + // Still carried in full because `reconcile` builds the submit/driver pod from the whole spec. pub spark_application: v1alpha1::SparkApplication, pub resolved_template_refs: Vec, pub s3_connection: Option, @@ -40,6 +64,35 @@ pub struct ValidatedSparkApplication { pub resolved_product_image: ResolvedProductImage, } +impl Resource for ValidatedSparkApplication { + type DynamicType = (); + type Scope = ::Scope; + + fn kind(_: &Self::DynamicType) -> Cow<'_, str> { + ::kind(&()) + } + + fn group(_: &Self::DynamicType) -> Cow<'_, str> { + ::group(&()) + } + + fn version(_: &Self::DynamicType) -> Cow<'_, str> { + ::version(&()) + } + + fn plural(_: &Self::DynamicType) -> Cow<'_, str> { + ::plural(&()) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} + pub fn validate( dereferenced: DereferencedSparkApplication, operator_environment: &OperatorEnvironmentOptions, @@ -62,7 +115,16 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; + let name = + get_cluster_name(&dereferenced.spark_application).context(ResolveClusterNameSnafu)?; + let namespace = + get_namespace(&dereferenced.spark_application).context(ResolveNamespaceSnafu)?; + let metadata = dereferenced.spark_application.meta().clone(); + Ok(ValidatedSparkApplication { + metadata, + name, + namespace, spark_application: dereferenced.spark_application, resolved_template_refs: dereferenced.resolved_template_refs, s3_connection: dereferenced.s3_connection, From 6c7134659124839143b799a5a3e43093be8c329b Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 15 Jun 2026 17:04:34 +0200 Subject: [PATCH 22/50] history: add HasName/HasUid and use v2 ownerreference_from_resource in build_config_map --- .../operator-binary/src/history/controller.rs | 11 +++++++-- .../src/history/controller/validate.rs | 24 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index f51e7b61..6456161b 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -44,7 +44,10 @@ use stackable_operator::{ }, role_utils::RoleGroupRef, shared::time::Duration, - v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, + v2::{ + builder::meta::ownerreference_from_resource, + config_file_writer::{PropertiesWriterError, to_java_properties_string}, + }, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -450,7 +453,11 @@ fn build_config_map( ObjectMetaBuilder::new() .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference(validated.owner_reference()) + .ownerreference(ownerreference_from_resource( + validated, + Some(true), + Some(true), + )) .labels(recommended_labels.clone()) .build(), ) diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 748b9e96..b66d030c 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -9,9 +9,10 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - k8s_openapi::apimachinery::pkg::apis::meta::v1::{ObjectMeta, OwnerReference}, + k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, v2::{ + HasName, HasUid, controller_utils::{get_cluster_name, get_namespace, get_uid}, types::{ kubernetes::{NamespaceName, Uid}, @@ -72,18 +73,15 @@ pub struct ValidatedSparkHistoryServer { pub log_dir_settings: BTreeMap, } -impl ValidatedSparkHistoryServer { - pub fn owner_reference(&self) -> OwnerReference { - let mut owner_reference = self.controller_owner_ref(&()).unwrap_or(OwnerReference { - api_version: v1alpha1::SparkHistoryServer::api_version(&()).to_string(), - block_owner_deletion: Some(true), - controller: Some(true), - kind: v1alpha1::SparkHistoryServer::kind(&()).to_string(), - name: String::from(&self.name), - uid: String::from(&self.uid), - }); - owner_reference.block_owner_deletion = Some(true); - owner_reference +impl HasName for ValidatedSparkHistoryServer { + fn to_name(&self) -> String { + String::from(&self.name) + } +} + +impl HasUid for ValidatedSparkHistoryServer { + fn to_uid(&self) -> Uid { + self.uid.clone() } } From 1a16afde075c643c9fddcd213db16f9d0454c373 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 15 Jun 2026 17:31:07 +0200 Subject: [PATCH 23/50] history: v2 owner-ref migration for metrics service and statefulset --- rust/operator-binary/src/history/controller.rs | 15 ++++++--------- rust/operator-binary/src/history/service.rs | 11 ++++------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 6456161b..047ea8e4 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -114,11 +114,6 @@ pub enum Error { source: stackable_operator::builder::pod::container::Error, }, - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to update the history server stateful set"))] ApplyStatefulSet { source: stackable_operator::cluster_resources::Error, @@ -327,11 +322,13 @@ pub async fn reconcile( .context(LabelBuildSnafu)?, )?; - let metrics_service = build_rolegroup_metrics_service(shs, resolved_product_image, &rgr) - .context(BuildMetricsServiceSnafu)?; + let metrics_service = + build_rolegroup_metrics_service(shs, &validated, resolved_product_image, &rgr) + .context(BuildMetricsServiceSnafu)?; let sts = build_stateful_set( shs, + &validated, resolved_product_image, &rgr, log_dir, @@ -492,6 +489,7 @@ fn build_config_map( #[allow(clippy::result_large_err)] fn build_stateful_set( shs: &v1alpha1::SparkHistoryServer, + validated: &validate::ValidatedSparkHistoryServer, resolved_product_image: &ResolvedProductImage, rolegroupref: &RoleGroupRef, log_dir: &ResolvedLogDir, @@ -658,8 +656,7 @@ fn build_stateful_set( let sts_metadata = ObjectMetaBuilder::new() .name_and_namespace(shs) .name(rolegroupref.object_name()) - .ownerreference_from_resource(shs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&recommended_object_labels) .context(MetadataBuildSnafu)? .build(); diff --git a/rust/operator-binary/src/history/service.rs b/rust/operator-binary/src/history/service.rs index a9a3b640..3cab7acf 100644 --- a/rust/operator-binary/src/history/service.rs +++ b/rust/operator-binary/src/history/service.rs @@ -5,6 +5,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels}, role_utils::RoleGroupRef, + v2::builder::meta::ownerreference_from_resource, }; use crate::{ @@ -12,7 +13,7 @@ use crate::{ constants::{HISTORY_APP_NAME, METRICS_PORT}, history::v1alpha1, }, - history::recommended_labels, + history::{controller::validate::ValidatedSparkHistoryServer, recommended_labels}, }; #[derive(Snafu, Debug)] @@ -26,15 +27,12 @@ pub enum Error { MetadataBuild { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, } /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label pub fn build_rolegroup_metrics_service( shs: &v1alpha1::SparkHistoryServer, + validated: &ValidatedSparkHistoryServer, resolved_product_image: &ResolvedProductImage, rolegroup_ref: &RoleGroupRef, ) -> Result { @@ -42,8 +40,7 @@ pub fn build_rolegroup_metrics_service( metadata: ObjectMetaBuilder::new() .name_and_namespace(shs) .name(rolegroup_ref.rolegroup_metrics_service_name()) - .ownerreference_from_resource(shs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&recommended_labels( shs, &resolved_product_image.app_version_label_value, From c16787c73d6bec319afdc35c474ad6df80917bcf Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 15 Jun 2026 18:35:08 +0200 Subject: [PATCH 24/50] history: hold validated role groups; v2 ResourceNames naming + labels --- Cargo.lock | 19 +- Cargo.nix | 41 ++-- crate-hashes.json | 18 +- .../operator-binary/src/history/controller.rs | 181 ++++++----------- .../src/history/controller/validate.rs | 184 +++++++++++++++++- rust/operator-binary/src/history/service.rs | 61 ++---- 6 files changed, 294 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f1c6153..cf410e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "darling", "regex", @@ -2894,7 +2894,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "const-oid", "ecdsa", @@ -2918,7 +2918,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "base64", "clap", @@ -2943,6 +2943,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "sha2", "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", @@ -2962,7 +2963,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "darling", "proc-macro2", @@ -2973,7 +2974,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "jiff", "k8s-openapi", @@ -3014,7 +3015,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "axum", "clap", @@ -3038,7 +3039,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "kube", "schemars", @@ -3052,7 +3053,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "convert_case", "convert_case_extras", @@ -3070,7 +3071,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.nix b/Cargo.nix index 6b6caf4e..beda83bc 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4831,8 +4831,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "k8s_version"; authors = [ @@ -9514,8 +9514,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_certs"; authors = [ @@ -9617,8 +9617,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_operator"; authors = [ @@ -9727,6 +9727,11 @@ rec { name = "serde_yaml"; packageId = "serde_yaml"; } + { + name = "sha2"; + packageId = "sha2"; + features = [ "oid" ]; + } { name = "snafu"; packageId = "snafu 0.9.1"; @@ -9811,8 +9816,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9846,8 +9851,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_shared"; authors = [ @@ -10033,8 +10038,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_telemetry"; authors = [ @@ -10143,8 +10148,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_versioned"; authors = [ @@ -10193,8 +10198,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10261,8 +10266,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_webhook"; authors = [ diff --git a/crate-hashes.json b/crate-hashes.json index c9a6e6a9..bb8b51c7 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 047ea8e4..19ed521b 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -18,7 +18,6 @@ use stackable_operator::{ }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, - config::merge::Merge, crd::listener, k8s_openapi::{ DeepMerge, @@ -33,7 +32,6 @@ use stackable_operator::{ core::{DeserializeGuard, error_boundary}, runtime::{controller::Action, reflector::ObjectRef}, }, - kvp::Labels, logging::controller::ReconcilerError, product_logging::{ framework::{LoggingError, calculate_log_volume_size_limit, vector_container}, @@ -47,6 +45,7 @@ use stackable_operator::{ v2::{ builder::meta::ownerreference_from_resource, config_file_writer::{PropertiesWriterError, to_java_properties_string}, + types::operator::RoleGroupName, }, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -65,15 +64,14 @@ use crate::{ VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, }, - history::{self, HistoryConfig, SparkHistoryServerContainer, v1alpha1}, + history::{SparkHistoryServerContainer, v1alpha1}, listener_ext, logdir::ResolvedLogDir, tlscerts, to_spark_env_sh_string, }, history::{ - operations::pdb::add_pdbs, - recommended_labels, - service::{self, build_rolegroup_metrics_service}, + controller::validate::ValidatedHistoryRoleGroup, operations::pdb::add_pdbs, + recommended_labels, service::build_rolegroup_metrics_service, }, product_logging::{self}, }; @@ -145,9 +143,6 @@ pub enum Error { #[snafu(display("failed to validate SparkHistoryServer"))] ValidateSparkHistoryServer { source: validate::Error }, - #[snafu(display("failed to resolve and merge config for role and role group"))] - FailedToResolveConfig { source: crate::crd::history::Error }, - #[snafu(display("failed to create cluster resources"))] CreateClusterResources { source: stackable_operator::cluster_resources::Error, @@ -170,9 +165,6 @@ pub enum Error { #[snafu(display("failed to configure logging"))] ConfigureLogging { source: LoggingError }, - #[snafu(display("cannot retrieve role group"))] - CannotRetrieveRoleGroup { source: history::Error }, - #[snafu(display( "History server : failed to serialize [{JVM_SECURITY_PROPERTIES_FILE}] for group {}", rolegroup @@ -187,16 +179,6 @@ pub enum Error { source: crate::history::operations::pdb::Error, }, - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to get required Labels"))] GetRequiredLabels { source: @@ -227,9 +209,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to build metrics service"))] - BuildMetricsService { source: service::Error }, - #[snafu(display("failed to serialize Spark default properties"))] InvalidSparkDefaults { source: PropertiesWriterError }, } @@ -292,47 +271,17 @@ pub async fn reconcile( .await .context(ApplyRoleBindingSnafu)?; - for rolegroup_name in shs.spec.nodes.role_groups.keys() { - let rgr = RoleGroupRef { - cluster: ObjectRef::from_obj(shs), - role: HISTORY_ROLE_NAME.to_string(), - role_group: rolegroup_name.to_string(), - }; - - let merged_config = shs - .merged_config(&rgr) - .context(FailedToResolveConfigSnafu)?; - - let role_group = shs.rolegroup(&rgr).context(CannotRetrieveRoleGroupSnafu)?; + for (role_group_name, rg) in &validated.role_groups { + let config_map = build_config_map(&validated, role_group_name, rg)?; - // Merge config_overrides from both nodes and role group levels - let mut merged_config_overrides = role_group.config.config_overrides; - merged_config_overrides.merge(&shs.spec.nodes.config.config_overrides); - - let config_map = build_config_map( - &validated, - &merged_config_overrides, - &merged_config, - &rgr, - &Labels::recommended(&recommended_labels( - shs, - &resolved_product_image.app_version_label_value, - &rgr.role_group, - )) - .context(LabelBuildSnafu)?, - )?; - - let metrics_service = - build_rolegroup_metrics_service(shs, &validated, resolved_product_image, &rgr) - .context(BuildMetricsServiceSnafu)?; + let metrics_service = build_rolegroup_metrics_service(&validated, role_group_name); let sts = build_stateful_set( shs, &validated, - resolved_product_image, - &rgr, + role_group_name, + rg, log_dir, - &merged_config, &service_account, )?; @@ -426,22 +375,23 @@ pub fn error_policy( #[allow(clippy::result_large_err)] fn build_config_map( validated: &validate::ValidatedSparkHistoryServer, - config_overrides: &v1alpha1::ConfigOverrides, - merged_config: &HistoryConfig, - rolegroupref: &RoleGroupRef, - recommended_labels: &Labels, + role_group_name: &RoleGroupName, + rg: &ValidatedHistoryRoleGroup, ) -> Result { - let cm_name = rolegroupref.object_name(); + let cm_name = validated + .resource_names(role_group_name) + .role_group_config_map() + .to_string(); let spark_defaults = to_java_properties_string( - spark_defaults(validated, rolegroupref) + spark_defaults(validated, role_group_name) .iter() .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), ) .context(InvalidSparkDefaultsSnafu)?; let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); + jvm_sec_props.extend(rg.config_overrides.security_properties.overrides.clone()); let mut cm_builder = ConfigMapBuilder::new(); @@ -455,26 +405,35 @@ fn build_config_map( Some(true), Some(true), )) - .labels(recommended_labels.clone()) + .labels(validated.recommended_labels(role_group_name)) .build(), ) .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) .add_data( SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), + to_spark_env_sh_string(rg.config_overrides.spark_env_sh.overrides.iter()), ) .add_data( JVM_SECURITY_PROPERTIES_FILE, to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { JvmSecurityPropertiesSnafu { - rolegroup: rolegroupref.role_group.clone(), + rolegroup: role_group_name.to_string(), } })?, ); + // `product_logging::extend_config_map` still expects a `RoleGroupRef`, so build a local temp + // one purely for that call until the logging path is migrated. + let rgr = RoleGroupRef { + cluster: ObjectRef::::new(validated.name.as_ref()) + .within(validated.namespace.as_ref()), + role: HISTORY_ROLE_NAME.to_string(), + role_group: role_group_name.to_string(), + }; + product_logging::extend_config_map( - rolegroupref, - &merged_config.logging, + &rgr, + &rg.config.logging, SparkHistoryServerContainer::SparkHistory, SparkHistoryServerContainer::Vector, &mut cm_builder, @@ -490,43 +449,40 @@ fn build_config_map( fn build_stateful_set( shs: &v1alpha1::SparkHistoryServer, validated: &validate::ValidatedSparkHistoryServer, - resolved_product_image: &ResolvedProductImage, - rolegroupref: &RoleGroupRef, + role_group_name: &RoleGroupName, + rg: &ValidatedHistoryRoleGroup, log_dir: &ResolvedLogDir, - merged_config: &HistoryConfig, serviceaccount: &ServiceAccount, ) -> Result { + let resolved_product_image = &validated.resolved_product_image; + let resource_names = validated.resource_names(role_group_name); + let log_config_map = if let Some(ContainerLogConfig { choice: Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { custom: ConfigMapLogConfig { config_map }, })), - }) = merged_config + }) = rg + .config .logging .containers .get(&SparkHistoryServerContainer::SparkHistory) { config_map.into() } else { - rolegroupref.object_name() + resource_names.role_group_config_map().to_string() }; - let recommended_object_labels = recommended_labels( - shs, - &resolved_product_image.app_version_label_value, - rolegroupref.role_group.as_ref(), - ); - let recommended_labels = - Labels::recommended(&recommended_object_labels).context(LabelBuildSnafu)?; + let recommended_labels = validated.recommended_labels(role_group_name); let pb_metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? + .with_labels(recommended_labels.clone()) .build(); let mut pb = PodBuilder::new(); - let requested_secret_lifetime = merged_config + let requested_secret_lifetime = rg + .config .requested_secret_lifetime .context(MissingSecretLifetimeSnafu)?; pb.service_account_name(serviceaccount.name_unchecked()) @@ -534,7 +490,7 @@ fn build_stateful_set( .image_pull_secrets_from_product_image(resolved_product_image) .add_volume( VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) - .with_config_map(rolegroupref.object_name()) + .with_config_map(resource_names.role_group_config_map().to_string()) .build(), ) .context(AddVolumeSnafu)? @@ -564,23 +520,15 @@ fn build_stateful_set( ..PodSecurityContext::default() }); - let role_group = shs - .rolegroup(rolegroupref) - .with_context(|_| CannotRetrieveRoleGroupSnafu)?; - let merged_env = shs - .merged_env( - &rolegroupref.role_group, - log_dir, - role_group.config.env_overrides, - ) + .merged_env(role_group_name.as_ref(), log_dir, rg.env_overrides.clone()) .context(MergeEnvSnafu)?; let container_name = "spark-history"; let container = ContainerBuilder::new(container_name) .context(InvalidContainerNameSnafu)? .image_from_product_image(resolved_product_image) - .resources(merged_config.resources.clone().into()) + .resources(rg.config.resources.clone().into()) .command(vec![ "/bin/bash".to_string(), "-x".to_string(), @@ -611,7 +559,7 @@ fn build_stateful_set( // cluster-internal) as the address should still be consistent. let volume_claim_templates = Some(vec![ ListenerOperatorVolumeSourceBuilder::new( - &ListenerReference::ListenerName(group_listener_name(shs, &rolegroupref.role)), + &ListenerReference::ListenerName(group_listener_name(shs, HISTORY_ROLE_NAME)), &recommended_labels, ) .build_pvc(LISTENER_VOLUME_NAME.to_string()) @@ -620,7 +568,7 @@ fn build_stateful_set( pb.add_container(container); - if merged_config.logging.enable_vector_agent { + if rg.config.logging.enable_vector_agent { match &shs.spec.vector_aggregator_config_map_name { Some(vector_aggregator_config_map_name) => { pb.add_container( @@ -628,7 +576,7 @@ fn build_stateful_set( resolved_product_image, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, - merged_config + rg.config .logging .containers .get(&SparkHistoryServerContainer::Vector), @@ -650,15 +598,13 @@ fn build_stateful_set( } let mut pod_template = pb.build_template(); - pod_template.merge_from(shs.role().config.pod_overrides.clone()); - pod_template.merge_from(role_group.config.pod_overrides); + pod_template.merge_from(rg.pod_overrides.clone()); let sts_metadata = ObjectMetaBuilder::new() - .name_and_namespace(shs) - .name(rolegroupref.object_name()) + .name_and_namespace(validated) + .name(resource_names.stateful_set_name().to_string()) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? + .with_labels(recommended_labels) .build(); Ok(StatefulSet { @@ -666,18 +612,9 @@ fn build_stateful_set( spec: Some(StatefulSetSpec { template: pod_template, volume_claim_templates, - replicas: shs.replicas(rolegroupref), + replicas: rg.replicas, selector: LabelSelector { - match_labels: Some( - Labels::role_group_selector( - shs, - HISTORY_APP_NAME, - &rolegroupref.role, - &rolegroupref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), + match_labels: Some(validated.role_group_selector(role_group_name).into()), ..LabelSelector::default() }, ..StatefulSetSpec::default() @@ -688,12 +625,12 @@ fn build_stateful_set( fn spark_defaults( validated: &validate::ValidatedSparkHistoryServer, - rolegroupref: &RoleGroupRef, + role_group_name: &RoleGroupName, ) -> BTreeMap> { let mut default_properties = validated.log_dir_settings.clone(); // add cleaner spark settings if requested - default_properties.extend(cleaner_config(validated, rolegroupref)); + default_properties.extend(cleaner_config(validated, role_group_name)); // add user provided configuration. These can overwrite everything. default_properties.extend(validated.spark_conf.clone()); @@ -744,10 +681,10 @@ fn default_jvm_security_properties() -> BTreeMap { /// Return the Spark properties for the cleaner role group (if any). fn cleaner_config( validated: &validate::ValidatedSparkHistoryServer, - rolegroup_ref: &RoleGroupRef, + role_group_name: &RoleGroupName, ) -> BTreeMap { match validated.cleaner_rolegroup_name.as_ref() { - Some(cleaner_rolegroup) if cleaner_rolegroup == &rolegroup_ref.role_group => { + Some(cleaner_rolegroup) if cleaner_rolegroup == role_group_name.as_ref() => { BTreeMap::from([( "spark.history.fs.cleaner.enabled".to_string(), "true".to_string(), diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index b66d030c..3e92f960 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,26 +3,47 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use std::{borrow::Cow, collections::BTreeMap}; +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, + str::FromStr, +}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, - kube::Resource, + config::merge::Merge, + k8s_openapi::{ + DeepMerge, api::core::v1::PodTemplateSpec, apimachinery::pkg::apis::meta::v1::ObjectMeta, + }, + kube::{Resource, runtime::reflector::ObjectRef}, + kvp::Labels, + role_utils::RoleGroupRef, v2::{ - HasName, HasUid, + HasName, HasUid, NameIsValidLabelValue, controller_utils::{get_cluster_name, get_namespace, get_uid}, + kvp::label::{recommended_labels, role_group_selector}, + role_group_utils::ResourceNames, types::{ kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + operator::{ + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, + RoleGroupName, RoleName, + }, }, }, }; use crate::{ - crd::{constants::CONTAINER_IMAGE_BASE_NAME, history::v1alpha1, logdir::ResolvedLogDir}, + crd::{ + constants::{ + CONTAINER_IMAGE_BASE_NAME, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, + HISTORY_ROLE_NAME, OPERATOR_NAME, + }, + history::{HistoryConfig, v1alpha1}, + logdir::ResolvedLogDir, + }, history::controller::dereference::DereferencedSparkHistoryServer, }; @@ -53,15 +74,47 @@ pub enum Error { #[snafu(display("invalid log directory settings"))] InvalidLogDirSettings { source: crate::crd::logdir::Error }, + + #[snafu(display("invalid role group name {role_group}"))] + ParseRoleGroupName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + role_group: String, + }, + + #[snafu(display("failed to resolve and merge config for role group {role_group}"))] + FailedToResolveConfig { + source: crate::crd::history::Error, + role_group: String, + }, + + #[snafu(display("cannot retrieve role group {role_group}"))] + CannotRetrieveRoleGroup { + source: crate::crd::history::Error, + role_group: String, + }, } type Result = std::result::Result; +/// A pre-validated history server role group: the per-role-group merge products that the build +/// steps used to recompute from the raw CRD on every reconcile. +pub struct ValidatedHistoryRoleGroup { + pub config: HistoryConfig, + pub config_overrides: v1alpha1::ConfigOverrides, + pub env_overrides: HashMap, + pub pod_overrides: PodTemplateSpec, + pub replicas: Option, +} + pub struct ValidatedSparkHistoryServer { metadata: ObjectMeta, pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, + /// 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 cleaner_rolegroup_name: Option, pub spark_conf: BTreeMap, pub resolved_product_image: ResolvedProductImage, @@ -71,6 +124,72 @@ pub struct ValidatedSparkHistoryServer { // is needed for command args and volume mounts. pub log_dir: ResolvedLogDir, pub log_dir_settings: BTreeMap, + pub role_groups: BTreeMap, +} + +impl ValidatedSparkHistoryServer { + /// The single history server role name (`node`). + pub fn role_name() -> RoleName { + RoleName::from_str(HISTORY_ROLE_NAME).expect("HISTORY_ROLE_NAME is a valid role name") + } + + /// Type-safe names for the resources of a given role group. + pub(crate) fn resource_names(&self, role_group_name: &RoleGroupName) -> ResourceNames { + ResourceNames { + cluster_name: self.name.clone(), + role_name: Self::role_name(), + role_group_name: role_group_name.clone(), + } + } + + /// Recommended labels for a role-group resource, using the given product version. + fn recommended_labels_for( + &self, + product_version: &ProductVersion, + role_group_name: &RoleGroupName, + ) -> Labels { + recommended_labels( + self, + &product_name(), + product_version, + &operator_name(), + &controller_name(), + &Self::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) + } + + /// 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) + } +} + +/// The product name (`spark-history`) as a type-safe label value. +pub(crate) fn product_name() -> ProductName { + ProductName::from_str(HISTORY_APP_NAME).expect("HISTORY_APP_NAME is a valid product name") +} + +/// The operator name as a type-safe label value. +pub(crate) fn operator_name() -> OperatorName { + OperatorName::from_str(OPERATOR_NAME).expect("the operator name is a valid label value") +} + +/// The controller name as a type-safe label value. +pub(crate) fn controller_name() -> ControllerName { + ControllerName::from_str(HISTORY_CONTROLLER_NAME) + .expect("the controller name is a valid label value") +} + +impl NameIsValidLabelValue for ValidatedSparkHistoryServer { + fn to_label_value(&self) -> String { + self.name.to_label_value() + } } impl HasName for ValidatedSparkHistoryServer { @@ -142,15 +261,68 @@ pub fn validate( .history_server_spark_config() .context(InvalidLogDirSettingsSnafu)?; + // `app_version_label_value` is constructed to be a valid label value, so it is also a valid + // `ProductVersion`. + let product_version = ProductVersion::from_str(&resolved_product_image.app_version_label_value) + .expect("the app version label value is a valid product version"); + + let mut role_groups = BTreeMap::new(); + for rg_name in shs.spec.nodes.role_groups.keys() { + let role_group_name = + RoleGroupName::from_str(rg_name).with_context(|_| ParseRoleGroupNameSnafu { + role_group: rg_name.clone(), + })?; + + // A temporary reference used purely as the merge key for the existing CRD accessors. + let rgr = RoleGroupRef { + cluster: ObjectRef::from_obj(shs), + role: HISTORY_ROLE_NAME.to_string(), + role_group: rg_name.clone(), + }; + + let config = shs + .merged_config(&rgr) + .with_context(|_| FailedToResolveConfigSnafu { + role_group: rg_name.clone(), + })?; + + let role_group = shs + .rolegroup(&rgr) + .with_context(|_| CannotRetrieveRoleGroupSnafu { + role_group: rg_name.clone(), + })?; + + // Merge config_overrides from both nodes and role group levels. + let mut config_overrides = role_group.config.config_overrides; + config_overrides.merge(&shs.spec.nodes.config.config_overrides); + + // Merge pod_overrides: role-base first, then role-group on top. + let mut pod_overrides = shs.role().config.pod_overrides.clone(); + pod_overrides.merge_from(role_group.config.pod_overrides); + + role_groups.insert( + role_group_name, + ValidatedHistoryRoleGroup { + config, + config_overrides, + env_overrides: role_group.config.env_overrides, + pod_overrides, + replicas: shs.replicas(&rgr), + }, + ); + } + Ok(ValidatedSparkHistoryServer { metadata: shs.meta().clone(), name, namespace, uid, + product_version, cleaner_rolegroup_name, spark_conf: shs.spec.spark_conf.clone(), log_dir: dereferenced.log_dir, log_dir_settings, resolved_product_image, + role_groups, }) } diff --git a/rust/operator-binary/src/history/service.rs b/rust/operator-binary/src/history/service.rs index 3cab7acf..0b6009ce 100644 --- a/rust/operator-binary/src/history/service.rs +++ b/rust/operator-binary/src/history/service.rs @@ -1,52 +1,30 @@ -use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, - commons::product_image_selection::ResolvedProductImage, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels}, - role_utils::RoleGroupRef, - v2::builder::meta::ownerreference_from_resource, + v2::{builder::meta::ownerreference_from_resource, types::operator::RoleGroupName}, }; use crate::{ - crd::{ - constants::{HISTORY_APP_NAME, METRICS_PORT}, - history::v1alpha1, - }, - history::{controller::validate::ValidatedSparkHistoryServer, recommended_labels}, + crd::constants::METRICS_PORT, history::controller::validate::ValidatedSparkHistoryServer, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, -} - /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label pub fn build_rolegroup_metrics_service( - shs: &v1alpha1::SparkHistoryServer, validated: &ValidatedSparkHistoryServer, - resolved_product_image: &ResolvedProductImage, - rolegroup_ref: &RoleGroupRef, -) -> Result { - Ok(Service { + role_group_name: &RoleGroupName, +) -> Service { + Service { metadata: ObjectMetaBuilder::new() - .name_and_namespace(shs) - .name(rolegroup_ref.rolegroup_metrics_service_name()) + .name_and_namespace(validated) + .name( + validated + .resource_names(role_group_name) + .metrics_service_name() + .to_string(), + ) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&recommended_labels( - shs, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role_group, - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels(role_group_name)) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), @@ -55,21 +33,12 @@ pub fn build_rolegroup_metrics_service( type_: Some("ClusterIP".to_string()), cluster_ip: Some("None".to_string()), ports: Some(metrics_ports()), - selector: Some( - Labels::role_group_selector( - shs, - HISTORY_APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), + selector: Some(validated.role_group_selector(role_group_name).into()), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() }), status: None, - }) + } } fn metrics_ports() -> Vec { From 7c9c0e9f9c87b93600ef7b8c65d9ee5a19da0c31 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 15 Jun 2026 18:57:06 +0200 Subject: [PATCH 25/50] history: build the Vector container via operator-rs v2 (validate-time ValidatedLogging) --- .../operator-binary/src/history/controller.rs | 62 ++++++-------- .../src/history/controller/validate.rs | 80 ++++++++++++++++++- 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 19ed521b..7ae72407 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -9,7 +9,6 @@ use stackable_operator::{ pod::{ PodBuilder, container::ContainerBuilder, - resources::ResourceRequirementsBuilder, volume::{ ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, ListenerReference, VolumeBuilder, @@ -34,7 +33,7 @@ use stackable_operator::{ }, logging::controller::ReconcilerError, product_logging::{ - framework::{LoggingError, calculate_log_volume_size_limit, vector_container}, + framework::calculate_log_volume_size_limit, spec::{ ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, CustomContainerLogConfig, @@ -43,13 +42,23 @@ use stackable_operator::{ role_utils::RoleGroupRef, shared::time::Duration, v2::{ - builder::meta::ownerreference_from_resource, + builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, config_file_writer::{PropertiesWriterError, to_java_properties_string}, - types::operator::RoleGroupName, + product_logging::framework::vector_container, + types::{ + kubernetes::{ContainerName, VolumeName}, + operator::RoleGroupName, + }, }, }; use strum::{EnumDiscriminants, IntoStaticStr}; +stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); +// Typed volume names required by the v2 `vector_container`; values match the `&str` volume-mount +// name constants used elsewhere to build the same volumes. +stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); +stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); + use crate::{ Ctx, crd::{ @@ -153,18 +162,12 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] InvalidLoggingConfig { source: product_logging::Error, cm_name: String, }, - #[snafu(display("failed to configure logging"))] - ConfigureLogging { source: LoggingError }, - #[snafu(display( "History server : failed to serialize [{JVM_SECURITY_PROPERTIES_FILE}] for group {}", rolegroup @@ -568,33 +571,16 @@ fn build_stateful_set( pb.add_container(container); - if rg.config.logging.enable_vector_agent { - match &shs.spec.vector_aggregator_config_map_name { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - vector_container( - resolved_product_image, - VOLUME_MOUNT_NAME_CONFIG, - VOLUME_MOUNT_NAME_LOG, - rg.config - .logging - .containers - .get(&SparkHistoryServerContainer::Vector), - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("500m") - .with_memory_request("128Mi") - .with_memory_limit("128Mi") - .build(), - vector_aggregator_config_map_name, - ) - .context(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } + if let Some(vector_log_config) = &rg.logging.vector_container { + pb.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + resolved_product_image, + vector_log_config, + &resource_names, + &VOLUME_MOUNT_NAME_CONFIG_TYPED, + &VOLUME_MOUNT_NAME_LOG_TYPED, + EnvVarSet::new(), + )); } let mut pod_template = pb.build_template(); diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 3e92f960..e2598f96 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -9,7 +9,7 @@ use std::{ str::FromStr, }; -use snafu::{ResultExt, Snafu}; +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, @@ -19,14 +19,18 @@ use stackable_operator::{ }, kube::{Resource, runtime::reflector::ObjectRef}, kvp::Labels, + product_logging::spec::Logging, role_utils::RoleGroupRef, v2::{ HasName, HasUid, NameIsValidLabelValue, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::{recommended_labels, role_group_selector}, + product_logging::framework::{ + VectorContainerLogConfig, validate_logging_configuration_for_container, + }, role_group_utils::ResourceNames, types::{ - kubernetes::{NamespaceName, Uid}, + kubernetes::{ConfigMapName, NamespaceName, Uid}, operator::{ ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleGroupName, RoleName, @@ -41,7 +45,7 @@ use crate::{ CONTAINER_IMAGE_BASE_NAME, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, HISTORY_ROLE_NAME, OPERATOR_NAME, }, - history::{HistoryConfig, v1alpha1}, + history::{HistoryConfig, SparkHistoryServerContainer, v1alpha1}, logdir::ResolvedLogDir, }, history::controller::dereference::DereferencedSparkHistoryServer, @@ -92,6 +96,51 @@ pub enum Error { source: crate::crd::history::Error, role_group: String, }, + + #[snafu(display("failed to validate the logging configuration"))] + ValidateLoggingConfig { + source: stackable_operator::v2::product_logging::framework::Error, + }, + + #[snafu(display( + "the Vector aggregator discovery ConfigMap name must be set when the Vector agent is enabled" + ))] + MissingVectorAggregatorConfigMapName, + + #[snafu(display("invalid Vector aggregator discovery ConfigMap name"))] + ParseVectorAggregatorConfigMapName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, +} + +/// Validates the logging configuration for the (optional) Vector container. +/// +/// `vector_aggregator_config_map_name` is the discovery ConfigMap name of the Vector aggregator; +/// it is required (and validated) only when the Vector agent is enabled. +fn validate_logging( + logging: &Logging, + vector_aggregator_config_map_name: &Option, +) -> Result { + let vector_container = if logging.enable_vector_agent { + let vector_aggregator_config_map_name = vector_aggregator_config_map_name + .clone() + .context(MissingVectorAggregatorConfigMapNameSnafu)?; + Some(VectorContainerLogConfig { + log_config: validate_logging_configuration_for_container( + logging, + &SparkHistoryServerContainer::Vector, + ) + .context(ValidateLoggingConfigSnafu)?, + vector_aggregator_config_map_name, + }) + } else { + None + }; + + Ok(ValidatedLogging { + vector_container, + enable_vector_agent: logging.enable_vector_agent, + }) } type Result = std::result::Result; @@ -104,6 +153,18 @@ pub struct ValidatedHistoryRoleGroup { pub env_overrides: HashMap, pub pod_overrides: PodTemplateSpec, pub replicas: Option, + pub logging: ValidatedLogging, +} + +/// Validated logging configuration for the (optional) Vector container. +/// +/// Produced up-front by [`validate_logging`] so that an +/// invalid custom log ConfigMap name or a missing Vector aggregator discovery ConfigMap name fails +/// reconciliation during validation rather than at resource-build time. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValidatedLogging { + pub vector_container: Option, + pub enable_vector_agent: bool, } pub struct ValidatedSparkHistoryServer { @@ -266,6 +327,16 @@ pub fn validate( let product_version = ProductVersion::from_str(&resolved_product_image.app_version_label_value) .expect("the app version label value is a valid product version"); + // The Vector aggregator discovery ConfigMap name (validated here so an invalid name fails + // up-front). It is only required when the Vector agent is enabled for a role group. + let vector_aggregator_config_map_name = shs + .spec + .vector_aggregator_config_map_name + .as_deref() + .map(ConfigMapName::from_str) + .transpose() + .context(ParseVectorAggregatorConfigMapNameSnafu)?; + let mut role_groups = BTreeMap::new(); for rg_name in shs.spec.nodes.role_groups.keys() { let role_group_name = @@ -300,6 +371,8 @@ pub fn validate( let mut pod_overrides = shs.role().config.pod_overrides.clone(); pod_overrides.merge_from(role_group.config.pod_overrides); + let logging = validate_logging(&config.logging, &vector_aggregator_config_map_name)?; + role_groups.insert( role_group_name, ValidatedHistoryRoleGroup { @@ -308,6 +381,7 @@ pub fn validate( env_overrides: role_group.config.env_overrides, pod_overrides, replicas: shs.replicas(&rgr), + logging, }, ); } From d2d2b4dc1b72b36da30639d5234d7ddc53dfe591 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 08:06:02 +0200 Subject: [PATCH 26/50] history: merge role-group config via operator-rs v2 with_validated_config (incl. JVM) --- rust/operator-binary/src/crd/affinity.rs | 59 ++++- rust/operator-binary/src/crd/history.rs | 216 +++++------------- .../operator-binary/src/history/config/jvm.rs | 95 +++++--- .../operator-binary/src/history/controller.rs | 62 +++-- .../src/history/controller/validate.rs | 112 ++++----- 5 files changed, 276 insertions(+), 268 deletions(-) diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index 5defa934..47527502 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -27,16 +27,26 @@ mod test { use std::collections::BTreeMap; use stackable_operator::{ - commons::affinity::StackableAffinity, + cli::OperatorEnvironmentOptions, + commons::{affinity::StackableAffinity, tls_verification::TlsClientDetails}, + crd::s3, k8s_openapi::{ api::core::v1::{PodAffinityTerm, PodAntiAffinity, WeightedPodAffinityTerm}, apimachinery::pkg::apis::meta::v1::LabelSelector, }, - kube::runtime::reflector::ObjectRef, - role_utils::RoleGroupRef, }; - use crate::crd::{constants::HISTORY_ROLE_NAME, history::v1alpha1}; + use crate::{ + crd::{ + constants::HISTORY_ROLE_NAME, + history::v1alpha1, + logdir::{ResolvedLogDir, S3LogDir}, + }, + history::controller::{ + dereference::DereferencedSparkHistoryServer, + validate::{ValidatedSparkHistoryServer, validate}, + }, + }; #[test] pub fn test_history_affinity_defaults() { @@ -45,6 +55,8 @@ mod test { kind: SparkHistoryServer metadata: name: spark-history + namespace: default + uid: 12345678-1234-1234-1234-123456789012 spec: image: productVersion: 3.5.8 @@ -99,13 +111,40 @@ mod test { }), }; - let rolegroup_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(&history), - role: HISTORY_ROLE_NAME.to_string(), - role_group: "default".to_string(), - }; + let validated: ValidatedSparkHistoryServer = validate( + &history, + DereferencedSparkHistoryServer { + log_dir: ResolvedLogDir::S3(S3LogDir { + bucket: s3::v1alpha1::ResolvedBucket { + bucket_name: "my-bucket".to_string(), + connection: s3::v1alpha1::ConnectionSpec { + host: "my-s3".to_string().try_into().unwrap(), + port: None, + access_style: Default::default(), + credentials: None, + tls: TlsClientDetails { tls: None }, + region: Default::default(), + }, + }, + prefix: "prefix".to_string(), + }), + }, + &OperatorEnvironmentOptions { + operator_namespace: "default".to_string(), + operator_service_name: "spark-k8s-operator".to_string(), + image_repository: "oci.stackable.tech/sdp".to_string(), + }, + ) + .expect("validation should succeed"); - let affinity = history.merged_config(&rolegroup_ref).unwrap().affinity; + let affinity = validated + .role_groups + .get(&"default".parse().expect("valid role group name")) + .expect("default role group should exist") + .config + .config + .affinity + .clone(); assert_eq!(affinity, expected); } diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index 76873cb4..c1eb0fa8 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -1,7 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::Snafu; use stackable_operator::{ commons::{ affinity::StackableAffinity, @@ -11,44 +11,24 @@ use stackable_operator::{ Resources, ResourcesFragment, }, }, - config::{ - fragment::{self, Fragment, ValidationError}, - merge::Merge, - }, + config::{fragment::Fragment, merge::Merge}, crd::s3, deep_merger::ObjectOverrides, - k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::api::resource::Quantity}, - kube::{CustomResource, ResourceExt}, + k8s_openapi::apimachinery::pkg::api::resource::Quantity, + kube::CustomResource, product_logging::{self, spec::Logging}, - role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef}, + role_utils::{GenericRoleConfig, Role}, schemars::{self, JsonSchema}, shared::time::Duration, - v2::config_overrides::KeyValueConfigOverrides, + v2::{config_overrides::KeyValueConfigOverrides, role_utils::JavaCommonConfig}, versioned::versioned, }; use strum::{Display, EnumIter}; -use crate::{ - crd::{ - affinity::history_affinity, constants::*, history::v1alpha1::SparkHistoryServerRoleConfig, - logdir::ResolvedLogDir, - }, - history::config::jvm::construct_history_jvm_args, -}; +use crate::crd::{affinity::history_affinity, history::v1alpha1::SparkHistoryServerRoleConfig}; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("fragment validation failure"))] - FragmentValidationFailure { source: ValidationError }, - - #[snafu(display("the role group {role_group} is not defined"))] - CannotRetrieveRoleGroup { role_group: String }, - - #[snafu(display("failed to construct JVM arguments"))] - ConstructJvmArguments { - source: crate::history::config::jvm::Error, - }, - #[snafu(display("too many cleaner replicas"))] TooManyCleanerReplicas, @@ -141,61 +121,11 @@ impl v1alpha1::SparkHistoryServer { &self.spec.nodes } - /// Returns a reference to the role group. Raises an error if the role or role group are not defined. - pub fn rolegroup( - &self, - rolegroup_ref: &RoleGroupRef, - ) -> Result, Error> - { - self.spec - .nodes - .role_groups - .get(&rolegroup_ref.role_group) - .with_context(|| CannotRetrieveRoleGroupSnafu { - role_group: rolegroup_ref.role_group.to_owned(), - }) - .cloned() - } - /// Return the listener class of the role config. pub fn node_listener_class(&self) -> &str { self.spec.nodes.role_config.listener_class.as_str() } - pub fn merged_config( - &self, - rolegroup_ref: &RoleGroupRef, - ) -> Result { - // Initialize the result with all default values as baseline - let conf_defaults = HistoryConfig::default_config(&self.name_any()); - - let role = &self.spec.nodes; - - // Retrieve role resource config - let mut conf_role = role.config.config.to_owned(); - - // Retrieve rolegroup specific resource config - let mut conf_rolegroup = role - .role_groups - .get(&rolegroup_ref.role_group) - .map(|rg| rg.config.config.clone()) - .unwrap_or_default(); - - conf_role.merge(&conf_defaults); - conf_rolegroup.merge(&conf_role); - - fragment::validate(conf_rolegroup).context(FragmentValidationFailureSnafu) - } - - pub fn replicas(&self, rolegroup_ref: &RoleGroupRef) -> Option { - self.spec - .nodes - .role_groups - .get(&rolegroup_ref.role_group) - .and_then(|rg| rg.replicas) - .map(i32::from) - } - // Returns the name of the cleaner role group if any. // Raises an error when: // * there are multiple cleaner role groups @@ -235,46 +165,6 @@ impl v1alpha1::SparkHistoryServer { .build()), } } - - pub fn merged_env( - &self, - role_group: &str, - logdir: &ResolvedLogDir, - role_group_env_overrides: HashMap, - ) -> Result, Error> { - let role = self.role(); - let history_jvm_args = construct_history_jvm_args(role, role_group, logdir) - .context(ConstructJvmArgumentsSnafu)?; - let mut envs = BTreeMap::from([ - // Needed by the `containerdebug` running in the background of the history container - // to log it's tracing information to. - ( - "CONTAINERDEBUG_LOG_DIRECTORY".to_string(), - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), - ), - // This env var prevents the history server from detaching itself from the - // start script because this leads to the Pod terminating immediately. - ("SPARK_NO_DAEMONIZE".to_string(), "true".to_string()), - ( - "SPARK_DAEMON_CLASSPATH".to_string(), - "/stackable/spark/extra-jars/*".to_string(), - ), - // JVM arguments for the history server - ("SPARK_HISTORY_OPTS".to_string(), history_jvm_args), - ]); - - envs.extend(role.config.env_overrides.clone()); - envs.extend(role_group_env_overrides); - - Ok(envs - .into_iter() - .map(|(name, value)| EnvVar { - name: name.to_owned(), - value: Some(value.to_owned()), - value_from: None, - }) - .collect()) - } } #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, Display)] @@ -366,7 +256,7 @@ impl HistoryConfig { // Auto TLS certificate lifetime const DEFAULT_HISTORY_SECRET_LIFETIME: Duration = Duration::from_days_unchecked(1); - fn default_config(cluster_name: &str) -> HistoryConfigFragment { + pub fn default_config(cluster_name: &str) -> HistoryConfigFragment { HistoryConfigFragment { cleaner: None, resources: ResourcesFragment { @@ -402,14 +292,22 @@ fn default_listener_class() -> String { #[cfg(test)] mod test { + use std::str::FromStr; + use indoc::indoc; use stackable_operator::{ - commons::tls_verification::TlsClientDetails, crd::s3, - versioned::test_utils::RoundtripTestData, + cli::OperatorEnvironmentOptions, commons::tls_verification::TlsClientDetails, crd::s3, + v2::builder::pod::container::EnvVarName, versioned::test_utils::RoundtripTestData, }; use super::*; - use crate::crd::logdir::S3LogDir; + use crate::{ + crd::logdir::{ResolvedLogDir, S3LogDir}, + history::controller::{ + dereference::DereferencedSparkHistoryServer, + validate::{ValidatedSparkHistoryServer, validate}, + }, + }; #[test] pub fn test_env_overrides() { @@ -419,6 +317,8 @@ mod test { kind: SparkHistoryServer metadata: name: spark-history + namespace: default + uid: 12345678-1234-1234-1234-123456789012 spec: image: productVersion: 3.5.8 @@ -443,45 +343,45 @@ mod test { let history: v1alpha1::SparkHistoryServer = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - let log_dir = ResolvedLogDir::S3(S3LogDir { - bucket: s3::v1alpha1::ResolvedBucket { - bucket_name: "my-bucket".to_string(), - connection: s3::v1alpha1::ConnectionSpec { - host: "my-s3".to_string().try_into().unwrap(), - port: None, - access_style: Default::default(), - credentials: None, - tls: TlsClientDetails { tls: None }, - region: Default::default(), - }, + let validated: ValidatedSparkHistoryServer = validate( + &history, + DereferencedSparkHistoryServer { + log_dir: ResolvedLogDir::S3(S3LogDir { + bucket: s3::v1alpha1::ResolvedBucket { + bucket_name: "my-bucket".to_string(), + connection: s3::v1alpha1::ConnectionSpec { + host: "my-s3".to_string().try_into().unwrap(), + port: None, + access_style: Default::default(), + credentials: None, + tls: TlsClientDetails { tls: None }, + region: Default::default(), + }, + }, + prefix: "prefix".to_string(), + }), }, - prefix: "prefix".to_string(), - }); - - let merged_env = history - .merged_env( - "default", - &log_dir, - history - .spec - .nodes - .role_groups - .get("default") - .unwrap() - .config - .env_overrides - .clone(), - ) - .unwrap(); - - let env_map: BTreeMap<&str, Option> = merged_env - .iter() - .map(|env_var| (env_var.name.as_str(), env_var.value.clone())) - .collect(); + &OperatorEnvironmentOptions { + operator_namespace: "default".to_string(), + operator_service_name: "spark-k8s-operator".to_string(), + image_repository: "oci.stackable.tech/sdp".to_string(), + }, + ) + .expect("validation should succeed"); + + // The role group `envOverrides` value wins over the role-level one. + let env_overrides = &validated + .role_groups + .get(&"default".parse().expect("valid role group name")) + .expect("default role group should exist") + .config + .env_overrides; assert_eq!( - Some(&Some("ROLEGROUP".to_string())), - env_map.get("TEST_SPARK_HIST_VAR") + env_overrides + .get(&EnvVarName::from_str("TEST_SPARK_HIST_VAR").expect("valid env var name")) + .and_then(|env_var| env_var.value.clone()), + Some("ROLEGROUP".to_string()) ); } diff --git a/rust/operator-binary/src/history/config/jvm.rs b/rust/operator-binary/src/history/config/jvm.rs index 9773616e..ea57425c 100644 --- a/rust/operator-binary/src/history/config/jvm.rs +++ b/rust/operator-binary/src/history/config/jvm.rs @@ -1,28 +1,17 @@ -use snafu::{ResultExt, Snafu}; -use stackable_operator::role_utils::{self, JvmArgumentOverrides}; - -use crate::crd::{ - constants::{ - JVM_SECURITY_PROPERTIES_FILE, LOG4J2_CONFIG_FILE, METRICS_PORT, - STACKABLE_TLS_STORE_PASSWORD, STACKABLE_TRUST_STORE, VOLUME_MOUNT_PATH_CONFIG, - VOLUME_MOUNT_PATH_LOG_CONFIG, +use crate::{ + crd::{ + constants::{ + JVM_SECURITY_PROPERTIES_FILE, LOG4J2_CONFIG_FILE, METRICS_PORT, + STACKABLE_TLS_STORE_PASSWORD, STACKABLE_TRUST_STORE, VOLUME_MOUNT_PATH_CONFIG, + VOLUME_MOUNT_PATH_LOG_CONFIG, + }, + logdir::ResolvedLogDir, }, - history::SparkHistoryRoleType, - logdir::ResolvedLogDir, + history::controller::validate::HistoryRoleGroupConfig, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to merge jvm argument overrides"))] - MergeJvmArgumentOverrides { source: role_utils::Error }, -} - /// JVM arguments that go into `SPARK_HISTORY_OPTS` -pub fn construct_history_jvm_args( - role: &SparkHistoryRoleType, - role_group: &str, - logdir: &ResolvedLogDir, -) -> Result { +pub fn construct_history_jvm_args(rg: &HistoryRoleGroupConfig, logdir: &ResolvedLogDir) -> String { // Note (@sbernauer): As of 2025-03-04, we did not set any heap related JVM arguments, so I // kept the implementation as is. We can always re-visit this as needed. @@ -44,17 +33,28 @@ pub fn construct_history_jvm_args( ]); } - let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args); - let merged = role - .get_merged_jvm_argument_overrides(role_group, &operator_generated) - .context(MergeJvmArgumentOverridesSnafu)?; - Ok(merged.effective_jvm_config_after_merging().join(" ")) + // Apply the already-merged (role + role group) JVM argument overrides on top of the + // operator-generated base arguments. + rg.product_specific_common_config + .jvm_argument_overrides + .apply_to(jvm_args) + .join(" ") } #[cfg(test)] mod tests { + use stackable_operator::{ + cli::OperatorEnvironmentOptions, commons::tls_verification::TlsClientDetails, crd::s3, + }; + use super::*; - use crate::crd::history::v1alpha1::SparkHistoryServer; + use crate::{ + crd::{history::v1alpha1::SparkHistoryServer, logdir::S3LogDir}, + history::controller::{ + dereference::DereferencedSparkHistoryServer, + validate::{ValidatedSparkHistoryServer, validate}, + }, + }; #[test] fn test_construct_jvm_arguments_defaults() { @@ -63,6 +63,8 @@ mod tests { kind: SparkHistoryServer metadata: name: spark-history + namespace: default + uid: 12345678-1234-1234-1234-123456789012 spec: image: productVersion: 3.5.8 @@ -96,6 +98,8 @@ mod tests { kind: SparkHistoryServer metadata: name: spark-history + namespace: default + uid: 12345678-1234-1234-1234-123456789012 spec: image: productVersion: 3.5.8 @@ -135,14 +139,47 @@ mod tests { ); } + /// Validates the given `SparkHistoryServer` YAML and returns the `default` role group's merged + /// config, mirroring the controller's validate path so the JVM args are built from a real + /// [`HistoryRoleGroupConfig`]. fn construct_jvm_config_for_test(history_server: &str) -> String { let deserializer = serde_yaml::Deserializer::from_str(history_server); let history_server: SparkHistoryServer = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - let role = history_server.role(); let resolved_log_dir = ResolvedLogDir::Custom("local:/tmp/foo".to_owned()); - construct_history_jvm_args(role, "default", &resolved_log_dir).unwrap() + let validated: ValidatedSparkHistoryServer = validate( + &history_server, + DereferencedSparkHistoryServer { + log_dir: ResolvedLogDir::S3(S3LogDir { + bucket: s3::v1alpha1::ResolvedBucket { + bucket_name: "my-bucket".to_string(), + connection: s3::v1alpha1::ConnectionSpec { + host: "my-s3".to_string().try_into().unwrap(), + port: None, + access_style: Default::default(), + credentials: None, + tls: TlsClientDetails { tls: None }, + region: Default::default(), + }, + }, + prefix: "prefix".to_string(), + }), + }, + &OperatorEnvironmentOptions { + operator_namespace: "default".to_string(), + operator_service_name: "spark-k8s-operator".to_string(), + image_repository: "oci.stackable.tech/sdp".to_string(), + }, + ) + .expect("validation should succeed"); + + let rg = validated + .role_groups + .get(&"default".parse().expect("valid role group name")) + .expect("default role group should exist"); + + construct_history_jvm_args(&rg.config, &resolved_log_dir) } } diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 7ae72407..4a08b3f9 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -42,7 +42,10 @@ use stackable_operator::{ role_utils::RoleGroupRef, shared::time::Duration, v2::{ - builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, + builder::{ + meta::ownerreference_from_resource, + pod::container::{EnvVarName, EnvVarSet}, + }, config_file_writer::{PropertiesWriterError, to_java_properties_string}, product_logging::framework::vector_container, types::{ @@ -79,8 +82,8 @@ use crate::{ tlscerts, to_spark_env_sh_string, }, history::{ - controller::validate::ValidatedHistoryRoleGroup, operations::pdb::add_pdbs, - recommended_labels, service::build_rolegroup_metrics_service, + config::jvm::construct_history_jvm_args, controller::validate::ValidatedHistoryRoleGroup, + operations::pdb::add_pdbs, recommended_labels, service::build_rolegroup_metrics_service, }, product_logging::{self}, }; @@ -204,9 +207,6 @@ pub enum Error { source: error_boundary::InvalidObject, }, - #[snafu(display("failed to merge environment config and/or overrides"))] - MergeEnv { source: crate::crd::history::Error }, - #[snafu(display("failed to apply group listener"))] ApplyGroupListener { source: stackable_operator::cluster_resources::Error, @@ -394,7 +394,13 @@ fn build_config_map( .context(InvalidSparkDefaultsSnafu)?; let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(rg.config_overrides.security_properties.overrides.clone()); + jvm_sec_props.extend( + rg.config + .config_overrides + .security_properties + .overrides + .clone(), + ); let mut cm_builder = ConfigMapBuilder::new(); @@ -414,7 +420,7 @@ fn build_config_map( .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) .add_data( SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(rg.config_overrides.spark_env_sh.overrides.iter()), + to_spark_env_sh_string(rg.config.config_overrides.spark_env_sh.overrides.iter()), ) .add_data( JVM_SECURITY_PROPERTIES_FILE, @@ -436,7 +442,7 @@ fn build_config_map( product_logging::extend_config_map( &rgr, - &rg.config.logging, + &rg.config.config.logging, SparkHistoryServerContainer::SparkHistory, SparkHistoryServerContainer::Vector, &mut cm_builder, @@ -466,6 +472,7 @@ fn build_stateful_set( custom: ConfigMapLogConfig { config_map }, })), }) = rg + .config .config .logging .containers @@ -485,6 +492,7 @@ fn build_stateful_set( let mut pb = PodBuilder::new(); let requested_secret_lifetime = rg + .config .config .requested_secret_lifetime .context(MissingSecretLifetimeSnafu)?; @@ -523,15 +531,39 @@ fn build_stateful_set( ..PodSecurityContext::default() }); - let merged_env = shs - .merged_env(role_group_name.as_ref(), log_dir, rg.env_overrides.clone()) - .context(MergeEnvSnafu)?; + // Base environment variables, with the already-merged (role + role group) env overrides + // layered on top (overrides win). The base names are static and known to be valid. + let known_env_var_name = |name: &str| { + EnvVarName::from_str(name).expect("the operator-generated env var name is valid") + }; + let merged_env = EnvVarSet::new() + .with_values([ + // Needed by the `containerdebug` running in the background of the history container + // to log it's tracing information to. + ( + known_env_var_name("CONTAINERDEBUG_LOG_DIRECTORY"), + format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), + ), + // This env var prevents the history server from detaching itself from the + // start script because this leads to the Pod terminating immediately. + (known_env_var_name("SPARK_NO_DAEMONIZE"), "true".to_owned()), + ( + known_env_var_name("SPARK_DAEMON_CLASSPATH"), + "/stackable/spark/extra-jars/*".to_owned(), + ), + // JVM arguments for the history server. + ( + known_env_var_name("SPARK_HISTORY_OPTS"), + construct_history_jvm_args(&rg.config, log_dir), + ), + ]) + .merge(rg.config.env_overrides.clone()); let container_name = "spark-history"; let container = ContainerBuilder::new(container_name) .context(InvalidContainerNameSnafu)? .image_from_product_image(resolved_product_image) - .resources(rg.config.resources.clone().into()) + .resources(rg.config.config.resources.clone().into()) .command(vec![ "/bin/bash".to_string(), "-x".to_string(), @@ -584,7 +616,7 @@ fn build_stateful_set( } let mut pod_template = pb.build_template(); - pod_template.merge_from(rg.pod_overrides.clone()); + pod_template.merge_from(rg.config.pod_overrides.clone()); let sts_metadata = ObjectMetaBuilder::new() .name_and_namespace(validated) @@ -598,7 +630,7 @@ fn build_stateful_set( spec: Some(StatefulSetSpec { template: pod_template, volume_claim_templates, - replicas: rg.replicas, + replicas: Some(i32::from(rg.config.replicas)), selector: LabelSelector { match_labels: Some(validated.role_group_selector(role_group_name).into()), ..LabelSelector::default() diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index e2598f96..68a56626 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -3,32 +3,27 @@ //! Resolves the product image. //! Does not touch the Kubernetes API. -use std::{ - borrow::Cow, - collections::{BTreeMap, HashMap}, - str::FromStr, -}; +use std::{borrow::Cow, collections::BTreeMap, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - config::merge::Merge, - k8s_openapi::{ - DeepMerge, api::core::v1::PodTemplateSpec, apimachinery::pkg::apis::meta::v1::ObjectMeta, - }, - kube::{Resource, runtime::reflector::ObjectRef}, + config::fragment, + k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, + kube::Resource, kvp::Labels, product_logging::spec::Logging, - role_utils::RoleGroupRef, v2::{ HasName, HasUid, NameIsValidLabelValue, + builder::pod::container::{self, EnvVarName, EnvVarSet}, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::{recommended_labels, role_group_selector}, product_logging::framework::{ VectorContainerLogConfig, validate_logging_configuration_for_container, }, role_group_utils::ResourceNames, + role_utils::{JavaCommonConfig, RoleGroupConfig, with_validated_config}, types::{ kubernetes::{ConfigMapName, NamespaceName, Uid}, operator::{ @@ -45,7 +40,7 @@ use crate::{ CONTAINER_IMAGE_BASE_NAME, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, HISTORY_ROLE_NAME, OPERATOR_NAME, }, - history::{HistoryConfig, SparkHistoryServerContainer, v1alpha1}, + history::{HistoryConfig, HistoryConfigFragment, SparkHistoryServerContainer, v1alpha1}, logdir::ResolvedLogDir, }, history::controller::dereference::DereferencedSparkHistoryServer, @@ -87,13 +82,13 @@ pub enum Error { #[snafu(display("failed to resolve and merge config for role group {role_group}"))] FailedToResolveConfig { - source: crate::crd::history::Error, + source: fragment::ValidationError, role_group: String, }, - #[snafu(display("cannot retrieve role group {role_group}"))] - CannotRetrieveRoleGroup { - source: crate::crd::history::Error, + #[snafu(display("invalid environment variable override name in role group {role_group}"))] + ParseEnvVarName { + source: container::Error, role_group: String, }, @@ -145,14 +140,14 @@ fn validate_logging( type Result = std::result::Result; +/// A validated, merged history server role-group config. +pub type HistoryRoleGroupConfig = + RoleGroupConfig; + /// A pre-validated history server role group: the per-role-group merge products that the build /// steps used to recompute from the raw CRD on every reconcile. pub struct ValidatedHistoryRoleGroup { - pub config: HistoryConfig, - pub config_overrides: v1alpha1::ConfigOverrides, - pub env_overrides: HashMap, - pub pod_overrides: PodTemplateSpec, - pub replicas: Option, + pub config: HistoryRoleGroupConfig, pub logging: ValidatedLogging, } @@ -337,52 +332,57 @@ pub fn validate( .transpose() .context(ParseVectorAggregatorConfigMapNameSnafu)?; + let role = shs.role(); + let default_config = HistoryConfig::default_config(name.as_ref()); + let mut role_groups = BTreeMap::new(); - for rg_name in shs.spec.nodes.role_groups.keys() { + for (rg_name, role_group) in &shs.spec.nodes.role_groups { let role_group_name = RoleGroupName::from_str(rg_name).with_context(|_| ParseRoleGroupNameSnafu { role_group: rg_name.clone(), })?; - // A temporary reference used purely as the merge key for the existing CRD accessors. - let rgr = RoleGroupRef { - cluster: ObjectRef::from_obj(shs), - role: HISTORY_ROLE_NAME.to_string(), + let merged = with_validated_config::< + HistoryConfig, + JavaCommonConfig, + HistoryConfigFragment, + v1alpha1::SparkHistoryServerRoleConfig, + v1alpha1::ConfigOverrides, + >(role_group, role, &default_config) + .with_context(|_| FailedToResolveConfigSnafu { role_group: rg_name.clone(), - }; - - let config = shs - .merged_config(&rgr) - .with_context(|_| FailedToResolveConfigSnafu { - role_group: rg_name.clone(), - })?; - - let role_group = shs - .rolegroup(&rgr) - .with_context(|_| CannotRetrieveRoleGroupSnafu { - role_group: rg_name.clone(), - })?; - - // Merge config_overrides from both nodes and role group levels. - let mut config_overrides = role_group.config.config_overrides; - config_overrides.merge(&shs.spec.nodes.config.config_overrides); - - // Merge pod_overrides: role-base first, then role-group on top. - let mut pod_overrides = shs.role().config.pod_overrides.clone(); - pod_overrides.merge_from(role_group.config.pod_overrides); + })?; + + let mut env_overrides = EnvVarSet::new(); + for (env_var_name, env_var_value) in merged.config.env_overrides { + env_overrides = env_overrides.with_value( + &EnvVarName::from_str(&env_var_name).with_context(|_| ParseEnvVarNameSnafu { + role_group: rg_name.clone(), + })?, + env_var_value, + ); + } - let logging = validate_logging(&config.logging, &vector_aggregator_config_map_name)?; + let logging = validate_logging( + &merged.config.config.logging, + &vector_aggregator_config_map_name, + )?; + + let config = HistoryRoleGroupConfig { + replicas: merged.replicas.unwrap_or(1), + config: merged.config.config, + config_overrides: merged.config.config_overrides, + env_overrides, + // The history server does not use CLI overrides; the field is carried (and merged + // upstream) but unused. + cli_overrides: merged.config.cli_overrides, + pod_overrides: merged.config.pod_overrides, + product_specific_common_config: merged.config.product_specific_common_config, + }; role_groups.insert( role_group_name, - ValidatedHistoryRoleGroup { - config, - config_overrides, - env_overrides: role_group.config.env_overrides, - pod_overrides, - replicas: shs.replicas(&rgr), - logging, - }, + ValidatedHistoryRoleGroup { config, logging }, ); } From 961595a466fecf70f1764194b72fb4d0139d3f56 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 08:58:31 +0200 Subject: [PATCH 27/50] use v2::builder::pdb::pod_disruption_budget_builder_with_role --- .../operator-binary/src/history/controller.rs | 74 +++++++++---------- .../src/history/operations/pdb.rs | 31 +++----- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 4a08b3f9..29b40fc5 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -6,16 +6,9 @@ use stackable_operator::{ self, configmap::ConfigMapBuilder, meta::ObjectMetaBuilder, - pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{ - ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, - ListenerReference, VolumeBuilder, - }, - }, + pod::{PodBuilder, container::ContainerBuilder, volume::VolumeBuilder}, }, - cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, + cluster_resources::ClusterResourceApplyStrategy, commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, crd::listener, k8s_openapi::{ @@ -27,7 +20,7 @@ use stackable_operator::{ apimachinery::pkg::apis::meta::v1::LabelSelector, }, kube::{ - Resource, ResourceExt, + ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::{controller::Action, reflector::ObjectRef}, }, @@ -44,12 +37,16 @@ use stackable_operator::{ v2::{ builder::{ meta::ownerreference_from_resource, - pod::container::{EnvVarName, EnvVarSet}, + pod::{ + container::{EnvVarName, EnvVarSet}, + volume::{ListenerReference, listener_operator_volume_source_builder_build_pvc}, + }, }, + cluster_resources::cluster_resources_new, config_file_writer::{PropertiesWriterError, to_java_properties_string}, product_logging::framework::vector_container, types::{ - kubernetes::{ContainerName, VolumeName}, + kubernetes::{ContainerName, PersistentVolumeClaimName, VolumeName}, operator::RoleGroupName, }, }, @@ -61,17 +58,20 @@ stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); // name constants used elsewhere to build the same volumes. stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); +// PVC name for the listener volume, required by the v2 listener-volume builder. Its value matches +// `LISTENER_VOLUME_NAME` in `crd::constants`. +stackable_operator::constant!(LISTENER_VOLUME_NAME_PVC: PersistentVolumeClaimName = "listener"); use crate::{ Ctx, crd::{ constants::{ ACCESS_KEY_ID, DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, - DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, - HISTORY_ROLE_NAME, HISTORY_UI_PORT, JVM_SECURITY_PROPERTIES_FILE, + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, HISTORY_APP_NAME, HISTORY_ROLE_NAME, + HISTORY_UI_PORT, JVM_SECURITY_PROPERTIES_FILE, JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, - OPERATOR_NAME, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, + SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, STACKABLE_TRUST_STORE, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, @@ -105,11 +105,6 @@ pub enum Error { source: crate::crd::listener_ext::Error, }, - #[snafu(display("failed to build listener volume"))] - BuildListenerVolume { - source: ListenerOperatorVolumeSourceBuilderError, - }, - #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, @@ -155,11 +150,6 @@ pub enum Error { #[snafu(display("failed to validate SparkHistoryServer"))] ValidateSparkHistoryServer { source: validate::Error }, - #[snafu(display("failed to create cluster resources"))] - CreateClusterResources { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to delete orphaned resources"))] DeleteOrphanedResources { source: stackable_operator::cluster_resources::Error, @@ -243,15 +233,16 @@ pub async fn reconcile( let validated = validate::validate(shs, dereferenced, &ctx.operator_environment) .context(ValidateSparkHistoryServerSnafu)?; - let mut cluster_resources = ClusterResources::new( - HISTORY_APP_NAME, - OPERATOR_NAME, - HISTORY_CONTROLLER_NAME, - &validated.object_ref(&()), + let mut cluster_resources = cluster_resources_new( + &validate::product_name(), + &validate::operator_name(), + &validate::controller_name(), + &validated.name, + &validated.namespace, + &validated.uid, ClusterResourceApplyStrategy::Default, &shs.spec.object_overrides, - ) - .context(CreateClusterResourcesSnafu)?; + ); let resolved_product_image = &validated.resolved_product_image; let log_dir = &validated.log_dir; @@ -317,7 +308,7 @@ pub async fn reconcile( let role_config = &shs.spec.nodes.role_config; add_pdbs( &role_config.common.pod_disruption_budget, - shs, + &validated, client, &mut cluster_resources, ) @@ -592,14 +583,15 @@ fn build_stateful_set( // so that load balancers can hard-code the target addresses. This will // be the case even when no class is set (and the value defaults to // cluster-internal) as the address should still be consistent. - let volume_claim_templates = Some(vec![ - ListenerOperatorVolumeSourceBuilder::new( - &ListenerReference::ListenerName(group_listener_name(shs, HISTORY_ROLE_NAME)), - &recommended_labels, - ) - .build_pvc(LISTENER_VOLUME_NAME.to_string()) - .context(BuildListenerVolumeSnafu)?, - ]); + let volume_claim_templates = Some(vec![listener_operator_volume_source_builder_build_pvc( + &ListenerReference::Listener( + group_listener_name(shs, HISTORY_ROLE_NAME) + .parse() + .expect("the group listener name is a valid ListenerName"), + ), + &recommended_labels, + &LISTENER_VOLUME_NAME_PVC, + )]); pb.add_container(container); diff --git a/rust/operator-binary/src/history/operations/pdb.rs b/rust/operator-binary/src/history/operations/pdb.rs index 0c6827de..9616e450 100644 --- a/rust/operator-binary/src/history/operations/pdb.rs +++ b/rust/operator-binary/src/history/operations/pdb.rs @@ -1,21 +1,15 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pdb::PodDisruptionBudgetBuilder, client::Client, cluster_resources::ClusterResources, - commons::pdb::PdbConfig, kube::ResourceExt, + client::Client, cluster_resources::ClusterResources, commons::pdb::PdbConfig, + kube::ResourceExt, v2::builder::pdb::pod_disruption_budget_builder_with_role, }; -use crate::crd::{ - constants::{HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, HISTORY_ROLE_NAME, OPERATOR_NAME}, - history::v1alpha1, +use crate::history::controller::validate::{ + ValidatedSparkHistoryServer, controller_name, operator_name, product_name, }; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("Cannot create PodDisruptionBudget for role [{role}]"))] - CreatePdb { - source: stackable_operator::builder::pdb::Error, - role: String, - }, #[snafu(display("Cannot apply PodDisruptionBudget [{name}]"))] ApplyPdb { source: stackable_operator::cluster_resources::Error, @@ -25,7 +19,7 @@ pub enum Error { pub async fn add_pdbs( pdb: &PdbConfig, - history: &v1alpha1::SparkHistoryServer, + validated: &ValidatedSparkHistoryServer, client: &Client, cluster_resources: &mut ClusterResources<'_>, ) -> Result<(), Error> { @@ -35,16 +29,13 @@ pub async fn add_pdbs( let max_unavailable = pdb .max_unavailable .unwrap_or(max_unavailable_history_servers()); - let pdb = PodDisruptionBudgetBuilder::new_with_role( - history, - HISTORY_APP_NAME, - HISTORY_ROLE_NAME, - OPERATOR_NAME, - HISTORY_CONTROLLER_NAME, + let pdb = pod_disruption_budget_builder_with_role( + validated, + &product_name(), + &ValidatedSparkHistoryServer::role_name(), + &operator_name(), + &controller_name(), ) - .with_context(|_| CreatePdbSnafu { - role: HISTORY_ROLE_NAME, - })? .with_max_unavailable(max_unavailable) .build(); let pdb_name = pdb.name_any(); From 0d9d42d5fa48af8b9f79e2749ae99e1be32706b0 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 09:05:11 +0200 Subject: [PATCH 28/50] history: use v2 Port type for the UI and metrics port constants --- rust/operator-binary/src/crd/constants.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index 3a447a23..bbd84d43 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -1,5 +1,8 @@ use const_format::concatcp; -use stackable_operator::memory::{BinaryMultiple, MemoryQuantity}; +use stackable_operator::{ + memory::{BinaryMultiple, MemoryQuantity}, + v2::types::common::Port, +}; pub const APP_NAME: &str = "spark-k8s"; @@ -89,8 +92,8 @@ pub const SPARK_DEFAULTS_FILE_NAME: &str = "spark-defaults.conf"; pub const SPARK_ENV_SH_FILE_NAME: &str = "spark-env.sh"; pub const SPARK_CLUSTER_ROLE: &str = "spark-k8s-clusterrole"; -pub const METRICS_PORT: u16 = 18081; -pub const HISTORY_UI_PORT: u16 = 18080; +pub const METRICS_PORT: Port = Port(18081); +pub const HISTORY_UI_PORT: Port = Port(18080); pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; From 81093e08391508b68a9d1a0378ec69ab54523ac9 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 09:25:33 +0200 Subject: [PATCH 29/50] connect: use v2 ownerreference_from_resource and add HasName/HasUid --- .../operator-binary/src/connect/controller.rs | 19 +++++++++++++------ .../src/connect/controller/validate.rs | 13 +++++++++++++ rust/operator-binary/src/connect/executor.rs | 9 ++------- rust/operator-binary/src/connect/server.rs | 11 ++++------- rust/operator-binary/src/connect/service.rs | 13 ++++++------- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index f4c240d3..904efc86 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -219,9 +219,12 @@ pub async fn reconcile( .context(ApplyRoleBindingSnafu)?; // Headless service used by executors connect back to the driver - let headless_service = - service::build_headless_service(scs, &resolved_product_image.app_version_label_value) - .context(BuildServiceSnafu)?; + let headless_service = service::build_headless_service( + &validated, + scs, + &resolved_product_image.app_version_label_value, + ) + .context(BuildServiceSnafu)?; let applied_headless_service = cluster_resources .add(client, headless_service.clone()) @@ -229,9 +232,12 @@ pub async fn reconcile( .context(ApplyServiceSnafu)?; // Metrics service used for scraping - let metrics_service = - service::build_metrics_service(scs, &resolved_product_image.app_version_label_value) - .context(BuildServiceSnafu)?; + let metrics_service = service::build_metrics_service( + &validated, + scs, + &resolved_product_image.app_version_label_value, + ) + .context(BuildServiceSnafu)?; cluster_resources .add(client, metrics_service.clone()) @@ -335,6 +341,7 @@ pub async fn reconcile( // Server stateful set let args = server::command_args(&scs.spec.args); let stateful_set = server::build_stateful_set( + &validated, scs, server_config, resolved_product_image, diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index 1c75882f..e0c643b9 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -12,6 +12,7 @@ use stackable_operator::{ k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, v2::{ + HasName, HasUid, controller_utils::{get_cluster_name, get_namespace, get_uid}, types::{ kubernetes::{NamespaceName, Uid}, @@ -71,6 +72,18 @@ pub struct ValidatedSparkConnectServer { pub executor_config: v1alpha1::ExecutorConfig, } +impl HasName for ValidatedSparkConnectServer { + fn to_name(&self) -> String { + String::from(&self.name) + } +} + +impl HasUid for ValidatedSparkConnectServer { + fn to_uid(&self) -> Uid { + self.uid.clone() + } +} + impl Resource for ValidatedSparkConnectServer { type DynamicType = (); type Scope = ::Scope; diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index f5072977..3a29d598 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -19,6 +19,7 @@ use stackable_operator::{ kube::{ResourceExt, runtime::reflector::ObjectRef}, product_logging::framework::calculate_log_volume_size_limit, role_utils::RoleGroupRef, + v2::builder::meta::ownerreference_from_resource, }; use super::{ @@ -40,11 +41,6 @@ use crate::{ #[derive(Snafu, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build metadata for spark connect executor pod template"))] PodTemplateMetadataBuild { source: builder::meta::Error }, @@ -368,8 +364,7 @@ pub(crate) fn executor_config_map( ObjectMetaBuilder::new() .name_and_namespace(validated) .name(&cm_name) - .ownerreference_from_resource(validated, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&common::labels( validated, &resolved_product_image.app_version_label_value, diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index dfa80337..0dd34332 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -34,6 +34,7 @@ use stackable_operator::{ kvp::{Label, Labels}, product_logging::framework::{LoggingError, calculate_log_volume_size_limit, vector_container}, role_utils::RoleGroupRef, + v2::builder::meta::ownerreference_from_resource, }; use super::crd::CONNECT_APP_NAME; @@ -91,9 +92,6 @@ pub enum Error { source: builder::pod::container::Error, }, - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { source: builder::meta::Error }, - #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] InvalidLoggingConfig { source: product_logging::Error, @@ -184,8 +182,7 @@ pub(crate) fn server_config_map( ObjectMetaBuilder::new() .name_and_namespace(validated) .name(&cm_name) - .ownerreference_from_resource(validated, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&common::labels( validated, &resolved_product_image.app_version_label_value, @@ -218,6 +215,7 @@ pub(crate) fn server_config_map( #[allow(clippy::too_many_arguments)] pub(crate) fn build_stateful_set( + validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, config: &v1alpha1::ServerConfig, resolved_product_image: &ResolvedProductImage, @@ -395,8 +393,7 @@ pub(crate) fn build_stateful_set( metadata: ObjectMetaBuilder::new() .name_and_namespace(scs) .name(object_name(&scs.name_any(), SparkConnectRole::Server)) - .ownerreference_from_resource(scs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&common::labels( scs, &resolved_product_image.app_version_label_value, diff --git a/rust/operator-binary/src/connect/service.rs b/rust/operator-binary/src/connect/service.rs index f710f419..5a484114 100644 --- a/rust/operator-binary/src/connect/service.rs +++ b/rust/operator-binary/src/connect/service.rs @@ -4,21 +4,20 @@ use stackable_operator::{ k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kube::ResourceExt, kvp::{Annotations, Labels}, + v2::builder::meta::ownerreference_from_resource, }; use super::crd::CONNECT_APP_NAME; use crate::connect::{ GRPC, HTTP, common::{self, SparkConnectRole}, + controller::validate::ValidatedSparkConnectServer, crd::{CONNECT_GRPC_PORT, CONNECT_UI_PORT, v1alpha1}, }; #[derive(Snafu, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { source: builder::meta::Error }, - #[snafu(display("failed to build Labels"))] LabelBuild { source: stackable_operator::kvp::LabelError, @@ -31,6 +30,7 @@ pub enum Error { // This is the headless driver service used for the internal // communication with the executors as recommended by the Spark docs. pub(crate) fn build_headless_service( + validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, app_version_label: &str, ) -> Result { @@ -49,8 +49,7 @@ pub(crate) fn build_headless_service( metadata: ObjectMetaBuilder::new() .name_and_namespace(scs) .name(service_name) - .ownerreference_from_resource(scs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&common::labels( scs, app_version_label, @@ -86,6 +85,7 @@ pub(crate) fn build_headless_service( // This is the metrics service pub(crate) fn build_metrics_service( + validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, app_version_label: &str, ) -> Result { @@ -104,8 +104,7 @@ pub(crate) fn build_metrics_service( metadata: ObjectMetaBuilder::new() .name_and_namespace(scs) .name(service_name) - .ownerreference_from_resource(scs, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&common::labels( scs, app_version_label, From d23b22d68ceae7d6495d0b7323d90f9a3c7936f6 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 10:43:42 +0200 Subject: [PATCH 30/50] connect: use v2 kvp::label for recommended labels and selectors --- .../operator-binary/src/connect/controller.rs | 20 +---- .../src/connect/controller/validate.rs | 85 +++++++++++++++++-- rust/operator-binary/src/connect/executor.rs | 22 +---- rust/operator-binary/src/connect/server.rs | 45 ++-------- rust/operator-binary/src/connect/service.rs | 56 +++--------- 5 files changed, 106 insertions(+), 122 deletions(-) diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index 904efc86..f7c7c9e9 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -49,9 +49,6 @@ pub enum Error { #[snafu(display("failed to build connect server properties"))] ServerProperties { source: server::Error }, - #[snafu(display("failed to build spark connect service"))] - BuildService { source: service::Error }, - #[snafu(display("failed to build spark connect executor config map for {name}"))] BuildExecutorConfigMap { source: executor::Error, @@ -219,12 +216,7 @@ pub async fn reconcile( .context(ApplyRoleBindingSnafu)?; // Headless service used by executors connect back to the driver - let headless_service = service::build_headless_service( - &validated, - scs, - &resolved_product_image.app_version_label_value, - ) - .context(BuildServiceSnafu)?; + let headless_service = service::build_headless_service(&validated, scs); let applied_headless_service = cluster_resources .add(client, headless_service.clone()) @@ -232,12 +224,7 @@ pub async fn reconcile( .context(ApplyServiceSnafu)?; // Metrics service used for scraping - let metrics_service = service::build_metrics_service( - &validated, - scs, - &resolved_product_image.app_version_label_value, - ) - .context(BuildServiceSnafu)?; + let metrics_service = service::build_metrics_service(&validated, scs); cluster_resources .add(client, metrics_service.clone()) @@ -275,7 +262,6 @@ pub async fn reconcile( let executor_config_map = executor::executor_config_map( &validated, executor_config, - resolved_product_image, executor_config_overrides.as_ref(), ) .context(BuildExecutorConfigMapSnafu { @@ -290,6 +276,7 @@ pub async fn reconcile( let executor_pod_template = serde_yaml::to_string( &executor::executor_pod_template( + &validated, scs, executor_config, resolved_product_image, @@ -312,7 +299,6 @@ pub async fn reconcile( let server_config_map = server::server_config_map( &validated, server_config, - resolved_product_image, &spark_props, &executor_pod_template, server_config_overrides.as_ref(), diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index e0c643b9..6b3d62d8 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -3,7 +3,7 @@ //! Resolves the product image and the server/executor configs. //! Does not touch the Kubernetes API. -use std::borrow::Cow; +use std::{borrow::Cow, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ @@ -11,23 +11,32 @@ use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, + kvp::Labels, v2::{ - HasName, HasUid, + HasName, HasUid, NameIsValidLabelValue, controller_utils::{get_cluster_name, get_namespace, get_uid}, + kvp::label::{recommended_labels, role_group_selector, role_selector}, types::{ kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + operator::{ + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, + RoleGroupName, RoleName, + }, }, }, }; use crate::{ connect::{ + common::SparkConnectRole, controller::dereference::DereferencedSparkConnectServer, - crd::{self, v1alpha1}, + crd::{ + self, CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, CONNECT_EXECUTOR_ROLE_NAME, + CONNECT_SERVER_ROLE_NAME, DEFAULT_SPARK_CONNECT_GROUP_NAME, v1alpha1, + }, s3::ResolvedS3, }, - crd::constants::CONTAINER_IMAGE_BASE_NAME, + crd::constants::{CONTAINER_IMAGE_BASE_NAME, OPERATOR_NAME}, }; #[derive(Snafu, Debug)] @@ -72,6 +81,72 @@ pub struct ValidatedSparkConnectServer { pub executor_config: v1alpha1::ExecutorConfig, } +impl ValidatedSparkConnectServer { + /// Recommended labels for a resource of the given role. + pub(crate) fn recommended_labels(&self, role: SparkConnectRole) -> Labels { + // `app_version_label_value` is constructed to be a valid label value, so it is also a + // valid `ProductVersion`. + let product_version = + ProductVersion::from_str(&self.resolved_product_image.app_version_label_value) + .expect("the app version label value is a valid product version"); + let role_group = RoleGroupName::from_str(DEFAULT_SPARK_CONNECT_GROUP_NAME) + .expect("DEFAULT_SPARK_CONNECT_GROUP_NAME is a valid role group name"); + recommended_labels( + self, + &product_name(), + &product_version, + &operator_name(), + &controller_name(), + &role_name(role), + &role_group, + ) + } + + /// Selector labels matching the pods of the given role. + pub(crate) fn role_selector(&self, role: SparkConnectRole) -> Labels { + role_selector(self, &product_name(), &role_name(role)) + } + + /// Selector labels matching the pods of the given role's (single) role group. + pub(crate) fn role_group_selector(&self, role: SparkConnectRole) -> Labels { + let role_group = RoleGroupName::from_str(DEFAULT_SPARK_CONNECT_GROUP_NAME) + .expect("DEFAULT_SPARK_CONNECT_GROUP_NAME is a valid role group name"); + role_group_selector(self, &product_name(), &role_name(role), &role_group) + } +} + +/// The product name (`spark-connect`) as a type-safe label value. +pub(crate) fn product_name() -> ProductName { + ProductName::from_str(CONNECT_APP_NAME).expect("CONNECT_APP_NAME is a valid product name") +} + +/// The operator name as a type-safe label value. +pub(crate) fn operator_name() -> OperatorName { + OperatorName::from_str(OPERATOR_NAME).expect("the operator name is a valid label value") +} + +/// The controller name as a type-safe label value. +pub(crate) fn controller_name() -> ControllerName { + ControllerName::from_str(CONNECT_CONTROLLER_NAME) + .expect("the controller name is a valid label value") +} + +/// The role name for the given Spark Connect role as a type-safe label value. +fn role_name(role: SparkConnectRole) -> RoleName { + match role { + SparkConnectRole::Server => RoleName::from_str(CONNECT_SERVER_ROLE_NAME) + .expect("CONNECT_SERVER_ROLE_NAME is a valid role name"), + SparkConnectRole::Executor => RoleName::from_str(CONNECT_EXECUTOR_ROLE_NAME) + .expect("CONNECT_EXECUTOR_ROLE_NAME is a valid role name"), + } +} + +impl NameIsValidLabelValue for ValidatedSparkConnectServer { + fn to_label_value(&self) -> String { + self.name.to_label_value() + } +} + impl HasName for ValidatedSparkConnectServer { fn to_name(&self) -> String { String::from(&self.name) diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index 3a29d598..8265e04a 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -41,9 +41,6 @@ use crate::{ #[derive(Snafu, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("failed to build metadata for spark connect executor pod template"))] - PodTemplateMetadataBuild { source: builder::meta::Error }, - #[snafu(display("invalid connect container name"))] InvalidContainerName { source: builder::pod::container::Error, @@ -66,9 +63,6 @@ pub enum Error { #[snafu(display("executor metrics properties for spark connect {name}",))] MetricsProperties { source: common::Error, name: String }, - #[snafu(display("failed build connect executor config map metadata"))] - ConfigMapMetadataBuild { source: builder::meta::Error }, - #[snafu(display( "failed to add the logging configuration to connect executor config map [{cm_name}]" ))] @@ -104,6 +98,7 @@ pub enum Error { // #[allow(clippy::result_large_err)] pub fn executor_pod_template( + validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, config: &v1alpha1::ExecutorConfig, resolved_product_image: &ResolvedProductImage, @@ -134,12 +129,7 @@ pub fn executor_pod_template( .context(AddVolumeMountSnafu)?; let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&common::labels( - scs, - &resolved_product_image.app_version_label_value, - &SparkConnectRole::Executor.to_string(), - )) - .context(PodTemplateMetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Executor)) .build(); let mut template = PodBuilder::new(); @@ -335,7 +325,6 @@ fn executor_jvm_args( pub(crate) fn executor_config_map( validated: &ValidatedSparkConnectServer, config: &v1alpha1::ExecutorConfig, - resolved_product_image: &ResolvedProductImage, config_overrides: Option<&v1alpha1::ConfigOverrides>, ) -> Result { let cm_name = object_name(&validated.name_any(), SparkConnectRole::Executor); @@ -365,12 +354,7 @@ pub(crate) fn executor_config_map( .name_and_namespace(validated) .name(&cm_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&common::labels( - validated, - &resolved_product_image.app_version_label_value, - &SparkConnectRole::Executor.to_string(), - )) - .context(ConfigMapMetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Executor)) .build(), ) .add_data(JVM_SECURITY_PROPERTIES_FILE, jvm_sec_props) diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 0dd34332..b9519298 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -31,13 +31,12 @@ use stackable_operator::{ apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, kube::{ResourceExt, runtime::reflector::ObjectRef}, - kvp::{Label, Labels}, + kvp::Label, product_logging::framework::{LoggingError, calculate_log_volume_size_limit, vector_container}, role_utils::RoleGroupRef, v2::builder::meta::ownerreference_from_resource, }; -use super::crd::CONNECT_APP_NAME; use crate::{ connect::{ GRPC, HTTP, @@ -112,9 +111,6 @@ pub enum Error { source: stackable_operator::kvp::LabelError, }, - #[snafu(display("failed to build Metadata"))] - MetadataBuild { source: builder::meta::Error }, - #[snafu(display("failed to add needed volume"))] AddVolume { source: builder::pod::Error }, @@ -148,7 +144,6 @@ pub enum Error { pub(crate) fn server_config_map( validated: &ValidatedSparkConnectServer, config: &v1alpha1::ServerConfig, - resolved_product_image: &ResolvedProductImage, spark_properties: &str, executor_pod_template_spec: &str, config_overrides: Option<&v1alpha1::ConfigOverrides>, @@ -183,12 +178,7 @@ pub(crate) fn server_config_map( .name_and_namespace(validated) .name(&cm_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&common::labels( - validated, - &resolved_product_image.app_version_label_value, - &SparkConnectRole::Server.to_string(), - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Server)) .build(), ) .add_data(SPARK_DEFAULTS_FILE_NAME, spark_properties) @@ -225,19 +215,10 @@ pub(crate) fn build_stateful_set( args: Vec, resolved_s3: &s3::ResolvedS3, ) -> Result { - let server_role = SparkConnectRole::Server.to_string(); - let recommended_object_labels = common::labels( - scs, - &resolved_product_image.app_version_label_value, - &server_role, - ); - - let recommended_labels = - Labels::recommended(&recommended_object_labels).context(LabelBuildSnafu)?; + let recommended_labels = validated.recommended_labels(SparkConnectRole::Server); let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? + .with_labels(recommended_labels.clone()) .with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?) .build(); @@ -394,12 +375,7 @@ pub(crate) fn build_stateful_set( .name_and_namespace(scs) .name(object_name(&scs.name_any(), SparkConnectRole::Server)) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&common::labels( - scs, - &resolved_product_image.app_version_label_value, - &SparkConnectRole::Server.to_string(), - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Server)) .build(), spec: Some(StatefulSetSpec { template: pod_template, @@ -407,14 +383,9 @@ pub(crate) fn build_stateful_set( volume_claim_templates, selector: LabelSelector { match_labels: Some( - Labels::role_group_selector( - scs, - CONNECT_APP_NAME, - &SparkConnectRole::Server.to_string(), - DEFAULT_SPARK_CONNECT_GROUP_NAME, - ) - .context(LabelBuildSnafu)? - .into(), + validated + .role_group_selector(SparkConnectRole::Server) + .into(), ), ..LabelSelector::default() }, diff --git a/rust/operator-binary/src/connect/service.rs b/rust/operator-binary/src/connect/service.rs index 5a484114..c423330a 100644 --- a/rust/operator-binary/src/connect/service.rs +++ b/rust/operator-binary/src/connect/service.rs @@ -1,61 +1,38 @@ -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{self, meta::ObjectMetaBuilder}, + builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kube::ResourceExt, kvp::{Annotations, Labels}, v2::builder::meta::ownerreference_from_resource, }; -use super::crd::CONNECT_APP_NAME; use crate::connect::{ GRPC, HTTP, - common::{self, SparkConnectRole}, + common::SparkConnectRole, controller::validate::ValidatedSparkConnectServer, crd::{CONNECT_GRPC_PORT, CONNECT_UI_PORT, v1alpha1}, }; -#[derive(Snafu, Debug)] -#[allow(clippy::enum_variant_names)] -pub enum Error { - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { source: builder::meta::Error }, -} - // This is the headless driver service used for the internal // communication with the executors as recommended by the Spark docs. pub(crate) fn build_headless_service( validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, - app_version_label: &str, -) -> Result { +) -> Service { let service_name = format!( "{cluster}-{role}-headless", cluster = scs.name_any(), role = SparkConnectRole::Server ); - let selector = - Labels::role_selector(scs, CONNECT_APP_NAME, &SparkConnectRole::Server.to_string()) - .context(LabelBuildSnafu)? - .into(); + let selector = validated.role_selector(SparkConnectRole::Server).into(); - Ok(Service { + Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(scs) .name(service_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&common::labels( - scs, - app_version_label, - &SparkConnectRole::Server.to_string(), - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Server)) .build(), spec: Some(ServiceSpec { type_: Some("ClusterIP".to_owned()), @@ -80,37 +57,28 @@ pub(crate) fn build_headless_service( ..ServiceSpec::default() }), status: None, - }) + } } // This is the metrics service pub(crate) fn build_metrics_service( validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, - app_version_label: &str, -) -> Result { +) -> Service { let service_name = format!( "{cluster}-{role}-metrics", cluster = scs.name_any(), role = SparkConnectRole::Server ); - let selector = - Labels::role_selector(scs, CONNECT_APP_NAME, &SparkConnectRole::Server.to_string()) - .context(LabelBuildSnafu)? - .into(); + let selector = validated.role_selector(SparkConnectRole::Server).into(); - Ok(Service { + Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(scs) .name(service_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&common::labels( - scs, - app_version_label, - &SparkConnectRole::Server.to_string(), - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels(SparkConnectRole::Server)) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), @@ -126,7 +94,7 @@ pub(crate) fn build_metrics_service( ..ServiceSpec::default() }), status: None, - }) + } } fn metrics_ports() -> Vec { From 06b202d69483569fe5a76a45faca82b080286b49 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 10:55:52 +0200 Subject: [PATCH 31/50] connect: build the server Vector sidecar via operator-rs v2 (validate-time ValidatedLogging) --- .../src/connect/controller/validate.rs | 82 +++++++++++++++++- rust/operator-binary/src/connect/server.rs | 83 ++++++++++--------- 2 files changed, 123 insertions(+), 42 deletions(-) diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index 6b3d62d8..10de05cb 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -5,19 +5,23 @@ use std::{borrow::Cow, str::FromStr}; -use snafu::{ResultExt, Snafu}; +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, kvp::Labels, + product_logging::spec::Logging, v2::{ HasName, HasUid, NameIsValidLabelValue, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::{recommended_labels, role_group_selector, role_selector}, + product_logging::framework::{ + VectorContainerLogConfig, validate_logging_configuration_for_container, + }, types::{ - kubernetes::{NamespaceName, Uid}, + kubernetes::{ConfigMapName, NamespaceName, Uid}, operator::{ ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleGroupName, RoleName, @@ -32,7 +36,8 @@ use crate::{ controller::dereference::DereferencedSparkConnectServer, crd::{ self, CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, CONNECT_EXECUTOR_ROLE_NAME, - CONNECT_SERVER_ROLE_NAME, DEFAULT_SPARK_CONNECT_GROUP_NAME, v1alpha1, + CONNECT_SERVER_ROLE_NAME, DEFAULT_SPARK_CONNECT_GROUP_NAME, SparkConnectContainer, + v1alpha1, }, s3::ResolvedS3, }, @@ -66,10 +71,66 @@ pub enum Error { ResolveUid { source: stackable_operator::v2::controller_utils::Error, }, + + #[snafu(display("failed to validate the logging configuration"))] + ValidateLoggingConfig { + source: stackable_operator::v2::product_logging::framework::Error, + }, + + #[snafu(display( + "the Vector aggregator discovery ConfigMap name must be set when the Vector agent is enabled" + ))] + MissingVectorAggregatorConfigMapName, + + #[snafu(display("invalid Vector aggregator discovery ConfigMap name"))] + ParseVectorAggregatorConfigMapName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, +} + +/// Validates the logging configuration for the (optional) Vector container. +/// +/// `vector_aggregator_config_map_name` is the discovery ConfigMap name of the Vector aggregator; +/// it is required (and validated) only when the Vector agent is enabled. +fn validate_logging( + logging: &Logging, + vector_aggregator_config_map_name: &Option, +) -> Result { + let vector_container = if logging.enable_vector_agent { + let vector_aggregator_config_map_name = vector_aggregator_config_map_name + .clone() + .context(MissingVectorAggregatorConfigMapNameSnafu)?; + Some(VectorContainerLogConfig { + log_config: validate_logging_configuration_for_container( + logging, + &SparkConnectContainer::Vector, + ) + .context(ValidateLoggingConfigSnafu)?, + vector_aggregator_config_map_name, + }) + } else { + None + }; + + Ok(ValidatedLogging { + vector_container, + enable_vector_agent: logging.enable_vector_agent, + }) } type Result = std::result::Result; +/// Validated logging configuration for the (optional) Vector container. +/// +/// Produced up-front by [`validate_logging`] so that an +/// invalid custom log ConfigMap name or a missing Vector aggregator discovery ConfigMap name fails +/// reconciliation during validation rather than at resource-build time. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValidatedLogging { + pub vector_container: Option, + pub enable_vector_agent: bool, +} + pub struct ValidatedSparkConnectServer { metadata: ObjectMeta, pub name: ClusterName, @@ -78,6 +139,7 @@ pub struct ValidatedSparkConnectServer { pub resolved_s3: ResolvedS3, pub resolved_product_image: ResolvedProductImage, pub server_config: v1alpha1::ServerConfig, + pub server_logging: ValidatedLogging, pub executor_config: v1alpha1::ExecutorConfig, } @@ -209,6 +271,19 @@ pub fn validate( let namespace = get_namespace(scs).context(ResolveNamespaceSnafu)?; let uid = get_uid(scs).context(ResolveUidSnafu)?; + // The Vector aggregator discovery ConfigMap name (validated here so an invalid name fails + // up-front). It is only required when the Vector agent is enabled for the server. + let vector_aggregator_config_map_name = scs + .spec + .vector_aggregator_config_map_name + .as_deref() + .map(ConfigMapName::from_str) + .transpose() + .context(ParseVectorAggregatorConfigMapNameSnafu)?; + + let server_logging = + validate_logging(&server_config.logging, &vector_aggregator_config_map_name)?; + Ok(ValidatedSparkConnectServer { metadata: scs.meta().clone(), name, @@ -217,6 +292,7 @@ pub fn validate( resolved_s3: dereferenced.resolved_s3, resolved_product_image, server_config, + server_logging, executor_config, }) } diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index b9519298..e6a0d8a6 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -1,4 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap}, + str::FromStr, +}; use indoc::formatdoc; use snafu::{OptionExt, ResultExt, Snafu}; @@ -10,7 +13,6 @@ use stackable_operator::{ pod::{ PodBuilder, container::ContainerBuilder, - resources::ResourceRequirementsBuilder, volume::{ ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, ListenerReference, VolumeBuilder, @@ -32,19 +34,34 @@ use stackable_operator::{ }, kube::{ResourceExt, runtime::reflector::ObjectRef}, kvp::Label, - product_logging::framework::{LoggingError, calculate_log_volume_size_limit, vector_container}, + product_logging::framework::calculate_log_volume_size_limit, role_utils::RoleGroupRef, - v2::builder::meta::ownerreference_from_resource, + v2::{ + builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, + product_logging::framework::vector_container, + role_group_utils::ResourceNames, + types::{ + kubernetes::{ContainerName, VolumeName}, + operator::{RoleGroupName, RoleName}, + }, + }, }; +stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); +// Typed volume names required by the v2 `vector_container`; values match the `&str` volume-mount +// name constants (`VOLUME_MOUNT_NAME_CONFIG`/`VOLUME_MOUNT_NAME_LOG`) used elsewhere to build the +// same volumes. +stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); +stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); + use crate::{ connect::{ GRPC, HTTP, common::{self, SparkConnectRole, object_name}, controller::validate::ValidatedSparkConnectServer, crd::{ - CONNECT_GRPC_PORT, CONNECT_UI_PORT, DEFAULT_SPARK_CONNECT_GROUP_NAME, - SparkConnectContainer, v1alpha1, + CONNECT_GRPC_PORT, CONNECT_SERVER_ROLE_NAME, CONNECT_UI_PORT, + DEFAULT_SPARK_CONNECT_GROUP_NAME, SparkConnectContainer, v1alpha1, }, s3, }, @@ -74,9 +91,6 @@ pub enum Error { source: ListenerOperatorVolumeSourceBuilderError, }, - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - #[snafu(display("spark connect object has no namespace"))] ObjectHasNoNamespace, @@ -97,9 +111,6 @@ pub enum Error { cm_name: String, }, - #[snafu(display("failed to configure logging"))] - ConfigureLogging { source: LoggingError }, - #[snafu(display("server jvm security properties for spark connect {name}",))] ServerJvmSecurityProperties { source: common::Error, name: String }, @@ -304,33 +315,27 @@ pub(crate) fn build_stateful_set( pb.add_container(container.build()); - if config.logging.enable_vector_agent { - match scs.spec.vector_aggregator_config_map_name.to_owned() { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - vector_container( - resolved_product_image, - VOLUME_MOUNT_NAME_CONFIG, - VOLUME_MOUNT_NAME_LOG, - config - .logging - .containers - .get(&SparkConnectContainer::Vector), - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("500m") - .with_memory_request("128Mi") - .with_memory_limit("128Mi") - .build(), - &vector_aggregator_config_map_name, - ) - .context(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } + if let Some(vector_log_config) = &validated.server_logging.vector_container { + // These resource names are constructed SOLELY to provide the Vector sidecar with its + // `CLUSTER_NAME`/`ROLE_NAME`/`ROLE_GROUP_NAME` log-metadata env vars. They do NOT affect + // resource naming: Spark Connect keeps its `{cluster}-{role}` resource names. + let vector_resource_names = ResourceNames { + cluster_name: validated.name.clone(), + role_name: RoleName::from_str(CONNECT_SERVER_ROLE_NAME) + .expect("CONNECT_SERVER_ROLE_NAME is a valid role name"), + role_group_name: RoleGroupName::from_str(DEFAULT_SPARK_CONNECT_GROUP_NAME) + .expect("DEFAULT_SPARK_CONNECT_GROUP_NAME is a valid role group name"), + }; + + pb.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + resolved_product_image, + vector_log_config, + &vector_resource_names, + &VOLUME_MOUNT_NAME_CONFIG_TYPED, + &VOLUME_MOUNT_NAME_LOG_TYPED, + EnvVarSet::new(), + )); } // Add listener volume From cd7949c3937c47ded35a1ed20e8081c0e7604378 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 11:12:53 +0200 Subject: [PATCH 32/50] connect: use v2 cluster_resources_new and Port type --- .../operator-binary/src/connect/controller.rs | 27 +++++++++---------- rust/operator-binary/src/connect/crd.rs | 5 ++-- rust/operator-binary/src/connect/server.rs | 10 +++---- rust/operator-binary/src/connect/service.rs | 6 ++--- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index f7c7c9e9..cd5f2d98 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -2,10 +2,10 @@ use std::sync::Arc; use snafu::{ResultExt, Snafu}; use stackable_operator::{ - cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, + cluster_resources::ClusterResourceApplyStrategy, commons::rbac::build_rbac_resources, kube::{ - Resource, ResourceExt, + ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, @@ -15,10 +15,11 @@ use stackable_operator::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, + v2::cluster_resources::cluster_resources_new, }; use strum::{EnumDiscriminants, IntoStaticStr}; -use super::crd::{CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, v1alpha1}; +use super::crd::{CONNECT_APP_NAME, v1alpha1}; use crate::{ Ctx, connect::{common, crd::SparkConnectServerStatus, executor, server, service}, @@ -99,11 +100,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to create cluster resources"))] - CreateClusterResources { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to delete orphaned resources"))] DeleteOrphanedResources { source: stackable_operator::cluster_resources::Error, @@ -186,15 +182,16 @@ pub async fn reconcile( let server_role_config = &scs.spec.server.role_config; - let mut cluster_resources = ClusterResources::new( - CONNECT_APP_NAME, - OPERATOR_NAME, - CONNECT_CONTROLLER_NAME, - &validated.object_ref(&()), + let mut cluster_resources = cluster_resources_new( + &validate::product_name(), + &validate::operator_name(), + &validate::controller_name(), + &validated.name, + &validated.namespace, + &validated.uid, ClusterResourceApplyStrategy::from(&scs.spec.cluster_operation), &scs.spec.object_overrides, - ) - .context(CreateClusterResourcesSnafu)?; + ); // Use a dedicated service account for connect server pods. let (service_account, role_binding) = build_rbac_resources( diff --git a/rust/operator-binary/src/connect/crd.rs b/rust/operator-binary/src/connect/crd.rs index 77db139c..84b86476 100644 --- a/rust/operator-binary/src/connect/crd.rs +++ b/rust/operator-binary/src/connect/crd.rs @@ -31,6 +31,7 @@ use stackable_operator::{ schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, + v2::types::common::Port, versioned::versioned, }; use strum::{Display, EnumIter}; @@ -45,8 +46,8 @@ pub const CONNECT_FULL_CONTROLLER_NAME: &str = concatcp!( ); pub const CONNECT_SERVER_ROLE_NAME: &str = "server"; pub const CONNECT_EXECUTOR_ROLE_NAME: &str = "executor"; -pub const CONNECT_GRPC_PORT: i32 = 15002; -pub const CONNECT_UI_PORT: i32 = 4040; +pub const CONNECT_GRPC_PORT: Port = Port(15002); +pub const CONNECT_UI_PORT: Port = Port(4040); pub const DEFAULT_SPARK_CONNECT_GROUP_NAME: &str = "default"; diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index e6a0d8a6..48dbefbb 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -285,8 +285,8 @@ pub(crate) fn build_stateful_set( "-c".to_string(), ]) .args(args) - .add_container_port(GRPC, CONNECT_GRPC_PORT) - .add_container_port(HTTP, CONNECT_UI_PORT) + .add_container_port(GRPC, CONNECT_GRPC_PORT.into()) + .add_container_port(HTTP, CONNECT_UI_PORT.into()) .add_env_vars(container_env) .add_volume_mount(VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_PATH_CONFIG) .context(AddVolumeMountSnafu)? @@ -553,7 +553,7 @@ fn server_jvm_args( fn probe() -> Probe { Probe { http_get: Some(HTTPGetAction { - port: IntOrString::Int(CONNECT_UI_PORT), + port: IntOrString::Int(CONNECT_UI_PORT.into()), scheme: Some("HTTP".to_string()), path: Some("/metrics/prometheus".to_string()), ..Default::default() @@ -592,12 +592,12 @@ pub(crate) fn build_listener( let listener_ports = [ listener::v1alpha1::ListenerPort { name: GRPC.to_string(), - port: CONNECT_GRPC_PORT, + port: CONNECT_GRPC_PORT.into(), protocol: Some("TCP".to_string()), }, listener::v1alpha1::ListenerPort { name: HTTP.to_string(), - port: CONNECT_UI_PORT, + port: CONNECT_UI_PORT.into(), protocol: Some("TCP".to_string()), }, ]; diff --git a/rust/operator-binary/src/connect/service.rs b/rust/operator-binary/src/connect/service.rs index c423330a..f058183f 100644 --- a/rust/operator-binary/src/connect/service.rs +++ b/rust/operator-binary/src/connect/service.rs @@ -40,12 +40,12 @@ pub(crate) fn build_headless_service( ports: Some(vec![ ServicePort { name: Some(String::from(GRPC)), - port: CONNECT_GRPC_PORT, + port: CONNECT_GRPC_PORT.into(), ..ServicePort::default() }, ServicePort { name: Some(String::from(HTTP)), - port: CONNECT_UI_PORT, + port: CONNECT_UI_PORT.into(), ..ServicePort::default() }, ]), @@ -100,7 +100,7 @@ pub(crate) fn build_metrics_service( fn metrics_ports() -> Vec { vec![ServicePort { name: Some("metrics".to_string()), - port: CONNECT_UI_PORT, + port: CONNECT_UI_PORT.into(), protocol: Some("TCP".to_string()), ..ServicePort::default() }] From 6028731195ad05c1cca7a87b5a876eed7ef63476 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 11:41:59 +0200 Subject: [PATCH 33/50] job: use v2 ownerreference_from_resource --- .../src/spark_k8s_controller.rs | 36 ++++++++----------- .../src/spark_k8s_controller/validate.rs | 34 ++++++++++++++++-- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 02fac4a2..116d0a3b 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -43,7 +43,10 @@ use stackable_operator::{ }, role_utils::RoleGroupRef, shared::time::Duration, - v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, + v2::{ + builder::meta::ownerreference_from_resource, + config_file_writer::{PropertiesWriterError, to_java_properties_string}, + }, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -74,11 +77,6 @@ pub enum Error { #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to apply role ServiceAccount"))] ApplyServiceAccount { source: stackable_operator::client::Error, @@ -215,7 +213,7 @@ pub async fn reconcile( tracing::debug!("reconciling spark application [{spark_application:?}]"); let (serviceaccount, rolebinding) = - build_spark_role_serviceaccount(spark_application, resolved_product_image)?; + build_spark_role_serviceaccount(&validated, resolved_product_image)?; client .apply_patch(SPARK_CONTROLLER_NAME, &serviceaccount, &serviceaccount) .await @@ -543,8 +541,7 @@ fn pod_template( omb.name(&container_name) // this reference is not pointing to a controller but only provides a UID that can used to clean up resources // cleanly (specifically driver pods and related config maps) when the spark application is deleted. - .ownerreference_from_resource(validated, None, None) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, None)) .with_recommended_labels( &spark_application .build_recommended_labels(&spark_image.app_version_label_value, &container_name), @@ -691,8 +688,7 @@ fn pod_template_config_map( ObjectMetaBuilder::new() .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference_from_resource(validated, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&spark_application.build_recommended_labels( &spark_image.app_version_label_value, "pod-templates", @@ -749,8 +745,7 @@ fn submit_job_config_map( ObjectMetaBuilder::new() .namespace(validated.namespace.clone()) .name(&cm_name) - .ownerreference_from_resource(validated, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels( &spark_application .build_recommended_labels(&spark_image.app_version_label_value, "spark-submit"), @@ -905,8 +900,7 @@ fn spark_job( metadata: ObjectMetaBuilder::new() .name(validated.name.to_string()) .namespace(validated.namespace.clone()) - .ownerreference_from_resource(validated, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels( &spark_application .build_recommended_labels(&spark_image.app_version_label_value, "spark-job"), @@ -930,18 +924,17 @@ fn spark_job( /// Both objects have an owner reference to the SparkApplication, as well as the same name as the app. /// They are deleted when the job is deleted. fn build_spark_role_serviceaccount( - spark_app: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, spark_image: &ResolvedProductImage, ) -> Result<(ServiceAccount, RoleBinding)> { - // TODO (@NickLarsenNZ): Explain this unwrap. Either convert to expect, or gracefully handle the error. - let sa_name = spark_app.metadata.name.as_ref().unwrap().to_string(); + let spark_app = &validated.spark_application; + let sa_name = validated.name.to_string(); let sa = ServiceAccount { metadata: ObjectMetaBuilder::new() .name_and_namespace(spark_app) .name(&sa_name) - .ownerreference_from_resource(spark_app, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels(&spark_app.build_recommended_labels( &spark_image.app_version_label_value, "service-account", @@ -955,8 +948,7 @@ fn build_spark_role_serviceaccount( metadata: ObjectMetaBuilder::new() .name_and_namespace(spark_app) .name(binding_name) - .ownerreference_from_resource(spark_app, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) .with_recommended_labels( &spark_app .build_recommended_labels(&spark_image.app_version_label_value, "role-binding"), diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index 41579be4..a9db9758 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -16,8 +16,12 @@ use stackable_operator::{ k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, v2::{ - controller_utils::{get_cluster_name, get_namespace}, - types::{kubernetes::NamespaceName, operator::ClusterName}, + HasName, HasUid, NameIsValidLabelValue, + controller_utils::{get_cluster_name, get_namespace, get_uid}, + types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, }, }; @@ -43,6 +47,11 @@ pub enum Error { source: stackable_operator::v2::controller_utils::Error, }, + #[snafu(display("failed to resolve uid"))] + ResolveUid { + source: stackable_operator::v2::controller_utils::Error, + }, + #[snafu(display("S3 TLS with verification disabled is not supported ({context})"))] S3TlsNoVerificationNotSupported { context: String }, } @@ -56,6 +65,7 @@ pub struct ValidatedSparkApplication { metadata: ObjectMeta, pub name: ClusterName, pub namespace: NamespaceName, + pub uid: Uid, // Still carried in full because `reconcile` builds the submit/driver pod from the whole spec. pub spark_application: v1alpha1::SparkApplication, pub resolved_template_refs: Vec, @@ -64,6 +74,24 @@ pub struct ValidatedSparkApplication { pub resolved_product_image: ResolvedProductImage, } +impl NameIsValidLabelValue for ValidatedSparkApplication { + fn to_label_value(&self) -> String { + self.name.to_label_value() + } +} + +impl HasName for ValidatedSparkApplication { + fn to_name(&self) -> String { + String::from(&self.name) + } +} + +impl HasUid for ValidatedSparkApplication { + fn to_uid(&self) -> Uid { + self.uid.clone() + } +} + impl Resource for ValidatedSparkApplication { type DynamicType = (); type Scope = ::Scope; @@ -119,12 +147,14 @@ pub fn validate( get_cluster_name(&dereferenced.spark_application).context(ResolveClusterNameSnafu)?; let namespace = get_namespace(&dereferenced.spark_application).context(ResolveNamespaceSnafu)?; + let uid = get_uid(&dereferenced.spark_application).context(ResolveUidSnafu)?; let metadata = dereferenced.spark_application.meta().clone(); Ok(ValidatedSparkApplication { metadata, name, namespace, + uid, spark_application: dereferenced.spark_application, resolved_template_refs: dereferenced.resolved_template_refs, s3_connection: dereferenced.s3_connection, From 33de26666565663e5c98da17299624ecb53eec0c Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 11:47:04 +0200 Subject: [PATCH 34/50] fix: propagate init_containers error instead of panicking --- rust/operator-binary/src/spark_k8s_controller.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 116d0a3b..48fd718e 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -584,9 +584,7 @@ fn pod_template( s3conn, logdir, spark_image, - ) - // TODO (@NickLarsenNZ): Explain this unwrap. Either convert to expect, or gracefully handle the error. - .unwrap(); + )?; for init_container in init_containers { pb.add_init_container(init_container.clone()); From b4a7eecb39f69d0941f3da90e0145f3e0d828fc8 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 11:59:36 +0200 Subject: [PATCH 35/50] job: use v2 vector_container for the logging sidecar --- .../src/spark_k8s_controller.rs | 96 ++++++++++++------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 48fd718e..5d05dcdf 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, sync::Arc, vec}; +use std::{collections::BTreeMap, str::FromStr, sync::Arc, vec}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -32,10 +32,7 @@ use stackable_operator::{ kvp::Label, logging::controller::ReconcilerError, product_logging::{ - framework::{ - LoggingError, capture_shell_output, create_vector_shutdown_file_command, - vector_container, - }, + framework::{capture_shell_output, create_vector_shutdown_file_command}, spec::{ ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, CustomContainerLogConfig, Logging, @@ -44,12 +41,27 @@ use stackable_operator::{ role_utils::RoleGroupRef, shared::time::Duration, v2::{ - builder::meta::ownerreference_from_resource, + builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, config_file_writer::{PropertiesWriterError, to_java_properties_string}, + product_logging::framework::{ + VectorContainerLogConfig, validate_logging_configuration_for_container, + vector_container, + }, + role_group_utils::ResourceNames, + types::{ + kubernetes::{ConfigMapName, ContainerName, VolumeName}, + operator::{RoleGroupName, RoleName}, + }, }, }; use strum::{EnumDiscriminants, IntoStaticStr}; +stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); +// Typed volume names required by the v2 `vector_container`; values match the existing `&str` +// volume-mount consts VOLUME_MOUNT_NAME_CONFIG ("config") / VOLUME_MOUNT_NAME_LOG ("log"). +stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); +stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); + use crate::{ Ctx, crd::{ @@ -114,15 +126,22 @@ pub enum Error { #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] VectorAggregatorConfigMapMissing, + #[snafu(display("failed to validate the logging configuration"))] + ValidateLoggingConfig { + source: stackable_operator::v2::product_logging::framework::Error, + }, + + #[snafu(display("invalid Vector aggregator discovery ConfigMap name"))] + ParseVectorAggregatorConfigMapName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] InvalidLoggingConfig { source: product_logging::Error, cm_name: String, }, - #[snafu(display("failed to configure logging"))] - ConfigureLogging { source: LoggingError }, - #[snafu(display("failed to serialize [{JVM_SECURITY_PROPERTIES_FILE}] for {}", role))] JvmSecurityProperties { source: PropertiesWriterError, @@ -591,29 +610,42 @@ fn pod_template( } if config.logging.enable_vector_agent { - match &spark_application.spec.vector_aggregator_config_map_name { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - vector_container( - spark_image, - VOLUME_MOUNT_NAME_CONFIG, - VOLUME_MOUNT_NAME_LOG, - config.logging.containers.get(&SparkContainer::Vector), - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("500m") - .with_memory_request("128Mi") - .with_memory_limit("128Mi") - .build(), - vector_aggregator_config_map_name, - ) - .context(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } + let vector_aggregator_config_map_name = spark_application + .spec + .vector_aggregator_config_map_name + .as_ref() + .context(VectorAggregatorConfigMapMissingSnafu)?; + let vector_log_config = VectorContainerLogConfig { + log_config: validate_logging_configuration_for_container( + &config.logging, + &SparkContainer::Vector, + ) + .context(ValidateLoggingConfigSnafu)?, + vector_aggregator_config_map_name: ConfigMapName::from_str( + vector_aggregator_config_map_name, + ) + .context(ParseVectorAggregatorConfigMapNameSnafu)?, + }; + // These resource names are constructed SOLELY to provide the Vector sidecar with its + // `CLUSTER_NAME`/`ROLE_NAME`/`ROLE_GROUP_NAME` log-metadata env vars. They do NOT affect + // resource naming. A SparkApplication has no Stackable role groups, so the role group name + // is a placeholder; the role name reflects the pod's Spark role (driver/executor). + let vector_resource_names = ResourceNames { + cluster_name: validated.name.clone(), + role_name: RoleName::from_str(&role.to_string()) + .expect("a SparkApplicationRole serializes to a valid role name"), + role_group_name: RoleGroupName::from_str("default") + .expect("\"default\" is a valid role group name"), + }; + pb.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + spark_image, + &vector_log_config, + &vector_resource_names, + &VOLUME_MOUNT_NAME_CONFIG_TYPED, + &VOLUME_MOUNT_NAME_LOG_TYPED, + EnvVarSet::new(), + )); } let mut pod_template = pb.build_template(); From 87f236ea8455c2b0e49f5cd376a6c647a8e99bc5 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 12:22:04 +0200 Subject: [PATCH 36/50] job: use v2 recommended_labels --- rust/operator-binary/src/crd/mod.rs | 17 ----- .../src/spark_k8s_controller.rs | 72 +++++-------------- .../src/spark_k8s_controller/validate.rs | 58 ++++++++++++++- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index e881fd24..90973498 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -30,7 +30,6 @@ use stackable_operator::{ apimachinery::pkg::api::resource::Quantity, }, kube::{CustomResource, ResourceExt}, - kvp::ObjectLabels, memory::{BinaryMultiple, MemoryQuantity}, product_logging, role_utils::{CommonConfiguration, JavaCommonConfig, RoleGroup}, @@ -556,22 +555,6 @@ impl v1alpha1::SparkApplication { mounts } - pub fn build_recommended_labels<'a>( - &'a self, - app_version: &'a str, - role: &'a str, - ) -> ObjectLabels<'a, v1alpha1::SparkApplication> { - ObjectLabels { - owner: self, - app_name: APP_NAME, - app_version, - operator_name: OPERATOR_NAME, - controller_name: SPARK_CONTROLLER_NAME, - role, - role_group: SPARK_CONTROLLER_NAME, - } - } - pub fn build_command( &self, s3conn: &Option, diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 5d05dcdf..f747c278 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -156,11 +156,6 @@ pub enum Error { source: stackable_operator::kvp::LabelError, }, - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to create Volumes for SparkApplication"))] CreateVolumes { source: crate::crd::Error }, @@ -231,8 +226,7 @@ pub async fn reconcile( // No more mutating operations after this point (except for status). tracing::debug!("reconciling spark application [{spark_application:?}]"); - let (serviceaccount, rolebinding) = - build_spark_role_serviceaccount(&validated, resolved_product_image)?; + let (serviceaccount, rolebinding) = build_spark_role_serviceaccount(&validated)?; client .apply_patch(SPARK_CONTROLLER_NAME, &serviceaccount, &serviceaccount) .await @@ -321,8 +315,7 @@ pub async fn reconcile( .map(|job| job.config_overrides.clone()) .unwrap_or_default(); - let submit_job_config_map = - submit_job_config_map(&validated, &submit_config_overrides, resolved_product_image)?; + let submit_job_config_map = submit_job_config_map(&validated, &submit_config_overrides)?; client .apply_patch( SPARK_CONTROLLER_NAME, @@ -561,11 +554,7 @@ fn pod_template( // this reference is not pointing to a controller but only provides a UID that can used to clean up resources // cleanly (specifically driver pods and related config maps) when the spark application is deleted. .ownerreference(ownerreference_from_resource(validated, None, None)) - .with_recommended_labels( - &spark_application - .build_recommended_labels(&spark_image.app_version_label_value, &container_name), - ) - .context(MetadataBuildSnafu)?; + .with_labels(validated.recommended_labels(&container_name)); // Only the driver pod should be scraped by Prometheus // because the executor metrics are also available via /metrics/executors/prometheus/ @@ -719,11 +708,7 @@ fn pod_template_config_map( .namespace(validated.namespace.clone()) .name(&cm_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&spark_application.build_recommended_labels( - &spark_image.app_version_label_value, - "pod-templates", - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels("pod-templates")) .build(), ) .add_data( @@ -764,7 +749,6 @@ fn pod_template_config_map( fn submit_job_config_map( validated: &validate::ValidatedSparkApplication, config_overrides: &v1alpha1::ConfigOverrides, - spark_image: &ResolvedProductImage, ) -> Result { let spark_application = &validated.spark_application; let cm_name = spark_application.submit_job_config_map_name(); @@ -776,11 +760,7 @@ fn submit_job_config_map( .namespace(validated.namespace.clone()) .name(&cm_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels( - &spark_application - .build_recommended_labels(&spark_image.app_version_label_value, "spark-submit"), - ) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels("spark-submit")) .build(), ); @@ -897,11 +877,7 @@ fn spark_job( metadata: Some( ObjectMetaBuilder::new() .name("spark-submit") - .with_recommended_labels(&spark_application.build_recommended_labels( - &spark_image.app_version_label_value, - "spark-job-template", - )) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels("spark-job-template")) .build(), ), spec: Some(PodSpec { @@ -931,11 +907,7 @@ fn spark_job( .name(validated.name.to_string()) .namespace(validated.namespace.clone()) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels( - &spark_application - .build_recommended_labels(&spark_image.app_version_label_value, "spark-job"), - ) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels("spark-job")) .build(), spec: Some(JobSpec { template: pod, @@ -955,35 +927,25 @@ fn spark_job( /// They are deleted when the job is deleted. fn build_spark_role_serviceaccount( validated: &validate::ValidatedSparkApplication, - spark_image: &ResolvedProductImage, ) -> Result<(ServiceAccount, RoleBinding)> { let spark_app = &validated.spark_application; let sa_name = validated.name.to_string(); - let sa = - ServiceAccount { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(spark_app) - .name(&sa_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels(&spark_app.build_recommended_labels( - &spark_image.app_version_label_value, - "service-account", - )) - .context(MetadataBuildSnafu)? - .build(), - ..ServiceAccount::default() - }; + let sa = ServiceAccount { + metadata: ObjectMetaBuilder::new() + .name_and_namespace(spark_app) + .name(&sa_name) + .ownerreference(ownerreference_from_resource(validated, None, Some(true))) + .with_labels(validated.recommended_labels("service-account")) + .build(), + ..ServiceAccount::default() + }; let binding_name = &sa_name; let binding = RoleBinding { metadata: ObjectMetaBuilder::new() .name_and_namespace(spark_app) .name(binding_name) .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_recommended_labels( - &spark_app - .build_recommended_labels(&spark_image.app_version_label_value, "role-binding"), - ) - .context(MetadataBuildSnafu)? + .with_labels(validated.recommended_labels("role-binding")) .build(), role_ref: RoleRef { api_group: ClusterRole::GROUP.to_string(), diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index a9db9758..36ba1976 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -3,7 +3,7 @@ //! Synchronously validates the [`super::dereference::DereferencedSparkApplication`] and //! resolves the product image. Does not touch the Kubernetes API. -use std::borrow::Cow; +use std::{borrow::Cow, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ @@ -15,18 +15,27 @@ use stackable_operator::{ crd::s3, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, + kvp::Labels, v2::{ HasName, HasUid, NameIsValidLabelValue, controller_utils::{get_cluster_name, get_namespace, get_uid}, + kvp::label::recommended_labels, types::{ kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + operator::{ + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, + RoleGroupName, RoleName, + }, }, }, }; use crate::{ - crd::{constants::CONTAINER_IMAGE_BASE_NAME, logdir::ResolvedLogDir, v1alpha1}, + crd::{ + constants::{APP_NAME, CONTAINER_IMAGE_BASE_NAME, OPERATOR_NAME, SPARK_CONTROLLER_NAME}, + logdir::ResolvedLogDir, + v1alpha1, + }, spark_k8s_controller::dereference::DereferencedSparkApplication, }; @@ -121,6 +130,49 @@ impl Resource for ValidatedSparkApplication { } } +impl ValidatedSparkApplication { + /// Recommended labels for a resource fulfilling the given `role` within the SparkApplication. + /// + /// A SparkApplication has no Stackable role groups, so the role group label is fixed to the + /// controller name (preserving the previous `build_recommended_labels` behaviour). `role` is a + /// free-form component name such as "spark", "spark-submit" or "role-binding". + pub(crate) fn recommended_labels(&self, role: &str) -> Labels { + // `app_version_label_value` is constructed to be a valid label value, so it is also a + // valid `ProductVersion`. + let product_version = + ProductVersion::from_str(&self.resolved_product_image.app_version_label_value) + .expect("the app version label value is a valid product version"); + let role_name = RoleName::from_str(role).expect("the role is a valid role name"); + let role_group = RoleGroupName::from_str(SPARK_CONTROLLER_NAME) + .expect("SPARK_CONTROLLER_NAME is a valid role group name"); + recommended_labels( + self, + &product_name(), + &product_version, + &operator_name(), + &controller_name(), + &role_name, + &role_group, + ) + } +} + +/// The product name (`spark-k8s`) as a type-safe label value. +pub(crate) fn product_name() -> ProductName { + ProductName::from_str(APP_NAME).expect("APP_NAME is a valid product name") +} + +/// The operator name as a type-safe label value. +pub(crate) fn operator_name() -> OperatorName { + OperatorName::from_str(OPERATOR_NAME).expect("the operator name is a valid label value") +} + +/// The controller name as a type-safe label value. +pub(crate) fn controller_name() -> ControllerName { + ControllerName::from_str(SPARK_CONTROLLER_NAME) + .expect("the controller name is a valid label value") +} + pub fn validate( dereferenced: DereferencedSparkApplication, operator_environment: &OperatorEnvironmentOptions, From 46f6eeeba2fde0352ac95cf28af2fb7f46d7f446 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 12:37:15 +0200 Subject: [PATCH 37/50] refactor: use v2 ownerref and labels in shared build_listener --- rust/operator-binary/src/connect/common.rs | 23 +-------- .../operator-binary/src/connect/controller.rs | 6 +-- rust/operator-binary/src/connect/server.rs | 19 ++----- rust/operator-binary/src/crd/listener_ext.rs | 39 +++++---------- .../operator-binary/src/history/controller.rs | 49 +++++++------------ rust/operator-binary/src/history/mod.rs | 22 --------- 6 files changed, 36 insertions(+), 122 deletions(-) diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index 468bb16f..dd95371b 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use snafu::{ResultExt, Snafu}; use stackable_operator::{ - kvp::ObjectLabels, role_utils::{JavaCommonConfig, JvmArgumentOverrides}, v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, }; @@ -10,14 +9,10 @@ use strum::Display; use super::crd::CONNECT_EXECUTOR_ROLE_NAME; use crate::{ - connect::crd::{ - CONNECT_APP_NAME, CONNECT_CONTROLLER_NAME, CONNECT_SERVER_ROLE_NAME, - DEFAULT_SPARK_CONNECT_GROUP_NAME, - }, + connect::crd::CONNECT_SERVER_ROLE_NAME, crd::constants::{ DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, - OPERATOR_NAME, }, }; @@ -39,22 +34,6 @@ pub enum Error { MetricsProperties { source: PropertiesWriterError }, } -pub(crate) fn labels<'a, T>( - scs: &'a T, - app_version_label: &'a str, - role: &'a str, -) -> ObjectLabels<'a, T> { - ObjectLabels { - owner: scs, - app_name: CONNECT_APP_NAME, - app_version: app_version_label, - operator_name: OPERATOR_NAME, - controller_name: CONNECT_CONTROLLER_NAME, - role, - role_group: DEFAULT_SPARK_CONNECT_GROUP_NAME, - } -} - #[derive(Clone, Debug, Display)] #[strum(serialize_all = "lowercase")] pub(crate) enum SparkConnectRole { diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index cd5f2d98..526e8593 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -33,9 +33,6 @@ pub mod validate; #[strum_discriminants(derive(IntoStaticStr))] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("failed to build spark connect listener"))] - BuildListener { source: server::Error }, - #[snafu(display("failed to apply spark connect listener"))] ApplyListener { source: stackable_operator::cluster_resources::Error, @@ -312,8 +309,7 @@ pub async fn reconcile( // ======================================== // Server listener - let listener = server::build_listener(scs, server_role_config, resolved_product_image) - .context(BuildListenerSnafu)?; + let listener = server::build_listener(&validated, server_role_config); let applied_listener = cluster_resources .add(client, listener) diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 48dbefbb..a1edcf69 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -81,11 +81,6 @@ use crate::{ #[derive(Snafu, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("failed to build spark connect listener"))] - BuildListener { - source: crate::crd::listener_ext::Error, - }, - #[snafu(display("failed to build listener volume"))] BuildListenerVolume { source: ListenerOperatorVolumeSourceBuilderError, @@ -574,20 +569,17 @@ fn default_role_group_ref( } pub(crate) fn build_listener( - scs: &v1alpha1::SparkConnectServer, + validated: &ValidatedSparkConnectServer, role_config: &v1alpha1::SparkConnectServerRoleConfig, - resolved_product_image: &ResolvedProductImage, -) -> Result { +) -> listener::v1alpha1::Listener { let listener_name = format!( "{cluster}-{role}", - cluster = scs.name_any(), + cluster = validated.name_any(), role = SparkConnectRole::Server ); let listener_class = role_config.listener_class.clone(); - let role = SparkConnectRole::Server.to_string(); - let recommended_object_labels = - common::labels(scs, &resolved_product_image.app_version_label_value, &role); + let recommended_object_labels = validated.recommended_labels(SparkConnectRole::Server); let listener_ports = [ listener::v1alpha1::ListenerPort { @@ -603,11 +595,10 @@ pub(crate) fn build_listener( ]; listener_ext::build_listener( - scs, + validated, &listener_name, &listener_class, recommended_object_labels, &listener_ports, ) - .context(BuildListenerSnafu) } diff --git a/rust/operator-binary/src/crd/listener_ext.rs b/rust/operator-binary/src/crd/listener_ext.rs index 0914d9f8..905fae7e 100644 --- a/rust/operator-binary/src/crd/listener_ext.rs +++ b/rust/operator-binary/src/crd/listener_ext.rs @@ -1,40 +1,25 @@ -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::meta::ObjectMetaBuilder, crd::listener, kube::Resource, kvp::ObjectLabels, + builder::meta::ObjectMetaBuilder, + crd::listener, + kube::Resource, + kvp::Labels, + v2::{HasName, HasUid, builder::meta::ownerreference_from_resource}, }; -use strum::{EnumDiscriminants, IntoStaticStr}; - -#[derive(Snafu, Debug, EnumDiscriminants)] -#[strum_discriminants(derive(IntoStaticStr))] -#[allow(clippy::enum_variant_names)] -pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("failed to build object meta data"))] - ObjectMeta { - source: stackable_operator::builder::meta::Error, - }, -} // TODO (@NickLarsenNZ): Move this functionality to stackable-operator -pub fn build_listener>( +pub fn build_listener + HasName + HasUid>( resource: &T, listener_name: &str, listener_class: &str, - listener_labels: ObjectLabels, + listener_labels: Labels, listener_ports: &[listener::v1alpha1::ListenerPort], -) -> Result { - Ok(listener::v1alpha1::Listener { +) -> listener::v1alpha1::Listener { + listener::v1alpha1::Listener { metadata: ObjectMetaBuilder::new() .name_and_namespace(resource) .name(listener_name) - .ownerreference_from_resource(resource, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&listener_labels) - .context(ObjectMetaSnafu)? + .ownerreference(ownerreference_from_resource(resource, None, Some(true))) + .with_labels(listener_labels) .build(), spec: listener::v1alpha1::ListenerSpec { class_name: Some(listener_class.into()), @@ -42,5 +27,5 @@ pub fn build_listener>( ..listener::v1alpha1::ListenerSpec::default() }, status: None, - }) + } } diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 29b40fc5..3805f65f 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -9,7 +9,7 @@ use stackable_operator::{ pod::{PodBuilder, container::ContainerBuilder, volume::VolumeBuilder}, }, cluster_resources::ClusterResourceApplyStrategy, - commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, + commons::rbac::build_rbac_resources, crd::listener, k8s_openapi::{ DeepMerge, @@ -83,7 +83,7 @@ use crate::{ }, history::{ config::jvm::construct_history_jvm_args, controller::validate::ValidatedHistoryRoleGroup, - operations::pdb::add_pdbs, recommended_labels, service::build_rolegroup_metrics_service, + operations::pdb::add_pdbs, service::build_rolegroup_metrics_service, }, product_logging::{self}, }; @@ -100,11 +100,6 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to build spark history group listener"))] - BuildListener { - source: crate::crd::listener_ext::Error, - }, - #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, @@ -244,7 +239,6 @@ pub async fn reconcile( &shs.spec.object_overrides, ); - let resolved_product_image = &validated.resolved_product_image; let log_dir = &validated.log_dir; // Use a dedicated service account for history server pods. @@ -270,14 +264,7 @@ pub async fn reconcile( let metrics_service = build_rolegroup_metrics_service(&validated, role_group_name); - let sts = build_stateful_set( - shs, - &validated, - role_group_name, - rg, - log_dir, - &service_account, - )?; + let sts = build_stateful_set(&validated, role_group_name, rg, log_dir, &service_account)?; cluster_resources .add(client, config_map) @@ -294,11 +281,10 @@ pub async fn reconcile( } let rg_group_listener = build_group_listener( - shs, - resolved_product_image, + &validated, HISTORY_ROLE_NAME, shs.node_listener_class().to_string(), - )?; + ); cluster_resources .add(client, rg_group_listener) @@ -323,17 +309,18 @@ pub async fn reconcile( Ok(Action::await_change()) } -#[allow(clippy::result_large_err)] fn build_group_listener( - shs: &v1alpha1::SparkHistoryServer, - resolved_product_image: &ResolvedProductImage, + validated: &validate::ValidatedSparkHistoryServer, role: &str, listener_class: String, -) -> Result { - let listener_name = group_listener_name(shs, role); +) -> listener::v1alpha1::Listener { + let listener_name = group_listener_name(validated, role); - let recommended_object_labels = - recommended_labels(shs, &resolved_product_image.app_version_label_value, "none"); + // Group listeners are shared across role groups, so the role-group label is "none" (preserving + // the previous behaviour). + let recommended_object_labels = validated.recommended_labels( + &RoleGroupName::from_str("none").expect("\"none\" is a valid role group name"), + ); let listener_ports = [listener::v1alpha1::ListenerPort { name: "http".to_string(), @@ -342,17 +329,16 @@ fn build_group_listener( }]; listener_ext::build_listener( - shs, + validated, &listener_name, &listener_class, recommended_object_labels, &listener_ports, ) - .context(BuildListenerSnafu) } -fn group_listener_name(shs: &v1alpha1::SparkHistoryServer, role: &str) -> String { - format!("{cluster}-{role}", cluster = shs.name_any()) +fn group_listener_name(validated: &validate::ValidatedSparkHistoryServer, role: &str) -> String { + format!("{cluster}-{role}", cluster = validated.name_any()) } pub fn error_policy( @@ -447,7 +433,6 @@ fn build_config_map( #[allow(clippy::result_large_err)] fn build_stateful_set( - shs: &v1alpha1::SparkHistoryServer, validated: &validate::ValidatedSparkHistoryServer, role_group_name: &RoleGroupName, rg: &ValidatedHistoryRoleGroup, @@ -585,7 +570,7 @@ fn build_stateful_set( // cluster-internal) as the address should still be consistent. let volume_claim_templates = Some(vec![listener_operator_volume_source_builder_build_pvc( &ListenerReference::Listener( - group_listener_name(shs, HISTORY_ROLE_NAME) + group_listener_name(validated, HISTORY_ROLE_NAME) .parse() .expect("the group listener name is a valid ListenerName"), ), diff --git a/rust/operator-binary/src/history/mod.rs b/rust/operator-binary/src/history/mod.rs index a8b727c6..d0245da1 100644 --- a/rust/operator-binary/src/history/mod.rs +++ b/rust/operator-binary/src/history/mod.rs @@ -1,26 +1,4 @@ -use stackable_operator::kvp::ObjectLabels; - -use crate::crd::constants::{ - HISTORY_APP_NAME, HISTORY_CONTROLLER_NAME, HISTORY_ROLE_NAME, OPERATOR_NAME, -}; - pub mod config; pub mod controller; pub mod operations; pub mod service; - -pub(crate) fn recommended_labels<'a, T>( - shs: &'a T, - app_version_label_value: &'a str, - role_group: &'a str, -) -> ObjectLabels<'a, T> { - ObjectLabels { - owner: shs, - app_name: HISTORY_APP_NAME, - app_version: app_version_label_value, - operator_name: OPERATOR_NAME, - controller_name: HISTORY_CONTROLLER_NAME, - role: HISTORY_ROLE_NAME, - role_group, - } -} From 76013fc7a603b902b55ba27183750304df71847a Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 13:28:48 +0200 Subject: [PATCH 38/50] refactor: return PodDisruptionBudget from build_pdb instead of mutating ClusterResources --- .../operator-binary/src/history/controller.rs | 22 +++++++------- .../src/history/operations/pdb.rs | 30 +++++-------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 3805f65f..a2a2e70c 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -83,7 +83,7 @@ use crate::{ }, history::{ config::jvm::construct_history_jvm_args, controller::validate::ValidatedHistoryRoleGroup, - operations::pdb::add_pdbs, service::build_rolegroup_metrics_service, + operations::pdb::build_pdb, service::build_rolegroup_metrics_service, }, product_logging::{self}, }; @@ -165,9 +165,9 @@ pub enum Error { rolegroup: String, }, - #[snafu(display("failed to create PodDisruptionBudget"))] - FailedToCreatePdb { - source: crate::history::operations::pdb::Error, + #[snafu(display("failed to apply PodDisruptionBudget"))] + ApplyPdb { + source: stackable_operator::cluster_resources::Error, }, #[snafu(display("failed to get required Labels"))] @@ -292,14 +292,12 @@ pub async fn reconcile( .context(ApplyGroupListenerSnafu)?; let role_config = &shs.spec.nodes.role_config; - add_pdbs( - &role_config.common.pod_disruption_budget, - &validated, - client, - &mut cluster_resources, - ) - .await - .context(FailedToCreatePdbSnafu)?; + if let Some(pdb) = build_pdb(&role_config.common.pod_disruption_budget, &validated) { + cluster_resources + .add(client, pdb) + .await + .context(ApplyPdbSnafu)?; + } cluster_resources .delete_orphaned_resources(client) diff --git a/rust/operator-binary/src/history/operations/pdb.rs b/rust/operator-binary/src/history/operations/pdb.rs index 9616e450..903d1538 100644 --- a/rust/operator-binary/src/history/operations/pdb.rs +++ b/rust/operator-binary/src/history/operations/pdb.rs @@ -1,30 +1,19 @@ -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - client::Client, cluster_resources::ClusterResources, commons::pdb::PdbConfig, - kube::ResourceExt, v2::builder::pdb::pod_disruption_budget_builder_with_role, + commons::pdb::PdbConfig, k8s_openapi::api::policy::v1::PodDisruptionBudget, + v2::builder::pdb::pod_disruption_budget_builder_with_role, }; use crate::history::controller::validate::{ ValidatedSparkHistoryServer, controller_name, operator_name, product_name, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("Cannot apply PodDisruptionBudget [{name}]"))] - ApplyPdb { - source: stackable_operator::cluster_resources::Error, - name: String, - }, -} - -pub async fn add_pdbs( +/// Builds the [`PodDisruptionBudget`] for the history server role, or `None` if PDBs are disabled. +pub fn build_pdb( pdb: &PdbConfig, validated: &ValidatedSparkHistoryServer, - client: &Client, - cluster_resources: &mut ClusterResources<'_>, -) -> Result<(), Error> { +) -> Option { if !pdb.enabled { - return Ok(()); + return None; } let max_unavailable = pdb .max_unavailable @@ -38,13 +27,8 @@ pub async fn add_pdbs( ) .with_max_unavailable(max_unavailable) .build(); - let pdb_name = pdb.name_any(); - cluster_resources - .add(client, pdb) - .await - .with_context(|_| ApplyPdbSnafu { name: pdb_name })?; - Ok(()) + Some(pdb) } fn max_unavailable_history_servers() -> u16 { From 90e027ac0eb903ee8453ddae714ee4ff5a9e0f2f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 13:41:45 +0200 Subject: [PATCH 39/50] refactor: hoist shared typed logging consts into crd::constants --- rust/operator-binary/src/connect/server.rs | 17 ++++------------- rust/operator-binary/src/crd/constants.rs | 13 ++++++++++++- rust/operator-binary/src/history/controller.rs | 15 ++++----------- .../operator-binary/src/spark_k8s_controller.rs | 8 +------- 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index a1edcf69..98b63310 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -40,20 +40,10 @@ use stackable_operator::{ builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, product_logging::framework::vector_container, role_group_utils::ResourceNames, - types::{ - kubernetes::{ContainerName, VolumeName}, - operator::{RoleGroupName, RoleName}, - }, + types::operator::{RoleGroupName, RoleName}, }, }; -stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); -// Typed volume names required by the v2 `vector_container`; values match the `&str` volume-mount -// name constants (`VOLUME_MOUNT_NAME_CONFIG`/`VOLUME_MOUNT_NAME_LOG`) used elsewhere to build the -// same volumes. -stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); -stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); - use crate::{ connect::{ GRPC, HTTP, @@ -69,8 +59,9 @@ use crate::{ constants::{ JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, METRICS_PROPERTIES_FILE, - POD_TEMPLATE_FILE, SPARK_DEFAULTS_FILE_NAME, VOLUME_MOUNT_NAME_CONFIG, - VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, + POD_TEMPLATE_FILE, SPARK_DEFAULTS_FILE_NAME, VECTOR_CONTAINER_NAME, + VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, + VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, }, listener_ext, diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index bbd84d43..aa613bf6 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -1,7 +1,12 @@ +use std::str::FromStr; + use const_format::concatcp; use stackable_operator::{ memory::{BinaryMultiple, MemoryQuantity}, - v2::types::common::Port, + v2::types::{ + common::Port, + kubernetes::{ContainerName, VolumeName}, + }, }; pub const APP_NAME: &str = "spark-k8s"; @@ -95,6 +100,12 @@ pub const SPARK_CLUSTER_ROLE: &str = "spark-k8s-clusterrole"; pub const METRICS_PORT: Port = Port(18081); pub const HISTORY_UI_PORT: Port = Port(18080); +// Typed container/volume names required by the v2 `vector_container`; values match the `&str` +// volume-mount consts `VOLUME_MOUNT_NAME_CONFIG` ("config") / `VOLUME_MOUNT_NAME_LOG` ("log"). +stackable_operator::constant!(pub VECTOR_CONTAINER_NAME: ContainerName = "vector"); +stackable_operator::constant!(pub VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); +stackable_operator::constant!(pub VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); + pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index a2a2e70c..59e64e7a 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -45,19 +45,11 @@ use stackable_operator::{ cluster_resources::cluster_resources_new, config_file_writer::{PropertiesWriterError, to_java_properties_string}, product_logging::framework::vector_container, - types::{ - kubernetes::{ContainerName, PersistentVolumeClaimName, VolumeName}, - operator::RoleGroupName, - }, + types::{kubernetes::PersistentVolumeClaimName, operator::RoleGroupName}, }, }; use strum::{EnumDiscriminants, IntoStaticStr}; -stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); -// Typed volume names required by the v2 `vector_container`; values match the `&str` volume-mount -// name constants used elsewhere to build the same volumes. -stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); -stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); // PVC name for the listener volume, required by the v2 listener-volume builder. Its value matches // `LISTENER_VOLUME_NAME` in `crd::constants`. stackable_operator::constant!(LISTENER_VOLUME_NAME_PVC: PersistentVolumeClaimName = "listener"); @@ -72,8 +64,9 @@ use crate::{ JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, - STACKABLE_TRUST_STORE, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, - VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, + STACKABLE_TRUST_STORE, VECTOR_CONTAINER_NAME, VOLUME_MOUNT_NAME_CONFIG, + VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, + VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, }, history::{SparkHistoryServerContainer, v1alpha1}, diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index f747c278..216ceae7 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -49,19 +49,13 @@ use stackable_operator::{ }, role_group_utils::ResourceNames, types::{ - kubernetes::{ConfigMapName, ContainerName, VolumeName}, + kubernetes::ConfigMapName, operator::{RoleGroupName, RoleName}, }, }, }; use strum::{EnumDiscriminants, IntoStaticStr}; -stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); -// Typed volume names required by the v2 `vector_container`; values match the existing `&str` -// volume-mount consts VOLUME_MOUNT_NAME_CONFIG ("config") / VOLUME_MOUNT_NAME_LOG ("log"). -stackable_operator::constant!(VOLUME_MOUNT_NAME_CONFIG_TYPED: VolumeName = "config"); -stackable_operator::constant!(VOLUME_MOUNT_NAME_LOG_TYPED: VolumeName = "log"); - use crate::{ Ctx, crd::{ From 90b18a3953595167e5ef88956552693313f07ede Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 13:57:45 +0200 Subject: [PATCH 40/50] refactor: share single default_jvm_security_properties helper --- rust/operator-binary/src/crd/constants.rs | 17 +++++++++- .../operator-binary/src/history/controller.rs | 31 +++++-------------- .../src/spark_k8s_controller.rs | 16 +--------- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index aa613bf6..af40f517 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::BTreeMap, str::FromStr}; use const_format::concatcp; use stackable_operator::{ @@ -110,3 +110,18 @@ pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; pub const DEFAULT_SUBMIT_JOB_RETRY_ON_FAILURE_COUNT: u16 = 0; + +/// The JVM `security.properties` entries the operator sets by default (DNS cache TTLs). +pub fn default_jvm_security_properties() -> BTreeMap { + [ + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), + DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), + ), + ( + JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), + DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), + ), + ] + .into() +} diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 59e64e7a..55a1ecbf 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -58,16 +58,13 @@ use crate::{ Ctx, crd::{ constants::{ - ACCESS_KEY_ID, DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL, - DEFAULT_JVM_SECURITY_DNS_CACHE_TTL, HISTORY_APP_NAME, HISTORY_ROLE_NAME, - HISTORY_UI_PORT, JVM_SECURITY_PROPERTIES_FILE, - JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL, JVM_SECURITY_PROPERTY_DNS_CACHE_TTL, - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, - SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, - STACKABLE_TRUST_STORE, VECTOR_CONTAINER_NAME, VOLUME_MOUNT_NAME_CONFIG, - VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, - VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, - VOLUME_MOUNT_PATH_LOG_CONFIG, + ACCESS_KEY_ID, HISTORY_APP_NAME, HISTORY_ROLE_NAME, HISTORY_UI_PORT, + JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, + MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, + SPARK_ENV_SH_FILE_NAME, STACKABLE_TRUST_STORE, VECTOR_CONTAINER_NAME, + VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, + VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, + VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, default_jvm_security_properties, }, history::{SparkHistoryServerContainer, v1alpha1}, listener_ext, @@ -650,20 +647,6 @@ fn command_args(logdir: &ResolvedLogDir) -> Vec { vec![command.join("\n")] } -fn default_jvm_security_properties() -> BTreeMap { - [ - ( - JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), - DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), - ), - ( - JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), - DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), - ), - ] - .into() -} - /// Return the Spark properties for the cleaner role group (if any). fn cleaner_config( validated: &validate::ValidatedSparkHistoryServer, diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 216ceae7..f6de91a3 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, str::FromStr, sync::Arc, vec}; +use std::{str::FromStr, sync::Arc, vec}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -778,20 +778,6 @@ fn submit_job_config_map( cm_builder.build().context(PodTemplateConfigMapSnafu) } -fn default_jvm_security_properties() -> BTreeMap { - [ - ( - JVM_SECURITY_PROPERTY_DNS_CACHE_TTL.to_string(), - DEFAULT_JVM_SECURITY_DNS_CACHE_TTL.to_string(), - ), - ( - JVM_SECURITY_PROPERTY_DNS_CACHE_NEGATIVE_TTL.to_string(), - DEFAULT_JVM_SECURITY_DNS_CACHE_NEGATIVE_TTL.to_string(), - ), - ] - .into() -} - #[allow(clippy::too_many_arguments)] fn spark_job( validated: &validate::ValidatedSparkApplication, From c9b2becfe46390d0cb66cf9ba4a8ddf4aa1d53c8 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:02:10 +0200 Subject: [PATCH 41/50] refactor: adopt static vector.yaml and v2 product_logging, removing RoleGroupRef --- rust/operator-binary/src/connect/executor.rs | 12 +- rust/operator-binary/src/connect/server.rs | 16 +- .../operator-binary/src/history/controller.rs | 14 +- rust/operator-binary/src/product_logging.rs | 86 --- .../src/product_logging/mod.rs | 120 ++++ .../src/product_logging/test-vector.sh | 11 + .../src/product_logging/vector-test.yaml | 162 +++++ .../src/product_logging/vector.yaml | 603 ++++++++++++++++++ .../src/spark_k8s_controller.rs | 9 +- 9 files changed, 901 insertions(+), 132 deletions(-) delete mode 100644 rust/operator-binary/src/product_logging.rs create mode 100644 rust/operator-binary/src/product_logging/mod.rs create mode 100755 rust/operator-binary/src/product_logging/test-vector.sh create mode 100644 rust/operator-binary/src/product_logging/vector-test.yaml create mode 100644 rust/operator-binary/src/product_logging/vector.yaml diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index 8265e04a..1b841914 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -16,16 +16,15 @@ use stackable_operator::{ DeepMerge, api::core::v1::{ConfigMap, EnvVar, PodSecurityContext, PodTemplateSpec}, }, - kube::{ResourceExt, runtime::reflector::ObjectRef}, + kube::ResourceExt, product_logging::framework::calculate_log_volume_size_limit, - role_utils::RoleGroupRef, v2::builder::meta::ownerreference_from_resource, }; use super::{ common::{SparkConnectRole, object_name}, controller::validate::ValidatedSparkConnectServer, - crd::{DEFAULT_SPARK_CONNECT_GROUP_NAME, SparkConnectContainer}, + crd::SparkConnectContainer, }; use crate::{ connect::{common, crd::v1alpha1, s3}, @@ -360,16 +359,9 @@ pub(crate) fn executor_config_map( .add_data(JVM_SECURITY_PROPERTIES_FILE, jvm_sec_props) .add_data(METRICS_PROPERTIES_FILE, metrics_props); - let role_group_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(validated), - role: SparkConnectRole::Executor.to_string(), - role_group: DEFAULT_SPARK_CONNECT_GROUP_NAME.to_string(), - }; product_logging::extend_config_map( - &role_group_ref, &config.logging, SparkConnectContainer::Spark, - SparkConnectContainer::Vector, &mut cm_builder, ) .context(InvalidLoggingConfigSnafu { diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 98b63310..a8596186 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -32,10 +32,9 @@ use stackable_operator::{ }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, - kube::{ResourceExt, runtime::reflector::ObjectRef}, + kube::ResourceExt, kvp::Label, product_logging::framework::calculate_log_volume_size_limit, - role_utils::RoleGroupRef, v2::{ builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, product_logging::framework::vector_container, @@ -183,12 +182,9 @@ pub(crate) fn server_config_map( .add_data(JVM_SECURITY_PROPERTIES_FILE, jvm_sec_props) .add_data(METRICS_PROPERTIES_FILE, metrics_props); - let role_group_ref = default_role_group_ref(validated); product_logging::extend_config_map( - &role_group_ref, &config.logging, SparkConnectContainer::Spark, - SparkConnectContainer::Vector, &mut cm_builder, ) .context(InvalidLoggingConfigSnafu { @@ -549,16 +545,6 @@ fn probe() -> Probe { } } -fn default_role_group_ref( - validated: &ValidatedSparkConnectServer, -) -> RoleGroupRef { - RoleGroupRef { - cluster: ObjectRef::from_obj(validated), - role: SparkConnectRole::Server.to_string(), - role_group: DEFAULT_SPARK_CONNECT_GROUP_NAME.to_string(), - } -} - pub(crate) fn build_listener( validated: &ValidatedSparkConnectServer, role_config: &v1alpha1::SparkConnectServerRoleConfig, diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 55a1ecbf..746cb135 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -22,7 +22,7 @@ use stackable_operator::{ kube::{ ResourceExt, core::{DeserializeGuard, error_boundary}, - runtime::{controller::Action, reflector::ObjectRef}, + runtime::controller::Action, }, logging::controller::ReconcilerError, product_logging::{ @@ -32,7 +32,6 @@ use stackable_operator::{ CustomContainerLogConfig, }, }, - role_utils::RoleGroupRef, shared::time::Duration, v2::{ builder::{ @@ -396,20 +395,9 @@ fn build_config_map( })?, ); - // `product_logging::extend_config_map` still expects a `RoleGroupRef`, so build a local temp - // one purely for that call until the logging path is migrated. - let rgr = RoleGroupRef { - cluster: ObjectRef::::new(validated.name.as_ref()) - .within(validated.namespace.as_ref()), - role: HISTORY_ROLE_NAME.to_string(), - role_group: role_group_name.to_string(), - }; - product_logging::extend_config_map( - &rgr, &rg.config.config.logging, SparkHistoryServerContainer::SparkHistory, - SparkHistoryServerContainer::Vector, &mut cm_builder, ) .context(InvalidLoggingConfigSnafu { cm_name: &cm_name })?; diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/product_logging.rs deleted file mode 100644 index 084c4fe8..00000000 --- a/rust/operator-binary/src/product_logging.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::fmt::Display; - -use snafu::Snafu; -use stackable_operator::{ - builder::configmap::ConfigMapBuilder, - kube::Resource, - memory::BinaryMultiple, - product_logging::{ - self, - spec::{ContainerLogConfig, ContainerLogConfigChoice, Logging}, - }, - role_utils::RoleGroupRef, -}; - -use crate::crd::constants::{LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, VOLUME_MOUNT_PATH_LOG}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to retrieve the ConfigMap {cm_name}"))] - ConfigMapNotFound { - source: stackable_operator::client::Error, - cm_name: String, - }, - - #[snafu(display("failed to retrieve the entry {entry} for ConfigMap {cm_name}"))] - MissingConfigMapEntry { - entry: &'static str, - cm_name: String, - }, -} - -type Result = std::result::Result; - -pub const LOG_FILE: &str = "spark.log4j2.xml"; - -const CONSOLE_CONVERSION_PATTERN: &str = "%d{ISO8601} %p [%t] %c - %m%n"; - -/// Extend a ConfigMap with logging and Vector configurations -pub fn extend_config_map( - role_group: &RoleGroupRef, - logging: &Logging, - main_container: C, - vector_container: C, - cm_builder: &mut ConfigMapBuilder, -) -> Result<()> -where - C: Clone + Ord + Display, - K: Resource, -{ - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&main_container) - { - cm_builder.add_data( - LOG4J2_CONFIG_FILE, - product_logging::framework::create_log4j2_config( - &format!("{VOLUME_MOUNT_PATH_LOG}/{main_container}"), - LOG_FILE, - MAX_SPARK_LOG_FILES_SIZE - .scale_to(BinaryMultiple::Mebi) - .floor() - .value as u32, - CONSOLE_CONVERSION_PATTERN, - log_config, - ), - ); - } - - let vector_log_config = if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&vector_container) - { - Some(log_config) - } else { - None - }; - - if logging.enable_vector_agent { - cm_builder.add_data( - product_logging::framework::VECTOR_CONFIG_FILE, - product_logging::framework::create_vector_config(role_group, vector_log_config), - ); - } - - Ok(()) -} diff --git a/rust/operator-binary/src/product_logging/mod.rs b/rust/operator-binary/src/product_logging/mod.rs new file mode 100644 index 00000000..8d06986b --- /dev/null +++ b/rust/operator-binary/src/product_logging/mod.rs @@ -0,0 +1,120 @@ +use std::fmt::Display; + +use snafu::Snafu; +use stackable_operator::{ + builder::configmap::ConfigMapBuilder, + memory::BinaryMultiple, + product_logging::{ + self, + spec::{ContainerLogConfig, ContainerLogConfigChoice, Logging}, + }, +}; + +use crate::crd::constants::{LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, VOLUME_MOUNT_PATH_LOG}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to retrieve the ConfigMap {cm_name}"))] + ConfigMapNotFound { + source: stackable_operator::client::Error, + cm_name: String, + }, + + #[snafu(display("failed to retrieve the entry {entry} for ConfigMap {cm_name}"))] + MissingConfigMapEntry { + entry: &'static str, + cm_name: String, + }, +} + +type Result = std::result::Result; + +pub const LOG_FILE: &str = "spark.log4j2.xml"; + +const CONSOLE_CONVERSION_PATTERN: &str = "%d{ISO8601} %p [%t] %c - %m%n"; + +/// The static Vector agent configuration (`vector.yaml`). +/// +/// Cluster/role/role-group/namespace metadata and the file-log level are injected at runtime via +/// the env vars set by the v2 `vector_container` (`${CLUSTER_NAME}`, `${ROLE_NAME}`, +/// `${ROLE_GROUP_NAME}`, `${NAMESPACE}`, `${VECTOR_FILE_LOG_LEVEL}`, `${VECTOR_AGGREGATOR_ADDRESS}`, +/// …), so this file is role-group-agnostic. +const VECTOR_CONFIG: &str = include_str!("vector.yaml"); + +/// Returns the Vector agent config (`vector.yaml`) content. +pub fn vector_config_file_content() -> String { + VECTOR_CONFIG.to_owned() +} + +/// Extend a ConfigMap with logging (`log4j2.properties`) and the Vector agent configuration. +pub fn extend_config_map( + logging: &Logging, + main_container: C, + cm_builder: &mut ConfigMapBuilder, +) -> Result<()> +where + C: Clone + Ord + Display, +{ + if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = logging.containers.get(&main_container) + { + cm_builder.add_data( + LOG4J2_CONFIG_FILE, + product_logging::framework::create_log4j2_config( + &format!("{VOLUME_MOUNT_PATH_LOG}/{main_container}"), + LOG_FILE, + MAX_SPARK_LOG_FILES_SIZE + .scale_to(BinaryMultiple::Mebi) + .floor() + .value as u32, + CONSOLE_CONVERSION_PATTERN, + log_config, + ), + ); + } + + if logging.enable_vector_agent { + cm_builder.add_data( + product_logging::framework::VECTOR_CONFIG_FILE, + vector_config_file_content(), + ); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn vector_config_is_valid_and_complete() { + let content = vector_config_file_content(); + // Parses as YAML and has the expected top-level shape. + let parsed: serde_yaml::Value = + serde_yaml::from_str(&content).expect("vector.yaml must be valid YAML"); + let sources = parsed + .get("sources") + .and_then(|s| s.as_mapping()) + .expect("sources mapping"); + // All Spark log sources must be present. + for source in [ + "files_stdout", + "files_stderr", + "files_log4j", + "files_log4j2", + "files_py", + "files_airlift", + ] { + assert!( + sources.contains_key(serde_yaml::Value::from(source)), + "vector.yaml is missing source {source}" + ); + } + // Runtime metadata must come from env vars, not baked literals. + assert!(content.contains("${CLUSTER_NAME}")); + assert!(content.contains("${ROLE_GROUP_NAME}")); + assert!(content.contains("${VECTOR_AGGREGATOR_ADDRESS}")); + } +} diff --git a/rust/operator-binary/src/product_logging/test-vector.sh b/rust/operator-binary/src/product_logging/test-vector.sh new file mode 100755 index 00000000..fbf498f0 --- /dev/null +++ b/rust/operator-binary/src/product_logging/test-vector.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +DATA_DIR=/stackable/log/_vector-state \ +LOG_DIR=/stackable/log \ +NAMESPACE=default \ +CLUSTER_NAME=spark \ +ROLE_NAME=node \ +ROLE_GROUP_NAME=default \ +VECTOR_AGGREGATOR_ADDRESS=vector-aggregator \ +VECTOR_FILE_LOG_LEVEL=info \ +vector test vector.yaml vector-test.yaml diff --git a/rust/operator-binary/src/product_logging/vector-test.yaml b/rust/operator-binary/src/product_logging/vector-test.yaml new file mode 100644 index 00000000..54f4828a --- /dev/null +++ b/rust/operator-binary/src/product_logging/vector-test.yaml @@ -0,0 +1,162 @@ +# Run tests with `./test-vector.sh` +# +# A downside of these test cases is that they compare the whole event and that the message can +# contain source code positions in vector.yaml, e.g. "function call error for \"parse_xml\" at +# (584:643)". Please adapt the tests if you change VRL code in vector.yaml. +--- +tests: + - name: Test stdout log entry + inputs: + - type: log + insert_at: processed_files_stdout + log_fields: + file: /stackable/log/spark/spark.stdout.log + message: Starting Spark + pod: spark-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:28.582Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "spark", + "file": "spark.stdout.log", + "level": "INFO", + "logger": "ROOT", + "message": "Starting Spark", + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:27:28.582Z" + } + + assert_eq!(expected_log_event, .) + - name: Test stderr log entry + inputs: + - type: log + insert_at: processed_files_stderr + log_fields: + file: /stackable/log/spark/spark.stderr.log + message: "Exception in thread \"main\"" + pod: spark-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:28.582Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "spark", + "file": "spark.stderr.log", + "level": "ERROR", + "logger": "ROOT", + "message": "Exception in thread \"main\"", + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:27:28.582Z" + } + + assert_eq!(expected_log_event, .) + - name: Test log4j2 XML log entry without stacktrace + inputs: + - type: log + insert_at: processed_files_log4j2 + log_fields: + file: /stackable/log/spark/spark.log4j2.xml + message: > + Bound HistoryServer to 0.0.0.0, and started at + http://spark:18080 + pod: spark-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:29.473487331Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "spark", + "file": "spark.log4j2.xml", + "level": "INFO", + "logger": "org.apache.spark.deploy.history.HistoryServer", + "message": "Bound HistoryServer to 0.0.0.0, and started at http://spark:18080", + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": t'2025-10-02T09:27:28.582Z' + } + + assert_eq!(expected_log_event, .) + - name: Test Vector internal logs + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + arch: x86_64 + message: Vector has started. + metadata: + kind: event + level: INFO + module_path: vector::internal_events::process + target: vector + pid: 14 + pod: spark-node-default-0 + source_type: internal_logs + timestamp: 2025-10-02T09:46:14.479381097Z + version: 0.49.0 + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "arch": "x86_64", + "cluster": "spark", + "container": "vector", + "level": "INFO", + "logger": "vector::internal_events::process", + "message": "Vector has started.", + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:46:14.479381097Z", + "version": "0.49.0" + } + + assert_eq!(expected_log_event, .) + - name: Test Vector internal log level filtering - INFO passes + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + metadata: + level: INFO + outputs: + - extract_from: filtered_logs_vector + conditions: + - type: vrl + source: | + assert_eq!("INFO", .metadata.level) + - name: Test Vector internal log level filtering - DEBUG dropped + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + metadata: + level: DEBUG + no_outputs_from: + - filtered_logs_vector diff --git a/rust/operator-binary/src/product_logging/vector.yaml b/rust/operator-binary/src/product_logging/vector.yaml new file mode 100644 index 00000000..de6a15f8 --- /dev/null +++ b/rust/operator-binary/src/product_logging/vector.yaml @@ -0,0 +1,603 @@ +--- +data_dir: ${DATA_DIR} + +log_schema: + host_key: pod + +sources: + vector: + type: internal_logs + + files_stdout: + type: file + include: + - ${LOG_DIR}/*/*.stdout.log + + files_stderr: + type: file + include: + - ${LOG_DIR}/*/*.stderr.log + + files_log4j: + type: file + include: + - ${LOG_DIR}/*/*.log4j.xml + line_delimiter: "\r\n" + multiline: + mode: halt_before + start_pattern: ^" + raw_message + "" + parsed_event, err = parse_xml(wrapped_xml_event) + if err != null { + error = "XML not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + root = object!(parsed_event.root) + if !is_object(root.event) { + error = "Parsed event contains no \"event\" tag." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + if keys(root) != ["event"] { + .errors = push(.errors, "Parsed event contains multiple tags: " + join!(keys(root), ", ")) + } + event = object!(root.event) + + epoch_milliseconds, err = to_int(event.@timestamp) + if err == null && epoch_milliseconds != 0 { + converted_timestamp, err = from_unix_timestamp(epoch_milliseconds, "milliseconds") + if err == null { + .timestamp = converted_timestamp + } else { + .errors = push(.errors, "Time not parsable, using current time instead: " + err) + } + } else { + .errors = push(.errors, "Timestamp not found, using current time instead.") + } + + .logger, err = string(event.@logger) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger not found.") + } + + level, err = string(event.@level) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], level) { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } else { + .level = level + } + + message, err = string(event.message) + if err != null || is_empty(message) { + .errors = push(.errors, "Message not found.") + } + throwable = string(event.throwable) ?? "" + .message = join!(compact([message, throwable]), "\n") + } + } + + processed_files_log4j2: + inputs: + - files_log4j2 + type: remap + source: | + raw_message = string!(.message) + + .timestamp = now() + .logger = "" + .level = "INFO" + .message = "" + .errors = [] + + event = {} + parsed_event, err = parse_xml(raw_message) + if err != null { + error = "XML not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + if !is_object(parsed_event.Event) { + error = "Parsed event contains no \"Event\" tag." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + event = object!(parsed_event.Event) + + tag_instant_valid = false + instant, err = object(event.Instant) + if err == null { + epoch_nanoseconds, err = to_int(instant.@epochSecond) * 1_000_000_000 + to_int(instant.@nanoOfSecond) + if err == null && epoch_nanoseconds != 0 { + converted_timestamp, err = from_unix_timestamp(epoch_nanoseconds, "nanoseconds") + if err == null { + .timestamp = converted_timestamp + tag_instant_valid = true + } else { + .errors = push(.errors, "Instant invalid, trying property timeMillis instead: " + err) + } + } else { + .errors = push(.errors, "Instant invalid, trying property timeMillis instead: " + err) + } + } + if !tag_instant_valid { + epoch_milliseconds, err = to_int(event.@timeMillis) + if err == null && epoch_milliseconds != 0 { + converted_timestamp, err = from_unix_timestamp(epoch_milliseconds, "milliseconds") + if err == null { + .timestamp = converted_timestamp + } else { + .errors = push(.errors, "timeMillis not parsable, using current time instead: " + err) + } + } else { + .errors = push(.errors, "timeMillis not parsable, using current time instead: " + err) + } + } + + .logger, err = string(event.@loggerName) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger not found.") + } + + level, err = string(event.@level) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], level) { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } else { + .level = level + } + + exception = null + thrown = event.Thrown + if is_object(thrown) { + exception = "Exception" + thread, err = string(event.@thread) + if err == null && !is_empty(thread) { + exception = exception + " in thread \"" + thread + "\"" + } + thrown_name, err = string(thrown.@name) + if err == null && !is_empty(exception) { + exception = exception + " " + thrown_name + } + message = string(thrown.@localizedMessage) ?? + string(thrown.@message) ?? + "" + if !is_empty(message) { + exception = exception + ": " + message + } + stacktrace_items = array(thrown.ExtendedStackTrace.ExtendedStackTraceItem) ?? [] + stacktrace = "" + for_each(stacktrace_items) -> |_index, value| { + stacktrace = stacktrace + " " + class = string(value.@class) ?? "" + method = string(value.@method) ?? "" + if !is_empty(class) && !is_empty(method) { + stacktrace = stacktrace + "at " + class + "." + method + } + file = string(value.@file) ?? "" + line = string(value.@line) ?? "" + if !is_empty(file) && !is_empty(line) { + stacktrace = stacktrace + "(" + file + ":" + line + ")" + } + exact = to_bool(value.@exact) ?? false + location = string(value.@location) ?? "" + version = string(value.@version) ?? "" + if !is_empty(location) && !is_empty(version) { + stacktrace = stacktrace + " " + if !exact { + stacktrace = stacktrace + "~" + } + stacktrace = stacktrace + "[" + location + ":" + version + "]" + } + stacktrace = stacktrace + "\n" + } + if stacktrace != "" { + exception = exception + "\n" + stacktrace + } + } + + message, err = string(event.Message) + if err != null || is_empty(message) { + message = null + .errors = push(.errors, "Message not found.") + } + .message = join!(compact([message, exception]), "\n") + } + } + + processed_files_py: + inputs: + - files_py + type: remap + source: | + raw_message = string!(.message) + + .timestamp = now() + .logger = "" + .level = "INFO" + .message = "" + .errors = [] + + parsed_event, err = parse_json(raw_message) + if err != null { + error = "JSON not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else if !is_object(parsed_event) { + error = "Parsed event is not a JSON object." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + event = object!(parsed_event) + + asctime, err = string(event.asctime) + if err == null { + parsed_timestamp, err = parse_timestamp(asctime, "%F %T,%3f") + if err == null { + .timestamp = parsed_timestamp + } else { + .errors = push(.errors, "Timestamp not parsable, using current time instead: "+ err) + } + } else { + .errors = push(.errors, "Timestamp not found, using current time instead.") + } + + .logger, err = string(event.name) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger not found.") + } + + level, err = string(event.levelname) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if level == "DEBUG" { + .level = "DEBUG" + } else if level == "INFO" { + .level = "INFO" + } else if level == "WARNING" { + .level = "WARN" + } else if level == "ERROR" { + .level = "ERROR" + } else if level == "CRITICAL" { + .level = "FATAL" + } else { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } + + .message, err = string(event.message) + if err != null || is_empty(.message) { + .errors = push(.errors, "Message not found.") + } + } + + processed_files_airlift: + inputs: + - files_airlift + type: remap + source: | + raw_message = string!(.message) + + .timestamp = now() + .logger = "" + .level = "INFO" + .message = "" + .errors = [] + + parsed_event, err = parse_json(raw_message) + if err != null { + error = "JSON not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else if !is_object(parsed_event) { + error = "Parsed event is not a JSON object." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + event = object!(parsed_event) + + timestamp_string, err = string(event.timestamp) + if err == null { + parsed_timestamp, err = parse_timestamp(timestamp_string, "%Y-%m-%dT%H:%M:%S.%fZ") + if err == null { + .timestamp = parsed_timestamp + } else { + .errors = push(.errors, "Timestamp not parsable, using current time instead: " + err) + } + } else { + .errors = push(.errors, "Timestamp not found, using current time instead.") + } + + .logger, err = string(event.logger) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger not found.") + } + + level, err = string(event.level) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], level) { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } else { + .level = level + } + + .thread = string(parsed_event.thread) ?? null + + .message, err = string(event.message) + if err != null || is_empty(.message) { + .errors = push(.errors, "Message not found.") + } + stacktrace = string(event.stackTrace) ?? "" + .message = join!(compact([.message, stacktrace]), "\n\n") + } + + extended_logs_files: + inputs: + - processed_files_* + type: remap + source: | + del(.source_type) + if .errors == [] { + del(.errors) + } + . |= parse_regex!(.file, r'^${LOG_DIR}/(?P.*?)/(?P.*?)$') + + filtered_logs_vector: + inputs: + - vector + type: filter + condition: > + (.metadata.level == "TRACE" && "${VECTOR_FILE_LOG_LEVEL}" == "trace") || + (.metadata.level == "DEBUG" && includes(["trace", "debug"], "${VECTOR_FILE_LOG_LEVEL}")) || + (.metadata.level == "INFO" && includes(["trace", "debug", "info"], "${VECTOR_FILE_LOG_LEVEL}")) || + (.metadata.level == "WARN" && includes(["trace", "debug", "info", "warn"], "${VECTOR_FILE_LOG_LEVEL}")) || + (.metadata.level == "ERROR" && includes(["trace", "debug", "info", "warn", "error"], "${VECTOR_FILE_LOG_LEVEL}")) + + extended_logs_vector: + inputs: + - filtered_logs_vector + type: remap + source: | + .container = "vector" + .level = .metadata.level + .logger = .metadata.module_path + if exists(.file) { .processed_file = del(.file) } + del(.metadata) + del(.pid) + del(.source_type) + + extended_logs: + inputs: + - extended_logs_* + type: remap + source: | + .namespace = "${NAMESPACE}" + .cluster = "${CLUSTER_NAME}" + .role = "${ROLE_NAME}" + .roleGroup = "${ROLE_GROUP_NAME}" + +sinks: + aggregator: + inputs: + - extended_logs + type: vector + address: ${VECTOR_AGGREGATOR_ADDRESS} diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index f6de91a3..2bfb40ad 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -27,7 +27,7 @@ use stackable_operator::{ kube::{ ResourceExt, core::{DeserializeGuard, error_boundary}, - runtime::{controller::Action, reflector::ObjectRef}, + runtime::controller::Action, }, kvp::Label, logging::controller::ReconcilerError, @@ -38,7 +38,6 @@ use stackable_operator::{ CustomContainerLogConfig, Logging, }, }, - role_utils::RoleGroupRef, shared::time::Duration, v2::{ builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, @@ -711,14 +710,8 @@ fn pod_template_config_map( ); product_logging::extend_config_map( - &RoleGroupRef { - cluster: ObjectRef::from_obj(spark_application), - role: String::new(), - role_group: String::new(), - }, &merged_config.logging, SparkContainer::Spark, - SparkContainer::Vector, &mut cm_builder, ) .context(InvalidLoggingConfigSnafu { cm_name })?; From 3620c6f7e9e1379699c8eb7dcf7a65e461cdc7da Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:12:21 +0200 Subject: [PATCH 42/50] refactor(history): build child ObjectMeta via a shared object_meta helper --- .../operator-binary/src/history/controller.rs | 10 ++++---- .../src/history/controller/validate.rs | 23 ++++++++++++++++++- rust/operator-binary/src/history/service.rs | 11 ++++----- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index 746cb135..d4d43cc9 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -571,11 +571,11 @@ fn build_stateful_set( let mut pod_template = pb.build_template(); pod_template.merge_from(rg.config.pod_overrides.clone()); - let sts_metadata = ObjectMetaBuilder::new() - .name_and_namespace(validated) - .name(resource_names.stateful_set_name().to_string()) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(recommended_labels) + let sts_metadata = validated + .object_meta( + resource_names.stateful_set_name().to_string(), + role_group_name, + ) .build(); Ok(StatefulSet { diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 68a56626..5d965846 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -7,6 +7,7 @@ use std::{borrow::Cow, collections::BTreeMap, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, config::fragment, @@ -16,7 +17,10 @@ use stackable_operator::{ product_logging::spec::Logging, v2::{ HasName, HasUid, NameIsValidLabelValue, - builder::pod::container::{self, EnvVarName, EnvVarSet}, + builder::{ + meta::ownerreference_from_resource, + pod::container::{self, EnvVarName, EnvVarSet}, + }, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::{recommended_labels, role_group_selector}, product_logging::framework::{ @@ -224,6 +228,23 @@ impl ValidatedSparkHistoryServer { pub fn role_group_selector(&self, role_group_name: &RoleGroupName) -> Labels { role_group_selector(self, &product_name(), &Self::role_name(), role_group_name) } + + /// Object metadata for a child resource named `name`, owned by this SparkHistoryServer and + /// carrying the recommended labels for the given role group. Returns the builder so callers can + /// add extra labels (e.g. Prometheus annotations) before building. + pub(crate) fn object_meta( + &self, + name: impl Into, + role_group_name: &RoleGroupName, + ) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .name_and_namespace(self) + .name(name) + .ownerreference(ownerreference_from_resource(self, None, Some(true))) + .with_labels(self.recommended_labels(role_group_name)); + builder + } } /// The product name (`spark-history`) as a type-safe label value. diff --git a/rust/operator-binary/src/history/service.rs b/rust/operator-binary/src/history/service.rs index 0b6009ce..097aaf5e 100644 --- a/rust/operator-binary/src/history/service.rs +++ b/rust/operator-binary/src/history/service.rs @@ -1,8 +1,7 @@ use stackable_operator::{ - builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels}, - v2::{builder::meta::ownerreference_from_resource, types::operator::RoleGroupName}, + v2::types::operator::RoleGroupName, }; use crate::{ @@ -15,16 +14,14 @@ pub fn build_rolegroup_metrics_service( role_group_name: &RoleGroupName, ) -> Service { Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(validated) - .name( + metadata: validated + .object_meta( validated .resource_names(role_group_name) .metrics_service_name() .to_string(), + role_group_name, ) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(role_group_name)) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), From f1caec609868e3a7bb6caa0c6c7f18086b1f6e44 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:17:22 +0200 Subject: [PATCH 43/50] refactor(connect): build child ObjectMeta via a shared object_meta helper --- .../src/connect/controller/validate.rs | 18 ++++++++++++++++++ rust/operator-binary/src/connect/executor.rs | 8 ++------ rust/operator-binary/src/connect/server.rs | 19 ++++++++----------- rust/operator-binary/src/connect/service.rs | 16 ++++------------ 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index 10de05cb..f565b427 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -7,6 +7,7 @@ use std::{borrow::Cow, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, @@ -15,6 +16,7 @@ use stackable_operator::{ product_logging::spec::Logging, v2::{ HasName, HasUid, NameIsValidLabelValue, + builder::meta::ownerreference_from_resource, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::{recommended_labels, role_group_selector, role_selector}, product_logging::framework::{ @@ -175,6 +177,22 @@ impl ValidatedSparkConnectServer { .expect("DEFAULT_SPARK_CONNECT_GROUP_NAME is a valid role group name"); role_group_selector(self, &product_name(), &role_name(role), &role_group) } + + /// Object metadata for a child resource named `name`, owned by this SparkConnectServer and + /// carrying the recommended labels for the given role. + pub(crate) fn object_meta( + &self, + name: impl Into, + role: SparkConnectRole, + ) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .name_and_namespace(self) + .name(name) + .ownerreference(ownerreference_from_resource(self, None, Some(true))) + .with_labels(self.recommended_labels(role)); + builder + } } /// The product name (`spark-connect`) as a type-safe label value. diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index 1b841914..e19cdcc4 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -18,7 +18,6 @@ use stackable_operator::{ }, kube::ResourceExt, product_logging::framework::calculate_log_volume_size_limit, - v2::builder::meta::ownerreference_from_resource, }; use super::{ @@ -349,11 +348,8 @@ pub(crate) fn executor_config_map( cm_builder .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(validated) - .name(&cm_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(SparkConnectRole::Executor)) + validated + .object_meta(&cm_name, SparkConnectRole::Executor) .build(), ) .add_data(JVM_SECURITY_PROPERTIES_FILE, jvm_sec_props) diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index a8596186..88a6d44e 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -36,7 +36,7 @@ use stackable_operator::{ kvp::Label, product_logging::framework::calculate_log_volume_size_limit, v2::{ - builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, + builder::pod::container::EnvVarSet, product_logging::framework::vector_container, role_group_utils::ResourceNames, types::operator::{RoleGroupName, RoleName}, @@ -170,11 +170,8 @@ pub(crate) fn server_config_map( cm_builder .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(validated) - .name(&cm_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(SparkConnectRole::Server)) + validated + .object_meta(&cm_name, SparkConnectRole::Server) .build(), ) .add_data(SPARK_DEFAULTS_FILE_NAME, spark_properties) @@ -358,11 +355,11 @@ pub(crate) fn build_stateful_set( } Ok(StatefulSet { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(scs) - .name(object_name(&scs.name_any(), SparkConnectRole::Server)) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(SparkConnectRole::Server)) + metadata: validated + .object_meta( + object_name(&scs.name_any(), SparkConnectRole::Server), + SparkConnectRole::Server, + ) .build(), spec: Some(StatefulSetSpec { template: pod_template, diff --git a/rust/operator-binary/src/connect/service.rs b/rust/operator-binary/src/connect/service.rs index f058183f..1d766195 100644 --- a/rust/operator-binary/src/connect/service.rs +++ b/rust/operator-binary/src/connect/service.rs @@ -1,9 +1,7 @@ use stackable_operator::{ - builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kube::ResourceExt, kvp::{Annotations, Labels}, - v2::builder::meta::ownerreference_from_resource, }; use crate::connect::{ @@ -28,11 +26,8 @@ pub(crate) fn build_headless_service( let selector = validated.role_selector(SparkConnectRole::Server).into(); Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(scs) - .name(service_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(SparkConnectRole::Server)) + metadata: validated + .object_meta(service_name, SparkConnectRole::Server) .build(), spec: Some(ServiceSpec { type_: Some("ClusterIP".to_owned()), @@ -74,11 +69,8 @@ pub(crate) fn build_metrics_service( let selector = validated.role_selector(SparkConnectRole::Server).into(); Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(scs) - .name(service_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels(SparkConnectRole::Server)) + metadata: validated + .object_meta(service_name, SparkConnectRole::Server) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), From 3257652b3b01b6b11e1c760e246362de667c5565 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:35:30 +0200 Subject: [PATCH 44/50] refactor(job): build child ObjectMeta via a shared object_meta helper --- .../src/spark_k8s_controller.rs | 40 +++---------------- .../src/spark_k8s_controller/validate.rs | 15 +++++++ 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 2bfb40ad..66ddce10 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -696,14 +696,7 @@ fn pod_template_config_map( let mut cm_builder = ConfigMapBuilder::new(); cm_builder - .metadata( - ObjectMetaBuilder::new() - .namespace(validated.namespace.clone()) - .name(&cm_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels("pod-templates")) - .build(), - ) + .metadata(validated.object_meta(&cm_name, "pod-templates").build()) .add_data( POD_TEMPLATE_FILE, serde_yaml::to_string(&template).context(PodTemplateSerdeSnafu)?, @@ -742,14 +735,7 @@ fn submit_job_config_map( let mut cm_builder = ConfigMapBuilder::new(); - cm_builder.metadata( - ObjectMetaBuilder::new() - .namespace(validated.namespace.clone()) - .name(&cm_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels("spark-submit")) - .build(), - ); + cm_builder.metadata(validated.object_meta(&cm_name, "spark-submit").build()); cm_builder.add_data( SPARK_ENV_SH_FILE_NAME, @@ -876,11 +862,8 @@ fn spark_job( } let job = Job { - metadata: ObjectMetaBuilder::new() - .name(validated.name.to_string()) - .namespace(validated.namespace.clone()) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels("spark-job")) + metadata: validated + .object_meta(validated.name.to_string(), "spark-job") .build(), spec: Some(JobSpec { template: pod, @@ -901,25 +884,14 @@ fn spark_job( fn build_spark_role_serviceaccount( validated: &validate::ValidatedSparkApplication, ) -> Result<(ServiceAccount, RoleBinding)> { - let spark_app = &validated.spark_application; let sa_name = validated.name.to_string(); let sa = ServiceAccount { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(spark_app) - .name(&sa_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels("service-account")) - .build(), + metadata: validated.object_meta(&sa_name, "service-account").build(), ..ServiceAccount::default() }; let binding_name = &sa_name; let binding = RoleBinding { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(spark_app) - .name(binding_name) - .ownerreference(ownerreference_from_resource(validated, None, Some(true))) - .with_labels(validated.recommended_labels("role-binding")) - .build(), + metadata: validated.object_meta(binding_name, "role-binding").build(), role_ref: RoleRef { api_group: ClusterRole::GROUP.to_string(), kind: ClusterRole::KIND.to_string(), diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index 36ba1976..3274191a 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -7,6 +7,7 @@ use std::{borrow::Cow, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, cli::OperatorEnvironmentOptions, commons::{ product_image_selection::{self, ResolvedProductImage}, @@ -18,6 +19,7 @@ use stackable_operator::{ kvp::Labels, v2::{ HasName, HasUid, NameIsValidLabelValue, + builder::meta::ownerreference_from_resource, controller_utils::{get_cluster_name, get_namespace, get_uid}, kvp::label::recommended_labels, types::{ @@ -155,6 +157,19 @@ impl ValidatedSparkApplication { &role_group, ) } + + /// Object metadata for a child resource named `name`, owned by this SparkApplication and + /// carrying the recommended labels for the given `role`. Returns the builder so callers can add + /// extra labels before building. + pub(crate) fn object_meta(&self, name: impl Into, role: &str) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .namespace(self.namespace.clone()) + .name(name) + .ownerreference(ownerreference_from_resource(self, None, Some(true))) + .with_labels(self.recommended_labels(role)); + builder + } } /// The product name (`spark-k8s`) as a type-safe label value. From 2df3717ff6bf2ecd3a9c49d430cfcb37eec6509c Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:50:50 +0200 Subject: [PATCH 45/50] refactor(connect): use v2 role_utils JavaCommonConfig and apply_to for JVM args --- rust/operator-binary/src/connect/common.rs | 35 ++++++-------------- rust/operator-binary/src/connect/crd.rs | 4 +-- rust/operator-binary/src/connect/executor.rs | 10 ++---- rust/operator-binary/src/connect/server.rs | 13 ++------ 4 files changed, 17 insertions(+), 45 deletions(-) diff --git a/rust/operator-binary/src/connect/common.rs b/rust/operator-binary/src/connect/common.rs index dd95371b..85cd8b8e 100644 --- a/rust/operator-binary/src/connect/common.rs +++ b/rust/operator-binary/src/connect/common.rs @@ -1,9 +1,9 @@ use std::collections::BTreeMap; use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - role_utils::{JavaCommonConfig, JvmArgumentOverrides}, - v2::config_file_writer::{PropertiesWriterError, to_java_properties_string}, +use stackable_operator::v2::{ + config_file_writer::{PropertiesWriterError, to_java_properties_string}, + role_utils::JavaCommonConfig, }; use strum::Display; @@ -19,11 +19,6 @@ use crate::{ #[derive(Snafu, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("failed to merge jvm argument overrides"))] - MergeJvmArgumentOverrides { - source: stackable_operator::role_utils::Error, - }, - #[snafu(display("failed to serialize spark properties"))] SparkProperties { source: PropertiesWriterError }, @@ -48,22 +43,14 @@ pub(crate) fn object_name(stacklet_name: &str, role: SparkConnectRole) -> String } } -// Returns the jvm arguments a user has provided merged with the operator props. -pub(crate) fn jvm_args( - jvm_args: &[String], - user_java_config: Option<&JavaCommonConfig>, -) -> Result { - if let Some(user_jvm_props) = user_java_config { - let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args.to_vec()); - let mut user_jvm_props_copy = user_jvm_props.jvm_argument_overrides.clone(); - user_jvm_props_copy - .try_merge(&operator_generated) - .context(MergeJvmArgumentOverridesSnafu)?; - Ok(user_jvm_props_copy - .effective_jvm_config_after_merging() - .join(" ")) - } else { - Ok(jvm_args.join(" ")) +// Returns the operator-generated jvm arguments with the user-provided overrides applied on top. +pub(crate) fn jvm_args(jvm_args: &[String], user_java_config: Option<&JavaCommonConfig>) -> String { + match user_java_config { + Some(user) => user + .jvm_argument_overrides + .apply_to(jvm_args.iter().cloned()) + .join(" "), + None => jvm_args.join(" "), } } diff --git a/rust/operator-binary/src/connect/crd.rs b/rust/operator-binary/src/connect/crd.rs index 84b86476..c197facf 100644 --- a/rust/operator-binary/src/connect/crd.rs +++ b/rust/operator-binary/src/connect/crd.rs @@ -27,11 +27,11 @@ use stackable_operator::{ CustomContainerLogConfig, Logging, }, }, - role_utils::{CommonConfiguration, JavaCommonConfig}, + role_utils::CommonConfiguration, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, - v2::types::common::Port, + v2::{role_utils::JavaCommonConfig, types::common::Port}, versioned::versioned, }; use strum::{Display, EnumIter}; diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/executor.rs index e19cdcc4..c787ace0 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/executor.rs @@ -52,9 +52,6 @@ pub enum Error { source: builder::pod::container::Error, }, - #[snafu(display("failed build connect executor jvm args for {name}"))] - ExecutorJvmArgs { source: common::Error, name: String }, - #[snafu(display("failed build connect executor security properties"))] ExecutorJvmSecurityProperties { source: common::Error }, @@ -230,7 +227,7 @@ pub(crate) fn executor_properties( ), ( "spark.executor.defaultJavaOptions".to_string(), - Some(executor_jvm_args(scs, config)?), + Some(executor_jvm_args(scs, config)), ), ( "spark.kubernetes.executor.podTemplateFile".to_string(), @@ -292,7 +289,7 @@ pub(crate) fn executor_properties( fn executor_jvm_args( scs: &v1alpha1::SparkConnectServer, config: &v1alpha1::ExecutorConfig, -) -> Result { +) -> String { let mut jvm_args = vec![format!( "-Djava.security.properties={VOLUME_MOUNT_PATH_CONFIG}/{JVM_SECURITY_PROPERTIES_FILE}" )]; @@ -310,9 +307,6 @@ fn executor_jvm_args( .as_ref() .map(|s| &s.product_specific_common_config), ) - .context(ExecutorJvmArgsSnafu { - name: scs.name_any(), - }) } // Assemble the configuration of the spark-connect executor. diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 88a6d44e..6067cad7 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -115,9 +115,6 @@ pub enum Error { source: builder::pod::container::Error, }, - #[snafu(display("failed build connect server jvm args for {name}"))] - ServerJvmArgs { source: common::Error, name: String }, - #[snafu(display("failed to build S3 volumes and mounts for the server"))] BuildS3VolumesAndMounts { source: s3::Error }, @@ -475,7 +472,7 @@ pub(crate) fn server_properties( ), ( "spark.driver.defaultJavaOptions".to_string(), - Some(server_jvm_args(scs, config)?), + Some(server_jvm_args(scs, config)), ), ( "spark.driver.extraClassPath".to_string(), @@ -502,10 +499,7 @@ pub(crate) fn server_properties( Ok(result) } -fn server_jvm_args( - scs: &v1alpha1::SparkConnectServer, - config: &v1alpha1::ServerConfig, -) -> Result { +fn server_jvm_args(scs: &v1alpha1::SparkConnectServer, config: &v1alpha1::ServerConfig) -> String { let mut jvm_args = vec![format!( "-Djava.security.properties={VOLUME_MOUNT_PATH_CONFIG}/{JVM_SECURITY_PROPERTIES_FILE}" )]; @@ -524,9 +518,6 @@ fn server_jvm_args( .as_ref() .map(|s| &s.product_specific_common_config), ) - .context(ServerJvmArgsSnafu { - name: scs.name_any(), - }) } fn probe() -> Probe { From d18e370085a70afa99cb4fe794c13ef19a21a81f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 15:59:34 +0200 Subject: [PATCH 46/50] refactor(job): use v2 role_utils JavaCommonConfig and apply_to for JVM args --- rust/operator-binary/src/config/jvm.rs | 66 ++++++++++---------------- rust/operator-binary/src/crd/mod.rs | 10 ++-- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/rust/operator-binary/src/config/jvm.rs b/rust/operator-binary/src/config/jvm.rs index 52968674..cc5388b8 100644 --- a/rust/operator-binary/src/config/jvm.rs +++ b/rust/operator-binary/src/config/jvm.rs @@ -1,8 +1,4 @@ -use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - crd::s3, - role_utils::{self, JvmArgumentOverrides}, -}; +use stackable_operator::crd::s3; use crate::crd::{ constants::{ @@ -14,20 +10,17 @@ use crate::crd::{ v1alpha1::SparkApplication, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to merge jvm argument overrides"))] - MergeJvmArgumentOverrides { source: role_utils::Error }, -} - /// JVM arguments that go into /// 1. `spark.driver.extraJavaOptions` /// 2. `spark.executor.extraJavaOptions` +/// +/// Returns `(driver, executor)`: the operator-generated base arguments with the role's +/// `jvmArgumentOverrides` applied on top. pub fn construct_extra_java_options( spark_application: &SparkApplication, s3_conn: &Option, log_dir: &Option, -) -> Result<(String, String), Error> { +) -> (String, String) { // Note (@sbernauer): As of 2025-03-04, we did not set any heap related JVM arguments, so I // kept the implementation as is. We can always re-visit this as needed. @@ -43,36 +36,27 @@ pub fn construct_extra_java_options( ]); } - let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args); - let from_driver = match &spark_application.spec.driver { - Some(driver) => &driver.product_specific_common_config.jvm_argument_overrides, - None => &JvmArgumentOverrides::default(), + // The role's `jvmArgumentOverrides` are applied on top of the operator-generated arguments + // above. Note this is not purely additive: a role may also remove or replace operator-set + // arguments (e.g. a `removeRegex` dropping the `-Djava.security.properties` default) — see the + // unit tests below. + let driver = match &spark_application.spec.driver { + Some(driver) => driver + .product_specific_common_config + .jvm_argument_overrides + .apply_to(jvm_args.clone()), + None => jvm_args.clone(), }; - let from_executor = match &spark_application.spec.executor { - Some(executor) => { - &executor - .config - .product_specific_common_config - .jvm_argument_overrides - } - None => &JvmArgumentOverrides::default(), + let executor = match &spark_application.spec.executor { + Some(executor) => executor + .config + .product_specific_common_config + .jvm_argument_overrides + .apply_to(jvm_args.clone()), + None => jvm_args.clone(), }; - // Please note that the merge order is different than we normally do! - // This is not trivial, as the merge operation is not purely additive (as it is with e.g. `PodTemplateSpec). - let mut from_driver = from_driver.clone(); - let mut from_executor = from_executor.clone(); - from_driver - .try_merge(&operator_generated) - .context(MergeJvmArgumentOverridesSnafu)?; - from_executor - .try_merge(&operator_generated) - .context(MergeJvmArgumentOverridesSnafu)?; - - Ok(( - from_driver.effective_jvm_config_after_merging().join(" "), - from_executor.effective_jvm_config_after_merging().join(" "), - )) + (driver.join(" "), executor.join(" ")) } #[cfg(test)] @@ -97,7 +81,7 @@ mod tests { let spark_app: SparkApplication = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let (driver_extra_java_options, executor_extra_java_options) = - construct_extra_java_options(&spark_app, &None, &None).unwrap(); + construct_extra_java_options(&spark_app, &None, &None); assert_eq!( driver_extra_java_options, @@ -137,7 +121,7 @@ mod tests { let spark_app: SparkApplication = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let (driver_extra_java_options, executor_extra_java_options) = - construct_extra_java_options(&spark_app, &None, &None).unwrap(); + construct_extra_java_options(&spark_app, &None, &None); assert_eq!( driver_extra_java_options, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 90973498..d9974819 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -32,11 +32,11 @@ use stackable_operator::{ kube::{CustomResource, ResourceExt}, memory::{BinaryMultiple, MemoryQuantity}, product_logging, - role_utils::{CommonConfiguration, JavaCommonConfig, RoleGroup}, + role_utils::{CommonConfiguration, RoleGroup}, schemars::{self, JsonSchema}, shared::time::Duration, utils::crds::raw_object_list_schema, - v2::config_overrides::KeyValueConfigOverrides, + v2::{config_overrides::KeyValueConfigOverrides, role_utils::JavaCommonConfig}, versioned::versioned, }; @@ -111,9 +111,6 @@ pub enum Error { #[snafu(display("failed to configure log directory"))] ConfigureLogDir { source: logdir::Error }, - - #[snafu(display("failed to construct JVM arguments"))] - ConstructJvmArguments { source: crate::config::jvm::Error }, } pub type SparkApplicationJobRoleType = @@ -658,8 +655,7 @@ impl v1alpha1::SparkApplication { } let (driver_extra_java_options, executor_extra_java_options) = - construct_extra_java_options(self, s3conn, log_dir) - .context(ConstructJvmArgumentsSnafu)?; + construct_extra_java_options(self, s3conn, log_dir); submit_cmd.extend(vec![ format!("--conf spark.driver.extraJavaOptions=\"{driver_extra_java_options}\""), format!("--conf spark.executor.extraJavaOptions=\"{executor_extra_java_options}\""), From 7f62dbf1e41108c6691388df5aa9118ad9632745 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 16:14:20 +0200 Subject: [PATCH 47/50] refactor(history): group validated state into ValidatedClusterConfig + ValidatedRoleConfig --- .../operator-binary/src/history/controller.rs | 13 +++-- .../src/history/controller/validate.rs | 48 ++++++++++++++----- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index d4d43cc9..cadd33b9 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -228,7 +228,7 @@ pub async fn reconcile( &shs.spec.object_overrides, ); - let log_dir = &validated.log_dir; + let log_dir = &validated.cluster_config.log_dir; // Use a dedicated service account for history server pods. let (service_account, role_binding) = build_rbac_resources( @@ -272,7 +272,7 @@ pub async fn reconcile( let rg_group_listener = build_group_listener( &validated, HISTORY_ROLE_NAME, - shs.node_listener_class().to_string(), + validated.role_config.listener_class.clone(), ); cluster_resources @@ -280,8 +280,7 @@ pub async fn reconcile( .await .context(ApplyGroupListenerSnafu)?; - let role_config = &shs.spec.nodes.role_config; - if let Some(pdb) = build_pdb(&role_config.common.pod_disruption_budget, &validated) { + if let Some(pdb) = build_pdb(&validated.role_config.pdb, &validated) { cluster_resources .add(client, pdb) .await @@ -598,13 +597,13 @@ fn spark_defaults( validated: &validate::ValidatedSparkHistoryServer, role_group_name: &RoleGroupName, ) -> BTreeMap> { - let mut default_properties = validated.log_dir_settings.clone(); + let mut default_properties = validated.cluster_config.log_dir_settings.clone(); // add cleaner spark settings if requested default_properties.extend(cleaner_config(validated, role_group_name)); // add user provided configuration. These can overwrite everything. - default_properties.extend(validated.spark_conf.clone()); + default_properties.extend(validated.cluster_config.spark_conf.clone()); default_properties .into_iter() @@ -640,7 +639,7 @@ fn cleaner_config( validated: &validate::ValidatedSparkHistoryServer, role_group_name: &RoleGroupName, ) -> BTreeMap { - match validated.cleaner_rolegroup_name.as_ref() { + match validated.cluster_config.cleaner_rolegroup_name.as_ref() { Some(cleaner_rolegroup) if cleaner_rolegroup == role_group_name.as_ref() => { BTreeMap::from([( "spark.history.fs.cleaner.enabled".to_string(), diff --git a/rust/operator-binary/src/history/controller/validate.rs b/rust/operator-binary/src/history/controller/validate.rs index 5d965846..85585652 100644 --- a/rust/operator-binary/src/history/controller/validate.rs +++ b/rust/operator-binary/src/history/controller/validate.rs @@ -9,7 +9,10 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, cli::OperatorEnvironmentOptions, - commons::product_image_selection::{self, ResolvedProductImage}, + commons::{ + pdb::PdbConfig, + product_image_selection::{self, ResolvedProductImage}, + }, config::fragment, k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource, @@ -175,16 +178,27 @@ pub struct ValidatedSparkHistoryServer { /// `app.kubernetes.io/version` label. Derived from the resolved image's app version label /// value. pub product_version: ProductVersion, + pub resolved_product_image: ResolvedProductImage, + pub cluster_config: ValidatedClusterConfig, + pub role_config: ValidatedRoleConfig, + pub role_groups: BTreeMap, +} + +/// Cluster-wide settings resolved during validation and dereferencing, so the resource builders +/// never have to read the raw [`v1alpha1::SparkHistoryServer`] spec. +pub struct ValidatedClusterConfig { pub cleaner_rolegroup_name: Option, pub spark_conf: BTreeMap, - pub resolved_product_image: ResolvedProductImage, - // These two are a bit redundant right now. - // This is a temporary situation until we remove all v1alpha1::SparkHistoryServer usages after validation. - // Currently log_dir_settings is needed for history::controller::build_configmap() function whereas log_dir - // is needed for command args and volume mounts. + /// The resolved log directory. pub log_dir: ResolvedLogDir, + /// Spark configuration properties that configure event logging into the `log_dir`. pub log_dir_settings: BTreeMap, - pub role_groups: BTreeMap, +} + +/// Per-role configuration extracted during validation. +pub struct ValidatedRoleConfig { + pub pdb: PdbConfig, + pub listener_class: String, } impl ValidatedSparkHistoryServer { @@ -413,11 +427,23 @@ pub fn validate( namespace, uid, product_version, - cleaner_rolegroup_name, - spark_conf: shs.spec.spark_conf.clone(), - log_dir: dereferenced.log_dir, - log_dir_settings, resolved_product_image, + cluster_config: ValidatedClusterConfig { + cleaner_rolegroup_name, + spark_conf: shs.spec.spark_conf.clone(), + log_dir: dereferenced.log_dir, + log_dir_settings, + }, + role_config: ValidatedRoleConfig { + pdb: shs + .spec + .nodes + .role_config + .common + .pod_disruption_budget + .clone(), + listener_class: shs.node_listener_class().to_string(), + }, role_groups, }) } From 04b8ab64da3afeb7f023669952f05c2afc814764 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 16:21:22 +0200 Subject: [PATCH 48/50] refactor(connect,job): group validated state into ValidatedClusterConfig (+ ValidatedRoleConfig for connect) --- .../operator-binary/src/connect/controller.rs | 6 ++---- .../src/connect/controller/validate.rs | 21 +++++++++++++++++-- rust/operator-binary/src/connect/server.rs | 3 +-- .../src/spark_k8s_controller.rs | 6 +++--- .../src/spark_k8s_controller/validate.rs | 16 ++++++++++---- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index 526e8593..a31db31a 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -175,9 +175,7 @@ pub async fn reconcile( let server_config = &validated.server_config; let executor_config = &validated.executor_config; let resolved_product_image = &validated.resolved_product_image; - let resolved_s3 = &validated.resolved_s3; - - let server_role_config = &scs.spec.server.role_config; + let resolved_s3 = &validated.cluster_config.resolved_s3; let mut cluster_resources = cluster_resources_new( &validate::product_name(), @@ -309,7 +307,7 @@ pub async fn reconcile( // ======================================== // Server listener - let listener = server::build_listener(&validated, server_role_config); + let listener = server::build_listener(&validated); let applied_listener = cluster_resources .add(client, listener) diff --git a/rust/operator-binary/src/connect/controller/validate.rs b/rust/operator-binary/src/connect/controller/validate.rs index f565b427..ca0230c5 100644 --- a/rust/operator-binary/src/connect/controller/validate.rs +++ b/rust/operator-binary/src/connect/controller/validate.rs @@ -138,13 +138,25 @@ pub struct ValidatedSparkConnectServer { pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, - pub resolved_s3: ResolvedS3, pub resolved_product_image: ResolvedProductImage, + pub cluster_config: ValidatedClusterConfig, + pub role_config: ValidatedRoleConfig, pub server_config: v1alpha1::ServerConfig, pub server_logging: ValidatedLogging, pub executor_config: v1alpha1::ExecutorConfig, } +/// Cluster-wide settings resolved during validation, so the resource builders never have to read +/// the raw [`v1alpha1::SparkConnectServer`] spec. +pub struct ValidatedClusterConfig { + pub resolved_s3: ResolvedS3, +} + +/// Per-role configuration extracted during validation (Spark Connect exposes only the server role). +pub struct ValidatedRoleConfig { + pub listener_class: String, +} + impl ValidatedSparkConnectServer { /// Recommended labels for a resource of the given role. pub(crate) fn recommended_labels(&self, role: SparkConnectRole) -> Labels { @@ -307,8 +319,13 @@ pub fn validate( name, namespace, uid, - resolved_s3: dereferenced.resolved_s3, resolved_product_image, + cluster_config: ValidatedClusterConfig { + resolved_s3: dereferenced.resolved_s3, + }, + role_config: ValidatedRoleConfig { + listener_class: scs.spec.server.role_config.listener_class.clone(), + }, server_config, server_logging, executor_config, diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/server.rs index 6067cad7..1dbb5956 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/server.rs @@ -535,7 +535,6 @@ fn probe() -> Probe { pub(crate) fn build_listener( validated: &ValidatedSparkConnectServer, - role_config: &v1alpha1::SparkConnectServerRoleConfig, ) -> listener::v1alpha1::Listener { let listener_name = format!( "{cluster}-{role}", @@ -543,7 +542,7 @@ pub(crate) fn build_listener( role = SparkConnectRole::Server ); - let listener_class = role_config.listener_class.clone(); + let listener_class = validated.role_config.listener_class.clone(); let recommended_object_labels = validated.recommended_labels(SparkConnectRole::Server); let listener_ports = [ diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 66ddce10..cfd5ca23 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -212,8 +212,8 @@ pub async fn reconcile( .context(ValidateSparkApplicationSnafu)?; let spark_application = &validated.spark_application; - let opt_s3conn = &validated.s3_connection; - let logdir = &validated.log_dir; + let opt_s3conn = &validated.cluster_config.s3_connection; + let logdir = &validated.cluster_config.log_dir; let resolved_product_image = &validated.resolved_product_image; // This is the final version of the spark app to reconcile. // No more mutating operations after this point (except for status). @@ -342,7 +342,7 @@ pub async fn reconcile( spark_application, &v1alpha1::SparkApplicationStatus { phase: "Unknown".to_string(), - resolved_template_ref: validated.resolved_template_refs.clone(), + resolved_template_ref: validated.cluster_config.resolved_template_refs.clone(), }, ) .await diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index 3274191a..1166ce92 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -79,10 +79,16 @@ pub struct ValidatedSparkApplication { pub uid: Uid, // Still carried in full because `reconcile` builds the submit/driver pod from the whole spec. pub spark_application: v1alpha1::SparkApplication, + pub resolved_product_image: ResolvedProductImage, + pub cluster_config: ValidatedClusterConfig, +} + +/// Cluster-wide settings resolved during validation and dereferencing, so the resource builders +/// can use the resolved values rather than re-resolving from the raw spec. +pub struct ValidatedClusterConfig { pub resolved_template_refs: Vec, pub s3_connection: Option, pub log_dir: Option, - pub resolved_product_image: ResolvedProductImage, } impl NameIsValidLabelValue for ValidatedSparkApplication { @@ -223,10 +229,12 @@ pub fn validate( namespace, uid, spark_application: dereferenced.spark_application, - resolved_template_refs: dereferenced.resolved_template_refs, - s3_connection: dereferenced.s3_connection, - log_dir: dereferenced.log_dir, resolved_product_image, + cluster_config: ValidatedClusterConfig { + resolved_template_refs: dereferenced.resolved_template_refs, + s3_connection: dereferenced.s3_connection, + log_dir: dereferenced.log_dir, + }, }) } From 08fc433edb6434611effd9c08c0aef8332ed84e1 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 16:40:34 +0200 Subject: [PATCH 49/50] refactor(job): thread ValidatedSparkApplication into init_containers; document the retained raw CR --- rust/operator-binary/src/spark_k8s_controller.rs | 11 +++-------- .../src/spark_k8s_controller/validate.rs | 9 ++++++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index cfd5ca23..953dd5a0 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -354,12 +354,13 @@ pub async fn reconcile( } fn init_containers( - spark_application: &v1alpha1::SparkApplication, + validated: &validate::ValidatedSparkApplication, logging: &Logging, s3conn: &Option, logdir: &Option, spark_image: &ResolvedProductImage, ) -> Result> { + let spark_application = &validated.spark_application; let mut jcb = ContainerBuilder::new(&SparkContainer::Job.to_string()) .context(IllegalContainerNameSnafu)?; let job_container = match &spark_application.spec.image { @@ -579,13 +580,7 @@ fn pod_template( .affinity(&config.affinity) .service_account_name(service_account.name_any()); - let init_containers = init_containers( - spark_application, - &config.logging, - s3conn, - logdir, - spark_image, - )?; + let init_containers = init_containers(validated, &config.logging, s3conn, logdir, spark_image)?; for init_container in init_containers { pb.add_init_container(init_container.clone()); diff --git a/rust/operator-binary/src/spark_k8s_controller/validate.rs b/rust/operator-binary/src/spark_k8s_controller/validate.rs index 1166ce92..8c0d47bc 100644 --- a/rust/operator-binary/src/spark_k8s_controller/validate.rs +++ b/rust/operator-binary/src/spark_k8s_controller/validate.rs @@ -77,7 +77,14 @@ pub struct ValidatedSparkApplication { pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, - // Still carried in full because `reconcile` builds the submit/driver pod from the whole spec. + /// The full source spec. + /// + /// Unlike the other operators' validated types, a `SparkApplication` cannot be reduced to + /// resolved fields: it has no Stackable role/role-group model to decompose into. The submit, + /// driver and executor pods are built directly from the whole spec, and the per-role helpers + /// (`merged_env`, `pod_overrides`, `requirements`, …) read it at build time. Resolving all of + /// that up front would duplicate the spec rather than replace it, so the raw CR is retained + /// here as the deliberate "too interwoven to refactor" exception. pub spark_application: v1alpha1::SparkApplication, pub resolved_product_image: ResolvedProductImage, pub cluster_config: ValidatedClusterConfig, From 8ec3bf2c220e1fdaaec805b7d5e51a6e660a599a Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 16 Jun 2026 17:29:50 +0200 Subject: [PATCH 50/50] refactor: move resource builders into controller/build modules and drop redundant params --- .../operator-binary/src/connect/controller.rs | 12 +- .../{ => controller/build}/executor.rs | 12 +- .../src/connect/controller/build/mod.rs | 10 + .../connect/{ => controller/build}/server.rs | 8 +- .../connect/{ => controller/build}/service.rs | 0 rust/operator-binary/src/connect/mod.rs | 3 - .../operator-binary/src/history/controller.rs | 412 +----------- .../src/history/controller/build/mod.rs | 1 + .../controller/build/resource/config_map.rs | 130 ++++ .../controller/build/resource/listener.rs | 43 ++ .../history/controller/build/resource/mod.rs | 5 + .../build/resource}/pdb.rs | 0 .../build/resource}/service.rs | 0 .../controller/build/resource/statefulset.rs | 272 ++++++++ rust/operator-binary/src/history/mod.rs | 2 - .../src/history/operations/mod.rs | 1 - .../src/spark_k8s_controller.rs | 634 +----------------- .../src/spark_k8s_controller/build/mod.rs | 2 + .../src/spark_k8s_controller/build/pod.rs | 331 +++++++++ .../build/resource/config_map.rs | 138 ++++ .../build/resource/job.rs | 145 ++++ .../build/resource/mod.rs | 3 + .../build/resource/serviceaccount.rs | 42 ++ 23 files changed, 1162 insertions(+), 1044 deletions(-) rename rust/operator-binary/src/connect/{ => controller/build}/executor.rs (98%) create mode 100644 rust/operator-binary/src/connect/controller/build/mod.rs rename rust/operator-binary/src/connect/{ => controller/build}/server.rs (99%) rename rust/operator-binary/src/connect/{ => controller/build}/service.rs (100%) create mode 100644 rust/operator-binary/src/history/controller/build/mod.rs create mode 100644 rust/operator-binary/src/history/controller/build/resource/config_map.rs create mode 100644 rust/operator-binary/src/history/controller/build/resource/listener.rs create mode 100644 rust/operator-binary/src/history/controller/build/resource/mod.rs rename rust/operator-binary/src/history/{operations => controller/build/resource}/pdb.rs (100%) rename rust/operator-binary/src/history/{ => controller/build/resource}/service.rs (100%) create mode 100644 rust/operator-binary/src/history/controller/build/resource/statefulset.rs delete mode 100644 rust/operator-binary/src/history/operations/mod.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/mod.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/pod.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/resource/config_map.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/resource/job.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/resource/mod.rs create mode 100644 rust/operator-binary/src/spark_k8s_controller/build/resource/serviceaccount.rs diff --git a/rust/operator-binary/src/connect/controller.rs b/rust/operator-binary/src/connect/controller.rs index a31db31a..5c57e2a7 100644 --- a/rust/operator-binary/src/connect/controller.rs +++ b/rust/operator-binary/src/connect/controller.rs @@ -22,10 +22,15 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use super::crd::{CONNECT_APP_NAME, v1alpha1}; use crate::{ Ctx, - connect::{common, crd::SparkConnectServerStatus, executor, server, service}, + connect::{ + common, + controller::build::{executor, server, service}, + crd::SparkConnectServerStatus, + }, crd::constants::OPERATOR_NAME, }; +pub mod build; pub mod dereference; pub mod validate; @@ -120,7 +125,7 @@ pub enum Error { #[snafu(display("failed to build connect executor pod template"))] ExecutorPodTemplate { - source: crate::connect::executor::Error, + source: crate::connect::controller::build::executor::Error, }, #[snafu(display("failed to serialize executor pod template"))] @@ -320,13 +325,10 @@ pub async fn reconcile( let stateful_set = server::build_stateful_set( &validated, scs, - server_config, - resolved_product_image, &service_account, &server_config_map, &applied_listener.name_any(), args, - resolved_s3, ) .context(BuildServerStatefulSetSnafu)?; diff --git a/rust/operator-binary/src/connect/executor.rs b/rust/operator-binary/src/connect/controller/build/executor.rs similarity index 98% rename from rust/operator-binary/src/connect/executor.rs rename to rust/operator-binary/src/connect/controller/build/executor.rs index c787ace0..bf5ec676 100644 --- a/rust/operator-binary/src/connect/executor.rs +++ b/rust/operator-binary/src/connect/controller/build/executor.rs @@ -20,13 +20,13 @@ use stackable_operator::{ product_logging::framework::calculate_log_volume_size_limit, }; -use super::{ - common::{SparkConnectRole, object_name}, - controller::validate::ValidatedSparkConnectServer, - crd::SparkConnectContainer, -}; use crate::{ - connect::{common, crd::v1alpha1, s3}, + connect::{ + common::{self, SparkConnectRole, object_name}, + controller::validate::ValidatedSparkConnectServer, + crd::{SparkConnectContainer, v1alpha1}, + s3, + }, crd::constants::{ JVM_SECURITY_PROPERTIES_FILE, LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, METRICS_PROPERTIES_FILE, POD_TEMPLATE_FILE, VOLUME_MOUNT_NAME_CONFIG, diff --git a/rust/operator-binary/src/connect/controller/build/mod.rs b/rust/operator-binary/src/connect/controller/build/mod.rs new file mode 100644 index 00000000..ff9c47cc --- /dev/null +++ b/rust/operator-binary/src/connect/controller/build/mod.rs @@ -0,0 +1,10 @@ +//! Builders that turn a `ValidatedSparkConnectServer` into Kubernetes resources. +//! +//! These are grouped by role (`server`, `executor`) rather than by resource kind: each Spark +//! Connect role bundles a cohesive set of builders — its ConfigMap, StatefulSet/pod template, +//! Spark properties, environment variables and JVM arguments — so keeping a role's builders +//! together in one module is clearer than scattering them across per-kind modules. + +pub(crate) mod executor; +pub(crate) mod server; +pub(crate) mod service; diff --git a/rust/operator-binary/src/connect/server.rs b/rust/operator-binary/src/connect/controller/build/server.rs similarity index 99% rename from rust/operator-binary/src/connect/server.rs rename to rust/operator-binary/src/connect/controller/build/server.rs index 1dbb5956..fb7ea7d6 100644 --- a/rust/operator-binary/src/connect/server.rs +++ b/rust/operator-binary/src/connect/controller/build/server.rs @@ -190,18 +190,18 @@ pub(crate) fn server_config_map( .context(InvalidConfigMapSnafu { name: cm_name }) } -#[allow(clippy::too_many_arguments)] pub(crate) fn build_stateful_set( validated: &ValidatedSparkConnectServer, scs: &v1alpha1::SparkConnectServer, - config: &v1alpha1::ServerConfig, - resolved_product_image: &ResolvedProductImage, service_account: &ServiceAccount, config_map: &ConfigMap, listener_name: &str, args: Vec, - resolved_s3: &s3::ResolvedS3, ) -> Result { + let config = &validated.server_config; + let resolved_product_image = &validated.resolved_product_image; + let resolved_s3 = &validated.cluster_config.resolved_s3; + let recommended_labels = validated.recommended_labels(SparkConnectRole::Server); let metadata = ObjectMetaBuilder::new() diff --git a/rust/operator-binary/src/connect/service.rs b/rust/operator-binary/src/connect/controller/build/service.rs similarity index 100% rename from rust/operator-binary/src/connect/service.rs rename to rust/operator-binary/src/connect/controller/build/service.rs diff --git a/rust/operator-binary/src/connect/mod.rs b/rust/operator-binary/src/connect/mod.rs index daab765f..0daf16c5 100644 --- a/rust/operator-binary/src/connect/mod.rs +++ b/rust/operator-binary/src/connect/mod.rs @@ -1,10 +1,7 @@ mod common; pub mod controller; pub mod crd; -mod executor; mod s3; -pub mod server; -mod service; pub(crate) const GRPC: &str = "grpc"; pub(crate) const HTTP: &str = "http"; diff --git a/rust/operator-binary/src/history/controller.rs b/rust/operator-binary/src/history/controller.rs index cadd33b9..9a1a9c83 100644 --- a/rust/operator-binary/src/history/controller.rs +++ b/rust/operator-binary/src/history/controller.rs @@ -1,82 +1,34 @@ -use std::{collections::BTreeMap, str::FromStr, sync::Arc}; +use std::sync::Arc; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{ - self, - configmap::ConfigMapBuilder, - meta::ObjectMetaBuilder, - pod::{PodBuilder, container::ContainerBuilder, volume::VolumeBuilder}, - }, + builder::{self}, cluster_resources::ClusterResourceApplyStrategy, commons::rbac::build_rbac_resources, - crd::listener, - k8s_openapi::{ - DeepMerge, - api::{ - apps::v1::{StatefulSet, StatefulSetSpec}, - core::v1::{ConfigMap, PodSecurityContext, ServiceAccount}, - }, - apimachinery::pkg::apis::meta::v1::LabelSelector, - }, kube::{ - ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, logging::controller::ReconcilerError, - product_logging::{ - framework::calculate_log_volume_size_limit, - spec::{ - ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, - CustomContainerLogConfig, - }, - }, shared::time::Duration, - v2::{ - builder::{ - meta::ownerreference_from_resource, - pod::{ - container::{EnvVarName, EnvVarSet}, - volume::{ListenerReference, listener_operator_volume_source_builder_build_pvc}, - }, - }, - cluster_resources::cluster_resources_new, - config_file_writer::{PropertiesWriterError, to_java_properties_string}, - product_logging::framework::vector_container, - types::{kubernetes::PersistentVolumeClaimName, operator::RoleGroupName}, - }, + v2::{cluster_resources::cluster_resources_new, config_file_writer::PropertiesWriterError}, }; use strum::{EnumDiscriminants, IntoStaticStr}; -// PVC name for the listener volume, required by the v2 listener-volume builder. Its value matches -// `LISTENER_VOLUME_NAME` in `crd::constants`. -stackable_operator::constant!(LISTENER_VOLUME_NAME_PVC: PersistentVolumeClaimName = "listener"); - use crate::{ Ctx, crd::{ - constants::{ - ACCESS_KEY_ID, HISTORY_APP_NAME, HISTORY_ROLE_NAME, HISTORY_UI_PORT, - JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, - MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, - SPARK_ENV_SH_FILE_NAME, STACKABLE_TRUST_STORE, VECTOR_CONTAINER_NAME, - VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, - VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, - VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, default_jvm_security_properties, - }, - history::{SparkHistoryServerContainer, v1alpha1}, - listener_ext, - logdir::ResolvedLogDir, - tlscerts, to_spark_env_sh_string, + constants::{HISTORY_APP_NAME, HISTORY_ROLE_NAME, JVM_SECURITY_PROPERTIES_FILE}, + history::v1alpha1, }, - history::{ - config::jvm::construct_history_jvm_args, controller::validate::ValidatedHistoryRoleGroup, - operations::pdb::build_pdb, service::build_rolegroup_metrics_service, + history::controller::build::resource::{ + config_map::build_config_map, listener::build_group_listener, pdb::build_pdb, + service::build_rolegroup_metrics_service, statefulset::build_stateful_set, }, product_logging::{self}, }; +pub mod build; pub mod dereference; pub mod validate; @@ -295,38 +247,6 @@ pub async fn reconcile( Ok(Action::await_change()) } -fn build_group_listener( - validated: &validate::ValidatedSparkHistoryServer, - role: &str, - listener_class: String, -) -> listener::v1alpha1::Listener { - let listener_name = group_listener_name(validated, role); - - // Group listeners are shared across role groups, so the role-group label is "none" (preserving - // the previous behaviour). - let recommended_object_labels = validated.recommended_labels( - &RoleGroupName::from_str("none").expect("\"none\" is a valid role group name"), - ); - - let listener_ports = [listener::v1alpha1::ListenerPort { - name: "http".to_string(), - port: HISTORY_UI_PORT.into(), - protocol: Some("TCP".to_string()), - }]; - - listener_ext::build_listener( - validated, - &listener_name, - &listener_class, - recommended_object_labels, - &listener_ports, - ) -} - -fn group_listener_name(validated: &validate::ValidatedSparkHistoryServer, role: &str) -> String { - format!("{cluster}-{role}", cluster = validated.name_any()) -} - pub fn error_policy( _obj: Arc>, error: &Error, @@ -337,315 +257,3 @@ pub fn error_policy( _ => Action::requeue(*Duration::from_secs(5)), } } - -#[allow(clippy::result_large_err)] -fn build_config_map( - validated: &validate::ValidatedSparkHistoryServer, - role_group_name: &RoleGroupName, - rg: &ValidatedHistoryRoleGroup, -) -> Result { - let cm_name = validated - .resource_names(role_group_name) - .role_group_config_map() - .to_string(); - - let spark_defaults = to_java_properties_string( - spark_defaults(validated, role_group_name) - .iter() - .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), - ) - .context(InvalidSparkDefaultsSnafu)?; - - let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend( - rg.config - .config_overrides - .security_properties - .overrides - .clone(), - ); - - let mut cm_builder = ConfigMapBuilder::new(); - - cm_builder - .metadata( - ObjectMetaBuilder::new() - .namespace(validated.namespace.clone()) - .name(&cm_name) - .ownerreference(ownerreference_from_resource( - validated, - Some(true), - Some(true), - )) - .labels(validated.recommended_labels(role_group_name)) - .build(), - ) - .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) - .add_data( - SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(rg.config.config_overrides.spark_env_sh.overrides.iter()), - ) - .add_data( - JVM_SECURITY_PROPERTIES_FILE, - to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { - JvmSecurityPropertiesSnafu { - rolegroup: role_group_name.to_string(), - } - })?, - ); - - product_logging::extend_config_map( - &rg.config.config.logging, - SparkHistoryServerContainer::SparkHistory, - &mut cm_builder, - ) - .context(InvalidLoggingConfigSnafu { cm_name: &cm_name })?; - - cm_builder - .build() - .context(InvalidConfigMapSnafu { name: cm_name }) -} - -#[allow(clippy::result_large_err)] -fn build_stateful_set( - validated: &validate::ValidatedSparkHistoryServer, - role_group_name: &RoleGroupName, - rg: &ValidatedHistoryRoleGroup, - log_dir: &ResolvedLogDir, - serviceaccount: &ServiceAccount, -) -> Result { - let resolved_product_image = &validated.resolved_product_image; - let resource_names = validated.resource_names(role_group_name); - - let log_config_map = if let Some(ContainerLogConfig { - choice: - Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { - custom: ConfigMapLogConfig { config_map }, - })), - }) = rg - .config - .config - .logging - .containers - .get(&SparkHistoryServerContainer::SparkHistory) - { - config_map.into() - } else { - resource_names.role_group_config_map().to_string() - }; - - let recommended_labels = validated.recommended_labels(role_group_name); - - let pb_metadata = ObjectMetaBuilder::new() - .with_labels(recommended_labels.clone()) - .build(); - - let mut pb = PodBuilder::new(); - - let requested_secret_lifetime = rg - .config - .config - .requested_secret_lifetime - .context(MissingSecretLifetimeSnafu)?; - pb.service_account_name(serviceaccount.name_unchecked()) - .metadata(pb_metadata) - .image_pull_secrets_from_product_image(resolved_product_image) - .add_volume( - VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) - .with_config_map(resource_names.role_group_config_map().to_string()) - .build(), - ) - .context(AddVolumeSnafu)? - .add_volume( - VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG_CONFIG) - .with_config_map(log_config_map) - .build(), - ) - .context(AddVolumeSnafu)? - .add_volume( - VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG) - .with_empty_dir( - None::, - Some(calculate_log_volume_size_limit(&[MAX_SPARK_LOG_FILES_SIZE])), - ) - .build(), - ) - .context(AddVolumeSnafu)? - .add_volumes( - log_dir - .volumes(&requested_secret_lifetime) - .context(CreateLogDirVolumesSpecSnafu)?, - ) - .context(AddVolumeSnafu)? - .security_context(PodSecurityContext { - fs_group: Some(1000), - ..PodSecurityContext::default() - }); - - // Base environment variables, with the already-merged (role + role group) env overrides - // layered on top (overrides win). The base names are static and known to be valid. - let known_env_var_name = |name: &str| { - EnvVarName::from_str(name).expect("the operator-generated env var name is valid") - }; - let merged_env = EnvVarSet::new() - .with_values([ - // Needed by the `containerdebug` running in the background of the history container - // to log it's tracing information to. - ( - known_env_var_name("CONTAINERDEBUG_LOG_DIRECTORY"), - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), - ), - // This env var prevents the history server from detaching itself from the - // start script because this leads to the Pod terminating immediately. - (known_env_var_name("SPARK_NO_DAEMONIZE"), "true".to_owned()), - ( - known_env_var_name("SPARK_DAEMON_CLASSPATH"), - "/stackable/spark/extra-jars/*".to_owned(), - ), - // JVM arguments for the history server. - ( - known_env_var_name("SPARK_HISTORY_OPTS"), - construct_history_jvm_args(&rg.config, log_dir), - ), - ]) - .merge(rg.config.env_overrides.clone()); - - let container_name = "spark-history"; - let container = ContainerBuilder::new(container_name) - .context(InvalidContainerNameSnafu)? - .image_from_product_image(resolved_product_image) - .resources(rg.config.config.resources.clone().into()) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(command_args(log_dir)) - .add_container_port("http", HISTORY_UI_PORT.into()) - .add_container_port("metrics", METRICS_PORT.into()) - .add_env_vars(merged_env) - .add_volume_mounts(log_dir.volume_mounts()) - .context(AddVolumeMountSnafu)? - .add_volume_mount(VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_PATH_CONFIG) - .context(AddVolumeMountSnafu)? - .add_volume_mount(VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_LOG_CONFIG) - .context(AddVolumeMountSnafu)? - .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) - .context(AddVolumeMountSnafu)? - .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) - .context(AddVolumeMountSnafu)? - .build(); - - // Add listener volume - // Listener endpoints for the Webserver role will use persistent volumes - // so that load balancers can hard-code the target addresses. This will - // be the case even when no class is set (and the value defaults to - // cluster-internal) as the address should still be consistent. - let volume_claim_templates = Some(vec![listener_operator_volume_source_builder_build_pvc( - &ListenerReference::Listener( - group_listener_name(validated, HISTORY_ROLE_NAME) - .parse() - .expect("the group listener name is a valid ListenerName"), - ), - &recommended_labels, - &LISTENER_VOLUME_NAME_PVC, - )]); - - pb.add_container(container); - - if let Some(vector_log_config) = &rg.logging.vector_container { - pb.add_container(vector_container( - &VECTOR_CONTAINER_NAME, - resolved_product_image, - vector_log_config, - &resource_names, - &VOLUME_MOUNT_NAME_CONFIG_TYPED, - &VOLUME_MOUNT_NAME_LOG_TYPED, - EnvVarSet::new(), - )); - } - - let mut pod_template = pb.build_template(); - pod_template.merge_from(rg.config.pod_overrides.clone()); - - let sts_metadata = validated - .object_meta( - resource_names.stateful_set_name().to_string(), - role_group_name, - ) - .build(); - - Ok(StatefulSet { - metadata: sts_metadata, - spec: Some(StatefulSetSpec { - template: pod_template, - volume_claim_templates, - replicas: Some(i32::from(rg.config.replicas)), - selector: LabelSelector { - match_labels: Some(validated.role_group_selector(role_group_name).into()), - ..LabelSelector::default() - }, - ..StatefulSetSpec::default() - }), - ..StatefulSet::default() - }) -} - -fn spark_defaults( - validated: &validate::ValidatedSparkHistoryServer, - role_group_name: &RoleGroupName, -) -> BTreeMap> { - let mut default_properties = validated.cluster_config.log_dir_settings.clone(); - - // add cleaner spark settings if requested - default_properties.extend(cleaner_config(validated, role_group_name)); - - // add user provided configuration. These can overwrite everything. - default_properties.extend(validated.cluster_config.spark_conf.clone()); - - default_properties - .into_iter() - .map(|(key, value)| (key, Some(value))) - .collect() -} - -fn command_args(logdir: &ResolvedLogDir) -> Vec { - let mut command = vec![]; - - if let Some(secret_dir) = logdir.credentials_mount_path() { - command.extend(vec![ - format!("export AWS_ACCESS_KEY_ID=\"$(cat {secret_dir}/{ACCESS_KEY_ID})\""), - format!("export AWS_SECRET_ACCESS_KEY=\"$(cat {secret_dir}/{SECRET_ACCESS_KEY})\""), - ]); - } - - if let Some(secret_name) = logdir.tls_secret_name() { - command.push(format!("mkdir -p {STACKABLE_TRUST_STORE}")); - command.push(tlscerts::convert_system_trust_store_to_pkcs12()); - command.push(tlscerts::import_truststore(secret_name)); - } - - command.extend(vec![ - format!("containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &"), - format!("/stackable/spark/sbin/start-history-server.sh --properties-file {VOLUME_MOUNT_PATH_CONFIG}/{SPARK_DEFAULTS_FILE_NAME}"), - ]); - vec![command.join("\n")] -} - -/// Return the Spark properties for the cleaner role group (if any). -fn cleaner_config( - validated: &validate::ValidatedSparkHistoryServer, - role_group_name: &RoleGroupName, -) -> BTreeMap { - match validated.cluster_config.cleaner_rolegroup_name.as_ref() { - Some(cleaner_rolegroup) if cleaner_rolegroup == role_group_name.as_ref() => { - BTreeMap::from([( - "spark.history.fs.cleaner.enabled".to_string(), - "true".to_string(), - )]) - } - _ => BTreeMap::new(), - } -} diff --git a/rust/operator-binary/src/history/controller/build/mod.rs b/rust/operator-binary/src/history/controller/build/mod.rs new file mode 100644 index 00000000..c6bee053 --- /dev/null +++ b/rust/operator-binary/src/history/controller/build/mod.rs @@ -0,0 +1 @@ +pub mod resource; diff --git a/rust/operator-binary/src/history/controller/build/resource/config_map.rs b/rust/operator-binary/src/history/controller/build/resource/config_map.rs new file mode 100644 index 00000000..b34e233f --- /dev/null +++ b/rust/operator-binary/src/history/controller/build/resource/config_map.rs @@ -0,0 +1,130 @@ +use std::collections::BTreeMap; + +use snafu::ResultExt; +use stackable_operator::{ + builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, + k8s_openapi::api::core::v1::ConfigMap, + v2::{ + builder::meta::ownerreference_from_resource, config_file_writer::to_java_properties_string, + types::operator::RoleGroupName, + }, +}; + +use crate::{ + crd::{ + constants::{ + JVM_SECURITY_PROPERTIES_FILE, SPARK_DEFAULTS_FILE_NAME, SPARK_ENV_SH_FILE_NAME, + default_jvm_security_properties, + }, + history::SparkHistoryServerContainer, + to_spark_env_sh_string, + }, + history::controller::{ + Error, InvalidConfigMapSnafu, InvalidLoggingConfigSnafu, InvalidSparkDefaultsSnafu, + JvmSecurityPropertiesSnafu, + validate::{self, ValidatedHistoryRoleGroup}, + }, + product_logging::{self}, +}; + +#[allow(clippy::result_large_err)] +pub(crate) fn build_config_map( + validated: &validate::ValidatedSparkHistoryServer, + role_group_name: &RoleGroupName, + rg: &ValidatedHistoryRoleGroup, +) -> Result { + let cm_name = validated + .resource_names(role_group_name) + .role_group_config_map() + .to_string(); + + let spark_defaults = to_java_properties_string( + spark_defaults(validated, role_group_name) + .iter() + .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))), + ) + .context(InvalidSparkDefaultsSnafu)?; + + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend( + rg.config + .config_overrides + .security_properties + .overrides + .clone(), + ); + + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder + .metadata( + ObjectMetaBuilder::new() + .namespace(validated.namespace.clone()) + .name(&cm_name) + .ownerreference(ownerreference_from_resource( + validated, + Some(true), + Some(true), + )) + .labels(validated.recommended_labels(role_group_name)) + .build(), + ) + .add_data(SPARK_DEFAULTS_FILE_NAME, spark_defaults) + .add_data( + SPARK_ENV_SH_FILE_NAME, + to_spark_env_sh_string(rg.config.config_overrides.spark_env_sh.overrides.iter()), + ) + .add_data( + JVM_SECURITY_PROPERTIES_FILE, + to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { + JvmSecurityPropertiesSnafu { + rolegroup: role_group_name.to_string(), + } + })?, + ); + + product_logging::extend_config_map( + &rg.config.config.logging, + SparkHistoryServerContainer::SparkHistory, + &mut cm_builder, + ) + .context(InvalidLoggingConfigSnafu { cm_name: &cm_name })?; + + cm_builder + .build() + .context(InvalidConfigMapSnafu { name: cm_name }) +} + +fn spark_defaults( + validated: &validate::ValidatedSparkHistoryServer, + role_group_name: &RoleGroupName, +) -> BTreeMap> { + let mut default_properties = validated.cluster_config.log_dir_settings.clone(); + + // add cleaner spark settings if requested + default_properties.extend(cleaner_config(validated, role_group_name)); + + // add user provided configuration. These can overwrite everything. + default_properties.extend(validated.cluster_config.spark_conf.clone()); + + default_properties + .into_iter() + .map(|(key, value)| (key, Some(value))) + .collect() +} + +/// Return the Spark properties for the cleaner role group (if any). +fn cleaner_config( + validated: &validate::ValidatedSparkHistoryServer, + role_group_name: &RoleGroupName, +) -> BTreeMap { + match validated.cluster_config.cleaner_rolegroup_name.as_ref() { + Some(cleaner_rolegroup) if cleaner_rolegroup == role_group_name.as_ref() => { + BTreeMap::from([( + "spark.history.fs.cleaner.enabled".to_string(), + "true".to_string(), + )]) + } + _ => BTreeMap::new(), + } +} diff --git a/rust/operator-binary/src/history/controller/build/resource/listener.rs b/rust/operator-binary/src/history/controller/build/resource/listener.rs new file mode 100644 index 00000000..fe91d2ce --- /dev/null +++ b/rust/operator-binary/src/history/controller/build/resource/listener.rs @@ -0,0 +1,43 @@ +use std::str::FromStr; + +use stackable_operator::{crd::listener, kube::ResourceExt, v2::types::operator::RoleGroupName}; + +use crate::{ + crd::{constants::HISTORY_UI_PORT, listener_ext}, + history::controller::validate, +}; + +pub(crate) fn build_group_listener( + validated: &validate::ValidatedSparkHistoryServer, + role: &str, + listener_class: String, +) -> listener::v1alpha1::Listener { + let listener_name = group_listener_name(validated, role); + + // Group listeners are shared across role groups, so the role-group label is "none" (preserving + // the previous behaviour). + let recommended_object_labels = validated.recommended_labels( + &RoleGroupName::from_str("none").expect("\"none\" is a valid role group name"), + ); + + let listener_ports = [listener::v1alpha1::ListenerPort { + name: "http".to_string(), + port: HISTORY_UI_PORT.into(), + protocol: Some("TCP".to_string()), + }]; + + listener_ext::build_listener( + validated, + &listener_name, + &listener_class, + recommended_object_labels, + &listener_ports, + ) +} + +pub(crate) fn group_listener_name( + validated: &validate::ValidatedSparkHistoryServer, + role: &str, +) -> String { + format!("{cluster}-{role}", cluster = validated.name_any()) +} diff --git a/rust/operator-binary/src/history/controller/build/resource/mod.rs b/rust/operator-binary/src/history/controller/build/resource/mod.rs new file mode 100644 index 00000000..2923f3a2 --- /dev/null +++ b/rust/operator-binary/src/history/controller/build/resource/mod.rs @@ -0,0 +1,5 @@ +pub mod config_map; +pub mod listener; +pub mod pdb; +pub mod service; +pub mod statefulset; diff --git a/rust/operator-binary/src/history/operations/pdb.rs b/rust/operator-binary/src/history/controller/build/resource/pdb.rs similarity index 100% rename from rust/operator-binary/src/history/operations/pdb.rs rename to rust/operator-binary/src/history/controller/build/resource/pdb.rs diff --git a/rust/operator-binary/src/history/service.rs b/rust/operator-binary/src/history/controller/build/resource/service.rs similarity index 100% rename from rust/operator-binary/src/history/service.rs rename to rust/operator-binary/src/history/controller/build/resource/service.rs diff --git a/rust/operator-binary/src/history/controller/build/resource/statefulset.rs b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs new file mode 100644 index 00000000..59afba0c --- /dev/null +++ b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs @@ -0,0 +1,272 @@ +use std::str::FromStr; + +use snafu::{OptionExt, ResultExt}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{PodBuilder, container::ContainerBuilder, volume::VolumeBuilder}, + }, + k8s_openapi::{ + DeepMerge, + api::{ + apps::v1::{StatefulSet, StatefulSetSpec}, + core::v1::{PodSecurityContext, ServiceAccount}, + }, + apimachinery::pkg::apis::meta::v1::LabelSelector, + }, + kube::ResourceExt, + product_logging::{ + framework::calculate_log_volume_size_limit, + spec::{ + ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, + CustomContainerLogConfig, + }, + }, + v2::{ + builder::pod::{ + container::{EnvVarName, EnvVarSet}, + volume::{ListenerReference, listener_operator_volume_source_builder_build_pvc}, + }, + product_logging::framework::vector_container, + types::{kubernetes::PersistentVolumeClaimName, operator::RoleGroupName}, + }, +}; + +// PVC name for the listener volume, required by the v2 listener-volume builder. Its value matches +// `LISTENER_VOLUME_NAME` in `crd::constants`. +stackable_operator::constant!(LISTENER_VOLUME_NAME_PVC: PersistentVolumeClaimName = "listener"); + +use crate::{ + crd::{ + constants::{ + ACCESS_KEY_ID, HISTORY_ROLE_NAME, HISTORY_UI_PORT, LISTENER_VOLUME_DIR, + LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, SECRET_ACCESS_KEY, + SPARK_DEFAULTS_FILE_NAME, STACKABLE_TRUST_STORE, VECTOR_CONTAINER_NAME, + VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_CONFIG_TYPED, VOLUME_MOUNT_NAME_LOG, + VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_NAME_LOG_TYPED, VOLUME_MOUNT_PATH_CONFIG, + VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, + }, + history::SparkHistoryServerContainer, + logdir::ResolvedLogDir, + tlscerts, + }, + history::{ + config::jvm::construct_history_jvm_args, + controller::{ + AddVolumeMountSnafu, AddVolumeSnafu, CreateLogDirVolumesSpecSnafu, Error, + InvalidContainerNameSnafu, MissingSecretLifetimeSnafu, + build::resource::listener::group_listener_name, + validate::{self, ValidatedHistoryRoleGroup}, + }, + }, +}; + +#[allow(clippy::result_large_err)] +pub(crate) fn build_stateful_set( + validated: &validate::ValidatedSparkHistoryServer, + role_group_name: &RoleGroupName, + rg: &ValidatedHistoryRoleGroup, + log_dir: &ResolvedLogDir, + serviceaccount: &ServiceAccount, +) -> Result { + let resolved_product_image = &validated.resolved_product_image; + let resource_names = validated.resource_names(role_group_name); + + let log_config_map = if let Some(ContainerLogConfig { + choice: + Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { + custom: ConfigMapLogConfig { config_map }, + })), + }) = rg + .config + .config + .logging + .containers + .get(&SparkHistoryServerContainer::SparkHistory) + { + config_map.into() + } else { + resource_names.role_group_config_map().to_string() + }; + + let recommended_labels = validated.recommended_labels(role_group_name); + + let pb_metadata = ObjectMetaBuilder::new() + .with_labels(recommended_labels.clone()) + .build(); + + let mut pb = PodBuilder::new(); + + let requested_secret_lifetime = rg + .config + .config + .requested_secret_lifetime + .context(MissingSecretLifetimeSnafu)?; + pb.service_account_name(serviceaccount.name_unchecked()) + .metadata(pb_metadata) + .image_pull_secrets_from_product_image(resolved_product_image) + .add_volume( + VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) + .with_config_map(resource_names.role_group_config_map().to_string()) + .build(), + ) + .context(AddVolumeSnafu)? + .add_volume( + VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG_CONFIG) + .with_config_map(log_config_map) + .build(), + ) + .context(AddVolumeSnafu)? + .add_volume( + VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG) + .with_empty_dir( + None::, + Some(calculate_log_volume_size_limit(&[MAX_SPARK_LOG_FILES_SIZE])), + ) + .build(), + ) + .context(AddVolumeSnafu)? + .add_volumes( + log_dir + .volumes(&requested_secret_lifetime) + .context(CreateLogDirVolumesSpecSnafu)?, + ) + .context(AddVolumeSnafu)? + .security_context(PodSecurityContext { + fs_group: Some(1000), + ..PodSecurityContext::default() + }); + + // Base environment variables, with the already-merged (role + role group) env overrides + // layered on top (overrides win). The base names are static and known to be valid. + let known_env_var_name = |name: &str| { + EnvVarName::from_str(name).expect("the operator-generated env var name is valid") + }; + let merged_env = EnvVarSet::new() + .with_values([ + // Needed by the `containerdebug` running in the background of the history container + // to log it's tracing information to. + ( + known_env_var_name("CONTAINERDEBUG_LOG_DIRECTORY"), + format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), + ), + // This env var prevents the history server from detaching itself from the + // start script because this leads to the Pod terminating immediately. + (known_env_var_name("SPARK_NO_DAEMONIZE"), "true".to_owned()), + ( + known_env_var_name("SPARK_DAEMON_CLASSPATH"), + "/stackable/spark/extra-jars/*".to_owned(), + ), + // JVM arguments for the history server. + ( + known_env_var_name("SPARK_HISTORY_OPTS"), + construct_history_jvm_args(&rg.config, log_dir), + ), + ]) + .merge(rg.config.env_overrides.clone()); + + let container_name = "spark-history"; + let container = ContainerBuilder::new(container_name) + .context(InvalidContainerNameSnafu)? + .image_from_product_image(resolved_product_image) + .resources(rg.config.config.resources.clone().into()) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(command_args(log_dir)) + .add_container_port("http", HISTORY_UI_PORT.into()) + .add_container_port("metrics", METRICS_PORT.into()) + .add_env_vars(merged_env) + .add_volume_mounts(log_dir.volume_mounts()) + .context(AddVolumeMountSnafu)? + .add_volume_mount(VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_PATH_CONFIG) + .context(AddVolumeMountSnafu)? + .add_volume_mount(VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_LOG_CONFIG) + .context(AddVolumeMountSnafu)? + .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) + .context(AddVolumeMountSnafu)? + .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) + .context(AddVolumeMountSnafu)? + .build(); + + // Add listener volume + // Listener endpoints for the Webserver role will use persistent volumes + // so that load balancers can hard-code the target addresses. This will + // be the case even when no class is set (and the value defaults to + // cluster-internal) as the address should still be consistent. + let volume_claim_templates = Some(vec![listener_operator_volume_source_builder_build_pvc( + &ListenerReference::Listener( + group_listener_name(validated, HISTORY_ROLE_NAME) + .parse() + .expect("the group listener name is a valid ListenerName"), + ), + &recommended_labels, + &LISTENER_VOLUME_NAME_PVC, + )]); + + pb.add_container(container); + + if let Some(vector_log_config) = &rg.logging.vector_container { + pb.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + resolved_product_image, + vector_log_config, + &resource_names, + &VOLUME_MOUNT_NAME_CONFIG_TYPED, + &VOLUME_MOUNT_NAME_LOG_TYPED, + EnvVarSet::new(), + )); + } + + let mut pod_template = pb.build_template(); + pod_template.merge_from(rg.config.pod_overrides.clone()); + + let sts_metadata = validated + .object_meta( + resource_names.stateful_set_name().to_string(), + role_group_name, + ) + .build(); + + Ok(StatefulSet { + metadata: sts_metadata, + spec: Some(StatefulSetSpec { + template: pod_template, + volume_claim_templates, + replicas: Some(i32::from(rg.config.replicas)), + selector: LabelSelector { + match_labels: Some(validated.role_group_selector(role_group_name).into()), + ..LabelSelector::default() + }, + ..StatefulSetSpec::default() + }), + ..StatefulSet::default() + }) +} + +fn command_args(logdir: &ResolvedLogDir) -> Vec { + let mut command = vec![]; + + if let Some(secret_dir) = logdir.credentials_mount_path() { + command.extend(vec![ + format!("export AWS_ACCESS_KEY_ID=\"$(cat {secret_dir}/{ACCESS_KEY_ID})\""), + format!("export AWS_SECRET_ACCESS_KEY=\"$(cat {secret_dir}/{SECRET_ACCESS_KEY})\""), + ]); + } + + if let Some(secret_name) = logdir.tls_secret_name() { + command.push(format!("mkdir -p {STACKABLE_TRUST_STORE}")); + command.push(tlscerts::convert_system_trust_store_to_pkcs12()); + command.push(tlscerts::import_truststore(secret_name)); + } + + command.extend(vec![ + format!("containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &"), + format!("/stackable/spark/sbin/start-history-server.sh --properties-file {VOLUME_MOUNT_PATH_CONFIG}/{SPARK_DEFAULTS_FILE_NAME}"), + ]); + vec![command.join("\n")] +} diff --git a/rust/operator-binary/src/history/mod.rs b/rust/operator-binary/src/history/mod.rs index d0245da1..a420ae76 100644 --- a/rust/operator-binary/src/history/mod.rs +++ b/rust/operator-binary/src/history/mod.rs @@ -1,4 +1,2 @@ pub mod config; pub mod controller; -pub mod operations; -pub mod service; diff --git a/rust/operator-binary/src/history/operations/mod.rs b/rust/operator-binary/src/history/operations/mod.rs deleted file mode 100644 index d3cf6e9c..00000000 --- a/rust/operator-binary/src/history/operations/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pdb; diff --git a/rust/operator-binary/src/spark_k8s_controller.rs b/rust/operator-binary/src/spark_k8s_controller.rs index 953dd5a0..ebd080bb 100644 --- a/rust/operator-binary/src/spark_k8s_controller.rs +++ b/rust/operator-binary/src/spark_k8s_controller.rs @@ -1,71 +1,26 @@ -use std::{str::FromStr, sync::Arc, vec}; +use std::sync::Arc; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{ - self, - configmap::ConfigMapBuilder, - meta::ObjectMetaBuilder, - pod::{ - PodBuilder, container::ContainerBuilder, resources::ResourceRequirementsBuilder, - volume::VolumeBuilder, - }, - }, - commons::product_image_selection::ResolvedProductImage, - crd::s3, - k8s_openapi::{ - DeepMerge, Resource, - api::{ - batch::v1::{Job, JobSpec}, - core::v1::{ - Affinity, ConfigMap, Container, EnvVar, PodSecurityContext, PodSpec, - PodTemplateSpec, ServiceAccount, Volume, - }, - rbac::v1::{ClusterRole, RoleBinding, RoleRef, Subject}, - }, - }, + builder::{self}, kube::{ ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, - kvp::Label, logging::controller::ReconcilerError, - product_logging::{ - framework::{capture_shell_output, create_vector_shutdown_file_command}, - spec::{ - ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, - CustomContainerLogConfig, Logging, - }, - }, shared::time::Duration, - v2::{ - builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, - config_file_writer::{PropertiesWriterError, to_java_properties_string}, - product_logging::framework::{ - VectorContainerLogConfig, validate_logging_configuration_for_container, - vector_container, - }, - role_group_utils::ResourceNames, - types::{ - kubernetes::ConfigMapName, - operator::{RoleGroupName, RoleName}, - }, - }, + v2::config_file_writer::PropertiesWriterError, }; use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ Ctx, - crd::{ - constants::*, - logdir::ResolvedLogDir, - roles::{RoleConfig, SparkApplicationRole, SparkContainer, SubmitConfig}, - tlscerts, to_spark_env_sh_string, v1alpha1, - }, + crd::{constants::*, roles::SparkApplicationRole, v1alpha1}, product_logging::{self}, }; +pub mod build; pub mod dereference; pub mod validate; @@ -219,7 +174,8 @@ pub async fn reconcile( // No more mutating operations after this point (except for status). tracing::debug!("reconciling spark application [{spark_application:?}]"); - let (serviceaccount, rolebinding) = build_spark_role_serviceaccount(&validated)?; + let (serviceaccount, rolebinding) = + build::resource::serviceaccount::build_spark_role_serviceaccount(&validated)?; client .apply_patch(SPARK_CONTROLLER_NAME, &serviceaccount, &serviceaccount) .await @@ -242,15 +198,12 @@ pub async fn reconcile( .map(|driver| driver.config_overrides.clone()) .unwrap_or_default(); - let driver_pod_template_config_map = pod_template_config_map( + let driver_pod_template_config_map = build::resource::config_map::pod_template_config_map( &validated, SparkApplicationRole::Driver, &driver_config, &driver_config_overrides, &env_vars, - opt_s3conn, - logdir, - resolved_product_image, &serviceaccount, )?; client @@ -273,15 +226,12 @@ pub async fn reconcile( .map(|executor| executor.config.config_overrides.clone()) .unwrap_or_default(); - let executor_pod_template_config_map = pod_template_config_map( + let executor_pod_template_config_map = build::resource::config_map::pod_template_config_map( &validated, SparkApplicationRole::Executor, &executor_config, &executor_config_overrides, &env_vars, - opt_s3conn, - logdir, - resolved_product_image, &serviceaccount, )?; client @@ -308,7 +258,8 @@ pub async fn reconcile( .map(|job| job.config_overrides.clone()) .unwrap_or_default(); - let submit_job_config_map = submit_job_config_map(&validated, &submit_config_overrides)?; + let submit_job_config_map = + build::resource::config_map::submit_job_config_map(&validated, &submit_config_overrides)?; client .apply_patch( SPARK_CONTROLLER_NAME, @@ -318,14 +269,11 @@ pub async fn reconcile( .await .context(ApplyApplicationSnafu)?; - let job = spark_job( + let job = build::resource::job::spark_job( &validated, - resolved_product_image, &serviceaccount, &env_vars, &job_commands, - opt_s3conn, - logdir, &submit_config, )?; client @@ -353,562 +301,6 @@ pub async fn reconcile( Ok(Action::await_change()) } -fn init_containers( - validated: &validate::ValidatedSparkApplication, - logging: &Logging, - s3conn: &Option, - logdir: &Option, - spark_image: &ResolvedProductImage, -) -> Result> { - let spark_application = &validated.spark_application; - let mut jcb = ContainerBuilder::new(&SparkContainer::Job.to_string()) - .context(IllegalContainerNameSnafu)?; - let job_container = match &spark_application.spec.image { - Some(job_image) => { - let mut args = Vec::new(); - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&SparkContainer::Job) - { - args.push(capture_shell_output( - VOLUME_MOUNT_PATH_LOG, - &SparkContainer::Job.to_string(), - log_config, - )); - }; - args.push(format!("echo Copying job files to {VOLUME_MOUNT_PATH_JOB}")); - args.push(format!("cp /jobs/* {VOLUME_MOUNT_PATH_JOB}")); - // Wait until the log file is written. - args.push("sleep 1".into()); - - Some( - jcb.image(job_image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![args.join("\n")]) - .add_volume_mount(VOLUME_MOUNT_NAME_JOB, VOLUME_MOUNT_PATH_JOB) - .context(AddVolumeMountSnafu)? - .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) - .context(AddVolumeMountSnafu)? - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("500m") - .with_memory_request("128Mi") - .with_memory_limit("128Mi") - .build(), - ) - .build(), - ) - } - None => None, - }; - - let mut rcb = ContainerBuilder::new(&SparkContainer::Requirements.to_string()) - .context(IllegalContainerNameSnafu)?; - let requirements_container = match spark_application.requirements() { - Some(req) => { - let mut args = Vec::new(); - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&SparkContainer::Requirements) - { - args.push(capture_shell_output( - VOLUME_MOUNT_PATH_LOG, - &SparkContainer::Requirements.to_string(), - log_config, - )); - }; - args.push(format!( - "echo Installing requirements to {VOLUME_MOUNT_PATH_REQ}: {req}" - )); - args.push(format!( - "pip install --target={VOLUME_MOUNT_PATH_REQ} {req}" - )); - - rcb.image(&spark_image.image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![args.join("\n")]) - .add_volume_mount(VOLUME_MOUNT_NAME_REQ, VOLUME_MOUNT_PATH_REQ) - .context(AddVolumeMountSnafu)? - .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) - .context(AddVolumeMountSnafu)? - .image_pull_policy(&spark_image.image_pull_policy); - - rcb.resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("1000m") - .with_memory_request("1024Mi") - .with_memory_limit("1024Mi") - .build(), - ); - - Some(rcb.build()) - } - None => None, - }; - - // if TLS is enabled, build TrustStore and put secret inside. - let mut tcb = ContainerBuilder::new(&SparkContainer::Tls.to_string()) - .context(IllegalContainerNameSnafu)?; - let mut args = Vec::new(); - - let tls_container = match tlscerts::tls_secret_names(s3conn, logdir) { - Some(cert_secrets) => { - args.push(tlscerts::convert_system_trust_store_to_pkcs12()); - for cert_secret in cert_secrets { - args.push(tlscerts::import_truststore(cert_secret)); - tcb.add_volume_mount( - cert_secret, - format!("{STACKABLE_MOUNT_PATH_TLS}/{cert_secret}"), - ) - .context(AddVolumeMountSnafu)?; - } - Some( - tcb.image(&spark_image.image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![args.join("\n")]) - .add_volume_mount(STACKABLE_TRUST_STORE_NAME, STACKABLE_TRUST_STORE) - .context(AddVolumeMountSnafu)? - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("250m") - .with_cpu_limit("1000m") - .with_memory_request("1024Mi") - .with_memory_limit("1024Mi") - .build(), - ) - .build(), - ) - } - None => None, - }; - - Ok(vec![job_container, requirements_container, tls_container] - .into_iter() - .flatten() - .collect()) -} - -#[allow(clippy::too_many_arguments)] -fn pod_template( - validated: &validate::ValidatedSparkApplication, - role: SparkApplicationRole, - config: &RoleConfig, - volumes: &[Volume], - env: &[EnvVar], - s3conn: &Option, - logdir: &Option, - spark_image: &ResolvedProductImage, - service_account: &ServiceAccount, -) -> Result { - let spark_application = &validated.spark_application; - let container_name = SparkContainer::Spark.to_string(); - let mut cb = ContainerBuilder::new(&container_name).context(IllegalContainerNameSnafu)?; - let merged_env = spark_application.merged_env(role.clone(), env); - - cb.add_volume_mounts(config.volume_mounts(spark_application, s3conn, logdir)) - .context(AddVolumeMountSnafu)? - .add_env_vars(merged_env) - .resources(config.resources.clone().into()) - .image_from_product_image(spark_image); - - if config.logging.enable_vector_agent { - cb.add_env_var( - "_STACKABLE_POST_HOOK", - [ - // Wait for Vector to gather the logs. - "sleep 10", - &create_vector_shutdown_file_command(VOLUME_MOUNT_PATH_LOG), - ] - .join("; "), - ); - } - - let mut omb = ObjectMetaBuilder::new(); - omb.name(&container_name) - // this reference is not pointing to a controller but only provides a UID that can used to clean up resources - // cleanly (specifically driver pods and related config maps) when the spark application is deleted. - .ownerreference(ownerreference_from_resource(validated, None, None)) - .with_labels(validated.recommended_labels(&container_name)); - - // Only the driver pod should be scraped by Prometheus - // because the executor metrics are also available via /metrics/executors/prometheus/ - if role == SparkApplicationRole::Driver { - omb.with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?); - } - - let mut metadata = omb.build(); - - // We explicitly remove the application owner reference from driver and executor pods. - // - // The executors then only have the driver as owner and Kubernetes can garbage collect them - // early when the driver pod or the spark-submit job is deleted. - // Drivers must not have the SparkApplication as owner because this prevents proper cleanup - // when the application is finished. - // The submit pod doesn't use this function right now, but we keep the "if" below for future - // sanity. - if role == SparkApplicationRole::Executor || role == SparkApplicationRole::Driver { - metadata.owner_references = None; - } - - let mut pb = PodBuilder::new(); - pb.metadata(metadata) - .add_container(cb.build()) - .add_volumes(volumes.to_vec()) - .context(AddVolumeSnafu)? - .security_context(security_context()) - .image_pull_secrets_from_product_image(spark_image) - .affinity(&config.affinity) - .service_account_name(service_account.name_any()); - - let init_containers = init_containers(validated, &config.logging, s3conn, logdir, spark_image)?; - - for init_container in init_containers { - pb.add_init_container(init_container.clone()); - } - - if config.logging.enable_vector_agent { - let vector_aggregator_config_map_name = spark_application - .spec - .vector_aggregator_config_map_name - .as_ref() - .context(VectorAggregatorConfigMapMissingSnafu)?; - let vector_log_config = VectorContainerLogConfig { - log_config: validate_logging_configuration_for_container( - &config.logging, - &SparkContainer::Vector, - ) - .context(ValidateLoggingConfigSnafu)?, - vector_aggregator_config_map_name: ConfigMapName::from_str( - vector_aggregator_config_map_name, - ) - .context(ParseVectorAggregatorConfigMapNameSnafu)?, - }; - // These resource names are constructed SOLELY to provide the Vector sidecar with its - // `CLUSTER_NAME`/`ROLE_NAME`/`ROLE_GROUP_NAME` log-metadata env vars. They do NOT affect - // resource naming. A SparkApplication has no Stackable role groups, so the role group name - // is a placeholder; the role name reflects the pod's Spark role (driver/executor). - let vector_resource_names = ResourceNames { - cluster_name: validated.name.clone(), - role_name: RoleName::from_str(&role.to_string()) - .expect("a SparkApplicationRole serializes to a valid role name"), - role_group_name: RoleGroupName::from_str("default") - .expect("\"default\" is a valid role group name"), - }; - pb.add_container(vector_container( - &VECTOR_CONTAINER_NAME, - spark_image, - &vector_log_config, - &vector_resource_names, - &VOLUME_MOUNT_NAME_CONFIG_TYPED, - &VOLUME_MOUNT_NAME_LOG_TYPED, - EnvVarSet::new(), - )); - } - - let mut pod_template = pb.build_template(); - if let Some(pod_overrides) = spark_application.pod_overrides(role) { - pod_template.merge_from(pod_overrides); - } - Ok(pod_template) -} - -#[allow(clippy::too_many_arguments)] -fn pod_template_config_map( - validated: &validate::ValidatedSparkApplication, - role: SparkApplicationRole, - merged_config: &RoleConfig, - config_overrides: &v1alpha1::ConfigOverrides, - env: &[EnvVar], - s3conn: &Option, - logdir: &Option, - spark_image: &ResolvedProductImage, - service_account: &ServiceAccount, -) -> Result { - let spark_application = &validated.spark_application; - let cm_name = spark_application.pod_template_config_map_name(role.clone()); - - let log_config_map = if let Some(ContainerLogConfig { - choice: - Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { - custom: ConfigMapLogConfig { config_map }, - })), - }) = merged_config.logging.containers.get(&SparkContainer::Spark) - { - config_map.into() - } else { - cm_name.clone() - }; - - let requested_secret_lifetime = merged_config - .requested_secret_lifetime - .context(MissingSecretLifetimeSnafu)?; - let mut volumes = spark_application - .volumes( - s3conn, - logdir, - Some(&log_config_map), - &requested_secret_lifetime, - ) - .context(CreateVolumesSnafu)?; - volumes.push( - VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) - .with_config_map(&cm_name) - .build(), - ); - - let template = pod_template( - validated, - role.clone(), - merged_config, - volumes.as_ref(), - env, - s3conn, - logdir, - spark_image, - service_account, - )?; - - let mut cm_builder = ConfigMapBuilder::new(); - - cm_builder - .metadata(validated.object_meta(&cm_name, "pod-templates").build()) - .add_data( - POD_TEMPLATE_FILE, - serde_yaml::to_string(&template).context(PodTemplateSerdeSnafu)?, - ); - - product_logging::extend_config_map( - &merged_config.logging, - SparkContainer::Spark, - &mut cm_builder, - ) - .context(InvalidLoggingConfigSnafu { cm_name })?; - - cm_builder.add_data( - SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), - ); - - let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); - - cm_builder.add_data( - JVM_SECURITY_PROPERTIES_FILE, - to_java_properties_string(jvm_sec_props.iter()) - .with_context(|_| JvmSecurityPropertiesSnafu { role })?, - ); - - cm_builder.build().context(PodTemplateConfigMapSnafu) -} - -fn submit_job_config_map( - validated: &validate::ValidatedSparkApplication, - config_overrides: &v1alpha1::ConfigOverrides, -) -> Result { - let spark_application = &validated.spark_application; - let cm_name = spark_application.submit_job_config_map_name(); - - let mut cm_builder = ConfigMapBuilder::new(); - - cm_builder.metadata(validated.object_meta(&cm_name, "spark-submit").build()); - - cm_builder.add_data( - SPARK_ENV_SH_FILE_NAME, - to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), - ); - - let mut jvm_sec_props = default_jvm_security_properties(); - jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); - - cm_builder.add_data( - JVM_SECURITY_PROPERTIES_FILE, - to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { - JvmSecurityPropertiesSnafu { - role: SparkApplicationRole::Submit, - } - })?, - ); - - cm_builder.build().context(PodTemplateConfigMapSnafu) -} - -#[allow(clippy::too_many_arguments)] -fn spark_job( - validated: &validate::ValidatedSparkApplication, - spark_image: &ResolvedProductImage, - serviceaccount: &ServiceAccount, - env: &[EnvVar], - job_commands: &[String], - s3conn: &Option, - logdir: &Option, - job_config: &SubmitConfig, -) -> Result { - let spark_application = &validated.spark_application; - let mut cb = ContainerBuilder::new(&SparkContainer::SparkSubmit.to_string()) - .context(IllegalContainerNameSnafu)?; - - let merged_env = spark_application.merged_env(SparkApplicationRole::Submit, env); - - // The SPARK_SUBMIT_OPTS env var is used to configure the JVM settings of the spark-submit job. - // Here we need to point the JVM to our logging configuration and if S3 is used for data or Spark History, - // we also need to tell the JVM where the trust store is located. - // The same properties are also set for the driver and executor pods via the pod template config maps. - let mut spark_submit_opts_env = vec![format!( - "-Dlog4j.configurationFile={VOLUME_MOUNT_PATH_LOG_CONFIG}/{LOG4J2_CONFIG_FILE}" - )]; - if tlscerts::tls_secret_names(s3conn, logdir).is_some() { - spark_submit_opts_env.push(format!( - "-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12" - )); - spark_submit_opts_env.push(format!( - "-Djavax.net.ssl.trustStorePassword={STACKABLE_TLS_STORE_PASSWORD}" - )); - } - cb.image_from_product_image(spark_image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![job_commands.join("\n")]) - .resources(job_config.resources.clone().into()) - .add_volume_mounts(spark_application.spark_job_volume_mounts(s3conn, logdir)) - .context(AddVolumeMountSnafu)? - .add_env_vars(merged_env) - .add_env_var("SPARK_SUBMIT_OPTS", spark_submit_opts_env.join(" ")) - // TODO: move this to the image - .add_env_var("SPARK_CONF_DIR", "/stackable/spark/conf"); - - let mut volumes = vec![ - VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) - .with_config_map(spark_application.submit_job_config_map_name()) - .build(), - VolumeBuilder::new(VOLUME_MOUNT_NAME_DRIVER_POD_TEMPLATES) - .with_config_map( - spark_application.pod_template_config_map_name(SparkApplicationRole::Driver), - ) - .build(), - VolumeBuilder::new(VOLUME_MOUNT_NAME_EXECUTOR_POD_TEMPLATES) - .with_config_map( - spark_application.pod_template_config_map_name(SparkApplicationRole::Executor), - ) - .build(), - ]; - let requested_secret_lifetime = job_config - .requested_secret_lifetime - .context(MissingSecretLifetimeSnafu)?; - volumes.extend( - spark_application - .volumes(s3conn, logdir, None, &requested_secret_lifetime) - .context(CreateVolumesSnafu)?, - ); - - let containers = vec![cb.build()]; - - let mut pod = PodTemplateSpec { - metadata: Some( - ObjectMetaBuilder::new() - .name("spark-submit") - .with_labels(validated.recommended_labels("spark-job-template")) - .build(), - ), - spec: Some(PodSpec { - containers, - restart_policy: Some("Never".to_string()), - service_account_name: serviceaccount.metadata.name.clone(), - volumes: Some(volumes), - affinity: Some(Affinity { - node_affinity: job_config.affinity.node_affinity.clone(), - pod_affinity: job_config.affinity.pod_affinity.clone(), - pod_anti_affinity: job_config.affinity.pod_anti_affinity.clone(), - }), - image_pull_secrets: spark_image.pull_secrets.clone(), - security_context: Some(security_context()), - ..PodSpec::default() - }), - }; - - if let Some(submit_pod_overrides) = - spark_application.pod_overrides(SparkApplicationRole::Submit) - { - pod.merge_from(submit_pod_overrides); - } - - let job = Job { - metadata: validated - .object_meta(validated.name.to_string(), "spark-job") - .build(), - spec: Some(JobSpec { - template: pod, - ttl_seconds_after_finished: Some(600), - backoff_limit: Some(spark_application.retry_on_failure_count()), - ..Default::default() - }), - status: None, - }; - - Ok(job) -} - -/// For a given SparkApplication, we create a ServiceAccount with a RoleBinding to the ClusterRole -/// that allows the driver to create pods etc. -/// Both objects have an owner reference to the SparkApplication, as well as the same name as the app. -/// They are deleted when the job is deleted. -fn build_spark_role_serviceaccount( - validated: &validate::ValidatedSparkApplication, -) -> Result<(ServiceAccount, RoleBinding)> { - let sa_name = validated.name.to_string(); - let sa = ServiceAccount { - metadata: validated.object_meta(&sa_name, "service-account").build(), - ..ServiceAccount::default() - }; - let binding_name = &sa_name; - let binding = RoleBinding { - metadata: validated.object_meta(binding_name, "role-binding").build(), - role_ref: RoleRef { - api_group: ClusterRole::GROUP.to_string(), - kind: ClusterRole::KIND.to_string(), - name: SPARK_CLUSTER_ROLE.to_string(), - }, - subjects: Some(vec![Subject { - api_group: Some(ServiceAccount::GROUP.to_string()), - kind: ServiceAccount::KIND.to_string(), - name: sa_name, - namespace: sa.metadata.namespace.clone(), - }]), - }; - Ok((sa, binding)) -} - -fn security_context() -> PodSecurityContext { - PodSecurityContext { - fs_group: Some(1000), - ..PodSecurityContext::default() - } -} - pub fn error_policy( _obj: Arc>, error: &Error, diff --git a/rust/operator-binary/src/spark_k8s_controller/build/mod.rs b/rust/operator-binary/src/spark_k8s_controller/build/mod.rs new file mode 100644 index 00000000..f94ce8fb --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/mod.rs @@ -0,0 +1,2 @@ +pub mod pod; +pub mod resource; diff --git a/rust/operator-binary/src/spark_k8s_controller/build/pod.rs b/rust/operator-binary/src/spark_k8s_controller/build/pod.rs new file mode 100644 index 00000000..bf391770 --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/pod.rs @@ -0,0 +1,331 @@ +use std::str::FromStr; + +use snafu::{OptionExt, ResultExt}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{PodBuilder, container::ContainerBuilder, resources::ResourceRequirementsBuilder}, + }, + k8s_openapi::{ + DeepMerge, + api::core::v1::{ + Container, EnvVar, PodSecurityContext, PodTemplateSpec, ServiceAccount, Volume, + }, + }, + kube::ResourceExt, + kvp::Label, + product_logging::{ + framework::{capture_shell_output, create_vector_shutdown_file_command}, + spec::{ContainerLogConfig, ContainerLogConfigChoice, Logging}, + }, + v2::{ + builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, + product_logging::framework::{ + VectorContainerLogConfig, validate_logging_configuration_for_container, + vector_container, + }, + role_group_utils::ResourceNames, + types::{ + kubernetes::ConfigMapName, + operator::{RoleGroupName, RoleName}, + }, + }, +}; + +use crate::{ + crd::{ + constants::*, + roles::{RoleConfig, SparkApplicationRole, SparkContainer}, + tlscerts, + }, + spark_k8s_controller::{ + AddVolumeMountSnafu, AddVolumeSnafu, IllegalContainerNameSnafu, LabelBuildSnafu, + ParseVectorAggregatorConfigMapNameSnafu, Result, ValidateLoggingConfigSnafu, + VectorAggregatorConfigMapMissingSnafu, validate, + }, +}; + +fn init_containers( + validated: &validate::ValidatedSparkApplication, + logging: &Logging, +) -> Result> { + let spark_application = &validated.spark_application; + let s3conn = &validated.cluster_config.s3_connection; + let logdir = &validated.cluster_config.log_dir; + let spark_image = &validated.resolved_product_image; + let mut jcb = ContainerBuilder::new(&SparkContainer::Job.to_string()) + .context(IllegalContainerNameSnafu)?; + let job_container = match &spark_application.spec.image { + Some(job_image) => { + let mut args = Vec::new(); + if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = logging.containers.get(&SparkContainer::Job) + { + args.push(capture_shell_output( + VOLUME_MOUNT_PATH_LOG, + &SparkContainer::Job.to_string(), + log_config, + )); + }; + args.push(format!("echo Copying job files to {VOLUME_MOUNT_PATH_JOB}")); + args.push(format!("cp /jobs/* {VOLUME_MOUNT_PATH_JOB}")); + // Wait until the log file is written. + args.push("sleep 1".into()); + + Some( + jcb.image(job_image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![args.join("\n")]) + .add_volume_mount(VOLUME_MOUNT_NAME_JOB, VOLUME_MOUNT_PATH_JOB) + .context(AddVolumeMountSnafu)? + .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) + .context(AddVolumeMountSnafu)? + .resources( + ResourceRequirementsBuilder::new() + .with_cpu_request("250m") + .with_cpu_limit("500m") + .with_memory_request("128Mi") + .with_memory_limit("128Mi") + .build(), + ) + .build(), + ) + } + None => None, + }; + + let mut rcb = ContainerBuilder::new(&SparkContainer::Requirements.to_string()) + .context(IllegalContainerNameSnafu)?; + let requirements_container = match spark_application.requirements() { + Some(req) => { + let mut args = Vec::new(); + if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = logging.containers.get(&SparkContainer::Requirements) + { + args.push(capture_shell_output( + VOLUME_MOUNT_PATH_LOG, + &SparkContainer::Requirements.to_string(), + log_config, + )); + }; + args.push(format!( + "echo Installing requirements to {VOLUME_MOUNT_PATH_REQ}: {req}" + )); + args.push(format!( + "pip install --target={VOLUME_MOUNT_PATH_REQ} {req}" + )); + + rcb.image(&spark_image.image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![args.join("\n")]) + .add_volume_mount(VOLUME_MOUNT_NAME_REQ, VOLUME_MOUNT_PATH_REQ) + .context(AddVolumeMountSnafu)? + .add_volume_mount(VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_PATH_LOG) + .context(AddVolumeMountSnafu)? + .image_pull_policy(&spark_image.image_pull_policy); + + rcb.resources( + ResourceRequirementsBuilder::new() + .with_cpu_request("250m") + .with_cpu_limit("1000m") + .with_memory_request("1024Mi") + .with_memory_limit("1024Mi") + .build(), + ); + + Some(rcb.build()) + } + None => None, + }; + + // if TLS is enabled, build TrustStore and put secret inside. + let mut tcb = ContainerBuilder::new(&SparkContainer::Tls.to_string()) + .context(IllegalContainerNameSnafu)?; + let mut args = Vec::new(); + + let tls_container = match tlscerts::tls_secret_names(s3conn, logdir) { + Some(cert_secrets) => { + args.push(tlscerts::convert_system_trust_store_to_pkcs12()); + for cert_secret in cert_secrets { + args.push(tlscerts::import_truststore(cert_secret)); + tcb.add_volume_mount( + cert_secret, + format!("{STACKABLE_MOUNT_PATH_TLS}/{cert_secret}"), + ) + .context(AddVolumeMountSnafu)?; + } + Some( + tcb.image(&spark_image.image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![args.join("\n")]) + .add_volume_mount(STACKABLE_TRUST_STORE_NAME, STACKABLE_TRUST_STORE) + .context(AddVolumeMountSnafu)? + .resources( + ResourceRequirementsBuilder::new() + .with_cpu_request("250m") + .with_cpu_limit("1000m") + .with_memory_request("1024Mi") + .with_memory_limit("1024Mi") + .build(), + ) + .build(), + ) + } + None => None, + }; + + Ok(vec![job_container, requirements_container, tls_container] + .into_iter() + .flatten() + .collect()) +} + +pub(crate) fn pod_template( + validated: &validate::ValidatedSparkApplication, + role: SparkApplicationRole, + config: &RoleConfig, + volumes: &[Volume], + env: &[EnvVar], + service_account: &ServiceAccount, +) -> Result { + let spark_application = &validated.spark_application; + let s3conn = &validated.cluster_config.s3_connection; + let logdir = &validated.cluster_config.log_dir; + let spark_image = &validated.resolved_product_image; + let container_name = SparkContainer::Spark.to_string(); + let mut cb = ContainerBuilder::new(&container_name).context(IllegalContainerNameSnafu)?; + let merged_env = spark_application.merged_env(role.clone(), env); + + cb.add_volume_mounts(config.volume_mounts(spark_application, s3conn, logdir)) + .context(AddVolumeMountSnafu)? + .add_env_vars(merged_env) + .resources(config.resources.clone().into()) + .image_from_product_image(spark_image); + + if config.logging.enable_vector_agent { + cb.add_env_var( + "_STACKABLE_POST_HOOK", + [ + // Wait for Vector to gather the logs. + "sleep 10", + &create_vector_shutdown_file_command(VOLUME_MOUNT_PATH_LOG), + ] + .join("; "), + ); + } + + let mut omb = ObjectMetaBuilder::new(); + omb.name(&container_name) + // this reference is not pointing to a controller but only provides a UID that can used to clean up resources + // cleanly (specifically driver pods and related config maps) when the spark application is deleted. + .ownerreference(ownerreference_from_resource(validated, None, None)) + .with_labels(validated.recommended_labels(&container_name)); + + // Only the driver pod should be scraped by Prometheus + // because the executor metrics are also available via /metrics/executors/prometheus/ + if role == SparkApplicationRole::Driver { + omb.with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?); + } + + let mut metadata = omb.build(); + + // We explicitly remove the application owner reference from driver and executor pods. + // + // The executors then only have the driver as owner and Kubernetes can garbage collect them + // early when the driver pod or the spark-submit job is deleted. + // Drivers must not have the SparkApplication as owner because this prevents proper cleanup + // when the application is finished. + // The submit pod doesn't use this function right now, but we keep the "if" below for future + // sanity. + if role == SparkApplicationRole::Executor || role == SparkApplicationRole::Driver { + metadata.owner_references = None; + } + + let mut pb = PodBuilder::new(); + pb.metadata(metadata) + .add_container(cb.build()) + .add_volumes(volumes.to_vec()) + .context(AddVolumeSnafu)? + .security_context(security_context()) + .image_pull_secrets_from_product_image(spark_image) + .affinity(&config.affinity) + .service_account_name(service_account.name_any()); + + let init_containers = init_containers(validated, &config.logging)?; + + for init_container in init_containers { + pb.add_init_container(init_container.clone()); + } + + if config.logging.enable_vector_agent { + let vector_aggregator_config_map_name = spark_application + .spec + .vector_aggregator_config_map_name + .as_ref() + .context(VectorAggregatorConfigMapMissingSnafu)?; + let vector_log_config = VectorContainerLogConfig { + log_config: validate_logging_configuration_for_container( + &config.logging, + &SparkContainer::Vector, + ) + .context(ValidateLoggingConfigSnafu)?, + vector_aggregator_config_map_name: ConfigMapName::from_str( + vector_aggregator_config_map_name, + ) + .context(ParseVectorAggregatorConfigMapNameSnafu)?, + }; + // These resource names are constructed SOLELY to provide the Vector sidecar with its + // `CLUSTER_NAME`/`ROLE_NAME`/`ROLE_GROUP_NAME` log-metadata env vars. They do NOT affect + // resource naming. A SparkApplication has no Stackable role groups, so the role group name + // is a placeholder; the role name reflects the pod's Spark role (driver/executor). + let vector_resource_names = ResourceNames { + cluster_name: validated.name.clone(), + role_name: RoleName::from_str(&role.to_string()) + .expect("a SparkApplicationRole serializes to a valid role name"), + role_group_name: RoleGroupName::from_str("default") + .expect("\"default\" is a valid role group name"), + }; + pb.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + spark_image, + &vector_log_config, + &vector_resource_names, + &VOLUME_MOUNT_NAME_CONFIG_TYPED, + &VOLUME_MOUNT_NAME_LOG_TYPED, + EnvVarSet::new(), + )); + } + + let mut pod_template = pb.build_template(); + if let Some(pod_overrides) = spark_application.pod_overrides(role) { + pod_template.merge_from(pod_overrides); + } + Ok(pod_template) +} + +pub(crate) fn security_context() -> PodSecurityContext { + PodSecurityContext { + fs_group: Some(1000), + ..PodSecurityContext::default() + } +} diff --git a/rust/operator-binary/src/spark_k8s_controller/build/resource/config_map.rs b/rust/operator-binary/src/spark_k8s_controller/build/resource/config_map.rs new file mode 100644 index 00000000..52de48a9 --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/resource/config_map.rs @@ -0,0 +1,138 @@ +use snafu::{OptionExt, ResultExt}; +use stackable_operator::{ + builder::{configmap::ConfigMapBuilder, pod::volume::VolumeBuilder}, + k8s_openapi::api::core::v1::{ConfigMap, EnvVar, ServiceAccount}, + product_logging::spec::{ + ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, CustomContainerLogConfig, + }, + v2::config_file_writer::to_java_properties_string, +}; + +use crate::{ + crd::{ + constants::*, + roles::{RoleConfig, SparkApplicationRole, SparkContainer}, + to_spark_env_sh_string, v1alpha1, + }, + product_logging::{self}, + spark_k8s_controller::{ + CreateVolumesSnafu, InvalidLoggingConfigSnafu, JvmSecurityPropertiesSnafu, + MissingSecretLifetimeSnafu, PodTemplateConfigMapSnafu, PodTemplateSerdeSnafu, Result, + build::pod::pod_template, validate, + }, +}; + +pub(crate) fn pod_template_config_map( + validated: &validate::ValidatedSparkApplication, + role: SparkApplicationRole, + merged_config: &RoleConfig, + config_overrides: &v1alpha1::ConfigOverrides, + env: &[EnvVar], + service_account: &ServiceAccount, +) -> Result { + let spark_application = &validated.spark_application; + let s3conn = &validated.cluster_config.s3_connection; + let logdir = &validated.cluster_config.log_dir; + let cm_name = spark_application.pod_template_config_map_name(role.clone()); + + let log_config_map = if let Some(ContainerLogConfig { + choice: + Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { + custom: ConfigMapLogConfig { config_map }, + })), + }) = merged_config.logging.containers.get(&SparkContainer::Spark) + { + config_map.into() + } else { + cm_name.clone() + }; + + let requested_secret_lifetime = merged_config + .requested_secret_lifetime + .context(MissingSecretLifetimeSnafu)?; + let mut volumes = spark_application + .volumes( + s3conn, + logdir, + Some(&log_config_map), + &requested_secret_lifetime, + ) + .context(CreateVolumesSnafu)?; + volumes.push( + VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) + .with_config_map(&cm_name) + .build(), + ); + + let template = pod_template( + validated, + role.clone(), + merged_config, + volumes.as_ref(), + env, + service_account, + )?; + + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder + .metadata(validated.object_meta(&cm_name, "pod-templates").build()) + .add_data( + POD_TEMPLATE_FILE, + serde_yaml::to_string(&template).context(PodTemplateSerdeSnafu)?, + ); + + product_logging::extend_config_map( + &merged_config.logging, + SparkContainer::Spark, + &mut cm_builder, + ) + .context(InvalidLoggingConfigSnafu { cm_name })?; + + cm_builder.add_data( + SPARK_ENV_SH_FILE_NAME, + to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), + ); + + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); + + cm_builder.add_data( + JVM_SECURITY_PROPERTIES_FILE, + to_java_properties_string(jvm_sec_props.iter()) + .with_context(|_| JvmSecurityPropertiesSnafu { role })?, + ); + + cm_builder.build().context(PodTemplateConfigMapSnafu) +} + +pub(crate) fn submit_job_config_map( + validated: &validate::ValidatedSparkApplication, + config_overrides: &v1alpha1::ConfigOverrides, +) -> Result { + let spark_application = &validated.spark_application; + let cm_name = spark_application.submit_job_config_map_name(); + + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder.metadata(validated.object_meta(&cm_name, "spark-submit").build()); + + cm_builder.add_data( + SPARK_ENV_SH_FILE_NAME, + to_spark_env_sh_string(config_overrides.spark_env_sh.overrides.iter()), + ); + + let mut jvm_sec_props = default_jvm_security_properties(); + jvm_sec_props.extend(config_overrides.security_properties.overrides.clone()); + + cm_builder.add_data( + JVM_SECURITY_PROPERTIES_FILE, + to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { + JvmSecurityPropertiesSnafu { + role: SparkApplicationRole::Submit, + } + })?, + ); + + cm_builder.build().context(PodTemplateConfigMapSnafu) +} diff --git a/rust/operator-binary/src/spark_k8s_controller/build/resource/job.rs b/rust/operator-binary/src/spark_k8s_controller/build/resource/job.rs new file mode 100644 index 00000000..a0eab2aa --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/resource/job.rs @@ -0,0 +1,145 @@ +use snafu::{OptionExt, ResultExt}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{container::ContainerBuilder, volume::VolumeBuilder}, + }, + k8s_openapi::{ + DeepMerge, + api::{ + batch::v1::{Job, JobSpec}, + core::v1::{Affinity, EnvVar, PodSpec, PodTemplateSpec, ServiceAccount}, + }, + }, +}; + +use crate::{ + crd::{ + constants::*, + roles::{SparkApplicationRole, SparkContainer, SubmitConfig}, + tlscerts, + }, + spark_k8s_controller::{ + AddVolumeMountSnafu, CreateVolumesSnafu, IllegalContainerNameSnafu, + MissingSecretLifetimeSnafu, Result, build::pod::security_context, validate, + }, +}; + +pub(crate) fn spark_job( + validated: &validate::ValidatedSparkApplication, + serviceaccount: &ServiceAccount, + env: &[EnvVar], + job_commands: &[String], + job_config: &SubmitConfig, +) -> Result { + let spark_application = &validated.spark_application; + let spark_image = &validated.resolved_product_image; + let s3conn = &validated.cluster_config.s3_connection; + let logdir = &validated.cluster_config.log_dir; + let mut cb = ContainerBuilder::new(&SparkContainer::SparkSubmit.to_string()) + .context(IllegalContainerNameSnafu)?; + + let merged_env = spark_application.merged_env(SparkApplicationRole::Submit, env); + + // The SPARK_SUBMIT_OPTS env var is used to configure the JVM settings of the spark-submit job. + // Here we need to point the JVM to our logging configuration and if S3 is used for data or Spark History, + // we also need to tell the JVM where the trust store is located. + // The same properties are also set for the driver and executor pods via the pod template config maps. + let mut spark_submit_opts_env = vec![format!( + "-Dlog4j.configurationFile={VOLUME_MOUNT_PATH_LOG_CONFIG}/{LOG4J2_CONFIG_FILE}" + )]; + if tlscerts::tls_secret_names(s3conn, logdir).is_some() { + spark_submit_opts_env.push(format!( + "-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12" + )); + spark_submit_opts_env.push(format!( + "-Djavax.net.ssl.trustStorePassword={STACKABLE_TLS_STORE_PASSWORD}" + )); + } + cb.image_from_product_image(spark_image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![job_commands.join("\n")]) + .resources(job_config.resources.clone().into()) + .add_volume_mounts(spark_application.spark_job_volume_mounts(s3conn, logdir)) + .context(AddVolumeMountSnafu)? + .add_env_vars(merged_env) + .add_env_var("SPARK_SUBMIT_OPTS", spark_submit_opts_env.join(" ")) + // TODO: move this to the image + .add_env_var("SPARK_CONF_DIR", "/stackable/spark/conf"); + + let mut volumes = vec![ + VolumeBuilder::new(VOLUME_MOUNT_NAME_CONFIG) + .with_config_map(spark_application.submit_job_config_map_name()) + .build(), + VolumeBuilder::new(VOLUME_MOUNT_NAME_DRIVER_POD_TEMPLATES) + .with_config_map( + spark_application.pod_template_config_map_name(SparkApplicationRole::Driver), + ) + .build(), + VolumeBuilder::new(VOLUME_MOUNT_NAME_EXECUTOR_POD_TEMPLATES) + .with_config_map( + spark_application.pod_template_config_map_name(SparkApplicationRole::Executor), + ) + .build(), + ]; + let requested_secret_lifetime = job_config + .requested_secret_lifetime + .context(MissingSecretLifetimeSnafu)?; + volumes.extend( + spark_application + .volumes(s3conn, logdir, None, &requested_secret_lifetime) + .context(CreateVolumesSnafu)?, + ); + + let containers = vec![cb.build()]; + + let mut pod = PodTemplateSpec { + metadata: Some( + ObjectMetaBuilder::new() + .name("spark-submit") + .with_labels(validated.recommended_labels("spark-job-template")) + .build(), + ), + spec: Some(PodSpec { + containers, + restart_policy: Some("Never".to_string()), + service_account_name: serviceaccount.metadata.name.clone(), + volumes: Some(volumes), + affinity: Some(Affinity { + node_affinity: job_config.affinity.node_affinity.clone(), + pod_affinity: job_config.affinity.pod_affinity.clone(), + pod_anti_affinity: job_config.affinity.pod_anti_affinity.clone(), + }), + image_pull_secrets: spark_image.pull_secrets.clone(), + security_context: Some(security_context()), + ..PodSpec::default() + }), + }; + + if let Some(submit_pod_overrides) = + spark_application.pod_overrides(SparkApplicationRole::Submit) + { + pod.merge_from(submit_pod_overrides); + } + + let job = Job { + metadata: validated + .object_meta(validated.name.to_string(), "spark-job") + .build(), + spec: Some(JobSpec { + template: pod, + ttl_seconds_after_finished: Some(600), + backoff_limit: Some(spark_application.retry_on_failure_count()), + ..Default::default() + }), + status: None, + }; + + Ok(job) +} diff --git a/rust/operator-binary/src/spark_k8s_controller/build/resource/mod.rs b/rust/operator-binary/src/spark_k8s_controller/build/resource/mod.rs new file mode 100644 index 00000000..584c4fa8 --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/resource/mod.rs @@ -0,0 +1,3 @@ +pub mod config_map; +pub mod job; +pub mod serviceaccount; diff --git a/rust/operator-binary/src/spark_k8s_controller/build/resource/serviceaccount.rs b/rust/operator-binary/src/spark_k8s_controller/build/resource/serviceaccount.rs new file mode 100644 index 00000000..1268214f --- /dev/null +++ b/rust/operator-binary/src/spark_k8s_controller/build/resource/serviceaccount.rs @@ -0,0 +1,42 @@ +use stackable_operator::k8s_openapi::{ + Resource, + api::{ + core::v1::ServiceAccount, + rbac::v1::{ClusterRole, RoleBinding, RoleRef, Subject}, + }, +}; + +use crate::{ + crd::constants::*, + spark_k8s_controller::{Result, validate}, +}; + +/// For a given SparkApplication, we create a ServiceAccount with a RoleBinding to the ClusterRole +/// that allows the driver to create pods etc. +/// Both objects have an owner reference to the SparkApplication, as well as the same name as the app. +/// They are deleted when the job is deleted. +pub(crate) fn build_spark_role_serviceaccount( + validated: &validate::ValidatedSparkApplication, +) -> Result<(ServiceAccount, RoleBinding)> { + let sa_name = validated.name.to_string(); + let sa = ServiceAccount { + metadata: validated.object_meta(&sa_name, "service-account").build(), + ..ServiceAccount::default() + }; + let binding_name = &sa_name; + let binding = RoleBinding { + metadata: validated.object_meta(binding_name, "role-binding").build(), + role_ref: RoleRef { + api_group: ClusterRole::GROUP.to_string(), + kind: ClusterRole::KIND.to_string(), + name: SPARK_CLUSTER_ROLE.to_string(), + }, + subjects: Some(vec![Subject { + api_group: Some(ServiceAccount::GROUP.to_string()), + kind: ServiceAccount::KIND.to_string(), + name: sa_name, + namespace: sa.metadata.namespace.clone(), + }]), + }; + Ok((sa, binding)) +}