From 347e19a79f0d54548a86f9cf83a0eb8e4e0041c5 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 16:13:11 +0200 Subject: [PATCH 01/65] feat: remove product config & clean up --- Cargo.lock | 332 ++++---- Cargo.nix | 634 ++++++++------- Cargo.toml | 10 +- crate-hashes.json | 18 +- deploy/config-spec/properties.yaml | 270 +------ .../trino-operator/configs/properties.yaml | 270 +------ extra/crds.yaml | 108 ++- rust/operator-binary/Cargo.toml | 8 +- .../operator-binary/src/authentication/mod.rs | 90 ++- .../src/authentication/password/mod.rs | 50 +- rust/operator-binary/src/authorization/opa.rs | 1 + rust/operator-binary/src/catalog/config.rs | 1 + rust/operator-binary/src/command.rs | 8 +- .../src/config/client_protocol.rs | 10 +- .../src/config/fault_tolerant_execution.rs | 4 +- rust/operator-binary/src/config/jvm.rs | 8 +- rust/operator-binary/src/config/s3.rs | 6 - rust/operator-binary/src/controller.rs | 731 +++++------------- rust/operator-binary/src/controller/build.rs | 7 + .../src/controller/build/config_map.rs | 225 ++++++ .../properties/access_control_properties.rs | 46 ++ .../build/properties/config_properties.rs | 278 +++++++ .../properties/exchange_manager_properties.rs | 46 ++ .../build/properties/log_properties.rs | 47 ++ .../src/controller/build/properties/mod.rs | 105 +++ .../build/properties/node_properties.rs | 48 ++ .../build/properties/security_properties.rs | 65 ++ .../properties/spooling_manager_properties.rs | 46 ++ .../src/controller/build/properties/writer.rs | 143 ++++ .../src/controller/validate.rs | 289 ++++--- rust/operator-binary/src/crd/mod.rs | 397 ++-------- rust/operator-binary/src/framework.rs | 10 + .../src/framework/role_utils.rs | 156 ++++ rust/operator-binary/src/main.rs | 9 +- .../src/operations/graceful_shutdown.rs | 51 +- rust/operator-binary/src/product_logging.rs | 46 +- rust/operator-binary/src/service.rs | 13 +- .../src/webhooks/conversion.rs | 4 +- 38 files changed, 2470 insertions(+), 2120 deletions(-) create mode 100644 rust/operator-binary/src/controller/build.rs create mode 100644 rust/operator-binary/src/controller/build/config_map.rs create mode 100644 rust/operator-binary/src/controller/build/properties/access_control_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/config_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/log_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/mod.rs create mode 100644 rust/operator-binary/src/controller/build/properties/node_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/security_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs create mode 100644 rust/operator-binary/src/controller/build/properties/writer.rs create mode 100644 rust/operator-binary/src/framework.rs create mode 100644 rust/operator-binary/src/framework/role_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 9227a96e7..a330e784e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,9 +163,9 @@ 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" @@ -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.60" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -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" @@ -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", @@ -1054,9 +1054,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1086,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", @@ -1137,9 +1137,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1335,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", @@ -1350,7 +1350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", ] [[package]] @@ -1368,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.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1412,9 +1402,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1422,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.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1463,9 +1453,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1475,14 +1465,15 @@ dependencies = [ [[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]] @@ -1524,13 +1515,28 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" 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" @@ -1650,15 +1656,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +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", @@ -1674,9 +1680,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.28" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -1701,9 +1707,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "matchers" @@ -1722,9 +1728,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" @@ -1744,9 +1750,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1773,16 +1779,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.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2046,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", @@ -2105,9 +2111,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -2219,9 +2225,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", @@ -2458,9 +2464,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -2473,9 +2479,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", @@ -2485,9 +2491,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", ] @@ -2661,9 +2667,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", @@ -2741,9 +2747,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" @@ -2804,11 +2810,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]] @@ -2836,9 +2842,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", @@ -2848,9 +2854,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2881,7 +2887,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "const-oid", "ecdsa", @@ -2893,7 +2899,7 @@ dependencies = [ "rsa", "sha2", "signature", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-shared", "tokio", "tokio-rustls", @@ -2904,8 +2910,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.111.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +version = "0.111.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "base64", "clap", @@ -2929,7 +2935,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", "stackable-telemetry", @@ -2941,12 +2947,13 @@ 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.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "darling", "proc-macro2", @@ -2957,7 +2964,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "jiff", "k8s-openapi", @@ -2966,7 +2973,7 @@ dependencies = [ "semver", "serde", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "time", ] @@ -2974,7 +2981,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "axum", "clap", @@ -2985,7 +2992,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "tokio", "tower", @@ -3006,36 +3013,36 @@ dependencies = [ "const_format", "futures 0.3.32", "indoc", - "product-config", "rstest", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator", "strum", "tokio", "tracing", + "uuid", ] [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" 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.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "convert_case", "convert_case_extras", @@ -3053,7 +3060,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "arc-swap", "async-trait", @@ -3069,7 +3076,7 @@ dependencies = [ "rand 0.9.4", "serde", "serde_json", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-certs", "stackable-shared", "stackable-telemetry", @@ -3115,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" @@ -3270,9 +3283,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3342,9 +3355,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime", @@ -3363,9 +3376,9 @@ dependencies = [ [[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", @@ -3390,9 +3403,9 @@ 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", @@ -3420,9 +3433,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", @@ -3430,13 +3443,13 @@ dependencies = [ "futures-util", "http", "http-body", - "iri-string", "mime", "pin-project-lite", "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3465,11 +3478,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", @@ -3562,9 +3576,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" @@ -3627,6 +3641,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" @@ -3662,18 +3686,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.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3684,9 +3708,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3694,9 +3718,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3704,9 +3728,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3717,18 +3741,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3887,18 +3911,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +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" @@ -3922,9 +3946,9 @@ 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" @@ -3951,18 +3975,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", @@ -3971,9 +3995,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/Cargo.nix b/Cargo.nix index 628be318d..08e91cdec 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -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 " ]; @@ -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.60"; + version = "1.2.63"; edition = "2018"; - sha256 = "084a8ziprdlyrj865f3303qr0b7aaggilkl18slncss6m4yp1ia3"; + sha256 = "0zy2bqc4nvj6bv2cipx4h4bn65wf1zqf1fw1hsh64mmvg1hh2vjm"; authors = [ "Alex Crichton " ]; @@ -1060,10 +1061,10 @@ rec { }; "clap" = rec { crateName = "clap"; - version = "4.6.0"; + version = "4.6.1"; edition = "2024"; crateBin = []; - sha256 = "0l8k0ja5rf4hpn2g98bqv5m6lkh2q6b6likjpmm6fjw3cxdsz4xi"; + sha256 = "0lcf88l7vlg796rrqr7wipbbmfa5sgsgx4211b7xmxxv8dz13nqx"; dependencies = [ { name = "clap_builder"; @@ -1141,9 +1142,9 @@ rec { }; "clap_derive" = rec { crateName = "clap_derive"; - version = "4.6.0"; + version = "4.6.1"; edition = "2024"; - sha256 = "0snapc468s7n3avr33dky4y7rmb7ha3qsp9l0k5vh6jacf5bs40i"; + sha256 = "1acpz49hi00iv9jkapixjzcv7s51x8qkfaqscjm36rqgf428dkpj"; procMacro = true; dependencies = [ { @@ -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" ]; @@ -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 " @@ -3322,14 +3322,11 @@ rec { }; resolvedDefaultFeatures = [ "allocator-api2" "default" "default-hasher" "equivalent" "inline-more" "raw-entry" ]; }; - "hashbrown 0.17.0" = rec { + "hashbrown 0.17.1" = rec { crateName = "hashbrown"; - version = "0.17.0"; + version = "0.17.1"; edition = "2024"; - sha256 = "0l8gvcz80lvinb7x22h53cqbi2y1fm603y2jhhh9qwygvkb7sijg"; - authors = [ - "Amanieu d'Antras " - ]; + sha256 = "0jmqz7i4yl6cm7rbn0i2ffkfrmwi6xkmzkaldr2v8bcsx2v0jngd"; features = { "alloc" = [ "dep:alloc" ]; "allocator-api2" = [ "dep:allocator-api2" ]; @@ -3404,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 " @@ -3523,9 +3520,9 @@ rec { }; "hyper" = rec { crateName = "hyper"; - version = "1.9.0"; + version = "1.10.1"; edition = "2021"; - sha256 = "1jmwbwqcaficskg76kq402gbymbnh2z4v99xwq3l5aa6n8bg16b2"; + sha256 = "1624nwrh1ci34psqcl3q8q266kha8kd6fmqjj14qck49l59iqa2m"; authors = [ "Sean McArthur " ]; @@ -4296,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" ]; @@ -4332,7 +4329,7 @@ rec { } { name = "hashbrown"; - packageId = "hashbrown 0.17.0"; + packageId = "hashbrown 0.17.1"; usesDefaultFeatures = false; } ]; @@ -4390,39 +4387,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "iri-string" = rec { - crateName = "iri-string"; - version = "0.7.12"; - edition = "2021"; - sha256 = "082fpx6c5ghvmqpwxaf2b268m47z2ic3prajqbmi1s1qpfj5kri5"; - 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"; @@ -4492,9 +4456,9 @@ rec { }; "jiff" = rec { crateName = "jiff"; - version = "0.2.23"; + version = "0.2.28"; edition = "2021"; - sha256 = "0nc37n7jvgrzxdkcgc2hsfdf70lfagigjalh4igjrm5njvf4cd8s"; + sha256 = "00lixngcc7amh2fcsxfr0z38j06lllhapz192biv1qj97q1x60s6"; authors = [ "Andrew Gallant " ]; @@ -4540,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 = [ @@ -4564,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" ]; @@ -4574,9 +4536,9 @@ rec { }; "jiff-static" = rec { crateName = "jiff-static"; - version = "0.2.23"; + version = "0.2.28"; edition = "2021"; - sha256 = "192ss3cnixvg79cpa76clwkhn4mmz10vnwsbf7yjw8i484s8p31a"; + sha256 = "0irbhfh2f4i9w5l53jcmh6ssnhdd92wfy76978chgwnxilvk4bbq"; procMacro = true; libName = "jiff_static"; authors = [ @@ -4656,9 +4618,9 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.95"; + version = "0.3.99"; edition = "2021"; - sha256 = "1jhj3kgxxgwm0cpdjiz7i2qapqr7ya9qswadmr63dhwx3lnyjr19"; + sha256 = "04azrzsz91gr5s3z0ij36lz0kj9ry4lw3jz0mmbiwb251rsc8aql"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" @@ -4667,7 +4629,6 @@ rec { { name = "cfg-if"; packageId = "cfg-if"; - optional = true; } { name = "futures-util"; @@ -4689,17 +4650,16 @@ rec { ]; features = { "default" = [ "std" "unsafe-eval" ]; - "futures" = [ "dep:cfg-if" "dep:futures-util" ]; - "futures-core-03-stream" = [ "futures" "dep:futures-core" ]; - "std" = [ "wasm-bindgen/std" ]; + "futures-core-03-stream" = [ "dep:futures-util" "dep:futures-core" ]; + "std" = [ "wasm-bindgen/std" "dep:futures-util" ]; }; - resolvedDefaultFeatures = [ "default" "futures" "std" "unsafe-eval" ]; + 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 " @@ -4709,6 +4669,11 @@ rec { name = "jsonptr"; packageId = "jsonptr"; } + { + name = "schemars"; + packageId = "schemars"; + optional = true; + } { name = "serde"; packageId = "serde"; @@ -4720,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"; @@ -4735,7 +4704,7 @@ rec { "schemars" = [ "dep:schemars" ]; "utoipa" = [ "dep:utoipa" ]; }; - resolvedDefaultFeatures = [ "default" "diff" ]; + resolvedDefaultFeatures = [ "default" "diff" "schemars" ]; }; "jsonpath-rust" = rec { crateName = "jsonpath-rust"; @@ -4861,9 +4830,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "k8s_version"; authors = [ @@ -4881,7 +4850,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } ]; features = { @@ -4890,6 +4859,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"; @@ -5466,9 +5482,9 @@ rec { }; "libc" = rec { crateName = "libc"; - version = "0.2.185"; + version = "0.2.186"; edition = "2021"; - sha256 = "13rbdaa59l3w92q7kfcxx8zbikm99zzw54h59aqvcv5wx47jrzsj"; + sha256 = "0rnyhzjyqq9x56skkllbjzzzwym3r61lq3l4hqj64v71gw0r3av8"; authors = [ "The Rust Project Developers" ]; @@ -5482,10 +5498,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 = [ @@ -5543,10 +5559,10 @@ rec { }; "libz-sys" = rec { crateName = "libz-sys"; - version = "1.1.28"; + version = "1.1.29"; edition = "2018"; links = "z"; - sha256 = "08hyf9v85zifl3353xc7i5wr53v9b3scri856cmphl3gaxp24fpw"; + sha256 = "1n98kqya7a7a0cxf5n5z3g13rj7a1vqxynk2xc7bja1qfxbrdg45"; libName = "libz_sys"; authors = [ "Alex Crichton " @@ -5623,9 +5639,9 @@ rec { }; "log" = rec { crateName = "log"; - version = "0.4.29"; + version = "0.4.30"; edition = "2021"; - sha256 = "15q8j9c8g5zpkcw0hnd6cf2z7fxqnvsjh3rw5mv5q10r83i34l2y"; + sha256 = "1rd6sw3gv9hb93464w7x3sip99zf8sjagm662r2ckg14b1lcavk1"; authors = [ "The Rust Project Developers" ]; @@ -5679,9 +5695,9 @@ rec { }; "memchr" = rec { crateName = "memchr"; - version = "2.8.0"; + version = "2.8.1"; edition = "2021"; - sha256 = "0y9zzxcqxvdqg6wyag7vc3h0blhdn7hkq164bxyx2vph8zs5ijpq"; + sha256 = "1n448jx01h5z2xknj6x2dhxgr8s8fb717cf6vfqj5lmhkpj7m53b"; authors = [ "Andrew Gallant " "bluss" @@ -5742,9 +5758,9 @@ rec { }; "mio" = rec { crateName = "mio"; - version = "1.2.0"; + version = "1.2.1"; edition = "2021"; - sha256 = "1hanrh4fwsfkdqdaqfidz48zz1wdix23zwn3r2x78am0garfbdsh"; + sha256 = "1nkggmrlnjs93w8rja4lvjj4aml1xqahgimv1h0p7d373kvhmg82"; authors = [ "Carl Lerche " "Thomas de Zeeuw " @@ -5843,7 +5859,7 @@ rec { } { name = "rand"; - packageId = "rand 0.8.5"; + packageId = "rand 0.8.6"; optional = true; usesDefaultFeatures = false; } @@ -5862,7 +5878,7 @@ rec { devDependencies = [ { name = "rand"; - packageId = "rand 0.8.5"; + packageId = "rand 0.8.6"; features = [ "small_rng" ]; } ]; @@ -5880,9 +5896,9 @@ rec { }; "num-conv" = rec { crateName = "num-conv"; - version = "0.2.1"; + version = "0.2.2"; edition = "2021"; - sha256 = "0rqrr29brafaa2za352pbmhkk556n7f8z9rrkgmjp1idvdl3fry6"; + sha256 = "0hg4f9bwmy7cwpxdkm165dmkfc8jhkkayci234jsmi5ssb33j5sj"; libName = "num_conv"; authors = [ "Jacob Pratt " @@ -6830,9 +6846,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 = [ { @@ -6844,9 +6860,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 = [ @@ -6969,9 +6985,9 @@ rec { }; "portable-atomic-util" = rec { crateName = "portable-atomic-util"; - version = "0.2.6"; + version = "0.2.7"; edition = "2018"; - sha256 = "18wrsx7fjwc2kgbpfjfm3igv3vdzsidmjhbqivjln7d0c6z9f4q9"; + sha256 = "0616j0fhy6y71hyxg3n86f6hng0fmsc269s3wp4gl8ww4p8hd8f2"; libName = "portable_atomic_util"; dependencies = [ { @@ -6982,6 +6998,7 @@ rec { } ]; features = { + "serde" = [ "dep:serde" ]; "std" = [ "alloc" ]; }; resolvedDefaultFeatures = [ "alloc" ]; @@ -7265,11 +7282,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" @@ -7291,12 +7308,9 @@ 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" ]; }; @@ -8200,9 +8214,9 @@ rec { }; "rustls" = rec { crateName = "rustls"; - version = "0.23.38"; + version = "0.23.40"; edition = "2021"; - sha256 = "089ssmhd79f0kd22brh6lkaadql2p3pi6579ax1s0kn1n9pldyb9"; + sha256 = "12qnv3ag4wrw7aj8jng74kgrilpjm2b1rfcjaac8h691frccv1pg"; dependencies = [ { name = "log"; @@ -8269,9 +8283,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 = [ { @@ -8300,9 +8314,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 = [ { @@ -8838,9 +8852,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 " @@ -9081,9 +9095,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 " @@ -9262,29 +9276,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" ]; }; @@ -9352,11 +9362,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 = [ @@ -9382,7 +9392,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 = { @@ -9390,9 +9400,9 @@ rec { }; "socket2" = rec { crateName = "socket2"; - version = "0.6.3"; + version = "0.6.4"; edition = "2021"; - sha256 = "0gkjjcyn69hqhhlh5kl8byk5m0d7hyrp2aqwzbs3d33q208nwxis"; + sha256 = "0ldyp5rhba15spwxj1n94xh7sjks1398c3vwpwkxkd1087nwzlaj"; authors = [ "Alex Crichton " "Thomas de Zeeuw " @@ -9490,9 +9500,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_certs"; authors = [ @@ -9550,7 +9560,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-shared"; @@ -9589,13 +9599,13 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.111.0"; + version = "0.111.1"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_operator"; authors = [ @@ -9652,6 +9662,7 @@ rec { { name = "json-patch"; packageId = "json-patch"; + features = [ "schemars" ]; } { name = "k8s-openapi"; @@ -9701,7 +9712,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator-derive"; @@ -9755,6 +9766,10 @@ rec { packageId = "url"; features = [ "serde" ]; } + { + name = "uuid"; + packageId = "uuid"; + } ]; features = { "certs" = [ "dep:stackable-certs" ]; @@ -9773,9 +9788,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9808,9 +9823,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_shared"; authors = [ @@ -9854,7 +9869,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -9889,9 +9904,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_telemetry"; authors = [ @@ -9942,7 +9957,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -10034,10 +10049,6 @@ rec { name = "indoc"; packageId = "indoc"; } - { - name = "product-config"; - packageId = "product-config"; - } { name = "serde"; packageId = "serde"; @@ -10053,7 +10064,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator"; @@ -10074,6 +10085,10 @@ rec { name = "tracing"; packageId = "tracing"; } + { + name = "uuid"; + packageId = "uuid"; + } ]; buildDependencies = [ { @@ -10100,9 +10115,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_versioned"; authors = [ @@ -10135,7 +10150,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-versioned-macros"; @@ -10150,9 +10165,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10218,9 +10233,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_webhook"; authors = [ @@ -10292,7 +10307,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-certs"; @@ -10422,6 +10437,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"; @@ -10854,9 +10879,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.52.0"; + version = "1.52.3"; edition = "2021"; - sha256 = "0xnpygq9578c8rqjgkj5bj8pgfx9zj337kvk3v4kigqwkgska4d9"; + sha256 = "1zpzazypkg61sw91na1m85x5s4rsjym335fwwhwm1hcs70dz1iwg"; authors = [ "Tokio Contributors " ]; @@ -11166,9 +11191,9 @@ rec { }; "toml_edit" = rec { crateName = "toml_edit"; - version = "0.25.11+spec-1.1.0"; + version = "0.25.12+spec-1.1.0"; edition = "2024"; - sha256 = "0awzffbkx33v9x4h19b5mfrwp3sn4ifr16y58sbk6j6l5v9c8n8b"; + sha256 = "1mx5paq837rjw7w51zprrjynk1vaig9yzxfqz9ac79jmd7f3w5fj"; dependencies = [ { name = "indexmap"; @@ -11221,9 +11246,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 " ]; @@ -11350,9 +11375,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 " @@ -11494,9 +11519,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 " @@ -11530,11 +11555,6 @@ rec { packageId = "http-body"; optional = true; } - { - name = "iri-string"; - packageId = "iri-string"; - optional = true; - } { name = "mime"; packageId = "mime"; @@ -11564,6 +11584,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "url"; + packageId = "url"; + optional = true; + } ]; devDependencies = [ { @@ -11585,35 +11610,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" ]; @@ -11622,7 +11645,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"; @@ -11695,9 +11718,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 " @@ -11708,6 +11731,10 @@ rec { name = "crossbeam-channel"; packageId = "crossbeam-channel"; } + { + name = "symlink"; + packageId = "symlink"; + } { name = "thiserror"; packageId = "thiserror 2.0.18"; @@ -12062,13 +12089,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" ]; @@ -12229,6 +12252,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"; @@ -12296,9 +12379,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"; @@ -12316,9 +12399,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.118"; + version = "0.2.122"; edition = "2021"; - sha256 = "129s5r14fx4v4xrzpx2c6l860nkxpl48j50y7kl6j16bpah3iy8b"; + sha256 = "02flix96brsb2r1i3grnikii302iqpdm337kl3xv5lklz5v4bl1y"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12367,9 +12450,9 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.68"; + version = "0.4.72"; edition = "2021"; - sha256 = "1y7bq5d9fk7s9xaayx38bgs9ns35na0kpb5zw19944zvya1x6wgk"; + sha256 = "03qb24gfr072rk8hb69glfdc8yhqqqq2rhy3j5i0ps8sk79dnwwl"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" @@ -12379,7 +12462,6 @@ rec { name = "js-sys"; packageId = "js-sys"; usesDefaultFeatures = false; - features = [ "futures" ]; } { name = "wasm-bindgen"; @@ -12396,9 +12478,9 @@ rec { }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.118"; + version = "0.2.122"; edition = "2021"; - sha256 = "1v98r8vs17cj8918qsg0xx4nlg4nxk1g0jd4nwnyrh1687w29zzf"; + sha256 = "1inyl55bvdifx7l60q9wl0ivmw7236jg7jqmcqpxhsx3knq52qci"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12420,9 +12502,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.118"; + version = "0.2.122"; edition = "2021"; - sha256 = "0169jr0q469hfx5zqxfyywf2h2f4aj17vn4zly02nfwqmxghc24x"; + sha256 = "0pjw5kc2mbfz59agk5l21kh4hxzp94rygdvsnr4f3z6b5hv4g419"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12456,10 +12538,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.118"; + version = "0.2.122"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "0ag1vvdzi4334jlzilsy14y3nyzwddf1ndn62fyhf6bg62g4vl2z"; + sha256 = "0ds4mmfqvxwc5fp33hn0jblf0f6b4lghrd9mpkls66zic4n9p4ls"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12474,9 +12556,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.95"; + version = "0.3.99"; edition = "2021"; - sha256 = "0zfr2jy5bpkkggl88i43yy37p538hg20i56kwn421yj9g6qznbag"; + sha256 = "0dilfvl9jnyhi4skl6cry9wc300r693j0w82jjbq8yy3rx0i8qkd"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -12560,6 +12642,7 @@ rec { "CssStyleSheet" = [ "StyleSheet" ]; "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ]; "CssTransition" = [ "Animation" "EventTarget" ]; + "CssViewTransitionRule" = [ "CssRule" ]; "CustomEvent" = [ "Event" ]; "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ]; "DelayNode" = [ "AudioNode" "EventTarget" ]; @@ -13636,7 +13719,7 @@ rec { "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" ]; + 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-targets" = rec { crateName = "windows-targets"; @@ -13773,9 +13856,9 @@ rec { }; "winnow" = rec { crateName = "winnow"; - version = "1.0.1"; + version = "1.0.3"; edition = "2021"; - sha256 = "1dbji1bwviy08pl74f2qw2m4w9hc4p3vyl3lfj05jdydy59w1nh9"; + sha256 = "1wajycd3krn6h699vydjv7hm0ll5l31p899qzpk59y2is74y34h5"; dependencies = [ { name = "memchr"; @@ -13798,19 +13881,20 @@ rec { }; "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" ]; }; @@ -13886,9 +13970,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)" @@ -13963,9 +14047,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.48"; + version = "0.8.50"; edition = "2021"; - sha256 = "1sb8plax8jbrsng1jdval7bdhk7hhrx40dz3hwh074k6knzkgm7f"; + sha256 = "1laahnfxs4qyfb1fdf5nbb2qfshi72b1hbi0ffp2zy2m1r7ms1iv"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -13999,9 +14083,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.48"; + version = "0.8.50"; edition = "2021"; - sha256 = "1m5s0g92cxggqc74j83k1priz24k3z93sj5gadppd20p9c4cvqvh"; + sha256 = "0fdnr9qslx1hbn2i9rsvy9s95mychfy2vj90ajsjm2basccinqqb"; procMacro = true; libName = "zerocopy_derive"; authors = [ @@ -14034,11 +14118,11 @@ rec { }; "zerofrom" = rec { crateName = "zerofrom"; - version = "0.1.7"; + version = "0.1.8"; edition = "2021"; - sha256 = "1py40in4rirc9q8w36q67pld0zk8ssg024xhh0cncxgal7ra3yk9"; + sha256 = "0wjjdj7gdmd0iq91gzkxl7dlv0nhkk80l4bmdpzh3a1yh48mmh0f"; authors = [ - "Manish Goregaokar " + "The ICU4X Project Developers" ]; dependencies = [ { diff --git a/Cargo.toml b/Cargo.toml index acced5f78..d2ad0181d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,12 @@ edition = "2021" repository = "https://github.com/stackabletech/trino-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.0", features = ["webhook"] } anyhow = "1.0" async-trait = "0.1" built = { version = "0.8", features = ["chrono", "git2"] } -clap = "4.5" +clap = "4.6" const_format = "0.2" futures = { version = "0.3", features = ["compat"] } indoc = "2.0" @@ -26,9 +25,10 @@ serde_json = "1.0" serde_yaml = "0.9" snafu = "0.9" strum = { version = "0.28", features = ["derive"] } -tokio = { version = "1.40", features = ["full"] } +tokio = { version = "1.52", features = ["full"] } tracing = "0.1" +uuid = "1.23" -# [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } +[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" } diff --git a/crate-hashes.json b/crate-hashes.json index 71fbc1c38..c76bf06c2 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#k8s-version@0.1.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-certs@0.4.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-operator-derive@0.3.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-operator@0.111.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-shared@0.1.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-telemetry@0.6.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-versioned-macros@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-versioned@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-webhook@0.9.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.3": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "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 89c7e6b32..9bd8c3b2a 100644 --- a/deploy/config-spec/properties.yaml +++ b/deploy/config-spec/properties.yaml @@ -1,269 +1,5 @@ +--- version: 0.1.0 spec: - units: - - unit: &unitNodeEnvironment - name: "node_environment" - regex: "^[a-z][a-z0-9_]*[a-z0-9]$" - examples: - - "a1_2_3b" - - unit: &unitMemory - name: "memory" - regex: "(^\\p{N}+)(?:\\s*)((?:b|k|m|g|t|p|kb|mb|gb|tb|pb|B|K|M|G|T|P|KB|MB|GB|TB|PB)\\b$)" - examples: - - "1024b" - - "1024kb" - - "500m" - - "1g" - -################################################################################################### -# node.properties -################################################################################################### -properties: - - property: - 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: "coordinator" - required: true - asOfVersion: "0.0.0" - comment: "TTL for successfully resolved domain names." - description: "TTL for successfully resolved domain names." - - - property: - 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: "worker" - required: true - asOfVersion: "0.0.0" - comment: "TTL for successfully resolved domain names." - description: "TTL for successfully resolved domain names." - - - property: - 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: "coordinator" - required: true - asOfVersion: "0.0.0" - comment: "TTL for domain names that cannot be resolved." - description: "TTL for domain names that cannot be resolved." - - - property: - 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: "worker" - required: true - asOfVersion: "0.0.0" - comment: "TTL for domain names that cannot be resolved." - description: "TTL for domain names that cannot be resolved." - - - - property: &nodeEnvironment - propertyNames: - - name: "node.environment" - kind: - type: "file" - file: "node.properties" - datatype: - type: "string" - unit: *unitNodeEnvironment - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - -################################################################################################### -# config.properties -################################################################################################### - - property: &coordinator - propertyNames: - - name: "coordinator" - kind: - type: "file" - file: "config.properties" - datatype: - type: "bool" - defaultValues: - - fromVersion: "0.0.0" - value: "false" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - - - property: &nodeSchedulerIncludeCoordinator - propertyNames: - - name: "node-scheduler.include-coordinator" - kind: - type: "file" - file: "config.properties" - datatype: - type: "bool" - defaultValues: - - fromVersion: "0.0.0" - value: "false" - roles: - - name: "coordinator" - required: true - asOfVersion: "0.0.0" - - - property: &httpServerHttpPort - propertyNames: - - name: "http-server.http.port" - kind: - type: "file" - file: "config.properties" - datatype: - type: "integer" - min: "1024" - max: "65535" - defaultValues: - - fromVersion: "0.0.0" - value: "8080" - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &httpServerHttpsPort - propertyNames: - - name: "http-server.https.port" - kind: - type: "file" - file: "config.properties" - datatype: - type: "integer" - min: "1024" - max: "65535" - defaultValues: - - fromVersion: "0.0.0" - value: "8443" - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &queryMaxMemory - propertyNames: - - name: "query.max-memory" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - unit: *unitMemory - defaultValues: - - fromVersion: "0.0.0" - value: "50GB" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - - - property: &queryMaxMemoryPerNode - propertyNames: - - name: "query.max-memory-per-node" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - unit: *unitMemory - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &httpServerAuthenticationType - propertyNames: - - name: "http-server.authentication.type" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - roles: - - name: "coordinator" - required: false - asOfVersion: "0.0.0" - -################################################################################################### -# jvm.config -################################################################################################### - -################################################################################################### -# log.properties -################################################################################################### - - - property: &ioTrino - propertyNames: - - name: "io.trino" - kind: - type: "file" - file: "log.properties" - datatype: - type: "string" - defaultValues: - - fromVersion: "0.0.0" - value: "INFO" - allowedValues: - - "INFO" - - "DEBUG" - - "WARN" - - "ERROR" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" + units: [] +properties: [] diff --git a/deploy/helm/trino-operator/configs/properties.yaml b/deploy/helm/trino-operator/configs/properties.yaml index 89c7e6b32..9bd8c3b2a 100644 --- a/deploy/helm/trino-operator/configs/properties.yaml +++ b/deploy/helm/trino-operator/configs/properties.yaml @@ -1,269 +1,5 @@ +--- version: 0.1.0 spec: - units: - - unit: &unitNodeEnvironment - name: "node_environment" - regex: "^[a-z][a-z0-9_]*[a-z0-9]$" - examples: - - "a1_2_3b" - - unit: &unitMemory - name: "memory" - regex: "(^\\p{N}+)(?:\\s*)((?:b|k|m|g|t|p|kb|mb|gb|tb|pb|B|K|M|G|T|P|KB|MB|GB|TB|PB)\\b$)" - examples: - - "1024b" - - "1024kb" - - "500m" - - "1g" - -################################################################################################### -# node.properties -################################################################################################### -properties: - - property: - 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: "coordinator" - required: true - asOfVersion: "0.0.0" - comment: "TTL for successfully resolved domain names." - description: "TTL for successfully resolved domain names." - - - property: - 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: "worker" - required: true - asOfVersion: "0.0.0" - comment: "TTL for successfully resolved domain names." - description: "TTL for successfully resolved domain names." - - - property: - 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: "coordinator" - required: true - asOfVersion: "0.0.0" - comment: "TTL for domain names that cannot be resolved." - description: "TTL for domain names that cannot be resolved." - - - property: - 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: "worker" - required: true - asOfVersion: "0.0.0" - comment: "TTL for domain names that cannot be resolved." - description: "TTL for domain names that cannot be resolved." - - - - property: &nodeEnvironment - propertyNames: - - name: "node.environment" - kind: - type: "file" - file: "node.properties" - datatype: - type: "string" - unit: *unitNodeEnvironment - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - -################################################################################################### -# config.properties -################################################################################################### - - property: &coordinator - propertyNames: - - name: "coordinator" - kind: - type: "file" - file: "config.properties" - datatype: - type: "bool" - defaultValues: - - fromVersion: "0.0.0" - value: "false" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - - - property: &nodeSchedulerIncludeCoordinator - propertyNames: - - name: "node-scheduler.include-coordinator" - kind: - type: "file" - file: "config.properties" - datatype: - type: "bool" - defaultValues: - - fromVersion: "0.0.0" - value: "false" - roles: - - name: "coordinator" - required: true - asOfVersion: "0.0.0" - - - property: &httpServerHttpPort - propertyNames: - - name: "http-server.http.port" - kind: - type: "file" - file: "config.properties" - datatype: - type: "integer" - min: "1024" - max: "65535" - defaultValues: - - fromVersion: "0.0.0" - value: "8080" - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &httpServerHttpsPort - propertyNames: - - name: "http-server.https.port" - kind: - type: "file" - file: "config.properties" - datatype: - type: "integer" - min: "1024" - max: "65535" - defaultValues: - - fromVersion: "0.0.0" - value: "8443" - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &queryMaxMemory - propertyNames: - - name: "query.max-memory" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - unit: *unitMemory - defaultValues: - - fromVersion: "0.0.0" - value: "50GB" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" - - - property: &queryMaxMemoryPerNode - propertyNames: - - name: "query.max-memory-per-node" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - unit: *unitMemory - roles: - - name: "coordinator" - required: false - - name: "worker" - required: false - asOfVersion: "0.0.0" - - - property: &httpServerAuthenticationType - propertyNames: - - name: "http-server.authentication.type" - kind: - type: "file" - file: "config.properties" - datatype: - type: "string" - roles: - - name: "coordinator" - required: false - asOfVersion: "0.0.0" - -################################################################################################### -# jvm.config -################################################################################################### - -################################################################################################### -# log.properties -################################################################################################### - - - property: &ioTrino - propertyNames: - - name: "io.trino" - kind: - type: "file" - file: "log.properties" - datatype: - type: "string" - defaultValues: - - fromVersion: "0.0.0" - value: "INFO" - allowedValues: - - "INFO" - - "DEBUG" - - "WARN" - - "ERROR" - roles: - - name: "coordinator" - required: true - - name: "worker" - required: true - asOfVersion: "0.0.0" + units: [] +properties: [] diff --git a/extra/crds.yaml b/extra/crds.yaml index 59515338a..749c38cfb 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -1340,9 +1340,15 @@ spec: type: boolean type: object queryMaxMemory: + description: |- + This is the max amount of user memory a query can use across the entire cluster. + See nullable: true type: string queryMaxMemoryPerNode: + description: |- + This is the max amount of user memory a query can use on a worker. + See nullable: true type: string requestedSecretLifetime: @@ -1419,73 +1425,80 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -1967,9 +1980,15 @@ spec: type: boolean type: object queryMaxMemory: + description: |- + This is the max amount of user memory a query can use across the entire cluster. + See nullable: true type: string queryMaxMemoryPerNode: + description: |- + This is the max amount of user memory a query can use on a worker. + See nullable: true type: string requestedSecretLifetime: @@ -2046,73 +2065,80 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -2646,9 +2672,15 @@ spec: type: boolean type: object queryMaxMemory: + description: |- + This is the max amount of user memory a query can use across the entire cluster. + See nullable: true type: string queryMaxMemoryPerNode: + description: |- + This is the max amount of user memory a query can use on a worker. + See nullable: true type: string requestedSecretLifetime: @@ -2725,73 +2757,80 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -3268,9 +3307,15 @@ spec: type: boolean type: object queryMaxMemory: + description: |- + This is the max amount of user memory a query can use across the entire cluster. + See nullable: true type: string queryMaxMemoryPerNode: + description: |- + This is the max amount of user memory a query can use on a worker. + See nullable: true type: string requestedSecretLifetime: @@ -3347,73 +3392,80 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index efa9d3472..a81d68dc2 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -10,7 +10,6 @@ publish = false build = "build.rs" [dependencies] -product-config.workspace = true stackable-operator.workspace = true anyhow.workspace = true @@ -19,13 +18,14 @@ clap.workspace = true const_format.workspace = true futures.workspace = true indoc.workspace = true +serde_yaml.workspace = true +serde.workspace = true +serde_json.workspace = true snafu.workspace = true strum.workspace = true tokio.workspace = true tracing.workspace = true -serde_yaml.workspace = true -serde.workspace = true -serde_json.workspace = true +uuid.workspace = true [dev-dependencies] rstest.workspace = true diff --git a/rust/operator-binary/src/authentication/mod.rs b/rust/operator-binary/src/authentication/mod.rs index c31172429..431e7c0ca 100644 --- a/rust/operator-binary/src/authentication/mod.rs +++ b/rust/operator-binary/src/authentication/mod.rs @@ -50,11 +50,6 @@ pub enum Error { authentication_class: ObjectRef, }, - #[snafu(display("failed to format trino authentication java properties"))] - FailedToWriteJavaProperties { - source: product_config::writer::PropertiesWriterError, - }, - #[snafu(display("failed to configure trino password authentication"))] InvalidPasswordAuthenticationConfig { source: password::Error }, @@ -87,7 +82,7 @@ pub struct TrinoAuthenticationConfig { /// All config properties that have to be added to the `config.properties` of the given role config_properties: HashMap>, /// All extra config files required for authentication for each role. - config_files: HashMap>, + config_files: HashMap>>, /// Additional env variables for a certain role and container env_vars: HashMap>>, /// All extra container commands for a certain role and container @@ -211,13 +206,18 @@ impl TrinoAuthenticationConfig { .insert(property_name, property_value); } - /// Add config file for a given role. The file_content must already be formatted to its final - /// representation in the file. - pub fn add_config_file(&mut self, role: TrinoRole, file_name: String, file_content: String) { + /// Add config file for a given role. The `properties` are stored as a raw key/value map and + /// rendered to their final file representation by the ConfigMap builder when writing. + pub fn add_config_file( + &mut self, + role: TrinoRole, + file_name: String, + properties: BTreeMap, + ) { self.config_files .entry(role) .or_default() - .insert(file_name, file_content); + .insert(file_name, properties); } /// Add env variables for a given role and container. @@ -318,7 +318,7 @@ impl TrinoAuthenticationConfig { } /// Retrieve additional config files for a given role. - pub fn config_files(&self, role: &TrinoRole) -> BTreeMap { + pub fn config_files(&self, role: &TrinoRole) -> BTreeMap> { self.config_files.get(role).cloned().unwrap_or_default() } @@ -767,23 +767,73 @@ mod tests { let config_files = setup_authentication_config().config_files(&TrinoRole::Coordinator); assert_eq!( - config_files.get(&format!("{FILE_AUTH_CLASS_1}-password-file-auth.properties")), - Some(format!("file.password-file=/stackable/users/{FILE_AUTH_CLASS_1}.db\npassword-authenticator.name=file\n")).as_ref() - ); + config_files.get(&format!( + "{FILE_AUTH_CLASS_1}-password-file-auth.properties" + )), + Some(&BTreeMap::from([ + ( + "file.password-file".to_string(), + format!("/stackable/users/{FILE_AUTH_CLASS_1}.db") + ), + ( + "password-authenticator.name".to_string(), + "file".to_string() + ), + ])) + ); assert_eq!( - config_files.get(&format!("{FILE_AUTH_CLASS_2}-password-file-auth.properties")), - Some(format!("file.password-file=/stackable/users/{FILE_AUTH_CLASS_2}.db\npassword-authenticator.name=file\n")).as_ref() + config_files.get(&format!( + "{FILE_AUTH_CLASS_2}-password-file-auth.properties" + )), + Some(&BTreeMap::from([ + ( + "file.password-file".to_string(), + format!("/stackable/users/{FILE_AUTH_CLASS_2}.db") + ), + ( + "password-authenticator.name".to_string(), + "file".to_string() + ), + ])) ); assert_eq!( - config_files.get(&format!("{LDAP_AUTH_CLASS_1}-password-ldap-auth.properties")), - Some(format!("ldap.allow-insecure=true\nldap.group-auth-pattern=(&(uid\\=${{USER}}))\nldap.url=ldap\\://{HOST_NAME}\\:389\nldap.user-base-dn={SEARCH_BASE}\npassword-authenticator.name=ldap\n")).as_ref() + config_files.get(&format!( + "{LDAP_AUTH_CLASS_1}-password-ldap-auth.properties" + )), + Some(&BTreeMap::from([ + ("ldap.allow-insecure".to_string(), "true".to_string()), + ( + "ldap.group-auth-pattern".to_string(), + "(&(uid=${USER}))".to_string() + ), + ("ldap.url".to_string(), format!("ldap://{HOST_NAME}:389")), + ("ldap.user-base-dn".to_string(), SEARCH_BASE.to_string()), + ( + "password-authenticator.name".to_string(), + "ldap".to_string() + ), + ])) ); assert_eq!( - config_files.get(&format!("{LDAP_AUTH_CLASS_2}-password-ldap-auth.properties")), - Some(format!("ldap.allow-insecure=true\nldap.group-auth-pattern=(&(uid\\=${{USER}}))\nldap.url=ldap\\://{HOST_NAME}\\:389\nldap.user-base-dn={SEARCH_BASE}\npassword-authenticator.name=ldap\n")).as_ref() + config_files.get(&format!( + "{LDAP_AUTH_CLASS_2}-password-ldap-auth.properties" + )), + Some(&BTreeMap::from([ + ("ldap.allow-insecure".to_string(), "true".to_string()), + ( + "ldap.group-auth-pattern".to_string(), + "(&(uid=${USER}))".to_string() + ), + ("ldap.url".to_string(), format!("ldap://{HOST_NAME}:389")), + ("ldap.user-base-dn".to_string(), SEARCH_BASE.to_string()), + ( + "password-authenticator.name".to_string(), + "ldap".to_string() + ), + ])) ); } diff --git a/rust/operator-binary/src/authentication/password/mod.rs b/rust/operator-binary/src/authentication/password/mod.rs index f5eeb09ce..54cae2c3a 100644 --- a/rust/operator-binary/src/authentication/password/mod.rs +++ b/rust/operator-binary/src/authentication/password/mod.rs @@ -8,8 +8,6 @@ //! - volume and volume mounts //! - extra containers and commands //! -use std::collections::BTreeMap; - use snafu::{ResultExt, Snafu}; use stackable_operator::commons::product_image_selection::ResolvedProductImage; use tracing::trace; @@ -34,11 +32,6 @@ pub enum Error { #[snafu(display("failed to configure LDAP password authentication"))] InvalidLdapAuthenticationConfiguration { source: ldap::Error }, - #[snafu(display("failed to write password authentication config file"))] - WritePasswordAuthenticationFile { - source: product_config::writer::PropertiesWriterError, - }, - #[snafu(display("failed to create LDAP Volumes and VolumeMounts"))] LdapVolumeAndVolumeMounts { source: ldap::Error }, @@ -93,15 +86,7 @@ impl TrinoPasswordAuthentication { password_authentication_config.add_config_file( TrinoRole::Coordinator, config_file_name, - product_config::writer::to_java_properties_string( - file_authenticator - .config_file_data() - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect::>>() - .iter(), - ) - .context(WritePasswordAuthenticationFileSnafu)?, + file_authenticator.config_file_data(), ); // required volumes password_authentication_config.add_volume(file_authenticator.secret_volume()); @@ -133,16 +118,9 @@ impl TrinoPasswordAuthentication { password_authentication_config.add_config_file( TrinoRole::Coordinator, config_file_name, - product_config::writer::to_java_properties_string( - ldap_authenticator - .config_file_data() - .context(InvalidLdapAuthenticationConfigurationSnafu)? - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect::>>() - .iter(), - ) - .context(WritePasswordAuthenticationFileSnafu)?, + ldap_authenticator + .config_file_data() + .context(InvalidLdapAuthenticationConfigurationSnafu)?, ); // extra commands @@ -206,6 +184,8 @@ impl TrinoPasswordAuthentication { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use stackable_operator::crd::authentication::{ldap, r#static}; use super::*; @@ -300,16 +280,24 @@ mod tests { // check file auth assert_eq!( config_files.get(&file_auth_1.config_file_name()).unwrap(), - &format!( - "file.password-file=/stackable/users/{FILE_AUTH_CLASS_1}.db\npassword-authenticator.name=file\n" - ) + &BTreeMap::from([ + ( + "file.password-file".to_string(), + format!("/stackable/users/{FILE_AUTH_CLASS_1}.db") + ), + ( + "password-authenticator.name".to_string(), + "file".to_string() + ), + ]) ); // check ldap - assert!( + assert_eq!( config_files .get(&ldap_auth_1.config_file_name()) .unwrap() - .contains("password-authenticator.name=ldap") + .get("password-authenticator.name"), + Some(&"ldap".to_string()) ); // Coordinator diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 1fa9bd95f..0ae42bd27 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -9,6 +9,7 @@ use crate::crd::v1alpha1; pub const OPA_TLS_VOLUME_NAME: &str = "opa-tls"; +#[derive(Clone, Debug)] pub struct TrinoOpaConfig { /// URI for OPA policies, e.g. /// `http://localhost:8081/v1/data/trino/allow` diff --git a/rust/operator-binary/src/catalog/config.rs b/rust/operator-binary/src/catalog/config.rs index 8d4ef5530..5104b772c 100644 --- a/rust/operator-binary/src/catalog/config.rs +++ b/rust/operator-binary/src/catalog/config.rs @@ -11,6 +11,7 @@ use stackable_operator::{ use super::{FromTrinoCatalogError, ToCatalogConfig}; use crate::crd::catalog::{TrinoCatalogConnector, v1alpha1}; +#[derive(Clone, Debug)] pub struct CatalogConfig { /// Name of the catalog pub name: String, diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index e14cac173..2e1f9ab5f 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -12,14 +12,18 @@ use crate::{ config::{client_protocol, fault_tolerant_execution}, controller::{STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR}, crd::{ - CONFIG_DIR_NAME, Container, EXCHANGE_MANAGER_PROPERTIES, LOG_PROPERTIES, - RW_CONFIG_DIR_NAME, SPOOLING_MANAGER_PROPERTIES, STACKABLE_CLIENT_TLS_DIR, + CONFIG_DIR_NAME, Container, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, TrinoRole, v1alpha1, }, }; +// File names not exported from crd/mod.rs. +const LOG_PROPERTIES: &str = "log.properties"; +const EXCHANGE_MANAGER_PROPERTIES: &str = "exchange-manager.properties"; +const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; + pub fn container_prepare_args( trino: &v1alpha1::TrinoCluster, catalogs: &[CatalogConfig], diff --git a/rust/operator-binary/src/config/client_protocol.rs b/rust/operator-binary/src/config/client_protocol.rs index 96354c21a..478f7f48a 100644 --- a/rust/operator-binary/src/config/client_protocol.rs +++ b/rust/operator-binary/src/config/client_protocol.rs @@ -20,17 +20,9 @@ use crate::{ pub enum Error { #[snafu(display("failed to resolve S3 connection"))] ResolveS3Connection { source: config::s3::Error }, - - #[snafu(display("trino does not support disabling the TLS verification of S3 servers"))] - S3TlsNoVerificationNotSupported, - - #[snafu(display("failed to convert data size for [{field}] to bytes"))] - QuantityConversion { - source: stackable_operator::memory::Error, - field: &'static str, - }, } +#[derive(Clone, Debug)] pub struct ResolvedClientProtocolConfig { /// Properties to add to config.properties pub config_properties: BTreeMap, diff --git a/rust/operator-binary/src/config/fault_tolerant_execution.rs b/rust/operator-binary/src/config/fault_tolerant_execution.rs index 852f15c3e..6e4c2a14e 100644 --- a/rust/operator-binary/src/config/fault_tolerant_execution.rs +++ b/rust/operator-binary/src/config/fault_tolerant_execution.rs @@ -31,9 +31,6 @@ pub enum Error { #[snafu(display("failed to resolve S3 connection"))] ResolveS3Connection { source: config::s3::Error }, - #[snafu(display("trino does not support disabling the TLS verification of S3 servers"))] - S3TlsNoVerificationNotSupported, - #[snafu(display("failed to convert data size for [{field}] to bytes"))] QuantityConversion { source: stackable_operator::memory::Error, @@ -42,6 +39,7 @@ pub enum Error { } /// Fault tolerant execution configuration with external resources resolved +#[derive(Clone, Debug)] pub struct ResolvedFaultTolerantExecutionConfig { /// Properties to add to config.properties pub config_properties: BTreeMap, diff --git a/rust/operator-binary/src/config/jvm.rs b/rust/operator-binary/src/config/jvm.rs index 0fa18c674..76a62f5bf 100644 --- a/rust/operator-binary/src/config/jvm.rs +++ b/rust/operator-binary/src/config/jvm.rs @@ -7,10 +7,14 @@ use stackable_operator::{ }; use crate::crd::{ - JVM_HEAP_FACTOR, JVM_SECURITY_PROPERTIES, METRICS_PORT, RW_CONFIG_DIR_NAME, - STACKABLE_CLIENT_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, TrinoRoleType, v1alpha1, + METRICS_PORT, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, + TrinoRoleType, v1alpha1, }; +const JVM_SECURITY_PROPERTIES: &str = "security.properties"; + +const JVM_HEAP_FACTOR: f32 = 0.8; + #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to convert java heap config to unit [{unit}]"))] diff --git a/rust/operator-binary/src/config/s3.rs b/rust/operator-binary/src/config/s3.rs index a403ce836..c75a82089 100644 --- a/rust/operator-binary/src/config/s3.rs +++ b/rust/operator-binary/src/config/s3.rs @@ -19,12 +19,6 @@ pub enum Error { #[snafu(display("trino does not support disabling the TLS verification of S3 servers"))] S3TlsNoVerificationNotSupported, - - #[snafu(display("failed to convert data size for [{field}] to bytes"))] - QuantityConversion { - source: stackable_operator::memory::Error, - field: &'static str, - }, } pub struct ResolvedS3Config { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 623b914b2..7bfa02b23 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1,22 +1,10 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha1::TrinoCluster`] -use std::{ - collections::{BTreeMap, HashMap}, - convert::Infallible, - num::ParseIntError, - str::FromStr, - sync::Arc, -}; +use std::{collections::BTreeMap, convert::Infallible, sync::Arc}; use const_format::concatcp; -use product_config::{ - self, ProductConfigManager, - types::PropertyNameKind, - writer::{PropertiesWriterError, to_java_properties_string}, -}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ - self, configmap::ConfigMapBuilder, meta::ObjectMetaBuilder, pod::{ @@ -67,45 +55,39 @@ use stackable_operator::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, - utils::cluster_info::KubernetesClusterInfo, }; use strum::{EnumDiscriminants, IntoStaticStr}; +mod build; mod dereference; mod validate; +pub use validate::{TrinoRoleGroupConfig, ValidatedCluster}; + use crate::{ authentication::TrinoAuthenticationConfig, authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, catalog::config::CatalogConfig, command, - config::{self, client_protocol, fault_tolerant_execution}, + config::{client_protocol, fault_tolerant_execution}, crd::{ - ACCESS_CONTROL_PROPERTIES, APP_NAME, CONFIG_DIR_NAME, CONFIG_PROPERTIES, Container, - DISCOVERY_URI, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, EXCHANGE_MANAGER_PROPERTIES, - HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, JVM_CONFIG, - JVM_SECURITY_PROPERTIES, LOG_PROPERTIES, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, - METRICS_PORT_NAME, NODE_PROPERTIES, RW_CONFIG_DIR_NAME, SPOOLING_MANAGER_PROPERTIES, - STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, + APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, + HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, + METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, + STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - TrinoRole, TrinoRoleType, - discovery::{TrinoDiscovery, TrinoDiscoveryProtocol, TrinoPodRef}, - v1alpha1, + TrinoRole, v1alpha1, }, listener::{ LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener, build_group_listener_pvc, group_listener_name, secret_volume_listener_scope, }, - operations::{ - add_graceful_shutdown_config, graceful_shutdown_config_properties, pdb::add_pdbs, - }, - product_logging::{get_log_properties, get_vector_toml}, + operations::{add_graceful_shutdown_config, pdb::add_pdbs}, service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, }; pub struct Ctx { pub client: stackable_operator::client::Client, - pub product_config: ProductConfigManager, pub operator_environment: OperatorEnvironmentOptions, } @@ -152,6 +134,12 @@ pub enum Error { rolegroup: RoleGroupRef, }, + #[snafu(display("failed to build ConfigMap for {}", rolegroup))] + BuildRoleGroupConfigMap { + source: build::config_map::Error, + rolegroup: RoleGroupRef, + }, + #[snafu(display("failed to apply ConfigMap for {}", rolegroup))] ApplyRoleGroupConfig { source: stackable_operator::cluster_resources::Error, @@ -170,38 +158,25 @@ pub enum Error { }, #[snafu(display("failed to format runtime properties"))] - FailedToWriteJavaProperties { source: PropertiesWriterError }, - - #[snafu(display("failed to parse role: {source}"))] - FailedToParseRole { source: strum::ParseError }, + FailedToWriteJavaProperties { + source: build::properties::writer::Error, + }, #[snafu(display("internal operator failure: {source}"))] InternalOperatorFailure { source: crate::crd::Error }, - #[snafu(display("no coordinator pods found for discovery"))] - MissingCoordinatorPods, - #[snafu(display("illegal container name: [{container_name}]"))] IllegalContainerName { source: stackable_operator::builder::pod::container::Error, container_name: String, }, - #[snafu(display("failed to resolve and merge config for role and role group"))] - FailedToResolveConfig { source: crate::crd::Error }, - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] VectorAggregatorConfigMapMissing, #[snafu(display("failed to build vector container"))] BuildVectorContainer { source: LoggingError }, - #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] - InvalidLoggingConfig { - source: crate::product_logging::Error, - cm_name: String, - }, - #[snafu(display("failed to patch service account"))] ApplyServiceAccount { source: stackable_operator::cluster_resources::Error, @@ -222,12 +197,6 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to serialize [{JVM_SECURITY_PROPERTIES}] for {}", rolegroup))] - JvmSecurityProperties { - source: PropertiesWriterError, - rolegroup: String, - }, - #[snafu(display("failed to create PodDisruptionBudget"))] FailedToCreatePdb { source: crate::operations::pdb::Error, @@ -264,15 +233,14 @@ pub enum Error { source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, }, - #[snafu(display("failed to build JVM config"))] - FailedToCreateJvmConfig { source: crate::config::jvm::Error }, - #[snafu(display("failed to add needed volume"))] - AddVolume { source: builder::pod::Error }, + AddVolume { + source: stackable_operator::builder::pod::Error, + }, #[snafu(display("failed to add needed volumeMount"))] AddVolumeMount { - source: builder::pod::container::Error, + source: stackable_operator::builder::pod::container::Error, }, #[snafu(display("invalid TrinoCluster object"))] @@ -286,20 +254,11 @@ pub enum Error { #[snafu(display("failed to validate cluster"))] ValidateCluster { source: validate::Error }, - #[snafu(display("failed to read role"))] - ReadRole { source: crate::crd::Error }, - #[snafu(display("invalid Trino authentication"))] InvalidAuthenticationConfig { source: crate::authentication::Error, }, - #[snafu(display("unable to parse Trino version: {product_version:?}"))] - ParseTrinoVersion { - source: ParseIntError, - product_version: String, - }, - #[snafu(display("failed to apply group listener"))] ApplyGroupListener { source: stackable_operator::cluster_resources::Error, @@ -344,13 +303,8 @@ pub async fn reconcile_trino( .context(DereferenceSnafu)?; // validate (no client required) - let validated = validate::validate( - trino, - &dereferenced_objects, - &ctx.operator_environment, - &ctx.product_config, - ) - .context(ValidateClusterSnafu)?; + let validated = validate::validate(trino, &dereferenced_objects, &ctx.operator_environment) + .context(ValidateClusterSnafu)?; let mut cluster_resources = ClusterResources::new( APP_NAME, @@ -405,19 +359,10 @@ pub async fn reconcile_trino( let mut sts_cond_builder = StatefulSetConditionBuilder::default(); - for (trino_role_str, role_config) in validated.validated_role_config { - let trino_role = TrinoRole::from_str(&trino_role_str).context(FailedToParseRoleSnafu)?; - let role = trino.role(&trino_role).context(ReadRoleSnafu)?; - for (role_group, config) in role_config { - let role_group_ref = trino_role.rolegroup_ref(trino, &role_group); - - let merged_config = trino - .merged_config( - &trino_role, - &role_group_ref, - &dereferenced_objects.catalog_definitions, - ) - .context(FailedToResolveConfigSnafu)?; + for (trino_role, role_group_configs) in &validated.role_group_configs { + for (role_group_name, rg) in role_group_configs { + let role_group_ref = trino_role.rolegroup_ref(trino, role_group_name); + let merged_config = &rg.config; let role_group_service_recommended_labels = build_recommended_labels( trino, @@ -437,7 +382,7 @@ pub async fn reconcile_trino( let rg_headless_service = build_rolegroup_headless_service( trino, &role_group_ref, - role_group_service_recommended_labels.clone(), + &role_group_service_recommended_labels, role_group_service_selector.clone().into(), ) .context(ServiceConfigurationSnafu)?; @@ -445,44 +390,43 @@ pub async fn reconcile_trino( let rg_metrics_service = build_rolegroup_metrics_service( trino, &role_group_ref, - role_group_service_recommended_labels, + &role_group_service_recommended_labels, role_group_service_selector.into(), ) .context(ServiceConfigurationSnafu)?; - let rg_configmap = build_rolegroup_config_map( - trino, - &validated.image, - &role, - &trino_role, + let rg_configmap = build::config_map::build_rolegroup_config_map( + &validated, + trino_role, &role_group_ref, - &config, - &merged_config, - &validated.trino_authentication_config, - &dereferenced_objects.trino_opa_config, &client.kubernetes_cluster_info, - &dereferenced_objects.resolved_fte_config, - &dereferenced_objects.resolved_client_protocol_config, - )?; + &role_group_service_recommended_labels, + trino, + ) + .with_context(|_| BuildRoleGroupConfigMapSnafu { + rolegroup: role_group_ref.clone(), + })?; + let rg_catalog_configmap = build_rolegroup_catalog_config_map( trino, &validated.image, &role_group_ref, - &dereferenced_objects.catalogs, + &validated.cluster_config.catalogs, )?; + let rg_stateful_set = build_rolegroup_statefulset( trino, - &trino_role, + trino_role, &validated.image, &role_group_ref, - &config, - &merged_config, - &validated.trino_authentication_config, - &dereferenced_objects.catalogs, + &rg.env_overrides, + merged_config, + &validated.cluster_config.authentication, + &validated.cluster_config.catalogs, &rbac_sa.name_any(), - &dereferenced_objects.resolved_fte_config, - &dereferenced_objects.resolved_client_protocol_config, - &dereferenced_objects.trino_opa_config, + &validated.cluster_config.fault_tolerant_execution, + &validated.cluster_config.client_protocol, + &validated.cluster_config.authorization, )?; cluster_resources @@ -527,13 +471,13 @@ pub async fn reconcile_trino( } if let Some(listener_class) = trino_role.listener_class_name(trino) { - if let Some(listener_group_name) = group_listener_name(trino, &trino_role) { + if let Some(listener_group_name) = group_listener_name(trino, trino_role) { let role_group_listener = build_group_listener( trino, build_recommended_labels( trino, &validated.image.app_version_label_value, - &trino_role_str, + &trino_role.to_string(), "none", ), listener_class.to_string(), @@ -548,12 +492,12 @@ pub async fn reconcile_trino( } } - let role_config = trino.generic_role_config(&trino_role); + let role_config = trino.generic_role_config(trino_role); if let Some(GenericRoleConfig { pod_disruption_budget: pdb, }) = role_config { - add_pdbs(pdb, trino, &trino_role, client, &mut cluster_resources) + add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) .await .context(FailedToCreatePdbSnafu)?; } @@ -581,248 +525,6 @@ pub async fn reconcile_trino( Ok(Action::await_change()) } -/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator -#[allow(clippy::too_many_arguments)] -fn build_rolegroup_config_map( - trino: &v1alpha1::TrinoCluster, - resolved_product_image: &ResolvedProductImage, - role: &TrinoRoleType, - trino_role: &TrinoRole, - rolegroup_ref: &RoleGroupRef, - config: &HashMap>, - merged_config: &v1alpha1::TrinoConfig, - trino_authentication_config: &TrinoAuthenticationConfig, - trino_opa_config: &Option, - cluster_info: &KubernetesClusterInfo, - resolved_fte_config: &Option, - resolved_spooling_config: &Option, -) -> Result { - let mut cm_conf_data = BTreeMap::new(); - - let product_version = &resolved_product_image.product_version; - let product_version = - u16::from_str(product_version).context(ParseTrinoVersionSnafu { product_version })?; - let jvm_config = config::jvm::jvm_config( - product_version, - merged_config, - role, - &rolegroup_ref.role_group, - ) - .context(FailedToCreateJvmConfigSnafu)?; - - // TODO: we support only one coordinator for now - let coordinator_ref: TrinoPodRef = trino - .coordinator_pods() - .context(InternalOperatorFailureSnafu)? - .next() - .context(MissingCoordinatorPodsSnafu)?; - - // Add additional config files for authentication - cm_conf_data.extend(trino_authentication_config.config_files(trino_role)); - - for (property_name_kind, config) in config { - // We used this temporary map to add all dynamically resolved (e.g. discovery config maps) - // properties. This will be extended with the merged role group properties (transformed_config) - // to respect all possible override settings. - let mut dynamic_resolved_config = BTreeMap::>::new(); - - let transformed_config: BTreeMap> = config - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))) - .collect(); - - match property_name_kind { - PropertyNameKind::File(file_name) if file_name == CONFIG_PROPERTIES => { - // Add authentication properties (only required for the Coordinator) - dynamic_resolved_config.extend( - trino_authentication_config - .config_properties(trino_role) - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect::>>(), - ); - - let protocol = if trino.get_internal_tls().is_some() { - TrinoDiscoveryProtocol::Https - } else { - TrinoDiscoveryProtocol::Http - }; - - let discovery = TrinoDiscovery::new(&coordinator_ref, protocol); - dynamic_resolved_config.insert( - DISCOVERY_URI.to_string(), - Some(discovery.discovery_uri(cluster_info)), - ); - - dynamic_resolved_config - .extend(graceful_shutdown_config_properties(trino, trino_role)); - - // Add fault tolerant execution properties from resolved configuration - if let Some(resolved_fte) = resolved_fte_config { - dynamic_resolved_config.extend( - resolved_fte - .config_properties - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))), - ); - } - - // Add spooling properties from resolved configuration - if let Some(resolved_spooling) = resolved_spooling_config { - dynamic_resolved_config.extend( - resolved_spooling - .config_properties - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))), - ); - } - - // Add static properties and overrides - dynamic_resolved_config.extend(transformed_config); - - let config_properties = product_config::writer::to_java_properties_string( - dynamic_resolved_config.iter(), - ) - .context(FailedToWriteJavaPropertiesSnafu)?; - - cm_conf_data.insert(file_name.to_string(), config_properties); - } - - PropertyNameKind::File(file_name) if file_name == NODE_PROPERTIES => { - // Add static properties and overrides - dynamic_resolved_config.extend(transformed_config); - - let node_properties = product_config::writer::to_java_properties_string( - dynamic_resolved_config.iter(), - ) - .context(FailedToWriteJavaPropertiesSnafu)?; - - cm_conf_data.insert(file_name.to_string(), node_properties); - } - PropertyNameKind::File(file_name) if file_name == LOG_PROPERTIES => { - // No overrides required here, all settings can be set via logging options - if let Some(log_properties) = get_log_properties(&merged_config.logging) { - cm_conf_data.insert(file_name.to_string(), log_properties); - } - - if let Some(vector_toml) = get_vector_toml(rolegroup_ref, &merged_config.logging) - .context(InvalidLoggingConfigSnafu { - cm_name: rolegroup_ref.object_name(), - })? - { - cm_conf_data.insert( - product_logging::framework::VECTOR_CONFIG_FILE.to_string(), - vector_toml, - ); - } - } - PropertyNameKind::File(file_name) if file_name == ACCESS_CONTROL_PROPERTIES => { - if let Some(trino_opa_config) = trino_opa_config { - dynamic_resolved_config.extend(trino_opa_config.as_config()); - } - - // Add static properties and overrides - dynamic_resolved_config.extend(transformed_config); - - if !dynamic_resolved_config.is_empty() { - let access_control_properties = - product_config::writer::to_java_properties_string( - dynamic_resolved_config.iter(), - ) - .context(FailedToWriteJavaPropertiesSnafu)?; - - cm_conf_data.insert(file_name.to_string(), access_control_properties); - } - } - PropertyNameKind::File(file_name) if file_name == JVM_CONFIG => {} - PropertyNameKind::File(file_name) if file_name == SPOOLING_MANAGER_PROPERTIES => { - // Add automatic properties for the spooling protocol - if let Some(spooling_config) = resolved_spooling_config { - dynamic_resolved_config = spooling_config - .spooling_manager_properties - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))) - .collect(); - } - - // Override automatic properties with user provided configuration for the spooling protocol - dynamic_resolved_config.extend(transformed_config); - - if !dynamic_resolved_config.is_empty() { - cm_conf_data.insert( - file_name.to_string(), - to_java_properties_string(dynamic_resolved_config.iter()) - .with_context(|_| FailedToWriteJavaPropertiesSnafu)?, - ); - } - } - PropertyNameKind::File(file_name) if file_name == EXCHANGE_MANAGER_PROPERTIES => { - // Add exchange manager properties from resolved fault tolerant execution configuration - if let Some(resolved_fte) = resolved_fte_config { - dynamic_resolved_config = resolved_fte - .exchange_manager_properties - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))) - .collect(); - } - - // Override automatic properties with user provided configuration for the spooling protocol - dynamic_resolved_config.extend(transformed_config); - - if !dynamic_resolved_config.is_empty() { - cm_conf_data.insert( - file_name.to_string(), - to_java_properties_string(dynamic_resolved_config.iter()) - .with_context(|_| FailedToWriteJavaPropertiesSnafu)?, - ); - } - } - _ => {} - } - } - - cm_conf_data.insert(JVM_CONFIG.to_string(), jvm_config.to_string()); - - let jvm_sec_props: BTreeMap> = config - .get(&PropertyNameKind::File(JVM_SECURITY_PROPERTIES.to_string())) - .cloned() - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect(); - - cm_conf_data.insert( - JVM_SECURITY_PROPERTIES.to_string(), - to_java_properties_string(jvm_sec_props.iter()).with_context(|_| { - JvmSecurityPropertiesSnafu { - rolegroup: rolegroup_ref.role_group.clone(), - } - })?, - ); - - ConfigMapBuilder::new() - .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(trino) - .name(rolegroup_ref.object_name()) - .ownerreference_from_resource(trino, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .context(MetadataBuildSnafu)? - .build(), - ) - .data(cm_conf_data) - .build() - .with_context(|_| BuildRoleGroupConfigSnafu { - rolegroup: rolegroup_ref.clone(), - }) -} - /// The rolegroup catalog [`ConfigMap`] configures the rolegroup catalog based on the configuration /// given by the administrator fn build_rolegroup_catalog_config_map( @@ -851,21 +553,15 @@ fn build_rolegroup_catalog_config_map( catalogs .iter() .map(|catalog| { - let catalog_props = catalog + let catalog_props: BTreeMap = catalog .properties .iter() - .map(|(k, v)| (k.to_string(), Some(v.to_string()))) - .collect::>(); + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); Ok(( format!("{}.properties", catalog.name), - // false positive https://github.com/rust-lang/rust-clippy/issues/9280 - // we need the tuple (&String, &Option) which the extra map is doing. - // Removing the map changes the type to &(String, Option) - #[allow(clippy::map_identity)] - product_config::writer::to_java_properties_string( - catalog_props.iter().map(|(k, v)| (k, v)), - ) - .context(FailedToWriteJavaPropertiesSnafu)?, + build::properties::writer::to_java_properties_string(&catalog_props) + .context(FailedToWriteJavaPropertiesSnafu)?, )) }) .collect::>()?, @@ -886,7 +582,7 @@ fn build_rolegroup_statefulset( trino_role: &TrinoRole, resolved_product_image: &ResolvedProductImage, role_group_ref: &RoleGroupRef, - config: &HashMap>, + env_overrides: &BTreeMap, merged_config: &v1alpha1::TrinoConfig, trino_authentication_config: &TrinoAuthenticationConfig, catalogs: &[CatalogConfig], @@ -970,17 +666,11 @@ fn build_rolegroup_statefulset( }); // Finally add the user defined envOverrides properties. - env.extend( - config - .get(&PropertyNameKind::Env) - .into_iter() - .flatten() - .map(|(k, v)| EnvVar { - name: k.clone(), - value: Some(v.clone()), - ..EnvVar::default() - }), - ); + env.extend(env_overrides.iter().map(|(k, v)| EnvVar { + name: k.clone(), + value: Some(v.clone()), + ..EnvVar::default() + })); let requested_secret_lifetime = merged_config .requested_secret_lifetime @@ -1628,23 +1318,117 @@ fn tls_volume_mounts( #[cfg(test)] mod tests { use stackable_operator::{ - commons::networking::DomainName, - kube::runtime::reflector::ObjectRef, - product_config_utils::{ - transform_all_roles_to_config, validate_all_roles_and_groups_config, - }, + cli::OperatorEnvironmentOptions, commons::networking::DomainName, + k8s_openapi::api::core::v1::ConfigMap, kube::runtime::reflector::ObjectRef, + role_utils::RoleGroupRef, utils::cluster_info::KubernetesClusterInfo, }; use super::*; use crate::{ - authentication::TrinoAuthenticationTypes, + authorization::opa::TrinoOpaConfig, config::{ client_protocol::ResolvedClientProtocolConfig, fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, - crd::v1alpha1::TrinoCluster, + controller::dereference::DereferencedObjects, + crd::{ENV_SPOOLING_SECRET, TrinoRole, v1alpha1}, }; + async fn build_config_map(trino_yaml: &str) -> ConfigMap { + let deserializer = serde_yaml::Deserializer::from_str(trino_yaml); + let mut trino: v1alpha1::TrinoCluster = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer) + .expect("invalid test input"); + trino.metadata.namespace = Some("default".to_owned()); + trino.metadata.uid = Some("42".to_owned()); + + let cluster_info = KubernetesClusterInfo { + cluster_domain: DomainName::try_from("cluster.local").unwrap(), + }; + + let namespace = trino.metadata.namespace.clone().unwrap(); + let resolved_fte_config = match &trino.spec.cluster_config.fault_tolerant_execution { + Some(fte) => Some( + ResolvedFaultTolerantExecutionConfig::from_config(fte, None, &namespace) + .await + .unwrap(), + ), + None => None, + }; + let resolved_client_protocol_config = match &trino.spec.cluster_config.client_protocol { + Some(cp) => Some( + ResolvedClientProtocolConfig::from_config(cp, None, &namespace) + .await + .unwrap(), + ), + None => None, + }; + // For OPA, the legacy helper used a hard-coded `TrinoOpaConfig` literal + // rather than resolving from cluster config; mirror that here so that + // `test_access_control_overrides` does not need a Kubernetes client and + // so that `test_config_overrides` keeps observing an + // `access-control.properties` entry in the rendered ConfigMap. + let trino_opa_config = Some(TrinoOpaConfig { + non_batched_connection_string: + "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/allow" + .to_string(), + batched_connection_string: + "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batch" + .to_string(), + row_filters_connection_string: Some( + "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/rowFilters" + .to_string(), + ), + batched_column_masking_connection_string: Some( + "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks" + .to_string(), + ), + allow_permission_management_operations: true, + tls_secret_class: None, + }); + + let derefs = DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config, + resolved_fte_config, + resolved_client_protocol_config, + }; + + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + + let validated = + validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); + + let trino_role = TrinoRole::Coordinator; + let rolegroup_ref = RoleGroupRef { + cluster: ObjectRef::from_obj(&trino), + role: trino_role.to_string(), + role_group: "default".to_string(), + }; + let recommended_labels = build_recommended_labels( + &trino, + &validated.image.app_version_label_value, + &rolegroup_ref.role, + &rolegroup_ref.role_group, + ); + + build::config_map::build_rolegroup_config_map( + &validated, + &trino_role, + &rolegroup_ref, + &cluster_info, + &recommended_labels, + &trino, + ) + .expect("build_rolegroup_config_map should succeed") + } + #[tokio::test] async fn test_config_overrides() { let trino_yaml = r#" @@ -1755,143 +1539,6 @@ mod tests { assert!(config.contains("spooling-manager.name=filesystem")); } - async fn build_config_map(trino_yaml: &str) -> ConfigMap { - let deserializer = serde_yaml::Deserializer::from_str(trino_yaml); - let mut trino: TrinoCluster = - serde_yaml::with::singleton_map_recursive::deserialize(deserializer) - .expect("invalid test input"); - trino.metadata.namespace = Some("default".to_owned()); - trino.metadata.uid = Some("42".to_owned()); - let cluster_info = KubernetesClusterInfo { - cluster_domain: DomainName::try_from("cluster.local").unwrap(), - }; - let resolved_product_image = trino - .spec - .image - .resolve(CONTAINER_IMAGE_BASE_NAME, "oci.example.org", "0.0.0-dev") - .expect("test resolved product image is always valid"); - - let config_files = vec![ - PropertyNameKind::File(CONFIG_PROPERTIES.to_string()), - PropertyNameKind::File(NODE_PROPERTIES.to_string()), - PropertyNameKind::File(JVM_CONFIG.to_string()), - PropertyNameKind::File(LOG_PROPERTIES.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES.to_string()), - PropertyNameKind::File(ACCESS_CONTROL_PROPERTIES.to_string()), - PropertyNameKind::File(SPOOLING_MANAGER_PROPERTIES.to_string()), - PropertyNameKind::File(EXCHANGE_MANAGER_PROPERTIES.to_string()), - ]; - let validated_config = validate_all_roles_and_groups_config( - // The Trino version is a single number like 396. - // The product config expects semver formatted version strings. - // That is why we just add minor and patch version 0 here. - &format!("{}.0.0", resolved_product_image.product_version), - &transform_all_roles_to_config( - &trino, - &HashMap::from([ - ( - TrinoRole::Coordinator.to_string(), - ( - config_files.clone(), - trino.role(&TrinoRole::Coordinator).unwrap(), - ), - ), - ( - TrinoRole::Worker.to_string(), - (config_files, trino.role(&TrinoRole::Worker).unwrap()), - ), - ]), - ) - .unwrap(), - // Using this instead of ProductConfigManager::from_yaml_file, as that did not find the file - &ProductConfigManager::from_str(include_str!( - "../../../deploy/config-spec/properties.yaml" - )) - .unwrap(), - false, - false, - ) - .unwrap(); - - let trino_role = TrinoRole::Coordinator; - let role = trino.role(&trino_role).unwrap(); - let rolegroup_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(&trino), - role: trino_role.to_string(), - role_group: "default".to_string(), - }; - let trino_authentication_config = TrinoAuthenticationConfig::new( - &resolved_product_image, - TrinoAuthenticationTypes::try_from(Vec::new()).unwrap(), - ) - .unwrap(); - let trino_opa_config = Some(TrinoOpaConfig { - non_batched_connection_string: - "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/allow" - .to_string(), - batched_connection_string: - "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batch" - .to_string(), - row_filters_connection_string: Some( - "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/rowFilters" - .to_string(), - ), - batched_column_masking_connection_string: Some( - "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks" - .to_string(), - ), - allow_permission_management_operations: true, - tls_secret_class: None, - }); - let resolved_fte_config = match &trino.spec.cluster_config.fault_tolerant_execution { - Some(fault_tolerant_execution) => Some( - ResolvedFaultTolerantExecutionConfig::from_config( - fault_tolerant_execution, - None, - &trino.namespace().unwrap(), - ) - .await - .unwrap(), - ), - None => None, - }; - let resolved_spooling_config = match &trino.spec.cluster_config.client_protocol { - Some(client_protocol) => Some( - ResolvedClientProtocolConfig::from_config( - client_protocol, - None, - &trino.namespace().unwrap(), - ) - .await - .unwrap(), - ), - None => None, - }; - let merged_config = trino - .merged_config(&trino_role, &rolegroup_ref, &[]) - .unwrap(); - - build_rolegroup_config_map( - &trino, - &resolved_product_image, - &role, - &trino_role, - &rolegroup_ref, - validated_config - .get("coordinator") - .unwrap() - .get("default") - .unwrap(), - &merged_config, - &trino_authentication_config, - &trino_opa_config, - &cluster_info, - &resolved_fte_config, - &resolved_spooling_config, - ) - .unwrap() - } - #[tokio::test] async fn test_access_control_overrides() { let trino_yaml = r#" @@ -1952,6 +1599,8 @@ mod tests { kind: TrinoCluster metadata: name: trino + namespace: default + uid: "42" spec: image: productVersion: "479" @@ -1978,24 +1627,26 @@ mod tests { let trino: v1alpha1::TrinoCluster = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - let validated_config = validate::validated_product_config( - &trino, - "455.0.0", - &ProductConfigManager::from_yaml_file("../../deploy/config-spec/properties.yaml") - .unwrap(), - ) - .unwrap(); - - let env = validated_config - .get(&TrinoRole::Coordinator.to_string()) - .unwrap() - .get("default") - .unwrap() - .get(&PropertyNameKind::Env) - .unwrap(); - - assert_eq!(&"group-value".to_string(), env.get("COMMON_VAR").unwrap()); - assert_eq!(&"group-value".to_string(), env.get("GROUP_VAR").unwrap()); - assert_eq!(&"role-value".to_string(), env.get("ROLE_VAR").unwrap()); + let derefs = DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + }; + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + let validated = + validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); + + let env = &validated.role_group_configs[&TrinoRole::Coordinator]["default"].env_overrides; + let value = |name: &str| env.get(name).cloned(); + assert_eq!(value("COMMON_VAR").as_deref(), Some("group-value")); + assert_eq!(value("GROUP_VAR").as_deref(), Some("group-value")); + assert_eq!(value("ROLE_VAR").as_deref(), Some("role-value")); } } diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs new file mode 100644 index 000000000..fdc927c73 --- /dev/null +++ b/rust/operator-binary/src/controller/build.rs @@ -0,0 +1,7 @@ +//! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. +//! +//! `properties` renders the Trino `.properties` files; `config_map` assembles +//! the per-rolegroup ConfigMap. + +pub mod config_map; +pub mod properties; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs new file mode 100644 index 000000000..6b63418db --- /dev/null +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -0,0 +1,225 @@ +//! Build per-rolegroup `ConfigMap` for the Trino cluster. + +use std::collections::BTreeMap; + +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, + k8s_openapi::api::core::v1::ConfigMap, + kvp::ObjectLabels, + product_logging, + role_utils::RoleGroupRef, + utils::cluster_info::KubernetesClusterInfo, +}; + +use crate::{ + config::jvm, + controller::{ + ValidatedCluster, + build::properties::{ + ConfigFileName, access_control_properties, config_properties, + exchange_manager_properties, log_properties, node_properties, security_properties, + spooling_manager_properties, writer::to_java_properties_string, + }, + }, + crd::{TrinoRole, v1alpha1}, + product_logging::get_vector_toml, +}; + +// File name not exported from crd/mod.rs. +const JVM_CONFIG: &str = "jvm.config"; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to build config.properties"))] + BuildConfigProperties { source: config_properties::Error }, + + #[snafu(display("failed to write {file} properties"))] + WriteProperties { + source: super::properties::writer::Error, + file: String, + }, + + #[snafu(display("missing rolegroup {role_group} under role {role}"))] + MissingRoleGroup { role: String, role_group: String }, + + #[snafu(display("failed to assemble ConfigMap for {rolegroup}"))] + Assemble { + source: stackable_operator::builder::configmap::Error, + rolegroup: RoleGroupRef, + }, + + #[snafu(display("metadata build failure"))] + Metadata { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to build the vector configuration for {cm_name}"))] + InvalidLoggingConfig { + source: crate::product_logging::Error, + cm_name: String, + }, + + #[snafu(display("failed to resolve the {role} role"))] + ReadRole { + source: crate::crd::Error, + role: String, + }, + + #[snafu(display("failed to build jvm.config"))] + BuildJvmConfig { source: crate::config::jvm::Error }, +} + +type Result = std::result::Result; + +#[allow(clippy::too_many_arguments)] +pub fn build_rolegroup_config_map( + cluster: &ValidatedCluster, + role: &TrinoRole, + rolegroup_ref: &RoleGroupRef, + cluster_info: &KubernetesClusterInfo, + recommended_labels: &ObjectLabels<'_, v1alpha1::TrinoCluster>, + owner_target: &v1alpha1::TrinoCluster, +) -> Result { + let role_group_configs = + cluster + .role_group_configs + .get(role) + .with_context(|| MissingRoleGroupSnafu { + role: role.to_string(), + role_group: rolegroup_ref.role_group.clone(), + })?; + let rg = role_group_configs + .get(&rolegroup_ref.role_group) + .with_context(|| MissingRoleGroupSnafu { + role: role.to_string(), + role_group: rolegroup_ref.role_group.clone(), + })?; + + let mut data: BTreeMap = BTreeMap::new(); + + // Auth files (e.g. password-authenticator file contents) — inserted FIRST + // to match the legacy ordering in controller.rs:621. + for (file_name, props) in cluster.cluster_config.authentication.config_files(role) { + let rendered = + to_java_properties_string(&props).with_context(|_| WritePropertiesSnafu { + file: file_name.clone(), + })?; + data.insert(file_name, rendered); + } + + // 1. config.properties (fallible). + let cfg = config_properties::build(cluster, role.clone(), rg, cluster_info) + .context(BuildConfigPropertiesSnafu)?; + data.insert( + ConfigFileName::Config.to_string(), + to_java_properties_string(&cfg).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::Config.to_string(), + })?, + ); + + // 2. node.properties. + let node = node_properties::build(cluster, rg); + data.insert( + ConfigFileName::Node.to_string(), + to_java_properties_string(&node).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::Node.to_string(), + })?, + ); + + // 3. log.properties (optional — empty map → omit). + let log = log_properties::build(rg); + if !log.is_empty() { + data.insert( + ConfigFileName::Log.to_string(), + to_java_properties_string(&log).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::Log.to_string(), + })?, + ); + } + + // 4. security.properties. + let sec = security_properties::build(rg); + data.insert( + ConfigFileName::Security.to_string(), + to_java_properties_string(&sec).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::Security.to_string(), + })?, + ); + + // 5. access-control.properties (optional). + let ac = access_control_properties::build(cluster, rg); + if !ac.is_empty() { + data.insert( + ConfigFileName::AccessControl.to_string(), + to_java_properties_string(&ac).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::AccessControl.to_string(), + })?, + ); + } + + // 6. exchange-manager.properties (optional). + let em = exchange_manager_properties::build(cluster, rg); + if !em.is_empty() { + data.insert( + ConfigFileName::ExchangeManager.to_string(), + to_java_properties_string(&em).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::ExchangeManager.to_string(), + })?, + ); + } + + // 7. spooling-manager.properties (optional). + let sm = spooling_manager_properties::build(cluster, rg); + if !sm.is_empty() { + data.insert( + ConfigFileName::SpoolingManager.to_string(), + to_java_properties_string(&sm).with_context(|_| WritePropertiesSnafu { + file: ConfigFileName::SpoolingManager.to_string(), + })?, + ); + } + + // 8. jvm.config. + let role_obj = owner_target.role(role).with_context(|_| ReadRoleSnafu { + role: role.to_string(), + })?; + let jvm_config = jvm::jvm_config( + cluster.product_version, + &rg.config, + &role_obj, + &rolegroup_ref.role_group, + ) + .context(BuildJvmConfigSnafu)?; + data.insert(JVM_CONFIG.to_string(), jvm_config); + + // 9. Vector sidecar toml if enabled. + let vector_toml = get_vector_toml(rolegroup_ref, &rg.config.logging).with_context(|_| { + InvalidLoggingConfigSnafu { + cm_name: rolegroup_ref.object_name(), + } + })?; + if let Some(vector_toml) = vector_toml { + data.insert( + product_logging::framework::VECTOR_CONFIG_FILE.to_string(), + vector_toml, + ); + } + + ConfigMapBuilder::new() + .metadata( + ObjectMetaBuilder::new() + .name_and_namespace(owner_target) + .name(rolegroup_ref.object_name()) + .ownerreference_from_resource(owner_target, None, Some(true)) + .context(MetadataSnafu)? + .with_recommended_labels(recommended_labels) + .context(MetadataSnafu)? + .build(), + ) + .data(data) + .build() + .with_context(|_| AssembleSnafu { + rolegroup: rolegroup_ref.clone(), + }) +} diff --git a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs new file mode 100644 index 000000000..47b475a05 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs @@ -0,0 +1,46 @@ +//! Builder for `access-control.properties`. + +use std::collections::BTreeMap; + +use crate::controller::{TrinoRoleGroupConfig, ValidatedCluster}; + +/// Build the `access-control.properties` key/value pairs. +/// +/// Returns an empty map when neither OPA authorization is configured nor user overrides are provided. +/// Callers should omit the file from the ConfigMap in that case. +pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. No defaults. + // 2. Automatic OPA config when configured. + if let Some(opa) = &cluster.cluster_config.authorization { + props.extend(super::defined_entries(opa.as_config())); + } + + // 3. No merged_config contribution. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.access_control_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }, + crd::TrinoRole, + }; + + #[test] + fn default_renders_empty_when_no_opa() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let props = build(&cluster, &rg); + assert!(props.is_empty()); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs new file mode 100644 index 000000000..a7ae856f6 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -0,0 +1,278 @@ +//! Builder for `config.properties`. The main Trino server config. + +use std::{collections::BTreeMap, ops::Div}; + +use snafu::Snafu; +use stackable_operator::{memory::BinaryMultiple, utils::cluster_info::KubernetesClusterInfo}; + +use crate::{ + controller::{TrinoRoleGroupConfig, ValidatedCluster}, + crd::{ + Container, ENV_INTERNAL_SECRET, HTTP_PORT, HTTPS_PORT, MAX_TRINO_LOG_FILES_SIZE, + STACKABLE_INTERNAL_TLS_DIR, STACKABLE_LOG_DIR, STACKABLE_SERVER_TLS_DIR, + STACKABLE_TLS_STORE_PASSWORD, TrinoRole, + discovery::{TrinoDiscovery, TrinoDiscoveryProtocol}, + }, +}; + +const NODE_SCHEDULER_INCLUDE_COORDINATOR: &str = "node-scheduler.include-coordinator"; +const HTTP_SERVER_LOG_ENABLED: &str = "http-server.log.enabled"; + +// config.properties +const COORDINATOR: &str = "coordinator"; +const DISCOVERY_URI: &str = "discovery.uri"; +const HTTP_SERVER_HTTP_PORT: &str = "http-server.http.port"; +const QUERY_MAX_MEMORY: &str = "query.max-memory"; +const QUERY_MAX_MEMORY_PER_NODE: &str = "query.max-memory-per-node"; +// - server tls +const HTTP_SERVER_HTTPS_PORT: &str = "http-server.https.port"; +const HTTP_SERVER_HTTPS_ENABLED: &str = "http-server.https.enabled"; +const HTTP_SERVER_HTTPS_KEYSTORE_KEY: &str = "http-server.https.keystore.key"; +const HTTP_SERVER_KEYSTORE_PATH: &str = "http-server.https.keystore.path"; +const HTTP_SERVER_HTTPS_TRUSTSTORE_KEY: &str = "http-server.https.truststore.key"; +const HTTP_SERVER_TRUSTSTORE_PATH: &str = "http-server.https.truststore.path"; +const HTTP_SERVER_AUTHENTICATION_ALLOW_INSECURE_OVER_HTTP: &str = + "http-server.authentication.allow-insecure-over-http"; +// - internal tls +const INTERNAL_COMMUNICATION_SHARED_SECRET: &str = "internal-communication.shared-secret"; +const INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_PATH: &str = + "internal-communication.https.keystore.path"; +const INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_KEY: &str = "internal-communication.https.keystore.key"; +const INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_PATH: &str = + "internal-communication.https.truststore.path"; +const INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_KEY: &str = + "internal-communication.https.truststore.key"; +const NODE_INTERNAL_ADDRESS_SOURCE: &str = "node.internal-address-source"; +const NODE_INTERNAL_ADDRESS_SOURCE_FQDN: &str = "FQDN"; +// Logging +const LOG_FORMAT: &str = "log.format"; +const LOG_PATH: &str = "log.path"; +const LOG_COMPRESSION: &str = "log.compression"; +const LOG_MAX_SIZE: &str = "log.max-size"; +const LOG_MAX_TOTAL_SIZE: &str = "log.max-total-size"; + +// Defaults migrated from deploy/config-spec/properties.yaml. +const DEFAULT_QUERY_MAX_MEMORY: &str = "50GB"; +const DEFAULT_NODE_SCHEDULER_INCLUDE_COORDINATOR: &str = "false"; + +// Mirrors the private constant of the same name in `crd/mod.rs`. +const LOG_FILE_COUNT: u32 = 2; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display( + "Trino requires client TLS to be enabled if any authentication method is enabled" + ))] + AuthenticationRequiresTls, +} + +/// Build the `config.properties` key/value pairs. +pub fn build( + cluster: &ValidatedCluster, + role: TrinoRole, + rg: &TrinoRoleGroupConfig, + cluster_info: &KubernetesClusterInfo, +) -> Result, Error> { + let mut props = BTreeMap::new(); + + // ---- 1. Hardcoded defaults (lowest precedence) ---- + props.insert( + QUERY_MAX_MEMORY.to_string(), + DEFAULT_QUERY_MAX_MEMORY.to_string(), + ); + if role == TrinoRole::Coordinator { + props.insert( + NODE_SCHEDULER_INCLUDE_COORDINATOR.to_string(), + DEFAULT_NODE_SCHEDULER_INCLUDE_COORDINATOR.to_string(), + ); + } + + // ---- 2. Operator-injected automatic values ---- + props.insert( + COORDINATOR.to_string(), + (role == TrinoRole::Coordinator).to_string(), + ); + + // Trino's own JSON logging output. + props.insert(LOG_FORMAT.to_string(), "json".to_string()); + props.insert( + LOG_PATH.to_string(), + format!( + "{STACKABLE_LOG_DIR}/{container}/server.airlift.json", + container = Container::Trino + ), + ); + props.insert(LOG_COMPRESSION.to_string(), "none".to_string()); + props.insert( + LOG_MAX_SIZE.to_string(), + format!( + // Trino uses the unit "MB" for MiB. + "{}MB", + MAX_TRINO_LOG_FILES_SIZE + .scale_to(BinaryMultiple::Mebi) + .div(LOG_FILE_COUNT as f32) + .ceil() + .value, + ), + ); + props.insert( + LOG_MAX_TOTAL_SIZE.to_string(), + format!( + "{}MB", + MAX_TRINO_LOG_FILES_SIZE + .scale_to(BinaryMultiple::Mebi) + .ceil() + .value, + ), + ); + props.insert(HTTP_SERVER_LOG_ENABLED.to_string(), "false".to_string()); + props.insert( + INTERNAL_COMMUNICATION_SHARED_SECRET.to_string(), + format!("${{ENV:{ENV_INTERNAL_SECRET}}}"), + ); + + // TLS gating — mirrors the existing compute_files logic, including the + // authentication-requires-TLS check. + let server_tls_enabled = cluster.cluster_config.tls.server.is_some(); + let internal_tls_enabled = cluster.cluster_config.tls.internal.is_some(); + if cluster.cluster_config.authentication_enabled && !server_tls_enabled { + return Err(Error::AuthenticationRequiresTls); + } + if server_tls_enabled || internal_tls_enabled { + props.insert(HTTP_SERVER_HTTPS_ENABLED.to_string(), "true".to_string()); + props.insert(HTTP_SERVER_HTTPS_PORT.to_string(), HTTPS_PORT.to_string()); + let tls_store_dir = if server_tls_enabled { + STACKABLE_SERVER_TLS_DIR + } else { + // allow insecure communication via the http port + props.insert( + HTTP_SERVER_AUTHENTICATION_ALLOW_INSECURE_OVER_HTTP.to_string(), + "true".to_string(), + ); + props.insert(HTTP_SERVER_HTTP_PORT.to_string(), HTTP_PORT.to_string()); + STACKABLE_INTERNAL_TLS_DIR + }; + props.insert( + HTTP_SERVER_KEYSTORE_PATH.to_string(), + format!("{tls_store_dir}/keystore.p12"), + ); + props.insert( + HTTP_SERVER_HTTPS_KEYSTORE_KEY.to_string(), + STACKABLE_TLS_STORE_PASSWORD.to_string(), + ); + props.insert( + HTTP_SERVER_TRUSTSTORE_PATH.to_string(), + format!("{tls_store_dir}/truststore.p12"), + ); + props.insert( + HTTP_SERVER_HTTPS_TRUSTSTORE_KEY.to_string(), + STACKABLE_TLS_STORE_PASSWORD.to_string(), + ); + } + if internal_tls_enabled { + props.insert( + INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_PATH.to_string(), + format!("{STACKABLE_INTERNAL_TLS_DIR}/keystore.p12"), + ); + props.insert( + INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_KEY.to_string(), + STACKABLE_TLS_STORE_PASSWORD.to_string(), + ); + props.insert( + INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_PATH.to_string(), + format!("{STACKABLE_INTERNAL_TLS_DIR}/truststore.p12"), + ); + props.insert( + INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_KEY.to_string(), + STACKABLE_TLS_STORE_PASSWORD.to_string(), + ); + props.insert( + NODE_INTERNAL_ADDRESS_SOURCE.to_string(), + NODE_INTERNAL_ADDRESS_SOURCE_FQDN.to_string(), + ); + } + + // Authentication properties (only contributes when authentication is enabled). + for (k, v) in cluster + .cluster_config + .authentication + .config_properties(&role) + { + props.insert(k, v); + } + + // Discovery URI. + if let Some(coordinator_ref) = cluster.cluster_config.coordinator_pod_refs.first() { + let protocol = if internal_tls_enabled { + TrinoDiscoveryProtocol::Https + } else { + TrinoDiscoveryProtocol::Http + }; + let discovery = TrinoDiscovery::new(coordinator_ref, protocol); + props.insert( + DISCOVERY_URI.to_string(), + discovery.discovery_uri(cluster_info), + ); + } + + // Graceful shutdown. + for (k, v) in crate::operations::graceful_shutdown_config_properties(cluster, role) { + props.insert(k, v); + } + + // Fault-tolerant execution. + if let Some(fte) = &cluster.cluster_config.fault_tolerant_execution { + props.extend(fte.config_properties.clone()); + } + // Client spooling protocol. + if let Some(spooling) = &cluster.cluster_config.client_protocol { + props.extend(spooling.config_properties.clone()); + } + + // ---- 3. merged_config CRD-spec values ---- + if let Some(qmm) = &rg.config.query_max_memory { + props.insert(QUERY_MAX_MEMORY.to_string(), qmm.clone()); + } + if let Some(qmmpn) = &rg.config.query_max_memory_per_node { + props.insert(QUERY_MAX_MEMORY_PER_NODE.to_string(), qmmpn.clone()); + } + + // ---- 4. User overrides (highest precedence) ---- + props.extend(super::resolved_overrides( + rg.config_overrides.config_properties.clone(), + )); + + Ok(props) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }; + + #[test] + fn default_renders_includes_coordinator_default_and_query_max_memory_default() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let cluster_info = stackable_operator::utils::cluster_info::KubernetesClusterInfo { + cluster_domain: stackable_operator::commons::networking::DomainName::try_from( + "cluster.local", + ) + .unwrap(), + }; + let props = build(&cluster, TrinoRole::Coordinator, &rg, &cluster_info).unwrap(); + assert_eq!(props.get("coordinator").map(String::as_str), Some("true")); + assert_eq!( + props + .get("node-scheduler.include-coordinator") + .map(String::as_str), + Some("false"), + ); + assert_eq!( + props.get("query.max-memory").map(String::as_str), + Some("50GB") + ); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs new file mode 100644 index 000000000..018240fe2 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs @@ -0,0 +1,46 @@ +//! Builder for `exchange-manager.properties`. + +use std::collections::BTreeMap; + +use crate::controller::{TrinoRoleGroupConfig, ValidatedCluster}; + +/// Build the `exchange-manager.properties` key/value pairs. +/// +/// Returns an empty map when fault-tolerant execution is not configured and no user overrides are provided. +/// Callers should omit the file from the ConfigMap in that case. +pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. No defaults. + // 2. Automatic from resolved fault-tolerant-execution config. + if let Some(fte) = &cluster.cluster_config.fault_tolerant_execution { + props.extend(fte.exchange_manager_properties.clone()); + } + + // 3. No merged_config contribution. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.exchange_manager_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }, + crd::TrinoRole, + }; + + #[test] + fn default_renders_empty_when_no_fte() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let props = build(&cluster, &rg); + assert!(props.is_empty()); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/log_properties.rs b/rust/operator-binary/src/controller/build/properties/log_properties.rs new file mode 100644 index 000000000..5da06f423 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/log_properties.rs @@ -0,0 +1,47 @@ +//! Builder for `log.properties`. + +use std::collections::BTreeMap; + +use crate::controller::TrinoRoleGroupConfig; + +/// Build the `log.properties` key/value pairs for `(role, rg)`. +/// +/// Returns `None`-equivalent (empty map) when there is nothing to write — +/// callers should omit the file from the ConfigMap if the result is empty. +pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. No defaults + // 2. Automatic per-container logger levels + if let Some(per_container) = crate::product_logging::get_log_property_map(&rg.config.logging) { + props.extend(per_container); + } + + // 3. No merged_config contribution. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.log_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }, + crd::TrinoRole, + }; + + #[test] + fn default_renders_root_logger_only() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let props = build(&rg); + assert_eq!(props.get("").map(String::as_str), Some("info")); + assert!(!props.contains_key("io.trino")); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs new file mode 100644 index 000000000..f0025a78e --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -0,0 +1,105 @@ +//! Per-file builders for Trino `.properties` files. +//! +//! Each `.rs` module produces the rendered key/value pairs for one +//! Trino config file. The shared [`writer`] module serializes the map to the +//! Java-properties on-wire format. + +use std::collections::BTreeMap; + +use stackable_operator::v2::config_overrides::KeyValueConfigOverrides; + +pub mod access_control_properties; +pub mod config_properties; +pub mod exchange_manager_properties; +pub mod log_properties; +pub mod node_properties; +pub mod security_properties; +pub mod spooling_manager_properties; +pub mod writer; + +/// The names of the Trino `.properties` files assembled into the rolegroup ConfigMap. +#[derive(Clone, Copy, Debug, strum::Display)] +pub enum ConfigFileName { + #[strum(serialize = "config.properties")] + Config, + #[strum(serialize = "node.properties")] + Node, + #[strum(serialize = "log.properties")] + Log, + #[strum(serialize = "security.properties")] + Security, + #[strum(serialize = "access-control.properties")] + AccessControl, + #[strum(serialize = "exchange-manager.properties")] + ExchangeManager, + #[strum(serialize = "spooling-manager.properties")] + SpoolingManager, +} + +/// Keep only the set (`Some`) entries of a `key -> optional value` map, as `(key, value)` pairs. +fn defined_entries( + entries: BTreeMap>, +) -> impl Iterator { + entries + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) +} + +/// Resolve user-provided [`KeyValueConfigOverrides`] into the key/value pairs to merge +/// into a `.properties` file, dropping entries whose value is unset (`None`). +fn resolved_overrides( + overrides: KeyValueConfigOverrides, +) -> impl Iterator { + defined_entries(overrides.overrides) +} + +#[cfg(test)] +pub(crate) mod test_support { + use stackable_operator::cli::OperatorEnvironmentOptions; + + use crate::{ + controller::{ValidatedCluster, dereference::DereferencedObjects}, + crd::v1alpha1, + }; + + pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster { + let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); + let derefs = DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + }; + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + crate::controller::validate::validate(&trino, &derefs, &operator_env) + .expect("validate should succeed for the minimal fixture") + } + + pub const MINIMAL_TRINO_YAML: &str = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "42" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; +} diff --git a/rust/operator-binary/src/controller/build/properties/node_properties.rs b/rust/operator-binary/src/controller/build/properties/node_properties.rs new file mode 100644 index 000000000..de66bb640 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/node_properties.rs @@ -0,0 +1,48 @@ +//! Builder for `node.properties`. + +use std::collections::BTreeMap; + +use crate::controller::{TrinoRoleGroupConfig, ValidatedCluster}; + +const NODE_ENVIRONMENT: &str = "node.environment"; + +/// Build the `node.properties` key/value pairs. +/// +/// `node.environment` is derived from the cluster name: lowercased, with `-` +/// replaced by `_`. Trino requires `^[a-z][a-z0-9_]*[a-z0-9]$`; cluster names +/// constrained by Kubernetes naming already satisfy this after the transform. +pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. No defaults. + // 2. Automatic derived from cluster name. + let node_env = cluster.name.as_ref().to_ascii_lowercase().replace('-', "_"); + props.insert(NODE_ENVIRONMENT.to_string(), node_env); + + // 3. No merged_config contribution for node.properties. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.node_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }; + + #[test] + fn default_renders_node_environment_from_cluster_name() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&crate::crd::TrinoRole::Coordinator]["default"].clone(); + let props = build(&cluster, &rg); + assert_eq!( + props.get("node.environment").map(String::as_str), + Some("simple_trino"), + ); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/security_properties.rs b/rust/operator-binary/src/controller/build/properties/security_properties.rs new file mode 100644 index 000000000..95437b04f --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/security_properties.rs @@ -0,0 +1,65 @@ +//! Builder for `security.properties` (Trino's JVM security properties file). + +use std::collections::BTreeMap; + +use crate::controller::TrinoRoleGroupConfig; + +const NETWORKADDRESS_CACHE_TTL: &str = "networkaddress.cache.ttl"; +const NETWORKADDRESS_CACHE_NEGATIVE_TTL: &str = "networkaddress.cache.negative.ttl"; + +const DEFAULT_NETWORKADDRESS_CACHE_TTL: &str = "30"; +const DEFAULT_NETWORKADDRESS_CACHE_NEGATIVE_TTL: &str = "0"; + +/// Build the `security.properties` key/value pairs. +/// +/// Both keys apply to both `coordinator` and `worker` roles. +pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. Defaults + props.insert( + NETWORKADDRESS_CACHE_TTL.to_string(), + DEFAULT_NETWORKADDRESS_CACHE_TTL.to_string(), + ); + props.insert( + NETWORKADDRESS_CACHE_NEGATIVE_TTL.to_string(), + DEFAULT_NETWORKADDRESS_CACHE_NEGATIVE_TTL.to_string(), + ); + + // 2. No automatic operator-injected values. + // 3. No merged_config contribution. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.security_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }, + crd::TrinoRole, + }; + + #[test] + fn default_renders_networkaddress_cache_settings() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let props = build(&rg); + assert_eq!( + props.get("networkaddress.cache.ttl").map(String::as_str), + Some("30") + ); + assert_eq!( + props + .get("networkaddress.cache.negative.ttl") + .map(String::as_str), + Some("0") + ); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs new file mode 100644 index 000000000..657368cde --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs @@ -0,0 +1,46 @@ +//! Builder for `spooling-manager.properties`. + +use std::collections::BTreeMap; + +use crate::controller::{TrinoRoleGroupConfig, ValidatedCluster}; + +/// Build the `spooling-manager.properties` key/value pairs. +/// +/// Returns an empty map when client spooling is not configured and no user overrides are provided. +/// Callers should omit the file from the ConfigMap in that case. +pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap { + let mut props = BTreeMap::new(); + + // 1. No defaults. + // 2. Automatic from resolved client-spooling protocol config. + if let Some(spooling) = &cluster.cluster_config.client_protocol { + props.extend(spooling.spooling_manager_properties.clone()); + } + + // 3. No merged_config contribution. + // 4. User overrides (highest precedence). + props.extend(super::resolved_overrides( + rg.config_overrides.spooling_manager_properties.clone(), + )); + + props +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + }, + crd::TrinoRole, + }; + + #[test] + fn default_renders_empty_when_no_spooling() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let props = build(&cluster, &rg); + assert!(props.is_empty()); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/writer.rs b/rust/operator-binary/src/controller/build/properties/writer.rs new file mode 100644 index 000000000..ce3d75218 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/writer.rs @@ -0,0 +1,143 @@ +//! Java-properties writer. +//! +//! Reproduces the escape rules required for Trino's `.properties` files. Pinned +//! by the kuttl ConfigMap snapshot at +//! `tests/templates/kuttl/smoke/14-assert.yaml.j2`. +// TODO(@maltesander): should be moved to a common crate in operator-rs. + +use std::collections::BTreeMap; + +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to write properties output"))] + Format { source: std::fmt::Error }, +} + +/// Serialize `props` as a Java-properties string, sorted by key. +/// +/// Keys and values are escaped per : +/// `:`, `=`, `#`, `!`, `\\`, leading whitespace, and ` ` (space). +pub fn to_java_properties_string(props: &BTreeMap) -> Result { + use std::fmt::Write; + let mut out = String::new(); + for (k, v) in props { + writeln!(out, "{}={}", escape_key(k), escape_value(v)).context(FormatSnafu)?; + } + Ok(out) +} + +fn escape_key(key: &str) -> String { + let mut out = String::with_capacity(key.len()); + for c in key.chars() { + match c { + '\\' | ':' | '=' | '#' | '!' | ' ' => { + out.push('\\'); + out.push(c); + } + _ => out.push(c), + } + } + out +} + +fn escape_value(value: &str) -> String { + let mut out = String::with_capacity(value.len()); + let mut at_start = true; + for c in value.chars() { + match c { + '\\' | ':' | '=' | '#' | '!' => { + out.push('\\'); + out.push(c); + } + ' ' if at_start => { + out.push('\\'); + out.push(' '); + } + _ => out.push(c), + } + if c != ' ' { + at_start = false; + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn render(pairs: &[(&str, &str)]) -> String { + let props: BTreeMap = pairs + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect(); + to_java_properties_string(&props).unwrap() + } + + #[test] + fn empty_map_renders_empty_string() { + let props: BTreeMap = BTreeMap::new(); + assert_eq!(to_java_properties_string(&props).unwrap(), ""); + } + + #[test] + fn keys_are_sorted_alphabetically() { + assert_eq!(render(&[("b", "2"), ("a", "1")]), "a=1\nb=2\n"); + } + + #[test] + fn colon_in_value_is_escaped() { + // From smoke snapshot: + // internal-communication.shared-secret=${ENV\:INTERNAL_SECRET} + assert_eq!( + render(&[( + "internal-communication.shared-secret", + "${ENV:INTERNAL_SECRET}" + )]), + "internal-communication.shared-secret=${ENV\\:INTERNAL_SECRET}\n" + ); + } + + #[test] + fn colon_in_url_value_is_escaped() { + // From smoke snapshot: + // discovery.uri=https\://trino-coordinator-default-0...:8443 + assert_eq!( + render(&[("discovery.uri", "https://trino-coordinator.svc:8443")]), + "discovery.uri=https\\://trino-coordinator.svc\\:8443\n" + ); + } + + #[test] + fn equals_in_value_is_escaped() { + assert_eq!(render(&[("k", "a=b")]), "k=a\\=b\n"); + } + + #[test] + fn backslash_in_value_is_escaped() { + assert_eq!(render(&[("k", "a\\b")]), "k=a\\\\b\n"); + } + + #[test] + fn leading_space_in_value_is_escaped() { + assert_eq!(render(&[("k", " v")]), "k=\\ v\n"); + } + + #[test] + fn non_leading_space_in_value_is_not_escaped() { + assert_eq!(render(&[("k", "a b")]), "k=a b\n"); + } + + #[test] + fn space_in_key_is_escaped() { + assert_eq!(render(&[("a b", "1")]), "a\\ b=1\n"); + } + + #[test] + fn hash_and_bang_in_value_are_escaped() { + assert_eq!(render(&[("k", "#comment")]), "k=\\#comment\n"); + assert_eq!(render(&[("k", "!bang")]), "k=\\!bang\n"); + } +} diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 8d16b7874..7e71df48a 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,30 +1,31 @@ -//! The validate step in the TrinoCluster controller +//! The validate step in the TrinoCluster controller. //! -//! Synchronously validates inputs that don't require a Kubernetes client. Produces -//! [`ValidatedInputs`], consumed by the rest of `reconcile_trino`. +//! Synchronously validates inputs that don't require a Kubernetes client and +//! produces the typed [`ValidatedCluster`], consumed by `controller::build::*`. -use std::collections::HashMap; +use std::{collections::BTreeMap, str::FromStr}; -use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - product_config_utils::{ - ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config, - validate_all_roles_and_groups_config, - }, + kube::ResourceExt as _, + role_utils::{GenericRoleConfig, JavaCommonConfig}, + v2::types::operator::ClusterName, }; -use strum::{EnumDiscriminants, IntoStaticStr}; +use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, - controller::dereference::DereferencedObjects, - crd::{ - ACCESS_CONTROL_PROPERTIES, CONFIG_PROPERTIES, EXCHANGE_MANAGER_PROPERTIES, JVM_CONFIG, - JVM_SECURITY_PROPERTIES, LOG_PROPERTIES, NODE_PROPERTIES, SPOOLING_MANAGER_PROPERTIES, - TrinoRole, v1alpha1, + authorization::opa::TrinoOpaConfig, + catalog::config::CatalogConfig, + config::{ + client_protocol::ResolvedClientProtocolConfig, + fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, + controller::dereference::DereferencedObjects, + crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, + framework::role_utils::with_validated_config, }; #[derive(Snafu, Debug, EnumDiscriminants)] @@ -36,6 +37,17 @@ pub enum Error { source: product_image_selection::Error, }, + #[snafu(display("invalid cluster name"))] + InvalidClusterName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + + #[snafu(display("unable to parse Trino version {product_version:?}"))] + ParseTrinoVersion { + source: std::num::ParseIntError, + product_version: String, + }, + #[snafu(display("unsupported Trino authentication"))] UnsupportedAuthenticationConfig { source: authentication::Error }, @@ -53,34 +65,60 @@ pub enum Error { role: String, }, - #[snafu(display("failed to transform configs"))] - ProductConfigTransform { - source: stackable_operator::product_config_utils::Error, - }, + #[snafu(display("failed to enumerate coordinator pods"))] + CoordinatorPods { source: crate::crd::Error }, - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, + #[snafu(display("failed to validate config fragment"))] + InvalidConfigFragment { + source: stackable_operator::config::fragment::ValidationError, }, } type Result = std::result::Result; -/// Synchronous inputs the rest of `reconcile_trino` needs after dereferencing. -pub struct ValidatedInputs { +pub type RoleGroupName = String; + +pub type TrinoRoleGroupConfig = crate::framework::role_utils::RoleGroupConfig< + v1alpha1::TrinoConfig, + JavaCommonConfig, + v1alpha1::TrinoConfigOverrides, +>; + +#[derive(Clone, Debug)] +pub struct ValidatedTls { + pub server: Option, + pub internal: Option, +} + +/// Cluster-wide settings, grouped to parallel `spec.clusterConfig` CRD. +#[derive(Clone, Debug)] +pub struct ValidatedClusterConfig { + pub tls: ValidatedTls, + pub authentication: TrinoAuthenticationConfig, + pub authentication_enabled: bool, + pub authorization: Option, + pub fault_tolerant_execution: Option, + pub client_protocol: Option, + pub coordinator_pod_refs: Vec, + pub catalogs: Vec, +} + +/// The validated TrinoCluster. The output of the validate step. +pub struct ValidatedCluster { + pub name: ClusterName, pub image: ResolvedProductImage, - pub trino_authentication_config: TrinoAuthenticationConfig, - pub validated_role_config: ValidatedRoleConfigByPropertyKind, + pub product_version: u16, + pub cluster_config: ValidatedClusterConfig, + pub role_group_configs: BTreeMap>, } -/// Validates the cluster spec and the dereferenced inputs. +/// Validates the cluster spec and dereferenced inputs. pub fn validate( trino: &v1alpha1::TrinoCluster, dereferenced_objects: &DereferencedObjects, operator_environment: &OperatorEnvironmentOptions, - product_config: &ProductConfigManager, -) -> Result { - let resolved_product_image = trino +) -> Result { + let image = trino .spec .image .resolve( @@ -90,8 +128,13 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let trino_authentication_config = TrinoAuthenticationConfig::new( - &resolved_product_image, + let product_version = + u16::from_str(&image.product_version).context(ParseTrinoVersionSnafu { + product_version: image.product_version.clone(), + })?; + + let authentication = TrinoAuthenticationConfig::new( + &image, TrinoAuthenticationTypes::try_from( dereferenced_objects.resolved_authentication_classes.clone(), ) @@ -102,77 +145,129 @@ pub fn validate( if dereferenced_objects .resolved_client_protocol_config .is_some() - && resolved_product_image.product_version.starts_with("45") + && image.product_version.starts_with("45") { return Err(Error::ClientSpoolingProtocolTrinoVersion { - product_version: resolved_product_image.product_version.clone(), + product_version: image.product_version.clone(), }); } - let validated_role_config = validated_product_config( - trino, - // The Trino version is a single number like 396. - // The product config expects semver formatted version strings. - // That is why we just add minor and patch version 0 here. - &format!("{}.0.0", resolved_product_image.product_version), - product_config, - )?; - - Ok(ValidatedInputs { - image: resolved_product_image, - trino_authentication_config, - validated_role_config, + let mut role_group_configs: BTreeMap> = + BTreeMap::new(); + for trino_role in TrinoRole::iter() { + let role = trino + .role(&trino_role) + .with_context(|_| MissingTrinoRoleSnafu { + role: trino_role.to_string(), + })?; + let default_config = v1alpha1::TrinoConfig::default_config( + &trino.name_any(), + &trino_role, + &dereferenced_objects.catalog_definitions, + ); + let mut groups = BTreeMap::new(); + for (rg_name, rg) in &role.role_groups { + let validated_rg = with_validated_config::< + v1alpha1::TrinoConfig, + JavaCommonConfig, + v1alpha1::TrinoConfigFragment, + GenericRoleConfig, + v1alpha1::TrinoConfigOverrides, + >(rg, &role, &default_config) + .context(InvalidConfigFragmentSnafu)?; + groups.insert(rg_name.clone(), validated_rg); + } + role_group_configs.insert(trino_role, groups); + } + + let cluster_config = ValidatedClusterConfig { + tls: ValidatedTls { + server: trino.get_server_tls().map(String::from), + internal: trino.get_internal_tls().map(String::from), + }, + authentication, + authentication_enabled: trino.authentication_enabled(), + authorization: dereferenced_objects.trino_opa_config.clone(), + fault_tolerant_execution: dereferenced_objects.resolved_fte_config.clone(), + client_protocol: dereferenced_objects.resolved_client_protocol_config.clone(), + coordinator_pod_refs: trino + .coordinator_pods() + .context(CoordinatorPodsSnafu)? + .collect(), + catalogs: dereferenced_objects.catalogs.clone(), + }; + + Ok(ValidatedCluster { + name: ClusterName::from_str(&trino.name_any()).context(InvalidClusterNameSnafu)?, + image, + product_version, + cluster_config, + role_group_configs, }) } -pub(super) fn validated_product_config( - trino: &v1alpha1::TrinoCluster, - version: &str, - product_config: &ProductConfigManager, -) -> Result { - let mut roles = HashMap::new(); - - let config_files = vec![ - PropertyNameKind::Env, - PropertyNameKind::File(CONFIG_PROPERTIES.to_string()), - PropertyNameKind::File(NODE_PROPERTIES.to_string()), - PropertyNameKind::File(JVM_CONFIG.to_string()), - PropertyNameKind::File(LOG_PROPERTIES.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES.to_string()), - PropertyNameKind::File(ACCESS_CONTROL_PROPERTIES.to_string()), - PropertyNameKind::File(SPOOLING_MANAGER_PROPERTIES.to_string()), - PropertyNameKind::File(EXCHANGE_MANAGER_PROPERTIES.to_string()), - ]; - - let coordinator_role = TrinoRole::Coordinator; - roles.insert( - coordinator_role.to_string(), - ( - config_files.clone(), - trino - .role(&coordinator_role) - .with_context(|_| MissingTrinoRoleSnafu { - role: coordinator_role.to_string(), - })?, - ), - ); - - let worker_role = TrinoRole::Worker; - roles.insert( - worker_role.to_string(), - ( - config_files, - trino - .role(&worker_role) - .with_context(|_| MissingTrinoRoleSnafu { - role: worker_role.to_string(), - })?, - ), - ); - - let role_config = - transform_all_roles_to_config(trino, &roles).context(ProductConfigTransformSnafu)?; - - validate_all_roles_and_groups_config(version, &role_config, product_config, false, false) - .context(InvalidProductConfigSnafu) +#[cfg(test)] +mod tests { + use super::*; + + const MINIMAL_TRINO_YAML: &str = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "42" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + #[test] + fn validate_minimal_cluster() { + let trino: v1alpha1::TrinoCluster = + serde_yaml::from_str(MINIMAL_TRINO_YAML).expect("invalid test input"); + let derefs = DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + }; + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + + let validated = validate(&trino, &derefs, &operator_env).expect("validate should succeed"); + + assert_eq!(validated.name.to_string(), "simple-trino"); + assert_eq!(validated.product_version, 479); + assert!(!validated.cluster_config.authentication_enabled); + assert!( + validated + .role_group_configs + .contains_key(&TrinoRole::Coordinator) + ); + assert!( + validated + .role_group_configs + .contains_key(&TrinoRole::Worker) + ); + assert_eq!( + validated.role_group_configs[&TrinoRole::Coordinator]["default"].replicas, + 1 + ); + } } diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index ae133dfcc..221bd9349 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -5,7 +5,7 @@ pub mod client_protocol; pub mod discovery; pub mod fault_tolerant_execution; -use std::{collections::BTreeMap, ops::Div, str::FromStr}; +use std::{collections::BTreeMap, str::FromStr}; use affinity::get_affinity; use serde::{Deserialize, Serialize}; @@ -25,13 +25,11 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, - config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, crd::authentication::core, deep_merger::ObjectOverrides, k8s_openapi::apimachinery::pkg::{api::resource::Quantity, apis::meta::v1::LabelSelector}, kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef}, memory::{BinaryMultiple, MemoryQuantity}, - product_config_utils::{Configuration, Error as ConfigError}, product_logging::{self, spec::Logging}, role_utils::{ CommonConfiguration, GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef, @@ -40,6 +38,7 @@ use stackable_operator::{ shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, utils::cluster_info::KubernetesClusterInfo, + v2::config_overrides::KeyValueConfigOverrides, versioned::versioned, }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; @@ -63,7 +62,6 @@ pub type TrinoRoleType = Role< pub type TrinoRoleGroupType = RoleGroup; -pub const FIELD_MANAGER: &str = "trino-operator"; pub const APP_NAME: &str = "trino"; // ports pub const HTTP_PORT: u16 = 8080; @@ -73,44 +71,6 @@ pub const METRICS_PORT: u16 = 8081; pub const HTTP_PORT_NAME: &str = "http"; pub const HTTPS_PORT_NAME: &str = "https"; pub const METRICS_PORT_NAME: &str = "metrics"; -// file names -pub const CONFIG_PROPERTIES: &str = "config.properties"; -pub const JVM_CONFIG: &str = "jvm.config"; -pub const NODE_PROPERTIES: &str = "node.properties"; -pub const LOG_PROPERTIES: &str = "log.properties"; -pub const ACCESS_CONTROL_PROPERTIES: &str = "access-control.properties"; -pub const JVM_SECURITY_PROPERTIES: &str = "security.properties"; -pub const EXCHANGE_MANAGER_PROPERTIES: &str = "exchange-manager.properties"; -pub const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; -// node.properties -pub const NODE_ENVIRONMENT: &str = "node.environment"; -// config.properties -pub const COORDINATOR: &str = "coordinator"; -pub const DISCOVERY_URI: &str = "discovery.uri"; -pub const HTTP_SERVER_HTTP_PORT: &str = "http-server.http.port"; -pub const QUERY_MAX_MEMORY: &str = "query.max-memory"; -pub const QUERY_MAX_MEMORY_PER_NODE: &str = "query.max-memory-per-node"; -// - server tls -pub const HTTP_SERVER_HTTPS_PORT: &str = "http-server.https.port"; -pub const HTTP_SERVER_HTTPS_ENABLED: &str = "http-server.https.enabled"; -pub const HTTP_SERVER_HTTPS_KEYSTORE_KEY: &str = "http-server.https.keystore.key"; -pub const HTTP_SERVER_KEYSTORE_PATH: &str = "http-server.https.keystore.path"; -pub const HTTP_SERVER_HTTPS_TRUSTSTORE_KEY: &str = "http-server.https.truststore.key"; -pub const HTTP_SERVER_TRUSTSTORE_PATH: &str = "http-server.https.truststore.path"; -pub const HTTP_SERVER_AUTHENTICATION_ALLOW_INSECURE_OVER_HTTP: &str = - "http-server.authentication.allow-insecure-over-http"; -// - internal tls -pub const INTERNAL_COMMUNICATION_SHARED_SECRET: &str = "internal-communication.shared-secret"; -pub const INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_PATH: &str = - "internal-communication.https.keystore.path"; -pub const INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_KEY: &str = - "internal-communication.https.keystore.key"; -pub const INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_PATH: &str = - "internal-communication.https.truststore.path"; -pub const INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_KEY: &str = - "internal-communication.https.truststore.key"; -pub const NODE_INTERNAL_ADDRESS_SOURCE: &str = "node.internal-address-source"; -pub const NODE_INTERNAL_ADDRESS_SOURCE_FQDN: &str = "FQDN"; // directories pub const CONFIG_DIR_NAME: &str = "/stackable/config"; pub const RW_CONFIG_DIR_NAME: &str = "/stackable/rwconfig"; @@ -126,34 +86,17 @@ pub const STACKABLE_TLS_STORE_PASSWORD: &str = "changeit"; pub const ENV_INTERNAL_SECRET: &str = "INTERNAL_SECRET"; pub const ENV_SPOOLING_SECRET: &str = "SPOOLING_SECRET"; // TLS -pub const TLS_DEFAULT_SECRET_CLASS: &str = "tls"; +const TLS_DEFAULT_SECRET_CLASS: &str = "tls"; // Logging -pub const LOG_FORMAT: &str = "log.format"; -pub const LOG_PATH: &str = "log.path"; -pub const LOG_COMPRESSION: &str = "log.compression"; -pub const LOG_MAX_SIZE: &str = "log.max-size"; -pub const LOG_MAX_TOTAL_SIZE: &str = "log.max-total-size"; -const LOG_FILE_COUNT: u32 = 2; pub const MAX_TRINO_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { value: 10.0, unit: BinaryMultiple::Mebi, }; -pub const JVM_HEAP_FACTOR: f32 = 0.8; - -pub const DEFAULT_COORDINATOR_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = +const DEFAULT_COORDINATOR_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_minutes_unchecked(15); pub const DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_minutes_unchecked(60); -/// Corresponds to "shutdown.grace-period", which defaults to 2 min. -/// This seems a bit high, as Pod termination - even with no queries running on the worker - -/// takes at least 4 minutes (see ). -/// So we set it to 30 seconds, so the Pod termination takes at least 1 minute. -pub const WORKER_SHUTDOWN_GRACE_PERIOD: Duration = Duration::from_secs(30); - -/// Safety puffer to guarantee the graceful shutdown works every time. -pub const WORKER_GRACEFUL_SHUTDOWN_SAFETY_OVERHEAD: Duration = Duration::from_secs(10); - /// Convert a Kubernetes `Quantity` to a Trino property string in bytes, e.g. `"65536B"`. pub(crate) fn quantity_to_trino_bytes( q: &Quantity, @@ -237,60 +180,6 @@ pub mod versioned { pub workers: Option, } - #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] - #[serde(rename_all = "camelCase")] - pub struct TrinoConfigOverrides { - #[serde( - default, - rename = "config.properties", - skip_serializing_if = "Option::is_none" - )] - pub config_properties: Option, - - #[serde( - default, - rename = "node.properties", - skip_serializing_if = "Option::is_none" - )] - pub node_properties: Option, - - #[serde( - default, - rename = "log.properties", - skip_serializing_if = "Option::is_none" - )] - pub log_properties: Option, - - #[serde( - default, - rename = "security.properties", - skip_serializing_if = "Option::is_none" - )] - pub security_properties: Option, - - #[serde( - default, - rename = "access-control.properties", - skip_serializing_if = "Option::is_none" - )] - pub access_control_properties: Option, - - #[serde( - default, - rename = "exchange-manager.properties", - skip_serializing_if = "Option::is_none" - )] - pub exchange_manager_properties: Option, - - #[serde( - default, - rename = "spooling-manager.properties", - skip_serializing_if = "Option::is_none" - )] - pub spooling_manager_properties: Option, - } - - // TODO: move generic version to op-rs? #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TrinoCoordinatorRoleConfig { @@ -302,6 +191,38 @@ pub mod versioned { pub listener_class: String, } + #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct TrinoConfigOverrides { + // File name defined in [`crate::controller::build::properties::CONFIG_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "config.properties")] + pub config_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::NODE_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "node.properties")] + pub node_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::LOG_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "log.properties")] + pub log_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::SECURITY_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "security.properties")] + pub security_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::ACCESS_CONTROL_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "access-control.properties")] + pub access_control_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::EXCHANGE_MANAGER_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "exchange-manager.properties")] + pub exchange_manager_properties: KeyValueConfigOverrides, + + // File name defined in [`crate::controller::build::properties::SPOOLING_MANAGER_PROPERTIES_FILE_NAME`] + #[serde(default, rename = "spooling-manager.properties")] + pub spooling_manager_properties: KeyValueConfigOverrides, + } + #[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)] #[fragment_attrs( derive( @@ -317,26 +238,29 @@ pub mod versioned { serde(rename_all = "camelCase") )] pub struct TrinoConfig { - // config.properties - pub query_max_memory: Option, + #[fragment_attrs(serde(default))] + pub affinity: StackableAffinity, - pub query_max_memory_per_node: Option, + /// Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the operator documentation for details. + #[fragment_attrs(serde(default))] + pub graceful_shutdown_timeout: Option, #[fragment_attrs(serde(default))] pub logging: Logging, + /// This is the max amount of user memory a query can use across the entire cluster. + /// See + pub query_max_memory: Option, + + /// This is the max amount of user memory a query can use on a worker. + /// See + pub query_max_memory_per_node: Option, + // We need to provide *something* that implements `Fragment`, so we pick an empty struct here. // Note that a unit "()" would not work, as we need something that implements `Fragment`. #[fragment_attrs(serde(default))] pub resources: Resources, - #[fragment_attrs(serde(default))] - pub affinity: StackableAffinity, - - /// Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the operator documentation for details. - #[fragment_attrs(serde(default))] - pub graceful_shutdown_timeout: Option, - /// Request secret (currently only autoTls certificates) lifetime from the secret operator, e.g. `7d`, or `30d`. /// This can be shortened by the `maxCertificateLifetime` setting on the SecretClass issuing the TLS certificate. /// @@ -460,24 +384,6 @@ impl v1alpha1::TrinoAuthorizationOpaConfig { } } -impl KeyValueOverridesProvider for v1alpha1::TrinoConfigOverrides { - fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - let field = match file { - CONFIG_PROPERTIES => self.config_properties.as_ref(), - NODE_PROPERTIES => self.node_properties.as_ref(), - LOG_PROPERTIES => self.log_properties.as_ref(), - JVM_SECURITY_PROPERTIES => self.security_properties.as_ref(), - ACCESS_CONTROL_PROPERTIES => self.access_control_properties.as_ref(), - EXCHANGE_MANAGER_PROPERTIES => self.exchange_manager_properties.as_ref(), - SPOOLING_MANAGER_PROPERTIES => self.spooling_manager_properties.as_ref(), - _ => None, - }; - field - .map(KeyValueConfigOverrides::as_product_config_overrides) - .unwrap_or_default() - } -} - impl Default for v1alpha1::TrinoCoordinatorRoleConfig { fn default() -> Self { v1alpha1::TrinoCoordinatorRoleConfig { @@ -513,7 +419,9 @@ fn tls_secret_class_default() -> Option { Eq, Hash, JsonSchema, + Ord, PartialEq, + PartialOrd, Serialize, EnumString, )] @@ -595,7 +503,7 @@ pub enum Container { } impl v1alpha1::TrinoConfig { - fn default_config( + pub(crate) fn default_config( cluster_name: &str, role: &TrinoRole, trino_catalogs: &[catalog::v1alpha1::TrinoCatalog], @@ -638,207 +546,6 @@ impl v1alpha1::TrinoConfig { } } -impl Configuration for v1alpha1::TrinoConfigFragment { - type Configurable = v1alpha1::TrinoCluster; - - fn compute_env( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, ConfigError> { - Ok(BTreeMap::new()) - } - - fn compute_cli( - &self, - _resource: &Self::Configurable, - _role_name: &str, - ) -> Result>, ConfigError> { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - resource: &Self::Configurable, - role_name: &str, - file: &str, - ) -> Result>, ConfigError> { - let mut result = BTreeMap::new(); - let authentication_enabled = resource.authentication_enabled(); - let server_tls_enabled: bool = resource.get_server_tls().is_some(); - let internal_tls_enabled: bool = resource.get_internal_tls().is_some(); - - match file { - NODE_PROPERTIES => { - // The resource name is alphanumeric and may have "-" characters - // The Trino node environment is bound to alphanumeric lowercase and "_" characters - // and must start with alphanumeric (which is the case for resource names as well?) - // see https://trino.io/docs/current/installation/deployment.html - let node_env = resource.name_any().to_ascii_lowercase().replace('-', "_"); - result.insert(NODE_ENVIRONMENT.to_string(), Some(node_env)); - } - CONFIG_PROPERTIES => { - // coordinator or worker - result.insert( - COORDINATOR.to_string(), - Some((role_name == TrinoRole::Coordinator.to_string()).to_string()), - ); - // TrinoConfig properties - if let Some(query_max_memory) = &self.query_max_memory { - result.insert( - QUERY_MAX_MEMORY.to_string(), - Some(query_max_memory.to_string()), - ); - } - if let Some(query_max_memory_per_node) = &self.query_max_memory_per_node { - result.insert( - QUERY_MAX_MEMORY_PER_NODE.to_string(), - Some(query_max_memory_per_node.to_string()), - ); - } - - // The log format used by Trino - result.insert(LOG_FORMAT.to_string(), Some("json".to_string())); - // The path to the log file used by Trino - result.insert( - LOG_PATH.to_string(), - Some(format!( - "{STACKABLE_LOG_DIR}/{container}/server.airlift.json", - container = Container::Trino - )), - ); - - // We do not compress. This will result in LOG_MAX_TOTAL_SIZE / LOG_MAX_SIZE files. - result.insert(LOG_COMPRESSION.to_string(), Some("none".to_string())); - - // The size of one log file - result.insert( - LOG_MAX_SIZE.to_string(), - Some(format!( - // Trino uses the unit "MB" for MiB. - "{}MB", - MAX_TRINO_LOG_FILES_SIZE - .scale_to(BinaryMultiple::Mebi) - .div(LOG_FILE_COUNT as f32) - .ceil() - .value, - )), - ); - // The maximum size of all logfiles combined - result.insert( - LOG_MAX_TOTAL_SIZE.to_string(), - Some(format!( - // Trino uses the unit "MB" for MiB. - "{}MB", - MAX_TRINO_LOG_FILES_SIZE - .scale_to(BinaryMultiple::Mebi) - .ceil() - .value, - )), - ); - - // disable http-request logs - result.insert( - "http-server.log.enabled".to_string(), - Some("false".to_string()), - ); - - // Always use the internal secret (base64) - result.insert( - INTERNAL_COMMUNICATION_SHARED_SECRET.to_string(), - Some(format!("${{ENV:{secret}}}", secret = ENV_INTERNAL_SECRET)), - ); - - // If authentication is enabled and client tls is explicitly deactivated we error out - // Therefore from here on we can use resource.get_server_tls() as the only source - // of truth when enabling client TLS. - if authentication_enabled && !server_tls_enabled { - return Err(ConfigError::InvalidProductSpecificConfiguration { - reason: - "Trino requires client TLS to be enabled if any authentication method is enabled! TLS was set to null. \ - Please set 'spec.clusterConfig.tls.secretClass' or use the provided default value.".to_string(), - }); - } - - if server_tls_enabled || internal_tls_enabled { - // enable TLS - result.insert( - HTTP_SERVER_HTTPS_ENABLED.to_string(), - Some(true.to_string()), - ); - // via https port - result.insert( - HTTP_SERVER_HTTPS_PORT.to_string(), - Some(HTTPS_PORT.to_string()), - ); - - let tls_store_dir = if server_tls_enabled { - STACKABLE_SERVER_TLS_DIR - } else { - // allow insecure communication - result.insert( - HTTP_SERVER_AUTHENTICATION_ALLOW_INSECURE_OVER_HTTP.to_string(), - Some("true".to_string()), - ); - // via the http port - result.insert( - HTTP_SERVER_HTTP_PORT.to_string(), - Some(HTTP_PORT.to_string()), - ); - - STACKABLE_INTERNAL_TLS_DIR - }; - - result.insert( - HTTP_SERVER_KEYSTORE_PATH.to_string(), - Some(format!("{}/{}", tls_store_dir, "keystore.p12")), - ); - result.insert( - HTTP_SERVER_HTTPS_KEYSTORE_KEY.to_string(), - Some(STACKABLE_TLS_STORE_PASSWORD.to_string()), - ); - result.insert( - HTTP_SERVER_TRUSTSTORE_PATH.to_string(), - Some(format!("{}/{}", tls_store_dir, "truststore.p12")), - ); - result.insert( - HTTP_SERVER_HTTPS_TRUSTSTORE_KEY.to_string(), - Some(STACKABLE_TLS_STORE_PASSWORD.to_string()), - ); - } - - if internal_tls_enabled { - result.insert( - INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_PATH.to_string(), - Some(format!("{}/keystore.p12", STACKABLE_INTERNAL_TLS_DIR)), - ); - result.insert( - INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_KEY.to_string(), - Some(STACKABLE_TLS_STORE_PASSWORD.to_string()), - ); - result.insert( - INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_PATH.to_string(), - Some(format!("{}/truststore.p12", STACKABLE_INTERNAL_TLS_DIR)), - ); - result.insert( - INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_KEY.to_string(), - Some(STACKABLE_TLS_STORE_PASSWORD.to_string()), - ); - result.insert( - NODE_INTERNAL_ADDRESS_SOURCE.to_string(), - Some(NODE_INTERNAL_ADDRESS_SOURCE_FQDN.to_string()), - ); - } - } - LOG_PROPERTIES => {} - ACCESS_CONTROL_PROPERTIES => {} - _ => {} - } - - Ok(result) - } -} - impl v1alpha1::TrinoCluster { /// Returns the name of the cluster and raises an Error if the name is not set. pub fn name_r(&self) -> Result { diff --git a/rust/operator-binary/src/framework.rs b/rust/operator-binary/src/framework.rs new file mode 100644 index 000000000..d0a9b1bbf --- /dev/null +++ b/rust/operator-binary/src/framework.rs @@ -0,0 +1,10 @@ +//! Local framework helpers that mirror the work-in-progress upstream +//! `stackable_operator::v2::*` modules. +//! +//! The upstream `v2` module on the `smooth-operator` branch is not yet exported +//! from `lib.rs`, so we vendor the small subset of helpers we need. +//! +//! Follow-up: replace these with `stackable_operator::v2::*` imports once +//! upstream publishes the module. + +pub mod role_utils; diff --git a/rust/operator-binary/src/framework/role_utils.rs b/rust/operator-binary/src/framework/role_utils.rs new file mode 100644 index 000000000..2e5729a04 --- /dev/null +++ b/rust/operator-binary/src/framework/role_utils.rs @@ -0,0 +1,156 @@ +//! Vendored variant of `stackable_operator::v2::role_utils` from the +//! `smooth-operator` branch, with simplifications appropriate for trino-operator. +//! +//! Differences from upstream: +//! - `env_overrides` is `HashMap` instead of `EnvVarSet`. +//! - No `cli_overrides_to_vec` helper, `ResourceNames`, or service-account helpers. +//! - The `CommonConfig` (a.k.a. `product_specific_common_config`) does NOT need to +//! implement `Merge`. Upstream Trino uses `JavaCommonConfig`, which intentionally +//! does not implement `Merge` because its inner `JvmArgumentOverrides::try_merge` +//! is fallible (regex validation). Merging JVM argument overrides for Trino is +//! handled separately via `Role::get_merged_jvm_argument_overrides`. The +//! `RoleGroupConfig::product_specific_common_config` field here simply carries +//! the role-group level value through. +//! +//! Replace with `stackable_operator::v2::role_utils::*` once upstream publishes +//! the module. + +use std::collections::BTreeMap; + +use serde::Serialize; +use stackable_operator::{ + config::{ + fragment::{self, FromFragment}, + merge::{Merge, merge}, + }, + k8s_openapi::{DeepMerge, api::core::v1::PodTemplateSpec}, + role_utils::{Role, RoleGroup}, + schemars::JsonSchema, +}; + +/// Trino-friendly view of a validated, merged `RoleGroup`. +/// +/// Mirrors `stackable_operator::v2::role_utils::RoleGroupConfig` on the +/// `smooth-operator` branch, with `env_overrides: BTreeMap` +/// instead of the upstream `EnvVarSet`. +#[derive(Clone, Debug, PartialEq)] +pub struct RoleGroupConfig { + pub replicas: u16, + pub config: Config, + pub config_overrides: ConfigOverrides, + pub env_overrides: BTreeMap, + pub cli_overrides: BTreeMap, + pub pod_overrides: PodTemplateSpec, + pub product_specific_common_config: CommonConfig, +} + +/// Merges and validates the `RoleGroup` with the given `role` and `default_config`, +/// returning a `RoleGroupConfig`. +/// +/// Merge order matches `with_validated_config` on `smooth-operator`: +/// - `Config` (Fragment): `default_config <- role.config <- rg.config` via `Merge::merge`, +/// then validated to `ValidatedConfig` via `FromFragment`. +/// - `ConfigOverrides`: `role.config_overrides <- rg.config_overrides` via `Merge::merge`. +/// - `env_overrides` / `cli_overrides`: `extend` (rg keys overwrite role keys). +/// - `pod_overrides`: `DeepMerge::merge_from` (rg overrides role). +/// - `product_specific_common_config`: passes through the role-group level value +/// (see module docs for rationale). +pub fn with_validated_config( + role_group: &RoleGroup, + role: &Role, + default_config: &Config, +) -> Result< + RoleGroupConfig, + fragment::ValidationError, +> +where + ValidatedConfig: FromFragment, + CommonConfig: Clone + Default + JsonSchema + Serialize, + Config: Clone + Merge, + RoleConfig: Default + JsonSchema + Serialize, + ConfigOverrides: Clone + Default + JsonSchema + Merge + Serialize, +{ + let validated_config = validate_config(role_group, role, default_config)?; + Ok(RoleGroupConfig { + replicas: role_group.replicas.unwrap_or(1), + config: validated_config, + config_overrides: merged_config_overrides( + &role.config.config_overrides, + role_group.config.config_overrides.clone(), + ), + env_overrides: merged_env_overrides( + role.config + .env_overrides + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + role_group + .config + .env_overrides + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + ), + cli_overrides: merged_cli_overrides( + role.config.cli_overrides.clone(), + role_group.config.cli_overrides.clone(), + ), + pod_overrides: merged_pod_overrides( + role.config.pod_overrides.clone(), + role_group.config.pod_overrides.clone(), + ), + product_specific_common_config: role_group.config.product_specific_common_config.clone(), + }) +} + +fn validate_config( + role_group: &RoleGroup, + role: &Role, + default_config: &Config, +) -> Result +where + ValidatedConfig: FromFragment, + CommonConfig: Default + JsonSchema + Serialize, + Config: Clone + Merge, + RoleConfig: Default + JsonSchema + Serialize, + ConfigOverrides: Default + JsonSchema + Serialize, +{ + role_group.validate_config(role, default_config) +} + +fn merged_config_overrides( + role_config_overrides: &ConfigOverrides, + role_group_config_overrides: ConfigOverrides, +) -> ConfigOverrides +where + ConfigOverrides: Merge, +{ + merge(role_group_config_overrides, role_config_overrides) +} + +fn merged_env_overrides( + role_env_overrides: BTreeMap, + role_group_env_overrides: BTreeMap, +) -> BTreeMap { + let mut merged = role_env_overrides; + merged.extend(role_group_env_overrides); + merged +} + +fn merged_cli_overrides( + role_cli_overrides: BTreeMap, + role_group_cli_overrides: BTreeMap, +) -> BTreeMap { + let mut merged = role_cli_overrides; + merged.extend(role_group_cli_overrides); + merged +} + +fn merged_pod_overrides( + role_pod_overrides: PodTemplateSpec, + role_group_pod_overrides: PodTemplateSpec, +) -> PodTemplateSpec { + let mut merged = role_pod_overrides; + merged.merge_from(role_group_pod_overrides); + merged +} diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 223765adc..fb00b6a5b 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -49,6 +49,7 @@ mod command; mod config; mod controller; mod crd; +mod framework; mod listener; mod operations; mod product_logging; @@ -79,7 +80,7 @@ async fn main() -> anyhow::Result<()> { Command::Run(RunArguments { operator_environment, watch_namespace, - product_config, + product_config: _, maintenance, common, }) => { @@ -126,11 +127,6 @@ async fn main() -> anyhow::Result<()> { .run(sigterm_watcher.handle()) .map_err(|err| anyhow!(err).context("failed to run webhook server")); - let product_config = product_config.load(&[ - "deploy/config-spec/properties.yaml", - "/etc/stackable/trino-operator/config-spec/properties.yaml", - ])?; - let event_recorder = Arc::new(Recorder::new( client.as_kube_client(), Reporter { @@ -205,7 +201,6 @@ async fn main() -> anyhow::Result<()> { Arc::new(controller::Ctx { client: client.clone(), operator_environment, - product_config, }), ) // We can let the reporting happen in the background diff --git a/rust/operator-binary/src/operations/graceful_shutdown.rs b/rust/operator-binary/src/operations/graceful_shutdown.rs index 50bad7ae0..2de7f1325 100644 --- a/rust/operator-binary/src/operations/graceful_shutdown.rs +++ b/rust/operator-binary/src/operations/graceful_shutdown.rs @@ -7,12 +7,23 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::pod::{PodBuilder, container::ContainerBuilder}, k8s_openapi::api::core::v1::{ExecAction, LifecycleHandler}, + shared::time::Duration, }; -use crate::crd::{ - TrinoRole, WORKER_GRACEFUL_SHUTDOWN_SAFETY_OVERHEAD, WORKER_SHUTDOWN_GRACE_PERIOD, v1alpha1, +use crate::{ + controller::ValidatedCluster, + crd::{DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT, TrinoRole, v1alpha1}, }; +/// Corresponds to "shutdown.grace-period", which defaults to 2 min. +/// This seems a bit high, as Pod termination - even with no queries running on the worker - +/// takes at least 4 minutes (see ). +/// So we set it to 30 seconds, so the Pod termination takes at least 1 minute. +const WORKER_SHUTDOWN_GRACE_PERIOD: Duration = Duration::from_secs(30); + +/// Safety puffer to guarantee the graceful shutdown works every time. +const WORKER_GRACEFUL_SHUTDOWN_SAFETY_OVERHEAD: Duration = Duration::from_secs(10); + #[derive(Debug, Snafu)] pub enum Error { #[snafu(display("failed to set terminationGracePeriod"))] @@ -21,25 +32,24 @@ pub enum Error { }, } +/// Computes the graceful-shutdown-related properties for the role's +/// `config.properties` file from a [`ValidatedCluster`]. pub fn graceful_shutdown_config_properties( - trino: &v1alpha1::TrinoCluster, - role: &TrinoRole, -) -> BTreeMap> { + cluster: &ValidatedCluster, + role: TrinoRole, +) -> BTreeMap { match role { TrinoRole::Coordinator => { // Only set query.max-execution-time if fault tolerant execution is not configured. // With fault tolerant execution enabled, queries can be retried and run indefinitely. - if trino.spec.cluster_config.fault_tolerant_execution.is_none() { + if cluster.cluster_config.fault_tolerant_execution.is_none() { let min_worker_graceful_shutdown_timeout = - trino.min_worker_graceful_shutdown_timeout(); + min_worker_graceful_shutdown_timeout(cluster); // We know that queries taking longer than the minimum gracefulShutdownTimeout are subject to failure. // Read operator docs for reasoning. BTreeMap::from([( "query.max-execution-time".to_string(), - Some(format!( - "{}s", - min_worker_graceful_shutdown_timeout.as_secs() - )), + format!("{}s", min_worker_graceful_shutdown_timeout.as_secs()), )]) } else { BTreeMap::new() @@ -47,11 +57,28 @@ pub fn graceful_shutdown_config_properties( } TrinoRole::Worker => BTreeMap::from([( "shutdown.grace-period".to_string(), - Some(format!("{}s", WORKER_SHUTDOWN_GRACE_PERIOD.as_secs())), + format!("{}s", WORKER_SHUTDOWN_GRACE_PERIOD.as_secs()), )]), } } +/// Returns the minimal `gracefulShutdownTimeout` across all worker role-groups. +/// +/// Mirrors [`v1alpha1::TrinoCluster::min_worker_graceful_shutdown_timeout`] but +/// reads from [`ValidatedCluster::role_group_configs`]. +fn min_worker_graceful_shutdown_timeout( + cluster: &ValidatedCluster, +) -> stackable_operator::shared::time::Duration { + cluster + .role_group_configs + .get(&TrinoRole::Worker) + .into_iter() + .flat_map(|groups| groups.values()) + .filter_map(|rg| rg.config.graceful_shutdown_timeout) + .min() + .unwrap_or(DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT) +} + pub fn add_graceful_shutdown_config( trino: &v1alpha1::TrinoCluster, role: &TrinoRole, diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/product_logging.rs index 0906218d0..197660666 100644 --- a/rust/operator-binary/src/product_logging.rs +++ b/rust/operator-binary/src/product_logging.rs @@ -55,13 +55,29 @@ impl From for TrinoLogLevel { } } -/// Return the `log.properties` configuration -pub fn get_log_properties(logging: &Logging) -> Option { +/// Return the `log.properties` content as a typed `BTreeMap`. +pub fn get_log_property_map( + logging: &Logging, +) -> Option> { if let Some(ContainerLogConfig { choice: Some(ContainerLogConfigChoice::Automatic(log_config)), }) = logging.containers.get(&Container::Trino) { - Some(create_trino_log_properties(log_config)) + let map = log_config + .loggers + .iter() + .map(|(logger, config)| { + let log_level = TrinoLogLevel::from(config.level); + let key = if logger == AutomaticContainerLogConfig::ROOT_LOGGER { + // ROOT logger maps to an empty key in log.properties (=LEVEL). + String::new() + } else { + logger.clone() + }; + (key, log_level.to_string()) + }) + .collect(); + Some(map) } else { None } @@ -87,27 +103,3 @@ pub fn get_vector_toml( Ok(None) } } - -/// Create trino `log.properties` containing loggers and their respective log levels. -/// The operator-rs framework `LogLevel` offers more choices which are parsed to the available -/// `TrinoLogLevel`. -/// -/// The `log.properties` will adhere to the example format: -/// ``` -/// io.trino=debug -/// io.trino.server=info -/// ``` -fn create_trino_log_properties(automatic_container_config: &AutomaticContainerLogConfig) -> String { - automatic_container_config - .loggers - .iter() - .map(|(logger, config)| { - let log_level = TrinoLogLevel::from(config.level); - if logger == AutomaticContainerLogConfig::ROOT_LOGGER { - format!("={}\n", log_level) - } else { - format!("{}={}\n", logger, log_level) - } - }) - .collect::() -} diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index 1ee44bad2..b899eddc2 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -21,11 +21,6 @@ pub enum Error { MetadataBuild { source: stackable_operator::builder::meta::Error, }, - - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, } /// The rolegroup headless [`Service`] is a service that allows direct access to the instances of a certain rolegroup @@ -33,7 +28,7 @@ pub enum Error { pub fn build_rolegroup_headless_service( trino: &v1alpha1::TrinoCluster, role_group_ref: &RoleGroupRef, - object_labels: ObjectLabels, + object_labels: &ObjectLabels, selector: BTreeMap, ) -> Result { Ok(Service { @@ -42,7 +37,7 @@ pub fn build_rolegroup_headless_service( .name(role_group_ref.rolegroup_headless_service_name()) .ownerreference_from_resource(trino, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&object_labels) + .with_recommended_labels(object_labels) .context(MetadataBuildSnafu)? .build(), spec: Some(ServiceSpec { @@ -62,7 +57,7 @@ pub fn build_rolegroup_headless_service( pub fn build_rolegroup_metrics_service( trino: &v1alpha1::TrinoCluster, role_group_ref: &RoleGroupRef, - object_labels: ObjectLabels, + object_labels: &ObjectLabels, selector: BTreeMap, ) -> Result { Ok(Service { @@ -71,7 +66,7 @@ pub fn build_rolegroup_metrics_service( .name(role_group_ref.rolegroup_metrics_service_name()) .ownerreference_from_resource(trino, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&object_labels) + .with_recommended_labels(object_labels) .context(MetadataBuildSnafu)? .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) diff --git a/rust/operator-binary/src/webhooks/conversion.rs b/rust/operator-binary/src/webhooks/conversion.rs index 29dcc5adf..fb563de7c 100644 --- a/rust/operator-binary/src/webhooks/conversion.rs +++ b/rust/operator-binary/src/webhooks/conversion.rs @@ -9,10 +9,12 @@ use stackable_operator::{ }; use crate::crd::{ - FIELD_MANAGER, TrinoCluster, TrinoClusterVersion, + TrinoCluster, TrinoClusterVersion, catalog::{TrinoCatalog, TrinoCatalogVersion}, }; +const FIELD_MANAGER: &str = "trino-operator"; + /// Contains errors which can be encountered when creating the conversion webhook server and the /// CRD maintainer. #[derive(Debug, Snafu)] From 8f223f38df6b18c5e8f5174a4e970157f41afd56 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 16:14:46 +0200 Subject: [PATCH 02/65] docs: remove product config CLI paramter --- .../pages/reference/commandline-parameters.adoc | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/modules/trino/pages/reference/commandline-parameters.adoc b/docs/modules/trino/pages/reference/commandline-parameters.adoc index f4f4dd366..4e27f3283 100644 --- a/docs/modules/trino/pages/reference/commandline-parameters.adoc +++ b/docs/modules/trino/pages/reference/commandline-parameters.adoc @@ -2,19 +2,6 @@ This operator accepts the following command line parameters: -== product-config - -*Default value*: `/etc/stackable/trino-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values:* false - -[source] ----- -cargo run -- run --product-config /foo/bar/properties.yaml ----- - == watch-namespace *Default value*: All namespaces From 9bece1dae0fc1d93cdb22b3f35e3219ae1055087 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 16:27:53 +0200 Subject: [PATCH 03/65] fix: add todo to reference file name enum --- rust/operator-binary/src/command.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index 2e1f9ab5f..dfcac270f 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -19,7 +19,8 @@ use crate::{ }, }; -// File names not exported from crd/mod.rs. +// TODO: replace with build::properties::ConfigFileName once command.rs moves under build/ +// (with the StatefulSet builder migration). const LOG_PROPERTIES: &str = "log.properties"; const EXCHANGE_MANAGER_PROPERTIES: &str = "exchange-manager.properties"; const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; From 381896fdd1222b9e151467eca33c4b0676557c32 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 16:31:13 +0200 Subject: [PATCH 04/65] docs: adapted changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20eb2f86d..76615a9d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ All notable changes to this project will be documented in this file. - Document Helm deployed RBAC permissions and remove unnecessary permissions ([#869]). - Bump `stackable-operator` to 0.111.0 and `kube` to 3.1.0 ([#878], [#884]). - Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#889]). +- BREAKING: Removed product-config machinery. This is a breaking change in terms of configuration. + Users relying on the product-config `properties.yaml` file have to set these properties via the CRD ([#897]). ### Fixed @@ -36,6 +38,7 @@ All notable changes to this project will be documented in this file. [#883]: https://github.com/stackabletech/trino-operator/pull/883 [#884]: https://github.com/stackabletech/trino-operator/pull/884 [#889]: https://github.com/stackabletech/trino-operator/pull/889 +[#897]: https://github.com/stackabletech/trino-operator/pull/897 ## [26.3.0] - 2026-03-16 From d936727481121392ed9f54d48f9eaad52107b1bf Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 17:49:32 +0200 Subject: [PATCH 05/65] docs: fix file name doc location --- rust/operator-binary/src/crd/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 221bd9349..6741b49ce 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -194,31 +194,31 @@ pub mod versioned { #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TrinoConfigOverrides { - // File name defined in [`crate::controller::build::properties::CONFIG_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "config.properties")] pub config_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::NODE_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "node.properties")] pub node_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::LOG_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "log.properties")] pub log_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::SECURITY_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "security.properties")] pub security_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::ACCESS_CONTROL_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "access-control.properties")] pub access_control_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::EXCHANGE_MANAGER_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "exchange-manager.properties")] pub exchange_manager_properties: KeyValueConfigOverrides, - // File name defined in [`crate::controller::build::properties::SPOOLING_MANAGER_PROPERTIES_FILE_NAME`] + // File name defined in [`crate::controller::build::properties::ConfigFileName`] #[serde(default, rename = "spooling-manager.properties")] pub spooling_manager_properties: KeyValueConfigOverrides, } From dedc010981ac889f40b949fa6d4e77ef90ea3dc1 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 18:25:18 +0200 Subject: [PATCH 06/65] fix: move graceful shutdown to build module --- rust/operator-binary/src/controller.rs | 6 +++--- rust/operator-binary/src/controller/build.rs | 4 +++- .../{operations => controller/build}/graceful_shutdown.rs | 0 .../src/controller/build/properties/config_properties.rs | 4 +++- rust/operator-binary/src/operations/mod.rs | 3 --- 5 files changed, 9 insertions(+), 8 deletions(-) rename rust/operator-binary/src/{operations => controller/build}/graceful_shutdown.rs (100%) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 7bfa02b23..dc42590b0 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -82,7 +82,7 @@ use crate::{ LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener, build_group_listener_pvc, group_listener_name, secret_volume_listener_scope, }, - operations::{add_graceful_shutdown_config, pdb::add_pdbs}, + operations::pdb::add_pdbs, service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, }; @@ -204,7 +204,7 @@ pub enum Error { #[snafu(display("failed to configure graceful shutdown"))] GracefulShutdown { - source: crate::operations::graceful_shutdown::Error, + source: build::graceful_shutdown::Error, }, #[snafu(display("failed to get required Labels"))] @@ -639,7 +639,7 @@ fn build_rolegroup_statefulset( &mut cb_trino, ) .context(InvalidAuthenticationConfigSnafu)?; - add_graceful_shutdown_config( + build::graceful_shutdown::add_graceful_shutdown_config( trino, trino_role, merged_config, diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index fdc927c73..24b6aa4ab 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -1,7 +1,9 @@ //! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. //! //! `properties` renders the Trino `.properties` files; `config_map` assembles -//! the per-rolegroup ConfigMap. +//! the per-rolegroup ConfigMap; `graceful_shutdown` contributes graceful-shutdown +//! `config.properties` entries and Pod lifecycle configuration. pub mod config_map; +pub mod graceful_shutdown; pub mod properties; diff --git a/rust/operator-binary/src/operations/graceful_shutdown.rs b/rust/operator-binary/src/controller/build/graceful_shutdown.rs similarity index 100% rename from rust/operator-binary/src/operations/graceful_shutdown.rs rename to rust/operator-binary/src/controller/build/graceful_shutdown.rs diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index a7ae856f6..7c3dadabb 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -216,7 +216,9 @@ pub fn build( } // Graceful shutdown. - for (k, v) in crate::operations::graceful_shutdown_config_properties(cluster, role) { + for (k, v) in crate::controller::build::graceful_shutdown::graceful_shutdown_config_properties( + cluster, role, + ) { props.insert(k, v); } diff --git a/rust/operator-binary/src/operations/mod.rs b/rust/operator-binary/src/operations/mod.rs index e1cf1fd2e..d3cf6e9c4 100644 --- a/rust/operator-binary/src/operations/mod.rs +++ b/rust/operator-binary/src/operations/mod.rs @@ -1,4 +1 @@ -pub mod graceful_shutdown; pub mod pdb; - -pub use graceful_shutdown::*; From 72905d85c9601d03070c761e1c00f04867301fac Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 1 Jun 2026 18:27:41 +0200 Subject: [PATCH 07/65] fix: remove uuid dependency --- Cargo.lock | 5 ++--- Cargo.nix | 8 ++------ Cargo.toml | 1 - rust/operator-binary/Cargo.toml | 1 - 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a330e784e..381740fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3022,7 +3022,6 @@ dependencies = [ "strum", "tokio", "tracing", - "uuid", ] [[package]] @@ -3594,9 +3593,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" diff --git a/Cargo.nix b/Cargo.nix index 08e91cdec..d5df5b152 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -10085,10 +10085,6 @@ rec { name = "tracing"; packageId = "tracing"; } - { - name = "uuid"; - packageId = "uuid"; - } ]; buildDependencies = [ { @@ -12124,9 +12120,9 @@ rec { }; "unicode-segmentation" = rec { crateName = "unicode-segmentation"; - version = "1.13.2"; + version = "1.13.3"; edition = "2018"; - sha256 = "135a26m4a0wj319gcw28j6a5aqvz00jmgwgmcs6szgxjf942facn"; + sha256 = "1a47zaq83p386r3baq4m018xd5q4q0grdg56i1x042dzn71x7xf6"; libName = "unicode_segmentation"; authors = [ "kwantam " diff --git a/Cargo.toml b/Cargo.toml index d2ad0181d..185f5e77b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ snafu = "0.9" strum = { version = "0.28", features = ["derive"] } tokio = { version = "1.52", features = ["full"] } tracing = "0.1" -uuid = "1.23" [patch."https://github.com/stackabletech/operator-rs.git"] stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator"} diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index a81d68dc2..62fa0c049 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -25,7 +25,6 @@ snafu.workspace = true strum.workspace = true tokio.workspace = true tracing.workspace = true -uuid.workspace = true [dev-dependencies] rstest.workspace = true From a8bddf9710641a892ee0cd1c974206f050ed5025 Mon Sep 17 00:00:00 2001 From: maltesander Date: Tue, 2 Jun 2026 15:08:48 +0200 Subject: [PATCH 08/65] Update rust/operator-binary/src/controller/build/properties/config_properties.rs Co-authored-by: Andrew Kenworthy <1712947+adwk67@users.noreply.github.com> --- .../src/controller/build/properties/config_properties.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 7c3dadabb..7538c3dc0 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -55,7 +55,6 @@ const LOG_MAX_TOTAL_SIZE: &str = "log.max-total-size"; const DEFAULT_QUERY_MAX_MEMORY: &str = "50GB"; const DEFAULT_NODE_SCHEDULER_INCLUDE_COORDINATOR: &str = "false"; -// Mirrors the private constant of the same name in `crd/mod.rs`. const LOG_FILE_COUNT: u32 = 2; #[derive(Debug, Snafu)] From e1e53be90a5ddd1f00528d587c310ac3d5fe285d Mon Sep 17 00:00:00 2001 From: maltesander Date: Tue, 2 Jun 2026 15:09:00 +0200 Subject: [PATCH 09/65] Update rust/operator-binary/src/controller/build/config_map.rs Co-authored-by: Andrew Kenworthy <1712947+adwk67@users.noreply.github.com> --- rust/operator-binary/src/controller/build/config_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 6b63418db..05cf5a7b4 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -99,7 +99,7 @@ pub fn build_rolegroup_config_map( let mut data: BTreeMap = BTreeMap::new(); // Auth files (e.g. password-authenticator file contents) — inserted FIRST - // to match the legacy ordering in controller.rs:621. + // to match the legacy precedence. for (file_name, props) in cluster.cluster_config.authentication.config_files(role) { let rendered = to_java_properties_string(&props).with_context(|_| WritePropertiesSnafu { From 0c16cbaad3fcca65cb4a8da1613af4e241a2b453 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 3 Jun 2026 13:12:59 +0200 Subject: [PATCH 10/65] docs: remove product config from env variables --- .../reference/environment-variables.adoc | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/docs/modules/trino/pages/reference/environment-variables.adoc b/docs/modules/trino/pages/reference/environment-variables.adoc index ae221056e..fddbd1ad0 100644 --- a/docs/modules/trino/pages/reference/environment-variables.adoc +++ b/docs/modules/trino/pages/reference/environment-variables.adoc @@ -33,32 +33,6 @@ docker run \ oci.stackable.tech/sdp/trino-operator:0.0.0-dev ---- -== PRODUCT_CONFIG - -*Default value*: `/etc/stackable/trino-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values*: false - -[source] ----- -export PRODUCT_CONFIG=/foo/bar/properties.yaml -cargo run -- run ----- - -or via docker: - ----- -docker run \ - --name trino-operator \ - --network host \ - --env KUBECONFIG=/home/stackable/.kube/config \ - --env PRODUCT_CONFIG=/my/product/config.yaml \ - --mount type=bind,source="$HOME/.kube/config",target="/home/stackable/.kube/config" \ - oci.stackable.tech/sdp/trino-operator:0.0.0-dev ----- - == WATCH_NAMESPACE *Default value*: All namespaces From fbb7ff13e72ff5150eb3b20845614a60cdec9bae Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 3 Jun 2026 16:28:10 +0200 Subject: [PATCH 11/65] fix: rename validated -> validated_cluster --- rust/operator-binary/src/controller.rs | 40 ++++++++++++++------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index dc42590b0..bed09e8cd 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -303,8 +303,9 @@ pub async fn reconcile_trino( .context(DereferenceSnafu)?; // validate (no client required) - let validated = validate::validate(trino, &dereferenced_objects, &ctx.operator_environment) - .context(ValidateClusterSnafu)?; + let validated_cluster = + validate::validate(trino, &dereferenced_objects, &ctx.operator_environment) + .context(ValidateClusterSnafu)?; let mut cluster_resources = ClusterResources::new( APP_NAME, @@ -359,14 +360,14 @@ pub async fn reconcile_trino( let mut sts_cond_builder = StatefulSetConditionBuilder::default(); - for (trino_role, role_group_configs) in &validated.role_group_configs { + for (trino_role, role_group_configs) in &validated_cluster.role_group_configs { for (role_group_name, rg) in role_group_configs { let role_group_ref = trino_role.rolegroup_ref(trino, role_group_name); let merged_config = &rg.config; let role_group_service_recommended_labels = build_recommended_labels( trino, - &validated.image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &role_group_ref.role, &role_group_ref.role_group, ); @@ -396,7 +397,7 @@ pub async fn reconcile_trino( .context(ServiceConfigurationSnafu)?; let rg_configmap = build::config_map::build_rolegroup_config_map( - &validated, + &validated_cluster, trino_role, &role_group_ref, &client.kubernetes_cluster_info, @@ -409,24 +410,24 @@ pub async fn reconcile_trino( let rg_catalog_configmap = build_rolegroup_catalog_config_map( trino, - &validated.image, + &validated_cluster.image, &role_group_ref, - &validated.cluster_config.catalogs, + &validated_cluster.cluster_config.catalogs, )?; let rg_stateful_set = build_rolegroup_statefulset( trino, trino_role, - &validated.image, + &validated_cluster.image, &role_group_ref, &rg.env_overrides, merged_config, - &validated.cluster_config.authentication, - &validated.cluster_config.catalogs, + &validated_cluster.cluster_config.authentication, + &validated_cluster.cluster_config.catalogs, &rbac_sa.name_any(), - &validated.cluster_config.fault_tolerant_execution, - &validated.cluster_config.client_protocol, - &validated.cluster_config.authorization, + &validated_cluster.cluster_config.fault_tolerant_execution, + &validated_cluster.cluster_config.client_protocol, + &validated_cluster.cluster_config.authorization, )?; cluster_resources @@ -476,7 +477,7 @@ pub async fn reconcile_trino( trino, build_recommended_labels( trino, - &validated.image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &trino_role.to_string(), "none", ), @@ -1402,7 +1403,7 @@ mod tests { image_repository: "oci.example.org".to_string(), }; - let validated = + let validated_cluster = validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); let trino_role = TrinoRole::Coordinator; @@ -1413,13 +1414,13 @@ mod tests { }; let recommended_labels = build_recommended_labels( &trino, - &validated.image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &rolegroup_ref.role, &rolegroup_ref.role_group, ); build::config_map::build_rolegroup_config_map( - &validated, + &validated_cluster, &trino_role, &rolegroup_ref, &cluster_info, @@ -1640,10 +1641,11 @@ mod tests { operator_service_name: "trino-operator".to_string(), image_repository: "oci.example.org".to_string(), }; - let validated = + let validated_cluster = validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); - let env = &validated.role_group_configs[&TrinoRole::Coordinator]["default"].env_overrides; + let env = + &validated_cluster.role_group_configs[&TrinoRole::Coordinator]["default"].env_overrides; let value = |name: &str| env.get(name).cloned(); assert_eq!(value("COMMON_VAR").as_deref(), Some("group-value")); assert_eq!(value("GROUP_VAR").as_deref(), Some("group-value")); From 66b2614b6df77f9b7509419bdfcffea4285a2b82 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 4 Jun 2026 13:40:56 +0200 Subject: [PATCH 12/65] refactor: move logging to build step --- .../src/controller/build/config_map.rs | 18 +++---------- .../build/properties/log_properties.rs | 2 +- .../build/properties/logging.rs} | 27 +++---------------- .../src/controller/build/properties/mod.rs | 1 + rust/operator-binary/src/main.rs | 1 - 5 files changed, 8 insertions(+), 41 deletions(-) rename rust/operator-binary/src/{product_logging.rs => controller/build/properties/logging.rs} (77%) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 05cf5a7b4..c8e037f4e 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -18,12 +18,11 @@ use crate::{ ValidatedCluster, build::properties::{ ConfigFileName, access_control_properties, config_properties, - exchange_manager_properties, log_properties, node_properties, security_properties, - spooling_manager_properties, writer::to_java_properties_string, + exchange_manager_properties, log_properties, logging::get_vector_toml, node_properties, + security_properties, spooling_manager_properties, writer::to_java_properties_string, }, }, crd::{TrinoRole, v1alpha1}, - product_logging::get_vector_toml, }; // File name not exported from crd/mod.rs. @@ -54,12 +53,6 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to build the vector configuration for {cm_name}"))] - InvalidLoggingConfig { - source: crate::product_logging::Error, - cm_name: String, - }, - #[snafu(display("failed to resolve the {role} role"))] ReadRole { source: crate::crd::Error, @@ -194,12 +187,7 @@ pub fn build_rolegroup_config_map( data.insert(JVM_CONFIG.to_string(), jvm_config); // 9. Vector sidecar toml if enabled. - let vector_toml = get_vector_toml(rolegroup_ref, &rg.config.logging).with_context(|_| { - InvalidLoggingConfigSnafu { - cm_name: rolegroup_ref.object_name(), - } - })?; - if let Some(vector_toml) = vector_toml { + if let Some(vector_toml) = get_vector_toml(rolegroup_ref, &rg.config.logging) { data.insert( product_logging::framework::VECTOR_CONFIG_FILE.to_string(), vector_toml, diff --git a/rust/operator-binary/src/controller/build/properties/log_properties.rs b/rust/operator-binary/src/controller/build/properties/log_properties.rs index 5da06f423..f1e736158 100644 --- a/rust/operator-binary/src/controller/build/properties/log_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/log_properties.rs @@ -13,7 +13,7 @@ pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { // 1. No defaults // 2. Automatic per-container logger levels - if let Some(per_container) = crate::product_logging::get_log_property_map(&rg.config.logging) { + if let Some(per_container) = super::logging::get_log_property_map(&rg.config.logging) { props.extend(per_container); } diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/controller/build/properties/logging.rs similarity index 77% rename from rust/operator-binary/src/product_logging.rs rename to rust/operator-binary/src/controller/build/properties/logging.rs index 197660666..dc9550a90 100644 --- a/rust/operator-binary/src/product_logging.rs +++ b/rust/operator-binary/src/controller/build/properties/logging.rs @@ -1,4 +1,3 @@ -use snafu::Snafu; use stackable_operator::{ product_logging::{ framework::create_vector_config, @@ -13,26 +12,6 @@ use strum::Display; use crate::crd::{Container, v1alpha1}; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object has no namespace"))] - ObjectHasNoNamespace, - - #[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; - #[derive(Display)] #[strum(serialize_all = "lowercase")] pub enum TrinoLogLevel { @@ -87,7 +66,7 @@ pub fn get_log_property_map( pub fn get_vector_toml( rolegroup: &RoleGroupRef, logging: &Logging, -) -> Result> { +) -> Option { let vector_log_config = if let Some(ContainerLogConfig { choice: Some(ContainerLogConfigChoice::Automatic(log_config)), }) = logging.containers.get(&Container::Vector) @@ -98,8 +77,8 @@ pub fn get_vector_toml( }; if logging.enable_vector_agent { - Ok(Some(create_vector_config(rolegroup, vector_log_config))) + Some(create_vector_config(rolegroup, vector_log_config)) } else { - Ok(None) + None } } diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index f0025a78e..db585327b 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -12,6 +12,7 @@ pub mod access_control_properties; pub mod config_properties; pub mod exchange_manager_properties; pub mod log_properties; +pub mod logging; pub mod node_properties; pub mod security_properties; pub mod spooling_manager_properties; diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index fb00b6a5b..bfb87c039 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -52,7 +52,6 @@ mod crd; mod framework; mod listener; mod operations; -mod product_logging; mod service; mod webhooks; From ad0c451ddfd5bee9955409a18c3302cc53b39613 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 5 Jun 2026 18:28:54 +0200 Subject: [PATCH 13/65] refactor: consume the config-file writer from stackable-operator Replace the hand-rolled Java-properties escaper (rust/operator-binary/src/controller/build/properties/writer.rs) with stackable_operator::v2::config_file_writer (moved there via operator-rs #1217 on the smooth-operator branch). A thin adapter in properties/mod.rs maps trino's String-valued maps onto the writer's Option-valued interface; the kuttl-pinned escape behaviours stay covered by unit tests against the shared writer. Unlike the other operators this is not a pure relocation: the hand-rolled escaper (introduced on this branch, never released) had unintentionally diverged from the product-config writer it replaced. A differential test showed 8 divergences, all unreachable from operator defaults (the kuttl smoke snapshot's 157 property lines are unaffected): - inner/trailing spaces in values: now escaped (read-back identical per the Java properties spec) - tab/newline/CR in values: now escaped (the raw output was malformed) - non-ASCII: now ISO-8859-1-spec behaviour again (error on Latin-1-range values, \uXXXX beyond) instead of raw UTF-8 pass-through This restores the exact serialization behaviour of the released operator, which renders via the identical product-config code. The base dependency tag moves from stackable-operator-0.111.0 to 0.111.1, matching the other operators; cargo only substitutes a [patch] whose package version matches, and the smooth-operator branch carries 0.111.1. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 86 ++++-- Cargo.nix | 291 ++++++++++-------- Cargo.toml | 2 +- crate-hashes.json | 4 +- rust/operator-binary/src/controller.rs | 4 +- .../src/controller/build/config_map.rs | 4 +- .../src/controller/build/properties/mod.rs | 73 ++++- .../src/controller/build/properties/writer.rs | 143 --------- 8 files changed, 299 insertions(+), 308 deletions(-) delete mode 100644 rust/operator-binary/src/controller/build/properties/writer.rs diff --git a/Cargo.lock b/Cargo.lock index 381740fe1..b8511d407 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#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "regex", @@ -1840,9 +1840,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", @@ -1854,9 +1854,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", @@ -1866,9 +1866,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", @@ -1879,9 +1879,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http", "opentelemetry", @@ -1893,14 +1893,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", @@ -1911,21 +1911,22 @@ 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", + "portable-atomic", "rand 0.9.4", "thiserror 2.0.18", "tokio", @@ -2208,6 +2209,15 @@ 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.45" @@ -2347,9 +2357,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", @@ -2365,9 +2375,6 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", @@ -2887,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#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "const-oid", "ecdsa", @@ -2911,7 +2918,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "base64", "clap", @@ -2923,6 +2930,7 @@ dependencies = [ "futures 0.3.32", "http", "indexmap", + "java-properties", "jiff", "json-patch", "k8s-openapi", @@ -2948,12 +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#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "proc-macro2", @@ -2963,8 +2972,8 @@ dependencies = [ [[package]] name = "stackable-shared" -version = "0.1.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +version = "0.1.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "jiff", "k8s-openapi", @@ -2980,8 +2989,8 @@ dependencies = [ [[package]] name = "stackable-telemetry" -version = "0.6.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +version = "0.6.4" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "axum", "clap", @@ -3027,7 +3036,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "kube", "schemars", @@ -3041,7 +3050,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "convert_case", "convert_case_extras", @@ -3059,7 +3068,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "arc-swap", "async-trait", @@ -3411,6 +3420,17 @@ dependencies = [ "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" @@ -3522,9 +3542,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", diff --git a/Cargo.nix b/Cargo.nix index d5df5b152..1db51076a 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4831,7 +4831,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "k8s_version"; @@ -6031,9 +6031,9 @@ rec { }; "opentelemetry" = rec { crateName = "opentelemetry"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "18629xsj4rsyiby9aj511q6wcw6s9m09gx3ymw1yjcvix1mcsjxq"; + sha256 = "10ln14d1jgc8rvw97mblc9blzcgpg1bimim4d170b7ia4mijq55h"; dependencies = [ { name = "futures-core"; @@ -6070,24 +6070,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 = [ { @@ -6130,18 +6130,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 = [ { @@ -6177,16 +6174,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.1"; + version = "0.32.0"; edition = "2021"; - sha256 = "07zp0b62b9dajnvvcd6j2ppw5zg7wp4ixka9z6fr3bxrrdmcss8z"; + sha256 = "0d9cys2flpidfxbr6h1103hjc633cax47ihnqgbj0xnicscr4rlr"; libName = "opentelemetry_otlp"; dependencies = [ { @@ -6247,10 +6244,9 @@ rec { usesDefaultFeatures = false; } { - name = "tracing"; - packageId = "tracing"; + name = "tonic-types"; + packageId = "tonic-types"; optional = true; - usesDefaultFeatures = false; } ]; devDependencies = [ @@ -6275,16 +6271,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" ]; @@ -6297,27 +6296,27 @@ rec { "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; "serialize" = [ "serde" "serde_json" ]; - "tls" = [ "tonic/tls-ring" ]; + "tls" = [ "tls-ring" ]; "tls-aws-lc" = [ "tonic/tls-aws-lc" ]; "tls-provider-agnostic" = [ "tonic/_tls-any" ]; "tls-ring" = [ "tonic/tls-ring" ]; - "tls-roots" = [ "tls" "tonic/tls-native-roots" ]; - "tls-webpki-roots" = [ "tls" "tonic/tls-webpki-roots" ]; + "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 = [ { @@ -6361,30 +6360,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 = { }; @@ -6392,9 +6390,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"; @@ -6420,6 +6418,13 @@ 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.4"; @@ -6444,10 +6449,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" ]; @@ -6464,15 +6477,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"; @@ -6981,7 +6993,7 @@ rec { "default" = [ "fallback" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "require-cas" ]; + resolvedDefaultFeatures = [ "fallback" "require-cas" ]; }; "portable-atomic-util" = rec { crateName = "portable-atomic-util"; @@ -7249,6 +7261,34 @@ 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.45"; @@ -7678,9 +7718,9 @@ rec { }; "reqwest" = rec { crateName = "reqwest"; - version = "0.12.28"; + version = "0.13.4"; edition = "2021"; - sha256 = "0iqidijghgqbzl3bjg5hb4zmigwa4r612bgi0yiq0c90b6jkrpgd"; + sha256 = "1hy1plns9krbh3h1dy2sdjygsfkdcnxm6pbxdi0ya9b5vq8mi711"; authors = [ "Sean McArthur " ]; @@ -7697,7 +7737,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"; @@ -7717,62 +7757,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"; @@ -7783,27 +7805,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"; @@ -7812,17 +7834,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" ]; } ]; @@ -7831,33 +7853,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" ]; } { @@ -7869,40 +7885,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" ]; @@ -9501,7 +9514,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_certs"; @@ -9604,7 +9617,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_operator"; @@ -9655,6 +9668,10 @@ rec { name = "indexmap"; packageId = "indexmap"; } + { + name = "java-properties"; + packageId = "java-properties"; + } { name = "jiff"; packageId = "jiff"; @@ -9770,12 +9787,17 @@ rec { name = "uuid"; packageId = "uuid"; } + { + name = "xml"; + packageId = "xml"; + } ]; 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" ]; @@ -9789,7 +9811,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; @@ -9819,12 +9841,12 @@ 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 = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_shared"; @@ -9900,12 +9922,12 @@ 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 = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_telemetry"; @@ -9949,7 +9971,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"; @@ -10112,7 +10134,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_versioned"; @@ -10162,7 +10184,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; @@ -10230,7 +10252,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_webhook"; @@ -11394,6 +11416,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"; @@ -11848,9 +11897,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 = [ { diff --git a/Cargo.toml b/Cargo.toml index 185f5e77b..f0cdf670b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" repository = "https://github.com/stackabletech/trino-operator" [workspace.dependencies] -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.111.0", features = ["webhook"] } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.111.1", features = ["webhook"] } anyhow = "1.0" async-trait = "0.1" diff --git a/crate-hashes.json b/crate-hashes.json index c76bf06c2..cd03561e9 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -3,8 +3,8 @@ "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.3": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index bed09e8cd..2062e297e 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -159,7 +159,7 @@ pub enum Error { #[snafu(display("failed to format runtime properties"))] FailedToWriteJavaProperties { - source: build::properties::writer::Error, + source: stackable_operator::v2::config_file_writer::PropertiesWriterError, }, #[snafu(display("internal operator failure: {source}"))] @@ -561,7 +561,7 @@ fn build_rolegroup_catalog_config_map( .collect(); Ok(( format!("{}.properties", catalog.name), - build::properties::writer::to_java_properties_string(&catalog_props) + build::properties::to_java_properties_string(&catalog_props) .context(FailedToWriteJavaPropertiesSnafu)?, )) }) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index c8e037f4e..195abb7d3 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -19,7 +19,7 @@ use crate::{ build::properties::{ ConfigFileName, access_control_properties, config_properties, exchange_manager_properties, log_properties, logging::get_vector_toml, node_properties, - security_properties, spooling_manager_properties, writer::to_java_properties_string, + security_properties, spooling_manager_properties, to_java_properties_string, }, }, crd::{TrinoRole, v1alpha1}, @@ -35,7 +35,7 @@ pub enum Error { #[snafu(display("failed to write {file} properties"))] WriteProperties { - source: super::properties::writer::Error, + source: stackable_operator::v2::config_file_writer::PropertiesWriterError, file: String, }, diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index db585327b..3eb57571b 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -1,12 +1,16 @@ //! Per-file builders for Trino `.properties` files. //! //! Each `.rs` module produces the rendered key/value pairs for one -//! Trino config file. The shared [`writer`] module serializes the map to the -//! Java-properties on-wire format. +//! Trino config file. The shared +//! [`stackable_operator::v2::config_file_writer`] serializes the map to the +//! Java-properties on-wire format (see [`to_java_properties_string`]). use std::collections::BTreeMap; -use stackable_operator::v2::config_overrides::KeyValueConfigOverrides; +use stackable_operator::v2::{ + config_file_writer::{self, PropertiesWriterError}, + config_overrides::KeyValueConfigOverrides, +}; pub mod access_control_properties; pub mod config_properties; @@ -16,7 +20,6 @@ pub mod logging; pub mod node_properties; pub mod security_properties; pub mod spooling_manager_properties; -pub mod writer; /// The names of the Trino `.properties` files assembled into the rolegroup ConfigMap. #[derive(Clone, Copy, Debug, strum::Display)] @@ -54,6 +57,27 @@ fn resolved_overrides( defined_entries(overrides.overrides) } +/// Serialize `props` as a Java-properties string, sorted by key, via the shared +/// [`config_file_writer`] — the same `product-config`-derived writer used by all +/// other operators (and by this operator before the product-config refactoring; +/// a hand-rolled escaper briefly lived here on this branch but accidentally +/// diverged from it on non-ASCII and control characters, so it was dropped in +/// favour of the shared implementation). +/// +/// This adapter maps trino's `String`-valued property maps onto the writer's +/// `Option`-valued interface. Alternative considered: converting at +/// each of the nine call sites — rejected as repetitive; the clone here is +/// negligible for config-sized maps. +pub fn to_java_properties_string( + props: &BTreeMap, +) -> Result { + let props: BTreeMap> = props + .iter() + .map(|(k, v)| (k.clone(), Some(v.clone()))) + .collect(); + config_file_writer::to_java_properties_string(props.iter()) +} + #[cfg(test)] pub(crate) mod test_support { use stackable_operator::cli::OperatorEnvironmentOptions; @@ -104,3 +128,44 @@ pub(crate) mod test_support { replicas: 1 "#; } + +#[cfg(test)] +mod tests { + use super::*; + + fn render(pairs: &[(&str, &str)]) -> String { + let props: BTreeMap = pairs + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect(); + to_java_properties_string(&props).expect("rendering the test properties should succeed") + } + + /// The escape behaviours pinned by the kuttl smoke snapshot + /// (`tests/templates/kuttl/smoke/14-assert.yaml.j2`); previously asserted + /// against the hand-rolled writer, now against the shared one. + #[test] + fn kuttl_pinned_escapes_are_stable() { + assert_eq!( + render(&[( + "internal-communication.shared-secret", + "${ENV:INTERNAL_SECRET}" + )]), + "internal-communication.shared-secret=${ENV\\:INTERNAL_SECRET}\n" + ); + assert_eq!( + render(&[("discovery.uri", "https://trino-coordinator.svc:8443")]), + "discovery.uri=https\\://trino-coordinator.svc\\:8443\n" + ); + } + + #[test] + fn keys_are_sorted_alphabetically() { + assert_eq!(render(&[("b", "2"), ("a", "1")]), "a=1\nb=2\n"); + } + + #[test] + fn empty_map_renders_empty_string() { + assert_eq!(render(&[]), ""); + } +} diff --git a/rust/operator-binary/src/controller/build/properties/writer.rs b/rust/operator-binary/src/controller/build/properties/writer.rs deleted file mode 100644 index ce3d75218..000000000 --- a/rust/operator-binary/src/controller/build/properties/writer.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Java-properties writer. -//! -//! Reproduces the escape rules required for Trino's `.properties` files. Pinned -//! by the kuttl ConfigMap snapshot at -//! `tests/templates/kuttl/smoke/14-assert.yaml.j2`. -// TODO(@maltesander): should be moved to a common crate in operator-rs. - -use std::collections::BTreeMap; - -use snafu::{ResultExt, Snafu}; - -#[derive(Debug, Snafu)] -pub enum Error { - #[snafu(display("failed to write properties output"))] - Format { source: std::fmt::Error }, -} - -/// Serialize `props` as a Java-properties string, sorted by key. -/// -/// Keys and values are escaped per : -/// `:`, `=`, `#`, `!`, `\\`, leading whitespace, and ` ` (space). -pub fn to_java_properties_string(props: &BTreeMap) -> Result { - use std::fmt::Write; - let mut out = String::new(); - for (k, v) in props { - writeln!(out, "{}={}", escape_key(k), escape_value(v)).context(FormatSnafu)?; - } - Ok(out) -} - -fn escape_key(key: &str) -> String { - let mut out = String::with_capacity(key.len()); - for c in key.chars() { - match c { - '\\' | ':' | '=' | '#' | '!' | ' ' => { - out.push('\\'); - out.push(c); - } - _ => out.push(c), - } - } - out -} - -fn escape_value(value: &str) -> String { - let mut out = String::with_capacity(value.len()); - let mut at_start = true; - for c in value.chars() { - match c { - '\\' | ':' | '=' | '#' | '!' => { - out.push('\\'); - out.push(c); - } - ' ' if at_start => { - out.push('\\'); - out.push(' '); - } - _ => out.push(c), - } - if c != ' ' { - at_start = false; - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - fn render(pairs: &[(&str, &str)]) -> String { - let props: BTreeMap = pairs - .iter() - .map(|(k, v)| ((*k).to_string(), (*v).to_string())) - .collect(); - to_java_properties_string(&props).unwrap() - } - - #[test] - fn empty_map_renders_empty_string() { - let props: BTreeMap = BTreeMap::new(); - assert_eq!(to_java_properties_string(&props).unwrap(), ""); - } - - #[test] - fn keys_are_sorted_alphabetically() { - assert_eq!(render(&[("b", "2"), ("a", "1")]), "a=1\nb=2\n"); - } - - #[test] - fn colon_in_value_is_escaped() { - // From smoke snapshot: - // internal-communication.shared-secret=${ENV\:INTERNAL_SECRET} - assert_eq!( - render(&[( - "internal-communication.shared-secret", - "${ENV:INTERNAL_SECRET}" - )]), - "internal-communication.shared-secret=${ENV\\:INTERNAL_SECRET}\n" - ); - } - - #[test] - fn colon_in_url_value_is_escaped() { - // From smoke snapshot: - // discovery.uri=https\://trino-coordinator-default-0...:8443 - assert_eq!( - render(&[("discovery.uri", "https://trino-coordinator.svc:8443")]), - "discovery.uri=https\\://trino-coordinator.svc\\:8443\n" - ); - } - - #[test] - fn equals_in_value_is_escaped() { - assert_eq!(render(&[("k", "a=b")]), "k=a\\=b\n"); - } - - #[test] - fn backslash_in_value_is_escaped() { - assert_eq!(render(&[("k", "a\\b")]), "k=a\\\\b\n"); - } - - #[test] - fn leading_space_in_value_is_escaped() { - assert_eq!(render(&[("k", " v")]), "k=\\ v\n"); - } - - #[test] - fn non_leading_space_in_value_is_not_escaped() { - assert_eq!(render(&[("k", "a b")]), "k=a b\n"); - } - - #[test] - fn space_in_key_is_escaped() { - assert_eq!(render(&[("a b", "1")]), "a\\ b=1\n"); - } - - #[test] - fn hash_and_bang_in_value_are_escaped() { - assert_eq!(render(&[("k", "#comment")]), "k=\\#comment\n"); - assert_eq!(render(&[("k", "!bang")]), "k=\\!bang\n"); - } -} From b7ea0c5bf83642e0571244e1a36343175ca2568f Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 8 Jun 2026 10:15:34 +0200 Subject: [PATCH 14/65] fix: improve map rendering --- rust/operator-binary/src/controller.rs | 8 +--- .../src/controller/build/config_map.rs | 24 ++++++------ .../src/controller/build/properties/mod.rs | 38 ++++++++----------- 3 files changed, 28 insertions(+), 42 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 2062e297e..1fd2871fe 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -62,6 +62,7 @@ mod build; mod dereference; mod validate; +use build::properties::render_java_properties; pub use validate::{TrinoRoleGroupConfig, ValidatedCluster}; use crate::{ @@ -554,14 +555,9 @@ fn build_rolegroup_catalog_config_map( catalogs .iter() .map(|catalog| { - let catalog_props: BTreeMap = catalog - .properties - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); Ok(( format!("{}.properties", catalog.name), - build::properties::to_java_properties_string(&catalog_props) + render_java_properties(catalog.properties.clone()) .context(FailedToWriteJavaPropertiesSnafu)?, )) }) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 195abb7d3..f1ed7fb11 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -19,7 +19,7 @@ use crate::{ build::properties::{ ConfigFileName, access_control_properties, config_properties, exchange_manager_properties, log_properties, logging::get_vector_toml, node_properties, - security_properties, spooling_manager_properties, to_java_properties_string, + render_java_properties, security_properties, spooling_manager_properties, }, }, crd::{TrinoRole, v1alpha1}, @@ -65,7 +65,6 @@ pub enum Error { type Result = std::result::Result; -#[allow(clippy::too_many_arguments)] pub fn build_rolegroup_config_map( cluster: &ValidatedCluster, role: &TrinoRole, @@ -94,10 +93,9 @@ pub fn build_rolegroup_config_map( // Auth files (e.g. password-authenticator file contents) — inserted FIRST // to match the legacy precedence. for (file_name, props) in cluster.cluster_config.authentication.config_files(role) { - let rendered = - to_java_properties_string(&props).with_context(|_| WritePropertiesSnafu { - file: file_name.clone(), - })?; + let rendered = render_java_properties(props).with_context(|_| WritePropertiesSnafu { + file: file_name.clone(), + })?; data.insert(file_name, rendered); } @@ -106,7 +104,7 @@ pub fn build_rolegroup_config_map( .context(BuildConfigPropertiesSnafu)?; data.insert( ConfigFileName::Config.to_string(), - to_java_properties_string(&cfg).with_context(|_| WritePropertiesSnafu { + render_java_properties(cfg).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Config.to_string(), })?, ); @@ -115,7 +113,7 @@ pub fn build_rolegroup_config_map( let node = node_properties::build(cluster, rg); data.insert( ConfigFileName::Node.to_string(), - to_java_properties_string(&node).with_context(|_| WritePropertiesSnafu { + render_java_properties(node).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Node.to_string(), })?, ); @@ -125,7 +123,7 @@ pub fn build_rolegroup_config_map( if !log.is_empty() { data.insert( ConfigFileName::Log.to_string(), - to_java_properties_string(&log).with_context(|_| WritePropertiesSnafu { + render_java_properties(log).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Log.to_string(), })?, ); @@ -135,7 +133,7 @@ pub fn build_rolegroup_config_map( let sec = security_properties::build(rg); data.insert( ConfigFileName::Security.to_string(), - to_java_properties_string(&sec).with_context(|_| WritePropertiesSnafu { + render_java_properties(sec).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Security.to_string(), })?, ); @@ -145,7 +143,7 @@ pub fn build_rolegroup_config_map( if !ac.is_empty() { data.insert( ConfigFileName::AccessControl.to_string(), - to_java_properties_string(&ac).with_context(|_| WritePropertiesSnafu { + render_java_properties(ac).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::AccessControl.to_string(), })?, ); @@ -156,7 +154,7 @@ pub fn build_rolegroup_config_map( if !em.is_empty() { data.insert( ConfigFileName::ExchangeManager.to_string(), - to_java_properties_string(&em).with_context(|_| WritePropertiesSnafu { + render_java_properties(em).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::ExchangeManager.to_string(), })?, ); @@ -167,7 +165,7 @@ pub fn build_rolegroup_config_map( if !sm.is_empty() { data.insert( ConfigFileName::SpoolingManager.to_string(), - to_java_properties_string(&sm).with_context(|_| WritePropertiesSnafu { + render_java_properties(sm).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::SpoolingManager.to_string(), })?, ); diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 3eb57571b..79bbdd5b9 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -8,7 +8,7 @@ use std::collections::BTreeMap; use stackable_operator::v2::{ - config_file_writer::{self, PropertiesWriterError}, + config_file_writer::{PropertiesWriterError, to_java_properties_string}, config_overrides::KeyValueConfigOverrides, }; @@ -40,6 +40,19 @@ pub enum ConfigFileName { SpoolingManager, } +/// Render a `key -> value` properties map to the Java `.properties` on-wire format. +/// +/// The upstream [`to_java_properties_string`] consumes an iterator of +/// `(&String, &Option)`, so we lift the plain `String` values into +/// `Some(..)` first. +pub(crate) fn render_java_properties( + props: BTreeMap, +) -> Result { + let props: BTreeMap> = + props.into_iter().map(|(k, v)| (k, Some(v))).collect(); + to_java_properties_string(props.iter()) +} + /// Keep only the set (`Some`) entries of a `key -> optional value` map, as `(key, value)` pairs. fn defined_entries( entries: BTreeMap>, @@ -57,27 +70,6 @@ fn resolved_overrides( defined_entries(overrides.overrides) } -/// Serialize `props` as a Java-properties string, sorted by key, via the shared -/// [`config_file_writer`] — the same `product-config`-derived writer used by all -/// other operators (and by this operator before the product-config refactoring; -/// a hand-rolled escaper briefly lived here on this branch but accidentally -/// diverged from it on non-ASCII and control characters, so it was dropped in -/// favour of the shared implementation). -/// -/// This adapter maps trino's `String`-valued property maps onto the writer's -/// `Option`-valued interface. Alternative considered: converting at -/// each of the nine call sites — rejected as repetitive; the clone here is -/// negligible for config-sized maps. -pub fn to_java_properties_string( - props: &BTreeMap, -) -> Result { - let props: BTreeMap> = props - .iter() - .map(|(k, v)| (k.clone(), Some(v.clone()))) - .collect(); - config_file_writer::to_java_properties_string(props.iter()) -} - #[cfg(test)] pub(crate) mod test_support { use stackable_operator::cli::OperatorEnvironmentOptions; @@ -138,7 +130,7 @@ mod tests { .iter() .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); - to_java_properties_string(&props).expect("rendering the test properties should succeed") + render_java_properties(props).expect("rendering the test properties should succeed") } /// The escape behaviours pinned by the kuttl smoke snapshot From 90dfd912e523fc2cf4ba20641197265305b41b63 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 8 Jun 2026 16:34:36 +0200 Subject: [PATCH 15/65] refactor: make namespace non option and pass trough catalogs --- .../operator-binary/src/catalog/black_hole.rs | 4 +- rust/operator-binary/src/catalog/commons.rs | 23 +++----- rust/operator-binary/src/catalog/config.rs | 5 +- .../operator-binary/src/catalog/delta_lake.rs | 10 ++-- rust/operator-binary/src/catalog/generic.rs | 4 +- .../src/catalog/google_sheet.rs | 3 +- rust/operator-binary/src/catalog/hive.rs | 10 ++-- rust/operator-binary/src/catalog/iceberg.rs | 10 ++-- rust/operator-binary/src/catalog/mod.rs | 12 ++-- .../operator-binary/src/catalog/postgresql.rs | 3 +- rust/operator-binary/src/catalog/tpcds.rs | 4 +- rust/operator-binary/src/catalog/tpch.rs | 4 +- rust/operator-binary/src/controller.rs | 2 + .../src/controller/build/properties/mod.rs | 1 + .../src/controller/dereference.rs | 57 ++++++++++++------- .../src/controller/validate.rs | 11 ++-- rust/operator-binary/src/crd/mod.rs | 48 +++------------- 17 files changed, 94 insertions(+), 117 deletions(-) diff --git a/rust/operator-binary/src/catalog/black_hole.rs b/rust/operator-binary/src/catalog/black_hole.rs index 9377351de..82ab529b5 100644 --- a/rust/operator-binary/src/catalog/black_hole.rs +++ b/rust/operator-binary/src/catalog/black_hole.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::black_hole::BlackHoleConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for BlackHoleConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/catalog/commons.rs b/rust/operator-binary/src/catalog/commons.rs index fc3f4e265..c326eeb31 100644 --- a/rust/operator-binary/src/catalog/commons.rs +++ b/rust/operator-binary/src/catalog/commons.rs @@ -6,6 +6,7 @@ use stackable_operator::{ commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification}, crd::s3, k8s_openapi::api::core::v1::ConfigMap, + v2::types::kubernetes::NamespaceName, }; use super::{ @@ -14,7 +15,7 @@ use super::{ from_trino_catalog_error::{ ConfigureS3Snafu, FailedToGetDiscoveryConfigMapDataKeySnafu, FailedToGetDiscoveryConfigMapDataSnafu, FailedToGetDiscoveryConfigMapSnafu, - ObjectHasNoNamespaceSnafu, S3TlsNoVerificationNotSupportedSnafu, S3TlsRequiredSnafu, + S3TlsNoVerificationNotSupportedSnafu, S3TlsRequiredSnafu, }, }; use crate::{ @@ -31,17 +32,12 @@ impl ExtendCatalogConfig for MetastoreConnection { &self, catalog_config: &mut CatalogConfig, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, _trino_version: u16, ) -> Result<(), FromTrinoCatalogError> { let hive_cm: ConfigMap = client - .get( - &self.config_map, - catalog_namespace - .as_deref() - .context(ObjectHasNoNamespaceSnafu)?, - ) + .get(&self.config_map, catalog_namespace.as_ref()) .await .with_context(|_| FailedToGetDiscoveryConfigMapSnafu { catalog: catalog_name.to_string(), @@ -78,18 +74,13 @@ impl ExtendCatalogConfig for s3::v1alpha1::InlineConnectionOrReference { &self, catalog_config: &mut CatalogConfig, _catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result<(), FromTrinoCatalogError> { let s3 = self .clone() - .resolve( - client, - catalog_namespace - .as_deref() - .context(ObjectHasNoNamespaceSnafu)?, - ) + .resolve(client, catalog_namespace.as_ref()) .await .context(ConfigureS3Snafu)?; @@ -168,7 +159,7 @@ impl ExtendCatalogConfig for HdfsConnection { &self, catalog_config: &mut CatalogConfig, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result<(), FromTrinoCatalogError> { diff --git a/rust/operator-binary/src/catalog/config.rs b/rust/operator-binary/src/catalog/config.rs index 5104b772c..86c9c42b7 100644 --- a/rust/operator-binary/src/catalog/config.rs +++ b/rust/operator-binary/src/catalog/config.rs @@ -5,7 +5,8 @@ use stackable_operator::{ k8s_openapi::api::core::v1::{ ConfigMapKeySelector, EnvVar, EnvVarSource, SecretKeySelector, Volume, VolumeMount, }, - kube::{Resource, ResourceExt}, + kube::Resource, + v2::types::kubernetes::NamespaceName, }; use super::{FromTrinoCatalogError, ToCatalogConfig}; @@ -106,6 +107,7 @@ impl CatalogConfig { pub async fn from_catalog( catalog: &v1alpha1::TrinoCatalog, client: &Client, + catalog_namespace: &NamespaceName, trino_version: u16, ) -> Result { let catalog_name = catalog @@ -113,7 +115,6 @@ impl CatalogConfig { .name .clone() .ok_or(FromTrinoCatalogError::InvalidCatalogSpec)?; - let catalog_namespace = catalog.namespace(); let to_catalog_config: &dyn ToCatalogConfig = match &catalog.spec.connector { TrinoCatalogConnector::BlackHole(black_hole_connector) => black_hole_connector, diff --git a/rust/operator-binary/src/catalog/delta_lake.rs b/rust/operator-binary/src/catalog/delta_lake.rs index 30380db3c..a2c089e90 100644 --- a/rust/operator-binary/src/catalog/delta_lake.rs +++ b/rust/operator-binary/src/catalog/delta_lake.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{ExtendCatalogConfig, FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::delta_lake::DeltaLakeConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for DeltaLakeConnector { async fn to_catalog_config( &self, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result { @@ -28,7 +28,7 @@ impl ToCatalogConfig for DeltaLakeConnector { .extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -38,7 +38,7 @@ impl ToCatalogConfig for DeltaLakeConnector { s3.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -49,7 +49,7 @@ impl ToCatalogConfig for DeltaLakeConnector { hdfs.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) diff --git a/rust/operator-binary/src/catalog/generic.rs b/rust/operator-binary/src/catalog/generic.rs index b7c42a86f..af3de842a 100644 --- a/rust/operator-binary/src/catalog/generic.rs +++ b/rust/operator-binary/src/catalog/generic.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::generic::{GenericConnector, Property}; @@ -9,7 +9,7 @@ impl ToCatalogConfig for GenericConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/catalog/google_sheet.rs b/rust/operator-binary/src/catalog/google_sheet.rs index 7d85cc9b5..90396be22 100644 --- a/rust/operator-binary/src/catalog/google_sheet.rs +++ b/rust/operator-binary/src/catalog/google_sheet.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use stackable_operator::{ builder::pod::volume::{VolumeBuilder, VolumeMountBuilder}, client::Client, + v2::types::kubernetes::NamespaceName, }; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; @@ -14,7 +15,7 @@ impl ToCatalogConfig for GoogleSheetConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/catalog/hive.rs b/rust/operator-binary/src/catalog/hive.rs index aa7e72656..96ffdf19e 100644 --- a/rust/operator-binary/src/catalog/hive.rs +++ b/rust/operator-binary/src/catalog/hive.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{ExtendCatalogConfig, FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::hive::HiveConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for HiveConnector { async fn to_catalog_config( &self, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result { @@ -28,7 +28,7 @@ impl ToCatalogConfig for HiveConnector { .extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -38,7 +38,7 @@ impl ToCatalogConfig for HiveConnector { s3.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -49,7 +49,7 @@ impl ToCatalogConfig for HiveConnector { hdfs.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) diff --git a/rust/operator-binary/src/catalog/iceberg.rs b/rust/operator-binary/src/catalog/iceberg.rs index 4ab4498a9..65b6f41f3 100644 --- a/rust/operator-binary/src/catalog/iceberg.rs +++ b/rust/operator-binary/src/catalog/iceberg.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{ExtendCatalogConfig, FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::iceberg::IcebergConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for IcebergConnector { async fn to_catalog_config( &self, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result { @@ -29,7 +29,7 @@ impl ToCatalogConfig for IcebergConnector { .extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -40,7 +40,7 @@ impl ToCatalogConfig for IcebergConnector { s3.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) @@ -51,7 +51,7 @@ impl ToCatalogConfig for IcebergConnector { hdfs.extend_catalog_config( &mut config, catalog_name, - catalog_namespace.clone(), + catalog_namespace, client, trino_version, ) diff --git a/rust/operator-binary/src/catalog/mod.rs b/rust/operator-binary/src/catalog/mod.rs index 9abc549a6..274ec9727 100644 --- a/rust/operator-binary/src/catalog/mod.rs +++ b/rust/operator-binary/src/catalog/mod.rs @@ -12,16 +12,16 @@ pub mod tpch; use async_trait::async_trait; use snafu::Snafu; -use stackable_operator::{client::Client, commons::tls_verification::TlsClientDetailsError}; +use stackable_operator::{ + client::Client, commons::tls_verification::TlsClientDetailsError, + v2::types::kubernetes::NamespaceName, +}; use self::config::CatalogConfig; #[derive(Debug, Snafu)] #[snafu(module)] pub enum FromTrinoCatalogError { - #[snafu(display("object has no namespace"))] - ObjectHasNoNamespace, - #[snafu(display("failed to configure S3 connection"))] ConfigureS3 { source: stackable_operator::crd::s3::v1alpha1::ConnectionError, @@ -74,7 +74,7 @@ pub trait ToCatalogConfig { async fn to_catalog_config( &self, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result; @@ -86,7 +86,7 @@ pub trait ExtendCatalogConfig { &self, catalog_config: &mut CatalogConfig, catalog_name: &str, - catalog_namespace: Option, + catalog_namespace: &NamespaceName, client: &Client, trino_version: u16, ) -> Result<(), FromTrinoCatalogError>; diff --git a/rust/operator-binary/src/catalog/postgresql.rs b/rust/operator-binary/src/catalog/postgresql.rs index e5731641b..65ef335fb 100644 --- a/rust/operator-binary/src/catalog/postgresql.rs +++ b/rust/operator-binary/src/catalog/postgresql.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use snafu::ResultExt; use stackable_operator::{ client::Client, database_connections::drivers::jdbc::JdbcDatabaseConnection, + v2::types::kubernetes::NamespaceName, }; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; @@ -17,7 +18,7 @@ impl ToCatalogConfig for PostgresqlConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/catalog/tpcds.rs b/rust/operator-binary/src/catalog/tpcds.rs index 8eb65512c..2da5ceb7e 100644 --- a/rust/operator-binary/src/catalog/tpcds.rs +++ b/rust/operator-binary/src/catalog/tpcds.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::tpcds::TpcdsConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for TpcdsConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/catalog/tpch.rs b/rust/operator-binary/src/catalog/tpch.rs index dadc07a6c..e355de3cd 100644 --- a/rust/operator-binary/src/catalog/tpch.rs +++ b/rust/operator-binary/src/catalog/tpch.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use stackable_operator::client::Client; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use super::{FromTrinoCatalogError, ToCatalogConfig, config::CatalogConfig}; use crate::crd::catalog::tpch::TpchConnector; @@ -11,7 +11,7 @@ impl ToCatalogConfig for TpchConnector { async fn to_catalog_config( &self, catalog_name: &str, - _catalog_namespace: Option, + _catalog_namespace: &NamespaceName, _client: &Client, _trino_version: u16, ) -> Result { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 1fd2871fe..3a168a27b 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1385,6 +1385,7 @@ mod tests { }); let derefs = DereferencedObjects { + namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), @@ -1625,6 +1626,7 @@ mod tests { serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let derefs = DereferencedObjects { + namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 79bbdd5b9..290fcaf8f 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -82,6 +82,7 @@ pub(crate) mod test_support { pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); let derefs = DereferencedObjects { + namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index 81fed2d88..9e9d0eced 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -8,8 +8,12 @@ use std::{num::ParseIntError, str::FromStr}; -use snafu::{OptionExt, ResultExt, Snafu}; -use stackable_operator::{client::Client, kube::runtime::reflector::ObjectRef}; +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + client::Client, + kube::runtime::reflector::ObjectRef, + v2::{controller_utils::get_namespace, types::kubernetes::NamespaceName}, +}; use crate::{ authorization::opa::TrinoOpaConfig, @@ -26,8 +30,10 @@ use crate::{ #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("object defines no namespace"))] - ObjectHasNoNamespace, + #[snafu(display("failed to get namespace"))] + GetNamespace { + source: stackable_operator::v2::controller_utils::Error, + }, #[snafu(display("failed to retrieve AuthenticationClass"))] AuthenticationClassRetrieval { @@ -70,6 +76,9 @@ type Result = std::result::Result; /// Kubernetes objects referenced from the TrinoCluster spec, already fetched (and, for now, partly /// validated by the existing helper functions). pub struct DereferencedObjects { + // Catalogs are dereferenced and require the namespace, so we check + // or validate the namespace here once and pass it to validate. + pub namespace: NamespaceName, pub resolved_authentication_classes: Vec, pub catalog_definitions: Vec, pub catalogs: Vec, @@ -83,11 +92,7 @@ pub async fn dereference( client: &Client, trino: &v1alpha1::TrinoCluster, ) -> Result { - let namespace = trino - .metadata - .namespace - .as_deref() - .context(ObjectHasNoNamespaceSnafu)?; + let namespace = get_namespace(trino).context(GetNamespaceSnafu)?; let resolved_authentication_classes = resolve_authentication_classes(client, trino.get_authentication()) @@ -96,7 +101,7 @@ pub async fn dereference( let catalog_definitions = client .list_with_label_selector::( - namespace, + namespace.as_ref(), &trino.spec.cluster_config.catalog_label_selector, ) .await @@ -110,11 +115,12 @@ pub async fn dereference( let mut catalogs = Vec::with_capacity(catalog_definitions.len()); for catalog in &catalog_definitions { let catalog_ref = ObjectRef::from_obj(catalog); - let catalog_config = CatalogConfig::from_catalog(catalog, client, product_version) - .await - .context(ParseCatalogSnafu { - catalog: catalog_ref, - })?; + let catalog_config = + CatalogConfig::from_catalog(catalog, client, &namespace, product_version) + .await + .context(ParseCatalogSnafu { + catalog: catalog_ref, + })?; catalogs.push(catalog_config); } @@ -129,23 +135,32 @@ pub async fn dereference( let resolved_fte_config = match trino.spec.cluster_config.fault_tolerant_execution.as_ref() { Some(fte_config) => Some( - ResolvedFaultTolerantExecutionConfig::from_config(fte_config, Some(client), namespace) - .await - .context(FaultTolerantExecutionSnafu)?, + ResolvedFaultTolerantExecutionConfig::from_config( + fte_config, + Some(client), + namespace.as_ref(), + ) + .await + .context(FaultTolerantExecutionSnafu)?, ), None => None, }; let resolved_client_protocol_config = match trino.spec.cluster_config.client_protocol.as_ref() { Some(spooling_config) => Some( - ResolvedClientProtocolConfig::from_config(spooling_config, Some(client), namespace) - .await - .context(ClientProtocolConfigurationSnafu)?, + ResolvedClientProtocolConfig::from_config( + spooling_config, + Some(client), + namespace.as_ref(), + ) + .await + .context(ClientProtocolConfigurationSnafu)?, ), None => None, }; Ok(DereferencedObjects { + namespace, resolved_authentication_classes, catalog_definitions, catalogs, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 7e71df48a..7b7cdbdbc 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -65,9 +65,6 @@ pub enum Error { role: String, }, - #[snafu(display("failed to enumerate coordinator pods"))] - CoordinatorPods { source: crate::crd::Error }, - #[snafu(display("failed to validate config fragment"))] InvalidConfigFragment { source: stackable_operator::config::fragment::ValidationError, @@ -118,6 +115,8 @@ pub fn validate( dereferenced_objects: &DereferencedObjects, operator_environment: &OperatorEnvironmentOptions, ) -> Result { + let namespace = dereferenced_objects.namespace.clone(); + let image = trino .spec .image @@ -190,10 +189,7 @@ pub fn validate( authorization: dereferenced_objects.trino_opa_config.clone(), fault_tolerant_execution: dereferenced_objects.resolved_fte_config.clone(), client_protocol: dereferenced_objects.resolved_client_protocol_config.clone(), - coordinator_pod_refs: trino - .coordinator_pods() - .context(CoordinatorPodsSnafu)? - .collect(), + coordinator_pod_refs: trino.coordinator_pods(&namespace).collect(), catalogs: dereferenced_objects.catalogs.clone(), }; @@ -237,6 +233,7 @@ mod tests { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(MINIMAL_TRINO_YAML).expect("invalid test input"); let derefs = DereferencedObjects { + namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 6741b49ce..6a22095e6 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -37,8 +37,7 @@ use stackable_operator::{ schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, - utils::cluster_info::KubernetesClusterInfo, - v2::config_overrides::KeyValueConfigOverrides, + v2::{config_overrides::KeyValueConfigOverrides, types::kubernetes::NamespaceName}, versioned::versioned, }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; @@ -108,12 +107,6 @@ pub(crate) fn quantity_to_trino_bytes( #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("object has no namespace associated"))] - NoNamespace, - - #[snafu(display("object has no names"))] - NoName, - #[snafu(display("unknown role {role}. Should be one of {roles:?}"))] UnknownTrinoRole { source: strum::ParseError, @@ -547,33 +540,6 @@ impl v1alpha1::TrinoConfig { } impl v1alpha1::TrinoCluster { - /// Returns the name of the cluster and raises an Error if the name is not set. - pub fn name_r(&self) -> Result { - self.metadata.name.to_owned().context(NoNameSnafu) - } - - /// Returns the namespace of the cluster and raises an Error if the name is not set. - pub fn namespace_r(&self) -> Result { - self.metadata.namespace.to_owned().context(NoNamespaceSnafu) - } - - pub fn role_service_name(&self, role: &TrinoRole) -> Result { - Ok(format!("{}-{}", self.name_r()?, role)) - } - - pub fn role_service_fqdn( - &self, - role: &TrinoRole, - cluster_info: &KubernetesClusterInfo, - ) -> Result { - Ok(format!( - "{role_service_name}.{namespace}.svc.{cluster_domain}", - role_service_name = self.role_service_name(role)?, - namespace = self.namespace_r()?, - cluster_domain = cluster_info.cluster_domain - )) - } - /// Returns a reference to the role. Raises an error if the role is not defined. pub fn role(&self, role_variant: &TrinoRole) -> Result { match role_variant { @@ -658,10 +624,12 @@ impl v1alpha1::TrinoCluster { /// /// We try to predict the pods here rather than looking at the current cluster state in order to /// avoid instance churn. - pub fn coordinator_pods(&self) -> Result + '_, Error> { - let ns = self.metadata.namespace.clone().context(NoNamespaceSnafu)?; - Ok(self - .spec + pub fn coordinator_pods( + &self, + namespace: &NamespaceName, + ) -> impl Iterator + '_ { + let ns = namespace.to_string(); + self.spec .coordinators .iter() .flat_map(|role| &role.role_groups) @@ -679,7 +647,7 @@ impl v1alpha1::TrinoCluster { role_group = role_group_ref.object_name() ), }) - })) + }) } /// Returns user provided authentication settings From 0055c8a8485ab38a78d1472cec0fbb86ecc217b3 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 9 Jun 2026 11:54:25 +0200 Subject: [PATCH 16/65] refactor: use option free properties writer --- Cargo.lock | 83 +++++++++--------- Cargo.nix | 87 +++++++++---------- rust/operator-binary/src/authorization/opa.rs | 14 +-- rust/operator-binary/src/controller.rs | 4 +- .../src/controller/build/config_map.rs | 24 ++--- .../properties/access_control_properties.rs | 2 +- .../src/controller/build/properties/mod.rs | 43 +++------ 7 files changed, 116 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8511d407..d50d22ae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,9 +265,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -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", @@ -1086,9 +1086,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1453,13 +1453,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1515,7 +1514,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "darling", "regex", @@ -1707,9 +1706,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "matchers" @@ -2188,9 +2187,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -2198,9 +2197,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools", @@ -2211,9 +2210,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] @@ -2894,7 +2893,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "const-oid", "ecdsa", @@ -2918,7 +2917,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "base64", "clap", @@ -2962,7 +2961,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "darling", "proc-macro2", @@ -2973,7 +2972,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "jiff", "k8s-openapi", @@ -2990,7 +2989,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "axum", "clap", @@ -3036,7 +3035,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "kube", "schemars", @@ -3050,7 +3049,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "convert_case", "convert_case_extras", @@ -3068,7 +3067,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#de69410331ea51a37ec91e511d0d2f33056b6032" dependencies = [ "arc-swap", "async-trait", @@ -3662,9 +3661,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "js-sys", "wasm-bindgen", @@ -3714,9 +3713,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -3727,9 +3726,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -3737,9 +3736,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3747,9 +3746,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -3760,18 +3759,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -3971,9 +3970,9 @@ checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", diff --git a/Cargo.nix b/Cargo.nix index 1db51076a..0d7f7aea0 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -866,9 +866,9 @@ rec { }; "bitflags" = rec { crateName = "bitflags"; - version = "2.11.1"; + version = "2.13.0"; edition = "2021"; - sha256 = "1cvqijg3rvwgis20a66vfdxannjsxfy5fgjqkaq3l13gyfcj4lf4"; + sha256 = "1y239gpvl061rfvav7jds8mjs42kmwi39is7yx5d1qw3hvp8nf5l"; authors = [ "The Rust Project Developers" ]; @@ -1012,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"; @@ -3401,9 +3401,9 @@ rec { }; "http" = rec { crateName = "http"; - version = "1.4.1"; + version = "1.4.2"; edition = "2021"; - sha256 = "1l7k2ia57z3q7q3ka497krzps795kd3fymm2k12lr623y4nldrwb"; + sha256 = "09b4p8fiivkg7wm0b59fyrn1jkm7px298ci7zb9igz6n647gaw39"; authors = [ "Alex Crichton " "Carl Lerche " @@ -4618,9 +4618,9 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.99"; + version = "0.3.100"; edition = "2021"; - sha256 = "04azrzsz91gr5s3z0ij36lz0kj9ry4lw3jz0mmbiwb251rsc8aql"; + sha256 = "0qi1wjakyw2rx9wwprcfx77g3lvn1b8n6yvfhj2pgym4swh5y0pj"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" @@ -4637,11 +4637,6 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } - { - name = "once_cell"; - packageId = "once_cell"; - usesDefaultFeatures = false; - } { name = "wasm-bindgen"; packageId = "wasm-bindgen"; @@ -4831,7 +4826,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "k8s_version"; @@ -5639,9 +5634,9 @@ rec { }; "log" = rec { crateName = "log"; - version = "0.4.30"; + version = "0.4.32"; edition = "2021"; - sha256 = "1rd6sw3gv9hb93464w7x3sip99zf8sjagm662r2ckg14b1lcavk1"; + sha256 = "0fmdg0cxig7i4fwf1sw7fmg4d1gdbfzniawcfpwydy1q7320fgwm"; authors = [ "The Rust Project Developers" ]; @@ -7196,9 +7191,9 @@ rec { }; "prost" = rec { crateName = "prost"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "0s057z9nzggzy7x4bbsiar852hg7zb81f4z4phcdb0ig99971snj"; + sha256 = "1qas5v5rap45f43v3ja0jngxrrafrkcwl0iw5a3ld1pz2rscd2jj"; authors = [ "Dan Burkert " "Lucio Franco " @@ -7225,9 +7220,9 @@ rec { }; "prost-derive" = rec { crateName = "prost-derive"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "02zvva6kb0pfvlyc4nac6gd37ncjrs8jq5scxcq4nbqkc8wh5ii7"; + sha256 = "1pqa77d7da5pf6ba3kjj7510m5cynz6902ax01ckvr0pfrgv4w5m"; procMacro = true; libName = "prost_derive"; authors = [ @@ -7263,9 +7258,9 @@ rec { }; "prost-types" = rec { crateName = "prost-types"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "1mrxrciryfgi6a0vmrgyj3g27r9hdhlgwkq71cgv3icbvg5w94c9"; + sha256 = "02ivjvc4cwl5bfgjs3l00hwlrk74z8zlg1xcgx60bww8fvf6fjgr"; libName = "prost_types"; authors = [ "Dan Burkert " @@ -9514,7 +9509,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_certs"; @@ -9617,7 +9612,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_operator"; @@ -9811,7 +9806,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; @@ -9846,7 +9841,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_shared"; @@ -9927,7 +9922,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_telemetry"; @@ -10134,7 +10129,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_versioned"; @@ -10184,7 +10179,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; procMacro = true; @@ -10252,7 +10247,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; + rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; }; libName = "stackable_webhook"; @@ -12299,9 +12294,9 @@ rec { }; "uuid" = rec { crateName = "uuid"; - version = "1.23.2"; + version = "1.23.3"; edition = "2021"; - sha256 = "1xy942s4z0bi8p3441wvd4ry3hx6ry1c7s6fgrr38462xqybhn6j"; + sha256 = "1drddl03gi12vl1s3l2h371dw39plhn9wappp00v707g7h96nk8l"; authors = [ "Ashley Mannix" "Dylan DPC" @@ -12444,9 +12439,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.122"; + version = "0.2.123"; edition = "2021"; - sha256 = "02flix96brsb2r1i3grnikii302iqpdm337kl3xv5lklz5v4bl1y"; + sha256 = "0qqmx07r597gm8lbz8qngvv0phwvpzzyfh3nl84nz9qr1jqs8m52"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12495,9 +12490,9 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.72"; + version = "0.4.73"; edition = "2021"; - sha256 = "03qb24gfr072rk8hb69glfdc8yhqqqq2rhy3j5i0ps8sk79dnwwl"; + sha256 = "1bva12h8gdpqkp753czlxabs0s21lvgzm41brr4lhpdzz818fmjl"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" @@ -12523,9 +12518,9 @@ rec { }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.122"; + version = "0.2.123"; edition = "2021"; - sha256 = "1inyl55bvdifx7l60q9wl0ivmw7236jg7jqmcqpxhsx3knq52qci"; + sha256 = "1p50xdwmv543b52bc49vm5lcsgd9adpx647bdisg7ihfbg3hz914"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12547,9 +12542,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.122"; + version = "0.2.123"; edition = "2021"; - sha256 = "0pjw5kc2mbfz59agk5l21kh4hxzp94rygdvsnr4f3z6b5hv4g419"; + sha256 = "0nwqyc63byl7rp9nnv45av8h85fncfmxywkvy35d9qwwkfyk93wh"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12583,10 +12578,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.122"; + version = "0.2.123"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "0ds4mmfqvxwc5fp33hn0jblf0f6b4lghrd9mpkls66zic4n9p4ls"; + sha256 = "14lvjm3pzywm5c4962i6s5zmngic1knpggshnnxr9c97dihzgjvs"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12601,9 +12596,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.99"; + version = "0.3.100"; edition = "2021"; - sha256 = "0dilfvl9jnyhi4skl6cry9wc300r693j0w82jjbq8yy3rx0i8qkd"; + sha256 = "0sffbkrpgyi1402mv4wzp9av6ky6rnb1d2m2dpf87wi7yfn7223f"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -14026,9 +14021,9 @@ rec { }; "yoke" = rec { crateName = "yoke"; - version = "0.8.2"; + version = "0.8.3"; edition = "2021"; - sha256 = "1jprcs7a98a5whvfs6r3jvfh1nnfp6zyijl7y4ywmn88lzywbs5b"; + sha256 = "1xgyj6c2lxj2bp891ynmhws87c6z7yyv2li1v0ss9di40hxf57vh"; authors = [ "Manish Goregaokar " ]; diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 0ae42bd27..5a4f82e44 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -105,22 +105,22 @@ impl TrinoOpaConfig { }) } - pub fn as_config(&self) -> BTreeMap> { + pub fn as_config(&self) -> BTreeMap { let mut config = BTreeMap::from([ - ("access-control.name".to_string(), Some("opa".to_string())), + ("access-control.name".to_string(), "opa".to_string()), ( "opa.policy.uri".to_string(), - Some(self.non_batched_connection_string.clone()), + self.non_batched_connection_string.clone(), ), ( "opa.policy.batched-uri".to_string(), - Some(self.batched_connection_string.clone()), + self.batched_connection_string.clone(), ), ]); if let Some(row_filters_connection_string) = &self.row_filters_connection_string { config.insert( "opa.policy.row-filters-uri".to_string(), - Some(row_filters_connection_string.clone()), + row_filters_connection_string.clone(), ); } if let Some(batched_column_masking_connection_string) = @@ -128,13 +128,13 @@ impl TrinoOpaConfig { { config.insert( "opa.policy.batch-column-masking-uri".to_string(), - Some(batched_column_masking_connection_string.clone()), + batched_column_masking_connection_string.clone(), ); } if self.allow_permission_management_operations { config.insert( "opa.allow-permission-management-operations".to_string(), - Some("true".to_string()), + "true".to_string(), ); } config diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 3a168a27b..68f6f225f 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -62,7 +62,7 @@ mod build; mod dereference; mod validate; -use build::properties::render_java_properties; +use stackable_operator::v2::config_file_writer::to_java_properties_string; pub use validate::{TrinoRoleGroupConfig, ValidatedCluster}; use crate::{ @@ -557,7 +557,7 @@ fn build_rolegroup_catalog_config_map( .map(|catalog| { Ok(( format!("{}.properties", catalog.name), - render_java_properties(catalog.properties.clone()) + to_java_properties_string(catalog.properties.iter()) .context(FailedToWriteJavaPropertiesSnafu)?, )) }) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index f1ed7fb11..328fb7f05 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -10,6 +10,7 @@ use stackable_operator::{ product_logging, role_utils::RoleGroupRef, utils::cluster_info::KubernetesClusterInfo, + v2::config_file_writer::to_java_properties_string, }; use crate::{ @@ -19,7 +20,7 @@ use crate::{ build::properties::{ ConfigFileName, access_control_properties, config_properties, exchange_manager_properties, log_properties, logging::get_vector_toml, node_properties, - render_java_properties, security_properties, spooling_manager_properties, + security_properties, spooling_manager_properties, }, }, crd::{TrinoRole, v1alpha1}, @@ -93,9 +94,10 @@ pub fn build_rolegroup_config_map( // Auth files (e.g. password-authenticator file contents) — inserted FIRST // to match the legacy precedence. for (file_name, props) in cluster.cluster_config.authentication.config_files(role) { - let rendered = render_java_properties(props).with_context(|_| WritePropertiesSnafu { - file: file_name.clone(), - })?; + let rendered = + to_java_properties_string(props.iter()).with_context(|_| WritePropertiesSnafu { + file: file_name.clone(), + })?; data.insert(file_name, rendered); } @@ -104,7 +106,7 @@ pub fn build_rolegroup_config_map( .context(BuildConfigPropertiesSnafu)?; data.insert( ConfigFileName::Config.to_string(), - render_java_properties(cfg).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(cfg.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Config.to_string(), })?, ); @@ -113,7 +115,7 @@ pub fn build_rolegroup_config_map( let node = node_properties::build(cluster, rg); data.insert( ConfigFileName::Node.to_string(), - render_java_properties(node).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(node.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Node.to_string(), })?, ); @@ -123,7 +125,7 @@ pub fn build_rolegroup_config_map( if !log.is_empty() { data.insert( ConfigFileName::Log.to_string(), - render_java_properties(log).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(log.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Log.to_string(), })?, ); @@ -133,7 +135,7 @@ pub fn build_rolegroup_config_map( let sec = security_properties::build(rg); data.insert( ConfigFileName::Security.to_string(), - render_java_properties(sec).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(sec.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::Security.to_string(), })?, ); @@ -143,7 +145,7 @@ pub fn build_rolegroup_config_map( if !ac.is_empty() { data.insert( ConfigFileName::AccessControl.to_string(), - render_java_properties(ac).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(ac.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::AccessControl.to_string(), })?, ); @@ -154,7 +156,7 @@ pub fn build_rolegroup_config_map( if !em.is_empty() { data.insert( ConfigFileName::ExchangeManager.to_string(), - render_java_properties(em).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(em.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::ExchangeManager.to_string(), })?, ); @@ -165,7 +167,7 @@ pub fn build_rolegroup_config_map( if !sm.is_empty() { data.insert( ConfigFileName::SpoolingManager.to_string(), - render_java_properties(sm).with_context(|_| WritePropertiesSnafu { + to_java_properties_string(sm.iter()).with_context(|_| WritePropertiesSnafu { file: ConfigFileName::SpoolingManager.to_string(), })?, ); diff --git a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs index 47b475a05..e6444fcf4 100644 --- a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs @@ -14,7 +14,7 @@ pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap< // 1. No defaults. // 2. Automatic OPA config when configured. if let Some(opa) = &cluster.cluster_config.authorization { - props.extend(super::defined_entries(opa.as_config())); + props.extend(opa.as_config()); } // 3. No merged_config contribution. diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 290fcaf8f..abcd330eb 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -3,14 +3,9 @@ //! Each `.rs` module produces the rendered key/value pairs for one //! Trino config file. The shared //! [`stackable_operator::v2::config_file_writer`] serializes the map to the -//! Java-properties on-wire format (see [`to_java_properties_string`]). +//! Java-properties on-wire format. -use std::collections::BTreeMap; - -use stackable_operator::v2::{ - config_file_writer::{PropertiesWriterError, to_java_properties_string}, - config_overrides::KeyValueConfigOverrides, -}; +use stackable_operator::v2::config_overrides::KeyValueConfigOverrides; pub mod access_control_properties; pub mod config_properties; @@ -40,34 +35,15 @@ pub enum ConfigFileName { SpoolingManager, } -/// Render a `key -> value` properties map to the Java `.properties` on-wire format. -/// -/// The upstream [`to_java_properties_string`] consumes an iterator of -/// `(&String, &Option)`, so we lift the plain `String` values into -/// `Some(..)` first. -pub(crate) fn render_java_properties( - props: BTreeMap, -) -> Result { - let props: BTreeMap> = - props.into_iter().map(|(k, v)| (k, Some(v))).collect(); - to_java_properties_string(props.iter()) -} - -/// Keep only the set (`Some`) entries of a `key -> optional value` map, as `(key, value)` pairs. -fn defined_entries( - entries: BTreeMap>, -) -> impl Iterator { - entries - .into_iter() - .filter_map(|(key, value)| value.map(|value| (key, value))) -} - /// Resolve user-provided [`KeyValueConfigOverrides`] into the key/value pairs to merge /// into a `.properties` file, dropping entries whose value is unset (`None`). fn resolved_overrides( overrides: KeyValueConfigOverrides, ) -> impl Iterator { - defined_entries(overrides.overrides) + overrides + .overrides + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) } #[cfg(test)] @@ -124,14 +100,17 @@ pub(crate) mod test_support { #[cfg(test)] mod tests { - use super::*; + use std::collections::BTreeMap; + + use stackable_operator::v2::config_file_writer::to_java_properties_string; fn render(pairs: &[(&str, &str)]) -> String { let props: BTreeMap = pairs .iter() .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); - render_java_properties(props).expect("rendering the test properties should succeed") + to_java_properties_string(props.iter()) + .expect("rendering the test properties should succeed") } /// The escape behaviours pinned by the kuttl smoke snapshot From c64742314b2c8dcd2f3aac7fb7e45865dab4b92d Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 9 Jun 2026 16:30:42 +0200 Subject: [PATCH 17/65] refactor: remove ownerrefs from builder functions --- rust/operator-binary/src/config/jvm.rs | 35 ++++- rust/operator-binary/src/controller.rs | 14 +- .../src/controller/build/config_map.rs | 26 ++-- .../src/controller/build/properties/mod.rs | 3 +- .../src/controller/dereference.rs | 8 +- .../src/controller/validate.rs | 132 ++++++++++++++++-- 6 files changed, 171 insertions(+), 47 deletions(-) diff --git a/rust/operator-binary/src/config/jvm.rs b/rust/operator-binary/src/config/jvm.rs index 76a62f5bf..d84c20bfc 100644 --- a/rust/operator-binary/src/config/jvm.rs +++ b/rust/operator-binary/src/config/jvm.rs @@ -8,7 +8,7 @@ use stackable_operator::{ use crate::crd::{ METRICS_PORT, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - TrinoRoleType, v1alpha1, + v1alpha1, }; const JVM_SECURITY_PROPERTIES: &str = "security.properties"; @@ -46,8 +46,8 @@ pub enum Error { pub fn jvm_config( product_version: u16, merged_config: &v1alpha1::TrinoConfig, - role: &TrinoRoleType, - role_group: &str, + role_jvm_argument_overrides: &JvmArgumentOverrides, + role_group_jvm_argument_overrides: &JvmArgumentOverrides, ) -> Result { let memory_unit = BinaryMultiple::Mebi; let heap_size = MemoryQuantity::try_from( @@ -93,8 +93,18 @@ pub fn jvm_config( jvm_args.push("# Arguments from jvmArgumentOverrides".to_owned()); let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args); - let merged_jvm_argument_overrides = role - .get_merged_jvm_argument_overrides(role_group, &operator_generated) + + // Merge order mirrors `Role::get_merged_jvm_argument_overrides`: + // 1. operator-generated args are layered on top of the role-level overrides, + // 2. the role-group-level overrides are applied last. + // Note that this is not a purely additive merge, hence the unusual order. + let mut from_role = role_jvm_argument_overrides.clone(); + from_role + .try_merge(&operator_generated) + .context(MergeJvmArgumentOverridesSnafu)?; + let mut merged_jvm_argument_overrides = role_group_jvm_argument_overrides.clone(); + merged_jvm_argument_overrides + .try_merge(&from_role) .context(MergeJvmArgumentOverridesSnafu)?; Ok(merged_jvm_argument_overrides @@ -264,13 +274,24 @@ mod tests { let merged_config = trino.merged_config(&role, &rolegroup_ref, &[]).unwrap(); let coordinators = trino.role(&role).unwrap(); + let role_jvm_argument_overrides = coordinators + .config + .product_specific_common_config + .jvm_argument_overrides + .clone(); + let role_group_jvm_argument_overrides = coordinators.role_groups["default"] + .config + .product_specific_common_config + .jvm_argument_overrides + .clone(); + let product_version = trino.spec.image.product_version(); jvm_config( u16::from_str(product_version).expect("trino version as u16"), &merged_config, - &coordinators, - "default", + &role_jvm_argument_overrides, + &role_group_jvm_argument_overrides, ) .unwrap() } diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 68f6f225f..4e4ef712b 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -307,6 +307,12 @@ pub async fn reconcile_trino( let validated_cluster = validate::validate(trino, &dereferenced_objects, &ctx.operator_environment) .context(ValidateClusterSnafu)?; + tracing::debug!( + trino.name = %validated_cluster.name, + trino.namespace = %validated_cluster.namespace, + trino.uid = %validated_cluster.uid, + "Validated TrinoCluster" + ); let mut cluster_resources = ClusterResources::new( APP_NAME, @@ -403,7 +409,6 @@ pub async fn reconcile_trino( &role_group_ref, &client.kubernetes_cluster_info, &role_group_service_recommended_labels, - trino, ) .with_context(|_| BuildRoleGroupConfigMapSnafu { rolegroup: role_group_ref.clone(), @@ -1337,7 +1342,7 @@ mod tests { serde_yaml::with::singleton_map_recursive::deserialize(deserializer) .expect("invalid test input"); trino.metadata.namespace = Some("default".to_owned()); - trino.metadata.uid = Some("42".to_owned()); + trino.metadata.uid = Some("e6ac237d-a6d4-43a1-8135-f36506110912".to_owned()); let cluster_info = KubernetesClusterInfo { cluster_domain: DomainName::try_from("cluster.local").unwrap(), @@ -1385,7 +1390,6 @@ mod tests { }); let derefs = DereferencedObjects { - namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), @@ -1422,7 +1426,6 @@ mod tests { &rolegroup_ref, &cluster_info, &recommended_labels, - &trino, ) .expect("build_rolegroup_config_map should succeed") } @@ -1598,7 +1601,7 @@ mod tests { metadata: name: trino namespace: default - uid: "42" + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" spec: image: productVersion: "479" @@ -1626,7 +1629,6 @@ mod tests { serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let derefs = DereferencedObjects { - namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 328fb7f05..790cc3728 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -54,11 +54,8 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to resolve the {role} role"))] - ReadRole { - source: crate::crd::Error, - role: String, - }, + #[snafu(display("missing JVM argument overrides for role {role}"))] + MissingRoleJvmArgumentOverrides { role: String }, #[snafu(display("failed to build jvm.config"))] BuildJvmConfig { source: crate::config::jvm::Error }, @@ -72,7 +69,6 @@ pub fn build_rolegroup_config_map( rolegroup_ref: &RoleGroupRef, cluster_info: &KubernetesClusterInfo, recommended_labels: &ObjectLabels<'_, v1alpha1::TrinoCluster>, - owner_target: &v1alpha1::TrinoCluster, ) -> Result { let role_group_configs = cluster @@ -174,14 +170,18 @@ pub fn build_rolegroup_config_map( } // 8. jvm.config. - let role_obj = owner_target.role(role).with_context(|_| ReadRoleSnafu { - role: role.to_string(), - })?; + let role_jvm_argument_overrides = + cluster + .role_jvm_argument_overrides + .get(role) + .with_context(|| MissingRoleJvmArgumentOverridesSnafu { + role: role.to_string(), + })?; let jvm_config = jvm::jvm_config( cluster.product_version, &rg.config, - &role_obj, - &rolegroup_ref.role_group, + role_jvm_argument_overrides, + &rg.product_specific_common_config.jvm_argument_overrides, ) .context(BuildJvmConfigSnafu)?; data.insert(JVM_CONFIG.to_string(), jvm_config); @@ -197,9 +197,9 @@ pub fn build_rolegroup_config_map( ConfigMapBuilder::new() .metadata( ObjectMetaBuilder::new() - .name_and_namespace(owner_target) .name(rolegroup_ref.object_name()) - .ownerreference_from_resource(owner_target, None, Some(true)) + .namespace(cluster.namespace.to_string()) + .ownerreference_from_resource(cluster, None, Some(true)) .context(MetadataSnafu)? .with_recommended_labels(recommended_labels) .context(MetadataSnafu)? diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index abcd330eb..d75948317 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -58,7 +58,6 @@ pub(crate) mod test_support { pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); let derefs = DereferencedObjects { - namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), @@ -81,7 +80,7 @@ pub(crate) mod test_support { metadata: name: simple-trino namespace: default - uid: "42" + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" spec: image: productVersion: "479" diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index 9e9d0eced..11f0a4a48 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -10,9 +10,7 @@ use std::{num::ParseIntError, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ - client::Client, - kube::runtime::reflector::ObjectRef, - v2::{controller_utils::get_namespace, types::kubernetes::NamespaceName}, + client::Client, kube::runtime::reflector::ObjectRef, v2::controller_utils::get_namespace, }; use crate::{ @@ -76,9 +74,6 @@ type Result = std::result::Result; /// Kubernetes objects referenced from the TrinoCluster spec, already fetched (and, for now, partly /// validated by the existing helper functions). pub struct DereferencedObjects { - // Catalogs are dereferenced and require the namespace, so we check - // or validate the namespace here once and pass it to validate. - pub namespace: NamespaceName, pub resolved_authentication_classes: Vec, pub catalog_definitions: Vec, pub catalogs: Vec, @@ -160,7 +155,6 @@ pub async fn dereference( }; Ok(DereferencedObjects { - namespace, resolved_authentication_classes, catalog_definitions, catalogs, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 7b7cdbdbc..38c7a7444 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -9,9 +9,15 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection::{self, ResolvedProductImage}, - kube::ResourceExt as _, - role_utils::{GenericRoleConfig, JavaCommonConfig}, - v2::types::operator::ClusterName, + kube::{Resource, ResourceExt as _, api::ObjectMeta}, + role_utils::{GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides}, + v2::{ + controller_utils::{get_cluster_name, get_namespace, get_uid}, + types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, + }, }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; @@ -37,9 +43,19 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("invalid cluster name"))] - InvalidClusterName { - source: stackable_operator::v2::macros::attributed_string_type::Error, + #[snafu(display("failed to get the cluster name"))] + GetClusterName { + source: stackable_operator::v2::controller_utils::Error, + }, + + #[snafu(display("failed to get the cluster namespace"))] + GetClusterNamespace { + source: stackable_operator::v2::controller_utils::Error, + }, + + #[snafu(display("failed to get the cluster UID"))] + GetClusterUid { + source: stackable_operator::v2::controller_utils::Error, }, #[snafu(display("unable to parse Trino version {product_version:?}"))] @@ -101,12 +117,85 @@ pub struct ValidatedClusterConfig { } /// The validated TrinoCluster. The output of the validate step. +#[derive(Clone, Debug)] pub struct ValidatedCluster { + /// Metadata mirroring the source [`v1alpha1::TrinoCluster`] (name, namespace and UID). + /// + /// Kept private and only exposed through the [`Resource`] implementation, so that a + /// `ValidatedCluster` can be used directly as the owner of generated objects (e.g. to set + /// owner references) without threading the raw `TrinoCluster` through the build step. + metadata: ObjectMeta, pub name: ClusterName, + pub namespace: NamespaceName, + pub uid: Uid, pub image: ResolvedProductImage, pub product_version: u16, pub cluster_config: ValidatedClusterConfig, pub role_group_configs: BTreeMap>, + /// Role-level JVM argument overrides per role. Required to render `jvm.config` in the build + /// step without access to the raw [`v1alpha1::TrinoCluster`] (the role-group level overrides + /// are carried by [`TrinoRoleGroupConfig::product_specific_common_config`]). + pub role_jvm_argument_overrides: BTreeMap, +} + +impl ValidatedCluster { + #[allow(clippy::too_many_arguments)] + pub fn new( + name: ClusterName, + namespace: NamespaceName, + uid: Uid, + image: ResolvedProductImage, + product_version: u16, + cluster_config: ValidatedClusterConfig, + role_group_configs: BTreeMap>, + role_jvm_argument_overrides: BTreeMap, + ) -> Self { + Self { + metadata: ObjectMeta { + name: Some(name.to_string()), + namespace: Some(namespace.to_string()), + uid: Some(uid.to_string()), + ..ObjectMeta::default() + }, + name, + namespace, + uid, + image, + product_version, + cluster_config, + role_group_configs, + role_jvm_argument_overrides, + } + } +} + +impl Resource for ValidatedCluster { + type DynamicType = ::DynamicType; + type Scope = ::Scope; + + fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::kind(dt) + } + + fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::group(dt) + } + + fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::version(dt) + } + + fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::plural(dt) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } } /// Validates the cluster spec and dereferenced inputs. @@ -115,7 +204,7 @@ pub fn validate( dereferenced_objects: &DereferencedObjects, operator_environment: &OperatorEnvironmentOptions, ) -> Result { - let namespace = dereferenced_objects.namespace.clone(); + let namespace = get_namespace(trino).context(GetClusterNamespaceSnafu)?; let image = trino .spec @@ -153,6 +242,8 @@ pub fn validate( let mut role_group_configs: BTreeMap> = BTreeMap::new(); + let mut role_jvm_argument_overrides: BTreeMap = + BTreeMap::new(); for trino_role in TrinoRole::iter() { let role = trino .role(&trino_role) @@ -176,6 +267,13 @@ pub fn validate( .context(InvalidConfigFragmentSnafu)?; groups.insert(rg_name.clone(), validated_rg); } + role_jvm_argument_overrides.insert( + trino_role.clone(), + role.config + .product_specific_common_config + .jvm_argument_overrides + .clone(), + ); role_group_configs.insert(trino_role, groups); } @@ -193,13 +291,19 @@ pub fn validate( catalogs: dereferenced_objects.catalogs.clone(), }; - Ok(ValidatedCluster { - name: ClusterName::from_str(&trino.name_any()).context(InvalidClusterNameSnafu)?, + let name = get_cluster_name(trino).context(GetClusterNameSnafu)?; + let uid = get_uid(trino).context(GetClusterUidSnafu)?; + + Ok(ValidatedCluster::new( + name, + namespace, + uid, image, product_version, cluster_config, role_group_configs, - }) + role_jvm_argument_overrides, + )) } #[cfg(test)] @@ -212,7 +316,7 @@ mod tests { metadata: name: simple-trino namespace: default - uid: "42" + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" spec: image: productVersion: "479" @@ -233,7 +337,6 @@ mod tests { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(MINIMAL_TRINO_YAML).expect("invalid test input"); let derefs = DereferencedObjects { - namespace: "default".parse().unwrap(), resolved_authentication_classes: Vec::new(), catalog_definitions: Vec::new(), catalogs: Vec::new(), @@ -250,6 +353,11 @@ mod tests { let validated = validate(&trino, &derefs, &operator_env).expect("validate should succeed"); assert_eq!(validated.name.to_string(), "simple-trino"); + assert_eq!(validated.namespace.to_string(), "default"); + assert_eq!( + validated.uid.to_string(), + "e6ac237d-a6d4-43a1-8135-f36506110912" + ); assert_eq!(validated.product_version, 479); assert!(!validated.cluster_config.authentication_enabled); assert!( From 6d3b17711100c6d1df67b6187e3254f77855dda8 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 9 Jun 2026 21:22:27 +0200 Subject: [PATCH 18/65] refactor: switch to EnvVarSet --- rust/operator-binary/src/controller.rs | 15 +++-- .../src/controller/validate.rs | 11 +-- .../src/framework/role_utils.rs | 67 +++++++++++-------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 4e4ef712b..165eb41ba 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -55,6 +55,7 @@ use stackable_operator::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, + v2::builder::pod::container::EnvVarSet, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -584,7 +585,7 @@ fn build_rolegroup_statefulset( trino_role: &TrinoRole, resolved_product_image: &ResolvedProductImage, role_group_ref: &RoleGroupRef, - env_overrides: &BTreeMap, + env_overrides: &EnvVarSet, merged_config: &v1alpha1::TrinoConfig, trino_authentication_config: &TrinoAuthenticationConfig, catalogs: &[CatalogConfig], @@ -668,11 +669,7 @@ fn build_rolegroup_statefulset( }); // Finally add the user defined envOverrides properties. - env.extend(env_overrides.iter().map(|(k, v)| EnvVar { - name: k.clone(), - value: Some(v.clone()), - ..EnvVar::default() - })); + env.extend(env_overrides.clone()); let requested_secret_lifetime = merged_config .requested_secret_lifetime @@ -1323,6 +1320,7 @@ mod tests { cli::OperatorEnvironmentOptions, commons::networking::DomainName, k8s_openapi::api::core::v1::ConfigMap, kube::runtime::reflector::ObjectRef, role_utils::RoleGroupRef, utils::cluster_info::KubernetesClusterInfo, + v2::builder::pod::container::EnvVarName, }; use super::*; @@ -1646,7 +1644,10 @@ mod tests { let env = &validated_cluster.role_group_configs[&TrinoRole::Coordinator]["default"].env_overrides; - let value = |name: &str| env.get(name).cloned(); + let value = |name: &str| { + env.get(&EnvVarName::from_str_unsafe(name)) + .and_then(|env_var| env_var.value.clone()) + }; assert_eq!(value("COMMON_VAR").as_deref(), Some("group-value")); assert_eq!(value("GROUP_VAR").as_deref(), Some("group-value")); assert_eq!(value("ROLE_VAR").as_deref(), Some("role-value")); diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 38c7a7444..086ce276e 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -81,9 +81,10 @@ pub enum Error { role: String, }, - #[snafu(display("failed to validate config fragment"))] - InvalidConfigFragment { - source: stackable_operator::config::fragment::ValidationError, + #[snafu(display("failed to resolve and merge config for role group {role_group}"))] + FailedToResolveConfig { + source: crate::framework::role_utils::Error, + role_group: String, }, } @@ -264,7 +265,9 @@ pub fn validate( GenericRoleConfig, v1alpha1::TrinoConfigOverrides, >(rg, &role, &default_config) - .context(InvalidConfigFragmentSnafu)?; + .with_context(|_| FailedToResolveConfigSnafu { + role_group: rg_name.clone(), + })?; groups.insert(rg_name.clone(), validated_rg); } role_jvm_argument_overrides.insert( diff --git a/rust/operator-binary/src/framework/role_utils.rs b/rust/operator-binary/src/framework/role_utils.rs index 2e5729a04..bc94b55bc 100644 --- a/rust/operator-binary/src/framework/role_utils.rs +++ b/rust/operator-binary/src/framework/role_utils.rs @@ -2,7 +2,6 @@ //! `smooth-operator` branch, with simplifications appropriate for trino-operator. //! //! Differences from upstream: -//! - `env_overrides` is `HashMap` instead of `EnvVarSet`. //! - No `cli_overrides_to_vec` helper, `ResourceNames`, or service-account helpers. //! - The `CommonConfig` (a.k.a. `product_specific_common_config`) does NOT need to //! implement `Merge`. Upstream Trino uses `JavaCommonConfig`, which intentionally @@ -15,9 +14,13 @@ //! Replace with `stackable_operator::v2::role_utils::*` once upstream publishes //! the module. -use std::collections::BTreeMap; +use std::{ + collections::{BTreeMap, HashMap}, + str::FromStr, +}; use serde::Serialize; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ config::{ fragment::{self, FromFragment}, @@ -26,19 +29,28 @@ use stackable_operator::{ k8s_openapi::{DeepMerge, api::core::v1::PodTemplateSpec}, role_utils::{Role, RoleGroup}, schemars::JsonSchema, + v2::builder::pod::container::{self, EnvVarName, EnvVarSet}, }; +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to validate the role group config"))] + ValidateConfig { source: fragment::ValidationError }, + + #[snafu(display("invalid environment variable override name"))] + ParseEnvVarName { source: container::Error }, +} + /// Trino-friendly view of a validated, merged `RoleGroup`. /// /// Mirrors `stackable_operator::v2::role_utils::RoleGroupConfig` on the -/// `smooth-operator` branch, with `env_overrides: BTreeMap` -/// instead of the upstream `EnvVarSet`. +/// `smooth-operator` branch. #[derive(Clone, Debug, PartialEq)] pub struct RoleGroupConfig { pub replicas: u16, pub config: Config, pub config_overrides: ConfigOverrides, - pub env_overrides: BTreeMap, + pub env_overrides: EnvVarSet, pub cli_overrides: BTreeMap, pub pod_overrides: PodTemplateSpec, pub product_specific_common_config: CommonConfig, @@ -59,10 +71,7 @@ pub fn with_validated_config, role: &Role, default_config: &Config, -) -> Result< - RoleGroupConfig, - fragment::ValidationError, -> +) -> Result, Error> where ValidatedConfig: FromFragment, CommonConfig: Clone + Default + JsonSchema + Serialize, @@ -70,7 +79,8 @@ where RoleConfig: Default + JsonSchema + Serialize, ConfigOverrides: Clone + Default + JsonSchema + Merge + Serialize, { - let validated_config = validate_config(role_group, role, default_config)?; + let validated_config = + validate_config(role_group, role, default_config).context(ValidateConfigSnafu)?; Ok(RoleGroupConfig { replicas: role_group.replicas.unwrap_or(1), config: validated_config, @@ -79,18 +89,9 @@ where role_group.config.config_overrides.clone(), ), env_overrides: merged_env_overrides( - role.config - .env_overrides - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - role_group - .config - .env_overrides - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ), + &role.config.env_overrides, + &role_group.config.env_overrides, + )?, cli_overrides: merged_cli_overrides( role.config.cli_overrides.clone(), role_group.config.cli_overrides.clone(), @@ -129,12 +130,22 @@ where } fn merged_env_overrides( - role_env_overrides: BTreeMap, - role_group_env_overrides: BTreeMap, -) -> BTreeMap { - let mut merged = role_env_overrides; - merged.extend(role_group_env_overrides); - merged + role_env_overrides: &HashMap, + role_group_env_overrides: &HashMap, +) -> Result { + // Process the role first, then the role group, so that role-group overrides win on key + // collisions (`EnvVarSet::with_value` overrides earlier entries with the same name). + let mut env_overrides = EnvVarSet::new(); + for (name, value) in role_env_overrides + .iter() + .chain(role_group_env_overrides.iter()) + { + env_overrides = env_overrides.with_value( + &EnvVarName::from_str(name).context(ParseEnvVarNameSnafu)?, + value.clone(), + ); + } + Ok(env_overrides) } fn merged_cli_overrides( From 39cf37f8c109aec3a7b42fd6a717b80bbec6e2b6 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 12:21:57 +0200 Subject: [PATCH 19/65] fix: missing parenthesis from main merge --- rust/operator-binary/src/controller.rs | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index d6c6c9e20..9f2ae2279 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -493,20 +493,21 @@ pub async fn reconcile_trino( ) .context(ListenerConfigurationSnafu)?; - cluster_resources - .add(client, role_group_listener) - .await - .context(ApplyGroupListenerSnafu)?; - } + cluster_resources + .add(client, role_group_listener) + .await + .context(ApplyGroupListenerSnafu)?; + } - let role_config = trino.generic_role_config(trino_role); - if let Some(GenericRoleConfig { - pod_disruption_budget: pdb, - }) = role_config - { - add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) - .await - .context(FailedToCreatePdbSnafu)?; + let role_config = trino.generic_role_config(trino_role); + if let Some(GenericRoleConfig { + pod_disruption_budget: pdb, + }) = role_config + { + add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) + .await + .context(FailedToCreatePdbSnafu)?; + } } } From b9ed0d2dce7c72e0ebdfcbed84c51f1dfc665e04 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 12:32:13 +0200 Subject: [PATCH 20/65] fix: bump stackable-operator for non optional KeyValueConfigOverrides --- Cargo.lock | 34 +-- Cargo.nix | 52 ++--- crate-hashes.json | 18 +- extra/crds.yaml | 196 +++--------------- .../properties/access_control_properties.rs | 4 +- .../build/properties/config_properties.rs | 4 +- .../properties/exchange_manager_properties.rs | 4 +- .../build/properties/log_properties.rs | 4 +- .../src/controller/build/properties/mod.rs | 13 -- .../build/properties/node_properties.rs | 4 +- .../build/properties/security_properties.rs | 4 +- .../properties/spooling_manager_properties.rs | 4 +- 12 files changed, 87 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d50d22ae4..41b6a2960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,7 +1514,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "regex", @@ -2321,9 +2321,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2344,9 +2344,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relative-path" @@ -2893,7 +2893,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "const-oid", "ecdsa", @@ -2917,7 +2917,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "base64", "clap", @@ -2961,7 +2961,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "proc-macro2", @@ -2972,7 +2972,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "jiff", "k8s-openapi", @@ -2989,7 +2989,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "axum", "clap", @@ -3035,7 +3035,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "kube", "schemars", @@ -3049,7 +3049,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "convert_case", "convert_case_extras", @@ -3067,7 +3067,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#de69410331ea51a37ec91e511d0d2f33056b6032" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "arc-swap", "async-trait", @@ -3993,18 +3993,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.nix b/Cargo.nix index 63b4be371..46eb91b45 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4826,8 +4826,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "k8s_version"; authors = [ @@ -7563,9 +7563,9 @@ rec { }; "regex" = rec { crateName = "regex"; - version = "1.12.3"; + version = "1.12.4"; edition = "2021"; - sha256 = "0xp2q0x7ybmpa5zlgaz00p8zswcirj9h8nry3rxxsdwi9fhm81z1"; + sha256 = "1fm6si2xpmhwqflabdqsakc0qkq718wx2ljl37nbj75fb5vjnagi"; authors = [ "The Rust Project Developers" "Andrew Gallant " @@ -7682,9 +7682,9 @@ rec { }; "regex-syntax" = rec { crateName = "regex-syntax"; - version = "0.8.10"; + version = "0.8.11"; edition = "2021"; - sha256 = "02jx311ka0daxxc7v45ikzhcl3iydjbbb0mdrpc1xgg8v7c7v2fw"; + sha256 = "1m25h5q2wp976fb9gc3dsc9l99svcvd5cri8lncb51c46ydgzxnn"; libName = "regex_syntax"; authors = [ "The Rust Project Developers" @@ -9509,8 +9509,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_certs"; authors = [ @@ -9612,8 +9612,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_operator"; authors = [ @@ -9806,8 +9806,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9841,8 +9841,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_shared"; authors = [ @@ -9922,8 +9922,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_telemetry"; authors = [ @@ -10129,8 +10129,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_versioned"; authors = [ @@ -10179,8 +10179,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10247,8 +10247,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "de69410331ea51a37ec91e511d0d2f33056b6032"; - sha256 = "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_webhook"; authors = [ @@ -14087,9 +14087,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.50"; + version = "0.8.52"; edition = "2021"; - sha256 = "1laahnfxs4qyfb1fdf5nbb2qfshi72b1hbi0ffp2zy2m1r7ms1iv"; + sha256 = "0gv563swc1yn3k8w3wjj07a8q293rkx99nfp3a25vzzmbycj446f"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -14123,9 +14123,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.50"; + version = "0.8.52"; edition = "2021"; - sha256 = "0fdnr9qslx1hbn2i9rsvy9s95mychfy2vj90ajsjm2basccinqqb"; + sha256 = "0c3rhsh4sd9kdym4z55zprybjkydy9y2gvw75d72aapcfa5z7rqs"; procMacro = true; libName = "zerocopy_derive"; authors = [ diff --git a/crate-hashes.json b/crate-hashes.json index cd03561e9..c9a6e6a9d 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": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "0idpq1xdkr94zrd95xsvrwkj3bvzbii9a7qmw23rn5w4yiwgmj96", + "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 749c38cfb..9da452905 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -1425,80 +1425,45 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -2065,80 +2030,45 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -2757,80 +2687,45 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: @@ -3392,80 +3287,45 @@ spec: properties: access-control.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 config.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 exchange-manager.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 log.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 node.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 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 spooling-manager.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 type: object envOverrides: diff --git a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs index e6444fcf4..dc0cef924 100644 --- a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs @@ -19,9 +19,7 @@ pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap< // 3. No merged_config contribution. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.access_control_properties.clone(), - )); + props.extend(rg.config_overrides.access_control_properties.clone()); props } diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 7538c3dc0..08dda5971 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -239,9 +239,7 @@ pub fn build( } // ---- 4. User overrides (highest precedence) ---- - props.extend(super::resolved_overrides( - rg.config_overrides.config_properties.clone(), - )); + props.extend(rg.config_overrides.config_properties.clone()); Ok(props) } diff --git a/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs index 018240fe2..3bc58123e 100644 --- a/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs @@ -19,9 +19,7 @@ pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap< // 3. No merged_config contribution. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.exchange_manager_properties.clone(), - )); + props.extend(rg.config_overrides.exchange_manager_properties.clone()); props } diff --git a/rust/operator-binary/src/controller/build/properties/log_properties.rs b/rust/operator-binary/src/controller/build/properties/log_properties.rs index f1e736158..8c85d1b6b 100644 --- a/rust/operator-binary/src/controller/build/properties/log_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/log_properties.rs @@ -19,9 +19,7 @@ pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { // 3. No merged_config contribution. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.log_properties.clone(), - )); + props.extend(rg.config_overrides.log_properties.clone()); props } diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index d75948317..a6a94f3a0 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -5,8 +5,6 @@ //! [`stackable_operator::v2::config_file_writer`] serializes the map to the //! Java-properties on-wire format. -use stackable_operator::v2::config_overrides::KeyValueConfigOverrides; - pub mod access_control_properties; pub mod config_properties; pub mod exchange_manager_properties; @@ -35,17 +33,6 @@ pub enum ConfigFileName { SpoolingManager, } -/// Resolve user-provided [`KeyValueConfigOverrides`] into the key/value pairs to merge -/// into a `.properties` file, dropping entries whose value is unset (`None`). -fn resolved_overrides( - overrides: KeyValueConfigOverrides, -) -> impl Iterator { - overrides - .overrides - .into_iter() - .filter_map(|(key, value)| value.map(|value| (key, value))) -} - #[cfg(test)] pub(crate) mod test_support { use stackable_operator::cli::OperatorEnvironmentOptions; diff --git a/rust/operator-binary/src/controller/build/properties/node_properties.rs b/rust/operator-binary/src/controller/build/properties/node_properties.rs index de66bb640..79bd0ec69 100644 --- a/rust/operator-binary/src/controller/build/properties/node_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/node_properties.rs @@ -21,9 +21,7 @@ pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap< // 3. No merged_config contribution for node.properties. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.node_properties.clone(), - )); + props.extend(rg.config_overrides.node_properties.clone()); props } diff --git a/rust/operator-binary/src/controller/build/properties/security_properties.rs b/rust/operator-binary/src/controller/build/properties/security_properties.rs index 95437b04f..ae02871bb 100644 --- a/rust/operator-binary/src/controller/build/properties/security_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/security_properties.rs @@ -29,9 +29,7 @@ pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { // 2. No automatic operator-injected values. // 3. No merged_config contribution. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.security_properties.clone(), - )); + props.extend(rg.config_overrides.security_properties.clone()); props } diff --git a/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs index 657368cde..75b7828f3 100644 --- a/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs @@ -19,9 +19,7 @@ pub fn build(cluster: &ValidatedCluster, rg: &TrinoRoleGroupConfig) -> BTreeMap< // 3. No merged_config contribution. // 4. User overrides (highest precedence). - props.extend(super::resolved_overrides( - rg.config_overrides.spooling_manager_properties.clone(), - )); + props.extend(rg.config_overrides.spooling_manager_properties.clone()); props } From 6865a0941476e89836401983fe20ff0da4ad4929 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 13:07:38 +0200 Subject: [PATCH 21/65] fix: move catalog configmap to builder module and use ValidatedCluster --- rust/operator-binary/src/controller.rs | 69 +++---------------- .../src/controller/build/config_map.rs | 37 ++++++++++ 2 files changed, 45 insertions(+), 61 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 9f2ae2279..a9e967084 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -5,7 +5,6 @@ use const_format::concatcp; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ - configmap::ConfigMapBuilder, meta::ObjectMetaBuilder, pod::{ PodBuilder, @@ -27,7 +26,7 @@ use stackable_operator::{ api::{ apps::v1::{StatefulSet, StatefulSetSpec}, core::v1::{ - ConfigMap, ConfigMapVolumeSource, ContainerPort, EnvVar, EnvVarSource, ExecAction, + ConfigMapVolumeSource, ContainerPort, EnvVar, EnvVarSource, ExecAction, HTTPGetAction, Probe, SecretKeySelector, Volume, }, }, @@ -63,7 +62,6 @@ mod build; mod dereference; mod validate; -use stackable_operator::v2::config_file_writer::to_java_properties_string; pub use validate::{TrinoRoleGroupConfig, ValidatedCluster}; use crate::{ @@ -130,12 +128,6 @@ pub enum Error { rolegroup: RoleGroupRef, }, - #[snafu(display("failed to build ConfigMap for {}", rolegroup))] - BuildRoleGroupConfig { - source: stackable_operator::builder::configmap::Error, - rolegroup: RoleGroupRef, - }, - #[snafu(display("failed to build ConfigMap for {}", rolegroup))] BuildRoleGroupConfigMap { source: build::config_map::Error, @@ -159,11 +151,6 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to format runtime properties"))] - FailedToWriteJavaProperties { - source: stackable_operator::v2::config_file_writer::PropertiesWriterError, - }, - #[snafu(display("internal operator failure: {source}"))] InternalOperatorFailure { source: crate::crd::Error }, @@ -415,12 +402,14 @@ pub async fn reconcile_trino( rolegroup: role_group_ref.clone(), })?; - let rg_catalog_configmap = build_rolegroup_catalog_config_map( - trino, - &validated_cluster.image, + let rg_catalog_configmap = build::config_map::build_rolegroup_catalog_config_map( + &validated_cluster, &role_group_ref, - &validated_cluster.cluster_config.catalogs, - )?; + &role_group_service_recommended_labels, + ) + .with_context(|_| BuildRoleGroupConfigMapSnafu { + rolegroup: role_group_ref.clone(), + })?; let rg_stateful_set = build_rolegroup_statefulset( trino, @@ -533,48 +522,6 @@ pub async fn reconcile_trino( Ok(Action::await_change()) } -/// The rolegroup catalog [`ConfigMap`] configures the rolegroup catalog based on the configuration -/// given by the administrator -fn build_rolegroup_catalog_config_map( - trino: &v1alpha1::TrinoCluster, - resolved_product_image: &ResolvedProductImage, - rolegroup_ref: &RoleGroupRef, - catalogs: &[CatalogConfig], -) -> Result { - ConfigMapBuilder::new() - .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(trino) - .name(format!("{}-catalog", rolegroup_ref.object_name())) - .ownerreference_from_resource(trino, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .context(MetadataBuildSnafu)? - .build(), - ) - .data( - catalogs - .iter() - .map(|catalog| { - Ok(( - format!("{}.properties", catalog.name), - to_java_properties_string(catalog.properties.iter()) - .context(FailedToWriteJavaPropertiesSnafu)?, - )) - }) - .collect::>()?, - ) - .build() - .with_context(|_| BuildRoleGroupConfigSnafu { - rolegroup: rolegroup_ref.clone(), - }) -} - /// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator. /// /// The [`Pod`](`stackable_operator::k8s_openapi::api::core::v1::Pod`)s are accessible through the diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 790cc3728..0ef90b418 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -211,3 +211,40 @@ pub fn build_rolegroup_config_map( rolegroup: rolegroup_ref.clone(), }) } + +/// The rolegroup catalog [`ConfigMap`] configures the rolegroup catalog based on the configuration +/// given by the administrator +pub fn build_rolegroup_catalog_config_map( + cluster: &ValidatedCluster, + rolegroup_ref: &RoleGroupRef, + recommended_labels: &ObjectLabels<'_, v1alpha1::TrinoCluster>, +) -> Result { + ConfigMapBuilder::new() + .metadata( + ObjectMetaBuilder::new() + .name(format!("{}-catalog", rolegroup_ref.object_name())) + .namespace(cluster.namespace.to_string()) + .ownerreference_from_resource(cluster, None, Some(true)) + .context(MetadataSnafu)? + .with_recommended_labels(recommended_labels) + .context(MetadataSnafu)? + .build(), + ) + .data( + cluster + .cluster_config + .catalogs + .iter() + .map(|catalog| { + let file = format!("{}.properties", catalog.name); + let rendered = to_java_properties_string(catalog.properties.iter()) + .with_context(|_| WritePropertiesSnafu { file: file.clone() })?; + Ok((file, rendered)) + }) + .collect::>()?, + ) + .build() + .with_context(|_| AssembleSnafu { + rolegroup: rolegroup_ref.clone(), + }) +} From 6154d032f22f88a4bb86212116c7d6ecda2976ae Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 13:48:09 +0200 Subject: [PATCH 22/65] fix: format change due to merge moved pdbs in a listener dependency an was never deployed for workers. --- rust/operator-binary/src/controller.rs | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index a9e967084..d7c3deba6 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -467,36 +467,36 @@ pub async fn reconcile_trino( ); } - if let Some(listener_class) = trino_role.listener_class_name(trino) { - if let Some(listener_group_name) = group_listener_name(trino, trino_role) { - let role_group_listener = build_group_listener( + if let Some(listener_class) = trino_role.listener_class_name(trino) + && let Some(listener_group_name) = group_listener_name(trino, trino_role) + { + let role_group_listener = build_group_listener( + trino, + build_recommended_labels( trino, - build_recommended_labels( - trino, - &validated_cluster.image.app_version_label_value, - &trino_role.to_string(), - "none", - ), - listener_class.to_string(), - listener_group_name, - ) - .context(ListenerConfigurationSnafu)?; + &validated_cluster.image.app_version_label_value, + &trino_role.to_string(), + "none", + ), + listener_class.to_string(), + listener_group_name, + ) + .context(ListenerConfigurationSnafu)?; - cluster_resources - .add(client, role_group_listener) - .await - .context(ApplyGroupListenerSnafu)?; - } + cluster_resources + .add(client, role_group_listener) + .await + .context(ApplyGroupListenerSnafu)?; + } - let role_config = trino.generic_role_config(trino_role); - if let Some(GenericRoleConfig { - pod_disruption_budget: pdb, - }) = role_config - { - add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) - .await - .context(FailedToCreatePdbSnafu)?; - } + let role_config = trino.generic_role_config(trino_role); + if let Some(GenericRoleConfig { + pod_disruption_budget: pdb, + }) = role_config + { + add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) + .await + .context(FailedToCreatePdbSnafu)?; } } From 1c0193e340ff601a5e7b21bd031e18e6e415fefe Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 11 Jun 2026 14:35:28 +0200 Subject: [PATCH 23/65] moved validated structs to controller/mod.rs, renamed controller -> trino_controller, pass through validated cluster --- .../src/authentication/password/file.rs | 4 +- rust/operator-binary/src/command.rs | 2 +- .../src/controller/build/config_map.rs | 4 +- rust/operator-binary/src/controller/mod.rs | 132 ++++++++++++++++++ .../src/controller/validate.rs | 124 +--------------- rust/operator-binary/src/main.rs | 9 +- rust/operator-binary/src/operations/pdb.rs | 2 +- rust/operator-binary/src/service.rs | 26 ++-- .../{controller.rs => trino_controller.rs} | 28 ++-- 9 files changed, 178 insertions(+), 153 deletions(-) create mode 100644 rust/operator-binary/src/controller/mod.rs rename rust/operator-binary/src/{controller.rs => trino_controller.rs} (99%) diff --git a/rust/operator-binary/src/authentication/password/file.rs b/rust/operator-binary/src/authentication/password/file.rs index 476e8cd74..cf2a250f4 100644 --- a/rust/operator-binary/src/authentication/password/file.rs +++ b/rust/operator-binary/src/authentication/password/file.rs @@ -17,7 +17,9 @@ use stackable_operator::{ utils::COMMON_BASH_TRAP_FUNCTIONS, }; -use crate::{authentication::password::PASSWORD_AUTHENTICATOR_NAME, controller::STACKABLE_LOG_DIR}; +use crate::{ + authentication::password::PASSWORD_AUTHENTICATOR_NAME, trino_controller::STACKABLE_LOG_DIR, +}; // mounts const PASSWORD_DB_VOLUME_NAME: &str = "users"; diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index dfcac270f..453f399cf 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -10,13 +10,13 @@ use crate::{ authentication::TrinoAuthenticationConfig, catalog::config::CatalogConfig, config::{client_protocol, fault_tolerant_execution}, - controller::{STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR}, crd::{ CONFIG_DIR_NAME, Container, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, TrinoRole, v1alpha1, }, + trino_controller::{STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR}, }; // TODO: replace with build::properties::ConfigFileName once command.rs moves under build/ diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 0ef90b418..4b7d09fec 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -68,7 +68,7 @@ pub fn build_rolegroup_config_map( role: &TrinoRole, rolegroup_ref: &RoleGroupRef, cluster_info: &KubernetesClusterInfo, - recommended_labels: &ObjectLabels<'_, v1alpha1::TrinoCluster>, + recommended_labels: &ObjectLabels<'_, ValidatedCluster>, ) -> Result { let role_group_configs = cluster @@ -217,7 +217,7 @@ pub fn build_rolegroup_config_map( pub fn build_rolegroup_catalog_config_map( cluster: &ValidatedCluster, rolegroup_ref: &RoleGroupRef, - recommended_labels: &ObjectLabels<'_, v1alpha1::TrinoCluster>, + recommended_labels: &ObjectLabels<'_, ValidatedCluster>, ) -> Result { ConfigMapBuilder::new() .metadata( diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs new file mode 100644 index 000000000..82fdc29aa --- /dev/null +++ b/rust/operator-binary/src/controller/mod.rs @@ -0,0 +1,132 @@ +//! Controller-level vocabulary: the [`ValidatedCluster`] type produced by the [`validate`] step +//! and consumed by the [`build`] steps, plus the `dereference` / `validate` / `build` sub-modules. + +use std::collections::BTreeMap; + +use stackable_operator::{ + commons::product_image_selection::ResolvedProductImage, + kube::{Resource, api::ObjectMeta}, + role_utils::JvmArgumentOverrides, + v2::types::{ + kubernetes::{NamespaceName, Uid}, + operator::ClusterName, + }, +}; + +use crate::{ + authentication::TrinoAuthenticationConfig, + authorization::opa::TrinoOpaConfig, + catalog::config::CatalogConfig, + config::{ + client_protocol::ResolvedClientProtocolConfig, + fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, + }, + crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, +}; + +pub(crate) mod build; +pub(crate) mod dereference; +pub(crate) mod validate; + +pub use validate::{RoleGroupName, TrinoRoleGroupConfig}; + +#[derive(Clone, Debug)] +pub struct ValidatedTls { + pub server: Option, + pub internal: Option, +} + +/// Cluster-wide settings, grouped to parallel `spec.clusterConfig` CRD. +#[derive(Clone, Debug)] +pub struct ValidatedClusterConfig { + pub tls: ValidatedTls, + pub authentication: TrinoAuthenticationConfig, + pub authentication_enabled: bool, + pub authorization: Option, + pub fault_tolerant_execution: Option, + pub client_protocol: Option, + pub coordinator_pod_refs: Vec, + pub catalogs: Vec, +} + +/// The validated TrinoCluster. The output of the validate step. +#[derive(Clone, Debug)] +pub struct ValidatedCluster { + /// Metadata mirroring the source [`v1alpha1::TrinoCluster`] (name, namespace and UID). + /// + /// Kept private and only exposed through the [`Resource`] implementation, so that a + /// `ValidatedCluster` can be used directly as the owner of generated objects (e.g. to set + /// owner references) without threading the raw `TrinoCluster` through the build step. + metadata: ObjectMeta, + pub name: ClusterName, + pub namespace: NamespaceName, + pub uid: Uid, + pub image: ResolvedProductImage, + pub product_version: u16, + pub cluster_config: ValidatedClusterConfig, + pub role_group_configs: BTreeMap>, + /// Role-level JVM argument overrides per role. Required to render `jvm.config` in the build + /// step without access to the raw [`v1alpha1::TrinoCluster`] (the role-group level overrides + /// are carried by [`TrinoRoleGroupConfig::product_specific_common_config`]). + pub role_jvm_argument_overrides: BTreeMap, +} + +impl ValidatedCluster { + #[allow(clippy::too_many_arguments)] + pub fn new( + name: ClusterName, + namespace: NamespaceName, + uid: Uid, + image: ResolvedProductImage, + product_version: u16, + cluster_config: ValidatedClusterConfig, + role_group_configs: BTreeMap>, + role_jvm_argument_overrides: BTreeMap, + ) -> Self { + Self { + metadata: ObjectMeta { + name: Some(name.to_string()), + namespace: Some(namespace.to_string()), + uid: Some(uid.to_string()), + ..ObjectMeta::default() + }, + name, + namespace, + uid, + image, + product_version, + cluster_config, + role_group_configs, + role_jvm_argument_overrides, + } + } +} + +impl Resource for ValidatedCluster { + type DynamicType = ::DynamicType; + type Scope = ::Scope; + + fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::kind(dt) + } + + fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::group(dt) + } + + fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::version(dt) + } + + fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha1::TrinoCluster::plural(dt) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 086ce276e..8545160fa 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -8,29 +8,18 @@ use std::{collections::BTreeMap, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, - commons::product_image_selection::{self, ResolvedProductImage}, - kube::{Resource, ResourceExt as _, api::ObjectMeta}, + commons::product_image_selection, + kube::ResourceExt as _, role_utils::{GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides}, - v2::{ - controller_utils::{get_cluster_name, get_namespace, get_uid}, - types::{ - kubernetes::{NamespaceName, Uid}, - operator::ClusterName, - }, - }, + v2::controller_utils::{get_cluster_name, get_namespace, get_uid}, }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; +use super::{ValidatedCluster, ValidatedClusterConfig, ValidatedTls}; use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, - authorization::opa::TrinoOpaConfig, - catalog::config::CatalogConfig, - config::{ - client_protocol::ResolvedClientProtocolConfig, - fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, - }, controller::dereference::DereferencedObjects, - crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, + crd::{TrinoRole, v1alpha1}, framework::role_utils::with_validated_config, }; @@ -98,107 +87,6 @@ pub type TrinoRoleGroupConfig = crate::framework::role_utils::RoleGroupConfig< v1alpha1::TrinoConfigOverrides, >; -#[derive(Clone, Debug)] -pub struct ValidatedTls { - pub server: Option, - pub internal: Option, -} - -/// Cluster-wide settings, grouped to parallel `spec.clusterConfig` CRD. -#[derive(Clone, Debug)] -pub struct ValidatedClusterConfig { - pub tls: ValidatedTls, - pub authentication: TrinoAuthenticationConfig, - pub authentication_enabled: bool, - pub authorization: Option, - pub fault_tolerant_execution: Option, - pub client_protocol: Option, - pub coordinator_pod_refs: Vec, - pub catalogs: Vec, -} - -/// The validated TrinoCluster. The output of the validate step. -#[derive(Clone, Debug)] -pub struct ValidatedCluster { - /// Metadata mirroring the source [`v1alpha1::TrinoCluster`] (name, namespace and UID). - /// - /// Kept private and only exposed through the [`Resource`] implementation, so that a - /// `ValidatedCluster` can be used directly as the owner of generated objects (e.g. to set - /// owner references) without threading the raw `TrinoCluster` through the build step. - metadata: ObjectMeta, - pub name: ClusterName, - pub namespace: NamespaceName, - pub uid: Uid, - pub image: ResolvedProductImage, - pub product_version: u16, - pub cluster_config: ValidatedClusterConfig, - pub role_group_configs: BTreeMap>, - /// Role-level JVM argument overrides per role. Required to render `jvm.config` in the build - /// step without access to the raw [`v1alpha1::TrinoCluster`] (the role-group level overrides - /// are carried by [`TrinoRoleGroupConfig::product_specific_common_config`]). - pub role_jvm_argument_overrides: BTreeMap, -} - -impl ValidatedCluster { - #[allow(clippy::too_many_arguments)] - pub fn new( - name: ClusterName, - namespace: NamespaceName, - uid: Uid, - image: ResolvedProductImage, - product_version: u16, - cluster_config: ValidatedClusterConfig, - role_group_configs: BTreeMap>, - role_jvm_argument_overrides: BTreeMap, - ) -> Self { - Self { - metadata: ObjectMeta { - name: Some(name.to_string()), - namespace: Some(namespace.to_string()), - uid: Some(uid.to_string()), - ..ObjectMeta::default() - }, - name, - namespace, - uid, - image, - product_version, - cluster_config, - role_group_configs, - role_jvm_argument_overrides, - } - } -} - -impl Resource for ValidatedCluster { - type DynamicType = ::DynamicType; - type Scope = ::Scope; - - fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha1::TrinoCluster::kind(dt) - } - - fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha1::TrinoCluster::group(dt) - } - - fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha1::TrinoCluster::version(dt) - } - - fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha1::TrinoCluster::plural(dt) - } - - fn meta(&self) -> &ObjectMeta { - &self.metadata - } - - fn meta_mut(&mut self) -> &mut ObjectMeta { - &mut self.metadata - } -} - /// Validates the cluster spec and dereferenced inputs. pub fn validate( trino: &v1alpha1::TrinoCluster, @@ -211,7 +99,7 @@ pub fn validate( .spec .image .resolve( - super::CONTAINER_IMAGE_BASE_NAME, + crate::trino_controller::CONTAINER_IMAGE_BASE_NAME, &operator_environment.image_repository, crate::built_info::PKG_VERSION, ) diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index bfb87c039..eb1e7be37 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -33,12 +33,12 @@ use stackable_operator::{ }; use crate::{ - controller::{FULL_CONTROLLER_NAME, OPERATOR_NAME}, crd::{ TrinoCluster, TrinoClusterVersion, catalog::{TrinoCatalog, TrinoCatalogVersion}, v1alpha1, }, + trino_controller::{FULL_CONTROLLER_NAME, OPERATOR_NAME}, webhooks::conversion::create_webhook_server, }; @@ -53,6 +53,7 @@ mod framework; mod listener; mod operations; mod service; +mod trino_controller; mod webhooks; mod built_info { @@ -195,9 +196,9 @@ async fn main() -> anyhow::Result<()> { ) .graceful_shutdown_on(sigterm_watcher.handle()) .run( - controller::reconcile_trino, - controller::error_policy, - Arc::new(controller::Ctx { + trino_controller::reconcile_trino, + trino_controller::error_policy, + Arc::new(trino_controller::Ctx { client: client.clone(), operator_environment, }), diff --git a/rust/operator-binary/src/operations/pdb.rs b/rust/operator-binary/src/operations/pdb.rs index b135cca96..245c80704 100644 --- a/rust/operator-binary/src/operations/pdb.rs +++ b/rust/operator-binary/src/operations/pdb.rs @@ -7,8 +7,8 @@ use stackable_operator::{ }; use crate::{ - controller::{CONTROLLER_NAME, OPERATOR_NAME}, crd::{APP_NAME, TrinoRole, v1alpha1}, + trino_controller::{CONTROLLER_NAME, OPERATOR_NAME}, }; #[derive(Snafu, Debug)] diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index b899eddc2..455c58f2e 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -8,7 +8,10 @@ use stackable_operator::{ role_utils::RoleGroupRef, }; -use crate::crd::{METRICS_PORT, METRICS_PORT_NAME, v1alpha1}; +use crate::{ + controller::ValidatedCluster, + crd::{METRICS_PORT, METRICS_PORT_NAME, v1alpha1}, +}; #[derive(Snafu, Debug)] pub enum Error { @@ -26,16 +29,17 @@ pub enum Error { /// The rolegroup headless [`Service`] is a service that allows direct access to the instances of a certain rolegroup /// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing. pub fn build_rolegroup_headless_service( - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, role_group_ref: &RoleGroupRef, - object_labels: &ObjectLabels, + object_labels: &ObjectLabels, selector: BTreeMap, + ports: Vec, ) -> Result { Ok(Service { metadata: ObjectMetaBuilder::new() - .name_and_namespace(trino) + .name_and_namespace(cluster) .name(role_group_ref.rolegroup_headless_service_name()) - .ownerreference_from_resource(trino, None, Some(true)) + .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) .context(MetadataBuildSnafu)? @@ -44,7 +48,7 @@ pub fn build_rolegroup_headless_service( // Internal communication does not need to be exposed type_: Some("ClusterIP".to_string()), cluster_ip: Some("None".to_string()), - ports: Some(headless_service_ports(trino)), + ports: Some(ports), selector: Some(selector), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() @@ -55,16 +59,16 @@ pub fn build_rolegroup_headless_service( /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label. pub fn build_rolegroup_metrics_service( - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, role_group_ref: &RoleGroupRef, - object_labels: &ObjectLabels, + object_labels: &ObjectLabels, selector: BTreeMap, ) -> Result { Ok(Service { metadata: ObjectMetaBuilder::new() - .name_and_namespace(trino) + .name_and_namespace(cluster) .name(role_group_ref.rolegroup_metrics_service_name()) - .ownerreference_from_resource(trino, None, Some(true)) + .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) .context(MetadataBuildSnafu)? @@ -84,7 +88,7 @@ pub fn build_rolegroup_metrics_service( }) } -fn headless_service_ports(trino: &v1alpha1::TrinoCluster) -> Vec { +pub(crate) fn headless_service_ports(trino: &v1alpha1::TrinoCluster) -> Vec { let name = trino.exposed_protocol().to_string(); let port = trino.exposed_port().into(); diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/trino_controller.rs similarity index 99% rename from rust/operator-binary/src/controller.rs rename to rust/operator-binary/src/trino_controller.rs index d7c3deba6..271b78709 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -58,18 +58,13 @@ use stackable_operator::{ }; use strum::{EnumDiscriminants, IntoStaticStr}; -mod build; -mod dereference; -mod validate; - -pub use validate::{TrinoRoleGroupConfig, ValidatedCluster}; - use crate::{ authentication::TrinoAuthenticationConfig, authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, catalog::config::CatalogConfig, command, config::{client_protocol, fault_tolerant_execution}, + controller::{build, dereference, validate}, crd::{ APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, @@ -83,7 +78,9 @@ use crate::{ group_listener_name, secret_volume_listener_scope, }, operations::pdb::add_pdbs, - service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, + service::{ + build_rolegroup_headless_service, build_rolegroup_metrics_service, headless_service_ports, + }, }; pub struct Ctx { @@ -103,7 +100,7 @@ pub const MAX_PREPARE_LOG_FILE_SIZE: MemoryQuantity = MemoryQuantity { unit: BinaryMultiple::Mebi, }; -pub(super) const CONTAINER_IMAGE_BASE_NAME: &str = "trino"; +pub(crate) const CONTAINER_IMAGE_BASE_NAME: &str = "trino"; #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] @@ -361,7 +358,7 @@ pub async fn reconcile_trino( let merged_config = &rg.config; let role_group_service_recommended_labels = build_recommended_labels( - trino, + &validated_cluster, &validated_cluster.image.app_version_label_value, &role_group_ref.role, &role_group_ref.role_group, @@ -376,15 +373,16 @@ pub async fn reconcile_trino( .context(LabelBuildSnafu)?; let rg_headless_service = build_rolegroup_headless_service( - trino, + &validated_cluster, &role_group_ref, &role_group_service_recommended_labels, role_group_service_selector.clone().into(), + headless_service_ports(trino), ) .context(ServiceConfigurationSnafu)?; let rg_metrics_service = build_rolegroup_metrics_service( - trino, + &validated_cluster, &role_group_ref, &role_group_service_recommended_labels, role_group_service_selector.into(), @@ -949,12 +947,12 @@ fn env_var_from_secret(secret_name: &str, secret_key: Option<&str>, env_var: &st } } -fn build_recommended_labels<'a>( - owner: &'a v1alpha1::TrinoCluster, +fn build_recommended_labels<'a, T>( + owner: &'a T, app_version: &'a str, role: &'a str, role_group: &'a str, -) -> ObjectLabels<'a, v1alpha1::TrinoCluster> { +) -> ObjectLabels<'a, T> { ObjectLabels { owner, app_name: APP_NAME, @@ -1359,7 +1357,7 @@ mod tests { role_group: "default".to_string(), }; let recommended_labels = build_recommended_labels( - &trino, + &validated_cluster, &validated_cluster.image.app_version_label_value, &rolegroup_ref.role, &rolegroup_ref.role_group, From 11b2d88cd9c0e54e36ece26b676471cc1590ef41 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 11 Jun 2026 20:05:50 +0200 Subject: [PATCH 24/65] refactor: switch to v2 JavaCommonConfig --- Cargo.lock | 18 +- Cargo.nix | 18 +- rust/operator-binary/src/config/jvm.rs | 63 ++----- .../src/controller/build/config_map.rs | 14 +- rust/operator-binary/src/controller/mod.rs | 62 ++++++- .../src/controller/validate.rs | 142 ++++++++------- rust/operator-binary/src/crd/affinity.rs | 20 +-- rust/operator-binary/src/crd/mod.rs | 50 +----- rust/operator-binary/src/framework.rs | 10 -- .../src/framework/role_utils.rs | 167 ------------------ rust/operator-binary/src/main.rs | 1 - 11 files changed, 192 insertions(+), 373 deletions(-) delete mode 100644 rust/operator-binary/src/framework.rs delete mode 100644 rust/operator-binary/src/framework/role_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 41b6a2960..478cb125a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,7 +1514,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "darling", "regex", @@ -2893,7 +2893,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "const-oid", "ecdsa", @@ -2917,7 +2917,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "base64", "clap", @@ -2961,7 +2961,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "darling", "proc-macro2", @@ -2972,7 +2972,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "jiff", "k8s-openapi", @@ -2989,7 +2989,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "axum", "clap", @@ -3035,7 +3035,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "kube", "schemars", @@ -3049,7 +3049,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "convert_case", "convert_case_extras", @@ -3067,7 +3067,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#1e8099fd157b06f27d93854b0838f67871448c4e" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.nix b/Cargo.nix index 46eb91b45..4c3a9b099 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4826,7 +4826,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "k8s_version"; @@ -9509,7 +9509,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_certs"; @@ -9612,7 +9612,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_operator"; @@ -9806,7 +9806,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; @@ -9841,7 +9841,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_shared"; @@ -9922,7 +9922,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_telemetry"; @@ -10129,7 +10129,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_versioned"; @@ -10179,7 +10179,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; @@ -10247,7 +10247,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "451088f77acee6c3d296754698260256c250ecb2"; + rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_webhook"; diff --git a/rust/operator-binary/src/config/jvm.rs b/rust/operator-binary/src/config/jvm.rs index d84c20bfc..3561d81fe 100644 --- a/rust/operator-binary/src/config/jvm.rs +++ b/rust/operator-binary/src/config/jvm.rs @@ -3,7 +3,7 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ memory::{BinaryMultiple, MemoryQuantity}, - role_utils::{self, JvmArgumentOverrides}, + v2::jvm_argument_overrides::JvmArgumentOverrides, }; use crate::crd::{ @@ -36,9 +36,6 @@ pub enum Error { "Trino version {version} is not supported. Only specific versions are handled due to version specific JVM configuration generation" ))] TrinoVersionNotSupported { version: u16 }, - - #[snafu(display("failed to merge jvm argument overrides"))] - MergeJvmArgumentOverrides { source: role_utils::Error }, } // Currently works for all supported versions (as of 2024-09-04) but maybe be changed @@ -46,8 +43,7 @@ pub enum Error { pub fn jvm_config( product_version: u16, merged_config: &v1alpha1::TrinoConfig, - role_jvm_argument_overrides: &JvmArgumentOverrides, - role_group_jvm_argument_overrides: &JvmArgumentOverrides, + jvm_argument_overrides: &JvmArgumentOverrides, ) -> Result { let memory_unit = BinaryMultiple::Mebi; let heap_size = MemoryQuantity::try_from( @@ -92,24 +88,10 @@ pub fn jvm_config( jvm_args.push("# Arguments from jvmArgumentOverrides".to_owned()); - let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args); - - // Merge order mirrors `Role::get_merged_jvm_argument_overrides`: - // 1. operator-generated args are layered on top of the role-level overrides, - // 2. the role-group-level overrides are applied last. - // Note that this is not a purely additive merge, hence the unusual order. - let mut from_role = role_jvm_argument_overrides.clone(); - from_role - .try_merge(&operator_generated) - .context(MergeJvmArgumentOverridesSnafu)?; - let mut merged_jvm_argument_overrides = role_group_jvm_argument_overrides.clone(); - merged_jvm_argument_overrides - .try_merge(&from_role) - .context(MergeJvmArgumentOverridesSnafu)?; - - Ok(merged_jvm_argument_overrides - .effective_jvm_config_after_merging() - .join("\n")) + // `jvm_argument_overrides` already carries the merged role + role-group overrides (merged by + // `with_validated_config` in the validate step). Applying them to the operator-generated args + // layers the overrides on top, in the order: operator-generated <- role <- role group. + Ok(jvm_argument_overrides.apply_to(jvm_args).join("\n")) } /// For tests we don't actually look at the Trino version, and return a single "representative" @@ -269,29 +251,22 @@ mod tests { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(trino_cluster).expect("illegal test input"); - let role = TrinoRole::Coordinator; - let rolegroup_ref = role.rolegroup_ref(&trino, "default"); - let merged_config = trino.merged_config(&role, &rolegroup_ref, &[]).unwrap(); - let coordinators = trino.role(&role).unwrap(); - - let role_jvm_argument_overrides = coordinators - .config - .product_specific_common_config - .jvm_argument_overrides - .clone(); - let role_group_jvm_argument_overrides = coordinators.role_groups["default"] - .config - .product_specific_common_config - .jvm_argument_overrides - .clone(); + // Merge + validate via the shared production path; the role + role-group + // `jvmArgumentOverrides` end up merged in `product_specific_common_config`. + let rg = crate::controller::validate::merged_role_group_config( + &trino, + &TrinoRole::Coordinator, + "default", + &[], + ); - let product_version = trino.spec.image.product_version(); + let product_version = + u16::from_str(trino.spec.image.product_version()).expect("trino version as u16"); jvm_config( - u16::from_str(product_version).expect("trino version as u16"), - &merged_config, - &role_jvm_argument_overrides, - &role_group_jvm_argument_overrides, + product_version, + &rg.config, + &rg.product_specific_common_config.jvm_argument_overrides, ) .unwrap() } diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 4b7d09fec..817ddaae0 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -54,9 +54,6 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("missing JVM argument overrides for role {role}"))] - MissingRoleJvmArgumentOverrides { role: String }, - #[snafu(display("failed to build jvm.config"))] BuildJvmConfig { source: crate::config::jvm::Error }, } @@ -169,18 +166,11 @@ pub fn build_rolegroup_config_map( ); } - // 8. jvm.config. - let role_jvm_argument_overrides = - cluster - .role_jvm_argument_overrides - .get(role) - .with_context(|| MissingRoleJvmArgumentOverridesSnafu { - role: role.to_string(), - })?; + // 8. jvm.config. The role + role-group `jvmArgumentOverrides` were already merged in the + // validate step and are carried by `product_specific_common_config`. let jvm_config = jvm::jvm_config( cluster.product_version, &rg.config, - role_jvm_argument_overrides, &rg.product_specific_common_config.jvm_argument_overrides, ) .context(BuildJvmConfigSnafu)?; diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 82fdc29aa..5537343a8 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -6,7 +6,6 @@ use std::collections::BTreeMap; use stackable_operator::{ commons::product_image_selection::ResolvedProductImage, kube::{Resource, api::ObjectMeta}, - role_utils::JvmArgumentOverrides, v2::types::{ kubernetes::{NamespaceName, Uid}, operator::ClusterName, @@ -65,10 +64,6 @@ pub struct ValidatedCluster { pub product_version: u16, pub cluster_config: ValidatedClusterConfig, pub role_group_configs: BTreeMap>, - /// Role-level JVM argument overrides per role. Required to render `jvm.config` in the build - /// step without access to the raw [`v1alpha1::TrinoCluster`] (the role-group level overrides - /// are carried by [`TrinoRoleGroupConfig::product_specific_common_config`]). - pub role_jvm_argument_overrides: BTreeMap, } impl ValidatedCluster { @@ -81,7 +76,6 @@ impl ValidatedCluster { product_version: u16, cluster_config: ValidatedClusterConfig, role_group_configs: BTreeMap>, - role_jvm_argument_overrides: BTreeMap, ) -> Self { Self { metadata: ObjectMeta { @@ -97,7 +91,6 @@ impl ValidatedCluster { product_version, cluster_config, role_group_configs, - role_jvm_argument_overrides, } } } @@ -130,3 +123,58 @@ impl Resource for ValidatedCluster { &mut self.metadata } } + +/// A minimal, valid TrinoCluster spec shared across unit tests. +#[cfg(test)] +pub(crate) const MINIMAL_TRINO_YAML: &str = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + +/// Parses [`MINIMAL_TRINO_YAML`] into a [`v1alpha1::TrinoCluster`]. +#[cfg(test)] +pub(crate) fn minimal_trino() -> v1alpha1::TrinoCluster { + serde_yaml::from_str(MINIMAL_TRINO_YAML).expect("invalid test TrinoCluster YAML") +} + +/// The validated [`MINIMAL_TRINO_YAML`] cluster with empty dereferenced inputs. The common test +/// fixture for build-step unit tests. +#[cfg(test)] +pub(crate) fn validated_cluster() -> ValidatedCluster { + use stackable_operator::cli::OperatorEnvironmentOptions; + + use crate::controller::dereference::DereferencedObjects; + + let derefs = DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + }; + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + + validate::validate(&minimal_trino(), &derefs, &operator_env).expect("validate should succeed") +} diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 8545160fa..8b42928de 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -9,9 +9,14 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection, + config::fragment, kube::ResourceExt as _, - role_utils::{GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides}, - v2::controller_utils::{get_cluster_name, get_namespace, get_uid}, + role_utils::{GenericRoleConfig, RoleGroup}, + v2::{ + builder::pod::container::{self, EnvVarName, EnvVarSet}, + controller_utils::{get_cluster_name, get_namespace, get_uid}, + role_utils::{JavaCommonConfig, RoleGroupConfig, with_validated_config}, + }, }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; @@ -20,7 +25,6 @@ use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, controller::dereference::DereferencedObjects, crd::{TrinoRole, v1alpha1}, - framework::role_utils::with_validated_config, }; #[derive(Snafu, Debug, EnumDiscriminants)] @@ -72,20 +76,20 @@ pub enum Error { #[snafu(display("failed to resolve and merge config for role group {role_group}"))] FailedToResolveConfig { - source: crate::framework::role_utils::Error, + source: fragment::ValidationError, role_group: String, }, + + #[snafu(display("invalid environment variable override name"))] + ParseEnvVarName { source: container::Error }, } type Result = std::result::Result; pub type RoleGroupName = String; -pub type TrinoRoleGroupConfig = crate::framework::role_utils::RoleGroupConfig< - v1alpha1::TrinoConfig, - JavaCommonConfig, - v1alpha1::TrinoConfigOverrides, ->; +pub type TrinoRoleGroupConfig = + RoleGroupConfig; /// Validates the cluster spec and dereferenced inputs. pub fn validate( @@ -131,8 +135,6 @@ pub fn validate( let mut role_group_configs: BTreeMap> = BTreeMap::new(); - let mut role_jvm_argument_overrides: BTreeMap = - BTreeMap::new(); for trino_role in TrinoRole::iter() { let role = trino .role(&trino_role) @@ -146,7 +148,10 @@ pub fn validate( ); let mut groups = BTreeMap::new(); for (rg_name, rg) in &role.role_groups { - let validated_rg = with_validated_config::< + // Merges and validates the role group config (default <- role <- role group). Because + // `JavaCommonConfig` implements `Merge`, the role and role-group `jvmArgumentOverrides` + // are merged here too and carried by `product_specific_common_config`. + let merged = with_validated_config::< v1alpha1::TrinoConfig, JavaCommonConfig, v1alpha1::TrinoConfigFragment, @@ -156,15 +161,8 @@ pub fn validate( .with_context(|_| FailedToResolveConfigSnafu { role_group: rg_name.clone(), })?; - groups.insert(rg_name.clone(), validated_rg); + groups.insert(rg_name.clone(), into_role_group_config(merged)?); } - role_jvm_argument_overrides.insert( - trino_role.clone(), - role.config - .product_specific_common_config - .jvm_argument_overrides - .clone(), - ); role_group_configs.insert(trino_role, groups); } @@ -193,55 +191,77 @@ pub fn validate( product_version, cluster_config, role_group_configs, - role_jvm_argument_overrides, )) } +/// Adapts the validated [`RoleGroup`] produced by [`with_validated_config`] into the flattened +/// [`TrinoRoleGroupConfig`] consumed by the build steps. +/// +/// Upstream `with_validated_config` returns a [`RoleGroup`] with a `HashMap` of env overrides and an +/// optional replica count; this converts it to the ergonomic [`RoleGroupConfig`] with an +/// [`EnvVarSet`] and a concrete replica count (defaulting to 1). +fn into_role_group_config( + merged: RoleGroup, +) -> Result { + let replicas = merged.replicas.unwrap_or(1); + let common = merged.config; + + let mut env_overrides = EnvVarSet::new(); + for (name, value) in common.env_overrides { + env_overrides = env_overrides.with_value( + &EnvVarName::from_str(&name).context(ParseEnvVarNameSnafu)?, + value, + ); + } + + Ok(RoleGroupConfig { + replicas, + config: common.config, + config_overrides: common.config_overrides, + env_overrides, + cli_overrides: common.cli_overrides, + pod_overrides: common.pod_overrides, + product_specific_common_config: common.product_specific_common_config, + }) +} + +/// Test-only helper: merges and validates a single role group's config from an arbitrary +/// [`v1alpha1::TrinoCluster`] (with the given catalogs feeding `default_config`), reusing the +/// production merge path. Shared by the `crd::affinity` and `config::jvm` unit tests, which need a +/// merged config without dereferencing a full cluster. +#[cfg(test)] +pub(crate) fn merged_role_group_config( + trino: &v1alpha1::TrinoCluster, + trino_role: &TrinoRole, + role_group: &str, + trino_catalogs: &[crate::crd::catalog::v1alpha1::TrinoCatalog], +) -> TrinoRoleGroupConfig { + let role = trino.role(trino_role).expect("role should be defined"); + let default_config = + v1alpha1::TrinoConfig::default_config(&trino.name_any(), trino_role, trino_catalogs); + let rg = role + .role_groups + .get(role_group) + .expect("role group should be defined"); + let merged = with_validated_config::< + v1alpha1::TrinoConfig, + JavaCommonConfig, + v1alpha1::TrinoConfigFragment, + GenericRoleConfig, + v1alpha1::TrinoConfigOverrides, + >(rg, &role, &default_config) + .expect("role group config should be valid"); + into_role_group_config(merged).expect("env overrides should be valid") +} + #[cfg(test)] mod tests { - use super::*; - - const MINIMAL_TRINO_YAML: &str = r#" - apiVersion: trino.stackable.tech/v1alpha1 - kind: TrinoCluster - metadata: - name: simple-trino - namespace: default - uid: "e6ac237d-a6d4-43a1-8135-f36506110912" - spec: - image: - productVersion: "479" - clusterConfig: - catalogLabelSelector: {} - coordinators: - roleGroups: - default: - replicas: 1 - workers: - roleGroups: - default: - replicas: 1 - "#; + use super::super::validated_cluster; + use crate::crd::TrinoRole; #[test] fn validate_minimal_cluster() { - let trino: v1alpha1::TrinoCluster = - serde_yaml::from_str(MINIMAL_TRINO_YAML).expect("invalid test input"); - let derefs = DereferencedObjects { - resolved_authentication_classes: Vec::new(), - catalog_definitions: Vec::new(), - catalogs: Vec::new(), - trino_opa_config: None, - resolved_fte_config: None, - resolved_client_protocol_config: None, - }; - let operator_env = OperatorEnvironmentOptions { - operator_namespace: "stackable-operators".to_string(), - operator_service_name: "trino-operator".to_string(), - image_repository: "oci.example.org".to_string(), - }; - - let validated = validate(&trino, &derefs, &operator_env).expect("validate should succeed"); + let validated = validated_cluster(); assert_eq!(validated.name.to_string(), "simple-trino"); assert_eq!(validated.namespace.to_string(), "default"); diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index bac46c9ad..53a80c8d6 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -136,9 +136,9 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - let merged_config = trino - .merged_config(&role, &role.rolegroup_ref(&trino, "default"), &[]) - .unwrap(); + let merged_config = + crate::controller::validate::merged_role_group_config(&trino, &role, "default", &[]) + .config; assert_eq!( merged_config.affinity, @@ -278,13 +278,13 @@ mod tests { let hive_catalog_2: catalog::v1alpha1::TrinoCatalog = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - let merged_config = trino - .merged_config( - &role, - &role.rolegroup_ref(&trino, "default"), - &[hive_catalog_1, tpch_catalog, hive_catalog_2], - ) - .unwrap(); + let merged_config = crate::controller::validate::merged_role_group_config( + &trino, + &role, + "default", + &[hive_catalog_1, tpch_catalog, hive_catalog_2], + ) + .config; let mut expected_affinities = vec![WeightedPodAffinityTerm { pod_affinity_term: PodAffinityTerm { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 6a22095e6..b01dbb8e2 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -21,23 +21,21 @@ use stackable_operator::{ Resources, ResourcesFragment, }, }, - config::{ - fragment::{self, Fragment, ValidationError}, - merge::Merge, - }, + config::{fragment::Fragment, merge::Merge}, crd::authentication::core, deep_merger::ObjectOverrides, k8s_openapi::apimachinery::pkg::{api::resource::Quantity, apis::meta::v1::LabelSelector}, - kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef}, + kube::{CustomResource, runtime::reflector::ObjectRef}, memory::{BinaryMultiple, MemoryQuantity}, product_logging::{self, spec::Logging}, - role_utils::{ - CommonConfiguration, GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef, - }, + role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup, RoleGroupRef}, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, - v2::{config_overrides::KeyValueConfigOverrides, types::kubernetes::NamespaceName}, + v2::{ + config_overrides::KeyValueConfigOverrides, role_utils::JavaCommonConfig, + types::kubernetes::NamespaceName, + }, versioned::versioned, }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; @@ -119,9 +117,6 @@ pub enum Error { #[snafu(display("the role group {role_group} is not defined"))] CannotRetrieveTrinoRoleGroup { role_group: String }, - - #[snafu(display("fragment validation failure"))] - FragmentValidationFailure { source: ValidationError }, } #[versioned( @@ -711,37 +706,6 @@ impl v1alpha1::TrinoCluster { pub fn expose_https_port(&self) -> bool { self.get_server_tls().is_some() } - - /// Retrieve and merge resource configs for role and role groups - pub fn merged_config( - &self, - role: &TrinoRole, - rolegroup_ref: &RoleGroupRef, - trino_catalogs: &[catalog::v1alpha1::TrinoCatalog], - ) -> Result { - // Initialize the result with all default values as baseline - let conf_defaults = - v1alpha1::TrinoConfig::default_config(&self.name_any(), role, trino_catalogs); - - let role = self.role(role)?; - - // Retrieve role resource config - let mut conf_role = role.config.config.to_owned(); - - // Retrieve rolegroup specific resource config - let mut conf_rolegroup = self.rolegroup(rolegroup_ref)?.config.config.clone(); - - // Merge more specific configs into default config - // Hierarchy is: - // 1. RoleGroup - // 2. Role - // 3. Default - conf_role.merge(&conf_defaults); - conf_rolegroup.merge(&conf_role); - - tracing::debug!("Merged config: {:?}", conf_rolegroup); - fragment::validate(conf_rolegroup).context(FragmentValidationFailureSnafu) - } } fn extract_role_from_coordinator_config(fragment: TrinoCoordinatorRoleType) -> TrinoRoleType { diff --git a/rust/operator-binary/src/framework.rs b/rust/operator-binary/src/framework.rs deleted file mode 100644 index d0a9b1bbf..000000000 --- a/rust/operator-binary/src/framework.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Local framework helpers that mirror the work-in-progress upstream -//! `stackable_operator::v2::*` modules. -//! -//! The upstream `v2` module on the `smooth-operator` branch is not yet exported -//! from `lib.rs`, so we vendor the small subset of helpers we need. -//! -//! Follow-up: replace these with `stackable_operator::v2::*` imports once -//! upstream publishes the module. - -pub mod role_utils; diff --git a/rust/operator-binary/src/framework/role_utils.rs b/rust/operator-binary/src/framework/role_utils.rs deleted file mode 100644 index bc94b55bc..000000000 --- a/rust/operator-binary/src/framework/role_utils.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Vendored variant of `stackable_operator::v2::role_utils` from the -//! `smooth-operator` branch, with simplifications appropriate for trino-operator. -//! -//! Differences from upstream: -//! - No `cli_overrides_to_vec` helper, `ResourceNames`, or service-account helpers. -//! - The `CommonConfig` (a.k.a. `product_specific_common_config`) does NOT need to -//! implement `Merge`. Upstream Trino uses `JavaCommonConfig`, which intentionally -//! does not implement `Merge` because its inner `JvmArgumentOverrides::try_merge` -//! is fallible (regex validation). Merging JVM argument overrides for Trino is -//! handled separately via `Role::get_merged_jvm_argument_overrides`. The -//! `RoleGroupConfig::product_specific_common_config` field here simply carries -//! the role-group level value through. -//! -//! Replace with `stackable_operator::v2::role_utils::*` once upstream publishes -//! the module. - -use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, -}; - -use serde::Serialize; -use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - config::{ - fragment::{self, FromFragment}, - merge::{Merge, merge}, - }, - k8s_openapi::{DeepMerge, api::core::v1::PodTemplateSpec}, - role_utils::{Role, RoleGroup}, - schemars::JsonSchema, - v2::builder::pod::container::{self, EnvVarName, EnvVarSet}, -}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to validate the role group config"))] - ValidateConfig { source: fragment::ValidationError }, - - #[snafu(display("invalid environment variable override name"))] - ParseEnvVarName { source: container::Error }, -} - -/// Trino-friendly view of a validated, merged `RoleGroup`. -/// -/// Mirrors `stackable_operator::v2::role_utils::RoleGroupConfig` on the -/// `smooth-operator` branch. -#[derive(Clone, Debug, PartialEq)] -pub struct RoleGroupConfig { - pub replicas: u16, - pub config: Config, - pub config_overrides: ConfigOverrides, - pub env_overrides: EnvVarSet, - pub cli_overrides: BTreeMap, - pub pod_overrides: PodTemplateSpec, - pub product_specific_common_config: CommonConfig, -} - -/// Merges and validates the `RoleGroup` with the given `role` and `default_config`, -/// returning a `RoleGroupConfig`. -/// -/// Merge order matches `with_validated_config` on `smooth-operator`: -/// - `Config` (Fragment): `default_config <- role.config <- rg.config` via `Merge::merge`, -/// then validated to `ValidatedConfig` via `FromFragment`. -/// - `ConfigOverrides`: `role.config_overrides <- rg.config_overrides` via `Merge::merge`. -/// - `env_overrides` / `cli_overrides`: `extend` (rg keys overwrite role keys). -/// - `pod_overrides`: `DeepMerge::merge_from` (rg overrides role). -/// - `product_specific_common_config`: passes through the role-group level value -/// (see module docs for rationale). -pub fn with_validated_config( - role_group: &RoleGroup, - role: &Role, - default_config: &Config, -) -> Result, Error> -where - ValidatedConfig: FromFragment, - CommonConfig: Clone + Default + JsonSchema + Serialize, - Config: Clone + Merge, - RoleConfig: Default + JsonSchema + Serialize, - ConfigOverrides: Clone + Default + JsonSchema + Merge + Serialize, -{ - let validated_config = - validate_config(role_group, role, default_config).context(ValidateConfigSnafu)?; - Ok(RoleGroupConfig { - replicas: role_group.replicas.unwrap_or(1), - config: validated_config, - config_overrides: merged_config_overrides( - &role.config.config_overrides, - role_group.config.config_overrides.clone(), - ), - env_overrides: merged_env_overrides( - &role.config.env_overrides, - &role_group.config.env_overrides, - )?, - cli_overrides: merged_cli_overrides( - role.config.cli_overrides.clone(), - role_group.config.cli_overrides.clone(), - ), - pod_overrides: merged_pod_overrides( - role.config.pod_overrides.clone(), - role_group.config.pod_overrides.clone(), - ), - product_specific_common_config: role_group.config.product_specific_common_config.clone(), - }) -} - -fn validate_config( - role_group: &RoleGroup, - role: &Role, - default_config: &Config, -) -> Result -where - ValidatedConfig: FromFragment, - CommonConfig: Default + JsonSchema + Serialize, - Config: Clone + Merge, - RoleConfig: Default + JsonSchema + Serialize, - ConfigOverrides: Default + JsonSchema + Serialize, -{ - role_group.validate_config(role, default_config) -} - -fn merged_config_overrides( - role_config_overrides: &ConfigOverrides, - role_group_config_overrides: ConfigOverrides, -) -> ConfigOverrides -where - ConfigOverrides: Merge, -{ - merge(role_group_config_overrides, role_config_overrides) -} - -fn merged_env_overrides( - role_env_overrides: &HashMap, - role_group_env_overrides: &HashMap, -) -> Result { - // Process the role first, then the role group, so that role-group overrides win on key - // collisions (`EnvVarSet::with_value` overrides earlier entries with the same name). - let mut env_overrides = EnvVarSet::new(); - for (name, value) in role_env_overrides - .iter() - .chain(role_group_env_overrides.iter()) - { - env_overrides = env_overrides.with_value( - &EnvVarName::from_str(name).context(ParseEnvVarNameSnafu)?, - value.clone(), - ); - } - Ok(env_overrides) -} - -fn merged_cli_overrides( - role_cli_overrides: BTreeMap, - role_group_cli_overrides: BTreeMap, -) -> BTreeMap { - let mut merged = role_cli_overrides; - merged.extend(role_group_cli_overrides); - merged -} - -fn merged_pod_overrides( - role_pod_overrides: PodTemplateSpec, - role_group_pod_overrides: PodTemplateSpec, -) -> PodTemplateSpec { - let mut merged = role_pod_overrides; - merged.merge_from(role_group_pod_overrides); - merged -} diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index eb1e7be37..29ca43cc8 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -49,7 +49,6 @@ mod command; mod config; mod controller; mod crd; -mod framework; mod listener; mod operations; mod service; From 85acf9e5a9e4090701ced284c3e362d4ef159334 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 14:59:10 +0200 Subject: [PATCH 25/65] chore: bump stackable-operator --- Cargo.lock | 91 +++++++++++++++---------------- Cargo.nix | 134 ++++++++++++++++++++++------------------------ crate-hashes.json | 18 +++---- 3 files changed, 116 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 478cb125a..7b559497f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,9 +302,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -574,9 +574,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -1024,9 +1021,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -1453,9 +1450,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.100" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", @@ -1514,7 +1511,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "darling", "regex", @@ -1727,9 +1724,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -2791,9 +2788,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "snafu" @@ -2893,7 +2890,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "const-oid", "ecdsa", @@ -2917,7 +2914,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "base64", "clap", @@ -2942,6 +2939,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "sha2", "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", @@ -2961,7 +2959,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "darling", "proc-macro2", @@ -2972,7 +2970,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "jiff", "k8s-openapi", @@ -2989,7 +2987,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "axum", "clap", @@ -3035,7 +3033,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "kube", "schemars", @@ -3049,7 +3047,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "convert_case", "convert_case_extras", @@ -3067,7 +3065,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#1e8099fd157b06f27d93854b0838f67871448c4e" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" dependencies = [ "arc-swap", "async-trait", @@ -3228,12 +3226,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -3243,15 +3240,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" dependencies = [ "num-conv", "time-core", @@ -3704,18 +3701,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -3726,9 +3723,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.73" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ "js-sys", "wasm-bindgen", @@ -3736,9 +3733,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3746,9 +3743,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -3759,18 +3756,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.100" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -4034,18 +4031,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.nix b/Cargo.nix index 4c3a9b099..e126cd7c3 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -962,9 +962,9 @@ rec { }; "cc" = rec { crateName = "cc"; - version = "1.2.63"; + version = "1.2.64"; edition = "2018"; - sha256 = "0zy2bqc4nvj6bv2cipx4h4bn65wf1zqf1fw1hsh64mmvg1hh2vjm"; + sha256 = "07shcd8faxw7csz13m3cg2mj6i8z07pqs960k181pscbjpyqgn6s"; authors = [ "Alex Crichton " ]; @@ -1740,14 +1740,6 @@ rec { authors = [ "Jacob Pratt " ]; - dependencies = [ - { - name = "powerfmt"; - packageId = "powerfmt"; - optional = true; - usesDefaultFeatures = false; - } - ]; features = { "macros" = [ "dep:deranged-macros" ]; "num" = [ "dep:num-traits" ]; @@ -1759,7 +1751,7 @@ rec { "rand09" = [ "dep:rand09" ]; "serde" = [ "dep:serde_core" ]; }; - resolvedDefaultFeatures = [ "default" "powerfmt" ]; + resolvedDefaultFeatures = [ "default" ]; }; "derive_more" = rec { crateName = "derive_more"; @@ -3209,9 +3201,9 @@ rec { }; "h2" = rec { crateName = "h2"; - version = "0.4.14"; + version = "0.4.15"; edition = "2021"; - sha256 = "0cw7jk7kn2vn6f8w8ssh6gis1mljnfjxd606gvi4sjpyjayfy7qp"; + sha256 = "0mgilh1g8gydcchqi6acs5l6j0gwg5jwpa64sj4b3ncb9v497c3c"; authors = [ "Carl Lerche " "Sean McArthur " @@ -4618,9 +4610,9 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.100"; + version = "0.3.102"; edition = "2021"; - sha256 = "0qi1wjakyw2rx9wwprcfx77g3lvn1b8n6yvfhj2pgym4swh5y0pj"; + sha256 = "0cgxklnyrfpzvf32cvdl3x5d070kfsv7ykdxfl3yizwdjqq4rl03"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" @@ -4826,8 +4818,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "k8s_version"; authors = [ @@ -5690,9 +5682,9 @@ rec { }; "memchr" = rec { crateName = "memchr"; - version = "2.8.1"; + version = "2.8.2"; edition = "2021"; - sha256 = "1n448jx01h5z2xknj6x2dhxgr8s8fb717cf6vfqj5lmhkpj7m53b"; + sha256 = "1i33wr49pcz2sbd12nds3n9fszay8kq5bk78gwciz462mcs49448"; authors = [ "Andrew Gallant " "bluss" @@ -9200,9 +9192,9 @@ rec { }; "smallvec" = rec { crateName = "smallvec"; - version = "1.15.1"; + version = "1.15.2"; edition = "2018"; - sha256 = "00xxdxxpgyq5vjnpljvkmy99xij5rxgh913ii1v16kzynnivgcb7"; + sha256 = "143wzbqf6vgapdp2z4qpl0yvlqcn17s8cnk8m28rqly808zsdmlf"; authors = [ "The Servo Project Developers" ]; @@ -9509,8 +9501,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_certs"; authors = [ @@ -9612,8 +9604,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_operator"; authors = [ @@ -9722,6 +9714,11 @@ rec { name = "serde_yaml"; packageId = "serde_yaml"; } + { + name = "sha2"; + packageId = "sha2"; + features = [ "oid" ]; + } { name = "snafu"; packageId = "snafu 0.9.1"; @@ -9806,8 +9803,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9841,8 +9838,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_shared"; authors = [ @@ -9922,8 +9919,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_telemetry"; authors = [ @@ -10129,8 +10126,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_versioned"; authors = [ @@ -10179,8 +10176,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10247,8 +10244,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "1e8099fd157b06f27d93854b0838f67871448c4e"; - sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; + rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_webhook"; authors = [ @@ -10687,9 +10684,9 @@ rec { }; "time" = rec { crateName = "time"; - version = "0.3.47"; + version = "0.3.49"; edition = "2024"; - sha256 = "0b7g9ly2iabrlgizliz6v5x23yq5d6bpp0mqz6407z1s526d8fvl"; + sha256 = "0sc4dgw6g187gvz5qj9iqqk2ashqzvdwi664b2183gbvsk1566ki"; authors = [ "Jacob Pratt " "Time contributors" @@ -10698,12 +10695,6 @@ rec { { name = "deranged"; packageId = "deranged"; - features = [ "powerfmt" ]; - } - { - name = "itoa"; - packageId = "itoa"; - optional = true; } { name = "num-conv"; @@ -10743,13 +10734,14 @@ rec { features = { "alloc" = [ "serde_core?/alloc" ]; "default" = [ "std" ]; - "formatting" = [ "dep:itoa" "std" "time-macros?/formatting" ]; + "formatting" = [ "std" "time-macros?/formatting" ]; "large-dates" = [ "time-core/large-dates" "time-macros?/large-dates" ]; "local-offset" = [ "std" "dep:libc" "dep:num_threads" ]; "macros" = [ "dep:time-macros" ]; "parsing" = [ "time-macros?/parsing" ]; "quickcheck" = [ "dep:quickcheck" "alloc" "deranged/quickcheck" ]; - "rand" = [ "rand08" "rand09" ]; + "rand" = [ "rand08" "rand09" "rand010" ]; + "rand010" = [ "dep:rand010" "deranged/rand010" ]; "rand08" = [ "dep:rand08" "deranged/rand08" ]; "rand09" = [ "dep:rand09" "deranged/rand09" ]; "serde" = [ "dep:serde_core" "time-macros?/serde" "deranged/serde" ]; @@ -10762,9 +10754,9 @@ rec { }; "time-core" = rec { crateName = "time-core"; - version = "0.1.8"; + version = "0.1.9"; edition = "2024"; - sha256 = "1jidl426mw48i7hjj4hs9vxgd9lwqq4vyalm4q8d7y4iwz7y353n"; + sha256 = "028ix0ax7ixp1h1k5zsqwgw85w6y1q32irslma7ci6ddd5kr074y"; libName = "time_core"; authors = [ "Jacob Pratt " @@ -10775,9 +10767,9 @@ rec { }; "time-macros" = rec { crateName = "time-macros"; - version = "0.2.27"; + version = "0.2.29"; edition = "2024"; - sha256 = "058ja265waq275wxvnfwavbz9r1hd4dgwpfn7a1a9a70l32y8w1f"; + sha256 = "0zf1ycfikg93ijf00qnprk801khqnqqga1zp0adbp73sfaim5iki"; procMacro = true; libName = "time_macros"; authors = [ @@ -12419,9 +12411,9 @@ rec { }; "wasip2" = rec { crateName = "wasip2"; - version = "1.0.3+wasi-0.2.9"; + version = "1.0.4+wasi-0.2.12"; edition = "2021"; - sha256 = "1mi3w855dz99xzjqc4aa8c9q5b6z1y5c963pkk4cvmr6vdr4c1i0"; + sha256 = "11wl7lqwq4pbmlmzr6n7bwz0hzy1z6sxc4554bkmrr86w4vznzmn"; dependencies = [ { name = "wit-bindgen"; @@ -12439,9 +12431,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.123"; + version = "0.2.125"; edition = "2021"; - sha256 = "0qqmx07r597gm8lbz8qngvv0phwvpzzyfh3nl84nz9qr1jqs8m52"; + sha256 = "06nakz7nfy0ymyp7a27wfbjwx69659i12117hkgddkiv2iwkznwd"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12490,9 +12482,9 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.73"; + version = "0.4.75"; edition = "2021"; - sha256 = "1bva12h8gdpqkp753czlxabs0s21lvgzm41brr4lhpdzz818fmjl"; + sha256 = "104jssshr6cm5hmkn6c66mbkyxgaaphng6c17g0dmj7jhk918fsh"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" @@ -12518,9 +12510,9 @@ rec { }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.123"; + version = "0.2.125"; edition = "2021"; - sha256 = "1p50xdwmv543b52bc49vm5lcsgd9adpx647bdisg7ihfbg3hz914"; + sha256 = "0g9w68dwcs4ylm5kxf7schi0kjdfarhc9qlnf8arxc9zn62a28af"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12542,9 +12534,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.123"; + version = "0.2.125"; edition = "2021"; - sha256 = "0nwqyc63byl7rp9nnv45av8h85fncfmxywkvy35d9qwwkfyk93wh"; + sha256 = "1gayzdx5iwl8gllh7ys79wg9cf4iyasl9hrzzhh5m4xx6nfgvkpy"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12578,10 +12570,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.123"; + version = "0.2.125"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "14lvjm3pzywm5c4962i6s5zmngic1knpggshnnxr9c97dihzgjvs"; + sha256 = "07w7fy5qa14ys3p8v2p84h98yqinw713smibz9v7apcspd29x4r3"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12596,9 +12588,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.100"; + version = "0.3.102"; edition = "2021"; - sha256 = "0sffbkrpgyi1402mv4wzp9av6ky6rnb1d2m2dpf87wi7yfn7223f"; + sha256 = "0786aybrnwsgdmcynhc2k5ii291a02rq9zk054j35csyvxr0lhx6"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -14211,9 +14203,9 @@ rec { }; "zeroize" = rec { crateName = "zeroize"; - version = "1.8.2"; - edition = "2021"; - sha256 = "1l48zxgcv34d7kjskr610zqsm6j2b4fcr2vfh9jm9j1jgvk58wdr"; + version = "1.9.0"; + edition = "2024"; + sha256 = "0kpnij2v1ig6g2mhc0bnci0lrdfdhiq40afbc0fahajqc9jiag71"; authors = [ "The RustCrypto Project Developers" ]; @@ -14235,9 +14227,9 @@ rec { }; "zeroize_derive" = rec { crateName = "zeroize_derive"; - version = "1.4.3"; - edition = "2021"; - sha256 = "0bl5vd1lz27p4z336nximg5wrlw5j7jc8fxh7iv6r1wrhhav99c5"; + version = "1.5.0"; + edition = "2024"; + sha256 = "0a7kq8srk81pn23xqn7c9jw1jpnfy41ffn802x1zrqqgpdf6al1w"; procMacro = true; authors = [ "The RustCrypto Project Developers" diff --git a/crate-hashes.json b/crate-hashes.json index c9a6e6a9d..bb8b51c7c 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 From 469788839d3145e11fdcc81f3d0c83c79a714b6c Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 16:01:33 +0200 Subject: [PATCH 26/65] refactor: reduce RolegroupRef usage --- .../src/controller/build/config_map.rs | 37 ++++-- .../controller/build/properties/logging.rs | 22 +++- rust/operator-binary/src/controller/mod.rs | 22 +++- rust/operator-binary/src/crd/mod.rs | 87 +++++-------- rust/operator-binary/src/service.rs | 23 +++- rust/operator-binary/src/trino_controller.rs | 114 +++++++++--------- 6 files changed, 161 insertions(+), 144 deletions(-) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 817ddaae0..420e3b00a 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -8,7 +8,6 @@ use stackable_operator::{ k8s_openapi::api::core::v1::ConfigMap, kvp::ObjectLabels, product_logging, - role_utils::RoleGroupRef, utils::cluster_info::KubernetesClusterInfo, v2::config_file_writer::to_java_properties_string, }; @@ -23,7 +22,7 @@ use crate::{ security_properties, spooling_manager_properties, }, }, - crd::{TrinoRole, v1alpha1}, + crd::TrinoRole, }; // File name not exported from crd/mod.rs. @@ -46,7 +45,7 @@ pub enum Error { #[snafu(display("failed to assemble ConfigMap for {rolegroup}"))] Assemble { source: stackable_operator::builder::configmap::Error, - rolegroup: RoleGroupRef, + rolegroup: String, }, #[snafu(display("metadata build failure"))] @@ -63,7 +62,7 @@ type Result = std::result::Result; pub fn build_rolegroup_config_map( cluster: &ValidatedCluster, role: &TrinoRole, - rolegroup_ref: &RoleGroupRef, + role_group_name: &str, cluster_info: &KubernetesClusterInfo, recommended_labels: &ObjectLabels<'_, ValidatedCluster>, ) -> Result { @@ -73,15 +72,20 @@ pub fn build_rolegroup_config_map( .get(role) .with_context(|| MissingRoleGroupSnafu { role: role.to_string(), - role_group: rolegroup_ref.role_group.clone(), + role_group: role_group_name.to_owned(), })?; let rg = role_group_configs - .get(&rolegroup_ref.role_group) + .get(role_group_name) .with_context(|| MissingRoleGroupSnafu { role: role.to_string(), - role_group: rolegroup_ref.role_group.clone(), + role_group: role_group_name.to_owned(), })?; + let config_map_name = cluster + .resource_names(role, role_group_name) + .role_group_config_map() + .to_string(); + let mut data: BTreeMap = BTreeMap::new(); // Auth files (e.g. password-authenticator file contents) — inserted FIRST @@ -177,7 +181,7 @@ pub fn build_rolegroup_config_map( data.insert(JVM_CONFIG.to_string(), jvm_config); // 9. Vector sidecar toml if enabled. - if let Some(vector_toml) = get_vector_toml(rolegroup_ref, &rg.config.logging) { + if let Some(vector_toml) = get_vector_toml(cluster, role, role_group_name, &rg.config.logging) { data.insert( product_logging::framework::VECTOR_CONFIG_FILE.to_string(), vector_toml, @@ -187,7 +191,7 @@ pub fn build_rolegroup_config_map( ConfigMapBuilder::new() .metadata( ObjectMetaBuilder::new() - .name(rolegroup_ref.object_name()) + .name(&config_map_name) .namespace(cluster.namespace.to_string()) .ownerreference_from_resource(cluster, None, Some(true)) .context(MetadataSnafu)? @@ -198,7 +202,7 @@ pub fn build_rolegroup_config_map( .data(data) .build() .with_context(|_| AssembleSnafu { - rolegroup: rolegroup_ref.clone(), + rolegroup: config_map_name.clone(), }) } @@ -206,13 +210,20 @@ pub fn build_rolegroup_config_map( /// given by the administrator pub fn build_rolegroup_catalog_config_map( cluster: &ValidatedCluster, - rolegroup_ref: &RoleGroupRef, + role: &TrinoRole, + role_group_name: &str, recommended_labels: &ObjectLabels<'_, ValidatedCluster>, ) -> Result { + let catalog_config_map_name = format!( + "{}-catalog", + cluster + .resource_names(role, role_group_name) + .role_group_config_map() + ); ConfigMapBuilder::new() .metadata( ObjectMetaBuilder::new() - .name(format!("{}-catalog", rolegroup_ref.object_name())) + .name(&catalog_config_map_name) .namespace(cluster.namespace.to_string()) .ownerreference_from_resource(cluster, None, Some(true)) .context(MetadataSnafu)? @@ -235,6 +246,6 @@ pub fn build_rolegroup_catalog_config_map( ) .build() .with_context(|_| AssembleSnafu { - rolegroup: rolegroup_ref.clone(), + rolegroup: catalog_config_map_name.clone(), }) } diff --git a/rust/operator-binary/src/controller/build/properties/logging.rs b/rust/operator-binary/src/controller/build/properties/logging.rs index dc9550a90..e2d653fe2 100644 --- a/rust/operator-binary/src/controller/build/properties/logging.rs +++ b/rust/operator-binary/src/controller/build/properties/logging.rs @@ -1,4 +1,5 @@ use stackable_operator::{ + kube::runtime::reflector::ObjectRef, product_logging::{ framework::create_vector_config, spec::{ @@ -10,7 +11,10 @@ use stackable_operator::{ }; use strum::Display; -use crate::crd::{Container, v1alpha1}; +use crate::{ + controller::ValidatedCluster, + crd::{Container, TrinoRole}, +}; #[derive(Display)] #[strum(serialize_all = "lowercase")] @@ -64,7 +68,9 @@ pub fn get_log_property_map( /// Return the vector toml configuration pub fn get_vector_toml( - rolegroup: &RoleGroupRef, + cluster: &ValidatedCluster, + role: &TrinoRole, + role_group_name: &str, logging: &Logging, ) -> Option { let vector_log_config = if let Some(ContainerLogConfig { @@ -77,7 +83,17 @@ pub fn get_vector_toml( }; if logging.enable_vector_agent { - Some(create_vector_config(rolegroup, vector_log_config)) + // The legacy `create_vector_config` still requires a `RoleGroupRef`, but ignores it + // entirely when rendering the config. We construct a throwaway one here so the rest of the + // operator no longer has to thread `RoleGroupRef` around. + // TODO(step-5): drop this once logging moves to the `v2` framework (`include_str!`-based + // `vector.yaml`), which does not need a `RoleGroupRef`. + let rolegroup_ref = RoleGroupRef { + cluster: ObjectRef::from_obj(cluster), + role: role.to_string(), + role_group: role_group_name.to_owned(), + }; + Some(create_vector_config(&rolegroup_ref, vector_log_config)) } else { None } diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 5537343a8..1c600f2d6 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -1,14 +1,17 @@ //! Controller-level vocabulary: the [`ValidatedCluster`] type produced by the [`validate`] step //! and consumed by the [`build`] steps, plus the `dereference` / `validate` / `build` sub-modules. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ commons::product_image_selection::ResolvedProductImage, kube::{Resource, api::ObjectMeta}, - v2::types::{ - kubernetes::{NamespaceName, Uid}, - operator::ClusterName, + v2::{ + role_group_utils::ResourceNames, + types::{ + kubernetes::{NamespaceName, Uid}, + operator::{ClusterName, RoleGroupName as RoleGroupNameV2, RoleName}, + }, }, }; @@ -93,6 +96,17 @@ impl ValidatedCluster { role_group_configs, } } + + /// Type-safe names for the resources of a given role group. + pub(crate) fn resource_names(&self, role: &TrinoRole, role_group_name: &str) -> ResourceNames { + ResourceNames { + cluster_name: self.name.clone(), + role_name: RoleName::from_str(&role.to_string()) + .expect("a TrinoRole is a valid RFC 1123 role name"), + role_group_name: RoleGroupNameV2::from_str(role_group_name) + .expect("a validated role group name is a valid role group name"), + } + } } impl Resource for ValidatedCluster { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index b01dbb8e2..4aa7a5685 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -9,7 +9,7 @@ use std::{collections::BTreeMap, str::FromStr}; use affinity::get_affinity; use serde::{Deserialize, Serialize}; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{OptionExt, Snafu}; use stackable_operator::{ commons::{ affinity::StackableAffinity, @@ -25,20 +25,25 @@ use stackable_operator::{ crd::authentication::core, deep_merger::ObjectOverrides, k8s_openapi::apimachinery::pkg::{api::resource::Quantity, apis::meta::v1::LabelSelector}, - kube::{CustomResource, runtime::reflector::ObjectRef}, + kube::{CustomResource, ResourceExt}, memory::{BinaryMultiple, MemoryQuantity}, product_logging::{self, spec::Logging}, - role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup, RoleGroupRef}, + role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup}, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, v2::{ - config_overrides::KeyValueConfigOverrides, role_utils::JavaCommonConfig, - types::kubernetes::NamespaceName, + config_overrides::KeyValueConfigOverrides, + role_group_utils::ResourceNames, + role_utils::JavaCommonConfig, + types::{ + kubernetes::NamespaceName, + operator::{ClusterName, RoleGroupName, RoleName}, + }, }, versioned::versioned, }; -use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; +use strum::{Display, EnumIter, EnumString}; use crate::crd::discovery::TrinoPodRef; @@ -105,13 +110,6 @@ pub(crate) fn quantity_to_trino_bytes( #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("unknown role {role}. Should be one of {roles:?}"))] - UnknownTrinoRole { - source: strum::ParseError, - role: String, - roles: Vec, - }, - #[snafu(display("the role {role} is not defined"))] CannotRetrieveTrinoRole { role: String }, @@ -421,36 +419,6 @@ pub enum TrinoRole { } impl TrinoRole { - /// Returns the container start command for a Trino node. - pub fn get_command(&self) -> Vec { - vec![ - "bin/launcher".to_string(), - "run".to_string(), - format!("--etc-dir={}", CONFIG_DIR_NAME), - ] - } - - /// Metadata about a rolegroup - pub fn rolegroup_ref( - &self, - trino: &v1alpha1::TrinoCluster, - group_name: impl Into, - ) -> RoleGroupRef { - RoleGroupRef { - cluster: ObjectRef::from_obj(trino), - role: self.to_string(), - role_group: group_name.into(), - } - } - - pub fn roles() -> Vec { - let mut roles = vec![]; - for role in Self::iter() { - roles.push(role.to_string()) - } - roles - } - pub fn listener_class_name(&self, trino: &v1alpha1::TrinoCluster) -> Option { match self { Self::Coordinator => trino @@ -553,22 +521,17 @@ impl v1alpha1::TrinoCluster { /// 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, + role: &TrinoRole, + role_group: &str, ) -> Result { - let trino_role = - TrinoRole::from_str(&rolegroup_ref.role).with_context(|_| UnknownTrinoRoleSnafu { - role: rolegroup_ref.role.to_owned(), - roles: TrinoRole::roles(), - })?; - - let role_variant = self.role(&trino_role)?; + let role_variant = self.role(role)?; role_variant .role_groups - .get(&rolegroup_ref.role_group) + .get(role_group) .cloned() .with_context(|| CannotRetrieveTrinoRoleGroupSnafu { - role_group: rolegroup_ref.role_group.to_owned(), + role_group: role_group.to_owned(), }) } @@ -624,6 +587,10 @@ impl v1alpha1::TrinoCluster { namespace: &NamespaceName, ) -> impl Iterator + '_ { let ns = namespace.to_string(); + let cluster_name = ClusterName::from_str(&self.name_any()) + .expect("a TrinoCluster name is a valid cluster name"); + let role_name = RoleName::from_str(&TrinoRole::Coordinator.to_string()) + .expect("the coordinator role name is a valid role name"); self.spec .coordinators .iter() @@ -632,15 +599,17 @@ impl v1alpha1::TrinoCluster { .collect::>() .into_iter() .flat_map(move |(rolegroup_name, rolegroup)| { - let role_group_ref = TrinoRole::Coordinator.rolegroup_ref(self, rolegroup_name); + let resource_names = ResourceNames { + cluster_name: cluster_name.clone(), + role_name: role_name.clone(), + role_group_name: RoleGroupName::from_str(rolegroup_name) + .expect("a role group name is a valid role group name"), + }; let ns = ns.clone(); (0..rolegroup.replicas.unwrap_or(0)).map(move |i| TrinoPodRef { namespace: ns.clone(), - role_group_service_name: role_group_ref.rolegroup_headless_service_name(), - pod_name: format!( - "{role_group}-{i}", - role_group = role_group_ref.object_name() - ), + role_group_service_name: resource_names.headless_service_name().to_string(), + pod_name: format!("{}-{i}", resource_names.stateful_set_name()), }) }) } diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index 455c58f2e..b393218d4 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -5,12 +5,11 @@ use stackable_operator::{ builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels, ObjectLabels}, - role_utils::RoleGroupRef, }; use crate::{ controller::ValidatedCluster, - crd::{METRICS_PORT, METRICS_PORT_NAME, v1alpha1}, + crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole, v1alpha1}, }; #[derive(Snafu, Debug)] @@ -30,7 +29,8 @@ pub enum Error { /// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing. pub fn build_rolegroup_headless_service( cluster: &ValidatedCluster, - role_group_ref: &RoleGroupRef, + role: &TrinoRole, + role_group_name: &str, object_labels: &ObjectLabels, selector: BTreeMap, ports: Vec, @@ -38,7 +38,12 @@ pub fn build_rolegroup_headless_service( Ok(Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) - .name(role_group_ref.rolegroup_headless_service_name()) + .name( + cluster + .resource_names(role, role_group_name) + .headless_service_name() + .to_string(), + ) .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) @@ -60,14 +65,20 @@ pub fn build_rolegroup_headless_service( /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label. pub fn build_rolegroup_metrics_service( cluster: &ValidatedCluster, - role_group_ref: &RoleGroupRef, + role: &TrinoRole, + role_group_name: &str, object_labels: &ObjectLabels, selector: BTreeMap, ) -> Result { Ok(Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) - .name(role_group_ref.rolegroup_metrics_service_name()) + .name( + cluster + .resource_names(role, role_group_name) + .metrics_service_name() + .to_string(), + ) .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 271b78709..86e70a092 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -48,7 +48,7 @@ use stackable_operator::{ CustomContainerLogConfig, }, }, - role_utils::{GenericRoleConfig, RoleGroupRef}, + role_utils::GenericRoleConfig, shared::time::Duration, status::condition::{ compute_conditions, operations::ClusterOperationsConditionBuilder, @@ -64,7 +64,7 @@ use crate::{ catalog::config::CatalogConfig, command, config::{client_protocol, fault_tolerant_execution}, - controller::{build, dereference, validate}, + controller::{ValidatedCluster, build, dereference, validate}, crd::{ APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, @@ -122,25 +122,25 @@ pub enum Error { #[snafu(display("failed to apply Service for {}", rolegroup))] ApplyRoleGroupService { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + rolegroup: String, }, #[snafu(display("failed to build ConfigMap for {}", rolegroup))] BuildRoleGroupConfigMap { source: build::config_map::Error, - rolegroup: RoleGroupRef, + rolegroup: String, }, #[snafu(display("failed to apply ConfigMap for {}", rolegroup))] ApplyRoleGroupConfig { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + rolegroup: String, }, #[snafu(display("failed to apply StatefulSet for {}", rolegroup))] ApplyRoleGroupStatefulSet { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + rolegroup: String, }, #[snafu(display("object is missing metadata to build owner reference"))] @@ -354,27 +354,24 @@ pub async fn reconcile_trino( for (trino_role, role_group_configs) in &validated_cluster.role_group_configs { for (role_group_name, rg) in role_group_configs { - let role_group_ref = trino_role.rolegroup_ref(trino, role_group_name); + let role_name = trino_role.to_string(); let merged_config = &rg.config; let role_group_service_recommended_labels = build_recommended_labels( &validated_cluster, &validated_cluster.image.app_version_label_value, - &role_group_ref.role, - &role_group_ref.role_group, + &role_name, + role_group_name, ); - let role_group_service_selector = Labels::role_group_selector( - trino, - APP_NAME, - &role_group_ref.role, - &role_group_ref.role_group, - ) - .context(LabelBuildSnafu)?; + let role_group_service_selector = + Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) + .context(LabelBuildSnafu)?; let rg_headless_service = build_rolegroup_headless_service( &validated_cluster, - &role_group_ref, + trino_role, + role_group_name, &role_group_service_recommended_labels, role_group_service_selector.clone().into(), headless_service_ports(trino), @@ -383,7 +380,8 @@ pub async fn reconcile_trino( let rg_metrics_service = build_rolegroup_metrics_service( &validated_cluster, - &role_group_ref, + trino_role, + role_group_name, &role_group_service_recommended_labels, role_group_service_selector.into(), ) @@ -392,28 +390,30 @@ pub async fn reconcile_trino( let rg_configmap = build::config_map::build_rolegroup_config_map( &validated_cluster, trino_role, - &role_group_ref, + role_group_name, &client.kubernetes_cluster_info, &role_group_service_recommended_labels, ) .with_context(|_| BuildRoleGroupConfigMapSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; let rg_catalog_configmap = build::config_map::build_rolegroup_catalog_config_map( &validated_cluster, - &role_group_ref, + trino_role, + role_group_name, &role_group_service_recommended_labels, ) .with_context(|_| BuildRoleGroupConfigMapSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; let rg_stateful_set = build_rolegroup_statefulset( trino, + &validated_cluster, trino_role, &validated_cluster.image, - &role_group_ref, + role_group_name, &rg.env_overrides, merged_config, &validated_cluster.cluster_config.authentication, @@ -428,28 +428,28 @@ pub async fn reconcile_trino( .add(client, rg_headless_service) .await .with_context(|_| ApplyRoleGroupServiceSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; cluster_resources .add(client, rg_metrics_service) .await .with_context(|_| ApplyRoleGroupServiceSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; cluster_resources .add(client, rg_configmap) .await .with_context(|_| ApplyRoleGroupConfigSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; cluster_resources .add(client, rg_catalog_configmap) .await .with_context(|_| ApplyRoleGroupConfigSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?; // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts @@ -460,7 +460,7 @@ pub async fn reconcile_trino( .add(client, rg_stateful_set) .await .with_context(|_| ApplyRoleGroupStatefulSetSnafu { - rolegroup: role_group_ref.clone(), + rolegroup: role_group_name.clone(), })?, ); } @@ -527,9 +527,10 @@ pub async fn reconcile_trino( #[allow(clippy::too_many_arguments)] fn build_rolegroup_statefulset( trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, trino_role: &TrinoRole, resolved_product_image: &ResolvedProductImage, - role_group_ref: &RoleGroupRef, + role_group_name: &str, env_overrides: &EnvVarSet, merged_config: &v1alpha1::TrinoConfig, trino_authentication_config: &TrinoAuthenticationConfig, @@ -543,9 +544,13 @@ fn build_rolegroup_statefulset( .role(trino_role) .context(InternalOperatorFailureSnafu)?; let rolegroup = trino - .rolegroup(role_group_ref) + .rolegroup(trino_role, role_group_name) .context(InternalOperatorFailureSnafu)?; + let role_name = trino_role.to_string(); + let resource_names = cluster.resource_names(trino_role, role_group_name); + let config_map_name = resource_names.role_group_config_map().to_string(); + let mut pod_builder = PodBuilder::new(); let prepare_container_name = Container::Prepare.to_string(); @@ -706,8 +711,8 @@ fn build_rolegroup_statefulset( trino, // A version value is required, and we do want to use the "recommended" format for the other desired labels "none", - &role_group_ref.role, - &role_group_ref.role_group, + &role_name, + role_group_name, )) .context(LabelBuildSnafu)?; @@ -774,7 +779,7 @@ fn build_rolegroup_statefulset( .add_volume(Volume { name: "log-config".to_string(), config_map: Some(ConfigMapVolumeSource { - name: role_group_ref.object_name(), + name: config_map_name.clone(), ..ConfigMapVolumeSource::default() }), ..Volume::default() @@ -812,8 +817,8 @@ fn build_rolegroup_statefulset( .with_recommended_labels(&build_recommended_labels( trino, &resolved_product_image.app_version_label_value, - &role_group_ref.role, - &role_group_ref.role_group, + &role_name, + role_group_name, )) .context(MetadataBuildSnafu)? .with_annotation( @@ -831,7 +836,7 @@ fn build_rolegroup_statefulset( .add_volume(Volume { name: "config".to_string(), config_map: Some(ConfigMapVolumeSource { - name: role_group_ref.object_name(), + name: config_map_name.clone(), ..ConfigMapVolumeSource::default() }), ..Volume::default() @@ -842,7 +847,7 @@ fn build_rolegroup_statefulset( .add_volume(Volume { name: "catalog".to_string(), config_map: Some(ConfigMapVolumeSource { - name: format!("{}-catalog", role_group_ref.object_name()), + name: format!("{config_map_name}-catalog"), ..ConfigMapVolumeSource::default() }), ..Volume::default() @@ -880,14 +885,14 @@ fn build_rolegroup_statefulset( Ok(StatefulSet { metadata: ObjectMetaBuilder::new() .name_and_namespace(trino) - .name(role_group_ref.object_name()) + .name(resource_names.stateful_set_name().to_string()) .ownerreference_from_resource(trino, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&build_recommended_labels( trino, &resolved_product_image.app_version_label_value, - &role_group_ref.role, - &role_group_ref.role_group, + &role_name, + role_group_name, )) .context(MetadataBuildSnafu)? .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) @@ -898,18 +903,13 @@ fn build_rolegroup_statefulset( replicas: rolegroup.replicas.map(i32::from), selector: LabelSelector { match_labels: Some( - Labels::role_group_selector( - trino, - APP_NAME, - &role_group_ref.role, - &role_group_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), + Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) + .context(LabelBuildSnafu)? + .into(), ), ..LabelSelector::default() }, - service_name: Some(role_group_ref.rolegroup_headless_service_name()), + service_name: Some(resource_names.headless_service_name().to_string()), template: pod_template, volume_claim_templates: Some(persistent_volume_claims), ..StatefulSetSpec::default() @@ -1263,8 +1263,7 @@ fn tls_volume_mounts( mod tests { use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::networking::DomainName, - k8s_openapi::api::core::v1::ConfigMap, kube::runtime::reflector::ObjectRef, - role_utils::RoleGroupRef, utils::cluster_info::KubernetesClusterInfo, + k8s_openapi::api::core::v1::ConfigMap, utils::cluster_info::KubernetesClusterInfo, v2::builder::pod::container::EnvVarName, }; @@ -1351,22 +1350,19 @@ mod tests { validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); let trino_role = TrinoRole::Coordinator; - let rolegroup_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(&trino), - role: trino_role.to_string(), - role_group: "default".to_string(), - }; + let role_name = trino_role.to_string(); + let role_group_name = "default"; let recommended_labels = build_recommended_labels( &validated_cluster, &validated_cluster.image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, + &role_name, + role_group_name, ); build::config_map::build_rolegroup_config_map( &validated_cluster, &trino_role, - &rolegroup_ref, + role_group_name, &cluster_info, &recommended_labels, ) From 3f6af2c1636ff27d5088711beecf13638cd0ea88 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 16:27:43 +0200 Subject: [PATCH 27/65] refactor: introduce ValidatedTrinoConfig --- rust/operator-binary/src/command.rs | 3 +- rust/operator-binary/src/config/jvm.rs | 10 +++-- .../src/controller/build/graceful_shutdown.rs | 4 +- rust/operator-binary/src/controller/mod.rs | 45 ++++++++++++++++++- .../src/controller/validate.rs | 6 +-- rust/operator-binary/src/trino_controller.rs | 4 +- 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index 453f399cf..9c2db7f0f 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -10,6 +10,7 @@ use crate::{ authentication::TrinoAuthenticationConfig, catalog::config::CatalogConfig, config::{client_protocol, fault_tolerant_execution}, + controller::ValidatedTrinoConfig, crd::{ CONFIG_DIR_NAME, Container, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, @@ -28,7 +29,7 @@ const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; pub fn container_prepare_args( trino: &v1alpha1::TrinoCluster, catalogs: &[CatalogConfig], - merged_config: &v1alpha1::TrinoConfig, + merged_config: &ValidatedTrinoConfig, resolved_fte_config: &Option, resolved_spooling_config: &Option, ) -> Vec { diff --git a/rust/operator-binary/src/config/jvm.rs b/rust/operator-binary/src/config/jvm.rs index 3561d81fe..8b2baccae 100644 --- a/rust/operator-binary/src/config/jvm.rs +++ b/rust/operator-binary/src/config/jvm.rs @@ -6,9 +6,11 @@ use stackable_operator::{ v2::jvm_argument_overrides::JvmArgumentOverrides, }; -use crate::crd::{ - METRICS_PORT, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - v1alpha1, +use crate::{ + controller::ValidatedTrinoConfig, + crd::{ + METRICS_PORT, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, + }, }; const JVM_SECURITY_PROPERTIES: &str = "security.properties"; @@ -42,7 +44,7 @@ pub enum Error { // in the future depending on the role and version. pub fn jvm_config( product_version: u16, - merged_config: &v1alpha1::TrinoConfig, + merged_config: &ValidatedTrinoConfig, jvm_argument_overrides: &JvmArgumentOverrides, ) -> Result { let memory_unit = BinaryMultiple::Mebi; diff --git a/rust/operator-binary/src/controller/build/graceful_shutdown.rs b/rust/operator-binary/src/controller/build/graceful_shutdown.rs index 2de7f1325..585d79cce 100644 --- a/rust/operator-binary/src/controller/build/graceful_shutdown.rs +++ b/rust/operator-binary/src/controller/build/graceful_shutdown.rs @@ -11,7 +11,7 @@ use stackable_operator::{ }; use crate::{ - controller::ValidatedCluster, + controller::{ValidatedCluster, ValidatedTrinoConfig}, crd::{DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT, TrinoRole, v1alpha1}, }; @@ -82,7 +82,7 @@ fn min_worker_graceful_shutdown_timeout( pub fn add_graceful_shutdown_config( trino: &v1alpha1::TrinoCluster, role: &TrinoRole, - merged_config: &v1alpha1::TrinoConfig, + merged_config: &ValidatedTrinoConfig, pod_builder: &mut PodBuilder, trino_builder: &mut ContainerBuilder, ) -> Result<(), Error> { diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 1c600f2d6..80c2e4516 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -4,8 +4,14 @@ use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ - commons::product_image_selection::ResolvedProductImage, + commons::{ + affinity::StackableAffinity, + product_image_selection::ResolvedProductImage, + resources::{NoRuntimeLimits, Resources}, + }, kube::{Resource, api::ObjectMeta}, + product_logging::spec::Logging, + shared::time::Duration, v2::{ role_group_utils::ResourceNames, types::{ @@ -23,7 +29,7 @@ use crate::{ client_protocol::ResolvedClientProtocolConfig, fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, - crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, + crd::{Container, TrinoRole, discovery::TrinoPodRef, v1alpha1}, }; pub(crate) mod build; @@ -51,6 +57,41 @@ pub struct ValidatedClusterConfig { pub catalogs: Vec, } +/// A validated, merged Trino role-group config. +/// +/// Holds the merged [`v1alpha1::TrinoConfig`] fields so the build steps consume this +/// controller-owned type instead of the raw CRD struct (mirroring the opensearch- and +/// hive-operators' `Validated…Config`). +/// +/// `logging` is still carried as the raw [`Logging`] for now; the up-front validation into a +/// `ValidatedLogging` (via the `v2` product-logging framework) lands together with the rest of the +/// logging migration in a later step. +#[derive(Clone, Debug)] +pub struct ValidatedTrinoConfig { + pub affinity: StackableAffinity, + pub graceful_shutdown_timeout: Option, + pub logging: Logging, + pub query_max_memory: Option, + pub query_max_memory_per_node: Option, + pub resources: Resources, + pub requested_secret_lifetime: Option, +} + +impl ValidatedTrinoConfig { + /// Builds the validated config from the merged [`v1alpha1::TrinoConfig`]. + fn from_merged(merged: v1alpha1::TrinoConfig) -> Self { + Self { + affinity: merged.affinity, + graceful_shutdown_timeout: merged.graceful_shutdown_timeout, + logging: merged.logging, + query_max_memory: merged.query_max_memory, + query_max_memory_per_node: merged.query_max_memory_per_node, + resources: merged.resources, + requested_secret_lifetime: merged.requested_secret_lifetime, + } + } +} + /// The validated TrinoCluster. The output of the validate step. #[derive(Clone, Debug)] pub struct ValidatedCluster { diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 8b42928de..5395acd53 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -20,7 +20,7 @@ use stackable_operator::{ }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; -use super::{ValidatedCluster, ValidatedClusterConfig, ValidatedTls}; +use super::{ValidatedCluster, ValidatedClusterConfig, ValidatedTls, ValidatedTrinoConfig}; use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, controller::dereference::DereferencedObjects, @@ -89,7 +89,7 @@ type Result = std::result::Result; pub type RoleGroupName = String; pub type TrinoRoleGroupConfig = - RoleGroupConfig; + RoleGroupConfig; /// Validates the cluster spec and dereferenced inputs. pub fn validate( @@ -216,7 +216,7 @@ fn into_role_group_config( Ok(RoleGroupConfig { replicas, - config: common.config, + config: ValidatedTrinoConfig::from_merged(common.config), config_overrides: common.config_overrides, env_overrides, cli_overrides: common.cli_overrides, diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 86e70a092..494b3f956 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -64,7 +64,7 @@ use crate::{ catalog::config::CatalogConfig, command, config::{client_protocol, fault_tolerant_execution}, - controller::{ValidatedCluster, build, dereference, validate}, + controller::{ValidatedCluster, ValidatedTrinoConfig, build, dereference, validate}, crd::{ APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, @@ -532,7 +532,7 @@ fn build_rolegroup_statefulset( resolved_product_image: &ResolvedProductImage, role_group_name: &str, env_overrides: &EnvVarSet, - merged_config: &v1alpha1::TrinoConfig, + merged_config: &ValidatedTrinoConfig, trino_authentication_config: &TrinoAuthenticationConfig, catalogs: &[CatalogConfig], sa_name: &str, From 44d71c4db94e5a78ded5e06a6c7acee8581f62da Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 16:41:26 +0200 Subject: [PATCH 28/65] refactor: move k8s resources to builder/resource --- rust/operator-binary/src/controller/build.rs | 8 +-- .../build/{ => resource}/config_map.rs | 0 .../build/resource}/listener.rs | 0 .../src/controller/build/resource/mod.rs | 11 +++ .../build/resource}/pdb.rs | 28 +++----- .../build/resource}/service.rs | 0 rust/operator-binary/src/main.rs | 3 - rust/operator-binary/src/operations/mod.rs | 1 - rust/operator-binary/src/trino_controller.rs | 70 ++++++++++++------- 9 files changed, 67 insertions(+), 54 deletions(-) rename rust/operator-binary/src/controller/build/{ => resource}/config_map.rs (100%) rename rust/operator-binary/src/{ => controller/build/resource}/listener.rs (100%) create mode 100644 rust/operator-binary/src/controller/build/resource/mod.rs rename rust/operator-binary/src/{operations => controller/build/resource}/pdb.rs (77%) rename rust/operator-binary/src/{ => controller/build/resource}/service.rs (100%) delete mode 100644 rust/operator-binary/src/operations/mod.rs diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index 24b6aa4ab..b2975aaf8 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -1,9 +1,9 @@ //! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. //! -//! `properties` renders the Trino `.properties` files; `config_map` assembles -//! the per-rolegroup ConfigMap; `graceful_shutdown` contributes graceful-shutdown -//! `config.properties` entries and Pod lifecycle configuration. +//! `properties` renders the Trino `.properties` files; `resource` builds the individual +//! Kubernetes resources (ConfigMap, Service, Listener, PDB, …); `graceful_shutdown` +//! contributes graceful-shutdown `config.properties` entries and Pod lifecycle configuration. -pub mod config_map; pub mod graceful_shutdown; pub mod properties; +pub mod resource; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs similarity index 100% rename from rust/operator-binary/src/controller/build/config_map.rs rename to rust/operator-binary/src/controller/build/resource/config_map.rs diff --git a/rust/operator-binary/src/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs similarity index 100% rename from rust/operator-binary/src/listener.rs rename to rust/operator-binary/src/controller/build/resource/listener.rs diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs new file mode 100644 index 000000000..fc1a740cb --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -0,0 +1,11 @@ +//! Builders for the individual Kubernetes resources of a TrinoCluster. +//! +//! Each submodule builds one kind of resource (ConfigMap, Service, Listener, PDB, …) and +//! *returns* it, rather than mutating shared state or applying it directly. The reconciler +//! collects the returned objects and applies them. This mirrors the hive- and +//! opensearch-operators' `controller/build/resource` layout. + +pub mod config_map; +pub mod listener; +pub mod pdb; +pub mod service; diff --git a/rust/operator-binary/src/operations/pdb.rs b/rust/operator-binary/src/controller/build/resource/pdb.rs similarity index 77% rename from rust/operator-binary/src/operations/pdb.rs rename to rust/operator-binary/src/controller/build/resource/pdb.rs index 245c80704..6e02f2c2a 100644 --- a/rust/operator-binary/src/operations/pdb.rs +++ b/rust/operator-binary/src/controller/build/resource/pdb.rs @@ -2,8 +2,8 @@ use std::cmp::max; use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pdb::PodDisruptionBudgetBuilder, client::Client, cluster_resources::ClusterResources, - commons::pdb::PdbConfig, kube::ResourceExt, + builder::pdb::PodDisruptionBudgetBuilder, commons::pdb::PdbConfig, + k8s_openapi::api::policy::v1::PodDisruptionBudget, }; use crate::{ @@ -18,23 +18,18 @@ pub enum Error { source: stackable_operator::builder::pdb::Error, role: String, }, - - #[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 given `role`, or `None` if PDBs are disabled. +/// +/// The reconciler applies the returned object; this function does not touch the cluster. +pub fn build_pdb( pdb: &PdbConfig, trino: &v1alpha1::TrinoCluster, role: &TrinoRole, - client: &Client, - cluster_resources: &mut ClusterResources<'_>, -) -> Result<(), Error> { +) -> Result, Error> { if !pdb.enabled { - return Ok(()); + return Ok(None); } let max_unavailable = pdb.max_unavailable.unwrap_or(match role { TrinoRole::Coordinator => max_unavailable_coordinators(), @@ -52,13 +47,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(()) + Ok(Some(pdb)) } fn max_unavailable_coordinators() -> u16 { diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs similarity index 100% rename from rust/operator-binary/src/service.rs rename to rust/operator-binary/src/controller/build/resource/service.rs diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 29ca43cc8..da6ec2d68 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -49,9 +49,6 @@ mod command; mod config; mod controller; mod crd; -mod listener; -mod operations; -mod service; mod trino_controller; mod webhooks; diff --git a/rust/operator-binary/src/operations/mod.rs b/rust/operator-binary/src/operations/mod.rs deleted file mode 100644 index d3cf6e9c4..000000000 --- a/rust/operator-binary/src/operations/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pdb; diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 494b3f956..5c4281536 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -64,7 +64,21 @@ use crate::{ catalog::config::CatalogConfig, command, config::{client_protocol, fault_tolerant_execution}, - controller::{ValidatedCluster, ValidatedTrinoConfig, build, dereference, validate}, + controller::{ + ValidatedCluster, ValidatedTrinoConfig, build, + build::resource::{ + listener::{ + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener, + build_group_listener_pvc, group_listener_name, secret_volume_listener_scope, + }, + pdb::build_pdb, + service::{ + build_rolegroup_headless_service, build_rolegroup_metrics_service, + headless_service_ports, + }, + }, + dereference, validate, + }, crd::{ APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, @@ -73,14 +87,6 @@ use crate::{ STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, TrinoRole, v1alpha1, }, - listener::{ - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener, build_group_listener_pvc, - group_listener_name, secret_volume_listener_scope, - }, - operations::pdb::add_pdbs, - service::{ - build_rolegroup_headless_service, build_rolegroup_metrics_service, headless_service_ports, - }, }; pub struct Ctx { @@ -127,7 +133,7 @@ pub enum Error { #[snafu(display("failed to build ConfigMap for {}", rolegroup))] BuildRoleGroupConfigMap { - source: build::config_map::Error, + source: build::resource::config_map::Error, rolegroup: String, }, @@ -184,8 +190,11 @@ pub enum Error { }, #[snafu(display("failed to create PodDisruptionBudget"))] - FailedToCreatePdb { - source: crate::operations::pdb::Error, + FailedToCreatePdb { source: build::resource::pdb::Error }, + + #[snafu(display("failed to apply PodDisruptionBudget"))] + ApplyPdb { + source: stackable_operator::cluster_resources::Error, }, #[snafu(display("failed to configure graceful shutdown"))] @@ -251,10 +260,14 @@ pub enum Error { }, #[snafu(display("failed to configure listener"))] - ListenerConfiguration { source: crate::listener::Error }, + ListenerConfiguration { + source: build::resource::listener::Error, + }, #[snafu(display("failed to configure service"))] - ServiceConfiguration { source: crate::service::Error }, + ServiceConfiguration { + source: build::resource::service::Error, + }, #[snafu(display("failed to create internal secret"))] CreateInternalSecret { @@ -387,7 +400,7 @@ pub async fn reconcile_trino( ) .context(ServiceConfigurationSnafu)?; - let rg_configmap = build::config_map::build_rolegroup_config_map( + let rg_configmap = build::resource::config_map::build_rolegroup_config_map( &validated_cluster, trino_role, role_group_name, @@ -398,15 +411,16 @@ pub async fn reconcile_trino( rolegroup: role_group_name.clone(), })?; - let rg_catalog_configmap = build::config_map::build_rolegroup_catalog_config_map( - &validated_cluster, - trino_role, - role_group_name, - &role_group_service_recommended_labels, - ) - .with_context(|_| BuildRoleGroupConfigMapSnafu { - rolegroup: role_group_name.clone(), - })?; + let rg_catalog_configmap = + build::resource::config_map::build_rolegroup_catalog_config_map( + &validated_cluster, + trino_role, + role_group_name, + &role_group_service_recommended_labels, + ) + .with_context(|_| BuildRoleGroupConfigMapSnafu { + rolegroup: role_group_name.clone(), + })?; let rg_stateful_set = build_rolegroup_statefulset( trino, @@ -491,10 +505,12 @@ pub async fn reconcile_trino( if let Some(GenericRoleConfig { pod_disruption_budget: pdb, }) = role_config + && let Some(pdb) = build_pdb(pdb, trino, trino_role).context(FailedToCreatePdbSnafu)? { - add_pdbs(pdb, trino, trino_role, client, &mut cluster_resources) + cluster_resources + .add(client, pdb) .await - .context(FailedToCreatePdbSnafu)?; + .context(ApplyPdbSnafu)?; } } @@ -1359,7 +1375,7 @@ mod tests { role_group_name, ); - build::config_map::build_rolegroup_config_map( + build::resource::config_map::build_rolegroup_config_map( &validated_cluster, &trino_role, role_group_name, From 3f6d28f60e8caae0f24498eb9e60e8473e59e599 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 17:11:31 +0200 Subject: [PATCH 29/65] refactor: move sts to resource folder --- .../src/controller/build/resource/mod.rs | 1 + .../controller/build/resource/statefulset.rs | 847 ++++++++++++++++++ rust/operator-binary/src/trino_controller.rs | 838 +---------------- 3 files changed, 870 insertions(+), 816 deletions(-) create mode 100644 rust/operator-binary/src/controller/build/resource/statefulset.rs diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs index fc1a740cb..600192351 100644 --- a/rust/operator-binary/src/controller/build/resource/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -9,3 +9,4 @@ pub mod config_map; pub mod listener; pub mod pdb; pub mod service; +pub mod statefulset; diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs new file mode 100644 index 000000000..4ea975f2a --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -0,0 +1,847 @@ +//! Builds the per-rolegroup [`StatefulSet`] that runs a Trino role group. + +use std::{collections::BTreeMap, convert::Infallible}; + +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{ + PodBuilder, + container::ContainerBuilder, + resources::ResourceRequirementsBuilder, + security::PodSecurityContextBuilder, + volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, + }, + }, + commons::{ + product_image_selection::ResolvedProductImage, + secret_class::SecretClassVolumeProvisionParts, + }, + constants::RESTART_CONTROLLER_ENABLED_LABEL, + k8s_openapi::{ + DeepMerge, + api::{ + apps::v1::{StatefulSet, StatefulSetSpec}, + core::v1::{ + ConfigMapVolumeSource, ContainerPort, EnvVar, EnvVarSource, ExecAction, + HTTPGetAction, Probe, SecretKeySelector, Volume, + }, + }, + apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, + }, + kvp::{Annotation, Annotations, Labels}, + product_logging::{ + self, + framework::LoggingError, + spec::{ + ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, + CustomContainerLogConfig, + }, + }, + shared::time::Duration, + v2::builder::pod::container::EnvVarSet, +}; + +use crate::{ + authentication::TrinoAuthenticationConfig, + authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, + catalog::config::CatalogConfig, + command, + config::{client_protocol, fault_tolerant_execution}, + controller::{ + ValidatedCluster, ValidatedTrinoConfig, build, + build::resource::listener::{ + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, + group_listener_name, secret_volume_listener_scope, + }, + }, + crd::{ + APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, + HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, + METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, + STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, + STACKABLE_TLS_STORE_PASSWORD, TrinoRole, v1alpha1, + }, + trino_controller::{ + MAX_PREPARE_LOG_FILE_SIZE, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, + build_recommended_labels, shared_internal_secret_name, shared_spooling_secret_name, + }, +}; + +#[derive(Snafu, Debug)] +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("internal operator failure: {source}"))] + InternalOperatorFailure { source: crate::crd::Error }, + + #[snafu(display("illegal container name: [{container_name}]"))] + IllegalContainerName { + source: stackable_operator::builder::pod::container::Error, + container_name: String, + }, + + #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] + VectorAggregatorConfigMapMissing, + + #[snafu(display("failed to build vector container"))] + BuildVectorContainer { source: LoggingError }, + + #[snafu(display("failed to configure graceful shutdown"))] + GracefulShutdown { + source: build::graceful_shutdown::Error, + }, + + #[snafu(display("failed to build Labels"))] + LabelBuild { + source: stackable_operator::kvp::LabelError, + }, + + #[snafu(display("failed to build Annotation"))] + AnnotationBuild { + source: stackable_operator::kvp::KeyValuePairError, + }, + + #[snafu(display("failed to build Metadata"))] + MetadataBuild { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to build TLS certificate SecretClass Volume"))] + TlsCertSecretClassVolumeBuild { + source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, + }, + + #[snafu(display("failed to add needed volume"))] + AddVolume { + source: stackable_operator::builder::pod::Error, + }, + + #[snafu(display("failed to add needed volumeMount"))] + AddVolumeMount { + source: stackable_operator::builder::pod::container::Error, + }, + + #[snafu(display("invalid Trino authentication"))] + InvalidAuthenticationConfig { + source: crate::authentication::Error, + }, + + #[snafu(display("failed to configure listener"))] + ListenerConfiguration { + source: build::resource::listener::Error, + }, +} + +type Result = std::result::Result; + +/// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator. +/// +/// The [`Pod`](`stackable_operator::k8s_openapi::api::core::v1::Pod`)s are accessible through the +/// corresponding [`stackable_operator::k8s_openapi::api::core::v1::Service`] (from +/// [`build_rolegroup_headless_service`](super::service::build_rolegroup_headless_service)). +#[allow(clippy::too_many_arguments)] +pub fn build_rolegroup_statefulset( + trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, + trino_role: &TrinoRole, + resolved_product_image: &ResolvedProductImage, + role_group_name: &str, + env_overrides: &EnvVarSet, + merged_config: &ValidatedTrinoConfig, + trino_authentication_config: &TrinoAuthenticationConfig, + catalogs: &[CatalogConfig], + sa_name: &str, + resolved_fte_config: &Option, + resolved_spooling_config: &Option, + trino_opa_config: &Option, +) -> Result { + let role = trino + .role(trino_role) + .context(InternalOperatorFailureSnafu)?; + let rolegroup = trino + .rolegroup(trino_role, role_group_name) + .context(InternalOperatorFailureSnafu)?; + + let role_name = trino_role.to_string(); + let resource_names = cluster.resource_names(trino_role, role_group_name); + let config_map_name = resource_names.role_group_config_map().to_string(); + + let mut pod_builder = PodBuilder::new(); + + let prepare_container_name = Container::Prepare.to_string(); + let mut cb_prepare = ContainerBuilder::new(&prepare_container_name).with_context(|_| { + IllegalContainerNameSnafu { + container_name: prepare_container_name.clone(), + } + })?; + + let trino_container_name = Container::Trino.to_string(); + let mut cb_trino = ContainerBuilder::new(&trino_container_name).with_context(|_| { + IllegalContainerNameSnafu { + container_name: trino_container_name.clone(), + } + })?; + + // additional authentication env vars + let mut env = trino_authentication_config.env_vars(trino_role, &Container::Trino); + + let internal_secret_name = shared_internal_secret_name(trino); + env.push(env_var_from_secret( + &internal_secret_name, + None, + ENV_INTERNAL_SECRET, + )); + + let spooling_secret_name = shared_spooling_secret_name(trino); + env.push(env_var_from_secret( + &spooling_secret_name, + None, + ENV_SPOOLING_SECRET, + )); + + trino_authentication_config + .add_authentication_pod_and_volume_config( + trino_role, + &mut pod_builder, + &mut cb_prepare, + &mut cb_trino, + ) + .context(InvalidAuthenticationConfigSnafu)?; + build::graceful_shutdown::add_graceful_shutdown_config( + trino, + trino_role, + merged_config, + &mut pod_builder, + &mut cb_trino, + ) + .context(GracefulShutdownSnafu)?; + + // Add the needed stuff for catalogs + env.extend( + catalogs + .iter() + .flat_map(|catalog| &catalog.env_bindings) + .cloned(), + ); + + // Needed by the `containerdebug` process to log it's tracing information to. + // This process runs in the background of the `trino` container. + // See command::container_trino_args() for how it's called. + env.push(EnvVar { + name: "CONTAINERDEBUG_LOG_DIRECTORY".into(), + value: Some(format!("{STACKABLE_LOG_DIR}/containerdebug")), + ..EnvVar::default() + }); + + // Finally add the user defined envOverrides properties. + env.extend(env_overrides.clone()); + + let requested_secret_lifetime = merged_config + .requested_secret_lifetime + .context(MissingSecretLifetimeSnafu)?; + + // add volume mounts depending on the client tls, internal tls, catalogs and authentication + tls_volume_mounts( + trino, + trino_role, + &mut pod_builder, + &mut cb_prepare, + &mut cb_trino, + catalogs, + &requested_secret_lifetime, + resolved_fte_config, + resolved_spooling_config, + trino_opa_config, + )?; + + let mut prepare_args = vec![]; + if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = merged_config.logging.containers.get(&Container::Prepare) + { + prepare_args.push(product_logging::framework::capture_shell_output( + STACKABLE_LOG_DIR, + &prepare_container_name, + log_config, + )); + } + + prepare_args.extend(command::container_prepare_args( + trino, + catalogs, + merged_config, + resolved_fte_config, + resolved_spooling_config, + )); + + prepare_args + .extend(trino_authentication_config.commands(&TrinoRole::Coordinator, &Container::Prepare)); + + // Add OPA TLS certificate to truststore if configured + if let Some(tls_mount_path) = trino_opa_config + .as_ref() + .and_then(|opa_config| opa_config.tls_mount_path()) + { + prepare_args.extend(command::add_cert_to_truststore( + format!("{}/ca.crt", tls_mount_path).as_str(), + STACKABLE_CLIENT_TLS_DIR, + )); + } + + let container_prepare = cb_prepare + .image_from_product_image(resolved_product_image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![prepare_args.join("\n")]) + .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) + .context(AddVolumeMountSnafu)? + .add_volume_mount("log-config", STACKABLE_LOG_CONFIG_DIR) + .context(AddVolumeMountSnafu)? + .add_volume_mount("log", STACKABLE_LOG_DIR) + .context(AddVolumeMountSnafu)? + .resources( + ResourceRequirementsBuilder::new() + .with_cpu_request("500m") + .with_cpu_limit("2000m") + .with_memory_request("4Gi") + .with_memory_limit("4Gi") + .build(), + ) + .build(); + + let mut persistent_volume_claims = vec![]; + // Add listener + if let Some(group_listener_name) = group_listener_name(trino, trino_role) { + cb_trino + .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) + .context(AddVolumeMountSnafu)?; + + // Used for PVC templates that cannot be modified once they are deployed + let unversioned_recommended_labels = Labels::recommended(&build_recommended_labels( + trino, + // A version value is required, and we do want to use the "recommended" format for the other desired labels + "none", + &role_name, + role_group_name, + )) + .context(LabelBuildSnafu)?; + + persistent_volume_claims.push( + build_group_listener_pvc(&group_listener_name, &unversioned_recommended_labels) + .context(ListenerConfigurationSnafu)?, + ); + } + + let container_trino = cb_trino + .image_from_product_image(resolved_product_image) + .command(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ]) + .args(vec![ + command::container_trino_args(trino_authentication_config, catalogs).join("\n"), + ]) + .add_env_vars(env) + .add_volume_mount("config", CONFIG_DIR_NAME) + .context(AddVolumeMountSnafu)? + .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) + .context(AddVolumeMountSnafu)? + .add_volume_mount("catalog", format!("{}/catalog", CONFIG_DIR_NAME)) + .context(AddVolumeMountSnafu)? + .add_volume_mount("log", STACKABLE_LOG_DIR) + .context(AddVolumeMountSnafu)? + .add_container_ports(container_ports(trino)) + .resources(merged_config.resources.clone().into()) + // The probes are set on coordinators and workers + .startup_probe(startup_probe(trino)) + .readiness_probe(readiness_probe(trino)) + .liveness_probe(liveness_probe(trino)) + .build(); + + // add trino container first to better default into that container (e.g. instead of vector) + pod_builder.add_container(container_trino); + + // add password-update container if required + trino_authentication_config.add_authentication_containers(trino_role, &mut pod_builder); + + if let Some(ContainerLogConfig { + choice: + Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { + custom: ConfigMapLogConfig { config_map }, + })), + }) = merged_config.logging.containers.get(&Container::Trino) + { + pod_builder + .add_volume(Volume { + name: "log-config".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: config_map.into(), + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }) + .context(AddVolumeSnafu)?; + } else { + pod_builder + .add_volume(Volume { + name: "log-config".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: config_map_name.clone(), + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }) + .context(AddVolumeSnafu)?; + } + + if merged_config.logging.enable_vector_agent { + match &trino.spec.cluster_config.vector_aggregator_config_map_name { + Some(vector_aggregator_config_map_name) => { + pod_builder.add_container( + product_logging::framework::vector_container( + resolved_product_image, + "config", + "log", + merged_config.logging.containers.get(&Container::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(BuildVectorContainerSnafu)?, + ); + } + None => { + VectorAggregatorConfigMapMissingSnafu.fail()?; + } + } + } + + let metadata = ObjectMetaBuilder::new() + .with_recommended_labels(&build_recommended_labels( + trino, + &resolved_product_image.app_version_label_value, + &role_name, + role_group_name, + )) + .context(MetadataBuildSnafu)? + .with_annotation( + // This is actually used by some kuttl tests (as they don't specify the container explicitly) + Annotation::try_from(("kubectl.kubernetes.io/default-container", "trino")) + .context(AnnotationBuildSnafu)?, + ) + .build(); + + pod_builder + .metadata(metadata) + .image_pull_secrets_from_product_image(resolved_product_image) + .affinity(&merged_config.affinity) + .add_init_container(container_prepare) + .add_volume(Volume { + name: "config".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: config_map_name.clone(), + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }) + .context(AddVolumeSnafu)? + .add_empty_dir_volume("rwconfig", None) + .context(AddVolumeSnafu)? + .add_volume(Volume { + name: "catalog".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: format!("{config_map_name}-catalog"), + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }) + .context(AddVolumeSnafu)? + .add_empty_dir_volume( + "log", + Some(product_logging::framework::calculate_log_volume_size_limit( + &[MAX_TRINO_LOG_FILES_SIZE, MAX_PREPARE_LOG_FILE_SIZE], + )), + ) + .context(AddVolumeSnafu)? + .service_account_name(sa_name) + .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); + + let mut pod_template = pod_builder.build_template(); + pod_template.merge_from(role.config.pod_overrides.clone()); + pod_template.merge_from(rolegroup.config.pod_overrides.clone()); + + let ignore_secret_annotations = trino_authentication_config + .hot_reloaded_secrets() + .iter() + .enumerate() + .map(|(i, secret_name)| { + ( + format!("restarter.stackable.tech/ignore-secret.{i}"), + secret_name, + ) + }) + .collect::>(); + + let annotations = + Annotations::try_from(ignore_secret_annotations).context(AnnotationBuildSnafu)?; + + Ok(StatefulSet { + metadata: ObjectMetaBuilder::new() + .name_and_namespace(trino) + .name(resource_names.stateful_set_name().to_string()) + .ownerreference_from_resource(trino, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(&build_recommended_labels( + trino, + &resolved_product_image.app_version_label_value, + &role_name, + role_group_name, + )) + .context(MetadataBuildSnafu)? + .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) + .with_annotations(annotations) + .build(), + spec: Some(StatefulSetSpec { + pod_management_policy: Some("Parallel".to_string()), + replicas: rolegroup.replicas.map(i32::from), + selector: LabelSelector { + match_labels: Some( + Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) + .context(LabelBuildSnafu)? + .into(), + ), + ..LabelSelector::default() + }, + service_name: Some(resource_names.headless_service_name().to_string()), + template: pod_template, + volume_claim_templates: Some(persistent_volume_claims), + ..StatefulSetSpec::default() + }), + status: None, + }) +} + +/// Give a secret name and an optional key in the secret to use. +/// The value from the key will be set into the given env var name. +/// If not secret key is given, the env var name will be used as the secret key. +fn env_var_from_secret(secret_name: &str, secret_key: Option<&str>, env_var: &str) -> EnvVar { + EnvVar { + name: env_var.to_string(), + value_from: Some(EnvVarSource { + secret_key_ref: Some(SecretKeySelector { + optional: Some(false), + name: secret_name.to_string(), + key: secret_key.unwrap_or(env_var).to_string(), + }), + ..EnvVarSource::default() + }), + ..EnvVar::default() + } +} + +fn container_ports(trino: &v1alpha1::TrinoCluster) -> Vec { + let mut ports = vec![ContainerPort { + name: Some(METRICS_PORT_NAME.to_string()), + container_port: METRICS_PORT.into(), + protocol: Some("TCP".to_string()), + ..ContainerPort::default() + }]; + + if trino.expose_http_port() { + ports.push(ContainerPort { + name: Some(HTTP_PORT_NAME.to_string()), + container_port: HTTP_PORT.into(), + protocol: Some("TCP".to_string()), + ..ContainerPort::default() + }) + } + + if trino.expose_https_port() { + ports.push(ContainerPort { + name: Some(HTTPS_PORT_NAME.to_string()), + container_port: HTTPS_PORT.into(), + protocol: Some("TCP".to_string()), + ..ContainerPort::default() + }); + } + + ports +} + +fn startup_probe(trino: &v1alpha1::TrinoCluster) -> Probe { + Probe { + exec: Some(finished_starting_probe(trino)), + period_seconds: Some(5), + // Give the coordinator or worker 10 minutes to start up + failure_threshold: Some(120), + timeout_seconds: Some(3), + ..Default::default() + } +} + +fn readiness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { + Probe { + http_get: Some(http_get_probe(trino)), + period_seconds: Some(5), + failure_threshold: Some(1), + timeout_seconds: Some(3), + ..Probe::default() + } +} + +fn liveness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { + Probe { + http_get: Some(http_get_probe(trino)), + period_seconds: Some(5), + // Coordinators are currently not highly available, so you always have a singe instance. + // Restarting it causes all queries to fail, so let's not restart it directly after the first + // probe failure, but wait for 3 failures + // NOTE: This also applies to workers + failure_threshold: Some(3), + timeout_seconds: Some(3), + ..Probe::default() + } +} + +/// Check that `/v1/info` returns `200`. +/// +/// This is the same probe as the [upstream helm-chart](https://github.com/trinodb/charts/blob/7cd0a7bff6c52e0ee6ca6d5394cd72c150ad4379/charts/trino/templates/deployment-coordinator.yaml#L214) +/// is using. +fn http_get_probe(trino: &v1alpha1::TrinoCluster) -> HTTPGetAction { + let (schema, port_name) = if trino.expose_https_port() { + ("HTTPS", HTTPS_PORT_NAME) + } else { + ("HTTP", HTTP_PORT_NAME) + }; + + HTTPGetAction { + port: IntOrString::String(port_name.to_string()), + scheme: Some(schema.to_string()), + path: Some("/v1/info".to_string()), + ..Default::default() + } +} + +/// Wait until `/v1/info` returns `"starting":false`. +/// +/// This probe works on coordinators and workers. +fn finished_starting_probe(trino: &v1alpha1::TrinoCluster) -> ExecAction { + let port = trino.exposed_port(); + let schema = if trino.expose_https_port() { + "https" + } else { + "http" + }; + + ExecAction { + command: Some(vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + format!( + "curl --fail --insecure {schema}://127.0.0.1:{port}/v1/info | grep --silent '\\\"starting\\\":false'" + ), + ]), + } +} + +fn create_tls_volume( + volume_name: &str, + tls_secret_class: &str, + requested_secret_lifetime: &Duration, + listener_scope: Option, +) -> Result { + let mut secret_volume_source_builder = SecretOperatorVolumeSourceBuilder::new( + tls_secret_class, + SecretClassVolumeProvisionParts::PublicPrivate, + ); + + secret_volume_source_builder + .with_pod_scope() + .with_format(SecretFormat::TlsPkcs12) + .with_tls_pkcs12_password(STACKABLE_TLS_STORE_PASSWORD) + .with_auto_tls_cert_lifetime(*requested_secret_lifetime); + + if let Some(listener_scope) = &listener_scope { + secret_volume_source_builder.with_listener_volume_scope(listener_scope); + } + + Ok(VolumeBuilder::new(volume_name) + .ephemeral( + secret_volume_source_builder + .build() + .context(TlsCertSecretClassVolumeBuildSnafu)?, + ) + .build()) +} + +#[allow(clippy::too_many_arguments)] +fn tls_volume_mounts( + trino: &v1alpha1::TrinoCluster, + trino_role: &TrinoRole, + pod_builder: &mut PodBuilder, + cb_prepare: &mut ContainerBuilder, + cb_trino: &mut ContainerBuilder, + catalogs: &[CatalogConfig], + requested_secret_lifetime: &Duration, + resolved_fte_config: &Option, + resolved_spooling_config: &Option, + trino_opa_config: &Option, +) -> Result<()> { + if let Some(server_tls) = trino.get_server_tls() { + cb_prepare + .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_volume(create_tls_volume( + "server-tls-mount", + server_tls, + requested_secret_lifetime, + // add listener + secret_volume_listener_scope(trino_role), + )?) + .context(AddVolumeSnafu)?; + } + + cb_prepare + .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_empty_dir_volume("server-tls", None) + .context(AddVolumeSnafu)?; + + cb_prepare + .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_empty_dir_volume("client-tls", None) + .context(AddVolumeSnafu)?; + + if let Some(internal_tls) = trino.get_internal_tls() { + cb_prepare + .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_volume(create_tls_volume( + "internal-tls-mount", + internal_tls, + requested_secret_lifetime, + None, + )?) + .context(AddVolumeSnafu)?; + + cb_prepare + .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_empty_dir_volume("internal-tls", None) + .context(AddVolumeSnafu)?; + } + + // catalogs + for catalog in catalogs { + cb_prepare + .add_volume_mounts(catalog.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mounts(catalog.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_volumes(catalog.volumes.clone()) + .context(AddVolumeSnafu)?; + } + + // Add OPA TLS certs if configured + if let Some((tls_secret_class, tls_mount_path)) = + trino_opa_config.as_ref().and_then(|opa_config| { + opa_config + .tls_secret_class + .as_ref() + .zip(opa_config.tls_mount_path()) + }) + { + cb_prepare + .add_volume_mount(OPA_TLS_VOLUME_NAME, &tls_mount_path) + .context(AddVolumeMountSnafu)?; + + let opa_tls_volume = VolumeBuilder::new(OPA_TLS_VOLUME_NAME) + .ephemeral( + SecretOperatorVolumeSourceBuilder::new( + tls_secret_class, + SecretClassVolumeProvisionParts::PublicPrivate, + ) + .build() + .context(TlsCertSecretClassVolumeBuildSnafu)?, + ) + .build(); + + pod_builder + .add_volume(opa_tls_volume) + .context(AddVolumeSnafu)?; + } + + // fault tolerant execution S3 credentials and other resources + if let Some(resolved_fte) = resolved_fte_config { + cb_prepare + .add_volume_mounts(resolved_fte.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mounts(resolved_fte.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_volumes(resolved_fte.volumes.clone()) + .context(AddVolumeSnafu)?; + } + + // client spooling S3 credentials and other resources + if let Some(resolved_spooling) = resolved_spooling_config { + cb_prepare + .add_volume_mounts(resolved_spooling.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + cb_trino + .add_volume_mounts(resolved_spooling.volume_mounts.clone()) + .context(AddVolumeMountSnafu)?; + pod_builder + .add_volumes(resolved_spooling.volumes.clone()) + .context(AddVolumeSnafu)?; + } + + Ok(()) +} diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 5c4281536..370123764 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -1,75 +1,38 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha1::TrinoCluster`] -use std::{collections::BTreeMap, convert::Infallible, sync::Arc}; +use std::sync::Arc; use const_format::concatcp; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{ - meta::ObjectMetaBuilder, - pod::{ - PodBuilder, - container::ContainerBuilder, - resources::ResourceRequirementsBuilder, - security::PodSecurityContextBuilder, - volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, - }, - }, cli::OperatorEnvironmentOptions, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{ - product_image_selection::ResolvedProductImage, random_secret_creation, - rbac::build_rbac_resources, secret_class::SecretClassVolumeProvisionParts, - }, - constants::RESTART_CONTROLLER_ENABLED_LABEL, - k8s_openapi::{ - DeepMerge, - api::{ - apps::v1::{StatefulSet, StatefulSetSpec}, - core::v1::{ - ConfigMapVolumeSource, ContainerPort, EnvVar, EnvVarSource, ExecAction, - HTTPGetAction, Probe, SecretKeySelector, Volume, - }, - }, - apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, + random_secret_creation, + rbac::build_rbac_resources, }, kube::{ Resource, ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, - kvp::{Annotation, Annotations, Labels, ObjectLabels}, + kvp::{Labels, ObjectLabels}, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, - product_logging::{ - self, - framework::LoggingError, - spec::{ - ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, - CustomContainerLogConfig, - }, - }, role_utils::GenericRoleConfig, shared::time::Duration, status::condition::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, - v2::builder::pod::container::EnvVarSet, }; use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - authentication::TrinoAuthenticationConfig, - authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, - catalog::config::CatalogConfig, - command, - config::{client_protocol, fault_tolerant_execution}, controller::{ - ValidatedCluster, ValidatedTrinoConfig, build, + build, build::resource::{ listener::{ - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener, - build_group_listener_pvc, group_listener_name, secret_volume_listener_scope, + build_group_listener, group_listener_name, }, pdb::build_pdb, service::{ @@ -80,12 +43,7 @@ use crate::{ dereference, validate, }, crd::{ - APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, - HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, - METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, - STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, - STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - TrinoRole, v1alpha1, + APP_NAME, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, v1alpha1, }, }; @@ -112,9 +70,6 @@ pub(crate) const CONTAINER_IMAGE_BASE_NAME: &str = "trino"; #[strum_discriminants(derive(IntoStaticStr))] #[allow(clippy::enum_variant_names)] pub enum Error { - #[snafu(display("missing secret lifetime"))] - MissingSecretLifetime, - #[snafu(display("failed to create cluster resources"))] CreateClusterResources { source: stackable_operator::cluster_resources::Error, @@ -143,32 +98,18 @@ pub enum Error { rolegroup: String, }, + #[snafu(display("failed to build StatefulSet for {}", rolegroup))] + BuildRoleGroupStatefulSet { + source: build::resource::statefulset::Error, + rolegroup: String, + }, + #[snafu(display("failed to apply StatefulSet for {}", rolegroup))] ApplyRoleGroupStatefulSet { source: stackable_operator::cluster_resources::Error, rolegroup: String, }, - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("internal operator failure: {source}"))] - InternalOperatorFailure { source: crate::crd::Error }, - - #[snafu(display("illegal container name: [{container_name}]"))] - IllegalContainerName { - source: stackable_operator::builder::pod::container::Error, - container_name: String, - }, - - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - - #[snafu(display("failed to build vector container"))] - BuildVectorContainer { source: LoggingError }, - #[snafu(display("failed to patch service account"))] ApplyServiceAccount { source: stackable_operator::cluster_resources::Error, @@ -197,11 +138,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to configure graceful shutdown"))] - GracefulShutdown { - source: build::graceful_shutdown::Error, - }, - #[snafu(display("failed to get required Labels"))] GetRequiredLabels { source: @@ -213,31 +149,6 @@ pub enum Error { source: stackable_operator::kvp::LabelError, }, - #[snafu(display("failed to build Annotation"))] - AnnotationBuild { - source: stackable_operator::kvp::KeyValuePairError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("failed to build TLS certificate SecretClass Volume"))] - TlsCertSecretClassVolumeBuild { - source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, - }, - - #[snafu(display("failed to add needed volume"))] - AddVolume { - source: stackable_operator::builder::pod::Error, - }, - - #[snafu(display("failed to add needed volumeMount"))] - AddVolumeMount { - source: stackable_operator::builder::pod::container::Error, - }, - #[snafu(display("invalid TrinoCluster object"))] InvalidTrinoCluster { source: error_boundary::InvalidObject, @@ -249,11 +160,6 @@ pub enum Error { #[snafu(display("failed to validate cluster"))] ValidateCluster { source: validate::Error }, - #[snafu(display("invalid Trino authentication"))] - InvalidAuthenticationConfig { - source: crate::authentication::Error, - }, - #[snafu(display("failed to apply group listener"))] ApplyGroupListener { source: stackable_operator::cluster_resources::Error, @@ -422,7 +328,7 @@ pub async fn reconcile_trino( rolegroup: role_group_name.clone(), })?; - let rg_stateful_set = build_rolegroup_statefulset( + let rg_stateful_set = build::resource::statefulset::build_rolegroup_statefulset( trino, &validated_cluster, trino_role, @@ -436,7 +342,10 @@ pub async fn reconcile_trino( &validated_cluster.cluster_config.fault_tolerant_execution, &validated_cluster.cluster_config.client_protocol, &validated_cluster.cluster_config.authorization, - )?; + ) + .with_context(|_| BuildRoleGroupStatefulSetSnafu { + rolegroup: role_group_name.clone(), + })?; cluster_resources .add(client, rg_headless_service) @@ -536,404 +445,6 @@ pub async fn reconcile_trino( Ok(Action::await_change()) } -/// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator. -/// -/// The [`Pod`](`stackable_operator::k8s_openapi::api::core::v1::Pod`)s are accessible through the -/// corresponding [`stackable_operator::k8s_openapi::api::core::v1::Service`] (from [`build_rolegroup_headless_service`]). -#[allow(clippy::too_many_arguments)] -fn build_rolegroup_statefulset( - trino: &v1alpha1::TrinoCluster, - cluster: &ValidatedCluster, - trino_role: &TrinoRole, - resolved_product_image: &ResolvedProductImage, - role_group_name: &str, - env_overrides: &EnvVarSet, - merged_config: &ValidatedTrinoConfig, - trino_authentication_config: &TrinoAuthenticationConfig, - catalogs: &[CatalogConfig], - sa_name: &str, - resolved_fte_config: &Option, - resolved_spooling_config: &Option, - trino_opa_config: &Option, -) -> Result { - let role = trino - .role(trino_role) - .context(InternalOperatorFailureSnafu)?; - let rolegroup = trino - .rolegroup(trino_role, role_group_name) - .context(InternalOperatorFailureSnafu)?; - - let role_name = trino_role.to_string(); - let resource_names = cluster.resource_names(trino_role, role_group_name); - let config_map_name = resource_names.role_group_config_map().to_string(); - - let mut pod_builder = PodBuilder::new(); - - let prepare_container_name = Container::Prepare.to_string(); - let mut cb_prepare = ContainerBuilder::new(&prepare_container_name).with_context(|_| { - IllegalContainerNameSnafu { - container_name: prepare_container_name.clone(), - } - })?; - - let trino_container_name = Container::Trino.to_string(); - let mut cb_trino = ContainerBuilder::new(&trino_container_name).with_context(|_| { - IllegalContainerNameSnafu { - container_name: trino_container_name.clone(), - } - })?; - - // additional authentication env vars - let mut env = trino_authentication_config.env_vars(trino_role, &Container::Trino); - - let internal_secret_name = shared_internal_secret_name(trino); - env.push(env_var_from_secret( - &internal_secret_name, - None, - ENV_INTERNAL_SECRET, - )); - - let spooling_secret_name = shared_spooling_secret_name(trino); - env.push(env_var_from_secret( - &spooling_secret_name, - None, - ENV_SPOOLING_SECRET, - )); - - trino_authentication_config - .add_authentication_pod_and_volume_config( - trino_role, - &mut pod_builder, - &mut cb_prepare, - &mut cb_trino, - ) - .context(InvalidAuthenticationConfigSnafu)?; - build::graceful_shutdown::add_graceful_shutdown_config( - trino, - trino_role, - merged_config, - &mut pod_builder, - &mut cb_trino, - ) - .context(GracefulShutdownSnafu)?; - - // Add the needed stuff for catalogs - env.extend( - catalogs - .iter() - .flat_map(|catalog| &catalog.env_bindings) - .cloned(), - ); - - // Needed by the `containerdebug` process to log it's tracing information to. - // This process runs in the background of the `trino` container. - // See command::container_trino_args() for how it's called. - env.push(EnvVar { - name: "CONTAINERDEBUG_LOG_DIRECTORY".into(), - value: Some(format!("{STACKABLE_LOG_DIR}/containerdebug")), - ..EnvVar::default() - }); - - // Finally add the user defined envOverrides properties. - env.extend(env_overrides.clone()); - - let requested_secret_lifetime = merged_config - .requested_secret_lifetime - .context(MissingSecretLifetimeSnafu)?; - - // add volume mounts depending on the client tls, internal tls, catalogs and authentication - tls_volume_mounts( - trino, - trino_role, - &mut pod_builder, - &mut cb_prepare, - &mut cb_trino, - catalogs, - &requested_secret_lifetime, - resolved_fte_config, - resolved_spooling_config, - trino_opa_config, - )?; - - let mut prepare_args = vec![]; - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = merged_config.logging.containers.get(&Container::Prepare) - { - prepare_args.push(product_logging::framework::capture_shell_output( - STACKABLE_LOG_DIR, - &prepare_container_name, - log_config, - )); - } - - prepare_args.extend(command::container_prepare_args( - trino, - catalogs, - merged_config, - resolved_fte_config, - resolved_spooling_config, - )); - - prepare_args - .extend(trino_authentication_config.commands(&TrinoRole::Coordinator, &Container::Prepare)); - - // Add OPA TLS certificate to truststore if configured - if let Some(tls_mount_path) = trino_opa_config - .as_ref() - .and_then(|opa_config| opa_config.tls_mount_path()) - { - prepare_args.extend(command::add_cert_to_truststore( - format!("{}/ca.crt", tls_mount_path).as_str(), - STACKABLE_CLIENT_TLS_DIR, - )); - } - - let container_prepare = cb_prepare - .image_from_product_image(resolved_product_image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![prepare_args.join("\n")]) - .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) - .context(AddVolumeMountSnafu)? - .add_volume_mount("log-config", STACKABLE_LOG_CONFIG_DIR) - .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) - .context(AddVolumeMountSnafu)? - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("500m") - .with_cpu_limit("2000m") - .with_memory_request("4Gi") - .with_memory_limit("4Gi") - .build(), - ) - .build(); - - let mut persistent_volume_claims = vec![]; - // Add listener - if let Some(group_listener_name) = group_listener_name(trino, trino_role) { - cb_trino - .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) - .context(AddVolumeMountSnafu)?; - - // Used for PVC templates that cannot be modified once they are deployed - let unversioned_recommended_labels = Labels::recommended(&build_recommended_labels( - trino, - // A version value is required, and we do want to use the "recommended" format for the other desired labels - "none", - &role_name, - role_group_name, - )) - .context(LabelBuildSnafu)?; - - persistent_volume_claims.push( - build_group_listener_pvc(&group_listener_name, &unversioned_recommended_labels) - .context(ListenerConfigurationSnafu)?, - ); - } - - let container_trino = cb_trino - .image_from_product_image(resolved_product_image) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![ - command::container_trino_args(trino_authentication_config, catalogs).join("\n"), - ]) - .add_env_vars(env) - .add_volume_mount("config", CONFIG_DIR_NAME) - .context(AddVolumeMountSnafu)? - .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) - .context(AddVolumeMountSnafu)? - .add_volume_mount("catalog", format!("{}/catalog", CONFIG_DIR_NAME)) - .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) - .context(AddVolumeMountSnafu)? - .add_container_ports(container_ports(trino)) - .resources(merged_config.resources.clone().into()) - // The probes are set on coordinators and workers - .startup_probe(startup_probe(trino)) - .readiness_probe(readiness_probe(trino)) - .liveness_probe(liveness_probe(trino)) - .build(); - - // add trino container first to better default into that container (e.g. instead of vector) - pod_builder.add_container(container_trino); - - // add password-update container if required - trino_authentication_config.add_authentication_containers(trino_role, &mut pod_builder); - - if let Some(ContainerLogConfig { - choice: - Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { - custom: ConfigMapLogConfig { config_map }, - })), - }) = merged_config.logging.containers.get(&Container::Trino) - { - pod_builder - .add_volume(Volume { - name: "log-config".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: config_map.into(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)?; - } else { - pod_builder - .add_volume(Volume { - name: "log-config".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: config_map_name.clone(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)?; - } - - if merged_config.logging.enable_vector_agent { - match &trino.spec.cluster_config.vector_aggregator_config_map_name { - Some(vector_aggregator_config_map_name) => { - pod_builder.add_container( - product_logging::framework::vector_container( - resolved_product_image, - "config", - "log", - merged_config.logging.containers.get(&Container::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(BuildVectorContainerSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } - } - - let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &role_name, - role_group_name, - )) - .context(MetadataBuildSnafu)? - .with_annotation( - // This is actually used by some kuttl tests (as they don't specify the container explicitly) - Annotation::try_from(("kubectl.kubernetes.io/default-container", "trino")) - .context(AnnotationBuildSnafu)?, - ) - .build(); - - pod_builder - .metadata(metadata) - .image_pull_secrets_from_product_image(resolved_product_image) - .affinity(&merged_config.affinity) - .add_init_container(container_prepare) - .add_volume(Volume { - name: "config".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: config_map_name.clone(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)? - .add_empty_dir_volume("rwconfig", None) - .context(AddVolumeSnafu)? - .add_volume(Volume { - name: "catalog".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: format!("{config_map_name}-catalog"), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)? - .add_empty_dir_volume( - "log", - Some(product_logging::framework::calculate_log_volume_size_limit( - &[MAX_TRINO_LOG_FILES_SIZE, MAX_PREPARE_LOG_FILE_SIZE], - )), - ) - .context(AddVolumeSnafu)? - .service_account_name(sa_name) - .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); - - let mut pod_template = pod_builder.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - pod_template.merge_from(rolegroup.config.pod_overrides.clone()); - - let ignore_secret_annotations = trino_authentication_config - .hot_reloaded_secrets() - .iter() - .enumerate() - .map(|(i, secret_name)| { - ( - format!("restarter.stackable.tech/ignore-secret.{i}"), - secret_name, - ) - }) - .collect::>(); - - let annotations = - Annotations::try_from(ignore_secret_annotations).context(AnnotationBuildSnafu)?; - - Ok(StatefulSet { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(trino) - .name(resource_names.stateful_set_name().to_string()) - .ownerreference_from_resource(trino, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &role_name, - role_group_name, - )) - .context(MetadataBuildSnafu)? - .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) - .with_annotations(annotations) - .build(), - spec: Some(StatefulSetSpec { - pod_management_policy: Some("Parallel".to_string()), - replicas: rolegroup.replicas.map(i32::from), - selector: LabelSelector { - match_labels: Some( - Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) - .context(LabelBuildSnafu)? - .into(), - ), - ..LabelSelector::default() - }, - service_name: Some(resource_names.headless_service_name().to_string()), - template: pod_template, - volume_claim_templates: Some(persistent_volume_claims), - ..StatefulSetSpec::default() - }), - status: None, - }) -} - pub fn error_policy( _obj: Arc>, error: &Error, @@ -945,25 +456,7 @@ pub fn error_policy( } } -/// Give a secret name and an optional key in the secret to use. -/// The value from the key will be set into the given env var name. -/// If not secret key is given, the env var name will be used as the secret key. -fn env_var_from_secret(secret_name: &str, secret_key: Option<&str>, env_var: &str) -> EnvVar { - EnvVar { - name: env_var.to_string(), - value_from: Some(EnvVarSource { - secret_key_ref: Some(SecretKeySelector { - optional: Some(false), - name: secret_name.to_string(), - key: secret_key.unwrap_or(env_var).to_string(), - }), - ..EnvVarSource::default() - }), - ..EnvVar::default() - } -} - -fn build_recommended_labels<'a, T>( +pub(crate) fn build_recommended_labels<'a, T>( owner: &'a T, app_version: &'a str, role: &'a str, @@ -980,301 +473,14 @@ fn build_recommended_labels<'a, T>( } } -fn shared_internal_secret_name(trino: &v1alpha1::TrinoCluster) -> String { +pub(crate) fn shared_internal_secret_name(trino: &v1alpha1::TrinoCluster) -> String { format!("{}-internal-secret", trino.name_any()) } -fn shared_spooling_secret_name(trino: &v1alpha1::TrinoCluster) -> String { +pub(crate) fn shared_spooling_secret_name(trino: &v1alpha1::TrinoCluster) -> String { format!("{}-spooling-secret", trino.name_any()) } -fn container_ports(trino: &v1alpha1::TrinoCluster) -> Vec { - let mut ports = vec![ContainerPort { - name: Some(METRICS_PORT_NAME.to_string()), - container_port: METRICS_PORT.into(), - protocol: Some("TCP".to_string()), - ..ContainerPort::default() - }]; - - if trino.expose_http_port() { - ports.push(ContainerPort { - name: Some(HTTP_PORT_NAME.to_string()), - container_port: HTTP_PORT.into(), - protocol: Some("TCP".to_string()), - ..ContainerPort::default() - }) - } - - if trino.expose_https_port() { - ports.push(ContainerPort { - name: Some(HTTPS_PORT_NAME.to_string()), - container_port: HTTPS_PORT.into(), - protocol: Some("TCP".to_string()), - ..ContainerPort::default() - }); - } - - ports -} - -fn startup_probe(trino: &v1alpha1::TrinoCluster) -> Probe { - Probe { - exec: Some(finished_starting_probe(trino)), - period_seconds: Some(5), - // Give the coordinator or worker 10 minutes to start up - failure_threshold: Some(120), - timeout_seconds: Some(3), - ..Default::default() - } -} - -fn readiness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { - Probe { - http_get: Some(http_get_probe(trino)), - period_seconds: Some(5), - failure_threshold: Some(1), - timeout_seconds: Some(3), - ..Probe::default() - } -} - -fn liveness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { - Probe { - http_get: Some(http_get_probe(trino)), - period_seconds: Some(5), - // Coordinators are currently not highly available, so you always have a singe instance. - // Restarting it causes all queries to fail, so let's not restart it directly after the first - // probe failure, but wait for 3 failures - // NOTE: This also applies to workers - failure_threshold: Some(3), - timeout_seconds: Some(3), - ..Probe::default() - } -} - -/// Check that `/v1/info` returns `200`. -/// -/// This is the same probe as the [upstream helm-chart](https://github.com/trinodb/charts/blob/7cd0a7bff6c52e0ee6ca6d5394cd72c150ad4379/charts/trino/templates/deployment-coordinator.yaml#L214) -/// is using. -fn http_get_probe(trino: &v1alpha1::TrinoCluster) -> HTTPGetAction { - let (schema, port_name) = if trino.expose_https_port() { - ("HTTPS", HTTPS_PORT_NAME) - } else { - ("HTTP", HTTP_PORT_NAME) - }; - - HTTPGetAction { - port: IntOrString::String(port_name.to_string()), - scheme: Some(schema.to_string()), - path: Some("/v1/info".to_string()), - ..Default::default() - } -} - -/// Wait until `/v1/info` returns `"starting":false`. -/// -/// This probe works on coordinators and workers. -fn finished_starting_probe(trino: &v1alpha1::TrinoCluster) -> ExecAction { - let port = trino.exposed_port(); - let schema = if trino.expose_https_port() { - "https" - } else { - "http" - }; - - ExecAction { - command: Some(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - format!( - "curl --fail --insecure {schema}://127.0.0.1:{port}/v1/info | grep --silent '\\\"starting\\\":false'" - ), - ]), - } -} - -fn create_tls_volume( - volume_name: &str, - tls_secret_class: &str, - requested_secret_lifetime: &Duration, - listener_scope: Option, -) -> Result { - let mut secret_volume_source_builder = SecretOperatorVolumeSourceBuilder::new( - tls_secret_class, - SecretClassVolumeProvisionParts::PublicPrivate, - ); - - secret_volume_source_builder - .with_pod_scope() - .with_format(SecretFormat::TlsPkcs12) - .with_tls_pkcs12_password(STACKABLE_TLS_STORE_PASSWORD) - .with_auto_tls_cert_lifetime(*requested_secret_lifetime); - - if let Some(listener_scope) = &listener_scope { - secret_volume_source_builder.with_listener_volume_scope(listener_scope); - } - - Ok(VolumeBuilder::new(volume_name) - .ephemeral( - secret_volume_source_builder - .build() - .context(TlsCertSecretClassVolumeBuildSnafu)?, - ) - .build()) -} - -#[allow(clippy::too_many_arguments)] -fn tls_volume_mounts( - trino: &v1alpha1::TrinoCluster, - trino_role: &TrinoRole, - pod_builder: &mut PodBuilder, - cb_prepare: &mut ContainerBuilder, - cb_trino: &mut ContainerBuilder, - catalogs: &[CatalogConfig], - requested_secret_lifetime: &Duration, - resolved_fte_config: &Option, - resolved_spooling_config: &Option, - trino_opa_config: &Option, -) -> Result<()> { - if let Some(server_tls) = trino.get_server_tls() { - cb_prepare - .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_volume(create_tls_volume( - "server-tls-mount", - server_tls, - requested_secret_lifetime, - // add listener - secret_volume_listener_scope(trino_role), - )?) - .context(AddVolumeSnafu)?; - } - - cb_prepare - .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_empty_dir_volume("server-tls", None) - .context(AddVolumeSnafu)?; - - cb_prepare - .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_empty_dir_volume("client-tls", None) - .context(AddVolumeSnafu)?; - - if let Some(internal_tls) = trino.get_internal_tls() { - cb_prepare - .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_volume(create_tls_volume( - "internal-tls-mount", - internal_tls, - requested_secret_lifetime, - None, - )?) - .context(AddVolumeSnafu)?; - - cb_prepare - .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_empty_dir_volume("internal-tls", None) - .context(AddVolumeSnafu)?; - } - - // catalogs - for catalog in catalogs { - cb_prepare - .add_volume_mounts(catalog.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mounts(catalog.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_volumes(catalog.volumes.clone()) - .context(AddVolumeSnafu)?; - } - - // Add OPA TLS certs if configured - if let Some((tls_secret_class, tls_mount_path)) = - trino_opa_config.as_ref().and_then(|opa_config| { - opa_config - .tls_secret_class - .as_ref() - .zip(opa_config.tls_mount_path()) - }) - { - cb_prepare - .add_volume_mount(OPA_TLS_VOLUME_NAME, &tls_mount_path) - .context(AddVolumeMountSnafu)?; - - let opa_tls_volume = VolumeBuilder::new(OPA_TLS_VOLUME_NAME) - .ephemeral( - SecretOperatorVolumeSourceBuilder::new( - tls_secret_class, - SecretClassVolumeProvisionParts::PublicPrivate, - ) - .build() - .context(TlsCertSecretClassVolumeBuildSnafu)?, - ) - .build(); - - pod_builder - .add_volume(opa_tls_volume) - .context(AddVolumeSnafu)?; - } - - // fault tolerant execution S3 credentials and other resources - if let Some(resolved_fte) = resolved_fte_config { - cb_prepare - .add_volume_mounts(resolved_fte.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mounts(resolved_fte.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_volumes(resolved_fte.volumes.clone()) - .context(AddVolumeSnafu)?; - } - - // client spooling S3 credentials and other resources - if let Some(resolved_spooling) = resolved_spooling_config { - cb_prepare - .add_volume_mounts(resolved_spooling.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - cb_trino - .add_volume_mounts(resolved_spooling.volume_mounts.clone()) - .context(AddVolumeMountSnafu)?; - pod_builder - .add_volumes(resolved_spooling.volumes.clone()) - .context(AddVolumeSnafu)?; - } - - Ok(()) -} - #[cfg(test)] mod tests { use stackable_operator::{ From 4859215b4486b11d54afd9c81926663a2a452bfe Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 17:41:53 +0200 Subject: [PATCH 30/65] test: add vector.toml test setup --- .../properties/product_logging/test-vector.sh | 15 ++ .../product_logging/vector-test.yaml | 99 +++++++++++ .../properties/product_logging/vector.yaml | 163 ++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100755 rust/operator-binary/src/controller/build/properties/product_logging/test-vector.sh create mode 100644 rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml create mode 100644 rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/test-vector.sh b/rust/operator-binary/src/controller/build/properties/product_logging/test-vector.sh new file mode 100755 index 000000000..a5cb43149 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/test-vector.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Trino emits airlift timestamps as UTC (trailing `Z`). The shared transform matches the `Z` +# literally, so `parse_timestamp` assumes the host timezone; pin it to UTC (as the pods run) to +# keep the tests deterministic regardless of the host's timezone. +TZ=UTC \ +DATA_DIR=/stackable/log/_vector-state \ +LOG_DIR=/stackable/log \ +NAMESPACE=default \ +CLUSTER_NAME=trino \ +ROLE_NAME=coordinator \ +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/controller/build/properties/product_logging/vector-test.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml new file mode 100644 index 000000000..76e8296ad --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml @@ -0,0 +1,99 @@ +# 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/trino/trino.stdout.log + message: Starting Trino + pod: trino-coordinator-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": "trino", + "container": "trino", + "file": "trino.stdout.log", + "level": "INFO", + "logger": "ROOT", + "message": "Starting Trino", + "namespace": "default", + "pod": "trino-coordinator-default-0", + "role": "coordinator", + "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/trino/trino.stderr.log + message: "Exception in thread \"main\"" + pod: trino-coordinator-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": "trino", + "container": "trino", + "file": "trino.stderr.log", + "level": "ERROR", + "logger": "ROOT", + "message": "Exception in thread \"main\"", + "namespace": "default", + "pod": "trino-coordinator-default-0", + "role": "coordinator", + "roleGroup": "default", + "timestamp": "2025-10-02T09:27:28.582Z" + } + + assert_eq!(expected_log_event, .) + - name: Test airlift JSON log entry + inputs: + - type: log + insert_at: processed_files_airlift + log_fields: + file: /stackable/log/trino/server.airlift.json + message: '{"timestamp":"2024-01-01T00:00:00.123456789Z","level":"INFO","logger":"io.trino.server.Server","thread":"main","message":"======== SERVER STARTED ========"}' + pod: trino-coordinator-default-0 + source_type: file + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "trino", + "container": "trino", + "file": "server.airlift.json", + "level": "INFO", + "logger": "io.trino.server.Server", + "message": "======== SERVER STARTED ========", + "namespace": "default", + "pod": "trino-coordinator-default-0", + "role": "coordinator", + "roleGroup": "default", + "thread": "main", + # `timestamp` is the airlift `timestamp` field parsed to a real timestamp value + # (nanosecond precision preserved). Asserted as a timestamp literal, not a string. + "timestamp": t'2024-01-01T00:00:00.123456789Z' + } + + assert_eq!(expected_log_event, .) diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml new file mode 100644 index 000000000..0d922ac58 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml @@ -0,0 +1,163 @@ +--- +data_dir: ${DATA_DIR} + +log_schema: + host_key: pod + +sources: + # Reads the internal Vector logs + vector: + type: internal_logs + + files_stdout: + type: file + include: + - ${LOG_DIR}/*/*.stdout.log + + files_stderr: + type: file + include: + - ${LOG_DIR}/*/*.stderr.log + + # Trino logs in the airlift JSON format (see `log.path` / `server.airlift.json`). + files_airlift: + type: file + include: + - ${LOG_DIR}/*/*.airlift.json + +transforms: + processed_files_stdout: + inputs: + - files_stdout + type: remap + source: | + .logger = "ROOT" + .level = "INFO" + + processed_files_stderr: + inputs: + - files_stderr + type: remap + source: | + .logger = "ROOT" + .level = "ERROR" + + 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") + } + + # Extends the processed files with the fields "container" and "file" + extended_logs_files: + inputs: + - processed_files_* + type: remap + source: | + del(.source_type) + if .errors == [] { + del(.errors) + } + . |= parse_regex!(.file, r'^${LOG_DIR}/(?P.*?)/(?P.*?)$') + + # Filters the logs of the Vector agent according to the defined log level + 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}")) + + # Aligns the logs of the Vector agent with the common format + 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) + + # Add the fields "namespace", "cluster", "role" and "roleGroup" to all logs + extended_logs: + inputs: + - extended_logs_* + type: remap + source: | + .namespace = "${NAMESPACE}" + .cluster = "${CLUSTER_NAME}" + .role = "${ROLE_NAME}" + .roleGroup = "${ROLE_GROUP_NAME}" + +sinks: + # Forward the logs to the Vector aggregator + aggregator: + inputs: + - extended_logs + type: vector + address: ${VECTOR_AGGREGATOR_ADDRESS} From 910ba949504eb5fd26920976567af1d2227ea06b Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 18:04:46 +0200 Subject: [PATCH 31/65] refactor: consolidatev2 logging --- rust/operator-binary/src/command.rs | 11 +- .../build/properties/log_properties.rs | 4 +- .../controller/build/properties/logging.rs | 100 +- .../src/controller/build/properties/mod.rs | 1 + .../build/properties/product_logging/mod.rs | 28 + .../controller/build/resource/config_map.rs | 13 +- .../controller/build/resource/statefulset.rs | 120 +-- rust/operator-binary/src/controller/mod.rs | 16 +- .../src/controller/validate.rs | 98 +- rust/operator-binary/src/trino_controller.rs | 13 +- tests/templates/kuttl/smoke/14-assert.yaml.j2 | 968 +----------------- 11 files changed, 269 insertions(+), 1103 deletions(-) create mode 100644 rust/operator-binary/src/controller/build/properties/product_logging/mod.rs diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index 9c2db7f0f..858faff8d 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -1,9 +1,9 @@ use stackable_operator::{ - product_logging::{ - framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command}, - spec::{ContainerLogConfig, ContainerLogConfigChoice}, + product_logging::framework::{ + create_vector_shutdown_file_command, remove_vector_shutdown_file_command, }, utils::COMMON_BASH_TRAP_FUNCTIONS, + v2::product_logging::framework::ValidatedContainerLogConfigChoice, }; use crate::{ @@ -36,10 +36,7 @@ pub fn container_prepare_args( let mut args = vec![]; // Copy custom logging provided `log.properties` to rw config - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Custom(_)), - }) = merged_config.logging.containers.get(&Container::Trino) - { + if let ValidatedContainerLogConfigChoice::Custom(_) = merged_config.logging.trino_container { // copy config files to a writeable empty folder args.push(format!( "echo copying {STACKABLE_LOG_CONFIG_DIR}/{LOG_PROPERTIES} {rw_conf}/{LOG_PROPERTIES}", diff --git a/rust/operator-binary/src/controller/build/properties/log_properties.rs b/rust/operator-binary/src/controller/build/properties/log_properties.rs index 8c85d1b6b..cce9987eb 100644 --- a/rust/operator-binary/src/controller/build/properties/log_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/log_properties.rs @@ -13,7 +13,9 @@ pub fn build(rg: &TrinoRoleGroupConfig) -> BTreeMap { // 1. No defaults // 2. Automatic per-container logger levels - if let Some(per_container) = super::logging::get_log_property_map(&rg.config.logging) { + if let Some(per_container) = + super::logging::get_log_property_map(&rg.config.logging.trino_container) + { props.extend(per_container); } diff --git a/rust/operator-binary/src/controller/build/properties/logging.rs b/rust/operator-binary/src/controller/build/properties/logging.rs index e2d653fe2..f0067baee 100644 --- a/rust/operator-binary/src/controller/build/properties/logging.rs +++ b/rust/operator-binary/src/controller/build/properties/logging.rs @@ -1,21 +1,11 @@ +use std::collections::BTreeMap; + use stackable_operator::{ - kube::runtime::reflector::ObjectRef, - product_logging::{ - framework::create_vector_config, - spec::{ - AutomaticContainerLogConfig, ContainerLogConfig, ContainerLogConfigChoice, LogLevel, - Logging, - }, - }, - role_utils::RoleGroupRef, + product_logging::spec::{AutomaticContainerLogConfig, LogLevel}, + v2::product_logging::framework::ValidatedContainerLogConfigChoice, }; use strum::Display; -use crate::{ - controller::ValidatedCluster, - crd::{Container, TrinoRole}, -}; - #[derive(Display)] #[strum(serialize_all = "lowercase")] pub enum TrinoLogLevel { @@ -38,63 +28,31 @@ impl From for TrinoLogLevel { } } -/// Return the `log.properties` content as a typed `BTreeMap`. +/// Return the `log.properties` content as a typed `BTreeMap` for the (validated) Trino container. +/// +/// Returns `None` when the Trino container uses a custom log ConfigMap (in which case the operator +/// does not generate `log.properties`). pub fn get_log_property_map( - logging: &Logging, -) -> Option> { - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&Container::Trino) - { - let map = log_config - .loggers - .iter() - .map(|(logger, config)| { - let log_level = TrinoLogLevel::from(config.level); - let key = if logger == AutomaticContainerLogConfig::ROOT_LOGGER { - // ROOT logger maps to an empty key in log.properties (=LEVEL). - String::new() - } else { - logger.clone() - }; - (key, log_level.to_string()) - }) - .collect(); - Some(map) - } else { - None - } -} - -/// Return the vector toml configuration -pub fn get_vector_toml( - cluster: &ValidatedCluster, - role: &TrinoRole, - role_group_name: &str, - logging: &Logging, -) -> Option { - let vector_log_config = if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(&Container::Vector) - { - Some(log_config) - } else { - None - }; - - if logging.enable_vector_agent { - // The legacy `create_vector_config` still requires a `RoleGroupRef`, but ignores it - // entirely when rendering the config. We construct a throwaway one here so the rest of the - // operator no longer has to thread `RoleGroupRef` around. - // TODO(step-5): drop this once logging moves to the `v2` framework (`include_str!`-based - // `vector.yaml`), which does not need a `RoleGroupRef`. - let rolegroup_ref = RoleGroupRef { - cluster: ObjectRef::from_obj(cluster), - role: role.to_string(), - role_group: role_group_name.to_owned(), - }; - Some(create_vector_config(&rolegroup_ref, vector_log_config)) - } else { - None + trino_container: &ValidatedContainerLogConfigChoice, +) -> Option> { + match trino_container { + ValidatedContainerLogConfigChoice::Automatic(log_config) => { + let map = log_config + .loggers + .iter() + .map(|(logger, config)| { + let log_level = TrinoLogLevel::from(config.level); + let key = if logger == AutomaticContainerLogConfig::ROOT_LOGGER { + // ROOT logger maps to an empty key in log.properties (=LEVEL). + String::new() + } else { + logger.clone() + }; + (key, log_level.to_string()) + }) + .collect(); + Some(map) + } + ValidatedContainerLogConfigChoice::Custom(_) => None, } } diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index a6a94f3a0..7dd398305 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -11,6 +11,7 @@ pub mod exchange_manager_properties; pub mod log_properties; pub mod logging; pub mod node_properties; +pub mod product_logging; pub mod security_properties; pub mod spooling_manager_properties; diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs new file mode 100644 index 000000000..0a4bd8273 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs @@ -0,0 +1,28 @@ +//! The Vector agent configuration (`vector.yaml`) assembled into the rolegroup `ConfigMap`. + +/// The Vector agent configuration (`vector.yaml`). +/// +/// It is templated with environment variables (`${LOG_DIR}`, `${NAMESPACE}`, …) that the +/// `v2` Vector container injects at runtime, so the same file content is used for every +/// rolegroup. +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() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vector_config_file_content() { + let content = vector_config_file_content(); + assert!(!content.is_empty()); + // The airlift source must be present (Trino's main log format) ... + assert!(content.contains("files_airlift")); + // ... while the aggregator sink must reference the injected address. + assert!(content.contains("${VECTOR_AGGREGATOR_ADDRESS}")); + } +} diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 420e3b00a..68e1b40db 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -7,7 +7,7 @@ use stackable_operator::{ builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, k8s_openapi::api::core::v1::ConfigMap, kvp::ObjectLabels, - product_logging, + product_logging::framework::VECTOR_CONFIG_FILE, utils::cluster_info::KubernetesClusterInfo, v2::config_file_writer::to_java_properties_string, }; @@ -18,7 +18,7 @@ use crate::{ ValidatedCluster, build::properties::{ ConfigFileName, access_control_properties, config_properties, - exchange_manager_properties, log_properties, logging::get_vector_toml, node_properties, + exchange_manager_properties, log_properties, node_properties, product_logging, security_properties, spooling_manager_properties, }, }, @@ -180,11 +180,12 @@ pub fn build_rolegroup_config_map( .context(BuildJvmConfigSnafu)?; data.insert(JVM_CONFIG.to_string(), jvm_config); - // 9. Vector sidecar toml if enabled. - if let Some(vector_toml) = get_vector_toml(cluster, role, role_group_name, &rg.config.logging) { + // 9. Vector agent config (`vector.yaml`) if the Vector agent is enabled. The file is templated + // with environment variables injected by the v2 Vector container at runtime. + if rg.config.logging.enable_vector_agent { data.insert( - product_logging::framework::VECTOR_CONFIG_FILE.to_string(), - vector_toml, + VECTOR_CONFIG_FILE.to_string(), + product_logging::vector_config_file_content(), ); } diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 4ea975f2a..dcbeaf551 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -1,6 +1,6 @@ //! Builds the per-rolegroup [`StatefulSet`] that runs a Trino role group. -use std::{collections::BTreeMap, convert::Infallible}; +use std::{collections::BTreeMap, convert::Infallible, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -31,16 +31,13 @@ use stackable_operator::{ apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, kvp::{Annotation, Annotations, Labels}, - product_logging::{ - self, - framework::LoggingError, - spec::{ - ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, - CustomContainerLogConfig, - }, - }, + product_logging, shared::time::Duration, - v2::builder::pod::container::EnvVarSet, + v2::{ + builder::pod::container::EnvVarSet, + product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, + types::kubernetes::{ContainerName, VolumeName}, + }, }; use crate::{ @@ -59,9 +56,10 @@ use crate::{ crd::{ APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, - METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, - STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, - STACKABLE_TLS_STORE_PASSWORD, TrinoRole, v1alpha1, + METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, + STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, + STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, + TrinoRole, v1alpha1, }, trino_controller::{ MAX_PREPARE_LOG_FILE_SIZE, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, @@ -69,6 +67,12 @@ use crate::{ }, }; +stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); +// The Vector agent reads its `vector.yaml` from the rolegroup ConfigMap (mounted as the "config" +// volume) and writes its state under the shared "log" volume. +stackable_operator::constant!(VECTOR_LOG_CONFIG_VOLUME_NAME: VolumeName = "config"); +stackable_operator::constant!(VECTOR_LOG_VOLUME_NAME: VolumeName = "log"); + #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("missing secret lifetime"))] @@ -88,12 +92,6 @@ pub enum Error { container_name: String, }, - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - - #[snafu(display("failed to build vector container"))] - BuildVectorContainer { source: LoggingError }, - #[snafu(display("failed to configure graceful shutdown"))] GracefulShutdown { source: build::graceful_shutdown::Error, @@ -263,9 +261,8 @@ pub fn build_rolegroup_statefulset( )?; let mut prepare_args = vec![]; - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = merged_config.logging.containers.get(&Container::Prepare) + if let ValidatedContainerLogConfigChoice::Automatic(log_config) = + &merged_config.logging.prepare_container { prepare_args.push(product_logging::framework::capture_shell_output( STACKABLE_LOG_DIR, @@ -380,60 +377,33 @@ pub fn build_rolegroup_statefulset( // add password-update container if required trino_authentication_config.add_authentication_containers(trino_role, &mut pod_builder); - if let Some(ContainerLogConfig { - choice: - Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { - custom: ConfigMapLogConfig { config_map }, - })), - }) = merged_config.logging.containers.get(&Container::Trino) - { - pod_builder - .add_volume(Volume { - name: "log-config".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: config_map.into(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)?; - } else { - pod_builder - .add_volume(Volume { - name: "log-config".to_string(), - config_map: Some(ConfigMapVolumeSource { - name: config_map_name.clone(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }) - .context(AddVolumeSnafu)?; - } + // The log-config volume mounts either the rolegroup ConfigMap (which carries the automatic + // `log.properties`) or a user-provided custom ConfigMap, depending on the validated choice. + let log_config_volume_config_map = match &merged_config.logging.trino_container { + ValidatedContainerLogConfigChoice::Custom(config_map) => config_map.to_string(), + ValidatedContainerLogConfigChoice::Automatic(_) => config_map_name.clone(), + }; + pod_builder + .add_volume(Volume { + name: "log-config".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: log_config_volume_config_map, + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }) + .context(AddVolumeSnafu)?; - if merged_config.logging.enable_vector_agent { - match &trino.spec.cluster_config.vector_aggregator_config_map_name { - Some(vector_aggregator_config_map_name) => { - pod_builder.add_container( - product_logging::framework::vector_container( - resolved_product_image, - "config", - "log", - merged_config.logging.containers.get(&Container::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(BuildVectorContainerSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } + if let Some(vector_log_config) = &merged_config.logging.vector_container { + pod_builder.add_container(vector_container( + &VECTOR_CONTAINER_NAME, + resolved_product_image, + vector_log_config, + &resource_names, + &VECTOR_LOG_CONFIG_VOLUME_NAME, + &VECTOR_LOG_VOLUME_NAME, + EnvVarSet::new(), + )); } let metadata = ObjectMetaBuilder::new() diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 80c2e4516..21a12fe76 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -10,7 +10,6 @@ use stackable_operator::{ resources::{NoRuntimeLimits, Resources}, }, kube::{Resource, api::ObjectMeta}, - product_logging::spec::Logging, shared::time::Duration, v2::{ role_group_utils::ResourceNames, @@ -29,7 +28,7 @@ use crate::{ client_protocol::ResolvedClientProtocolConfig, fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, - crd::{Container, TrinoRole, discovery::TrinoPodRef, v1alpha1}, + crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, }; pub(crate) mod build; @@ -62,15 +61,11 @@ pub struct ValidatedClusterConfig { /// Holds the merged [`v1alpha1::TrinoConfig`] fields so the build steps consume this /// controller-owned type instead of the raw CRD struct (mirroring the opensearch- and /// hive-operators' `Validated…Config`). -/// -/// `logging` is still carried as the raw [`Logging`] for now; the up-front validation into a -/// `ValidatedLogging` (via the `v2` product-logging framework) lands together with the rest of the -/// logging migration in a later step. #[derive(Clone, Debug)] pub struct ValidatedTrinoConfig { pub affinity: StackableAffinity, pub graceful_shutdown_timeout: Option, - pub logging: Logging, + pub logging: validate::ValidatedLogging, pub query_max_memory: Option, pub query_max_memory_per_node: Option, pub resources: Resources, @@ -78,12 +73,13 @@ pub struct ValidatedTrinoConfig { } impl ValidatedTrinoConfig { - /// Builds the validated config from the merged [`v1alpha1::TrinoConfig`]. - fn from_merged(merged: v1alpha1::TrinoConfig) -> Self { + /// Builds the validated config from the merged [`v1alpha1::TrinoConfig`], swapping in the + /// already-validated logging. + fn from_merged(merged: v1alpha1::TrinoConfig, logging: validate::ValidatedLogging) -> Self { Self { affinity: merged.affinity, graceful_shutdown_timeout: merged.graceful_shutdown_timeout, - logging: merged.logging, + logging, query_max_memory: merged.query_max_memory, query_max_memory_per_node: merged.query_max_memory_per_node, resources: merged.resources, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 5395acd53..1f3e4ed19 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -5,17 +5,23 @@ use std::{collections::BTreeMap, str::FromStr}; -use snafu::{ResultExt, Snafu}; +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::product_image_selection, config::fragment, kube::ResourceExt as _, + product_logging::spec::Logging, role_utils::{GenericRoleConfig, RoleGroup}, v2::{ builder::pod::container::{self, EnvVarName, EnvVarSet}, controller_utils::{get_cluster_name, get_namespace, get_uid}, + product_logging::framework::{ + ValidatedContainerLogConfigChoice, VectorContainerLogConfig, + validate_logging_configuration_for_container, + }, role_utils::{JavaCommonConfig, RoleGroupConfig, with_validated_config}, + types::kubernetes::ConfigMapName, }, }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; @@ -24,7 +30,7 @@ use super::{ValidatedCluster, ValidatedClusterConfig, ValidatedTls, ValidatedTri use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, controller::dereference::DereferencedObjects, - crd::{TrinoRole, v1alpha1}, + crd::{Container, TrinoRole, v1alpha1}, }; #[derive(Snafu, Debug, EnumDiscriminants)] @@ -82,12 +88,75 @@ pub enum Error { #[snafu(display("invalid environment variable override name"))] ParseEnvVarName { source: container::Error }, + + #[snafu(display("failed to validate logging configuration"))] + ValidateLoggingConfig { + source: stackable_operator::v2::product_logging::framework::Error, + }, + + #[snafu(display( + "the Vector aggregator discovery ConfigMap name is required 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, + }, } type Result = std::result::Result; pub type RoleGroupName = String; +/// Validated logging configuration for the Trino, prepare and (optional) Vector containers. +/// +/// Produced up-front by [`validate_logging`] (mirroring the opensearch- and hive-operators) 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 prepare_container: ValidatedContainerLogConfigChoice, + pub trino_container: ValidatedContainerLogConfigChoice, + pub vector_container: Option, + pub enable_vector_agent: bool, +} + +/// Validates the logging configuration for the Trino, prepare and (optional) Vector containers. +/// +/// `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 prepare_container = + validate_logging_configuration_for_container(logging, &Container::Prepare) + .context(ValidateLoggingConfigSnafu)?; + let trino_container = validate_logging_configuration_for_container(logging, &Container::Trino) + .context(ValidateLoggingConfigSnafu)?; + + 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, &Container::Vector) + .context(ValidateLoggingConfigSnafu)?, + vector_aggregator_config_map_name, + }) + } else { + None + }; + + Ok(ValidatedLogging { + prepare_container, + trino_container, + vector_container, + enable_vector_agent: logging.enable_vector_agent, + }) +} + pub type TrinoRoleGroupConfig = RoleGroupConfig; @@ -133,6 +202,17 @@ pub fn validate( }); } + // 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 = trino + .spec + .cluster_config + .vector_aggregator_config_map_name + .as_deref() + .map(ConfigMapName::from_str) + .transpose() + .context(ParseVectorAggregatorConfigMapNameSnafu)?; + let mut role_group_configs: BTreeMap> = BTreeMap::new(); for trino_role in TrinoRole::iter() { @@ -161,7 +241,10 @@ pub fn validate( .with_context(|_| FailedToResolveConfigSnafu { role_group: rg_name.clone(), })?; - groups.insert(rg_name.clone(), into_role_group_config(merged)?); + groups.insert( + rg_name.clone(), + into_role_group_config(merged, &vector_aggregator_config_map_name)?, + ); } role_group_configs.insert(trino_role, groups); } @@ -202,6 +285,7 @@ pub fn validate( /// [`EnvVarSet`] and a concrete replica count (defaulting to 1). fn into_role_group_config( merged: RoleGroup, + vector_aggregator_config_map_name: &Option, ) -> Result { let replicas = merged.replicas.unwrap_or(1); let common = merged.config; @@ -214,9 +298,11 @@ fn into_role_group_config( ); } + let logging = validate_logging(&common.config.logging, vector_aggregator_config_map_name)?; + Ok(RoleGroupConfig { replicas, - config: ValidatedTrinoConfig::from_merged(common.config), + config: ValidatedTrinoConfig::from_merged(common.config, logging), config_overrides: common.config_overrides, env_overrides, cli_overrides: common.cli_overrides, @@ -251,7 +337,9 @@ pub(crate) fn merged_role_group_config( v1alpha1::TrinoConfigOverrides, >(rg, &role, &default_config) .expect("role group config should be valid"); - into_role_group_config(merged).expect("env overrides should be valid") + // The shared test clusters do not enable the Vector agent, so no aggregator ConfigMap name is + // required here. + into_role_group_config(merged, &None).expect("env overrides should be valid") } #[cfg(test)] diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 370123764..7e252f657 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -6,10 +6,7 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, - commons::{ - random_secret_creation, - rbac::build_rbac_resources, - }, + commons::{random_secret_creation, rbac::build_rbac_resources}, kube::{ Resource, ResourceExt, core::{DeserializeGuard, error_boundary}, @@ -31,9 +28,7 @@ use crate::{ controller::{ build, build::resource::{ - listener::{ - build_group_listener, group_listener_name, - }, + listener::{build_group_listener, group_listener_name}, pdb::build_pdb, service::{ build_rolegroup_headless_service, build_rolegroup_metrics_service, @@ -42,9 +37,7 @@ use crate::{ }, dereference, validate, }, - crd::{ - APP_NAME, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, v1alpha1, - }, + crd::{APP_NAME, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, v1alpha1}, }; pub struct Ctx { diff --git a/tests/templates/kuttl/smoke/14-assert.yaml.j2 b/tests/templates/kuttl/smoke/14-assert.yaml.j2 index 39b7ed1e0..6e41ce1c3 100644 --- a/tests/templates/kuttl/smoke/14-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/14-assert.yaml.j2 @@ -96,211 +96,34 @@ commands: password-authenticator.name=file {% if lookup('env', 'VECTOR_AGGREGATOR') %} vector.yaml: | - data_dir: /stackable/log/_vector-state + --- + data_dir: ${DATA_DIR} log_schema: host_key: pod sources: + # Reads the internal Vector logs vector: type: internal_logs files_stdout: type: file include: - - /stackable/log/*/*.stdout.log + - ${LOG_DIR}/*/*.stdout.log files_stderr: type: file include: - - /stackable/log/*/*.stderr.log - - files_log4j: - type: file - include: - - /stackable/log/*/*.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 @@ -647,6 +203,7 @@ commands: .message = join!(compact([.message, stacktrace]), "\n\n") } + # Extends the processed files with the fields "container" and "file" extended_logs_files: inputs: - processed_files_* @@ -656,14 +213,21 @@ commands: if .errors == [] { del(.errors) } - . |= parse_regex!(.file, r'^/stackable/log/(?P.*?)/(?P.*?)$') + . |= parse_regex!(.file, r'^${LOG_DIR}/(?P.*?)/(?P.*?)$') + # Filters the logs of the Vector agent according to the defined log level filtered_logs_vector: inputs: - vector type: filter - condition: '!includes(["TRACE", "DEBUG"], .metadata.level)' - + 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}")) + + # Aligns the logs of the Vector agent with the common format extended_logs_vector: inputs: - filtered_logs_vector @@ -677,22 +241,24 @@ commands: del(.pid) del(.source_type) + # Add the fields "namespace", "cluster", "role" and "roleGroup" to all logs extended_logs: inputs: - extended_logs_* type: remap source: | - .namespace = "__NAMESPACE__" - .cluster = "trino" - .role = "coordinator" - .roleGroup = "default" + .namespace = "${NAMESPACE}" + .cluster = "${CLUSTER_NAME}" + .role = "${ROLE_NAME}" + .roleGroup = "${ROLE_GROUP_NAME}" sinks: + # Forward the logs to the Vector aggregator aggregator: inputs: - extended_logs type: vector - address: $VECTOR_AGGREGATOR_ADDRESS + address: ${VECTOR_AGGREGATOR_ADDRESS} {% endif %} YAMLEOF ) @@ -777,211 +343,34 @@ commands: networkaddress.cache.ttl=30 {% if lookup('env', 'VECTOR_AGGREGATOR') %} vector.yaml: | - data_dir: /stackable/log/_vector-state + --- + data_dir: ${DATA_DIR} log_schema: host_key: pod sources: + # Reads the internal Vector logs vector: type: internal_logs files_stdout: type: file include: - - /stackable/log/*/*.stdout.log + - ${LOG_DIR}/*/*.stdout.log files_stderr: type: file include: - - /stackable/log/*/*.stderr.log - - files_log4j: - type: file - include: - - /stackable/log/*/*.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 @@ -1328,6 +450,7 @@ commands: .message = join!(compact([.message, stacktrace]), "\n\n") } + # Extends the processed files with the fields "container" and "file" extended_logs_files: inputs: - processed_files_* @@ -1337,14 +460,21 @@ commands: if .errors == [] { del(.errors) } - . |= parse_regex!(.file, r'^/stackable/log/(?P.*?)/(?P.*?)$') + . |= parse_regex!(.file, r'^${LOG_DIR}/(?P.*?)/(?P.*?)$') + # Filters the logs of the Vector agent according to the defined log level filtered_logs_vector: inputs: - vector type: filter - condition: '!includes(["TRACE", "DEBUG"], .metadata.level)' - + 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}")) + + # Aligns the logs of the Vector agent with the common format extended_logs_vector: inputs: - filtered_logs_vector @@ -1358,22 +488,24 @@ commands: del(.pid) del(.source_type) + # Add the fields "namespace", "cluster", "role" and "roleGroup" to all logs extended_logs: inputs: - extended_logs_* type: remap source: | - .namespace = "__NAMESPACE__" - .cluster = "trino" - .role = "worker" - .roleGroup = "default" + .namespace = "${NAMESPACE}" + .cluster = "${CLUSTER_NAME}" + .role = "${ROLE_NAME}" + .roleGroup = "${ROLE_GROUP_NAME}" sinks: + # Forward the logs to the Vector aggregator aggregator: inputs: - extended_logs type: vector - address: $VECTOR_AGGREGATOR_ADDRESS + address: ${VECTOR_AGGREGATOR_ADDRESS} {% endif %} YAMLEOF ) From b5cb26c6070b3ef9fd8fb78c1919d97a414068b9 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 18:18:05 +0200 Subject: [PATCH 32/65] refactor: move raw cluster methods to ValidatedCluster --- .../src/controller/build/graceful_shutdown.rs | 131 ++++++++++++++++-- .../src/controller/build/resource/listener.rs | 21 +-- .../src/controller/build/resource/service.rs | 8 +- .../controller/build/resource/statefulset.rs | 52 ++++--- rust/operator-binary/src/controller/mod.rs | 31 ++++- rust/operator-binary/src/crd/mod.rs | 130 ----------------- rust/operator-binary/src/trino_controller.rs | 6 +- 7 files changed, 197 insertions(+), 182 deletions(-) diff --git a/rust/operator-binary/src/controller/build/graceful_shutdown.rs b/rust/operator-binary/src/controller/build/graceful_shutdown.rs index 585d79cce..7503b4187 100644 --- a/rust/operator-binary/src/controller/build/graceful_shutdown.rs +++ b/rust/operator-binary/src/controller/build/graceful_shutdown.rs @@ -12,7 +12,7 @@ use stackable_operator::{ use crate::{ controller::{ValidatedCluster, ValidatedTrinoConfig}, - crd::{DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT, TrinoRole, v1alpha1}, + crd::{DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT, TrinoRole}, }; /// Corresponds to "shutdown.grace-period", which defaults to 2 min. @@ -62,10 +62,8 @@ pub fn graceful_shutdown_config_properties( } } -/// Returns the minimal `gracefulShutdownTimeout` across all worker role-groups. -/// -/// Mirrors [`v1alpha1::TrinoCluster::min_worker_graceful_shutdown_timeout`] but -/// reads from [`ValidatedCluster::role_group_configs`]. +/// Returns the minimal `gracefulShutdownTimeout` across all worker role-groups, read from the +/// validated [`ValidatedCluster::role_group_configs`]. fn min_worker_graceful_shutdown_timeout( cluster: &ValidatedCluster, ) -> stackable_operator::shared::time::Duration { @@ -80,7 +78,7 @@ fn min_worker_graceful_shutdown_timeout( } pub fn add_graceful_shutdown_config( - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, role: &TrinoRole, merged_config: &ValidatedTrinoConfig, pod_builder: &mut PodBuilder, @@ -127,9 +125,9 @@ pub fn add_graceful_shutdown_config( echo 'Successfully sent graceful shutdown command' >> /proc/1/fd/1 2>&1 echo 'Sleeping {termination_grace_period_seconds} seconds' >> /proc/1/fd/1 2>&1 sleep {termination_grace_period_seconds}", - protocol = trino.exposed_protocol(), + protocol = cluster.exposed_protocol(), host = "127.0.0.1", - port = trino.exposed_port(), + port = cluster.exposed_port(), ), ]), }), @@ -141,3 +139,120 @@ pub fn add_graceful_shutdown_config( Ok(()) } + +#[cfg(test)] +mod tests { + use stackable_operator::shared::time::Duration; + + use super::*; + use crate::controller::build::properties::test_support::validated_cluster_from_yaml; + + /// A worker role group without an explicit `gracefulShutdownTimeout` falls back to the + /// product default. + #[test] + fn min_worker_timeout_defaults() { + let cluster = validated_cluster_from_yaml( + r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#, + ); + assert_eq!( + min_worker_graceful_shutdown_timeout(&cluster), + DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT + ); + } + + /// A role-level `gracefulShutdownTimeout` is merged into every worker role group. + #[test] + fn min_worker_timeout_from_role() { + let cluster = validated_cluster_from_yaml( + r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + config: + gracefulShutdownTimeout: 42h + roleGroups: + default: + replicas: 1 + "#, + ); + assert_eq!( + min_worker_graceful_shutdown_timeout(&cluster), + Duration::from_hours_unchecked(42) + ); + } + + /// The minimum is taken across all worker role groups (role <- role-group merge applied). + #[test] + fn min_worker_timeout_across_role_groups() { + let cluster = validated_cluster_from_yaml( + r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + config: + gracefulShutdownTimeout: 42h + roleGroups: + normal: + replicas: 1 + short: + replicas: 1 + config: + gracefulShutdownTimeout: 5m + long: + replicas: 1 + config: + gracefulShutdownTimeout: 7d + "#, + ); + assert_eq!( + min_worker_graceful_shutdown_timeout(&cluster), + Duration::from_minutes_unchecked(5) + ); + } +} diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index ef12ef0d3..f9548bb14 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -10,7 +10,10 @@ use stackable_operator::{ kvp::{Labels, ObjectLabels}, }; -use crate::crd::{TrinoRole, v1alpha1}; +use crate::{ + controller::ValidatedCluster, + crd::{TrinoRole, v1alpha1}, +}; pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; @@ -34,23 +37,23 @@ pub enum Error { } pub fn build_group_listener( - trino: &v1alpha1::TrinoCluster, - object_labels: ObjectLabels, + cluster: &ValidatedCluster, + object_labels: ObjectLabels, listener_class: String, listener_group_name: String, ) -> Result { Ok(Listener { metadata: ObjectMetaBuilder::new() - .name_and_namespace(trino) + .name_and_namespace(cluster) .name(listener_group_name) - .ownerreference_from_resource(trino, None, Some(true)) + .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&object_labels) .context(BuildObjectMetaSnafu)? .build(), spec: ListenerSpec { class_name: Some(listener_class), - ports: Some(listener_ports(trino)), + ports: Some(listener_ports(cluster)), ..ListenerSpec::default() }, status: None, @@ -91,9 +94,9 @@ pub fn secret_volume_listener_scope(role: &TrinoRole) -> Option { } /// We only use the http/https port here and intentionally omit the metrics one. -fn listener_ports(trino: &v1alpha1::TrinoCluster) -> Vec { - let name = trino.exposed_protocol().to_string(); - let port = trino.exposed_port().into(); +fn listener_ports(cluster: &ValidatedCluster) -> Vec { + let name = cluster.exposed_protocol().to_string(); + let port = cluster.exposed_port().into(); vec![ListenerPort { name, diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index b393218d4..d2b57ef52 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -9,7 +9,7 @@ use stackable_operator::{ use crate::{ controller::ValidatedCluster, - crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole, v1alpha1}, + crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole}, }; #[derive(Snafu, Debug)] @@ -99,9 +99,9 @@ pub fn build_rolegroup_metrics_service( }) } -pub(crate) fn headless_service_ports(trino: &v1alpha1::TrinoCluster) -> Vec { - let name = trino.exposed_protocol().to_string(); - let port = trino.exposed_port().into(); +pub(crate) fn headless_service_ports(cluster: &ValidatedCluster) -> Vec { + let name = cluster.exposed_protocol().to_string(); + let port = cluster.exposed_port().into(); vec![ServicePort { name: Some(name), diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index dcbeaf551..31a05a666 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -214,7 +214,7 @@ pub fn build_rolegroup_statefulset( ) .context(InvalidAuthenticationConfigSnafu)?; build::graceful_shutdown::add_graceful_shutdown_config( - trino, + cluster, trino_role, merged_config, &mut pod_builder, @@ -363,12 +363,12 @@ pub fn build_rolegroup_statefulset( .context(AddVolumeMountSnafu)? .add_volume_mount("log", STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? - .add_container_ports(container_ports(trino)) + .add_container_ports(container_ports(cluster)) .resources(merged_config.resources.clone().into()) // The probes are set on coordinators and workers - .startup_probe(startup_probe(trino)) - .readiness_probe(readiness_probe(trino)) - .liveness_probe(liveness_probe(trino)) + .startup_probe(startup_probe(cluster)) + .readiness_probe(readiness_probe(cluster)) + .liveness_probe(liveness_probe(cluster)) .build(); // add trino container first to better default into that container (e.g. instead of vector) @@ -529,7 +529,7 @@ fn env_var_from_secret(secret_name: &str, secret_key: Option<&str>, env_var: &st } } -fn container_ports(trino: &v1alpha1::TrinoCluster) -> Vec { +fn container_ports(cluster: &ValidatedCluster) -> Vec { let mut ports = vec![ContainerPort { name: Some(METRICS_PORT_NAME.to_string()), container_port: METRICS_PORT.into(), @@ -537,30 +537,28 @@ fn container_ports(trino: &v1alpha1::TrinoCluster) -> Vec { ..ContainerPort::default() }]; - if trino.expose_http_port() { - ports.push(ContainerPort { - name: Some(HTTP_PORT_NAME.to_string()), - container_port: HTTP_PORT.into(), - protocol: Some("TCP".to_string()), - ..ContainerPort::default() - }) - } - - if trino.expose_https_port() { + if cluster.server_tls_enabled() { ports.push(ContainerPort { name: Some(HTTPS_PORT_NAME.to_string()), container_port: HTTPS_PORT.into(), protocol: Some("TCP".to_string()), ..ContainerPort::default() }); + } else { + ports.push(ContainerPort { + name: Some(HTTP_PORT_NAME.to_string()), + container_port: HTTP_PORT.into(), + protocol: Some("TCP".to_string()), + ..ContainerPort::default() + }) } ports } -fn startup_probe(trino: &v1alpha1::TrinoCluster) -> Probe { +fn startup_probe(cluster: &ValidatedCluster) -> Probe { Probe { - exec: Some(finished_starting_probe(trino)), + exec: Some(finished_starting_probe(cluster)), period_seconds: Some(5), // Give the coordinator or worker 10 minutes to start up failure_threshold: Some(120), @@ -569,9 +567,9 @@ fn startup_probe(trino: &v1alpha1::TrinoCluster) -> Probe { } } -fn readiness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { +fn readiness_probe(cluster: &ValidatedCluster) -> Probe { Probe { - http_get: Some(http_get_probe(trino)), + http_get: Some(http_get_probe(cluster)), period_seconds: Some(5), failure_threshold: Some(1), timeout_seconds: Some(3), @@ -579,9 +577,9 @@ fn readiness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { } } -fn liveness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { +fn liveness_probe(cluster: &ValidatedCluster) -> Probe { Probe { - http_get: Some(http_get_probe(trino)), + http_get: Some(http_get_probe(cluster)), period_seconds: Some(5), // Coordinators are currently not highly available, so you always have a singe instance. // Restarting it causes all queries to fail, so let's not restart it directly after the first @@ -597,8 +595,8 @@ fn liveness_probe(trino: &v1alpha1::TrinoCluster) -> Probe { /// /// This is the same probe as the [upstream helm-chart](https://github.com/trinodb/charts/blob/7cd0a7bff6c52e0ee6ca6d5394cd72c150ad4379/charts/trino/templates/deployment-coordinator.yaml#L214) /// is using. -fn http_get_probe(trino: &v1alpha1::TrinoCluster) -> HTTPGetAction { - let (schema, port_name) = if trino.expose_https_port() { +fn http_get_probe(cluster: &ValidatedCluster) -> HTTPGetAction { + let (schema, port_name) = if cluster.server_tls_enabled() { ("HTTPS", HTTPS_PORT_NAME) } else { ("HTTP", HTTP_PORT_NAME) @@ -615,9 +613,9 @@ fn http_get_probe(trino: &v1alpha1::TrinoCluster) -> HTTPGetAction { /// Wait until `/v1/info` returns `"starting":false`. /// /// This probe works on coordinators and workers. -fn finished_starting_probe(trino: &v1alpha1::TrinoCluster) -> ExecAction { - let port = trino.exposed_port(); - let schema = if trino.expose_https_port() { +fn finished_starting_probe(cluster: &ValidatedCluster) -> ExecAction { + let port = cluster.exposed_port(); + let schema = if cluster.server_tls_enabled() { "https" } else { "http" diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 21a12fe76..98ebb761b 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -28,7 +28,10 @@ use crate::{ client_protocol::ResolvedClientProtocolConfig, fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, - crd::{TrinoRole, discovery::TrinoPodRef, v1alpha1}, + crd::{ + HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, TrinoRole, discovery::TrinoPodRef, + v1alpha1, + }, }; pub(crate) mod build; @@ -134,6 +137,32 @@ impl ValidatedCluster { } } + /// Whether the (client-facing) server TLS is enabled. + pub fn server_tls_enabled(&self) -> bool { + self.cluster_config.tls.server.is_some() + } + + /// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. + /// + /// Replaces `v1alpha1::TrinoCluster::exposed_port`, derived here from the validated TLS config + /// so build steps don't re-read the raw cluster. + pub fn exposed_port(&self) -> u16 { + if self.server_tls_enabled() { + HTTPS_PORT + } else { + HTTP_PORT + } + } + + /// The name of the client-facing port (see [`Self::exposed_port`]). + pub fn exposed_protocol(&self) -> &'static str { + if self.server_tls_enabled() { + HTTPS_PORT_NAME + } else { + HTTP_PORT_NAME + } + } + /// Type-safe names for the resources of a given role group. pub(crate) fn resource_names(&self, role: &TrinoRole, role_group_name: &str) -> ResourceNames { ResourceNames { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 4aa7a5685..555b446c3 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -555,29 +555,6 @@ impl v1alpha1::TrinoCluster { .sum() } - /// Returns the minimal gracefulShutdownTimeout of all the worker rolegroups. - pub fn min_worker_graceful_shutdown_timeout(&self) -> Duration { - let role_timeout = self - .spec - .workers - .as_ref() - .and_then(|w| w.config.config.graceful_shutdown_timeout); - self.spec - .workers - .as_ref() - .iter() - .flat_map(|worker| worker.role_groups.values()) - .map(|role_group| { - role_group - .config - .config - .graceful_shutdown_timeout - .unwrap_or(role_timeout.unwrap_or(DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT)) - }) - .min() - .unwrap_or(DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT) - } - /// List all coordinator pods expected to form the cluster /// /// We try to predict the pods here rather than looking at the current cluster state in order to @@ -651,30 +628,6 @@ impl v1alpha1::TrinoCluster { let spec: &v1alpha1::TrinoClusterSpec = &self.spec; spec.cluster_config.tls.internal_secret_class.as_deref() } - - pub fn exposed_port(&self) -> u16 { - match self.get_server_tls() { - Some(_) => HTTPS_PORT, - None => HTTP_PORT, - } - } - - pub fn exposed_protocol(&self) -> &str { - match self.get_server_tls() { - Some(_) => HTTPS_PORT_NAME, - None => HTTP_PORT_NAME, - } - } - - /// Returns if the HTTP port should be exposed - pub fn expose_http_port(&self) -> bool { - self.get_server_tls().is_none() - } - - /// Returns if the HTTPS port should be exposed - pub fn expose_https_port(&self) -> bool { - self.get_server_tls().is_some() - } } fn extract_role_from_coordinator_config(fragment: TrinoCoordinatorRoleType) -> TrinoRoleType { @@ -856,89 +809,6 @@ mod tests { assert_eq!(trino.get_server_tls(), Some("simple-trino-server-tls")); } - #[test] - fn test_graceful_shutdown_timeout_default() { - let input = r#" - apiVersion: trino.stackable.tech/v1alpha1 - kind: TrinoCluster - metadata: - name: simple-trino - spec: - image: - productVersion: "479" - clusterConfig: - catalogLabelSelector: {} - "#; - let trino: v1alpha1::TrinoCluster = - serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!( - trino.min_worker_graceful_shutdown_timeout(), - DEFAULT_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT - ); - } - - #[test] - fn test_graceful_shutdown_timeout_on_role() { - let input = r#" - apiVersion: trino.stackable.tech/v1alpha1 - kind: TrinoCluster - metadata: - name: simple-trino - spec: - image: - productVersion: "479" - clusterConfig: - catalogLabelSelector: {} - workers: - config: - gracefulShutdownTimeout: 42h - roleGroups: - default: - replicas: 1 - "#; - let trino: v1alpha1::TrinoCluster = - serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!( - trino.min_worker_graceful_shutdown_timeout(), - Duration::from_hours_unchecked(42) - ); - } - - #[test] - fn test_graceful_shutdown_timeout_on_role_and_rolegroup() { - let input = r#" - apiVersion: trino.stackable.tech/v1alpha1 - kind: TrinoCluster - metadata: - name: simple-trino - spec: - image: - productVersion: "479" - clusterConfig: - catalogLabelSelector: {} - workers: - config: - gracefulShutdownTimeout: 42h - roleGroups: - normal: - replicas: 1 - short: - replicas: 1 - config: - gracefulShutdownTimeout: 5m - long: - replicas: 1 - config: - gracefulShutdownTimeout: 7d - "#; - let trino: v1alpha1::TrinoCluster = - serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!( - trino.min_worker_graceful_shutdown_timeout(), - Duration::from_minutes_unchecked(5) - ); - } - impl RoundtripTestData for v1alpha1::TrinoClusterSpec { fn roundtrip_test_data() -> Vec { stackable_operator::utils::yaml_from_str_singleton_map(indoc::indoc! {r#" diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 7e252f657..8a37779d5 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -286,7 +286,7 @@ pub async fn reconcile_trino( role_group_name, &role_group_service_recommended_labels, role_group_service_selector.clone().into(), - headless_service_ports(trino), + headless_service_ports(&validated_cluster), ) .context(ServiceConfigurationSnafu)?; @@ -385,9 +385,9 @@ pub async fn reconcile_trino( && let Some(listener_group_name) = group_listener_name(trino, trino_role) { let role_group_listener = build_group_listener( - trino, + &validated_cluster, build_recommended_labels( - trino, + &validated_cluster, &validated_cluster.image.app_version_label_value, &trino_role.to_string(), "none", From ce93c807228d61636a24714dfa42954d0782a502 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 18:41:50 +0200 Subject: [PATCH 33/65] refactor: move to v2 pdb and clusteresources --- .../src/controller/build/resource/pdb.rs | 57 ++++++++++--------- rust/operator-binary/src/controller/mod.rs | 44 +++++++++++++- rust/operator-binary/src/crd/mod.rs | 9 --- rust/operator-binary/src/trino_controller.rs | 32 +++++------ 4 files changed, 83 insertions(+), 59 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/pdb.rs b/rust/operator-binary/src/controller/build/resource/pdb.rs index 6e02f2c2a..941a76b4a 100644 --- a/rust/operator-binary/src/controller/build/resource/pdb.rs +++ b/rust/operator-binary/src/controller/build/resource/pdb.rs @@ -1,54 +1,55 @@ -use std::cmp::max; +use std::{cmp::max, str::FromStr}; -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pdb::PodDisruptionBudgetBuilder, commons::pdb::PdbConfig, + commons::pdb::PdbConfig, k8s_openapi::api::policy::v1::PodDisruptionBudget, + v2::{builder::pdb::pod_disruption_budget_builder_with_role, types::operator::RoleName}, }; use crate::{ - crd::{APP_NAME, TrinoRole, v1alpha1}, - trino_controller::{CONTROLLER_NAME, OPERATOR_NAME}, + controller::{ValidatedCluster, controller_name, operator_name, product_name}, + crd::TrinoRole, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("cannot create PodDisruptionBudget for role [{role}]"))] - CreatePdb { - source: stackable_operator::builder::pdb::Error, - role: String, - }, -} - /// Builds the [`PodDisruptionBudget`] for the given `role`, or `None` if PDBs are disabled. /// /// The reconciler applies the returned object; this function does not touch the cluster. pub fn build_pdb( pdb: &PdbConfig, - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, role: &TrinoRole, -) -> Result, Error> { +) -> Option { if !pdb.enabled { - return Ok(None); + return None; } let max_unavailable = pdb.max_unavailable.unwrap_or(match role { TrinoRole::Coordinator => max_unavailable_coordinators(), - TrinoRole::Worker => max_unavailable_workers(trino.num_workers()), + TrinoRole::Worker => max_unavailable_workers(worker_count(cluster)), }); - let pdb = PodDisruptionBudgetBuilder::new_with_role( - trino, - APP_NAME, - &role.to_string(), - OPERATOR_NAME, - CONTROLLER_NAME, + let role_name = + RoleName::from_str(&role.to_string()).expect("a TrinoRole is a valid RFC 1123 role name"); + let pdb = pod_disruption_budget_builder_with_role( + cluster, + &product_name(), + &role_name, + &operator_name(), + &controller_name(), ) - .with_context(|_| CreatePdbSnafu { - role: role.to_string(), - })? .with_max_unavailable(max_unavailable) .build(); - Ok(Some(pdb)) + Some(pdb) +} + +/// Total number of worker replicas across all worker role groups. +fn worker_count(cluster: &ValidatedCluster) -> u16 { + cluster + .role_group_configs + .get(&TrinoRole::Worker) + .into_iter() + .flat_map(|groups| groups.values()) + .map(|rg| rg.replicas) + .sum() } fn max_unavailable_coordinators() -> u16 { diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 98ebb761b..83d86c041 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -12,10 +12,14 @@ use stackable_operator::{ kube::{Resource, api::ObjectMeta}, shared::time::Duration, v2::{ + HasName, HasUid, NameIsValidLabelValue, role_group_utils::ResourceNames, types::{ kubernetes::{NamespaceName, Uid}, - operator::{ClusterName, RoleGroupName as RoleGroupNameV2, RoleName}, + operator::{ + ClusterName, ControllerName, OperatorName, ProductName, + RoleGroupName as RoleGroupNameV2, RoleName, + }, }, }, }; @@ -29,9 +33,10 @@ use crate::{ fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, crd::{ - HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, TrinoRole, discovery::TrinoPodRef, - v1alpha1, + APP_NAME, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, TrinoRole, + discovery::TrinoPodRef, v1alpha1, }, + trino_controller::{CONTROLLER_NAME, OPERATOR_NAME}, }; pub(crate) mod build; @@ -204,6 +209,39 @@ impl Resource for ValidatedCluster { } } +impl HasName for ValidatedCluster { + fn to_name(&self) -> String { + self.name.to_string() + } +} + +impl HasUid for ValidatedCluster { + fn to_uid(&self) -> Uid { + self.uid.clone() + } +} + +impl NameIsValidLabelValue for ValidatedCluster { + fn to_label_value(&self) -> String { + self.name.to_label_value() + } +} + +/// The product name (`trino`) as a type-safe label value. +pub(crate) fn product_name() -> ProductName { + ProductName::from_str(APP_NAME).expect("'trino' 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(CONTROLLER_NAME).expect("the controller name is a valid label value") +} + /// A minimal, valid TrinoCluster spec shared across unit tests. #[cfg(test)] pub(crate) const MINIMAL_TRINO_YAML: &str = r#" diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 555b446c3..d61abdc14 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -546,15 +546,6 @@ impl v1alpha1::TrinoCluster { } } - pub fn num_workers(&self) -> u16 { - self.spec - .workers - .iter() - .flat_map(|w| w.role_groups.values()) - .map(|rg| rg.replicas.unwrap_or(1)) - .sum() - } - /// List all coordinator pods expected to form the cluster /// /// We try to predict the pods here rather than looking at the current cluster state in order to diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 8a37779d5..4e4b4f6a3 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -5,10 +5,10 @@ use const_format::concatcp; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cli::OperatorEnvironmentOptions, - cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, + cluster_resources::ClusterResourceApplyStrategy, commons::{random_secret_creation, rbac::build_rbac_resources}, kube::{ - Resource, ResourceExt, + ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, @@ -21,6 +21,7 @@ use stackable_operator::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, + v2::cluster_resources::cluster_resources_new, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -35,7 +36,7 @@ use crate::{ headless_service_ports, }, }, - dereference, validate, + controller_name, dereference, operator_name, product_name, validate, }, crd::{APP_NAME, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, v1alpha1}, }; @@ -63,11 +64,6 @@ pub(crate) const CONTAINER_IMAGE_BASE_NAME: &str = "trino"; #[strum_discriminants(derive(IntoStaticStr))] #[allow(clippy::enum_variant_names)] pub enum 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, @@ -123,9 +119,6 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to create PodDisruptionBudget"))] - FailedToCreatePdb { source: build::resource::pdb::Error }, - #[snafu(display("failed to apply PodDisruptionBudget"))] ApplyPdb { source: stackable_operator::cluster_resources::Error, @@ -211,15 +204,16 @@ pub async fn reconcile_trino( "Validated TrinoCluster" ); - let mut cluster_resources = ClusterResources::new( - APP_NAME, - OPERATOR_NAME, - CONTROLLER_NAME, - &trino.object_ref(&()), + let mut cluster_resources = cluster_resources_new( + &product_name(), + &operator_name(), + &controller_name(), + &validated_cluster.name, + &validated_cluster.namespace, + &validated_cluster.uid, ClusterResourceApplyStrategy::from(&trino.spec.cluster_operation), &trino.spec.object_overrides, - ) - .context(CreateClusterResourcesSnafu)?; + ); let (rbac_sa, rbac_rolebinding) = build_rbac_resources( trino, @@ -407,7 +401,7 @@ pub async fn reconcile_trino( if let Some(GenericRoleConfig { pod_disruption_budget: pdb, }) = role_config - && let Some(pdb) = build_pdb(pdb, trino, trino_role).context(FailedToCreatePdbSnafu)? + && let Some(pdb) = build_pdb(pdb, &validated_cluster, trino_role) { cluster_resources .add(client, pdb) From 8284feedf7e8203ff637adbe9bfeb364a1d4d10b Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 18:57:05 +0200 Subject: [PATCH 34/65] refactor: use v2 labeling --- .../controller/build/resource/config_map.rs | 12 ++-- .../src/controller/build/resource/listener.rs | 12 +--- .../src/controller/build/resource/service.rs | 17 ++--- .../controller/build/resource/statefulset.rs | 40 +++-------- rust/operator-binary/src/controller/mod.rs | 71 +++++++++++++++++-- rust/operator-binary/src/trino_controller.rs | 45 ++---------- 6 files changed, 95 insertions(+), 102 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 68e1b40db..d88549734 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -6,7 +6,7 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, k8s_openapi::api::core::v1::ConfigMap, - kvp::ObjectLabels, + kvp::Labels, product_logging::framework::VECTOR_CONFIG_FILE, utils::cluster_info::KubernetesClusterInfo, v2::config_file_writer::to_java_properties_string, @@ -64,7 +64,7 @@ pub fn build_rolegroup_config_map( role: &TrinoRole, role_group_name: &str, cluster_info: &KubernetesClusterInfo, - recommended_labels: &ObjectLabels<'_, ValidatedCluster>, + recommended_labels: &Labels, ) -> Result { let role_group_configs = cluster @@ -196,8 +196,7 @@ pub fn build_rolegroup_config_map( .namespace(cluster.namespace.to_string()) .ownerreference_from_resource(cluster, None, Some(true)) .context(MetadataSnafu)? - .with_recommended_labels(recommended_labels) - .context(MetadataSnafu)? + .with_labels(recommended_labels.clone()) .build(), ) .data(data) @@ -213,7 +212,7 @@ pub fn build_rolegroup_catalog_config_map( cluster: &ValidatedCluster, role: &TrinoRole, role_group_name: &str, - recommended_labels: &ObjectLabels<'_, ValidatedCluster>, + recommended_labels: &Labels, ) -> Result { let catalog_config_map_name = format!( "{}-catalog", @@ -228,8 +227,7 @@ pub fn build_rolegroup_catalog_config_map( .namespace(cluster.namespace.to_string()) .ownerreference_from_resource(cluster, None, Some(true)) .context(MetadataSnafu)? - .with_recommended_labels(recommended_labels) - .context(MetadataSnafu)? + .with_labels(recommended_labels.clone()) .build(), ) .data( diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index f9548bb14..690dc27d1 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -7,7 +7,7 @@ use stackable_operator::{ crd::listener::v1alpha1::{Listener, ListenerPort, ListenerSpec}, k8s_openapi::api::core::v1::PersistentVolumeClaim, kube::ResourceExt, - kvp::{Labels, ObjectLabels}, + kvp::Labels, }; use crate::{ @@ -25,11 +25,6 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to build listener object meta data"))] - BuildObjectMeta { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build listener volume"))] BuildListenerPersistentVolume { source: stackable_operator::builder::pod::volume::ListenerOperatorVolumeSourceBuilderError, @@ -38,7 +33,7 @@ pub enum Error { pub fn build_group_listener( cluster: &ValidatedCluster, - object_labels: ObjectLabels, + recommended_labels: Labels, listener_class: String, listener_group_name: String, ) -> Result { @@ -48,8 +43,7 @@ pub fn build_group_listener( .name(listener_group_name) .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&object_labels) - .context(BuildObjectMetaSnafu)? + .with_labels(recommended_labels) .build(), spec: ListenerSpec { class_name: Some(listener_class), diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index d2b57ef52..0fef856ae 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -4,7 +4,7 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, - kvp::{Annotations, Labels, ObjectLabels}, + kvp::{Annotations, Labels}, }; use crate::{ @@ -18,11 +18,6 @@ pub enum Error { ObjectMissingMetadataForOwnerRef { source: stackable_operator::builder::meta::Error, }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, } /// The rolegroup headless [`Service`] is a service that allows direct access to the instances of a certain rolegroup @@ -31,7 +26,7 @@ pub fn build_rolegroup_headless_service( cluster: &ValidatedCluster, role: &TrinoRole, role_group_name: &str, - object_labels: &ObjectLabels, + recommended_labels: &Labels, selector: BTreeMap, ports: Vec, ) -> Result { @@ -46,8 +41,7 @@ pub fn build_rolegroup_headless_service( ) .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(object_labels) - .context(MetadataBuildSnafu)? + .with_labels(recommended_labels.clone()) .build(), spec: Some(ServiceSpec { // Internal communication does not need to be exposed @@ -67,7 +61,7 @@ pub fn build_rolegroup_metrics_service( cluster: &ValidatedCluster, role: &TrinoRole, role_group_name: &str, - object_labels: &ObjectLabels, + recommended_labels: &Labels, selector: BTreeMap, ) -> Result { Ok(Service { @@ -81,8 +75,7 @@ pub fn build_rolegroup_metrics_service( ) .ownerreference_from_resource(cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(object_labels) - .context(MetadataBuildSnafu)? + .with_labels(recommended_labels.clone()) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 31a05a666..0291d2230 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -30,7 +30,7 @@ use stackable_operator::{ }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, - kvp::{Annotation, Annotations, Labels}, + kvp::{Annotation, Annotations}, product_logging, shared::time::Duration, v2::{ @@ -54,7 +54,7 @@ use crate::{ }, }, crd::{ - APP_NAME, CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, + CONFIG_DIR_NAME, Container, ENV_INTERNAL_SECRET, ENV_SPOOLING_SECRET, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, MAX_TRINO_LOG_FILES_SIZE, METRICS_PORT, METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, @@ -63,7 +63,7 @@ use crate::{ }, trino_controller::{ MAX_PREPARE_LOG_FILE_SIZE, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, - build_recommended_labels, shared_internal_secret_name, shared_spooling_secret_name, + shared_internal_secret_name, shared_spooling_secret_name, }, }; @@ -168,7 +168,6 @@ pub fn build_rolegroup_statefulset( .rolegroup(trino_role, role_group_name) .context(InternalOperatorFailureSnafu)?; - let role_name = trino_role.to_string(); let resource_names = cluster.resource_names(trino_role, role_group_name); let config_map_name = resource_names.role_group_config_map().to_string(); @@ -326,15 +325,10 @@ pub fn build_rolegroup_statefulset( .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) .context(AddVolumeMountSnafu)?; - // Used for PVC templates that cannot be modified once they are deployed - let unversioned_recommended_labels = Labels::recommended(&build_recommended_labels( - trino, - // A version value is required, and we do want to use the "recommended" format for the other desired labels - "none", - &role_name, - role_group_name, - )) - .context(LabelBuildSnafu)?; + // Used for PVC templates that cannot be modified once they are deployed, so a fixed + // "none" version is used while keeping the other recommended labels. + let unversioned_recommended_labels = + cluster.unversioned_recommended_labels(trino_role, role_group_name); persistent_volume_claims.push( build_group_listener_pvc(&group_listener_name, &unversioned_recommended_labels) @@ -407,13 +401,7 @@ pub fn build_rolegroup_statefulset( } let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &role_name, - role_group_name, - )) - .context(MetadataBuildSnafu)? + .with_labels(cluster.recommended_labels(trino_role, role_group_name)) .with_annotation( // This is actually used by some kuttl tests (as they don't specify the container explicitly) Annotation::try_from(("kubectl.kubernetes.io/default-container", "trino")) @@ -481,13 +469,7 @@ pub fn build_rolegroup_statefulset( .name(resource_names.stateful_set_name().to_string()) .ownerreference_from_resource(trino, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - trino, - &resolved_product_image.app_version_label_value, - &role_name, - role_group_name, - )) - .context(MetadataBuildSnafu)? + .with_labels(cluster.recommended_labels(trino_role, role_group_name)) .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) .with_annotations(annotations) .build(), @@ -496,8 +478,8 @@ pub fn build_rolegroup_statefulset( replicas: rolegroup.replicas.map(i32::from), selector: LabelSelector { match_labels: Some( - Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) - .context(LabelBuildSnafu)? + cluster + .role_group_selector(trino_role, role_group_name) .into(), ), ..LabelSelector::default() diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 83d86c041..e008f8316 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -10,14 +10,16 @@ use stackable_operator::{ resources::{NoRuntimeLimits, Resources}, }, kube::{Resource, api::ObjectMeta}, + kvp::Labels, shared::time::Duration, v2::{ HasName, HasUid, NameIsValidLabelValue, + kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, types::{ kubernetes::{NamespaceName, Uid}, operator::{ - ClusterName, ControllerName, OperatorName, ProductName, + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleGroupName as RoleGroupNameV2, RoleName, }, }, @@ -172,12 +174,71 @@ impl ValidatedCluster { pub(crate) fn resource_names(&self, role: &TrinoRole, role_group_name: &str) -> ResourceNames { ResourceNames { cluster_name: self.name.clone(), - role_name: RoleName::from_str(&role.to_string()) - .expect("a TrinoRole is a valid RFC 1123 role name"), - role_group_name: RoleGroupNameV2::from_str(role_group_name) - .expect("a validated role group name is a valid role group name"), + role_name: Self::role_name(role), + role_group_name: Self::role_group_name(role_group_name), } } + + /// A [`TrinoRole`] as a type-safe [`RoleName`]. + fn role_name(role: &TrinoRole) -> RoleName { + RoleName::from_str(&role.to_string()).expect("a TrinoRole is a valid RFC 1123 role name") + } + + /// A role-group name as a type-safe [`RoleGroupName`](RoleGroupNameV2). + fn role_group_name(role_group_name: &str) -> RoleGroupNameV2 { + RoleGroupNameV2::from_str(role_group_name) + .expect("a validated role group name is a valid role group name") + } + + /// The version label value (`app.kubernetes.io/version`) as a type-safe [`ProductVersion`]. + fn version_label(&self) -> ProductVersion { + ProductVersion::from_str(&self.image.app_version_label_value) + .expect("the app version label value is a valid product version") + } + + fn recommended_labels_with_version( + &self, + version: &ProductVersion, + role: &TrinoRole, + role_group_name: &str, + ) -> Labels { + recommended_labels( + self, + &product_name(), + version, + &operator_name(), + &controller_name(), + &Self::role_name(role), + &Self::role_group_name(role_group_name), + ) + } + + /// Recommended labels for a role-group resource (using the resolved product version). + pub(crate) fn recommended_labels(&self, role: &TrinoRole, role_group_name: &str) -> Labels { + self.recommended_labels_with_version(&self.version_label(), role, role_group_name) + } + + /// Recommended labels using a fixed `"none"` version, for resources whose labels must not + /// change after creation (e.g. listener PVC templates). + pub(crate) fn unversioned_recommended_labels( + &self, + role: &TrinoRole, + role_group_name: &str, + ) -> Labels { + let none = ProductVersion::from_str("none") + .expect("\"none\" is a valid product version label value"); + self.recommended_labels_with_version(&none, role, role_group_name) + } + + /// Selector labels matching the pods of a role group. + pub(crate) fn role_group_selector(&self, role: &TrinoRole, role_group_name: &str) -> Labels { + role_group_selector( + self, + &product_name(), + &Self::role_name(role), + &Self::role_group_name(role_group_name), + ) + } } impl Resource for ValidatedCluster { diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 4e4b4f6a3..debe3cde6 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -12,7 +12,6 @@ use stackable_operator::{ core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, - kvp::{Labels, ObjectLabels}, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, role_utils::GenericRoleConfig, @@ -260,19 +259,13 @@ pub async fn reconcile_trino( for (trino_role, role_group_configs) in &validated_cluster.role_group_configs { for (role_group_name, rg) in role_group_configs { - let role_name = trino_role.to_string(); let merged_config = &rg.config; - let role_group_service_recommended_labels = build_recommended_labels( - &validated_cluster, - &validated_cluster.image.app_version_label_value, - &role_name, - role_group_name, - ); + let role_group_service_recommended_labels = + validated_cluster.recommended_labels(trino_role, role_group_name); let role_group_service_selector = - Labels::role_group_selector(trino, APP_NAME, &role_name, role_group_name) - .context(LabelBuildSnafu)?; + validated_cluster.role_group_selector(trino_role, role_group_name); let rg_headless_service = build_rolegroup_headless_service( &validated_cluster, @@ -380,12 +373,7 @@ pub async fn reconcile_trino( { let role_group_listener = build_group_listener( &validated_cluster, - build_recommended_labels( - &validated_cluster, - &validated_cluster.image.app_version_label_value, - &trino_role.to_string(), - "none", - ), + validated_cluster.recommended_labels(trino_role, "none"), listener_class.to_string(), listener_group_name, ) @@ -443,23 +431,6 @@ pub fn error_policy( } } -pub(crate) fn build_recommended_labels<'a, T>( - owner: &'a T, - app_version: &'a str, - role: &'a str, - role_group: &'a str, -) -> ObjectLabels<'a, T> { - ObjectLabels { - owner, - app_name: APP_NAME, - app_version, - operator_name: OPERATOR_NAME, - controller_name: CONTROLLER_NAME, - role, - role_group, - } -} - pub(crate) fn shared_internal_secret_name(trino: &v1alpha1::TrinoCluster) -> String { format!("{}-internal-secret", trino.name_any()) } @@ -559,14 +530,8 @@ mod tests { validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); let trino_role = TrinoRole::Coordinator; - let role_name = trino_role.to_string(); let role_group_name = "default"; - let recommended_labels = build_recommended_labels( - &validated_cluster, - &validated_cluster.image.app_version_label_value, - &role_name, - role_group_name, - ); + let recommended_labels = validated_cluster.recommended_labels(&trino_role, role_group_name); build::resource::config_map::build_rolegroup_config_map( &validated_cluster, From 56e22d858b616a4a2b59d30b2a786d26546070a9 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 19:24:23 +0200 Subject: [PATCH 35/65] refactor: switch to v2 STACKABLE_LOG_DIR --- rust/operator-binary/src/crd/mod.rs | 2 +- rust/operator-binary/src/trino_controller.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index d61abdc14..999d04910 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -79,7 +79,7 @@ pub const RW_CONFIG_DIR_NAME: &str = "/stackable/rwconfig"; pub const STACKABLE_SERVER_TLS_DIR: &str = "/stackable/server_tls"; pub const STACKABLE_CLIENT_TLS_DIR: &str = "/stackable/client_tls"; pub const STACKABLE_INTERNAL_TLS_DIR: &str = "/stackable/internal_tls"; -pub const STACKABLE_LOG_DIR: &str = "/stackable/log"; +pub use stackable_operator::v2::product_logging::framework::STACKABLE_LOG_DIR; pub const STACKABLE_MOUNT_SERVER_TLS_DIR: &str = "/stackable/mount_server_tls"; pub const STACKABLE_MOUNT_INTERNAL_TLS_DIR: &str = "/stackable/mount_internal_tls"; // store pws diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index debe3cde6..dca1ba412 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -49,7 +49,7 @@ pub const OPERATOR_NAME: &str = "trino.stackable.tech"; pub const CONTROLLER_NAME: &str = "trinocluster"; pub const FULL_CONTROLLER_NAME: &str = concatcp!(CONTROLLER_NAME, '.', OPERATOR_NAME); -pub const STACKABLE_LOG_DIR: &str = "/stackable/log"; +pub use stackable_operator::v2::product_logging::framework::STACKABLE_LOG_DIR; pub const STACKABLE_LOG_CONFIG_DIR: &str = "/stackable/log_config"; pub const MAX_PREPARE_LOG_FILE_SIZE: MemoryQuantity = MemoryQuantity { From fb858b02f74885a31ebe113f543b68765fb00089 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 19:34:30 +0200 Subject: [PATCH 36/65] refactor: switch to v2 ownerref, remove results --- .../controller/build/resource/config_map.rs | 15 ++++------ .../src/controller/build/resource/listener.rs | 9 ++---- .../src/controller/build/resource/service.rs | 28 ++++++------------- .../controller/build/resource/statefulset.rs | 10 ++----- rust/operator-binary/src/trino_controller.rs | 11 ++------ 5 files changed, 20 insertions(+), 53 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index d88549734..f765f052c 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -9,7 +9,9 @@ use stackable_operator::{ kvp::Labels, product_logging::framework::VECTOR_CONFIG_FILE, utils::cluster_info::KubernetesClusterInfo, - v2::config_file_writer::to_java_properties_string, + v2::{ + builder::meta::ownerreference_from_resource, config_file_writer::to_java_properties_string, + }, }; use crate::{ @@ -48,11 +50,6 @@ pub enum Error { rolegroup: String, }, - #[snafu(display("metadata build failure"))] - Metadata { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build jvm.config"))] BuildJvmConfig { source: crate::config::jvm::Error }, } @@ -194,8 +191,7 @@ pub fn build_rolegroup_config_map( ObjectMetaBuilder::new() .name(&config_map_name) .namespace(cluster.namespace.to_string()) - .ownerreference_from_resource(cluster, None, Some(true)) - .context(MetadataSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(recommended_labels.clone()) .build(), ) @@ -225,8 +221,7 @@ pub fn build_rolegroup_catalog_config_map( ObjectMetaBuilder::new() .name(&catalog_config_map_name) .namespace(cluster.namespace.to_string()) - .ownerreference_from_resource(cluster, None, Some(true)) - .context(MetadataSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(recommended_labels.clone()) .build(), ) diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 690dc27d1..a4717a62d 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -8,6 +8,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::PersistentVolumeClaim, kube::ResourceExt, kvp::Labels, + v2::builder::meta::ownerreference_from_resource, }; use crate::{ @@ -20,11 +21,6 @@ pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("listener object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build listener volume"))] BuildListenerPersistentVolume { source: stackable_operator::builder::pod::volume::ListenerOperatorVolumeSourceBuilderError, @@ -41,8 +37,7 @@ pub fn build_group_listener( metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) .name(listener_group_name) - .ownerreference_from_resource(cluster, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(recommended_labels) .build(), spec: ListenerSpec { diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 0fef856ae..9ad3806db 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -1,10 +1,10 @@ use std::collections::BTreeMap; -use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels}, + v2::builder::meta::ownerreference_from_resource, }; use crate::{ @@ -12,14 +12,6 @@ use crate::{ crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole}, }; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, -} - /// The rolegroup headless [`Service`] is a service that allows direct access to the instances of a certain rolegroup /// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing. pub fn build_rolegroup_headless_service( @@ -29,8 +21,8 @@ pub fn build_rolegroup_headless_service( recommended_labels: &Labels, selector: BTreeMap, ports: Vec, -) -> Result { - Ok(Service { +) -> Service { + Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) .name( @@ -39,8 +31,7 @@ pub fn build_rolegroup_headless_service( .headless_service_name() .to_string(), ) - .ownerreference_from_resource(cluster, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(recommended_labels.clone()) .build(), spec: Some(ServiceSpec { @@ -53,7 +44,7 @@ pub fn build_rolegroup_headless_service( ..ServiceSpec::default() }), status: None, - }) + } } /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label. @@ -63,8 +54,8 @@ pub fn build_rolegroup_metrics_service( role_group_name: &str, recommended_labels: &Labels, selector: BTreeMap, -) -> Result { - Ok(Service { +) -> Service { + Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) .name( @@ -73,8 +64,7 @@ pub fn build_rolegroup_metrics_service( .metrics_service_name() .to_string(), ) - .ownerreference_from_resource(cluster, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(recommended_labels.clone()) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) @@ -89,7 +79,7 @@ pub fn build_rolegroup_metrics_service( ..ServiceSpec::default() }), status: None, - }) + } } pub(crate) fn headless_service_ports(cluster: &ValidatedCluster) -> Vec { diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 0291d2230..a5bef1127 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -34,7 +34,7 @@ use stackable_operator::{ product_logging, shared::time::Duration, v2::{ - builder::pod::container::EnvVarSet, + builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, types::kubernetes::{ContainerName, VolumeName}, }, @@ -78,11 +78,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("internal operator failure: {source}"))] InternalOperatorFailure { source: crate::crd::Error }, @@ -467,8 +462,7 @@ pub fn build_rolegroup_statefulset( metadata: ObjectMetaBuilder::new() .name_and_namespace(trino) .name(resource_names.stateful_set_name().to_string()) - .ownerreference_from_resource(trino, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? + .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(cluster.recommended_labels(trino_role, role_group_name)) .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) .with_annotations(annotations) diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index dca1ba412..a41864067 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -155,11 +155,6 @@ pub enum Error { source: build::resource::listener::Error, }, - #[snafu(display("failed to configure service"))] - ServiceConfiguration { - source: build::resource::service::Error, - }, - #[snafu(display("failed to create internal secret"))] CreateInternalSecret { source: random_secret_creation::Error, @@ -274,8 +269,7 @@ pub async fn reconcile_trino( &role_group_service_recommended_labels, role_group_service_selector.clone().into(), headless_service_ports(&validated_cluster), - ) - .context(ServiceConfigurationSnafu)?; + ); let rg_metrics_service = build_rolegroup_metrics_service( &validated_cluster, @@ -283,8 +277,7 @@ pub async fn reconcile_trino( role_group_name, &role_group_service_recommended_labels, role_group_service_selector.into(), - ) - .context(ServiceConfigurationSnafu)?; + ); let rg_configmap = build::resource::config_map::build_rolegroup_config_map( &validated_cluster, From fd3966ec36ef9121730c70a14e9fcfe69cd4e3be Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 19:59:06 +0200 Subject: [PATCH 37/65] refactor: use restarter_ignore_secret_annotations --- .../controller/build/resource/statefulset.rs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index a5bef1127..63a99d3ba 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -1,6 +1,6 @@ //! Builds the per-rolegroup [`StatefulSet`] that runs a Trino role group. -use std::{collections::BTreeMap, convert::Infallible, str::FromStr}; +use std::{convert::Infallible, str::FromStr}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -30,13 +30,16 @@ use stackable_operator::{ }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, - kvp::{Annotation, Annotations}, + kvp::Annotation, product_logging, shared::time::Duration, v2::{ - builder::{meta::ownerreference_from_resource, pod::container::EnvVarSet}, + builder::{ + meta::ownerreference_from_resource, pod::container::EnvVarSet, + statefulset::restarter_ignore_secret_annotations, + }, product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, - types::kubernetes::{ContainerName, VolumeName}, + types::kubernetes::{ContainerName, SecretName, VolumeName}, }, }; @@ -102,6 +105,11 @@ pub enum Error { source: stackable_operator::kvp::KeyValuePairError, }, + #[snafu(display("failed to parse hot-reloaded Secret name"))] + ParseSecretName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, + #[snafu(display("failed to build Metadata"))] MetadataBuild { source: stackable_operator::builder::meta::Error, @@ -443,20 +451,14 @@ pub fn build_rolegroup_statefulset( pod_template.merge_from(role.config.pod_overrides.clone()); pod_template.merge_from(rolegroup.config.pod_overrides.clone()); - let ignore_secret_annotations = trino_authentication_config + let ignore_secrets = trino_authentication_config .hot_reloaded_secrets() .iter() - .enumerate() - .map(|(i, secret_name)| { - ( - format!("restarter.stackable.tech/ignore-secret.{i}"), - secret_name, - ) - }) - .collect::>(); + .map(|secret_name| SecretName::from_str(secret_name)) + .collect::, _>>() + .context(ParseSecretNameSnafu)?; - let annotations = - Annotations::try_from(ignore_secret_annotations).context(AnnotationBuildSnafu)?; + let annotations = restarter_ignore_secret_annotations(ignore_secrets); Ok(StatefulSet { metadata: ObjectMetaBuilder::new() From 2af6a048e875bfdda9441b99e5b0fc5d96cd3809 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 20:04:41 +0200 Subject: [PATCH 38/65] refactor: use v2 SecretName --- .../operator-binary/src/authentication/mod.rs | 7 ++++--- .../src/authentication/password/mod.rs | 17 ++++++++++++--- .../controller/build/resource/statefulset.rs | 21 +++++++------------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/rust/operator-binary/src/authentication/mod.rs b/rust/operator-binary/src/authentication/mod.rs index 431e7c0ca..ba67a8c64 100644 --- a/rust/operator-binary/src/authentication/mod.rs +++ b/rust/operator-binary/src/authentication/mod.rs @@ -19,6 +19,7 @@ use stackable_operator::{ crd::authentication::core, k8s_openapi::api::core::v1::{Container, EnvVar, Volume, VolumeMount}, kube::{ResourceExt, runtime::reflector::ObjectRef}, + v2::types::kubernetes::SecretName, }; use strum::EnumDiscriminants; use tracing::trace; @@ -95,7 +96,7 @@ pub struct TrinoAuthenticationConfig { /// Additional side car container for the provided role sidecar_containers: HashMap>, /// Secrets which can be hot-reloaded and should be excluded from the restart controller - hot_reloaded_secrets: BTreeSet, + hot_reloaded_secrets: BTreeSet, } impl TrinoAuthenticationConfig { @@ -371,12 +372,12 @@ impl TrinoAuthenticationConfig { } /// Retrieve all Secrets which can be hot-reloaded - pub fn hot_reloaded_secrets(&self) -> &BTreeSet { + pub fn hot_reloaded_secrets(&self) -> &BTreeSet { &self.hot_reloaded_secrets } /// Add a Secret which can be hot-reloaded - pub fn add_hot_reloaded_secret(&mut self, secret_name: String) { + pub fn add_hot_reloaded_secret(&mut self, secret_name: SecretName) { self.hot_reloaded_secrets.insert(secret_name); } diff --git a/rust/operator-binary/src/authentication/password/mod.rs b/rust/operator-binary/src/authentication/password/mod.rs index 54cae2c3a..e94ed704d 100644 --- a/rust/operator-binary/src/authentication/password/mod.rs +++ b/rust/operator-binary/src/authentication/password/mod.rs @@ -8,8 +8,12 @@ //! - volume and volume mounts //! - extra containers and commands //! +use std::str::FromStr; + use snafu::{ResultExt, Snafu}; -use stackable_operator::commons::product_image_selection::ResolvedProductImage; +use stackable_operator::{ + commons::product_image_selection::ResolvedProductImage, v2::types::kubernetes::SecretName, +}; use tracing::trace; use crate::{ @@ -37,6 +41,11 @@ pub enum Error { #[snafu(display("failed to create LDAP Volumes and VolumeMounts"))] BuildPasswordFileUpdateContainer { source: file::Error }, + + #[snafu(display("failed to parse user credentials Secret name"))] + ParseSecretName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, } #[derive(Clone, Debug, Default)] @@ -105,8 +114,10 @@ impl TrinoPasswordAuthentication { FileAuthenticator::password_db_volume_mount(), ); - password_authentication_config - .add_hot_reloaded_secret(file_authenticator.secret_name()); + password_authentication_config.add_hot_reloaded_secret( + SecretName::from_str(&file_authenticator.secret_name()) + .context(ParseSecretNameSnafu)?, + ); } TrinoPasswordAuthenticator::Ldap(ldap_authenticator) => { let config_file_name = ldap_authenticator.config_file_name(); diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 63a99d3ba..acff17733 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -39,7 +39,7 @@ use stackable_operator::{ statefulset::restarter_ignore_secret_annotations, }, product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, - types::kubernetes::{ContainerName, SecretName, VolumeName}, + types::kubernetes::{ContainerName, VolumeName}, }, }; @@ -105,11 +105,6 @@ pub enum Error { source: stackable_operator::kvp::KeyValuePairError, }, - #[snafu(display("failed to parse hot-reloaded Secret name"))] - ParseSecretName { - source: stackable_operator::v2::macros::attributed_string_type::Error, - }, - #[snafu(display("failed to build Metadata"))] MetadataBuild { source: stackable_operator::builder::meta::Error, @@ -451,14 +446,12 @@ pub fn build_rolegroup_statefulset( pod_template.merge_from(role.config.pod_overrides.clone()); pod_template.merge_from(rolegroup.config.pod_overrides.clone()); - let ignore_secrets = trino_authentication_config - .hot_reloaded_secrets() - .iter() - .map(|secret_name| SecretName::from_str(secret_name)) - .collect::, _>>() - .context(ParseSecretNameSnafu)?; - - let annotations = restarter_ignore_secret_annotations(ignore_secrets); + let annotations = restarter_ignore_secret_annotations( + trino_authentication_config + .hot_reloaded_secrets() + .iter() + .cloned(), + ); Ok(StatefulSet { metadata: ObjectMetaBuilder::new() From 093c3fa0a5e63727a0b9d11bf6085eca7a043bef Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 20:09:47 +0200 Subject: [PATCH 39/65] refactor: use v2 SecretClass in CRD --- rust/operator-binary/src/authorization/opa.rs | 9 +++++---- .../src/controller/build/resource/statefulset.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 5a4f82e44..4c583b43c 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -1,8 +1,8 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ client::Client, commons::opa::OpaApiVersion, k8s_openapi::api::core::v1::ConfigMap, - kube::ResourceExt, + kube::ResourceExt, v2::types::kubernetes::SecretClassName, }; use crate::crd::v1alpha1; @@ -34,7 +34,7 @@ pub struct TrinoOpaConfig { /// Optional TLS secret class for OPA communication. /// If set, the CA certificate from this secret class will be added /// to Trino's truststore to make it trust OPA's TLS certificate. - pub(crate) tls_secret_class: Option, + pub(crate) tls_secret_class: Option, } impl TrinoOpaConfig { @@ -93,7 +93,8 @@ impl TrinoOpaConfig { .await .ok() .and_then(|cm| cm.data) - .and_then(|mut data| data.remove("OPA_SECRET_CLASS")); + .and_then(|mut data| data.remove("OPA_SECRET_CLASS")) + .and_then(|secret_class| SecretClassName::from_str(&secret_class).ok()); Ok(TrinoOpaConfig { non_batched_connection_string, diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index acff17733..d18db2e68 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -743,7 +743,7 @@ fn tls_volume_mounts( let opa_tls_volume = VolumeBuilder::new(OPA_TLS_VOLUME_NAME) .ephemeral( SecretOperatorVolumeSourceBuilder::new( - tls_secret_class, + tls_secret_class.as_ref(), SecretClassVolumeProvisionParts::PublicPrivate, ) .build() From c6cefe5985759ac2985b9a3fc004b8896e6bf732 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 20:32:47 +0200 Subject: [PATCH 40/65] refactor: use SecretName, ListenerName, ConfigMapName --- extra/crds.yaml | 36 +++++++++++++++++++ rust/operator-binary/src/catalog/commons.rs | 2 +- .../src/controller/build/resource/listener.rs | 6 ++-- .../src/controller/validate.rs | 10 +----- rust/operator-binary/src/crd/affinity.rs | 4 +-- .../src/crd/catalog/commons.rs | 9 +++-- rust/operator-binary/src/crd/mod.rs | 36 ++++++++++++------- rust/operator-binary/src/trino_controller.rs | 8 +---- 8 files changed, 74 insertions(+), 37 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index 9da452905..db68d3d08 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -354,6 +354,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the HDFS cluster. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -638,6 +641,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the HDFS cluster. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -898,7 +904,10 @@ spec: This setting controls: - Which cert the servers should use to authenticate themselves against other servers - Which ca.crt to use when validating the other server + maxLength: 253 + minLength: 1 nullable: true + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string serverSecretClass: default: tls @@ -907,7 +916,10 @@ spec: This setting controls: - If TLS encryption is used at all - Which cert the servers should use to authenticate themselves against the client + maxLength: 253 + minLength: 1 nullable: true + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string type: object vectorAggregatorConfigMapName: @@ -916,7 +928,10 @@ spec: It must contain the key `ADDRESS` with the address of the Vector aggregator. Follow the [logging tutorial](https://docs.stackable.tech/home/nightly/tutorials/logging-vector-aggregator) to learn how to configure log aggregation with Vector. + maxLength: 253 + minLength: 1 nullable: true + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - catalogLabelSelector @@ -1529,6 +1544,9 @@ spec: listenerClass: default: cluster-internal description: This field controls which [ListenerClass](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass.html) is used to expose the coordinator. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string podDisruptionBudget: default: @@ -3517,6 +3535,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the HDFS cluster. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -3526,6 +3547,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the Hive metastore. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -3787,6 +3811,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the HDFS cluster. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -3796,6 +3823,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the Hive metastore. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -3966,6 +3996,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the HDFS cluster. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap @@ -3980,6 +4013,9 @@ spec: properties: configMap: description: Name of the [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) providing information about the Hive metastore. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - configMap diff --git a/rust/operator-binary/src/catalog/commons.rs b/rust/operator-binary/src/catalog/commons.rs index c326eeb31..26778040e 100644 --- a/rust/operator-binary/src/catalog/commons.rs +++ b/rust/operator-binary/src/catalog/commons.rs @@ -37,7 +37,7 @@ impl ExtendCatalogConfig for MetastoreConnection { _trino_version: u16, ) -> Result<(), FromTrinoCatalogError> { let hive_cm: ConfigMap = client - .get(&self.config_map, catalog_namespace.as_ref()) + .get(self.config_map.as_ref(), catalog_namespace.as_ref()) .await .with_context(|_| FailedToGetDiscoveryConfigMapSnafu { catalog: catalog_name.to_string(), diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index a4717a62d..7f63d4646 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -32,8 +32,8 @@ pub fn build_group_listener( recommended_labels: Labels, listener_class: String, listener_group_name: String, -) -> Result { - Ok(Listener { +) -> Listener { + Listener { metadata: ObjectMetaBuilder::new() .name_and_namespace(cluster) .name(listener_group_name) @@ -46,7 +46,7 @@ pub fn build_group_listener( ..ListenerSpec::default() }, status: None, - }) + } } pub fn build_group_listener_pvc( diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 1f3e4ed19..d66fa13f7 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -98,11 +98,6 @@ pub enum Error { "the Vector aggregator discovery ConfigMap name is required 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, - }, } type Result = std::result::Result; @@ -208,10 +203,7 @@ pub fn validate( .spec .cluster_config .vector_aggregator_config_map_name - .as_deref() - .map(ConfigMapName::from_str) - .transpose() - .context(ParseVectorAggregatorConfigMapNameSnafu)?; + .clone(); let mut role_group_configs: BTreeMap> = BTreeMap::new(); diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index 53a80c8d6..75545b4bf 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -39,7 +39,7 @@ pub fn get_affinity( .map(|hive_cluster_name| { affinity_between_role_pods( "hive", - hive_cluster_name, // The discovery cm has the same name as the HiveCluster itself + hive_cluster_name.as_ref(), // The discovery cm has the same name as the HiveCluster itself "metastore", 50, ) @@ -67,7 +67,7 @@ pub fn get_affinity( .map(|hdfs_cluster_name| { affinity_between_role_pods( "hdfs", - hdfs_cluster_name, // The discovery cm has the same name as the HdfsCluster itself + hdfs_cluster_name.as_ref(), // The discovery cm has the same name as the HdfsCluster itself "datanode", 50, ) diff --git a/rust/operator-binary/src/crd/catalog/commons.rs b/rust/operator-binary/src/crd/catalog/commons.rs index 39fe834cc..a4fe7c2ef 100644 --- a/rust/operator-binary/src/crd/catalog/commons.rs +++ b/rust/operator-binary/src/crd/catalog/commons.rs @@ -1,16 +1,19 @@ use serde::{Deserialize, Serialize}; -use stackable_operator::schemars::{self, JsonSchema}; +use stackable_operator::{ + schemars::{self, JsonSchema}, + v2::types::kubernetes::ConfigMapName, +}; #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MetastoreConnection { /// Name of the [discovery ConfigMap](DOCS_BASE_URL_PLACEHOLDER/concepts/service_discovery) providing information about the Hive metastore. - pub config_map: String, + pub config_map: ConfigMapName, } #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct HdfsConnection { /// Name of the [discovery ConfigMap](DOCS_BASE_URL_PLACEHOLDER/concepts/service_discovery) providing information about the HDFS cluster. - pub config_map: String, + pub config_map: ConfigMapName, } diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 999d04910..cf7be6480 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -37,7 +37,7 @@ use stackable_operator::{ role_group_utils::ResourceNames, role_utils::JavaCommonConfig, types::{ - kubernetes::NamespaceName, + kubernetes::{ConfigMapName, ListenerClassName, NamespaceName, SecretClassName}, operator::{ClusterName, RoleGroupName, RoleName}, }, }, @@ -174,7 +174,7 @@ pub mod versioned { /// This field controls which [ListenerClass](DOCS_BASE_URL_PLACEHOLDER/listener-operator/listenerclass.html) is used to expose the coordinator. #[serde(default = "coordinator_default_listener_class")] - pub listener_class: String, + pub listener_class: ListenerClassName, } #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] @@ -292,7 +292,7 @@ pub mod versioned { /// Follow the [logging tutorial](DOCS_BASE_URL_PLACEHOLDER/tutorials/logging-vector-aggregator) /// to learn how to configure log aggregation with Vector. #[serde(skip_serializing_if = "Option::is_none")] - pub vector_aggregator_config_map_name: Option, + pub vector_aggregator_config_map_name: Option, } #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] @@ -328,7 +328,7 @@ pub mod versioned { default = "tls_secret_class_default", skip_serializing_if = "Option::is_none" )] - pub server_secret_class: Option, + pub server_secret_class: Option, /// Only affects internal communication. Use mutual verification between Trino nodes /// This setting controls: /// - Which cert the servers should use to authenticate themselves against other servers @@ -337,7 +337,7 @@ pub mod versioned { default = "tls_secret_class_default", skip_serializing_if = "Option::is_none" )] - pub internal_secret_class: Option, + pub internal_secret_class: Option, } #[derive(Clone, Debug, Default, JsonSchema, PartialEq, Fragment)] @@ -379,8 +379,9 @@ impl Default for v1alpha1::TrinoCoordinatorRoleConfig { } } -fn coordinator_default_listener_class() -> String { - "cluster-internal".to_string() +fn coordinator_default_listener_class() -> ListenerClassName { + ListenerClassName::from_str("cluster-internal") + .expect("the default listener class name must be valid") } impl Default for v1alpha1::TrinoTls { @@ -392,8 +393,11 @@ impl Default for v1alpha1::TrinoTls { } } -fn tls_secret_class_default() -> Option { - Some(TLS_DEFAULT_SECRET_CLASS.to_string()) +fn tls_secret_class_default() -> Option { + Some( + SecretClassName::from_str(TLS_DEFAULT_SECRET_CLASS) + .expect("the default TLS SecretClass name must be valid"), + ) } #[derive( @@ -419,7 +423,7 @@ pub enum TrinoRole { } impl TrinoRole { - pub fn listener_class_name(&self, trino: &v1alpha1::TrinoCluster) -> Option { + pub fn listener_class_name(&self, trino: &v1alpha1::TrinoCluster) -> Option { match self { Self::Coordinator => trino .spec @@ -606,7 +610,11 @@ impl v1alpha1::TrinoCluster { /// Return user provided server TLS settings pub fn get_server_tls(&self) -> Option<&str> { let spec: &v1alpha1::TrinoClusterSpec = &self.spec; - spec.cluster_config.tls.server_secret_class.as_deref() + spec.cluster_config + .tls + .server_secret_class + .as_ref() + .map(|secret_class| secret_class.as_ref()) } /// Return if client TLS should be set depending on settings for authentication and client TLS. @@ -617,7 +625,11 @@ impl v1alpha1::TrinoCluster { /// Return user provided internal TLS settings. pub fn get_internal_tls(&self) -> Option<&str> { let spec: &v1alpha1::TrinoClusterSpec = &self.spec; - spec.cluster_config.tls.internal_secret_class.as_deref() + spec.cluster_config + .tls + .internal_secret_class + .as_ref() + .map(|secret_class| secret_class.as_ref()) } } diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index a41864067..25f03dd87 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -150,11 +150,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to configure listener"))] - ListenerConfiguration { - source: build::resource::listener::Error, - }, - #[snafu(display("failed to create internal secret"))] CreateInternalSecret { source: random_secret_creation::Error, @@ -369,8 +364,7 @@ pub async fn reconcile_trino( validated_cluster.recommended_labels(trino_role, "none"), listener_class.to_string(), listener_group_name, - ) - .context(ListenerConfigurationSnafu)?; + ); cluster_resources .add(client, role_group_listener) From 30322340a61e8078d9e541c803ffbb50db6d4c2d Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 20:39:08 +0200 Subject: [PATCH 41/65] refactor: remove redudant paramters from build_statefulset --- .../controller/build/resource/statefulset.rs | 29 +++++++++---------- rust/operator-binary/src/trino_controller.rs | 11 +------ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index d18db2e68..4d46ea997 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -14,10 +14,7 @@ use stackable_operator::{ volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, }, }, - commons::{ - product_image_selection::ResolvedProductImage, - secret_class::SecretClassVolumeProvisionParts, - }, + commons::secret_class::SecretClassVolumeProvisionParts, constants::RESTART_CONTROLLER_ENABLED_LABEL, k8s_openapi::{ DeepMerge, @@ -44,13 +41,12 @@ use stackable_operator::{ }; use crate::{ - authentication::TrinoAuthenticationConfig, authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, catalog::config::CatalogConfig, command, config::{client_protocol, fault_tolerant_execution}, controller::{ - ValidatedCluster, ValidatedTrinoConfig, build, + TrinoRoleGroupConfig, ValidatedCluster, build, build::resource::listener::{ LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, group_listener_name, secret_volume_listener_scope, @@ -143,22 +139,25 @@ type Result = std::result::Result; /// The [`Pod`](`stackable_operator::k8s_openapi::api::core::v1::Pod`)s are accessible through the /// corresponding [`stackable_operator::k8s_openapi::api::core::v1::Service`] (from /// [`build_rolegroup_headless_service`](super::service::build_rolegroup_headless_service)). -#[allow(clippy::too_many_arguments)] pub fn build_rolegroup_statefulset( trino: &v1alpha1::TrinoCluster, cluster: &ValidatedCluster, trino_role: &TrinoRole, - resolved_product_image: &ResolvedProductImage, role_group_name: &str, - env_overrides: &EnvVarSet, - merged_config: &ValidatedTrinoConfig, - trino_authentication_config: &TrinoAuthenticationConfig, - catalogs: &[CatalogConfig], + role_group_config: &TrinoRoleGroupConfig, sa_name: &str, - resolved_fte_config: &Option, - resolved_spooling_config: &Option, - trino_opa_config: &Option, ) -> Result { + // Everything below is derived from the validated cluster and the validated role-group config, + // so the caller only needs to pass those (plus the applied ServiceAccount name). + let resolved_product_image = &cluster.image; + let trino_authentication_config = &cluster.cluster_config.authentication; + let catalogs = cluster.cluster_config.catalogs.as_slice(); + let resolved_fte_config = &cluster.cluster_config.fault_tolerant_execution; + let resolved_spooling_config = &cluster.cluster_config.client_protocol; + let trino_opa_config = &cluster.cluster_config.authorization; + let env_overrides = &role_group_config.env_overrides; + let merged_config = &role_group_config.config; + let role = trino .role(trino_role) .context(InternalOperatorFailureSnafu)?; diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 25f03dd87..f90a7391c 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -249,8 +249,6 @@ pub async fn reconcile_trino( for (trino_role, role_group_configs) in &validated_cluster.role_group_configs { for (role_group_name, rg) in role_group_configs { - let merged_config = &rg.config; - let role_group_service_recommended_labels = validated_cluster.recommended_labels(trino_role, role_group_name); @@ -300,16 +298,9 @@ pub async fn reconcile_trino( trino, &validated_cluster, trino_role, - &validated_cluster.image, role_group_name, - &rg.env_overrides, - merged_config, - &validated_cluster.cluster_config.authentication, - &validated_cluster.cluster_config.catalogs, + rg, &rbac_sa.name_any(), - &validated_cluster.cluster_config.fault_tolerant_execution, - &validated_cluster.cluster_config.client_protocol, - &validated_cluster.cluster_config.authorization, ) .with_context(|_| BuildRoleGroupStatefulSetSnafu { rolegroup: role_group_name.clone(), From 4e8723c4d3ef617d495a60c1418f88c26a702bb6 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Mon, 15 Jun 2026 21:08:10 +0200 Subject: [PATCH 42/65] refactor: cleanup build_statefulset and tls_volume parameters --- rust/operator-binary/src/command.rs | 12 +- .../src/controller/build/resource/listener.rs | 10 +- .../controller/build/resource/statefulset.rs | 57 ++++----- rust/operator-binary/src/controller/mod.rs | 18 +++ .../src/controller/validate.rs | 5 +- rust/operator-binary/src/crd/mod.rs | 114 ++++++++---------- rust/operator-binary/src/trino_controller.rs | 21 ++-- 7 files changed, 113 insertions(+), 124 deletions(-) diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/command.rs index 858faff8d..7c370c3fd 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/command.rs @@ -10,12 +10,12 @@ use crate::{ authentication::TrinoAuthenticationConfig, catalog::config::CatalogConfig, config::{client_protocol, fault_tolerant_execution}, - controller::ValidatedTrinoConfig, + controller::{ValidatedCluster, ValidatedTrinoConfig}, crd::{ CONFIG_DIR_NAME, Container, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - TrinoRole, v1alpha1, + TrinoRole, }, trino_controller::{STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR}, }; @@ -27,7 +27,7 @@ const EXCHANGE_MANAGER_PROPERTIES: &str = "exchange-manager.properties"; const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; pub fn container_prepare_args( - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, catalogs: &[CatalogConfig], merged_config: &ValidatedTrinoConfig, resolved_fte_config: &Option, @@ -54,15 +54,15 @@ pub fn container_prepare_args( // S3, LDAP, OIDC, FTE or whatnot. args.push(format!("cert-tools generate-pkcs12-truststore --pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem --out {STACKABLE_CLIENT_TLS_DIR}/truststore.p12 --out-password {STACKABLE_TLS_STORE_PASSWORD}")); - if trino.tls_enabled() { + if cluster.tls_enabled() { args.push(format!("cp {STACKABLE_MOUNT_SERVER_TLS_DIR}/truststore.p12 {STACKABLE_SERVER_TLS_DIR}/truststore.p12")); args.push(format!("cp {STACKABLE_MOUNT_SERVER_TLS_DIR}/keystore.p12 {STACKABLE_SERVER_TLS_DIR}/keystore.p12")); } - if trino.get_internal_tls().is_some() { + if cluster.get_internal_tls().is_some() { args.push(format!("cp {STACKABLE_MOUNT_INTERNAL_TLS_DIR}/truststore.p12 {STACKABLE_INTERNAL_TLS_DIR}/truststore.p12")); args.push(format!("cp {STACKABLE_MOUNT_INTERNAL_TLS_DIR}/keystore.p12 {STACKABLE_INTERNAL_TLS_DIR}/keystore.p12")); - if trino.tls_enabled() { + if cluster.tls_enabled() { args.push(format!("cert-tools generate-pkcs12-truststore --pkcs12 {STACKABLE_MOUNT_SERVER_TLS_DIR}/truststore.p12:{STACKABLE_TLS_STORE_PASSWORD} --pkcs12 {STACKABLE_INTERNAL_TLS_DIR}/truststore.p12:{STACKABLE_TLS_STORE_PASSWORD} --out {STACKABLE_INTERNAL_TLS_DIR}/truststore.p12 --out-password {STACKABLE_TLS_STORE_PASSWORD}")); } } diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 7f63d4646..f01d7aa2f 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -6,15 +6,11 @@ use stackable_operator::{ }, crd::listener::v1alpha1::{Listener, ListenerPort, ListenerSpec}, k8s_openapi::api::core::v1::PersistentVolumeClaim, - kube::ResourceExt, kvp::Labels, v2::builder::meta::ownerreference_from_resource, }; -use crate::{ - controller::ValidatedCluster, - crd::{TrinoRole, v1alpha1}, -}; +use crate::{controller::ValidatedCluster, crd::TrinoRole}; pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; @@ -64,11 +60,11 @@ pub fn build_group_listener_pvc( /// The name of the group-listener provided for a specific role-group. /// Coordinator(s) will use this group listener so that only one load balancer /// is needed (per role group). -pub fn group_listener_name(trino: &v1alpha1::TrinoCluster, role: &TrinoRole) -> Option { +pub fn group_listener_name(cluster: &ValidatedCluster, role: &TrinoRole) -> Option { match role { TrinoRole::Coordinator => Some(format!( "{cluster_name}-{role}", - cluster_name = trino.name_any() + cluster_name = cluster.name )), TrinoRole::Worker => None, } diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 4d46ea997..f4d1757bb 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -41,10 +41,8 @@ use stackable_operator::{ }; use crate::{ - authorization::opa::{OPA_TLS_VOLUME_NAME, TrinoOpaConfig}, - catalog::config::CatalogConfig, + authorization::opa::OPA_TLS_VOLUME_NAME, command, - config::{client_protocol, fault_tolerant_execution}, controller::{ TrinoRoleGroupConfig, ValidatedCluster, build, build::resource::listener::{ @@ -58,7 +56,7 @@ use crate::{ METRICS_PORT_NAME, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, STACKABLE_MOUNT_SERVER_TLS_DIR, STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD, - TrinoRole, v1alpha1, + TrinoRole, }, trino_controller::{ MAX_PREPARE_LOG_FILE_SIZE, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, @@ -77,9 +75,6 @@ pub enum Error { #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, - #[snafu(display("internal operator failure: {source}"))] - InternalOperatorFailure { source: crate::crd::Error }, - #[snafu(display("illegal container name: [{container_name}]"))] IllegalContainerName { source: stackable_operator::builder::pod::container::Error, @@ -140,7 +135,6 @@ type Result = std::result::Result; /// corresponding [`stackable_operator::k8s_openapi::api::core::v1::Service`] (from /// [`build_rolegroup_headless_service`](super::service::build_rolegroup_headless_service)). pub fn build_rolegroup_statefulset( - trino: &v1alpha1::TrinoCluster, cluster: &ValidatedCluster, trino_role: &TrinoRole, role_group_name: &str, @@ -158,13 +152,6 @@ pub fn build_rolegroup_statefulset( let env_overrides = &role_group_config.env_overrides; let merged_config = &role_group_config.config; - let role = trino - .role(trino_role) - .context(InternalOperatorFailureSnafu)?; - let rolegroup = trino - .rolegroup(trino_role, role_group_name) - .context(InternalOperatorFailureSnafu)?; - let resource_names = cluster.resource_names(trino_role, role_group_name); let config_map_name = resource_names.role_group_config_map().to_string(); @@ -187,14 +174,14 @@ pub fn build_rolegroup_statefulset( // additional authentication env vars let mut env = trino_authentication_config.env_vars(trino_role, &Container::Trino); - let internal_secret_name = shared_internal_secret_name(trino); + let internal_secret_name = shared_internal_secret_name(&cluster.name); env.push(env_var_from_secret( &internal_secret_name, None, ENV_INTERNAL_SECRET, )); - let spooling_secret_name = shared_spooling_secret_name(trino); + let spooling_secret_name = shared_spooling_secret_name(&cluster.name); env.push(env_var_from_secret( &spooling_secret_name, None, @@ -244,16 +231,12 @@ pub fn build_rolegroup_statefulset( // add volume mounts depending on the client tls, internal tls, catalogs and authentication tls_volume_mounts( - trino, + cluster, trino_role, &mut pod_builder, &mut cb_prepare, &mut cb_trino, - catalogs, &requested_secret_lifetime, - resolved_fte_config, - resolved_spooling_config, - trino_opa_config, )?; let mut prepare_args = vec![]; @@ -268,7 +251,7 @@ pub fn build_rolegroup_statefulset( } prepare_args.extend(command::container_prepare_args( - trino, + cluster, catalogs, merged_config, resolved_fte_config, @@ -317,7 +300,7 @@ pub fn build_rolegroup_statefulset( let mut persistent_volume_claims = vec![]; // Add listener - if let Some(group_listener_name) = group_listener_name(trino, trino_role) { + if let Some(group_listener_name) = group_listener_name(cluster, trino_role) { cb_trino .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) .context(AddVolumeMountSnafu)?; @@ -442,8 +425,10 @@ pub fn build_rolegroup_statefulset( .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); let mut pod_template = pod_builder.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - pod_template.merge_from(rolegroup.config.pod_overrides.clone()); + // `pod_overrides` already carries the merged role + role-group overrides (see + // `with_validated_config`), so a single merge here is equivalent to the previous + // role-then-role-group sequence. + pod_template.merge_from(role_group_config.pod_overrides.clone()); let annotations = restarter_ignore_secret_annotations( trino_authentication_config @@ -454,7 +439,7 @@ pub fn build_rolegroup_statefulset( Ok(StatefulSet { metadata: ObjectMetaBuilder::new() - .name_and_namespace(trino) + .name_and_namespace(cluster) .name(resource_names.stateful_set_name().to_string()) .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) .with_labels(cluster.recommended_labels(trino_role, role_group_name)) @@ -463,7 +448,7 @@ pub fn build_rolegroup_statefulset( .build(), spec: Some(StatefulSetSpec { pod_management_policy: Some("Parallel".to_string()), - replicas: rolegroup.replicas.map(i32::from), + replicas: Some(i32::from(role_group_config.replicas)), selector: LabelSelector { match_labels: Some( cluster @@ -635,20 +620,20 @@ fn create_tls_volume( .build()) } -#[allow(clippy::too_many_arguments)] fn tls_volume_mounts( - trino: &v1alpha1::TrinoCluster, + cluster: &ValidatedCluster, trino_role: &TrinoRole, pod_builder: &mut PodBuilder, cb_prepare: &mut ContainerBuilder, cb_trino: &mut ContainerBuilder, - catalogs: &[CatalogConfig], requested_secret_lifetime: &Duration, - resolved_fte_config: &Option, - resolved_spooling_config: &Option, - trino_opa_config: &Option, ) -> Result<()> { - if let Some(server_tls) = trino.get_server_tls() { + let catalogs = cluster.cluster_config.catalogs.as_slice(); + let resolved_fte_config = &cluster.cluster_config.fault_tolerant_execution; + let resolved_spooling_config = &cluster.cluster_config.client_protocol; + let trino_opa_config = &cluster.cluster_config.authorization; + + if let Some(server_tls) = cluster.get_server_tls() { cb_prepare .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) .context(AddVolumeMountSnafu)?; @@ -686,7 +671,7 @@ fn tls_volume_mounts( .add_empty_dir_volume("client-tls", None) .context(AddVolumeSnafu)?; - if let Some(internal_tls) = trino.get_internal_tls() { + if let Some(internal_tls) = cluster.get_internal_tls() { cb_prepare .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) .context(AddVolumeMountSnafu)?; diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index e008f8316..5fa5cf7c1 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -149,6 +149,24 @@ impl ValidatedCluster { self.cluster_config.tls.server.is_some() } + /// The user-provided server TLS SecretClass, if any. + /// + /// Mirrors [`v1alpha1::TrinoCluster::get_server_tls`] but reads from the validated config so + /// build steps don't need the raw cluster. + pub fn get_server_tls(&self) -> Option<&str> { + self.cluster_config.tls.server.as_deref() + } + + /// The user-provided internal TLS SecretClass, if any. + pub fn get_internal_tls(&self) -> Option<&str> { + self.cluster_config.tls.internal.as_deref() + } + + /// Whether client TLS should be set, depending on authentication and server TLS settings. + pub fn tls_enabled(&self) -> bool { + self.cluster_config.authentication_enabled || self.server_tls_enabled() + } + /// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. /// /// Replaces `v1alpha1::TrinoCluster::exposed_port`, derived here from the validated TLS config diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index d66fa13f7..1108d9186 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -241,10 +241,11 @@ pub fn validate( role_group_configs.insert(trino_role, groups); } + let tls = &trino.spec.cluster_config.tls; let cluster_config = ValidatedClusterConfig { tls: ValidatedTls { - server: trino.get_server_tls().map(String::from), - internal: trino.get_internal_tls().map(String::from), + server: tls.server_secret_class.as_ref().map(ToString::to_string), + internal: tls.internal_secret_class.as_ref().map(ToString::to_string), }, authentication, authentication_enabled: trino.authentication_enabled(), diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index cf7be6480..c73f5d3b5 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -61,9 +61,6 @@ pub type TrinoRoleType = Role< JavaCommonConfig, >; -pub type TrinoRoleGroupType = - RoleGroup; - pub const APP_NAME: &str = "trino"; // ports pub const HTTP_PORT: u16 = 8080; @@ -112,9 +109,6 @@ pub(crate) fn quantity_to_trino_bytes( pub enum Error { #[snafu(display("the role {role} is not defined"))] CannotRetrieveTrinoRole { role: String }, - - #[snafu(display("the role group {role_group} is not defined"))] - CannotRetrieveTrinoRoleGroup { role_group: String }, } #[versioned( @@ -522,23 +516,6 @@ impl v1alpha1::TrinoCluster { }) } - /// Returns a reference to the role group. Raises an error if the role or role group are not defined. - pub fn rolegroup( - &self, - role: &TrinoRole, - role_group: &str, - ) -> Result { - let role_variant = self.role(role)?; - - role_variant - .role_groups - .get(role_group) - .cloned() - .with_context(|| CannotRetrieveTrinoRoleGroupSnafu { - role_group: role_group.to_owned(), - }) - } - pub fn generic_role_config(&self, role: &TrinoRole) -> Option<&GenericRoleConfig> { match role { TrinoRole::Coordinator => self @@ -606,31 +583,6 @@ impl v1alpha1::TrinoCluster { v1alpha1::TrinoAuthorization::Opa { config } => config, }) } - - /// Return user provided server TLS settings - pub fn get_server_tls(&self) -> Option<&str> { - let spec: &v1alpha1::TrinoClusterSpec = &self.spec; - spec.cluster_config - .tls - .server_secret_class - .as_ref() - .map(|secret_class| secret_class.as_ref()) - } - - /// Return if client TLS should be set depending on settings for authentication and client TLS. - pub fn tls_enabled(&self) -> bool { - self.authentication_enabled() || self.get_server_tls().is_some() - } - - /// Return user provided internal TLS settings. - pub fn get_internal_tls(&self) -> Option<&str> { - let spec: &v1alpha1::TrinoClusterSpec = &self.spec; - spec.cluster_config - .tls - .internal_secret_class - .as_ref() - .map(|secret_class| secret_class.as_ref()) - } } fn extract_role_from_coordinator_config(fragment: TrinoCoordinatorRoleType) -> TrinoRoleType { @@ -682,6 +634,29 @@ mod tests { use super::*; + /// The user-provided server TLS SecretClass as `Option<&str>` (mirrors the former + /// `TrinoCluster::get_server_tls`, kept here only for these CRD-defaulting assertions). + fn server_secret_class(trino: &v1alpha1::TrinoCluster) -> Option<&str> { + trino + .spec + .cluster_config + .tls + .server_secret_class + .as_ref() + .map(|secret_class| secret_class.as_ref()) + } + + /// The user-provided internal TLS SecretClass as `Option<&str>`. + fn internal_secret_class(trino: &v1alpha1::TrinoCluster) -> Option<&str> { + trino + .spec + .cluster_config + .tls + .internal_secret_class + .as_ref() + .map(|secret_class| secret_class.as_ref()) + } + #[test] fn test_server_tls() { let input = r#" @@ -697,8 +672,11 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_server_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); - assert_eq!(trino.get_internal_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!(server_secret_class(&trino), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!( + internal_secret_class(&trino), + Some(TLS_DEFAULT_SECRET_CLASS) + ); let input = r#" apiVersion: trino.stackable.tech/v1alpha1 @@ -715,8 +693,11 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_server_tls(), Some("simple-trino-server-tls")); - assert_eq!(trino.get_internal_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!(server_secret_class(&trino), Some("simple-trino-server-tls")); + assert_eq!( + internal_secret_class(&trino), + Some(TLS_DEFAULT_SECRET_CLASS) + ); let input = r#" apiVersion: trino.stackable.tech/v1alpha1 @@ -734,8 +715,8 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_server_tls(), None); - assert_eq!(trino.get_internal_tls(), None); + assert_eq!(server_secret_class(&trino), None); + assert_eq!(internal_secret_class(&trino), None); let input = r#" apiVersion: trino.stackable.tech/v1alpha1 @@ -752,8 +733,11 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_server_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); - assert_eq!(trino.get_internal_tls(), Some("simple-trino-internal-tls")); + assert_eq!(server_secret_class(&trino), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!( + internal_secret_class(&trino), + Some("simple-trino-internal-tls") + ); } #[test] @@ -771,8 +755,11 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_internal_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); - assert_eq!(trino.get_server_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!( + internal_secret_class(&trino), + Some(TLS_DEFAULT_SECRET_CLASS) + ); + assert_eq!(server_secret_class(&trino), Some(TLS_DEFAULT_SECRET_CLASS)); let input = r#" apiVersion: trino.stackable.tech/v1alpha1 @@ -789,8 +776,11 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_internal_tls(), Some("simple-trino-internal-tls")); - assert_eq!(trino.get_server_tls(), Some(TLS_DEFAULT_SECRET_CLASS)); + assert_eq!( + internal_secret_class(&trino), + Some("simple-trino-internal-tls") + ); + assert_eq!(server_secret_class(&trino), Some(TLS_DEFAULT_SECRET_CLASS)); let input = r#" apiVersion: trino.stackable.tech/v1alpha1 @@ -808,8 +798,8 @@ mod tests { "#; let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(input).expect("illegal test input"); - assert_eq!(trino.get_internal_tls(), None); - assert_eq!(trino.get_server_tls(), Some("simple-trino-server-tls")); + assert_eq!(internal_secret_class(&trino), None); + assert_eq!(server_secret_class(&trino), Some("simple-trino-server-tls")); } impl RoundtripTestData for v1alpha1::TrinoClusterSpec { diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index f90a7391c..3d4733476 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -20,7 +20,7 @@ use stackable_operator::{ compute_conditions, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, - v2::cluster_resources::cluster_resources_new, + v2::{cluster_resources::cluster_resources_new, types::operator::ClusterName}, }; use strum::{EnumDiscriminants, IntoStaticStr}; @@ -224,10 +224,10 @@ pub async fn reconcile_trino( .context(ApplyRoleBindingSnafu)?; random_secret_creation::create_random_secret_if_not_exists( - &shared_internal_secret_name(trino), + &shared_internal_secret_name(&validated_cluster.name), ENV_INTERNAL_SECRET, 512, - trino, + &validated_cluster, client, ) .await @@ -236,10 +236,10 @@ pub async fn reconcile_trino( // This secret is created even if spooling is not configured. // Trino currently requires the secret to be exactly 256 bits long. random_secret_creation::create_random_secret_if_not_exists( - &shared_spooling_secret_name(trino), + &shared_spooling_secret_name(&validated_cluster.name), ENV_SPOOLING_SECRET, 32, - trino, + &validated_cluster, client, ) .await @@ -295,7 +295,6 @@ pub async fn reconcile_trino( })?; let rg_stateful_set = build::resource::statefulset::build_rolegroup_statefulset( - trino, &validated_cluster, trino_role, role_group_name, @@ -348,7 +347,7 @@ pub async fn reconcile_trino( } if let Some(listener_class) = trino_role.listener_class_name(trino) - && let Some(listener_group_name) = group_listener_name(trino, trino_role) + && let Some(listener_group_name) = group_listener_name(&validated_cluster, trino_role) { let role_group_listener = build_group_listener( &validated_cluster, @@ -409,12 +408,12 @@ pub fn error_policy( } } -pub(crate) fn shared_internal_secret_name(trino: &v1alpha1::TrinoCluster) -> String { - format!("{}-internal-secret", trino.name_any()) +pub(crate) fn shared_internal_secret_name(cluster_name: &ClusterName) -> String { + format!("{cluster_name}-internal-secret") } -pub(crate) fn shared_spooling_secret_name(trino: &v1alpha1::TrinoCluster) -> String { - format!("{}-spooling-secret", trino.name_any()) +pub(crate) fn shared_spooling_secret_name(cluster_name: &ClusterName) -> String { + format!("{cluster_name}-spooling-secret") } #[cfg(test)] From 6eb76b18c641b5e01b5820ababa18071d8449dab Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 16 Jun 2026 19:58:21 +0200 Subject: [PATCH 43/65] refactor: introduce ValidatedRoleConfig --- rust/operator-binary/src/controller/mod.rs | 21 ++++++++++++++++++- .../src/controller/validate.rs | 21 ++++++++++++++++++- rust/operator-binary/src/trino_controller.rs | 14 ++++++------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 5fa5cf7c1..e18bd934f 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -17,7 +17,7 @@ use stackable_operator::{ kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, types::{ - kubernetes::{NamespaceName, Uid}, + kubernetes::{ListenerClassName, NamespaceName, Uid}, operator::{ ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleGroupName as RoleGroupNameV2, RoleName, @@ -98,6 +98,17 @@ impl ValidatedTrinoConfig { } } +/// Per-role configuration extracted during validation. +/// +/// Lets the reconciler and build steps consume this controller-owned type instead of re-reading +/// the raw [`v1alpha1::TrinoCluster`] (mirroring the hive-operator's `ValidatedRoleConfig`). +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: stackable_operator::commons::pdb::PdbConfig, + /// The listener class for the role's group listener, if it has one (coordinator only). + pub listener_class: Option, +} + /// The validated TrinoCluster. The output of the validate step. #[derive(Clone, Debug)] pub struct ValidatedCluster { @@ -113,6 +124,7 @@ pub struct ValidatedCluster { pub image: ResolvedProductImage, pub product_version: u16, pub cluster_config: ValidatedClusterConfig, + pub role_configs: BTreeMap, pub role_group_configs: BTreeMap>, } @@ -125,6 +137,7 @@ impl ValidatedCluster { image: ResolvedProductImage, product_version: u16, cluster_config: ValidatedClusterConfig, + role_configs: BTreeMap, role_group_configs: BTreeMap>, ) -> Self { Self { @@ -140,10 +153,16 @@ impl ValidatedCluster { image, product_version, cluster_config, + role_configs, role_group_configs, } } + /// The validated per-role config for `role`, if the role is defined. + pub(crate) fn role_config(&self, role: &TrinoRole) -> Option<&ValidatedRoleConfig> { + self.role_configs.get(role) + } + /// Whether the (client-facing) server TLS is enabled. pub fn server_tls_enabled(&self) -> bool { self.cluster_config.tls.server.is_some() diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 1108d9186..7b46687d0 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -26,7 +26,10 @@ use stackable_operator::{ }; use strum::{EnumDiscriminants, IntoEnumIterator, IntoStaticStr}; -use super::{ValidatedCluster, ValidatedClusterConfig, ValidatedTls, ValidatedTrinoConfig}; +use super::{ + ValidatedCluster, ValidatedClusterConfig, ValidatedRoleConfig, ValidatedTls, + ValidatedTrinoConfig, +}; use crate::{ authentication::{self, TrinoAuthenticationConfig, TrinoAuthenticationTypes}, controller::dereference::DereferencedObjects, @@ -205,6 +208,7 @@ pub fn validate( .vector_aggregator_config_map_name .clone(); + let mut role_configs: BTreeMap = BTreeMap::new(); let mut role_group_configs: BTreeMap> = BTreeMap::new(); for trino_role in TrinoRole::iter() { @@ -213,6 +217,20 @@ pub fn validate( .with_context(|_| MissingTrinoRoleSnafu { role: trino_role.to_string(), })?; + + // Extract the per-role PDB and (optional) listener class up-front, so the reconciler and + // build steps consume the validated config instead of re-reading the raw cluster. + role_configs.insert( + trino_role.clone(), + ValidatedRoleConfig { + pdb: trino + .generic_role_config(&trino_role) + .map(|rc| rc.pod_disruption_budget.clone()) + .unwrap_or_default(), + listener_class: trino_role.listener_class_name(trino), + }, + ); + let default_config = v1alpha1::TrinoConfig::default_config( &trino.name_any(), &trino_role, @@ -266,6 +284,7 @@ pub fn validate( image, product_version, cluster_config, + role_configs, role_group_configs, )) } diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 3d4733476..1694a42bb 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -14,7 +14,6 @@ use stackable_operator::{ }, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, - role_utils::GenericRoleConfig, shared::time::Duration, status::condition::{ compute_conditions, operations::ClusterOperationsConditionBuilder, @@ -346,7 +345,11 @@ pub async fn reconcile_trino( ); } - if let Some(listener_class) = trino_role.listener_class_name(trino) + let Some(role_config) = validated_cluster.role_config(trino_role) else { + continue; + }; + + if let Some(listener_class) = &role_config.listener_class && let Some(listener_group_name) = group_listener_name(&validated_cluster, trino_role) { let role_group_listener = build_group_listener( @@ -362,12 +365,7 @@ pub async fn reconcile_trino( .context(ApplyGroupListenerSnafu)?; } - let role_config = trino.generic_role_config(trino_role); - if let Some(GenericRoleConfig { - pod_disruption_budget: pdb, - }) = role_config - && let Some(pdb) = build_pdb(pdb, &validated_cluster, trino_role) - { + if let Some(pdb) = build_pdb(&role_config.pdb, &validated_cluster, trino_role) { cluster_resources .add(client, pdb) .await From 39fcb0d7cdf5f47744013c912d52dfd47cd72819 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 16 Jun 2026 20:35:21 +0200 Subject: [PATCH 44/65] refactor: add ValidatedCluster::object_meta helper for child resource metadata --- .../controller/build/resource/config_map.rs | 25 ++++++------------- .../src/controller/build/resource/listener.rs | 13 +++------- .../src/controller/build/resource/service.rs | 18 +++++-------- .../controller/build/resource/statefulset.rs | 15 +++++------ rust/operator-binary/src/controller/mod.rs | 21 ++++++++++++++++ 5 files changed, 43 insertions(+), 49 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index f765f052c..75070ae4f 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -4,14 +4,9 @@ use std::collections::BTreeMap; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, - k8s_openapi::api::core::v1::ConfigMap, - kvp::Labels, - product_logging::framework::VECTOR_CONFIG_FILE, - utils::cluster_info::KubernetesClusterInfo, - v2::{ - builder::meta::ownerreference_from_resource, config_file_writer::to_java_properties_string, - }, + builder::configmap::ConfigMapBuilder, k8s_openapi::api::core::v1::ConfigMap, kvp::Labels, + product_logging::framework::VECTOR_CONFIG_FILE, utils::cluster_info::KubernetesClusterInfo, + v2::config_file_writer::to_java_properties_string, }; use crate::{ @@ -188,11 +183,8 @@ pub fn build_rolegroup_config_map( ConfigMapBuilder::new() .metadata( - ObjectMetaBuilder::new() - .name(&config_map_name) - .namespace(cluster.namespace.to_string()) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(recommended_labels.clone()) + cluster + .object_meta(&config_map_name, recommended_labels.clone()) .build(), ) .data(data) @@ -218,11 +210,8 @@ pub fn build_rolegroup_catalog_config_map( ); ConfigMapBuilder::new() .metadata( - ObjectMetaBuilder::new() - .name(&catalog_config_map_name) - .namespace(cluster.namespace.to_string()) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(recommended_labels.clone()) + cluster + .object_meta(&catalog_config_map_name, recommended_labels.clone()) .build(), ) .data( diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index f01d7aa2f..8785f6cc3 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -1,13 +1,9 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{ - meta::ObjectMetaBuilder, - pod::volume::{ListenerOperatorVolumeSourceBuilder, ListenerReference}, - }, + builder::pod::volume::{ListenerOperatorVolumeSourceBuilder, ListenerReference}, crd::listener::v1alpha1::{Listener, ListenerPort, ListenerSpec}, k8s_openapi::api::core::v1::PersistentVolumeClaim, kvp::Labels, - v2::builder::meta::ownerreference_from_resource, }; use crate::{controller::ValidatedCluster, crd::TrinoRole}; @@ -30,11 +26,8 @@ pub fn build_group_listener( listener_group_name: String, ) -> Listener { Listener { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(cluster) - .name(listener_group_name) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(recommended_labels) + metadata: cluster + .object_meta(listener_group_name, recommended_labels) .build(), spec: ListenerSpec { class_name: Some(listener_class), diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 9ad3806db..7f831860d 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -1,10 +1,8 @@ use std::collections::BTreeMap; use stackable_operator::{ - builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, kvp::{Annotations, Labels}, - v2::builder::meta::ownerreference_from_resource, }; use crate::{ @@ -23,16 +21,14 @@ pub fn build_rolegroup_headless_service( ports: Vec, ) -> Service { Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(cluster) - .name( + metadata: cluster + .object_meta( cluster .resource_names(role, role_group_name) .headless_service_name() .to_string(), + recommended_labels.clone(), ) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(recommended_labels.clone()) .build(), spec: Some(ServiceSpec { // Internal communication does not need to be exposed @@ -56,16 +52,14 @@ pub fn build_rolegroup_metrics_service( selector: BTreeMap, ) -> Service { Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(cluster) - .name( + metadata: cluster + .object_meta( cluster .resource_names(role, role_group_name) .metrics_service_name() .to_string(), + recommended_labels.clone(), ) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(recommended_labels.clone()) .with_labels(prometheus_labels()) .with_annotations(prometheus_annotations()) .build(), diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index f4d1757bb..ee73a9cfc 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -31,10 +31,7 @@ use stackable_operator::{ product_logging, shared::time::Duration, v2::{ - builder::{ - meta::ownerreference_from_resource, pod::container::EnvVarSet, - statefulset::restarter_ignore_secret_annotations, - }, + builder::{pod::container::EnvVarSet, statefulset::restarter_ignore_secret_annotations}, product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, types::kubernetes::{ContainerName, VolumeName}, }, @@ -438,11 +435,11 @@ pub fn build_rolegroup_statefulset( ); Ok(StatefulSet { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(cluster) - .name(resource_names.stateful_set_name().to_string()) - .ownerreference(ownerreference_from_resource(cluster, None, Some(true))) - .with_labels(cluster.recommended_labels(trino_role, role_group_name)) + metadata: cluster + .object_meta( + resource_names.stateful_set_name().to_string(), + cluster.recommended_labels(trino_role, role_group_name), + ) .with_label(RESTART_CONTROLLER_ENABLED_LABEL.to_owned()) .with_annotations(annotations) .build(), diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index e18bd934f..5729fd598 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -4,6 +4,7 @@ use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, commons::{ affinity::StackableAffinity, product_image_selection::ResolvedProductImage, @@ -14,6 +15,7 @@ use stackable_operator::{ shared::time::Duration, v2::{ HasName, HasUid, NameIsValidLabelValue, + builder::meta::ownerreference_from_resource, kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, types::{ @@ -216,6 +218,25 @@ impl ValidatedCluster { } } + /// Returns an [`ObjectMetaBuilder`] pre-filled with this cluster's namespace, an owner + /// reference back to the cluster, the resource `name` and the given `recommended_labels`. + /// + /// Consolidates the metadata chain repeated by the child-resource builders. Call sites that + /// need extra labels/annotations chain them onto the returned builder. + pub(crate) fn object_meta( + &self, + name: impl Into, + recommended_labels: Labels, + ) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .name_and_namespace(self) + .name(name) + .ownerreference(ownerreference_from_resource(self, None, Some(true))) + .with_labels(recommended_labels); + builder + } + /// A [`TrinoRole`] as a type-safe [`RoleName`]. fn role_name(role: &TrinoRole) -> RoleName { RoleName::from_str(&role.to_string()).expect("a TrinoRole is a valid RFC 1123 role name") From d70d8f2f7aedebf4b1a71c12e05c405d738b2348 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 16 Jun 2026 20:48:56 +0200 Subject: [PATCH 45/65] fix: move hardcoded values to constants --- .../src/authentication/password/file.rs | 5 +- .../controller/build/resource/statefulset.rs | 94 ++++++++++++------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/rust/operator-binary/src/authentication/password/file.rs b/rust/operator-binary/src/authentication/password/file.rs index cf2a250f4..7190c297c 100644 --- a/rust/operator-binary/src/authentication/password/file.rs +++ b/rust/operator-binary/src/authentication/password/file.rs @@ -18,7 +18,8 @@ use stackable_operator::{ }; use crate::{ - authentication::password::PASSWORD_AUTHENTICATOR_NAME, trino_controller::STACKABLE_LOG_DIR, + authentication::password::PASSWORD_AUTHENTICATOR_NAME, + controller::build::resource::statefulset::LOG_VOLUME_NAME, trino_controller::STACKABLE_LOG_DIR, }; // mounts @@ -189,7 +190,7 @@ wait_for_termination $! .add_volume_mounts(volume_mounts) .context(AddVolumeMountsSnafu)? // fixed - .add_volume_mount("log", STACKABLE_LOG_DIR) + .add_volume_mount(&*LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountsSnafu)? .resources( ResourceRequirementsBuilder::new() diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index ee73a9cfc..cb4a03745 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -62,10 +62,21 @@ use crate::{ }; stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); -// The Vector agent reads its `vector.yaml` from the rolegroup ConfigMap (mounted as the "config" -// volume) and writes its state under the shared "log" volume. -stackable_operator::constant!(VECTOR_LOG_CONFIG_VOLUME_NAME: VolumeName = "config"); -stackable_operator::constant!(VECTOR_LOG_VOLUME_NAME: VolumeName = "log"); + +// Typed names for the Pod's volumes, so each volume definition and its matching volume mounts +// stay in sync. The Vector agent reads its `vector.yaml` from the rolegroup ConfigMap (mounted as +// the `config` volume) and writes its state under the shared `log` volume. +stackable_operator::constant!(CONFIG_VOLUME_NAME: VolumeName = "config"); +stackable_operator::constant!(RW_CONFIG_VOLUME_NAME: VolumeName = "rwconfig"); +stackable_operator::constant!(CATALOG_VOLUME_NAME: VolumeName = "catalog"); +stackable_operator::constant!(LOG_CONFIG_VOLUME_NAME: VolumeName = "log-config"); +// `log` is also mounted by the password-file-updater container built in the authentication module. +stackable_operator::constant!(pub LOG_VOLUME_NAME: VolumeName = "log"); +stackable_operator::constant!(SERVER_TLS_MOUNT_VOLUME_NAME: VolumeName = "server-tls-mount"); +stackable_operator::constant!(SERVER_TLS_VOLUME_NAME: VolumeName = "server-tls"); +stackable_operator::constant!(CLIENT_TLS_VOLUME_NAME: VolumeName = "client-tls"); +stackable_operator::constant!(INTERNAL_TLS_MOUNT_VOLUME_NAME: VolumeName = "internal-tls-mount"); +stackable_operator::constant!(INTERNAL_TLS_VOLUME_NAME: VolumeName = "internal-tls"); #[derive(Snafu, Debug)] pub enum Error { @@ -279,11 +290,11 @@ pub fn build_rolegroup_statefulset( "-c".to_string(), ]) .args(vec![prepare_args.join("\n")]) - .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) + .add_volume_mount(&*RW_CONFIG_VOLUME_NAME, RW_CONFIG_DIR_NAME) .context(AddVolumeMountSnafu)? - .add_volume_mount("log-config", STACKABLE_LOG_CONFIG_DIR) + .add_volume_mount(&*LOG_CONFIG_VOLUME_NAME, STACKABLE_LOG_CONFIG_DIR) .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) + .add_volume_mount(&*LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? .resources( ResourceRequirementsBuilder::new() @@ -326,13 +337,16 @@ pub fn build_rolegroup_statefulset( command::container_trino_args(trino_authentication_config, catalogs).join("\n"), ]) .add_env_vars(env) - .add_volume_mount("config", CONFIG_DIR_NAME) + .add_volume_mount(&*CONFIG_VOLUME_NAME, CONFIG_DIR_NAME) .context(AddVolumeMountSnafu)? - .add_volume_mount("rwconfig", RW_CONFIG_DIR_NAME) + .add_volume_mount(&*RW_CONFIG_VOLUME_NAME, RW_CONFIG_DIR_NAME) .context(AddVolumeMountSnafu)? - .add_volume_mount("catalog", format!("{}/catalog", CONFIG_DIR_NAME)) + .add_volume_mount( + &*CATALOG_VOLUME_NAME, + format!("{}/catalog", CONFIG_DIR_NAME), + ) .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) + .add_volume_mount(&*LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? .add_container_ports(container_ports(cluster)) .resources(merged_config.resources.clone().into()) @@ -356,7 +370,7 @@ pub fn build_rolegroup_statefulset( }; pod_builder .add_volume(Volume { - name: "log-config".to_string(), + name: LOG_CONFIG_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { name: log_config_volume_config_map, ..ConfigMapVolumeSource::default() @@ -371,8 +385,8 @@ pub fn build_rolegroup_statefulset( resolved_product_image, vector_log_config, &resource_names, - &VECTOR_LOG_CONFIG_VOLUME_NAME, - &VECTOR_LOG_VOLUME_NAME, + &CONFIG_VOLUME_NAME, + &LOG_VOLUME_NAME, EnvVarSet::new(), )); } @@ -392,7 +406,7 @@ pub fn build_rolegroup_statefulset( .affinity(&merged_config.affinity) .add_init_container(container_prepare) .add_volume(Volume { - name: "config".to_string(), + name: CONFIG_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { name: config_map_name.clone(), ..ConfigMapVolumeSource::default() @@ -400,10 +414,10 @@ pub fn build_rolegroup_statefulset( ..Volume::default() }) .context(AddVolumeSnafu)? - .add_empty_dir_volume("rwconfig", None) + .add_empty_dir_volume(&*RW_CONFIG_VOLUME_NAME, None) .context(AddVolumeSnafu)? .add_volume(Volume { - name: "catalog".to_string(), + name: CATALOG_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { name: format!("{config_map_name}-catalog"), ..ConfigMapVolumeSource::default() @@ -412,7 +426,7 @@ pub fn build_rolegroup_statefulset( }) .context(AddVolumeSnafu)? .add_empty_dir_volume( - "log", + &*LOG_VOLUME_NAME, Some(product_logging::framework::calculate_log_volume_size_limit( &[MAX_TRINO_LOG_FILES_SIZE, MAX_PREPARE_LOG_FILE_SIZE], )), @@ -588,7 +602,7 @@ fn finished_starting_probe(cluster: &ValidatedCluster) -> ExecAction { } fn create_tls_volume( - volume_name: &str, + volume_name: impl Into, tls_secret_class: &str, requested_secret_lifetime: &Duration, listener_scope: Option, @@ -632,14 +646,20 @@ fn tls_volume_mounts( if let Some(server_tls) = cluster.get_server_tls() { cb_prepare - .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) + .add_volume_mount( + &*SERVER_TLS_MOUNT_VOLUME_NAME, + STACKABLE_MOUNT_SERVER_TLS_DIR, + ) .context(AddVolumeMountSnafu)?; cb_trino - .add_volume_mount("server-tls-mount", STACKABLE_MOUNT_SERVER_TLS_DIR) + .add_volume_mount( + &*SERVER_TLS_MOUNT_VOLUME_NAME, + STACKABLE_MOUNT_SERVER_TLS_DIR, + ) .context(AddVolumeMountSnafu)?; pod_builder .add_volume(create_tls_volume( - "server-tls-mount", + &*SERVER_TLS_MOUNT_VOLUME_NAME, server_tls, requested_secret_lifetime, // add listener @@ -649,35 +669,41 @@ fn tls_volume_mounts( } cb_prepare - .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) + .add_volume_mount(&*SERVER_TLS_VOLUME_NAME, STACKABLE_SERVER_TLS_DIR) .context(AddVolumeMountSnafu)?; cb_trino - .add_volume_mount("server-tls", STACKABLE_SERVER_TLS_DIR) + .add_volume_mount(&*SERVER_TLS_VOLUME_NAME, STACKABLE_SERVER_TLS_DIR) .context(AddVolumeMountSnafu)?; pod_builder - .add_empty_dir_volume("server-tls", None) + .add_empty_dir_volume(&*SERVER_TLS_VOLUME_NAME, None) .context(AddVolumeSnafu)?; cb_prepare - .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) + .add_volume_mount(&*CLIENT_TLS_VOLUME_NAME, STACKABLE_CLIENT_TLS_DIR) .context(AddVolumeMountSnafu)?; cb_trino - .add_volume_mount("client-tls", STACKABLE_CLIENT_TLS_DIR) + .add_volume_mount(&*CLIENT_TLS_VOLUME_NAME, STACKABLE_CLIENT_TLS_DIR) .context(AddVolumeMountSnafu)?; pod_builder - .add_empty_dir_volume("client-tls", None) + .add_empty_dir_volume(&*CLIENT_TLS_VOLUME_NAME, None) .context(AddVolumeSnafu)?; if let Some(internal_tls) = cluster.get_internal_tls() { cb_prepare - .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) + .add_volume_mount( + &*INTERNAL_TLS_MOUNT_VOLUME_NAME, + STACKABLE_MOUNT_INTERNAL_TLS_DIR, + ) .context(AddVolumeMountSnafu)?; cb_trino - .add_volume_mount("internal-tls-mount", STACKABLE_MOUNT_INTERNAL_TLS_DIR) + .add_volume_mount( + &*INTERNAL_TLS_MOUNT_VOLUME_NAME, + STACKABLE_MOUNT_INTERNAL_TLS_DIR, + ) .context(AddVolumeMountSnafu)?; pod_builder .add_volume(create_tls_volume( - "internal-tls-mount", + &*INTERNAL_TLS_MOUNT_VOLUME_NAME, internal_tls, requested_secret_lifetime, None, @@ -685,13 +711,13 @@ fn tls_volume_mounts( .context(AddVolumeSnafu)?; cb_prepare - .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) + .add_volume_mount(&*INTERNAL_TLS_VOLUME_NAME, STACKABLE_INTERNAL_TLS_DIR) .context(AddVolumeMountSnafu)?; cb_trino - .add_volume_mount("internal-tls", STACKABLE_INTERNAL_TLS_DIR) + .add_volume_mount(&*INTERNAL_TLS_VOLUME_NAME, STACKABLE_INTERNAL_TLS_DIR) .context(AddVolumeMountSnafu)?; pod_builder - .add_empty_dir_volume("internal-tls", None) + .add_empty_dir_volume(&*INTERNAL_TLS_VOLUME_NAME, None) .context(AddVolumeSnafu)?; } From ca9eb49bf38aefc3cf7679035f615c2d562d5aa6 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 16 Jun 2026 20:53:13 +0200 Subject: [PATCH 46/65] chore: improve vector tests --- .../product_logging/vector-test.yaml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml index 76e8296ad..f6af98be2 100644 --- a/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml @@ -97,3 +97,63 @@ tests: } 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: trino-coordinator-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": "trino", + "container": "vector", + "level": "INFO", + "logger": "vector::internal_events::process", + "message": "Vector has started.", + "namespace": "default", + "pod": "trino-coordinator-default-0", + "role": "coordinator", + "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 From 1f10a482ad012236713c62128add5a2d2ba8cbf0 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 16 Jun 2026 20:56:21 +0200 Subject: [PATCH 47/65] fix(doc): remove stale comments --- .../src/controller/build/resource/config_map.rs | 2 +- rust/operator-binary/src/controller/build/resource/mod.rs | 3 +-- rust/operator-binary/src/controller/mod.rs | 5 ++--- rust/operator-binary/src/controller/validate.rs | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 75070ae4f..bae52b352 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -173,7 +173,7 @@ pub fn build_rolegroup_config_map( data.insert(JVM_CONFIG.to_string(), jvm_config); // 9. Vector agent config (`vector.yaml`) if the Vector agent is enabled. The file is templated - // with environment variables injected by the v2 Vector container at runtime. + // with environment variables injected by the Vector container at runtime. if rg.config.logging.enable_vector_agent { data.insert( VECTOR_CONFIG_FILE.to_string(), diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs index 600192351..0c96e5909 100644 --- a/rust/operator-binary/src/controller/build/resource/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -2,8 +2,7 @@ //! //! Each submodule builds one kind of resource (ConfigMap, Service, Listener, PDB, …) and //! *returns* it, rather than mutating shared state or applying it directly. The reconciler -//! collects the returned objects and applies them. This mirrors the hive- and -//! opensearch-operators' `controller/build/resource` layout. +//! collects the returned objects and applies them. pub mod config_map; pub mod listener; diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 5729fd598..fe154df7b 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -71,8 +71,7 @@ pub struct ValidatedClusterConfig { /// A validated, merged Trino role-group config. /// /// Holds the merged [`v1alpha1::TrinoConfig`] fields so the build steps consume this -/// controller-owned type instead of the raw CRD struct (mirroring the opensearch- and -/// hive-operators' `Validated…Config`). +/// controller-owned type instead of the raw CRD struct. #[derive(Clone, Debug)] pub struct ValidatedTrinoConfig { pub affinity: StackableAffinity, @@ -103,7 +102,7 @@ impl ValidatedTrinoConfig { /// Per-role configuration extracted during validation. /// /// Lets the reconciler and build steps consume this controller-owned type instead of re-reading -/// the raw [`v1alpha1::TrinoCluster`] (mirroring the hive-operator's `ValidatedRoleConfig`). +/// the raw [`v1alpha1::TrinoCluster`]. #[derive(Clone, Debug)] pub struct ValidatedRoleConfig { pub pdb: stackable_operator::commons::pdb::PdbConfig, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 7b46687d0..a95276ed7 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -109,9 +109,9 @@ pub type RoleGroupName = String; /// Validated logging configuration for the Trino, prepare and (optional) Vector containers. /// -/// Produced up-front by [`validate_logging`] (mirroring the opensearch- and hive-operators) 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. +/// 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 prepare_container: ValidatedContainerLogConfigChoice, From c7e621ea78dc169bffc983b9f3aa3acae4c8c706 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 17:05:42 +0200 Subject: [PATCH 48/65] chore: bump dependencies --- Cargo.lock | 104 ++++++++++++++++++------------------- Cargo.nix | 130 +++++++++++++++++++++++----------------------- crate-hashes.json | 18 +++---- 3 files changed, 126 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b559497f..b231c8c6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,7 +141,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -152,7 +152,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -360,7 +360,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -520,7 +520,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -531,7 +531,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -542,7 +542,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -566,7 +566,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -593,7 +593,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -616,7 +616,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -668,7 +668,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -732,7 +732,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -904,7 +904,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1420,7 +1420,7 @@ checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1511,7 +1511,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "darling", "regex", @@ -1611,7 +1611,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2034,7 +2034,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2064,7 +2064,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2202,7 +2202,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2313,7 +2313,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2452,7 +2452,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", "unicode-ident", ] @@ -2556,7 +2556,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2654,7 +2654,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2665,7 +2665,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2840,7 +2840,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2852,7 +2852,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2890,7 +2890,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "const-oid", "ecdsa", @@ -2914,7 +2914,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "base64", "clap", @@ -2959,18 +2959,18 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "jiff", "k8s-openapi", @@ -2987,7 +2987,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "axum", "clap", @@ -3033,7 +3033,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "kube", "schemars", @@ -3047,7 +3047,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "convert_case", "convert_case_extras", @@ -3059,13 +3059,13 @@ dependencies = [ "kube", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "arc-swap", "async-trait", @@ -3118,7 +3118,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3146,9 +3146,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -3172,7 +3172,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3201,7 +3201,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3212,7 +3212,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3282,7 +3282,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3310,7 +3310,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3512,7 +3512,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3750,7 +3750,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -3804,7 +3804,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3815,7 +3815,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3984,7 +3984,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -4005,7 +4005,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4025,7 +4025,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -4046,7 +4046,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4079,7 +4079,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.nix b/Cargo.nix index e126cd7c3..1400e6072 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -439,7 +439,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "visit-mut" ]; } ]; @@ -466,7 +466,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "clone-impls" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } @@ -1161,7 +1161,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -1592,7 +1592,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "extra-traits" ]; } ]; @@ -1623,7 +1623,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -1649,7 +1649,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "visit-mut" ]; } ]; @@ -1726,7 +1726,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -1820,7 +1820,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; buildDependencies = [ @@ -1917,7 +1917,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -2083,13 +2083,13 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -2287,7 +2287,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -2788,7 +2788,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -4547,7 +4547,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -4818,8 +4818,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "k8s_version"; authors = [ @@ -5312,7 +5312,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -6806,7 +6806,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -6875,7 +6875,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "parsing" "printing" "clone-impls" "proc-macro" "full" "visit-mut" ]; } @@ -7242,7 +7242,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -7548,7 +7548,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -8179,7 +8179,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "parsing" "extra-traits" "visit" "visit-mut" ]; } { @@ -8521,13 +8521,13 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -8811,7 +8811,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ]; } @@ -8843,7 +8843,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" ]; } @@ -9354,7 +9354,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -9390,7 +9390,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } @@ -9501,8 +9501,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_certs"; authors = [ @@ -9604,8 +9604,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_operator"; authors = [ @@ -9803,8 +9803,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9826,7 +9826,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -9838,8 +9838,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_shared"; authors = [ @@ -9919,8 +9919,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_telemetry"; authors = [ @@ -10126,8 +10126,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_versioned"; authors = [ @@ -10176,8 +10176,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10232,7 +10232,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10244,8 +10244,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a9fbbc8d1f0dc0387f26ae443a8b7d5a6e24323c"; - sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp"; }; libName = "stackable_webhook"; authors = [ @@ -10427,7 +10427,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "parsing" ]; } ]; @@ -10491,11 +10491,11 @@ rec { }; resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "full" "parsing" "printing" "proc-macro" "quote" ]; }; - "syn 2.0.117" = rec { + "syn 2.0.118" = rec { crateName = "syn"; - version = "2.0.117"; + version = "2.0.118"; edition = "2021"; - sha256 = "16cv7c0wbn8amxc54n4w15kxlx5ypdmla8s0gxr2l7bv7s0bhrg6"; + sha256 = "08hlbc32lqd5d67p26ck7chg0rkclsw9as6f96vfn4s2j1zyb6hv"; authors = [ "David Tolnay " ]; @@ -10567,7 +10567,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "derive" "parsing" "printing" "clone-impls" "visit" "extra-traits" ]; } @@ -10634,7 +10634,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10660,7 +10660,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10872,7 +10872,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "parsing" ]; } ]; @@ -11024,7 +11024,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -11811,7 +11811,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ]; } @@ -12556,7 +12556,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "visit" "visit-mut" "full" "extra-traits" ]; } { @@ -13165,7 +13165,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -13192,7 +13192,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -14067,7 +14067,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "fold" ]; } { @@ -14135,14 +14135,14 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "visit" ]; } ]; @@ -14191,7 +14191,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "fold" ]; } { @@ -14245,7 +14245,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "extra-traits" "visit" ]; } ]; @@ -14358,7 +14358,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; diff --git a/crate-hashes.json b/crate-hashes.json index bb8b51c7c..0ff88d858 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": "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//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "1j0mx81r6ki3paw40gs69p4wbnfbzw1iykz8b45mmryxg8naxihp", "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 35e66aba2db2d09166b8c8ca6578657384c8ba78 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 17:05:54 +0200 Subject: [PATCH 49/65] fix: adujust to optimal replicas --- rust/operator-binary/src/controller/build/resource/pdb.rs | 5 ++++- .../src/controller/build/resource/statefulset.rs | 4 +++- rust/operator-binary/src/controller/validate.rs | 8 ++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/pdb.rs b/rust/operator-binary/src/controller/build/resource/pdb.rs index 941a76b4a..9f81f2cc0 100644 --- a/rust/operator-binary/src/controller/build/resource/pdb.rs +++ b/rust/operator-binary/src/controller/build/resource/pdb.rs @@ -42,13 +42,16 @@ pub fn build_pdb( } /// Total number of worker replicas across all worker role groups. +/// +/// Role groups without an explicit replica count (i.e. those left to a HorizontalPodAutoscaler) +/// contribute nothing, as their size is not known at reconcile time. fn worker_count(cluster: &ValidatedCluster) -> u16 { cluster .role_group_configs .get(&TrinoRole::Worker) .into_iter() .flat_map(|groups| groups.values()) - .map(|rg| rg.replicas) + .filter_map(|rg| rg.replicas) .sum() } diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index cb4a03745..e23d8dbcb 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -459,7 +459,9 @@ pub fn build_rolegroup_statefulset( .build(), spec: Some(StatefulSetSpec { pod_management_policy: Some("Parallel".to_string()), - replicas: Some(i32::from(role_group_config.replicas)), + // Forward `None` when the user did not set `replicas`, leaving the field unset on the + // StatefulSet so a HorizontalPodAutoscaler can own the replica count. + replicas: role_group_config.replicas.map(i32::from), selector: LabelSelector { match_labels: Some( cluster diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index a95276ed7..c32945428 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -291,15 +291,11 @@ pub fn validate( /// Adapts the validated [`RoleGroup`] produced by [`with_validated_config`] into the flattened /// [`TrinoRoleGroupConfig`] consumed by the build steps. -/// -/// Upstream `with_validated_config` returns a [`RoleGroup`] with a `HashMap` of env overrides and an -/// optional replica count; this converts it to the ergonomic [`RoleGroupConfig`] with an -/// [`EnvVarSet`] and a concrete replica count (defaulting to 1). fn into_role_group_config( merged: RoleGroup, vector_aggregator_config_map_name: &Option, ) -> Result { - let replicas = merged.replicas.unwrap_or(1); + let replicas = merged.replicas; let common = merged.config; let mut env_overrides = EnvVarSet::new(); @@ -383,7 +379,7 @@ mod tests { ); assert_eq!( validated.role_group_configs[&TrinoRole::Coordinator]["default"].replicas, - 1 + Some(1) ); } } From f000b9fd6bf8981dc5229f523d08e12897320e65 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 18:31:19 +0200 Subject: [PATCH 50/65] refactor: consolidate authentication build --- rust/operator-binary/src/authentication/mod.rs | 9 +++++++++ .../controller/build/properties/config_properties.rs | 2 +- rust/operator-binary/src/controller/mod.rs | 10 ++++++++-- rust/operator-binary/src/controller/validate.rs | 3 +-- rust/operator-binary/src/crd/mod.rs | 6 ------ 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/authentication/mod.rs b/rust/operator-binary/src/authentication/mod.rs index ba67a8c64..c2e0385bd 100644 --- a/rust/operator-binary/src/authentication/mod.rs +++ b/rust/operator-binary/src/authentication/mod.rs @@ -80,6 +80,9 @@ type Result = std::result::Result; /// may be in parts reused in other operators. #[derive(Clone, Debug, Default)] pub struct TrinoAuthenticationConfig { + /// The enabled `http-server.authentication.type` values, in evaluation order. Empty when no + /// authentication is configured. + authentication_types: Vec, /// All config properties that have to be added to the `config.properties` of the given role config_properties: HashMap>, /// All extra config files required for authentication for each role. @@ -136,6 +139,7 @@ impl TrinoAuthenticationConfig { http_server_authentication_types.join(","), ); } + authentication_config.authentication_types = http_server_authentication_types; trace!( "Final Trino authentication config: {:?}", @@ -145,6 +149,11 @@ impl TrinoAuthenticationConfig { Ok(authentication_config) } + /// Whether no authentication is configured. + pub fn is_empty(&self) -> bool { + self.authentication_types.is_empty() + } + /// Automatically add volumes, volume mounts, commands and containers to /// the respective pod / container builders. pub fn add_authentication_pod_and_volume_config( diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 08dda5971..dc1273d5c 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -134,7 +134,7 @@ pub fn build( // authentication-requires-TLS check. let server_tls_enabled = cluster.cluster_config.tls.server.is_some(); let internal_tls_enabled = cluster.cluster_config.tls.internal.is_some(); - if cluster.cluster_config.authentication_enabled && !server_tls_enabled { + if cluster.cluster_config.authentication_enabled() && !server_tls_enabled { return Err(Error::AuthenticationRequiresTls); } if server_tls_enabled || internal_tls_enabled { diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index fe154df7b..6f4490a6f 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -60,7 +60,6 @@ pub struct ValidatedTls { pub struct ValidatedClusterConfig { pub tls: ValidatedTls, pub authentication: TrinoAuthenticationConfig, - pub authentication_enabled: bool, pub authorization: Option, pub fault_tolerant_execution: Option, pub client_protocol: Option, @@ -68,6 +67,13 @@ pub struct ValidatedClusterConfig { pub catalogs: Vec, } +impl ValidatedClusterConfig { + /// Whether any authentication is configured. + pub fn authentication_enabled(&self) -> bool { + !self.authentication.is_empty() + } +} + /// A validated, merged Trino role-group config. /// /// Holds the merged [`v1alpha1::TrinoConfig`] fields so the build steps consume this @@ -184,7 +190,7 @@ impl ValidatedCluster { /// Whether client TLS should be set, depending on authentication and server TLS settings. pub fn tls_enabled(&self) -> bool { - self.cluster_config.authentication_enabled || self.server_tls_enabled() + self.cluster_config.authentication_enabled() || self.server_tls_enabled() } /// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index c32945428..36f995a2b 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -266,7 +266,6 @@ pub fn validate( internal: tls.internal_secret_class.as_ref().map(ToString::to_string), }, authentication, - authentication_enabled: trino.authentication_enabled(), authorization: dereferenced_objects.trino_opa_config.clone(), fault_tolerant_execution: dereferenced_objects.resolved_fte_config.clone(), client_protocol: dereferenced_objects.resolved_client_protocol_config.clone(), @@ -366,7 +365,7 @@ mod tests { "e6ac237d-a6d4-43a1-8135-f36506110912" ); assert_eq!(validated.product_version, 479); - assert!(!validated.cluster_config.authentication_enabled); + assert!(!validated.cluster_config.authentication_enabled()); assert!( validated .role_group_configs diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index c73f5d3b5..1fbd91d71 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -568,12 +568,6 @@ impl v1alpha1::TrinoCluster { &self.spec.cluster_config.authentication } - /// Check if any authentication settings are provided - pub fn authentication_enabled(&self) -> bool { - let spec: &v1alpha1::TrinoClusterSpec = &self.spec; - !spec.cluster_config.authentication.is_empty() - } - pub fn get_opa_config(&self) -> Option<&v1alpha1::TrinoAuthorizationOpaConfig> { self.spec .cluster_config From 7da182fdde51ff14682ac8de7940b5efa43d5e92 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 18:41:01 +0200 Subject: [PATCH 51/65] refactor: move constants and use Port type --- rust/operator-binary/src/controller/build.rs | 4 ++- .../src/controller/build/graceful_shutdown.rs | 4 +-- .../src/controller/build/ports.rs | 27 +++++++++++++++++++ .../src/controller/build/resource/listener.rs | 9 ++++--- .../src/controller/build/resource/service.rs | 6 ++--- .../controller/build/resource/statefulset.rs | 2 +- rust/operator-binary/src/controller/mod.rs | 26 +----------------- 7 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 rust/operator-binary/src/controller/build/ports.rs diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index b2975aaf8..c90530cb0 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -2,8 +2,10 @@ //! //! `properties` renders the Trino `.properties` files; `resource` builds the individual //! Kubernetes resources (ConfigMap, Service, Listener, PDB, …); `graceful_shutdown` -//! contributes graceful-shutdown `config.properties` entries and Pod lifecycle configuration. +//! contributes graceful-shutdown `config.properties` entries and Pod lifecycle configuration; +//! `ports` derives the client-facing port from the validated TLS configuration. pub mod graceful_shutdown; +pub mod ports; pub mod properties; pub mod resource; diff --git a/rust/operator-binary/src/controller/build/graceful_shutdown.rs b/rust/operator-binary/src/controller/build/graceful_shutdown.rs index 7503b4187..6de09737c 100644 --- a/rust/operator-binary/src/controller/build/graceful_shutdown.rs +++ b/rust/operator-binary/src/controller/build/graceful_shutdown.rs @@ -125,9 +125,9 @@ pub fn add_graceful_shutdown_config( echo 'Successfully sent graceful shutdown command' >> /proc/1/fd/1 2>&1 echo 'Sleeping {termination_grace_period_seconds} seconds' >> /proc/1/fd/1 2>&1 sleep {termination_grace_period_seconds}", - protocol = cluster.exposed_protocol(), + protocol = super::ports::exposed_protocol(cluster), host = "127.0.0.1", - port = cluster.exposed_port(), + port = super::ports::exposed_port(cluster), ), ]), }), diff --git a/rust/operator-binary/src/controller/build/ports.rs b/rust/operator-binary/src/controller/build/ports.rs new file mode 100644 index 000000000..9add10acf --- /dev/null +++ b/rust/operator-binary/src/controller/build/ports.rs @@ -0,0 +1,27 @@ +//! The client-facing port Trino exposes, derived from the validated TLS configuration. +//! +//! Mapping the server-TLS flag onto a concrete port number / name is a resource-shaping decision, +//! so it lives in the build step rather than on [`ValidatedCluster`]. + +use crate::{ + controller::ValidatedCluster, + crd::{HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME}, +}; + +/// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. +pub(crate) fn exposed_port(cluster: &ValidatedCluster) -> u16 { + if cluster.server_tls_enabled() { + HTTPS_PORT + } else { + HTTP_PORT + } +} + +/// The name of the client-facing port (see [`exposed_port`]). +pub(crate) fn exposed_protocol(cluster: &ValidatedCluster) -> &'static str { + if cluster.server_tls_enabled() { + HTTPS_PORT_NAME + } else { + HTTP_PORT_NAME + } +} diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 8785f6cc3..6bb292d7c 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -6,7 +6,10 @@ use stackable_operator::{ kvp::Labels, }; -use crate::{controller::ValidatedCluster, crd::TrinoRole}; +use crate::{ + controller::{ValidatedCluster, build::ports}, + crd::TrinoRole, +}; pub const LISTENER_VOLUME_NAME: &str = "listener"; pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; @@ -73,8 +76,8 @@ pub fn secret_volume_listener_scope(role: &TrinoRole) -> Option { /// We only use the http/https port here and intentionally omit the metrics one. fn listener_ports(cluster: &ValidatedCluster) -> Vec { - let name = cluster.exposed_protocol().to_string(); - let port = cluster.exposed_port().into(); + let name = ports::exposed_protocol(cluster).to_string(); + let port = ports::exposed_port(cluster).into(); vec![ListenerPort { name, diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 7f831860d..a8acd646c 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -6,7 +6,7 @@ use stackable_operator::{ }; use crate::{ - controller::ValidatedCluster, + controller::{ValidatedCluster, build::ports}, crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole}, }; @@ -77,8 +77,8 @@ pub fn build_rolegroup_metrics_service( } pub(crate) fn headless_service_ports(cluster: &ValidatedCluster) -> Vec { - let name = cluster.exposed_protocol().to_string(); - let port = cluster.exposed_port().into(); + let name = ports::exposed_protocol(cluster).to_string(); + let port = ports::exposed_port(cluster).into(); vec![ServicePort { name: Some(name), diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index e23d8dbcb..3d189a5cb 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -582,7 +582,7 @@ fn http_get_probe(cluster: &ValidatedCluster) -> HTTPGetAction { /// /// This probe works on coordinators and workers. fn finished_starting_probe(cluster: &ValidatedCluster) -> ExecAction { - let port = cluster.exposed_port(); + let port = build::ports::exposed_port(cluster); let schema = if cluster.server_tls_enabled() { "https" } else { diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 6f4490a6f..315723837 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -36,10 +36,7 @@ use crate::{ client_protocol::ResolvedClientProtocolConfig, fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, }, - crd::{ - APP_NAME, HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME, TrinoRole, - discovery::TrinoPodRef, v1alpha1, - }, + crd::{APP_NAME, TrinoRole, discovery::TrinoPodRef, v1alpha1}, trino_controller::{CONTROLLER_NAME, OPERATOR_NAME}, }; @@ -193,27 +190,6 @@ impl ValidatedCluster { self.cluster_config.authentication_enabled() || self.server_tls_enabled() } - /// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. - /// - /// Replaces `v1alpha1::TrinoCluster::exposed_port`, derived here from the validated TLS config - /// so build steps don't re-read the raw cluster. - pub fn exposed_port(&self) -> u16 { - if self.server_tls_enabled() { - HTTPS_PORT - } else { - HTTP_PORT - } - } - - /// The name of the client-facing port (see [`Self::exposed_port`]). - pub fn exposed_protocol(&self) -> &'static str { - if self.server_tls_enabled() { - HTTPS_PORT_NAME - } else { - HTTP_PORT_NAME - } - } - /// Type-safe names for the resources of a given role group. pub(crate) fn resource_names(&self, role: &TrinoRole, role_group_name: &str) -> ResourceNames { ResourceNames { From c8605f2518331b651666a36cad93881b506d1860 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 18:46:24 +0200 Subject: [PATCH 52/65] fix: add internal tls helper --- .../src/controller/build/properties/config_properties.rs | 4 ++-- rust/operator-binary/src/controller/mod.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index dc1273d5c..60048f537 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -132,8 +132,8 @@ pub fn build( // TLS gating — mirrors the existing compute_files logic, including the // authentication-requires-TLS check. - let server_tls_enabled = cluster.cluster_config.tls.server.is_some(); - let internal_tls_enabled = cluster.cluster_config.tls.internal.is_some(); + let server_tls_enabled = cluster.server_tls_enabled(); + let internal_tls_enabled = cluster.internal_tls_enabled(); if cluster.cluster_config.authentication_enabled() && !server_tls_enabled { return Err(Error::AuthenticationRequiresTls); } diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 315723837..8554cfb58 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -172,6 +172,11 @@ impl ValidatedCluster { self.cluster_config.tls.server.is_some() } + /// Whether internal (inter-node) TLS is enabled. + pub fn internal_tls_enabled(&self) -> bool { + self.cluster_config.tls.internal.is_some() + } + /// The user-provided server TLS SecretClass, if any. /// /// Mirrors [`v1alpha1::TrinoCluster::get_server_tls`] but reads from the validated config so From 12f152e66df237111ca10abb5cb2eb61aa2c04d9 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 17 Jun 2026 18:57:50 +0200 Subject: [PATCH 53/65] fix: remove obsolete comment --- rust/operator-binary/src/controller/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 8554cfb58..cf8d82a21 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -1,6 +1,3 @@ -//! Controller-level vocabulary: the [`ValidatedCluster`] type produced by the [`validate`] step -//! and consumed by the [`build`] steps, plus the `dereference` / `validate` / `build` sub-modules. - use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ @@ -178,9 +175,6 @@ impl ValidatedCluster { } /// The user-provided server TLS SecretClass, if any. - /// - /// Mirrors [`v1alpha1::TrinoCluster::get_server_tls`] but reads from the validated config so - /// build steps don't need the raw cluster. pub fn get_server_tls(&self) -> Option<&str> { self.cluster_config.tls.server.as_deref() } From ed4379fd4098332f3063d41ffe0a18ca369da85f Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 08:52:05 +0200 Subject: [PATCH 54/65] chore: remove obsolete error variants --- rust/operator-binary/src/catalog/mod.rs | 13 +------------ .../src/controller/build/resource/statefulset.rs | 10 ---------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/rust/operator-binary/src/catalog/mod.rs b/rust/operator-binary/src/catalog/mod.rs index 274ec9727..6a2f8ae38 100644 --- a/rust/operator-binary/src/catalog/mod.rs +++ b/rust/operator-binary/src/catalog/mod.rs @@ -12,10 +12,7 @@ pub mod tpch; use async_trait::async_trait; use snafu::Snafu; -use stackable_operator::{ - client::Client, commons::tls_verification::TlsClientDetailsError, - v2::types::kubernetes::NamespaceName, -}; +use stackable_operator::{client::Client, v2::types::kubernetes::NamespaceName}; use self::config::CatalogConfig; @@ -27,9 +24,6 @@ pub enum FromTrinoCatalogError { source: stackable_operator::crd::s3::v1alpha1::ConnectionError, }, - #[snafu(display("failed to configure S3 TLS client details"))] - ConfigureS3TlsClientDetails { source: TlsClientDetailsError }, - #[snafu(display("trino does not support disabling the TLS verification of S3 servers"))] S3TlsNoVerificationNotSupported, @@ -58,11 +52,6 @@ pub enum FromTrinoCatalogError { data_key: String, }, - #[snafu(display("failed to create the Secret Volume for the S3 credentials"))] - CreateS3CredentialsSecretOperatorVolume { - source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, - }, - #[snafu(display("failed to get PostgreSQL connection details"))] GetPostgresConnectionDetails { source: stackable_operator::database_connections::Error, diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 3d189a5cb..c7f753771 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -94,21 +94,11 @@ pub enum Error { source: build::graceful_shutdown::Error, }, - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - #[snafu(display("failed to build Annotation"))] AnnotationBuild { source: stackable_operator::kvp::KeyValuePairError, }, - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build TLS certificate SecretClass Volume"))] TlsCertSecretClassVolumeBuild { source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, From e2981adc35ba868ae32083663a932bbb361c725d Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 08:59:34 +0200 Subject: [PATCH 55/65] fix: remove insert loops --- .../build/properties/config_properties.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 60048f537..2b811bc23 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -192,13 +192,12 @@ pub fn build( } // Authentication properties (only contributes when authentication is enabled). - for (k, v) in cluster - .cluster_config - .authentication - .config_properties(&role) - { - props.insert(k, v); - } + props.extend( + cluster + .cluster_config + .authentication + .config_properties(&role), + ); // Discovery URI. if let Some(coordinator_ref) = cluster.cluster_config.coordinator_pod_refs.first() { @@ -215,11 +214,11 @@ pub fn build( } // Graceful shutdown. - for (k, v) in crate::controller::build::graceful_shutdown::graceful_shutdown_config_properties( - cluster, role, - ) { - props.insert(k, v); - } + props.extend( + crate::controller::build::graceful_shutdown::graceful_shutdown_config_properties( + cluster, role, + ), + ); // Fault-tolerant execution. if let Some(fte) = &cluster.cluster_config.fault_tolerant_execution { From 3389acae206e1e8ed348282ba4152dad8b0e2678 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 09:06:12 +0200 Subject: [PATCH 56/65] fix: consolidate listener class default, keystore / truststore constants --- .../src/config/fault_tolerant_execution.rs | 9 ++++++--- .../controller/build/properties/config_properties.rs | 12 ++++++++---- rust/operator-binary/src/crd/mod.rs | 4 +++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/rust/operator-binary/src/config/fault_tolerant_execution.rs b/rust/operator-binary/src/config/fault_tolerant_execution.rs index 6e4c2a14e..92bf7626a 100644 --- a/rust/operator-binary/src/config/fault_tolerant_execution.rs +++ b/rust/operator-binary/src/config/fault_tolerant_execution.rs @@ -21,6 +21,9 @@ use crate::{ }, }; +/// Sub-directory of [`CONFIG_DIR_NAME`] holding the HDFS exchange config. +const EXCHANGE_HDFS_CONFIG: &str = "exchange-hdfs-config"; + #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to resolve S3 connection"))] @@ -240,7 +243,7 @@ impl ResolvedFaultTolerantExecutionConfig { hdfs_config.skip_directory_scheme_validation, ); - let hdfs_config_dir = format!("{CONFIG_DIR_NAME}/exchange-hdfs-config"); + let hdfs_config_dir = format!("{CONFIG_DIR_NAME}/{EXCHANGE_HDFS_CONFIG}"); exchange_manager_properties.insert( "hdfs.config.resources".to_string(), format!("{hdfs_config_dir}/core-site.xml,{hdfs_config_dir}/hdfs-site.xml"), @@ -308,8 +311,8 @@ impl ResolvedFaultTolerantExecutionConfig { } fn resolve_hdfs_backend(&mut self, hdfs_config: &HdfsExchangeConfig) { - let hdfs_config_dir = format!("{CONFIG_DIR_NAME}/exchange-hdfs-config"); - let volume_name = "exchange-hdfs-config".to_string(); + let hdfs_config_dir = format!("{CONFIG_DIR_NAME}/{EXCHANGE_HDFS_CONFIG}"); + let volume_name = EXCHANGE_HDFS_CONFIG.to_string(); self.volumes.push( VolumeBuilder::new(&volume_name) diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 2b811bc23..99910d4cf 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -57,6 +57,10 @@ const DEFAULT_NODE_SCHEDULER_INCLUDE_COORDINATOR: &str = "false"; const LOG_FILE_COUNT: u32 = 2; +// TLS keystore/truststore file names (PKCS#12). +const KEYSTORE_P12: &str = "keystore.p12"; +const TRUSTSTORE_P12: &str = "truststore.p12"; + #[derive(Debug, Snafu)] pub enum Error { #[snafu(display( @@ -153,7 +157,7 @@ pub fn build( }; props.insert( HTTP_SERVER_KEYSTORE_PATH.to_string(), - format!("{tls_store_dir}/keystore.p12"), + format!("{tls_store_dir}/{KEYSTORE_P12}"), ); props.insert( HTTP_SERVER_HTTPS_KEYSTORE_KEY.to_string(), @@ -161,7 +165,7 @@ pub fn build( ); props.insert( HTTP_SERVER_TRUSTSTORE_PATH.to_string(), - format!("{tls_store_dir}/truststore.p12"), + format!("{tls_store_dir}/{TRUSTSTORE_P12}"), ); props.insert( HTTP_SERVER_HTTPS_TRUSTSTORE_KEY.to_string(), @@ -171,7 +175,7 @@ pub fn build( if internal_tls_enabled { props.insert( INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_PATH.to_string(), - format!("{STACKABLE_INTERNAL_TLS_DIR}/keystore.p12"), + format!("{STACKABLE_INTERNAL_TLS_DIR}/{KEYSTORE_P12}"), ); props.insert( INTERNAL_COMMUNICATION_HTTPS_KEYSTORE_KEY.to_string(), @@ -179,7 +183,7 @@ pub fn build( ); props.insert( INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_PATH.to_string(), - format!("{STACKABLE_INTERNAL_TLS_DIR}/truststore.p12"), + format!("{STACKABLE_INTERNAL_TLS_DIR}/{TRUSTSTORE_P12}"), ); props.insert( INTERNAL_COMMUNICATION_HTTPS_TRUSTSTORE_KEY.to_string(), diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 1fbd91d71..14451ff7c 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -86,6 +86,8 @@ pub const ENV_INTERNAL_SECRET: &str = "INTERNAL_SECRET"; pub const ENV_SPOOLING_SECRET: &str = "SPOOLING_SECRET"; // TLS const TLS_DEFAULT_SECRET_CLASS: &str = "tls"; +// Listener +pub const DEFAULT_LISTENER_CLASS: &str = "cluster-internal"; // Logging pub const MAX_TRINO_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { value: 10.0, @@ -374,7 +376,7 @@ impl Default for v1alpha1::TrinoCoordinatorRoleConfig { } fn coordinator_default_listener_class() -> ListenerClassName { - ListenerClassName::from_str("cluster-internal") + ListenerClassName::from_str(DEFAULT_LISTENER_CLASS) .expect("the default listener class name must be valid") } From e54361e5e17f414acfda9d0d1bc620205e15249f Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 09:08:48 +0200 Subject: [PATCH 57/65] fix: consolidate catalog naming --- .../src/controller/build/resource/config_map.rs | 7 +------ .../src/controller/build/resource/statefulset.rs | 2 +- rust/operator-binary/src/controller/mod.rs | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index bae52b352..b269d7512 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -202,12 +202,7 @@ pub fn build_rolegroup_catalog_config_map( role_group_name: &str, recommended_labels: &Labels, ) -> Result { - let catalog_config_map_name = format!( - "{}-catalog", - cluster - .resource_names(role, role_group_name) - .role_group_config_map() - ); + let catalog_config_map_name = cluster.role_group_catalog_config_map_name(role, role_group_name); ConfigMapBuilder::new() .metadata( cluster diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index c7f753771..ed4136c49 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -409,7 +409,7 @@ pub fn build_rolegroup_statefulset( .add_volume(Volume { name: CATALOG_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { - name: format!("{config_map_name}-catalog"), + name: cluster.role_group_catalog_config_map_name(trino_role, role_group_name), ..ConfigMapVolumeSource::default() }), ..Volume::default() diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index cf8d82a21..82ebd843e 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -198,6 +198,20 @@ impl ValidatedCluster { } } + /// Name of the rolegroup's catalog [`ConfigMap`], derived from the rolegroup config map name + /// by appending the `-catalog` suffix. + pub(crate) fn role_group_catalog_config_map_name( + &self, + role: &TrinoRole, + role_group_name: &str, + ) -> String { + format!( + "{}-catalog", + self.resource_names(role, role_group_name) + .role_group_config_map() + ) + } + /// Returns an [`ObjectMetaBuilder`] pre-filled with this cluster's namespace, an owner /// reference back to the cluster, the resource `name` and the given `recommended_labels`. /// From b6ec3b2942255e1fa50346de55458640c97e63ca Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 09:10:44 +0200 Subject: [PATCH 58/65] fix: use listener role group placeholder --- rust/operator-binary/src/controller/build.rs | 11 ++++++----- rust/operator-binary/src/trino_controller.rs | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index c90530cb0..996627c72 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -1,11 +1,12 @@ //! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. -//! -//! `properties` renders the Trino `.properties` files; `resource` builds the individual -//! Kubernetes resources (ConfigMap, Service, Listener, PDB, …); `graceful_shutdown` -//! contributes graceful-shutdown `config.properties` entries and Pod lifecycle configuration; -//! `ports` derives the client-facing port from the validated TLS configuration. pub mod graceful_shutdown; pub mod ports; pub mod properties; pub mod resource; + +/// Placeholder role-group name used for the recommended labels of a role's group listener. +/// +/// The group listener is owned by the role (not a single role-group), so there is no real +/// role-group to attribute it to. +pub(crate) const PLACEHOLDER_LISTENER_ROLE_GROUP: &str = "none"; diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 1694a42bb..8d82b41f5 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -354,7 +354,8 @@ pub async fn reconcile_trino( { let role_group_listener = build_group_listener( &validated_cluster, - validated_cluster.recommended_labels(trino_role, "none"), + validated_cluster + .recommended_labels(trino_role, build::PLACEHOLDER_LISTENER_ROLE_GROUP), listener_class.to_string(), listener_group_name, ); From a8af627be8ca6f0e518c08ea5d3b3a02a7edaf14 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 09:16:32 +0200 Subject: [PATCH 59/65] fix: use checked namespace im opa config --- rust/operator-binary/src/authorization/opa.rs | 12 ++++++------ rust/operator-binary/src/controller/dereference.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 4c583b43c..086dda3e8 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -1,8 +1,10 @@ use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ - client::Client, commons::opa::OpaApiVersion, k8s_openapi::api::core::v1::ConfigMap, - kube::ResourceExt, v2::types::kubernetes::SecretClassName, + client::Client, + commons::opa::OpaApiVersion, + k8s_openapi::api::core::v1::ConfigMap, + v2::types::kubernetes::{NamespaceName, SecretClassName}, }; use crate::crd::v1alpha1; @@ -41,6 +43,7 @@ impl TrinoOpaConfig { pub async fn from_opa_config( client: &Client, trino: &v1alpha1::TrinoCluster, + namespace: &NamespaceName, opa_config: &v1alpha1::TrinoAuthorizationOpaConfig, ) -> Result { let non_batched_connection_string = opa_config @@ -86,10 +89,7 @@ impl TrinoOpaConfig { }; let tls_secret_class = client - .get::( - &opa_config.opa.config_map_name, - trino.namespace().as_deref().unwrap_or("default"), - ) + .get::(&opa_config.opa.config_map_name, namespace.as_ref()) .await .ok() .and_then(|cm| cm.data) diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index 11f0a4a48..d11c4f22e 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -121,7 +121,7 @@ pub async fn dereference( let trino_opa_config = match trino.get_opa_config() { Some(opa_config) => Some( - TrinoOpaConfig::from_opa_config(client, trino, opa_config) + TrinoOpaConfig::from_opa_config(client, trino, &namespace, opa_config) .await .context(InvalidOpaConfigSnafu)?, ), From 8c809482eb57553b14e73c266a7e8e25e7ac3f46 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 09:16:53 +0200 Subject: [PATCH 60/65] fix: consolidate coordinator role extraction --- rust/operator-binary/src/crd/mod.rs | 35 +++++------------------------ 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 14451ff7c..d06d1c748 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -28,7 +28,7 @@ use stackable_operator::{ kube::{CustomResource, ResourceExt}, memory::{BinaryMultiple, MemoryQuantity}, product_logging::{self, spec::Logging}, - role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup}, + role_utils::{GenericRoleConfig, Role}, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, @@ -581,37 +581,14 @@ impl v1alpha1::TrinoCluster { } } +/// Converts the coordinator role (which carries the coordinator-specific `role_config`) into the +/// generic [`TrinoRoleType`]. Only the `role_config` type parameter differs between the two; the +/// `config` and `role_groups` carry over unchanged. fn extract_role_from_coordinator_config(fragment: TrinoCoordinatorRoleType) -> TrinoRoleType { Role { - config: CommonConfiguration { - config: fragment.config.config, - config_overrides: fragment.config.config_overrides, - env_overrides: fragment.config.env_overrides, - cli_overrides: fragment.config.cli_overrides, - pod_overrides: fragment.config.pod_overrides, - product_specific_common_config: fragment.config.product_specific_common_config, - }, + config: fragment.config, role_config: fragment.role_config.common, - role_groups: fragment - .role_groups - .into_iter() - .map(|(k, v)| { - ( - k, - RoleGroup { - config: CommonConfiguration { - config: v.config.config, - config_overrides: v.config.config_overrides, - env_overrides: v.config.env_overrides, - cli_overrides: v.config.cli_overrides, - pod_overrides: v.config.pod_overrides, - product_specific_common_config: v.config.product_specific_common_config, - }, - replicas: v.replicas, - }, - ) - }) - .collect(), + role_groups: fragment.role_groups, } } From 1739d4428040dd1a4b663817d8dd57b4c491aa29 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 10:10:06 +0200 Subject: [PATCH 61/65] fix: add s3_tls_truststore_commands & cleanup --- .../operator-binary/src/authentication/mod.rs | 8 +-- .../src/authentication/password/ldap.rs | 7 +-- rust/operator-binary/src/catalog/commons.rs | 26 +++------ rust/operator-binary/src/config/s3.rs | 54 ++++++++++++------- rust/operator-binary/src/main.rs | 4 +- 5 files changed, 48 insertions(+), 51 deletions(-) diff --git a/rust/operator-binary/src/authentication/mod.rs b/rust/operator-binary/src/authentication/mod.rs index c2e0385bd..69fde6544 100644 --- a/rust/operator-binary/src/authentication/mod.rs +++ b/rust/operator-binary/src/authentication/mod.rs @@ -336,9 +336,7 @@ impl TrinoAuthenticationConfig { pub fn env_vars(&self, role: &TrinoRole, container: &crate::crd::Container) -> Vec { self.env_vars .get(role) - .cloned() - .unwrap_or_default() - .get(container) + .and_then(|by_container| by_container.get(container)) .cloned() .unwrap_or_default() } @@ -347,9 +345,7 @@ impl TrinoAuthenticationConfig { pub fn commands(&self, role: &TrinoRole, container: &crate::crd::Container) -> Vec { self.commands .get(role) - .cloned() - .unwrap_or_default() - .get(container) + .and_then(|by_container| by_container.get(container)) .cloned() .unwrap_or_default() } diff --git a/rust/operator-binary/src/authentication/password/ldap.rs b/rust/operator-binary/src/authentication/password/ldap.rs index 671b4a100..a32262e8c 100644 --- a/rust/operator-binary/src/authentication/password/ldap.rs +++ b/rust/operator-binary/src/authentication/password/ldap.rs @@ -58,15 +58,12 @@ impl LdapAuthenticator { /// Return the content of the authenticator config file to register with Trino pub fn config_file_data(&self) -> Result, Error> { let mut config_data = BTreeMap::new(); - self.ldap.endpoint_url().context(LdapEndpointSnafu)?; + let endpoint_url = self.ldap.endpoint_url().context(LdapEndpointSnafu)?; config_data.insert( password::PASSWORD_AUTHENTICATOR_NAME.to_string(), PASSWORD_AUTHENTICATOR_NAME_LDAP.to_string(), ); - config_data.insert( - LDAP_URL.to_string(), - self.ldap.endpoint_url().context(LdapEndpointSnafu)?.into(), - ); + config_data.insert(LDAP_URL.to_string(), endpoint_url.into()); config_data.insert(LDAP_USER_BASE_DN.to_string(), self.ldap.search_base.clone()); diff --git a/rust/operator-binary/src/catalog/commons.rs b/rust/operator-binary/src/catalog/commons.rs index 26778040e..2558214e5 100644 --- a/rust/operator-binary/src/catalog/commons.rs +++ b/rust/operator-binary/src/catalog/commons.rs @@ -3,7 +3,6 @@ use snafu::{OptionExt, ResultExt, ensure}; use stackable_operator::{ builder::pod::volume::{VolumeBuilder, VolumeMountBuilder}, client::Client, - commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification}, crd::s3, k8s_openapi::api::core::v1::ConfigMap, v2::types::kubernetes::NamespaceName, @@ -19,9 +18,9 @@ use super::{ }, }; use crate::{ - command, + config, crd::{ - CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, + CONFIG_DIR_NAME, catalog::commons::{HdfsConnection, MetastoreConnection}, }, }; @@ -131,23 +130,10 @@ impl ExtendCatalogConfig for s3::v1alpha1::InlineConnectionOrReference { 469.. => ensure!(s3.tls.uses_tls(), S3TlsRequiredSnafu), }; - if let Some(tls) = s3.tls.tls.as_ref() { - match &tls.verification { - TlsVerification::None {} => return S3TlsNoVerificationNotSupportedSnafu.fail(), - TlsVerification::Server(TlsServerVerification { - ca_cert: CaCert::WebPki {}, - }) => {} - TlsVerification::Server(TlsServerVerification { - ca_cert: CaCert::SecretClass(_), - }) => { - if let Some(ca_cert) = s3.tls.tls_ca_cert_mount_path() { - catalog_config.init_container_extra_start_commands.extend( - command::add_cert_to_truststore(&ca_cert, STACKABLE_CLIENT_TLS_DIR), - ); - } - } - } - } + catalog_config.init_container_extra_start_commands.extend( + config::s3::s3_tls_truststore_commands(&s3.tls) + .map_err(|_| S3TlsNoVerificationNotSupportedSnafu.build())?, + ); Ok(()) } diff --git a/rust/operator-binary/src/config/s3.rs b/rust/operator-binary/src/config/s3.rs index c75a82089..b379a426b 100644 --- a/rust/operator-binary/src/config/s3.rs +++ b/rust/operator-binary/src/config/s3.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use snafu::{ResultExt, Snafu}; use stackable_operator::{ client::Client, - commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification}, + commons::tls_verification::{CaCert, TlsClientDetails, TlsServerVerification, TlsVerification}, crd::s3, k8s_openapi::api::core::v1::{Volume, VolumeMount}, }; @@ -21,6 +21,37 @@ pub enum Error { S3TlsNoVerificationNotSupported, } +/// Returned by [`s3_tls_truststore_commands`] when an S3 connection disables TLS verification, +/// which Trino does not support. +#[derive(Debug)] +pub struct S3TlsVerificationDisabled; + +/// Build the init-container commands that add an S3 server's CA certificate to the client +/// truststore. +/// +/// Returns an empty list when no extra trust setup is needed (TLS disabled, or WebPKI +/// verification), and [`S3TlsVerificationDisabled`] when TLS verification is turned off (which +/// Trino rejects). Shared by the spooling/exchange S3 config and the S3 catalog connection. +pub fn s3_tls_truststore_commands( + tls: &TlsClientDetails, +) -> Result, S3TlsVerificationDisabled> { + let Some(tls_config) = tls.tls.as_ref() else { + return Ok(Vec::new()); + }; + match &tls_config.verification { + TlsVerification::None {} => Err(S3TlsVerificationDisabled), + TlsVerification::Server(TlsServerVerification { + ca_cert: CaCert::WebPki {}, + }) => Ok(Vec::new()), + TlsVerification::Server(TlsServerVerification { + ca_cert: CaCert::SecretClass(_), + }) => Ok(tls + .tls_ca_cert_mount_path() + .map(|ca_cert| command::add_cert_to_truststore(&ca_cert, STACKABLE_CLIENT_TLS_DIR)) + .unwrap_or_default()), + } +} + pub struct ResolvedS3Config { /// Properties to add to config.properties pub properties: BTreeMap, @@ -91,23 +122,10 @@ impl ResolvedS3Config { ]); } - if let Some(tls) = s3_connection.tls.tls.as_ref() { - match &tls.verification { - TlsVerification::None {} => return S3TlsNoVerificationNotSupportedSnafu.fail(), - TlsVerification::Server(TlsServerVerification { - ca_cert: CaCert::WebPki {}, - }) => {} - TlsVerification::Server(TlsServerVerification { - ca_cert: CaCert::SecretClass(_), - }) => { - if let Some(ca_cert) = s3_connection.tls.tls_ca_cert_mount_path() { - resolved_config.init_container_extra_start_commands.extend( - command::add_cert_to_truststore(&ca_cert, STACKABLE_CLIENT_TLS_DIR), - ); - } - } - } - } + resolved_config.init_container_extra_start_commands.extend( + s3_tls_truststore_commands(&s3_connection.tls) + .map_err(|_| Error::S3TlsNoVerificationNotSupported)?, + ); Ok(resolved_config) } diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index da6ec2d68..899ca7278 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -186,8 +186,8 @@ async fn main() -> anyhow::Result<()> { config_map_cluster_store .state() .into_iter() - .filter(move |druid| references_config_map(druid, &config_map)) - .map(|druid| ObjectRef::from_obj(&*druid)) + .filter(move |trino| references_config_map(trino, &config_map)) + .map(|trino| ObjectRef::from_obj(&*trino)) }, ) .graceful_shutdown_on(sigterm_watcher.handle()) From 4a66071a5cee5193e5fb96ec04fa00471e28f799 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 10:33:52 +0200 Subject: [PATCH 62/65] refactor: move command.rs to build, use ConfigFileNames, remove v2 / upstream references --- .../src/authentication/oidc/mod.rs | 2 +- .../src/config/client_protocol.rs | 2 +- rust/operator-binary/src/config/s3.rs | 2 +- rust/operator-binary/src/controller/build.rs | 1 + .../src/{ => controller/build}/command.rs | 23 ++++++++++--------- .../build/properties/config_properties.rs | 5 ++-- .../src/controller/build/properties/mod.rs | 6 ++--- .../build/properties/product_logging/mod.rs | 2 +- .../controller/build/resource/config_map.rs | 3 +-- .../controller/build/resource/statefulset.rs | 15 ++++++------ .../src/controller/dereference.rs | 8 ++----- .../src/crd/client_protocol.rs | 4 ++-- rust/operator-binary/src/crd/mod.rs | 4 ++-- rust/operator-binary/src/main.rs | 2 -- rust/operator-binary/src/trino_controller.rs | 9 ++++---- 15 files changed, 40 insertions(+), 48 deletions(-) rename rust/operator-binary/src/{ => controller/build}/command.rs (88%) diff --git a/rust/operator-binary/src/authentication/oidc/mod.rs b/rust/operator-binary/src/authentication/oidc/mod.rs index 5f2449e6e..081534282 100644 --- a/rust/operator-binary/src/authentication/oidc/mod.rs +++ b/rust/operator-binary/src/authentication/oidc/mod.rs @@ -8,7 +8,7 @@ use stackable_operator::{ use crate::{ authentication::TrinoAuthenticationConfig, - command, + controller::build::command, crd::{STACKABLE_CLIENT_TLS_DIR, TrinoRole}, }; diff --git a/rust/operator-binary/src/config/client_protocol.rs b/rust/operator-binary/src/config/client_protocol.rs index 478f7f48a..472e71233 100644 --- a/rust/operator-binary/src/config/client_protocol.rs +++ b/rust/operator-binary/src/config/client_protocol.rs @@ -1,4 +1,4 @@ -// Consolidate Trino S3 properties in a single reusable struct. +//! Resolves the Trino client (spooling) protocol configuration. use std::collections::BTreeMap; diff --git a/rust/operator-binary/src/config/s3.rs b/rust/operator-binary/src/config/s3.rs index b379a426b..186c512ac 100644 --- a/rust/operator-binary/src/config/s3.rs +++ b/rust/operator-binary/src/config/s3.rs @@ -8,7 +8,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::{Volume, VolumeMount}, }; -use crate::{command, crd::STACKABLE_CLIENT_TLS_DIR}; +use crate::{controller::build::command, crd::STACKABLE_CLIENT_TLS_DIR}; #[derive(Snafu, Debug)] pub enum Error { diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index 996627c72..b2a8ba40c 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -1,5 +1,6 @@ //! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. +pub mod command; pub mod graceful_shutdown; pub mod ports; pub mod properties; diff --git a/rust/operator-binary/src/command.rs b/rust/operator-binary/src/controller/build/command.rs similarity index 88% rename from rust/operator-binary/src/command.rs rename to rust/operator-binary/src/controller/build/command.rs index 7c370c3fd..5c20ee65c 100644 --- a/rust/operator-binary/src/command.rs +++ b/rust/operator-binary/src/controller/build/command.rs @@ -10,7 +10,7 @@ use crate::{ authentication::TrinoAuthenticationConfig, catalog::config::CatalogConfig, config::{client_protocol, fault_tolerant_execution}, - controller::{ValidatedCluster, ValidatedTrinoConfig}, + controller::{ValidatedCluster, ValidatedTrinoConfig, build::properties::ConfigFileName}, crd::{ CONFIG_DIR_NAME, Container, RW_CONFIG_DIR_NAME, STACKABLE_CLIENT_TLS_DIR, STACKABLE_INTERNAL_TLS_DIR, STACKABLE_MOUNT_INTERNAL_TLS_DIR, @@ -20,12 +20,6 @@ use crate::{ trino_controller::{STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR}, }; -// TODO: replace with build::properties::ConfigFileName once command.rs moves under build/ -// (with the StatefulSet builder migration). -const LOG_PROPERTIES: &str = "log.properties"; -const EXCHANGE_MANAGER_PROPERTIES: &str = "exchange-manager.properties"; -const SPOOLING_MANAGER_PROPERTIES: &str = "spooling-manager.properties"; - pub fn container_prepare_args( cluster: &ValidatedCluster, catalogs: &[CatalogConfig], @@ -37,13 +31,14 @@ pub fn container_prepare_args( // Copy custom logging provided `log.properties` to rw config if let ValidatedContainerLogConfigChoice::Custom(_) = merged_config.logging.trino_container { + let log_properties = ConfigFileName::Log; // copy config files to a writeable empty folder args.push(format!( - "echo copying {STACKABLE_LOG_CONFIG_DIR}/{LOG_PROPERTIES} {rw_conf}/{LOG_PROPERTIES}", + "echo copying {STACKABLE_LOG_CONFIG_DIR}/{log_properties} {rw_conf}/{log_properties}", rw_conf = RW_CONFIG_DIR_NAME )); args.push(format!( - "cp -RL {STACKABLE_LOG_CONFIG_DIR}/{LOG_PROPERTIES} {rw_conf}/{LOG_PROPERTIES}", + "cp -RL {STACKABLE_LOG_CONFIG_DIR}/{log_properties} {rw_conf}/{log_properties}", rw_conf = RW_CONFIG_DIR_NAME )); } @@ -118,13 +113,19 @@ pub fn container_trino_args( // Resolve credentials for fault tolerant execution exchange manager if needed args.push(format!( "test -f {rw_exchange_manager_config_file} && config-utils template {rw_exchange_manager_config_file}", - rw_exchange_manager_config_file = format!("{RW_CONFIG_DIR_NAME}/{EXCHANGE_MANAGER_PROPERTIES}") + rw_exchange_manager_config_file = format!( + "{RW_CONFIG_DIR_NAME}/{exchange_manager}", + exchange_manager = ConfigFileName::ExchangeManager + ) )); // Resolve credentials for spooling manager if needed args.push(format!( "test -f {rw_spooling_config_file} && config-utils template {rw_spooling_config_file}", - rw_spooling_config_file = format!("{RW_CONFIG_DIR_NAME}/{SPOOLING_MANAGER_PROPERTIES}") + rw_spooling_config_file = format!( + "{RW_CONFIG_DIR_NAME}/{spooling_manager}", + spooling_manager = ConfigFileName::SpoolingManager + ) )); args.push("set -x".to_string()); diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 99910d4cf..2101ea17b 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -51,7 +51,7 @@ const LOG_COMPRESSION: &str = "log.compression"; const LOG_MAX_SIZE: &str = "log.max-size"; const LOG_MAX_TOTAL_SIZE: &str = "log.max-total-size"; -// Defaults migrated from deploy/config-spec/properties.yaml. +// Default values for `config.properties`. const DEFAULT_QUERY_MAX_MEMORY: &str = "50GB"; const DEFAULT_NODE_SCHEDULER_INCLUDE_COORDINATOR: &str = "false"; @@ -134,8 +134,7 @@ pub fn build( format!("${{ENV:{ENV_INTERNAL_SECRET}}}"), ); - // TLS gating — mirrors the existing compute_files logic, including the - // authentication-requires-TLS check. + // TLS gating, including the authentication-requires-TLS check. let server_tls_enabled = cluster.server_tls_enabled(); let internal_tls_enabled = cluster.internal_tls_enabled(); if cluster.cluster_config.authentication_enabled() && !server_tls_enabled { diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 7dd398305..6b070dd13 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -1,8 +1,7 @@ //! Per-file builders for Trino `.properties` files. //! //! Each `.rs` module produces the rendered key/value pairs for one -//! Trino config file. The shared -//! [`stackable_operator::v2::config_file_writer`] serializes the map to the +//! Trino config file. The shared config-file writer serializes the map to the //! Java-properties on-wire format. pub mod access_control_properties; @@ -101,8 +100,7 @@ mod tests { } /// The escape behaviours pinned by the kuttl smoke snapshot - /// (`tests/templates/kuttl/smoke/14-assert.yaml.j2`); previously asserted - /// against the hand-rolled writer, now against the shared one. + /// (`tests/templates/kuttl/smoke/14-assert.yaml.j2`). #[test] fn kuttl_pinned_escapes_are_stable() { assert_eq!( diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs index 0a4bd8273..59950da6d 100644 --- a/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs @@ -3,7 +3,7 @@ /// The Vector agent configuration (`vector.yaml`). /// /// It is templated with environment variables (`${LOG_DIR}`, `${NAMESPACE}`, …) that the -/// `v2` Vector container injects at runtime, so the same file content is used for every +/// Vector container injects at runtime, so the same file content is used for every /// rolegroup. const VECTOR_CONFIG: &str = include_str!("vector.yaml"); diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index b269d7512..f42eeff0a 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -80,8 +80,7 @@ pub fn build_rolegroup_config_map( let mut data: BTreeMap = BTreeMap::new(); - // Auth files (e.g. password-authenticator file contents) — inserted FIRST - // to match the legacy precedence. + // Auth files (e.g. password-authenticator file contents). for (file_name, props) in cluster.cluster_config.authentication.config_files(role) { let rendered = to_java_properties_string(props.iter()).with_context(|_| WritePropertiesSnafu { diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index ed4136c49..1aa7ca92c 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -39,12 +39,14 @@ use stackable_operator::{ use crate::{ authorization::opa::OPA_TLS_VOLUME_NAME, - command, controller::{ TrinoRoleGroupConfig, ValidatedCluster, build, - build::resource::listener::{ - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, - group_listener_name, secret_volume_listener_scope, + build::{ + command, + resource::listener::{ + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, + group_listener_name, secret_volume_listener_scope, + }, }, }, crd::{ @@ -426,9 +428,8 @@ pub fn build_rolegroup_statefulset( .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); let mut pod_template = pod_builder.build_template(); - // `pod_overrides` already carries the merged role + role-group overrides (see - // `with_validated_config`), so a single merge here is equivalent to the previous - // role-then-role-group sequence. + // `pod_overrides` already carries the merged role + role-group overrides, so a single merge + // here applies both. pod_template.merge_from(role_group_config.pod_overrides.clone()); let annotations = restarter_ignore_secret_annotations( diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index d11c4f22e..408657fb3 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -1,10 +1,7 @@ //! The dereference step in the TrinoCluster controller //! //! Fetches all Kubernetes objects referenced by the TrinoCluster spec and returns them in -//! [`DereferencedObjects`]. The functions called here (`CatalogConfig::from_catalog`, -//! `TrinoOpaConfig::from_opa_config`, `ResolvedFaultTolerantExecutionConfig::from_config`, -//! `ResolvedClientProtocolConfig::from_config`) currently mix fetching and validation; their -//! outputs are treated as "dereferenced" for now. Splitting those helpers is a follow-up. +//! [`DereferencedObjects`]. use std::{num::ParseIntError, str::FromStr}; @@ -71,8 +68,7 @@ pub enum Error { type Result = std::result::Result; -/// Kubernetes objects referenced from the TrinoCluster spec, already fetched (and, for now, partly -/// validated by the existing helper functions). +/// Kubernetes objects referenced from the TrinoCluster spec, fetched from the cluster. pub struct DereferencedObjects { pub resolved_authentication_classes: Vec, pub catalog_definitions: Vec, diff --git a/rust/operator-binary/src/crd/client_protocol.rs b/rust/operator-binary/src/crd/client_protocol.rs index 19d4dcae9..4a899e5ce 100644 --- a/rust/operator-binary/src/crd/client_protocol.rs +++ b/rust/operator-binary/src/crd/client_protocol.rs @@ -1,5 +1,5 @@ -/// This module manages the client protocol properties, especially the for spooling. -/// Trino documentation is available here: https://trino.io/docs/current/client/client-protocol.html +//! This module manages the client protocol properties, especially for spooling. +//! Trino documentation is available here: use serde::{Deserialize, Serialize}; use stackable_operator::schemars::{self, JsonSchema}; diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index d06d1c748..b21827e2b 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -607,8 +607,8 @@ mod tests { use super::*; - /// The user-provided server TLS SecretClass as `Option<&str>` (mirrors the former - /// `TrinoCluster::get_server_tls`, kept here only for these CRD-defaulting assertions). + /// The user-provided server TLS SecretClass as `Option<&str>`, used by these CRD-defaulting + /// assertions. fn server_secret_class(trino: &v1alpha1::TrinoCluster) -> Option<&str> { trino .spec diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 899ca7278..21f49cc42 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -1,5 +1,4 @@ // TODO: Look into how to properly resolve `clippy::result_large_err`. -// This will need changes in our and upstream error types. #![allow(clippy::result_large_err)] use std::sync::Arc; @@ -45,7 +44,6 @@ use crate::{ mod authentication; mod authorization; mod catalog; -mod command; mod config; mod controller; mod crd; diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 8d82b41f5..109a94c21 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -463,11 +463,10 @@ mod tests { ), None => None, }; - // For OPA, the legacy helper used a hard-coded `TrinoOpaConfig` literal - // rather than resolving from cluster config; mirror that here so that - // `test_access_control_overrides` does not need a Kubernetes client and - // so that `test_config_overrides` keeps observing an - // `access-control.properties` entry in the rendered ConfigMap. + // Build a `TrinoOpaConfig` literal directly instead of resolving it from cluster config, + // so that `test_access_control_overrides` does not need a Kubernetes client and + // `test_config_overrides` still observes an `access-control.properties` entry in the + // rendered ConfigMap. let trino_opa_config = Some(TrinoOpaConfig { non_batched_connection_string: "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/allow" From aa5f20ae48e252faae3b5da815b9c5ae452a9b37 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 11:39:17 +0200 Subject: [PATCH 63/65] refactor: use v2 types --- .../src/authentication/password/file.rs | 26 +++++---- .../src/authentication/password/mod.rs | 23 ++++---- rust/operator-binary/src/authorization/opa.rs | 13 +++-- rust/operator-binary/src/controller/build.rs | 13 +++-- .../src/controller/build/ports.rs | 4 +- .../properties/access_control_properties.rs | 6 ++- .../build/properties/config_properties.rs | 6 ++- .../properties/exchange_manager_properties.rs | 6 ++- .../build/properties/log_properties.rs | 6 ++- .../build/properties/node_properties.rs | 6 ++- .../build/properties/security_properties.rs | 6 ++- .../properties/spooling_manager_properties.rs | 6 ++- .../controller/build/resource/config_map.rs | 10 ++-- .../src/controller/build/resource/listener.rs | 9 ++-- .../src/controller/build/resource/service.rs | 6 +-- .../controller/build/resource/statefulset.rs | 16 +++--- rust/operator-binary/src/controller/mod.rs | 53 ++++++++++--------- .../src/controller/validate.rs | 30 ++++++++--- rust/operator-binary/src/crd/discovery.rs | 4 +- rust/operator-binary/src/crd/mod.rs | 7 +-- rust/operator-binary/src/trino_controller.rs | 32 ++++++----- 21 files changed, 179 insertions(+), 109 deletions(-) diff --git a/rust/operator-binary/src/authentication/password/file.rs b/rust/operator-binary/src/authentication/password/file.rs index 7190c297c..650e951bc 100644 --- a/rust/operator-binary/src/authentication/password/file.rs +++ b/rust/operator-binary/src/authentication/password/file.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ @@ -15,6 +15,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::{Container, Volume, VolumeMount}, product_logging::{self, spec::AutomaticContainerLogConfig}, utils::COMMON_BASH_TRAP_FUNCTIONS, + v2::types::kubernetes::{SecretName, VolumeName}, }; use crate::{ @@ -23,7 +24,7 @@ use crate::{ }; // mounts -const PASSWORD_DB_VOLUME_NAME: &str = "users"; +stackable_operator::constant!(PASSWORD_DB_VOLUME_NAME: VolumeName = "users"); pub const PASSWORD_DB_VOLUME_MOUNT_PATH: &str = "/stackable/users"; pub const PASSWORD_AUTHENTICATOR_SECRET_MOUNT_PATH: &str = "/stackable/auth-secrets"; // trino properties @@ -36,6 +37,11 @@ pub enum Error { AddVolumeMounts { source: builder::pod::container::Error, }, + + #[snafu(display("failed to parse user credentials Secret name"))] + ParseSecretName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, } #[derive(Clone, Debug)] @@ -69,15 +75,15 @@ impl FileAuthenticator { } /// Return the name of the Secret providing the usernames and passwords - pub fn secret_name(&self) -> String { - self.file.user_credentials_secret.name.clone() + pub fn secret_name(&self) -> Result { + SecretName::from_str(&self.file.user_credentials_secret.name).context(ParseSecretNameSnafu) } /// Build the volume for the user secret - pub fn secret_volume(&self) -> Volume { - VolumeBuilder::new(self.secret_volume_name()) - .with_secret(self.secret_name(), false) - .build() + pub fn secret_volume(&self) -> Result { + Ok(VolumeBuilder::new(self.secret_volume_name()) + .with_secret(self.secret_name()?, false) + .build()) } /// Build the volume mount for the user secret @@ -87,14 +93,14 @@ impl FileAuthenticator { /// Build the volume for the user password db pub fn password_db_volume() -> Volume { - VolumeBuilder::new(PASSWORD_DB_VOLUME_NAME) + VolumeBuilder::new(&*PASSWORD_DB_VOLUME_NAME) .with_empty_dir(None::, None) .build() } /// Build the volume mount for the user password db pub fn password_db_volume_mount() -> VolumeMount { - VolumeMountBuilder::new(PASSWORD_DB_VOLUME_NAME, PASSWORD_DB_VOLUME_MOUNT_PATH).build() + VolumeMountBuilder::new(&*PASSWORD_DB_VOLUME_NAME, PASSWORD_DB_VOLUME_MOUNT_PATH).build() } fn password_file_name(&self) -> String { diff --git a/rust/operator-binary/src/authentication/password/mod.rs b/rust/operator-binary/src/authentication/password/mod.rs index e94ed704d..08e8c5e95 100644 --- a/rust/operator-binary/src/authentication/password/mod.rs +++ b/rust/operator-binary/src/authentication/password/mod.rs @@ -8,12 +8,8 @@ //! - volume and volume mounts //! - extra containers and commands //! -use std::str::FromStr; - use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - commons::product_image_selection::ResolvedProductImage, v2::types::kubernetes::SecretName, -}; +use stackable_operator::commons::product_image_selection::ResolvedProductImage; use tracing::trace; use crate::{ @@ -42,10 +38,8 @@ pub enum Error { #[snafu(display("failed to create LDAP Volumes and VolumeMounts"))] BuildPasswordFileUpdateContainer { source: file::Error }, - #[snafu(display("failed to parse user credentials Secret name"))] - ParseSecretName { - source: stackable_operator::v2::macros::attributed_string_type::Error, - }, + #[snafu(display("failed to resolve password file authenticator Secret"))] + FileAuthenticatorSecret { source: file::Error }, } #[derive(Clone, Debug, Default)] @@ -98,7 +92,11 @@ impl TrinoPasswordAuthentication { file_authenticator.config_file_data(), ); // required volumes - password_authentication_config.add_volume(file_authenticator.secret_volume()); + password_authentication_config.add_volume( + file_authenticator + .secret_volume() + .context(FileAuthenticatorSecretSnafu)?, + ); password_authentication_config .add_volume(FileAuthenticator::password_db_volume()); @@ -115,8 +113,9 @@ impl TrinoPasswordAuthentication { ); password_authentication_config.add_hot_reloaded_secret( - SecretName::from_str(&file_authenticator.secret_name()) - .context(ParseSecretNameSnafu)?, + file_authenticator + .secret_name() + .context(FileAuthenticatorSecretSnafu)?, ); } TrinoPasswordAuthenticator::Ldap(ldap_authenticator) => { diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 086dda3e8..7c4979784 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -4,12 +4,12 @@ use stackable_operator::{ client::Client, commons::opa::OpaApiVersion, k8s_openapi::api::core::v1::ConfigMap, - v2::types::kubernetes::{NamespaceName, SecretClassName}, + v2::types::kubernetes::{NamespaceName, SecretClassName, VolumeName}, }; use crate::crd::v1alpha1; -pub const OPA_TLS_VOLUME_NAME: &str = "opa-tls"; +stackable_operator::constant!(pub OPA_TLS_VOLUME_NAME: VolumeName = "opa-tls"); #[derive(Clone, Debug)] pub struct TrinoOpaConfig { @@ -142,8 +142,11 @@ impl TrinoOpaConfig { } pub fn tls_mount_path(&self) -> Option { - self.tls_secret_class - .as_ref() - .map(|_| format!("/stackable/secrets/{OPA_TLS_VOLUME_NAME}")) + self.tls_secret_class.as_ref().map(|_| { + format!( + "/stackable/secrets/{opa_tls_volume_name}", + opa_tls_volume_name = &*OPA_TLS_VOLUME_NAME + ) + }) } } diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index b2a8ba40c..6363166bb 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -1,13 +1,16 @@ //! Builders that turn a `ValidatedCluster` into Kubernetes resource contents. +use std::str::FromStr; + +use stackable_operator::v2::types::operator::RoleGroupName; + pub mod command; pub mod graceful_shutdown; pub mod ports; pub mod properties; pub mod resource; -/// Placeholder role-group name used for the recommended labels of a role's group listener. -/// -/// The group listener is owned by the role (not a single role-group), so there is no real -/// role-group to attribute it to. -pub(crate) const PLACEHOLDER_LISTENER_ROLE_GROUP: &str = "none"; +// Placeholder role-group name used for the recommended labels of a role's group listener. +// The group listener is owned by the role (not a single role-group), so there is no real +// role-group to attribute it to. +stackable_operator::constant!(pub(crate) PLACEHOLDER_LISTENER_ROLE_GROUP: RoleGroupName = "none"); diff --git a/rust/operator-binary/src/controller/build/ports.rs b/rust/operator-binary/src/controller/build/ports.rs index 9add10acf..a681c8a0a 100644 --- a/rust/operator-binary/src/controller/build/ports.rs +++ b/rust/operator-binary/src/controller/build/ports.rs @@ -3,13 +3,15 @@ //! Mapping the server-TLS flag onto a concrete port number / name is a resource-shaping decision, //! so it lives in the build step rather than on [`ValidatedCluster`]. +use stackable_operator::v2::types::common::Port; + use crate::{ controller::ValidatedCluster, crd::{HTTP_PORT, HTTP_PORT_NAME, HTTPS_PORT, HTTPS_PORT_NAME}, }; /// The client-facing port Trino exposes: HTTPS when server TLS is enabled, otherwise HTTP. -pub(crate) fn exposed_port(cluster: &ValidatedCluster) -> u16 { +pub(crate) fn exposed_port(cluster: &ValidatedCluster) -> Port { if cluster.server_tls_enabled() { HTTPS_PORT } else { diff --git a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs index dc0cef924..0c7e47b8d 100644 --- a/rust/operator-binary/src/controller/build/properties/access_control_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/access_control_properties.rs @@ -37,7 +37,11 @@ mod tests { #[test] fn default_renders_empty_when_no_opa() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&cluster, &rg); assert!(props.is_empty()); } diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 2101ea17b..8f221c0e6 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -256,7 +256,11 @@ mod tests { #[test] fn default_renders_includes_coordinator_default_and_query_max_memory_default() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let cluster_info = stackable_operator::utils::cluster_info::KubernetesClusterInfo { cluster_domain: stackable_operator::commons::networking::DomainName::try_from( "cluster.local", diff --git a/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs index 3bc58123e..337e21751 100644 --- a/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/exchange_manager_properties.rs @@ -37,7 +37,11 @@ mod tests { #[test] fn default_renders_empty_when_no_fte() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&cluster, &rg); assert!(props.is_empty()); } diff --git a/rust/operator-binary/src/controller/build/properties/log_properties.rs b/rust/operator-binary/src/controller/build/properties/log_properties.rs index cce9987eb..6c5831b81 100644 --- a/rust/operator-binary/src/controller/build/properties/log_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/log_properties.rs @@ -39,7 +39,11 @@ mod tests { #[test] fn default_renders_root_logger_only() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&rg); assert_eq!(props.get("").map(String::as_str), Some("info")); assert!(!props.contains_key("io.trino")); diff --git a/rust/operator-binary/src/controller/build/properties/node_properties.rs b/rust/operator-binary/src/controller/build/properties/node_properties.rs index 79bd0ec69..2046b56d5 100644 --- a/rust/operator-binary/src/controller/build/properties/node_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/node_properties.rs @@ -36,7 +36,11 @@ mod tests { #[test] fn default_renders_node_environment_from_cluster_name() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&crate::crd::TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&crate::crd::TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&cluster, &rg); assert_eq!( props.get("node.environment").map(String::as_str), diff --git a/rust/operator-binary/src/controller/build/properties/security_properties.rs b/rust/operator-binary/src/controller/build/properties/security_properties.rs index ae02871bb..29ef9f279 100644 --- a/rust/operator-binary/src/controller/build/properties/security_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/security_properties.rs @@ -47,7 +47,11 @@ mod tests { #[test] fn default_renders_networkaddress_cache_settings() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&rg); assert_eq!( props.get("networkaddress.cache.ttl").map(String::as_str), diff --git a/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs index 75b7828f3..94a6d63a9 100644 --- a/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/spooling_manager_properties.rs @@ -37,7 +37,11 @@ mod tests { #[test] fn default_renders_empty_when_no_spooling() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator]["default"].clone(); + let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .clone(); let props = build(&cluster, &rg); assert!(props.is_empty()); } diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index f42eeff0a..37bb891e7 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -12,7 +12,7 @@ use stackable_operator::{ use crate::{ config::jvm, controller::{ - ValidatedCluster, + RoleGroupName, ValidatedCluster, build::properties::{ ConfigFileName, access_control_properties, config_properties, exchange_manager_properties, log_properties, node_properties, product_logging, @@ -54,7 +54,7 @@ type Result = std::result::Result; pub fn build_rolegroup_config_map( cluster: &ValidatedCluster, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, cluster_info: &KubernetesClusterInfo, recommended_labels: &Labels, ) -> Result { @@ -64,13 +64,13 @@ pub fn build_rolegroup_config_map( .get(role) .with_context(|| MissingRoleGroupSnafu { role: role.to_string(), - role_group: role_group_name.to_owned(), + role_group: role_group_name.to_string(), })?; let rg = role_group_configs .get(role_group_name) .with_context(|| MissingRoleGroupSnafu { role: role.to_string(), - role_group: role_group_name.to_owned(), + role_group: role_group_name.to_string(), })?; let config_map_name = cluster @@ -198,7 +198,7 @@ pub fn build_rolegroup_config_map( pub fn build_rolegroup_catalog_config_map( cluster: &ValidatedCluster, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, recommended_labels: &Labels, ) -> Result { let catalog_config_map_name = cluster.role_group_catalog_config_map_name(role, role_group_name); diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 6bb292d7c..68458ccc1 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -1,9 +1,12 @@ +use std::str::FromStr; + use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::pod::volume::{ListenerOperatorVolumeSourceBuilder, ListenerReference}, crd::listener::v1alpha1::{Listener, ListenerPort, ListenerSpec}, k8s_openapi::api::core::v1::PersistentVolumeClaim, kvp::Labels, + v2::types::kubernetes::{ListenerClassName, VolumeName}, }; use crate::{ @@ -11,7 +14,7 @@ use crate::{ crd::TrinoRole, }; -pub const LISTENER_VOLUME_NAME: &str = "listener"; +stackable_operator::constant!(pub LISTENER_VOLUME_NAME: VolumeName = "listener"); pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; #[derive(Snafu, Debug)] @@ -25,7 +28,7 @@ pub enum Error { pub fn build_group_listener( cluster: &ValidatedCluster, recommended_labels: Labels, - listener_class: String, + listener_class: &ListenerClassName, listener_group_name: String, ) -> Listener { Listener { @@ -33,7 +36,7 @@ pub fn build_group_listener( .object_meta(listener_group_name, recommended_labels) .build(), spec: ListenerSpec { - class_name: Some(listener_class), + class_name: Some(listener_class.to_string()), ports: Some(listener_ports(cluster)), ..ListenerSpec::default() }, diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index a8acd646c..2974bec88 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -6,7 +6,7 @@ use stackable_operator::{ }; use crate::{ - controller::{ValidatedCluster, build::ports}, + controller::{RoleGroupName, ValidatedCluster, build::ports}, crd::{METRICS_PORT, METRICS_PORT_NAME, TrinoRole}, }; @@ -15,7 +15,7 @@ use crate::{ pub fn build_rolegroup_headless_service( cluster: &ValidatedCluster, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, recommended_labels: &Labels, selector: BTreeMap, ports: Vec, @@ -47,7 +47,7 @@ pub fn build_rolegroup_headless_service( pub fn build_rolegroup_metrics_service( cluster: &ValidatedCluster, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, recommended_labels: &Labels, selector: BTreeMap, ) -> Service { diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 1aa7ca92c..19ed2321c 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -33,14 +33,14 @@ use stackable_operator::{ v2::{ builder::{pod::container::EnvVarSet, statefulset::restarter_ignore_secret_annotations}, product_logging::framework::{ValidatedContainerLogConfigChoice, vector_container}, - types::kubernetes::{ContainerName, VolumeName}, + types::kubernetes::{ContainerName, SecretClassName, VolumeName}, }, }; use crate::{ authorization::opa::OPA_TLS_VOLUME_NAME, controller::{ - TrinoRoleGroupConfig, ValidatedCluster, build, + RoleGroupName, TrinoRoleGroupConfig, ValidatedCluster, build, build::{ command, resource::listener::{ @@ -137,7 +137,7 @@ type Result = std::result::Result; pub fn build_rolegroup_statefulset( cluster: &ValidatedCluster, trino_role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, role_group_config: &TrinoRoleGroupConfig, sa_name: &str, ) -> Result { @@ -302,7 +302,7 @@ pub fn build_rolegroup_statefulset( // Add listener if let Some(group_listener_name) = group_listener_name(cluster, trino_role) { cb_trino - .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) + .add_volume_mount(&*LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) .context(AddVolumeMountSnafu)?; // Used for PVC templates that cannot be modified once they are deployed, so a fixed @@ -596,12 +596,12 @@ fn finished_starting_probe(cluster: &ValidatedCluster) -> ExecAction { fn create_tls_volume( volume_name: impl Into, - tls_secret_class: &str, + tls_secret_class: &SecretClassName, requested_secret_lifetime: &Duration, listener_scope: Option, ) -> Result { let mut secret_volume_source_builder = SecretOperatorVolumeSourceBuilder::new( - tls_secret_class, + tls_secret_class.as_ref(), SecretClassVolumeProvisionParts::PublicPrivate, ); @@ -737,10 +737,10 @@ fn tls_volume_mounts( }) { cb_prepare - .add_volume_mount(OPA_TLS_VOLUME_NAME, &tls_mount_path) + .add_volume_mount(&*OPA_TLS_VOLUME_NAME, &tls_mount_path) .context(AddVolumeMountSnafu)?; - let opa_tls_volume = VolumeBuilder::new(OPA_TLS_VOLUME_NAME) + let opa_tls_volume = VolumeBuilder::new(&*OPA_TLS_VOLUME_NAME) .ephemeral( SecretOperatorVolumeSourceBuilder::new( tls_secret_class.as_ref(), diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 82ebd843e..5c46f594c 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -16,10 +16,9 @@ use stackable_operator::{ kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, types::{ - kubernetes::{ListenerClassName, NamespaceName, Uid}, + kubernetes::{ListenerClassName, NamespaceName, SecretClassName, Uid}, operator::{ - ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, - RoleGroupName as RoleGroupNameV2, RoleName, + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleName, }, }, }, @@ -45,8 +44,8 @@ pub use validate::{RoleGroupName, TrinoRoleGroupConfig}; #[derive(Clone, Debug)] pub struct ValidatedTls { - pub server: Option, - pub internal: Option, + pub server: Option, + pub internal: Option, } /// Cluster-wide settings, grouped to parallel `spec.clusterConfig` CRD. @@ -175,13 +174,13 @@ impl ValidatedCluster { } /// The user-provided server TLS SecretClass, if any. - pub fn get_server_tls(&self) -> Option<&str> { - self.cluster_config.tls.server.as_deref() + pub fn get_server_tls(&self) -> Option<&SecretClassName> { + self.cluster_config.tls.server.as_ref() } /// The user-provided internal TLS SecretClass, if any. - pub fn get_internal_tls(&self) -> Option<&str> { - self.cluster_config.tls.internal.as_deref() + pub fn get_internal_tls(&self) -> Option<&SecretClassName> { + self.cluster_config.tls.internal.as_ref() } /// Whether client TLS should be set, depending on authentication and server TLS settings. @@ -190,11 +189,15 @@ impl ValidatedCluster { } /// Type-safe names for the resources of a given role group. - pub(crate) fn resource_names(&self, role: &TrinoRole, role_group_name: &str) -> ResourceNames { + pub(crate) fn resource_names( + &self, + role: &TrinoRole, + role_group_name: &RoleGroupName, + ) -> ResourceNames { ResourceNames { cluster_name: self.name.clone(), role_name: Self::role_name(role), - role_group_name: Self::role_group_name(role_group_name), + role_group_name: role_group_name.clone(), } } @@ -203,7 +206,7 @@ impl ValidatedCluster { pub(crate) fn role_group_catalog_config_map_name( &self, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, ) -> String { format!( "{}-catalog", @@ -236,12 +239,6 @@ impl ValidatedCluster { RoleName::from_str(&role.to_string()).expect("a TrinoRole is a valid RFC 1123 role name") } - /// A role-group name as a type-safe [`RoleGroupName`](RoleGroupNameV2). - fn role_group_name(role_group_name: &str) -> RoleGroupNameV2 { - RoleGroupNameV2::from_str(role_group_name) - .expect("a validated role group name is a valid role group name") - } - /// The version label value (`app.kubernetes.io/version`) as a type-safe [`ProductVersion`]. fn version_label(&self) -> ProductVersion { ProductVersion::from_str(&self.image.app_version_label_value) @@ -252,7 +249,7 @@ impl ValidatedCluster { &self, version: &ProductVersion, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, ) -> Labels { recommended_labels( self, @@ -261,12 +258,16 @@ impl ValidatedCluster { &operator_name(), &controller_name(), &Self::role_name(role), - &Self::role_group_name(role_group_name), + role_group_name, ) } /// Recommended labels for a role-group resource (using the resolved product version). - pub(crate) fn recommended_labels(&self, role: &TrinoRole, role_group_name: &str) -> Labels { + pub(crate) fn recommended_labels( + &self, + role: &TrinoRole, + role_group_name: &RoleGroupName, + ) -> Labels { self.recommended_labels_with_version(&self.version_label(), role, role_group_name) } @@ -275,7 +276,7 @@ impl ValidatedCluster { pub(crate) fn unversioned_recommended_labels( &self, role: &TrinoRole, - role_group_name: &str, + role_group_name: &RoleGroupName, ) -> Labels { let none = ProductVersion::from_str("none") .expect("\"none\" is a valid product version label value"); @@ -283,12 +284,16 @@ impl ValidatedCluster { } /// Selector labels matching the pods of a role group. - pub(crate) fn role_group_selector(&self, role: &TrinoRole, role_group_name: &str) -> Labels { + pub(crate) fn role_group_selector( + &self, + role: &TrinoRole, + role_group_name: &RoleGroupName, + ) -> Labels { role_group_selector( self, &product_name(), &Self::role_name(role), - &Self::role_group_name(role_group_name), + role_group_name, ) } } diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 36f995a2b..20ed4b01d 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -83,10 +83,16 @@ pub enum Error { role: String, }, + #[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: fragment::ValidationError, - role_group: String, + role_group: RoleGroupName, }, #[snafu(display("invalid environment variable override name"))] @@ -105,7 +111,7 @@ pub enum Error { type Result = std::result::Result; -pub type RoleGroupName = String; +pub use stackable_operator::v2::types::operator::RoleGroupName; /// Validated logging configuration for the Trino, prepare and (optional) Vector containers. /// @@ -238,6 +244,10 @@ pub fn validate( ); let mut groups = BTreeMap::new(); for (rg_name, rg) in &role.role_groups { + let role_group_name = + RoleGroupName::from_str(rg_name).with_context(|_| ParseRoleGroupNameSnafu { + role_group: rg_name.clone(), + })?; // Merges and validates the role group config (default <- role <- role group). Because // `JavaCommonConfig` implements `Merge`, the role and role-group `jvmArgumentOverrides` // are merged here too and carried by `product_specific_common_config`. @@ -249,10 +259,10 @@ pub fn validate( v1alpha1::TrinoConfigOverrides, >(rg, &role, &default_config) .with_context(|_| FailedToResolveConfigSnafu { - role_group: rg_name.clone(), + role_group: role_group_name.clone(), })?; groups.insert( - rg_name.clone(), + role_group_name, into_role_group_config(merged, &vector_aggregator_config_map_name)?, ); } @@ -262,8 +272,8 @@ pub fn validate( let tls = &trino.spec.cluster_config.tls; let cluster_config = ValidatedClusterConfig { tls: ValidatedTls { - server: tls.server_secret_class.as_ref().map(ToString::to_string), - internal: tls.internal_secret_class.as_ref().map(ToString::to_string), + server: tls.server_secret_class.clone(), + internal: tls.internal_secret_class.clone(), }, authentication, authorization: dereferenced_objects.trino_opa_config.clone(), @@ -351,7 +361,9 @@ pub(crate) fn merged_role_group_config( #[cfg(test)] mod tests { - use super::super::validated_cluster; + use std::str::FromStr; + + use super::{super::validated_cluster, RoleGroupName}; use crate::crd::TrinoRole; #[test] @@ -377,7 +389,9 @@ mod tests { .contains_key(&TrinoRole::Worker) ); assert_eq!( - validated.role_group_configs[&TrinoRole::Coordinator]["default"].replicas, + validated.role_group_configs[&TrinoRole::Coordinator] + [&RoleGroupName::from_str("default").unwrap()] + .replicas, Some(1) ); } diff --git a/rust/operator-binary/src/crd/discovery.rs b/rust/operator-binary/src/crd/discovery.rs index e1899fe46..0eb0ff996 100644 --- a/rust/operator-binary/src/crd/discovery.rs +++ b/rust/operator-binary/src/crd/discovery.rs @@ -1,4 +1,4 @@ -use stackable_operator::utils::cluster_info::KubernetesClusterInfo; +use stackable_operator::{utils::cluster_info::KubernetesClusterInfo, v2::types::common::Port}; use crate::crd::{HTTP_PORT, HTTPS_PORT}; @@ -55,7 +55,7 @@ pub enum TrinoDiscoveryProtocol { } impl TrinoDiscoveryProtocol { - pub fn port(&self) -> u16 { + pub fn port(&self) -> Port { match self { TrinoDiscoveryProtocol::Http => HTTP_PORT, TrinoDiscoveryProtocol::Https => HTTPS_PORT, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index b21827e2b..ec154f8af 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -37,6 +37,7 @@ use stackable_operator::{ role_group_utils::ResourceNames, role_utils::JavaCommonConfig, types::{ + common::Port, kubernetes::{ConfigMapName, ListenerClassName, NamespaceName, SecretClassName}, operator::{ClusterName, RoleGroupName, RoleName}, }, @@ -63,9 +64,9 @@ pub type TrinoRoleType = Role< pub const APP_NAME: &str = "trino"; // ports -pub const HTTP_PORT: u16 = 8080; -pub const HTTPS_PORT: u16 = 8443; -pub const METRICS_PORT: u16 = 8081; +pub const HTTP_PORT: Port = Port(8080); +pub const HTTPS_PORT: Port = Port(8443); +pub const METRICS_PORT: Port = Port(8081); // port names pub const HTTP_PORT_NAME: &str = "http"; pub const HTTPS_PORT_NAME: &str = "https"; diff --git a/rust/operator-binary/src/trino_controller.rs b/rust/operator-binary/src/trino_controller.rs index 109a94c21..eaf66aecd 100644 --- a/rust/operator-binary/src/trino_controller.rs +++ b/rust/operator-binary/src/trino_controller.rs @@ -25,7 +25,7 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ controller::{ - build, + RoleGroupName, build, build::resource::{ listener::{build_group_listener, group_listener_name}, pdb::build_pdb, @@ -70,31 +70,31 @@ pub enum Error { #[snafu(display("failed to apply Service for {}", rolegroup))] ApplyRoleGroupService { source: stackable_operator::cluster_resources::Error, - rolegroup: String, + rolegroup: RoleGroupName, }, #[snafu(display("failed to build ConfigMap for {}", rolegroup))] BuildRoleGroupConfigMap { source: build::resource::config_map::Error, - rolegroup: String, + rolegroup: RoleGroupName, }, #[snafu(display("failed to apply ConfigMap for {}", rolegroup))] ApplyRoleGroupConfig { source: stackable_operator::cluster_resources::Error, - rolegroup: String, + rolegroup: RoleGroupName, }, #[snafu(display("failed to build StatefulSet for {}", rolegroup))] BuildRoleGroupStatefulSet { source: build::resource::statefulset::Error, - rolegroup: String, + rolegroup: RoleGroupName, }, #[snafu(display("failed to apply StatefulSet for {}", rolegroup))] ApplyRoleGroupStatefulSet { source: stackable_operator::cluster_resources::Error, - rolegroup: String, + rolegroup: RoleGroupName, }, #[snafu(display("failed to patch service account"))] @@ -355,8 +355,8 @@ pub async fn reconcile_trino( let role_group_listener = build_group_listener( &validated_cluster, validated_cluster - .recommended_labels(trino_role, build::PLACEHOLDER_LISTENER_ROLE_GROUP), - listener_class.to_string(), + .recommended_labels(trino_role, &build::PLACEHOLDER_LISTENER_ROLE_GROUP), + listener_class, listener_group_name, ); @@ -417,6 +417,8 @@ pub(crate) fn shared_spooling_secret_name(cluster_name: &ClusterName) -> String #[cfg(test)] mod tests { + use std::str::FromStr; + use stackable_operator::{ cli::OperatorEnvironmentOptions, commons::networking::DomainName, k8s_openapi::api::core::v1::ConfigMap, utils::cluster_info::KubernetesClusterInfo, @@ -505,13 +507,14 @@ mod tests { validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); let trino_role = TrinoRole::Coordinator; - let role_group_name = "default"; - let recommended_labels = validated_cluster.recommended_labels(&trino_role, role_group_name); + let role_group_name = RoleGroupName::from_str("default").expect("valid role group name"); + let recommended_labels = + validated_cluster.recommended_labels(&trino_role, &role_group_name); build::resource::config_map::build_rolegroup_config_map( &validated_cluster, &trino_role, - role_group_name, + &role_group_name, &cluster_info, &recommended_labels, ) @@ -732,8 +735,11 @@ mod tests { let validated_cluster = validate::validate(&trino, &derefs, &operator_env).expect("validate should succeed"); - let env = - &validated_cluster.role_group_configs[&TrinoRole::Coordinator]["default"].env_overrides; + let env = &validated_cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .env_overrides; let value = |name: &str| { env.get(&EnvVarName::from_str_unsafe(name)) .and_then(|env_var| env_var.value.clone()) From 3cc284e59213d498c1e0cef5013ae1c2599dbcf3 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 11:58:15 +0200 Subject: [PATCH 64/65] test: improve test helper & add auth tests --- .../build/properties/config_properties.rs | 189 +++++++++++++++++- .../src/controller/build/properties/mod.rs | 40 +++- 2 files changed, 225 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/controller/build/properties/config_properties.rs b/rust/operator-binary/src/controller/build/properties/config_properties.rs index 8f221c0e6..baea16cee 100644 --- a/rust/operator-binary/src/controller/build/properties/config_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/config_properties.rs @@ -250,9 +250,75 @@ pub fn build( mod tests { use super::*; use crate::controller::build::properties::test_support::{ - MINIMAL_TRINO_YAML, validated_cluster_from_yaml, + MINIMAL_TRINO_YAML, file_auth_class, validated_cluster_from_yaml, + validated_cluster_from_yaml_with_auth, }; + const SERVER_TLS_ONLY_YAML: &str = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + tls: + internalSecretClass: null + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + const INTERNAL_TLS_ONLY_YAML: &str = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + tls: + serverSecretClass: null + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + fn cluster_info() -> stackable_operator::utils::cluster_info::KubernetesClusterInfo { + stackable_operator::utils::cluster_info::KubernetesClusterInfo { + cluster_domain: stackable_operator::commons::networking::DomainName::try_from( + "cluster.local", + ) + .unwrap(), + } + } + + fn rg(cluster: &ValidatedCluster, role: &TrinoRole) -> TrinoRoleGroupConfig { + cluster.role_group_configs[role] + .values() + .next() + .expect("the fixture defines a role group") + .clone() + } + #[test] fn default_renders_includes_coordinator_default_and_query_max_memory_default() { let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); @@ -280,4 +346,125 @@ mod tests { Some("50GB") ); } + + #[test] + fn server_tls_only_uses_server_keystore_dir_and_http_discovery() { + let cluster = validated_cluster_from_yaml(SERVER_TLS_ONLY_YAML); + let props = build( + &cluster, + TrinoRole::Coordinator, + &rg(&cluster, &TrinoRole::Coordinator), + &cluster_info(), + ) + .unwrap(); + + assert_eq!( + props.get("http-server.https.enabled").map(String::as_str), + Some("true") + ); + assert_eq!( + props.get("http-server.https.port").map(String::as_str), + Some("8443") + ); + assert_eq!( + props + .get("http-server.https.keystore.path") + .map(String::as_str), + Some("/stackable/server_tls/keystore.p12"), + ); + // Server TLS is on, so insecure HTTP access must not be allowed. + assert_eq!( + props.get("http-server.authentication.allow-insecure-over-http"), + None + ); + // Internal TLS is off: no internal-communication keystore or FQDN address source. + assert_eq!( + props.get("internal-communication.https.keystore.path"), + None + ); + assert_eq!(props.get("node.internal-address-source"), None); + // Discovery uses http when internal TLS is disabled. + assert!(props.get("discovery.uri").unwrap().starts_with("http://")); + } + + #[test] + fn internal_tls_only_allows_insecure_http_and_uses_internal_keystore_dir() { + let cluster = validated_cluster_from_yaml(INTERNAL_TLS_ONLY_YAML); + let props = build( + &cluster, + TrinoRole::Coordinator, + &rg(&cluster, &TrinoRole::Coordinator), + &cluster_info(), + ) + .unwrap(); + + assert_eq!( + props.get("http-server.https.enabled").map(String::as_str), + Some("true") + ); + assert_eq!( + props + .get("http-server.authentication.allow-insecure-over-http") + .map(String::as_str), + Some("true"), + ); + assert_eq!( + props.get("http-server.http.port").map(String::as_str), + Some("8080") + ); + assert_eq!( + props + .get("http-server.https.keystore.path") + .map(String::as_str), + Some("/stackable/internal_tls/keystore.p12"), + ); + // Internal TLS block is present. + assert_eq!( + props + .get("internal-communication.https.keystore.path") + .map(String::as_str), + Some("/stackable/internal_tls/keystore.p12"), + ); + assert_eq!( + props + .get("node.internal-address-source") + .map(String::as_str), + Some("FQDN") + ); + // Discovery uses https when internal TLS is enabled. + assert!(props.get("discovery.uri").unwrap().starts_with("https://")); + } + + #[test] + fn worker_omits_include_coordinator() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let props = build( + &cluster, + TrinoRole::Worker, + &rg(&cluster, &TrinoRole::Worker), + &cluster_info(), + ) + .unwrap(); + + assert_eq!(props.get("coordinator").map(String::as_str), Some("false")); + assert_eq!(props.get("node-scheduler.include-coordinator"), None); + } + + #[test] + fn authentication_without_server_tls_errors() { + // Server TLS off (only internal) plus an enabled authenticator must be rejected. + let cluster = validated_cluster_from_yaml_with_auth( + INTERNAL_TLS_ONLY_YAML, + vec![file_auth_class("file-auth")], + ); + let err = build( + &cluster, + TrinoRole::Coordinator, + &rg(&cluster, &TrinoRole::Coordinator), + &cluster_info(), + ) + .unwrap_err(); + + assert!(matches!(err, Error::AuthenticationRequiresTls)); + } } diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 6b070dd13..83b6d53cc 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -39,13 +39,22 @@ pub(crate) mod test_support { use crate::{ controller::{ValidatedCluster, dereference::DereferencedObjects}, - crd::v1alpha1, + crd::{authentication::ResolvedAuthenticationClassRef, v1alpha1}, }; pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster { + validated_cluster_from_yaml_with_auth(yaml, Vec::new()) + } + + /// Like [`validated_cluster_from_yaml`], but injects already-resolved AuthenticationClasses so + /// tests can exercise authentication-dependent branches. + pub fn validated_cluster_from_yaml_with_auth( + yaml: &str, + resolved_authentication_classes: Vec, + ) -> ValidatedCluster { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); let derefs = DereferencedObjects { - resolved_authentication_classes: Vec::new(), + resolved_authentication_classes, catalog_definitions: Vec::new(), catalogs: Vec::new(), trino_opa_config: None, @@ -58,7 +67,32 @@ pub(crate) mod test_support { image_repository: "oci.example.org".to_string(), }; crate::controller::validate::validate(&trino, &derefs, &operator_env) - .expect("validate should succeed for the minimal fixture") + .expect("validate should succeed for the test fixture") + } + + /// A resolved `static` (file) AuthenticationClass, enough to make + /// [`ValidatedClusterConfig::authentication_enabled`](crate::controller::ValidatedClusterConfig) + /// return `true`. + pub fn file_auth_class(name: &str) -> ResolvedAuthenticationClassRef { + let yaml = format!( + r#" + metadata: + name: {name} + spec: + provider: + static: + userCredentialsSecret: + name: {name} + "# + ); + let deserializer = serde_yaml::Deserializer::from_str(&yaml); + let authentication_class = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer) + .expect("invalid test AuthenticationClass"); + ResolvedAuthenticationClassRef { + authentication_class, + client_auth_options: None, + } } pub const MINIMAL_TRINO_YAML: &str = r#" From b53db59fb2e97638a5ac5b6fb20779e13995d4c0 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 18 Jun 2026 13:46:37 +0200 Subject: [PATCH 65/65] test: improve unit tes coverage --- rust/operator-binary/src/authorization/opa.rs | 67 ++++++ rust/operator-binary/src/config/s3.rs | 44 ++++ .../src/controller/build/graceful_shutdown.rs | 111 ++++++++- .../src/controller/build/properties/mod.rs | 33 ++- .../build/properties/security_properties.rs | 67 +++++- .../src/controller/validate.rs | 219 +++++++++++++++++- 6 files changed, 522 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 7c4979784..eb758713c 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -150,3 +150,70 @@ impl TrinoOpaConfig { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn minimal_opa() -> TrinoOpaConfig { + TrinoOpaConfig { + non_batched_connection_string: "http://opa/allow".to_string(), + batched_connection_string: "http://opa/batch".to_string(), + row_filters_connection_string: None, + batched_column_masking_connection_string: None, + allow_permission_management_operations: false, + tls_secret_class: None, + } + } + + #[test] + fn as_config_renders_only_required_keys_when_optionals_are_unset() { + let config = minimal_opa().as_config(); + + assert_eq!( + config.get("access-control.name").map(String::as_str), + Some("opa") + ); + assert_eq!( + config.get("opa.policy.uri").map(String::as_str), + Some("http://opa/allow") + ); + assert_eq!( + config.get("opa.policy.batched-uri").map(String::as_str), + Some("http://opa/batch") + ); + assert!(!config.contains_key("opa.policy.row-filters-uri")); + assert!(!config.contains_key("opa.policy.batch-column-masking-uri")); + assert!(!config.contains_key("opa.allow-permission-management-operations")); + } + + #[test] + fn as_config_renders_optional_keys_when_set() { + let config = TrinoOpaConfig { + row_filters_connection_string: Some("http://opa/rowFilters".to_string()), + batched_column_masking_connection_string: Some( + "http://opa/batchColumnMasks".to_string(), + ), + allow_permission_management_operations: true, + ..minimal_opa() + } + .as_config(); + + assert_eq!( + config.get("opa.policy.row-filters-uri").map(String::as_str), + Some("http://opa/rowFilters") + ); + assert_eq!( + config + .get("opa.policy.batch-column-masking-uri") + .map(String::as_str), + Some("http://opa/batchColumnMasks") + ); + assert_eq!( + config + .get("opa.allow-permission-management-operations") + .map(String::as_str), + Some("true") + ); + } +} diff --git a/rust/operator-binary/src/config/s3.rs b/rust/operator-binary/src/config/s3.rs index 186c512ac..4705bb448 100644 --- a/rust/operator-binary/src/config/s3.rs +++ b/rust/operator-binary/src/config/s3.rs @@ -130,3 +130,47 @@ impl ResolvedS3Config { Ok(resolved_config) } } + +#[cfg(test)] +mod tests { + use stackable_operator::commons::tls_verification::Tls; + + use super::*; + + fn tls_details(verification: Option) -> TlsClientDetails { + TlsClientDetails { + tls: verification.map(|verification| Tls { verification }), + } + } + + #[test] + fn no_tls_yields_no_truststore_commands() { + assert!( + s3_tls_truststore_commands(&tls_details(None)) + .unwrap() + .is_empty() + ); + } + + #[test] + fn webpki_verification_yields_no_truststore_commands() { + let details = tls_details(Some(TlsVerification::Server(TlsServerVerification { + ca_cert: CaCert::WebPki {}, + }))); + assert!(s3_tls_truststore_commands(&details).unwrap().is_empty()); + } + + #[test] + fn secret_class_verification_yields_truststore_commands() { + let details = tls_details(Some(TlsVerification::Server(TlsServerVerification { + ca_cert: CaCert::SecretClass("tls".to_string()), + }))); + assert!(!s3_tls_truststore_commands(&details).unwrap().is_empty()); + } + + #[test] + fn disabled_verification_is_rejected() { + let details = tls_details(Some(TlsVerification::None {})); + assert!(s3_tls_truststore_commands(&details).is_err()); + } +} diff --git a/rust/operator-binary/src/controller/build/graceful_shutdown.rs b/rust/operator-binary/src/controller/build/graceful_shutdown.rs index 6de09737c..37a076204 100644 --- a/rust/operator-binary/src/controller/build/graceful_shutdown.rs +++ b/rust/operator-binary/src/controller/build/graceful_shutdown.rs @@ -145,7 +145,13 @@ mod tests { use stackable_operator::shared::time::Duration; use super::*; - use crate::controller::build::properties::test_support::validated_cluster_from_yaml; + use crate::{ + config::fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig, + controller::build::properties::test_support::{ + MINIMAL_TRINO_YAML, empty_derefs, validated_cluster_from_yaml, + validated_cluster_from_yaml_with_derefs, + }, + }; /// A worker role group without an explicit `gracefulShutdownTimeout` falls back to the /// product default. @@ -255,4 +261,107 @@ mod tests { Duration::from_minutes_unchecked(5) ); } + + fn fte_derefs() -> crate::controller::dereference::DereferencedObjects { + let mut derefs = empty_derefs(); + derefs.resolved_fte_config = Some(ResolvedFaultTolerantExecutionConfig { + config_properties: BTreeMap::new(), + exchange_manager_properties: BTreeMap::new(), + volumes: Vec::new(), + volume_mounts: Vec::new(), + init_container_extra_start_commands: Vec::new(), + }); + derefs + } + + #[test] + fn coordinator_props_set_query_max_execution_time_without_fte() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Coordinator); + // The default worker graceful-shutdown timeout is 60 minutes (3600s). + assert_eq!( + props.get("query.max-execution-time").map(String::as_str), + Some("3600s") + ); + } + + #[test] + fn coordinator_props_empty_with_fault_tolerant_execution() { + let cluster = validated_cluster_from_yaml_with_derefs(MINIMAL_TRINO_YAML, fte_derefs()); + let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Coordinator); + // With fault-tolerant execution, queries may be retried, so no max-execution-time is set. + assert!(props.is_empty()); + } + + #[test] + fn worker_props_set_shutdown_grace_period() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Worker); + assert_eq!( + props.get("shutdown.grace-period").map(String::as_str), + Some("30s") + ); + } + + #[test] + fn worker_termination_grace_period_adds_overhead_and_sets_pre_stop() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let merged = &cluster.role_group_configs[&TrinoRole::Worker] + .values() + .next() + .unwrap() + .config; + let mut pod_builder = PodBuilder::new(); + let mut trino_builder = ContainerBuilder::new("trino").unwrap(); + add_graceful_shutdown_config( + &cluster, + &TrinoRole::Worker, + merged, + &mut pod_builder, + &mut trino_builder, + ) + .unwrap(); + + // Default worker timeout 3600s + 2 * 30s grace + 10s safety = 3670s. + let spec = pod_builder.build_template().spec.unwrap(); + assert_eq!(spec.termination_grace_period_seconds, Some(3670)); + + let command = trino_builder + .build() + .lifecycle + .unwrap() + .pre_stop + .unwrap() + .exec + .unwrap() + .command + .unwrap(); + assert!(command.iter().any(|arg| arg.contains("sleep 3670"))); + } + + #[test] + fn coordinator_termination_grace_period_has_no_overhead_or_pre_stop() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let merged = &cluster.role_group_configs[&TrinoRole::Coordinator] + .values() + .next() + .unwrap() + .config; + let mut pod_builder = PodBuilder::new(); + let mut trino_builder = ContainerBuilder::new("trino").unwrap(); + add_graceful_shutdown_config( + &cluster, + &TrinoRole::Coordinator, + merged, + &mut pod_builder, + &mut trino_builder, + ) + .unwrap(); + + // The coordinator default timeout (900s) is used verbatim, with no overhead. + let spec = pod_builder.build_template().spec.unwrap(); + assert_eq!(spec.termination_grace_period_seconds, Some(900)); + // Coordinators do not get a graceful-shutdown pre-stop hook. + assert!(trino_builder.build().lifecycle.is_none()); + } } diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 83b6d53cc..76b2fb278 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -42,8 +42,20 @@ pub(crate) mod test_support { crd::{authentication::ResolvedAuthenticationClassRef, v1alpha1}, }; + /// Dereferenced objects with no external resources resolved. + pub fn empty_derefs() -> DereferencedObjects { + DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + } + } + pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster { - validated_cluster_from_yaml_with_auth(yaml, Vec::new()) + validated_cluster_from_yaml_with_derefs(yaml, empty_derefs()) } /// Like [`validated_cluster_from_yaml`], but injects already-resolved AuthenticationClasses so @@ -51,16 +63,19 @@ pub(crate) mod test_support { pub fn validated_cluster_from_yaml_with_auth( yaml: &str, resolved_authentication_classes: Vec, + ) -> ValidatedCluster { + let mut derefs = empty_derefs(); + derefs.resolved_authentication_classes = resolved_authentication_classes; + validated_cluster_from_yaml_with_derefs(yaml, derefs) + } + + /// Validates `yaml` against the given (test-supplied) dereferenced objects, letting tests + /// exercise branches that depend on resolved inputs (e.g. fault-tolerant execution). + pub fn validated_cluster_from_yaml_with_derefs( + yaml: &str, + derefs: DereferencedObjects, ) -> ValidatedCluster { let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); - let derefs = DereferencedObjects { - resolved_authentication_classes, - catalog_definitions: Vec::new(), - catalogs: Vec::new(), - trino_opa_config: None, - resolved_fte_config: None, - resolved_client_protocol_config: None, - }; let operator_env = OperatorEnvironmentOptions { operator_namespace: "stackable-operators".to_string(), operator_service_name: "trino-operator".to_string(), diff --git a/rust/operator-binary/src/controller/build/properties/security_properties.rs b/rust/operator-binary/src/controller/build/properties/security_properties.rs index 29ef9f279..70a932d89 100644 --- a/rust/operator-binary/src/controller/build/properties/security_properties.rs +++ b/rust/operator-binary/src/controller/build/properties/security_properties.rs @@ -44,15 +44,20 @@ mod tests { crd::TrinoRole, }; - #[test] - fn default_renders_networkaddress_cache_settings() { - let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); - let rg = cluster.role_group_configs[&TrinoRole::Coordinator] + fn coordinator_rg( + cluster: &crate::controller::ValidatedCluster, + ) -> crate::controller::TrinoRoleGroupConfig { + cluster.role_group_configs[&TrinoRole::Coordinator] .values() .next() .unwrap() - .clone(); - let props = build(&rg); + .clone() + } + + #[test] + fn default_renders_networkaddress_cache_settings() { + let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML); + let props = build(&coordinator_rg(&cluster)); assert_eq!( props.get("networkaddress.cache.ttl").map(String::as_str), Some("30") @@ -64,4 +69,54 @@ mod tests { Some("0") ); } + + #[test] + fn user_override_wins_and_extra_key_is_added() { + let cluster = validated_cluster_from_yaml( + r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + configOverrides: + security.properties: + networkaddress.cache.ttl: "99" + custom.extra.key: "myvalue" + workers: + roleGroups: + default: + replicas: 1 + "#, + ); + let props = build(&coordinator_rg(&cluster)); + + // User override wins over the default. + assert_eq!( + props.get("networkaddress.cache.ttl").map(String::as_str), + Some("99") + ); + // Extra (non-default) override key is added. + assert_eq!( + props.get("custom.extra.key").map(String::as_str), + Some("myvalue") + ); + // Untouched default remains. + assert_eq!( + props + .get("networkaddress.cache.negative.ttl") + .map(String::as_str), + Some("0") + ); + } } diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 20ed4b01d..d9f7adee1 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -361,10 +361,67 @@ pub(crate) fn merged_role_group_config( #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::BTreeMap, str::FromStr}; - use super::{super::validated_cluster, RoleGroupName}; - use crate::crd::TrinoRole; + use stackable_operator::cli::OperatorEnvironmentOptions; + + use super::{Error, RoleGroupName, validate}; + use crate::{ + config::client_protocol::ResolvedClientProtocolConfig, + controller::{ValidatedCluster, dereference::DereferencedObjects, validated_cluster}, + crd::{TrinoRole, v1alpha1}, + }; + + fn empty_derefs() -> DereferencedObjects { + DereferencedObjects { + resolved_authentication_classes: Vec::new(), + catalog_definitions: Vec::new(), + catalogs: Vec::new(), + trino_opa_config: None, + resolved_fte_config: None, + resolved_client_protocol_config: None, + } + } + + fn validate_yaml( + yaml: &str, + derefs: &DereferencedObjects, + ) -> std::result::Result { + let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML"); + let operator_env = OperatorEnvironmentOptions { + operator_namespace: "stackable-operators".to_string(), + operator_service_name: "trino-operator".to_string(), + image_repository: "oci.example.org".to_string(), + }; + validate(&trino, derefs, &operator_env) + } + + /// Minimal cluster YAML with both roles defined, parameterised on the product version. + fn minimal_yaml(product_version: &str) -> String { + format!( + r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "{product_version}" + clusterConfig: + catalogLabelSelector: {{}} + coordinators: + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "# + ) + } #[test] fn validate_minimal_cluster() { @@ -395,4 +452,160 @@ mod tests { Some(1) ); } + + #[test] + fn validate_logging_rejects_invalid_custom_config_map_name() { + let yaml = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + config: + logging: + containers: + trino: + custom: + configMap: "invalid ConfigMap name" + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + let err = validate_yaml(yaml, &empty_derefs()).unwrap_err(); + assert!(matches!(err, Error::ValidateLoggingConfig { .. })); + } + + #[test] + fn validate_logging_requires_vector_aggregator_when_agent_enabled() { + // The Vector agent is enabled but no aggregator discovery ConfigMap name is provided. + let yaml = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + config: + logging: + enableVectorAgent: true + roleGroups: + default: + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + let err = validate_yaml(yaml, &empty_derefs()).unwrap_err(); + assert!(matches!(err, Error::MissingVectorAggregatorConfigMapName)); + } + + #[test] + fn spooling_protocol_rejected_on_trino_45() { + let mut derefs = empty_derefs(); + derefs.resolved_client_protocol_config = Some(ResolvedClientProtocolConfig { + config_properties: BTreeMap::new(), + spooling_manager_properties: BTreeMap::new(), + volumes: Vec::new(), + volume_mounts: Vec::new(), + init_container_extra_start_commands: Vec::new(), + }); + + let err = validate_yaml(&minimal_yaml("451"), &derefs).unwrap_err(); + assert!(matches!( + err, + Error::ClientSpoolingProtocolTrinoVersion { .. } + )); + } + + #[test] + fn spooling_protocol_accepted_on_newer_trino() { + let mut derefs = empty_derefs(); + derefs.resolved_client_protocol_config = Some(ResolvedClientProtocolConfig { + config_properties: BTreeMap::new(), + spooling_manager_properties: BTreeMap::new(), + volumes: Vec::new(), + volume_mounts: Vec::new(), + init_container_extra_start_commands: Vec::new(), + }); + + assert!(validate_yaml(&minimal_yaml("479"), &derefs).is_ok()); + } + + #[test] + fn rejects_invalid_env_override_name() { + let yaml = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + default: + replicas: 1 + envOverrides: + "BAD=NAME": "value" + workers: + roleGroups: + default: + replicas: 1 + "#; + + let err = validate_yaml(yaml, &empty_derefs()).unwrap_err(); + assert!(matches!(err, Error::ParseEnvVarName { .. })); + } + + #[test] + fn rejects_invalid_role_group_name() { + let yaml = r#" + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCluster + metadata: + name: simple-trino + namespace: default + uid: "e6ac237d-a6d4-43a1-8135-f36506110912" + spec: + image: + productVersion: "479" + clusterConfig: + catalogLabelSelector: {} + coordinators: + roleGroups: + "Invalid_Name": + replicas: 1 + workers: + roleGroups: + default: + replicas: 1 + "#; + + let err = validate_yaml(yaml, &empty_derefs()).unwrap_err(); + assert!(matches!(err, Error::ParseRoleGroupName { .. })); + } }