diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e88aef0..ff8cf415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ More information can be found in the [Superset database documentation](https://docs.stackable.tech/home/nightly/superset/usage-guide/database-connections) for details ([#722]). - Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#731]). - test: Bump vector-aggregator to 0.55.0, replace /graphql call with gRPC call ([#735]). +- 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. + The `--product-config` CLI flag and `PRODUCT_CONFIG` env var are now no-ops ([#738]). [#717]: https://github.com/stackabletech/superset-operator/pull/717 [#719]: https://github.com/stackabletech/superset-operator/pull/719 @@ -33,6 +36,7 @@ [#726]: https://github.com/stackabletech/superset-operator/pull/726 [#731]: https://github.com/stackabletech/superset-operator/pull/731 [#735]: https://github.com/stackabletech/superset-operator/pull/735 +[#738]: https://github.com/stackabletech/superset-operator/pull/738 ## [26.3.0] - 2026-03-16 diff --git a/Cargo.lock b/Cargo.lock index 9b4c258d..e58e18e9 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]] @@ -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" @@ -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" @@ -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.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,9 +320,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -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]] @@ -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" @@ -596,7 +593,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -613,13 +610,13 @@ 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", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -671,14 +668,14 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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" @@ -735,7 +732,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -907,7 +904,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -924,9 +921,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" @@ -983,15 +980,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]] @@ -1025,9 +1021,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -1055,9 +1051,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" @@ -1087,9 +1083,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1138,9 +1134,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", @@ -1336,9 +1332,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", @@ -1351,7 +1347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", ] [[package]] @@ -1369,16 +1365,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" @@ -1413,9 +1399,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", @@ -1423,18 +1409,18 @@ 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", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1464,26 +1450,26 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] [[package]] name = "json-patch" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", + "schemars", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -1525,11 +1511,11 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "darling", "regex", - "snafu 0.9.0", + "snafu 0.9.1", ] [[package]] @@ -1625,7 +1611,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1666,15 +1652,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", @@ -1690,9 +1676,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", @@ -1717,9 +1703,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "matchers" @@ -1738,9 +1724,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -1760,9 +1746,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", @@ -1796,9 +1782,9 @@ dependencies = [ [[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" @@ -1850,9 +1836,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", @@ -1864,9 +1850,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", @@ -1876,9 +1862,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", @@ -1889,9 +1875,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", @@ -1903,14 +1889,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", @@ -1921,21 +1907,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", @@ -2047,7 +2034,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2062,22 +2049,22 @@ 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", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2197,9 +2184,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", @@ -2207,15 +2194,24 @@ 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", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", ] [[package]] @@ -2317,14 +2313,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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", @@ -2345,9 +2341,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" @@ -2357,9 +2353,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", @@ -2375,9 +2371,6 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", @@ -2459,7 +2452,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", "unicode-ident", ] @@ -2474,9 +2467,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", @@ -2489,9 +2482,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", @@ -2501,9 +2494,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", ] @@ -2563,7 +2556,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2661,7 +2654,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2672,14 +2665,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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", @@ -2757,9 +2750,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" @@ -2795,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" @@ -2820,11 +2813,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]] @@ -2847,26 +2840,26 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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", @@ -2897,7 +2890,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.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "const-oid", "ecdsa", @@ -2909,7 +2902,7 @@ dependencies = [ "rsa", "sha2", "signature", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-shared", "tokio", "tokio-rustls", @@ -2921,7 +2914,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "base64", "clap", @@ -2933,6 +2926,7 @@ dependencies = [ "futures 0.3.32", "http", "indexmap", + "java-properties", "jiff", "json-patch", "k8s-openapi", @@ -2945,7 +2939,8 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "sha2", + "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", "stackable-telemetry", @@ -2957,23 +2952,25 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "url", + "uuid", + "xml", ] [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +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.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +version = "0.1.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "jiff", "k8s-openapi", @@ -2982,7 +2979,7 @@ dependencies = [ "semver", "serde", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "time", ] @@ -2998,12 +2995,11 @@ dependencies = [ "fnv", "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", @@ -3012,8 +3008,8 @@ dependencies = [ [[package]] name = "stackable-telemetry" -version = "0.6.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +version = "0.6.4" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "axum", "clap", @@ -3024,7 +3020,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "tokio", "tower", @@ -3037,21 +3033,21 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "kube", "schemars", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-versioned-macros", ] [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "convert_case", "convert_case_extras", @@ -3063,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?tag=stackable-operator-0.111.1#7a5f0c3fbcd091340214a23f0607fcd4b4fcc152" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3" dependencies = [ "arc-swap", "async-trait", @@ -3085,7 +3081,7 @@ dependencies = [ "rand 0.9.4", "serde", "serde_json", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-certs", "stackable-shared", "stackable-telemetry", @@ -3122,7 +3118,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3150,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", @@ -3176,7 +3172,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3205,7 +3201,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3216,7 +3212,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3230,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", @@ -3245,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", @@ -3287,14 +3282,14 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3315,7 +3310,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3364,9 +3359,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", @@ -3385,9 +3380,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", @@ -3412,15 +3407,26 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", "tonic", ] +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -3442,9 +3448,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", @@ -3452,13 +3458,13 @@ dependencies = [ "futures-util", "http", "http-body", - "iri-string", "mime", "pin-project-lite", "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3506,7 +3512,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3532,9 +3538,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", @@ -3585,9 +3591,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -3603,9 +3609,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" @@ -3650,6 +3656,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3685,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.118" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -3707,9 +3723,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ "js-sys", "wasm-bindgen", @@ -3717,9 +3733,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3727,31 +3743,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3788,7 +3804,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3799,7 +3815,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3910,9 +3926,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3945,15 +3961,15 @@ dependencies = [ [[package]] name = "xml" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" +checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "yoke" -version = "0.8.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", @@ -3968,35 +3984,35 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[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", ] @@ -4009,28 +4025,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[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", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4063,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 bb6969a0..2b60deaa 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" ]; } @@ -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 " ]; @@ -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" ]; @@ -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.64"; edition = "2018"; - sha256 = "084a8ziprdlyrj865f3303qr0b7aaggilkl18slncss6m4yp1ia3"; + sha256 = "07shcd8faxw7csz13m3cg2mj6i8z07pqs960k181pscbjpyqgn6s"; authors = [ "Alex Crichton " ]; @@ -1011,9 +1012,9 @@ rec { }; "chrono" = rec { crateName = "chrono"; - version = "0.4.44"; + version = "0.4.45"; edition = "2021"; - sha256 = "1c64mk9a235271j5g3v4zrzqqmd43vp9vki7vqfllpqf5rd0fwy6"; + sha256 = "09rkcgk6is2sdhqs9142zv8xqnj8ryx8m9hknllqwyv9wxi9x9qs"; dependencies = [ { name = "iana-time-zone"; @@ -1160,7 +1161,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -1591,7 +1592,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "extra-traits" ]; } ]; @@ -1622,7 +1623,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -1648,7 +1649,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "visit-mut" ]; } ]; @@ -1725,7 +1726,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -1739,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" ]; @@ -1758,7 +1751,7 @@ rec { "rand09" = [ "dep:rand09" ]; "serde" = [ "dep:serde_core" ]; }; - resolvedDefaultFeatures = [ "default" "powerfmt" ]; + resolvedDefaultFeatures = [ "default" ]; }; "derive_more" = rec { crateName = "derive_more"; @@ -1827,7 +1820,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; buildDependencies = [ @@ -1906,9 +1899,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 " @@ -1924,7 +1917,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -2090,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" ]; } ]; @@ -2108,12 +2101,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" ]; @@ -2297,7 +2287,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -2798,7 +2788,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -2830,9 +2820,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 " @@ -3090,9 +3080,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 " @@ -3114,17 +3104,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" ]; @@ -3214,9 +3201,9 @@ rec { }; "h2" = rec { crateName = "h2"; - version = "0.4.13"; + version = "0.4.15"; edition = "2021"; - sha256 = "0m6w5gg0n0m1m5915bxrv8n4rlazhx5icknkslz719jhh4xdli1g"; + sha256 = "0mgilh1g8gydcchqi6acs5l6j0gwg5jwpa64sj4b3ncb9v497c3c"; authors = [ "Carl Lerche " "Sean McArthur " @@ -3327,14 +3314,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" ]; @@ -3409,9 +3393,9 @@ rec { }; "http" = rec { crateName = "http"; - version = "1.4.0"; + version = "1.4.2"; edition = "2021"; - sha256 = "06iind4cwsj1d6q8c2xgq8i2wka4ps74kmws24gsi1bzdlw2mfp3"; + sha256 = "09b4p8fiivkg7wm0b59fyrn1jkm7px298ci7zb9igz6n647gaw39"; authors = [ "Alex Crichton " "Carl Lerche " @@ -3528,9 +3512,9 @@ rec { }; "hyper" = rec { crateName = "hyper"; - version = "1.9.0"; + version = "1.10.1"; edition = "2021"; - sha256 = "1jmwbwqcaficskg76kq402gbymbnh2z4v99xwq3l5aa6n8bg16b2"; + sha256 = "1624nwrh1ci34psqcl3q8q266kha8kd6fmqjj14qck49l59iqa2m"; authors = [ "Sean McArthur " ]; @@ -4301,9 +4285,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" ]; @@ -4337,7 +4321,7 @@ rec { } { name = "hashbrown"; - packageId = "hashbrown 0.17.0"; + packageId = "hashbrown 0.17.1"; usesDefaultFeatures = false; } ]; @@ -4395,39 +4379,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"; @@ -4497,9 +4448,9 @@ rec { }; "jiff" = rec { crateName = "jiff"; - version = "0.2.23"; + version = "0.2.28"; edition = "2021"; - sha256 = "0nc37n7jvgrzxdkcgc2hsfdf70lfagigjalh4igjrm5njvf4cd8s"; + sha256 = "00lixngcc7amh2fcsxfr0z38j06lllhapz192biv1qj97q1x60s6"; authors = [ "Andrew Gallant " ]; @@ -4545,12 +4496,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 = [ @@ -4569,7 +4518,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" ]; @@ -4579,9 +4528,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 = [ @@ -4598,7 +4547,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -4661,9 +4610,9 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.95"; + version = "0.3.102"; edition = "2021"; - sha256 = "1jhj3kgxxgwm0cpdjiz7i2qapqr7ya9qswadmr63dhwx3lnyjr19"; + sha256 = "0cgxklnyrfpzvf32cvdl3x5d070kfsv7ykdxfl3yizwdjqq4rl03"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" @@ -4672,7 +4621,6 @@ rec { { name = "cfg-if"; packageId = "cfg-if"; - optional = true; } { name = "futures-util"; @@ -4681,11 +4629,6 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } - { - name = "once_cell"; - packageId = "once_cell"; - usesDefaultFeatures = false; - } { name = "wasm-bindgen"; packageId = "wasm-bindgen"; @@ -4694,17 +4637,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 " @@ -4714,6 +4656,11 @@ rec { name = "jsonptr"; packageId = "jsonptr"; } + { + name = "schemars"; + packageId = "schemars"; + optional = true; + } { name = "serde"; packageId = "serde"; @@ -4725,10 +4672,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"; @@ -4740,7 +4691,7 @@ rec { "schemars" = [ "dep:schemars" ]; "utoipa" = [ "dep:utoipa" ]; }; - resolvedDefaultFeatures = [ "default" "diff" ]; + resolvedDefaultFeatures = [ "default" "diff" "schemars" ]; }; "jsonpath-rust" = rec { crateName = "jsonpath-rust"; @@ -4866,9 +4817,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "k8s_version"; authors = [ @@ -4886,7 +4837,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } ]; features = { @@ -5361,7 +5312,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; @@ -5518,9 +5469,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" ]; @@ -5534,10 +5485,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 = [ @@ -5595,10 +5546,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 " @@ -5675,9 +5626,9 @@ rec { }; "log" = rec { crateName = "log"; - version = "0.4.29"; + version = "0.4.32"; edition = "2021"; - sha256 = "15q8j9c8g5zpkcw0hnd6cf2z7fxqnvsjh3rw5mv5q10r83i34l2y"; + sha256 = "0fmdg0cxig7i4fwf1sw7fmg4d1gdbfzniawcfpwydy1q7320fgwm"; authors = [ "The Rust Project Developers" ]; @@ -5731,9 +5682,9 @@ rec { }; "memchr" = rec { crateName = "memchr"; - version = "2.8.0"; + version = "2.8.2"; edition = "2021"; - sha256 = "0y9zzxcqxvdqg6wyag7vc3h0blhdn7hkq164bxyx2vph8zs5ijpq"; + sha256 = "1i33wr49pcz2sbd12nds3n9fszay8kq5bk78gwciz462mcs49448"; authors = [ "Andrew Gallant " "bluss" @@ -5794,9 +5745,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 " @@ -5932,9 +5883,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 " @@ -6067,9 +6018,9 @@ rec { }; "opentelemetry" = rec { crateName = "opentelemetry"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "18629xsj4rsyiby9aj511q6wcw6s9m09gx3ymw1yjcvix1mcsjxq"; + sha256 = "10ln14d1jgc8rvw97mblc9blzcgpg1bimim4d170b7ia4mijq55h"; dependencies = [ { name = "futures-core"; @@ -6106,24 +6057,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 = [ { @@ -6166,18 +6117,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 = [ { @@ -6213,16 +6161,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 = [ { @@ -6283,10 +6231,9 @@ rec { usesDefaultFeatures = false; } { - name = "tracing"; - packageId = "tracing"; + name = "tonic-types"; + packageId = "tonic-types"; optional = true; - usesDefaultFeatures = false; } ]; devDependencies = [ @@ -6311,16 +6258,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" ]; @@ -6333,27 +6283,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 = [ { @@ -6397,30 +6347,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 = { }; @@ -6428,9 +6377,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"; @@ -6456,6 +6405,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"; @@ -6480,10 +6436,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" ]; @@ -6500,15 +6464,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"; @@ -6843,7 +6806,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; features = { @@ -6882,9 +6845,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 = [ { @@ -6896,9 +6859,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 = [ @@ -6912,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" ]; } @@ -7017,7 +6980,7 @@ rec { "default" = [ "fallback" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "require-cas" ]; + resolvedDefaultFeatures = [ "fallback" "require-cas" ]; }; "portable-atomic-util" = rec { crateName = "portable-atomic-util"; @@ -7220,9 +7183,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 " @@ -7249,9 +7212,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 = [ @@ -7279,12 +7242,40 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; }; + "prost-types" = rec { + crateName = "prost-types"; + version = "0.14.4"; + edition = "2021"; + sha256 = "02ivjvc4cwl5bfgjs3l00hwlrk74z8zlg1xcgx60bww8fvf6fjgr"; + 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"; @@ -7557,16 +7548,16 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; }; "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 " @@ -7683,9 +7674,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" @@ -7714,9 +7705,9 @@ rec { }; "reqwest" = rec { crateName = "reqwest"; - version = "0.12.28"; + version = "0.13.4"; edition = "2021"; - sha256 = "0iqidijghgqbzl3bjg5hb4zmigwa4r612bgi0yiq0c90b6jkrpgd"; + sha256 = "1hy1plns9krbh3h1dy2sdjygsfkdcnxm6pbxdi0ya9b5vq8mi711"; authors = [ "Sean McArthur " ]; @@ -7733,7 +7724,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"; @@ -7753,62 +7744,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"; @@ -7819,27 +7792,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"; @@ -7848,17 +7821,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" ]; } ]; @@ -7867,33 +7840,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" ]; } { @@ -7905,40 +7872,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" ]; @@ -8215,7 +8179,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "parsing" "extra-traits" "visit" "visit-mut" ]; } { @@ -8250,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"; @@ -8319,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 = [ { @@ -8350,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 = [ { @@ -8557,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" ]; } ]; @@ -8847,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" ]; } @@ -8879,7 +8843,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" ]; } @@ -8888,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 " @@ -9131,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 " @@ -9228,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" ]; @@ -9312,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" ]; }; @@ -9394,7 +9354,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -9402,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 = [ @@ -9430,9 +9390,9 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; - features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" ]; + features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } ]; features = { @@ -9440,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 " @@ -9540,9 +9500,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_certs"; authors = [ @@ -9600,7 +9560,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-shared"; @@ -9643,9 +9603,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_operator"; authors = [ @@ -9695,6 +9655,10 @@ rec { name = "indexmap"; packageId = "indexmap"; } + { + name = "java-properties"; + packageId = "java-properties"; + } { name = "jiff"; packageId = "jiff"; @@ -9702,6 +9666,7 @@ rec { { name = "json-patch"; packageId = "json-patch"; + features = [ "schemars" ]; } { name = "k8s-openapi"; @@ -9749,9 +9714,14 @@ rec { name = "serde_yaml"; packageId = "serde_yaml"; } + { + name = "sha2"; + packageId = "sha2"; + features = [ "oid" ]; + } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator-derive"; @@ -9805,12 +9775,21 @@ rec { packageId = "url"; features = [ "serde" ]; } + { + 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" ]; @@ -9823,9 +9802,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9847,20 +9826,20 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; }; "stackable-shared" = rec { crateName = "stackable-shared"; - version = "0.1.0"; + version = "0.1.1"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_shared"; authors = [ @@ -9904,7 +9883,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -9974,10 +9953,6 @@ rec { name = "indoc"; packageId = "indoc"; } - { - name = "product-config"; - packageId = "product-config"; - } { name = "serde"; packageId = "serde"; @@ -9989,7 +9964,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator"; @@ -10041,13 +10016,13 @@ rec { }; "stackable-telemetry" = rec { crateName = "stackable-telemetry"; - version = "0.6.3"; + version = "0.6.4"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_telemetry"; authors = [ @@ -10090,7 +10065,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"; @@ -10098,7 +10073,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -10155,9 +10130,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_versioned"; authors = [ @@ -10190,7 +10165,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-versioned-macros"; @@ -10205,9 +10180,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10262,7 +10237,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10273,9 +10248,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "7a5f0c3fbcd091340214a23f0607fcd4b4fcc152"; - sha256 = "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "bde231132cd604c12d69ffb9f3d16b6dc0e5c0c3"; + sha256 = "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr"; }; libName = "stackable_webhook"; authors = [ @@ -10347,7 +10322,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-certs"; @@ -10457,7 +10432,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "parsing" ]; } ]; @@ -10521,11 +10496,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 " ]; @@ -10597,7 +10572,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" ]; } @@ -10664,7 +10639,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10690,7 +10665,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; } ]; @@ -10714,9 +10689,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" @@ -10725,12 +10700,6 @@ rec { { name = "deranged"; packageId = "deranged"; - features = [ "powerfmt" ]; - } - { - name = "itoa"; - packageId = "itoa"; - optional = true; } { name = "num-conv"; @@ -10770,13 +10739,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" ]; @@ -10789,9 +10759,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 " @@ -10802,9 +10772,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 = [ @@ -10907,7 +10877,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "parsing" ]; } ]; @@ -10919,9 +10889,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.52.1"; + version = "1.52.3"; edition = "2021"; - sha256 = "1imw1dkkv38p66i33m5hsyk3d6prsbyrayjvqhndjvz89ybywzdn"; + sha256 = "1zpzazypkg61sw91na1m85x5s4rsjym335fwwhwm1hcs70dz1iwg"; authors = [ "Tokio Contributors " ]; @@ -11059,7 +11029,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" ]; } ]; @@ -11231,9 +11201,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"; @@ -11286,9 +11256,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 " ]; @@ -11415,9 +11385,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 " @@ -11438,6 +11408,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"; @@ -11559,9 +11556,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 " @@ -11595,11 +11592,6 @@ rec { packageId = "http-body"; optional = true; } - { - name = "iri-string"; - packageId = "iri-string"; - optional = true; - } { name = "mime"; packageId = "mime"; @@ -11629,6 +11621,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "url"; + packageId = "url"; + optional = true; + } ]; devDependencies = [ { @@ -11650,35 +11647,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" ]; @@ -11687,7 +11682,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"; @@ -11821,7 +11816,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" ]; } @@ -11894,9 +11889,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 = [ { @@ -12131,13 +12126,9 @@ rec { }; "typenum" = rec { crateName = "typenum"; - version = "1.20.0"; + version = "1.20.1"; edition = "2018"; - sha256 = "1pj35y6q11d3y55gdl6g1h2dfhmybjming0jdi9bh0bpnqm11kj0"; - authors = [ - "Paho Lurie-Gregg " - "Andre Bogus " - ]; + sha256 = "086s9ly0906kw5yw41249fba97w5zfxf03pyfwdkffvcprqfixdn"; features = { "scale-info" = [ "dep:scale-info" ]; "scale_info" = [ "scale-info/derive" ]; @@ -12170,9 +12161,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 " @@ -12298,6 +12289,66 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "uuid" = rec { + crateName = "uuid"; + version = "1.23.3"; + edition = "2021"; + sha256 = "1drddl03gi12vl1s3l2h371dw39plhn9wappp00v707g7h96nk8l"; + 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"; @@ -12365,9 +12416,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"; @@ -12385,9 +12436,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.118"; + version = "0.2.125"; edition = "2021"; - sha256 = "129s5r14fx4v4xrzpx2c6l860nkxpl48j50y7kl6j16bpah3iy8b"; + sha256 = "06nakz7nfy0ymyp7a27wfbjwx69659i12117hkgddkiv2iwkznwd"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12436,9 +12487,9 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.68"; + version = "0.4.75"; edition = "2021"; - sha256 = "1y7bq5d9fk7s9xaayx38bgs9ns35na0kpb5zw19944zvya1x6wgk"; + sha256 = "104jssshr6cm5hmkn6c66mbkyxgaaphng6c17g0dmj7jhk918fsh"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" @@ -12448,7 +12499,6 @@ rec { name = "js-sys"; packageId = "js-sys"; usesDefaultFeatures = false; - features = [ "futures" ]; } { name = "wasm-bindgen"; @@ -12465,9 +12515,9 @@ rec { }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.118"; + version = "0.2.125"; edition = "2021"; - sha256 = "1v98r8vs17cj8918qsg0xx4nlg4nxk1g0jd4nwnyrh1687w29zzf"; + sha256 = "0g9w68dwcs4ylm5kxf7schi0kjdfarhc9qlnf8arxc9zn62a28af"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12489,9 +12539,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.118"; + version = "0.2.125"; edition = "2021"; - sha256 = "0169jr0q469hfx5zqxfyywf2h2f4aj17vn4zly02nfwqmxghc24x"; + sha256 = "1gayzdx5iwl8gllh7ys79wg9cf4iyasl9hrzzhh5m4xx6nfgvkpy"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12511,7 +12561,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "visit" "visit-mut" "full" "extra-traits" ]; } { @@ -12525,10 +12575,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.118"; + version = "0.2.125"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "0ag1vvdzi4334jlzilsy14y3nyzwddf1ndn62fyhf6bg62g4vl2z"; + sha256 = "07w7fy5qa14ys3p8v2p84h98yqinw713smibz9v7apcspd29x4r3"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12543,9 +12593,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.95"; + version = "0.3.102"; edition = "2021"; - sha256 = "0zfr2jy5bpkkggl88i43yy37p538hg20i56kwn421yj9g6qznbag"; + sha256 = "0786aybrnwsgdmcynhc2k5ii291a02rq9zk054j35csyvxr0lhx6"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -12629,6 +12679,7 @@ rec { "CssStyleSheet" = [ "StyleSheet" ]; "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ]; "CssTransition" = [ "Animation" "EventTarget" ]; + "CssViewTransitionRule" = [ "CssRule" ]; "CustomEvent" = [ "Event" ]; "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ]; "DelayNode" = [ "AudioNode" "EventTarget" ]; @@ -13119,7 +13170,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -13146,7 +13197,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -13705,7 +13756,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"; @@ -13842,9 +13893,9 @@ rec { }; "winnow" = rec { crateName = "winnow"; - version = "1.0.2"; + version = "1.0.3"; edition = "2021"; - sha256 = "1l7xnfvlgy4da6gq5ip2bgcm8i9d0rwzaxg1p88nlw8lxy5p1q9f"; + sha256 = "1wajycd3krn6h699vydjv7hm0ll5l31p899qzpk59y2is74y34h5"; dependencies = [ { name = "memchr"; @@ -13956,9 +14007,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)" @@ -13967,9 +14018,9 @@ rec { }; "yoke" = rec { crateName = "yoke"; - version = "0.8.2"; + version = "0.8.3"; edition = "2021"; - sha256 = "1jprcs7a98a5whvfs6r3jvfh1nnfp6zyijl7y4ywmn88lzywbs5b"; + sha256 = "1xgyj6c2lxj2bp891ynmhws87c6z7yyv2li1v0ss9di40hxf57vh"; authors = [ "Manish Goregaokar " ]; @@ -14021,7 +14072,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "fold" ]; } { @@ -14033,9 +14084,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.48"; + version = "0.8.52"; edition = "2021"; - sha256 = "1sb8plax8jbrsng1jdval7bdhk7hhrx40dz3hwh074k6knzkgm7f"; + sha256 = "0gv563swc1yn3k8w3wjj07a8q293rkx99nfp3a25vzzmbycj446f"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -14069,9 +14120,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.48"; + version = "0.8.52"; edition = "2021"; - sha256 = "1m5s0g92cxggqc74j83k1priz24k3z93sj5gadppd20p9c4cvqvh"; + sha256 = "0c3rhsh4sd9kdym4z55zprybjkydy9y2gvw75d72aapcfa5z7rqs"; procMacro = true; libName = "zerocopy_derive"; authors = [ @@ -14089,14 +14140,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" ]; } ]; @@ -14104,11 +14155,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 = [ { @@ -14145,7 +14196,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "fold" ]; } { @@ -14157,9 +14208,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" ]; @@ -14181,9 +14232,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" @@ -14199,7 +14250,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "full" "extra-traits" "visit" ]; } ]; @@ -14312,7 +14363,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.117"; + packageId = "syn 2.0.118"; features = [ "extra-traits" ]; } ]; diff --git a/Cargo.toml b/Cargo.toml index 39e037d3..8cdf4ce8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ edition = "2024" repository = "https://github.com/stackabletech/superset-operator" [workspace.dependencies] -product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.8.0" } stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.111.1", features = ["webhook"] } anyhow = "1.0" @@ -29,6 +28,7 @@ strum = { version = "0.28", features = ["derive"] } tokio = { version = "1.40", features = ["full"] } tracing = "0.1" -[patch."https://github.com/stackabletech/operator-rs"] +[patch."https://github.com/stackabletech/operator-rs.git"] +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator" } # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } diff --git a/crate-hashes.json b/crate-hashes.json index 86f2b840..bb8b51c7 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.1#k8s-version@0.1.3": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-certs@0.4.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-operator-derive@0.3.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-operator@0.111.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-shared@0.1.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-telemetry@0.6.3": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-versioned-macros@0.10.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-versioned@0.10.0": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.1#stackable-webhook@0.9.1": "0lj969rjbxairjglrnaq0xhabvdrq5nd6wl1i0y9pr50nhh7zvgk", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "07r6kla2132l9dckn0bdxfy22n9x27xxjxmjydxcypdvwh5lk4gr", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/deploy/config-spec/properties.yaml b/deploy/config-spec/properties.yaml index e063e334..9bd8c3b2 100644 --- a/deploy/config-spec/properties.yaml +++ b/deploy/config-spec/properties.yaml @@ -2,45 +2,4 @@ version: 0.1.0 spec: units: [] -properties: - - property: &rowLimit - propertyNames: - - name: "ROW_LIMIT" - kind: - type: "file" - file: "superset_config.py" - datatype: - type: "integer" - defaultValues: - - fromVersion: "0.0.0" - value: "50000" - recommendedValues: - - fromVersion: "0.0.0" - value: "50000" - roles: - - name: "node" - required: false - asOfVersion: "0.0.0" - description: "Row limit when requesting chart data. Corresponds to ROW_LIMIT" - - property: &webserverTimeout - propertyNames: - - name: "SUPERSET_WEBSERVER_TIMEOUT" - kind: - type: "file" - file: "superset_config.py" - datatype: - type: "integer" - defaultValues: - - fromVersion: "0.0.0" - value: "60" # 1 min - recommendedValues: - # The default timeout of Superset is 60s which is way to low when querying "big data" systems. - # Especially Trino queries often take longer. - # see [the Superset docs](https://superset.apache.org/docs/frequently-asked-questions#why-are-my-queries-timing-out). - - fromVersion: "0.0.0" - value: "300" # 5 min - roles: - - name: "node" - required: true - asOfVersion: "0.0.0" - description: "Maximum number of seconds a Superset request can take before timing out. This settings effect the maximum duration a query to an underlying datasource can take. If you get timeout errors before your query returns the result you can need to increase this timeout. Corresponds to SUPERSET_WEBSERVER_TIMEOUT" +properties: [] diff --git a/deploy/helm/superset-operator/configs/properties.yaml b/deploy/helm/superset-operator/configs/properties.yaml index e063e334..9bd8c3b2 100644 --- a/deploy/helm/superset-operator/configs/properties.yaml +++ b/deploy/helm/superset-operator/configs/properties.yaml @@ -2,45 +2,4 @@ version: 0.1.0 spec: units: [] -properties: - - property: &rowLimit - propertyNames: - - name: "ROW_LIMIT" - kind: - type: "file" - file: "superset_config.py" - datatype: - type: "integer" - defaultValues: - - fromVersion: "0.0.0" - value: "50000" - recommendedValues: - - fromVersion: "0.0.0" - value: "50000" - roles: - - name: "node" - required: false - asOfVersion: "0.0.0" - description: "Row limit when requesting chart data. Corresponds to ROW_LIMIT" - - property: &webserverTimeout - propertyNames: - - name: "SUPERSET_WEBSERVER_TIMEOUT" - kind: - type: "file" - file: "superset_config.py" - datatype: - type: "integer" - defaultValues: - - fromVersion: "0.0.0" - value: "60" # 1 min - recommendedValues: - # The default timeout of Superset is 60s which is way to low when querying "big data" systems. - # Especially Trino queries often take longer. - # see [the Superset docs](https://superset.apache.org/docs/frequently-asked-questions#why-are-my-queries-timing-out). - - fromVersion: "0.0.0" - value: "300" # 5 min - roles: - - name: "node" - required: true - asOfVersion: "0.0.0" - description: "Maximum number of seconds a Superset request can take before timing out. This settings effect the maximum duration a query to an underlying datasource can take. If you get timeout errors before your query returns the result you can need to increase this timeout. Corresponds to SUPERSET_WEBSERVER_TIMEOUT" +properties: [] diff --git a/docs/modules/superset/pages/reference/commandline-parameters.adoc b/docs/modules/superset/pages/reference/commandline-parameters.adoc index 964229aa..6402afcd 100644 --- a/docs/modules/superset/pages/reference/commandline-parameters.adoc +++ b/docs/modules/superset/pages/reference/commandline-parameters.adoc @@ -2,19 +2,6 @@ This operator accepts the following command line parameters: -== product-config - -*Default value*: `/etc/stackable/superset-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values:* false - -[source] ----- -stackable-superset-operator run --product-config /foo/bar/properties.yaml ----- - == watch-namespace *Default value*: All namespaces diff --git a/docs/modules/superset/pages/reference/environment-variables.adoc b/docs/modules/superset/pages/reference/environment-variables.adoc index 7f46bf8f..d07fcd84 100644 --- a/docs/modules/superset/pages/reference/environment-variables.adoc +++ b/docs/modules/superset/pages/reference/environment-variables.adoc @@ -33,32 +33,6 @@ docker run \ oci.stackable.tech/sdp/superset-operator:0.0.0-dev ---- -== PRODUCT_CONFIG - -*Default value*: `/etc/stackable/superset-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values*: false - -[source] ----- -export PRODUCT_CONFIG=/foo/bar/properties.yaml -stackable-superset-operator run ----- - -or via docker: - ----- -docker run \ - --name superset-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/superset-operator:0.0.0-dev ----- - == WATCH_NAMESPACE *Default value*: All namespaces diff --git a/extra/crds.yaml b/extra/crds.yaml index 5add8d90..f250f312 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -336,12 +336,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -713,12 +709,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -1471,12 +1463,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -1848,12 +1836,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -2214,12 +2198,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -2591,12 +2571,8 @@ spec: superset_config.py: additionalProperties: type: string - 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 + default: {} + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index bfd455e9..f0ee44a5 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true publish = false [dependencies] -product-config.workspace = true stackable-operator.workspace = true anyhow.workspace = true diff --git a/rust/operator-binary/src/authorization/mod.rs b/rust/operator-binary/src/authorization/mod.rs deleted file mode 100644 index 932ba472..00000000 --- a/rust/operator-binary/src/authorization/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod opa; diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs deleted file mode 100644 index c662ef5c..00000000 --- a/rust/operator-binary/src/authorization/opa.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::BTreeMap; - -use stackable_operator::{client::Client, commons::opa::OpaApiVersion, shared::time::Duration}; - -use crate::crd::v1alpha1; - -pub const OPA_IMPORTS: &[&str] = - &["from opa_authorizer.opa_manager import OpaSupersetSecurityManager"]; - -pub struct SupersetOpaConfigResolved { - opa_endpoint: String, - cache_max_entries: u32, - cache_ttl: Duration, -} - -impl SupersetOpaConfigResolved { - pub async fn from_opa_config( - client: &Client, - superset: &v1alpha1::SupersetCluster, - opa_config: &v1alpha1::SupersetOpaRoleMappingConfig, - ) -> Result { - let opa_endpoint = opa_config - .opa - .full_document_url_from_config_map(client, superset, None, &OpaApiVersion::V1) - .await?; - - Ok(SupersetOpaConfigResolved { - opa_endpoint, - cache_max_entries: opa_config.cache.max_entries.to_owned(), - cache_ttl: opa_config.cache.entry_time_to_live.to_owned(), - }) - } - - // Adding necessary configurations. Imports are solved in config.rs - pub fn as_config(&self) -> BTreeMap { - BTreeMap::from([ - ( - "CUSTOM_SECURITY_MANAGER".to_string(), - "OpaSupersetSecurityManager".to_string(), - ), - ( - "AUTH_OPA_REQUEST_URL".to_string(), - self.opa_endpoint.to_owned(), - ), - ( - "AUTH_OPA_CACHE_MAX_ENTRIES".to_string(), - self.cache_max_entries.to_string(), - ), - ( - "AUTH_OPA_CACHE_TTL_IN_SEC".to_string(), - self.cache_ttl.as_secs().to_string(), - ), - ("AUTH_OPA_RULE".to_string(), "user_roles".to_string()), - ]) - } -} diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs deleted file mode 100644 index c09ab723..00000000 --- a/rust/operator-binary/src/config/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod commands; -pub mod product_logging; -pub mod superset; diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 0918caff..46fb63e9 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1,45 +1,71 @@ //! Ensures that `Pod`s are configured and running for each [`SupersetCluster`] +pub(crate) mod build; pub mod dereference; pub mod validate; -use std::sync::Arc; +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; use const_format::concatcp; -use product_config::ProductConfigManager; use snafu::{ResultExt, Snafu}; use stackable_operator::{ + builder::meta::ObjectMetaBuilder, cli::OperatorEnvironmentOptions, - cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, - commons::{random_secret_creation, rbac::build_rbac_resources}, + cluster_resources::ClusterResourceApplyStrategy, + commons::{ + product_image_selection::ResolvedProductImage, random_secret_creation, + rbac::build_rbac_resources, + }, kube::{ Resource, ResourceExt, + api::ObjectMeta, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, + kvp::Labels, logging::controller::ReconcilerError, - role_utils::RoleGroupRef, shared::time::Duration, status::condition::{ compute_conditions, deployment::DeploymentConditionBuilder, operations::ClusterOperationsConditionBuilder, statefulset::StatefulSetConditionBuilder, }, + v2::{ + HasName, HasUid, NameIsValidLabelValue, + builder::meta::ownerreference_from_resource, + cluster_resources::cluster_resources_new, + kvp::label::{recommended_labels, role_group_selector}, + product_logging::framework::{ValidatedContainerLogConfigChoice, VectorContainerLogConfig}, + role_group_utils::ResourceNames, + role_utils::{GenericCommonConfig, RoleGroupConfig}, + types::{ + kubernetes::{ListenerName, NamespaceName, Uid}, + operator::{ + ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, + RoleGroupName, RoleName, + }, + }, + }, }; use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ OPERATOR_NAME, - crd::{ - APP_NAME, INTERNAL_SECRET_SECRET_KEY, SupersetRole, - v1alpha1::{SupersetCluster, SupersetClusterStatus}, - }, - operations::pdb::add_pdbs, - resources::{ - build_recommended_labels, - configmap::build_rolegroup_config_map, - deployment::{build_beat_rolegroup_deployment, build_worker_rolegroup_deployment}, + controller::build::resource::{ + deployment::build_rolegroup_deployment, listener::build_group_listener, + pdb::build_pdb, service::{build_node_rolegroup_headless_service, build_node_rolegroup_metrics_service}, statefulset::build_server_rolegroup_statefulset, }, + crd::{ + APP_NAME, INTERNAL_SECRET_SECRET_KEY, SupersetRole, + authentication::SupersetClientAuthenticationDetailsResolved, + authorization::SupersetOpaConfigResolved, + databases::{ + CeleryBrokerConnection, CeleryResultsBackendConnection, MetadataDatabaseConnection, + }, + v1alpha1::{ + SupersetCluster, SupersetClusterStatus, SupersetConfig, SupersetConfigOverrides, + }, + }, }; pub const SUPERSET_CONTROLLER_NAME: &str = "supersetcluster"; @@ -49,10 +75,288 @@ pub const CONTAINER_IMAGE_BASE_NAME: &str = "superset"; pub struct Ctx { pub client: stackable_operator::client::Client, - pub product_config: ProductConfigManager, pub operator_environment: OperatorEnvironmentOptions, } +/// Per-role configuration extracted during validation. +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: Option, + pub listener_class: Option, + pub group_listener_name: Option, +} + +/// A validated, merged Superset role-group config. +/// +/// Aliasing the framework [`RoleGroupConfig`] keeps `replicas` optional (`Option`), so an +/// unset value is propagated all the way to the StatefulSet/Deployment `replicas` field. That lets +/// an external controller such as a HorizontalPodAutoscaler own the replica count instead of the +/// operator forcing a default. +pub type SupersetRoleGroupConfig = + RoleGroupConfig; + +/// A validated Superset config: the merged [`SupersetConfig`] together with the up-front–validated +/// [`ValidatedLogging`] (so an invalid custom log ConfigMap name or a missing Vector aggregator +/// name fails reconciliation during validation rather than at resource-build time). +#[derive(Clone, Debug)] +pub struct ValidatedSupersetConfig { + pub config: SupersetConfig, + pub logging: ValidatedLogging, +} + +impl ValidatedSupersetConfig { + /// Builds the validated config from the merged [`SupersetConfig`] and the already-validated + /// logging. + fn from_merged(config: SupersetConfig, logging: ValidatedLogging) -> Self { + Self { config, logging } + } +} + +/// Validated logging configuration for the Superset and (optional) Vector container. +/// +/// Produced up-front by `validate_logging` (mirroring the hive-operator) 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 superset_container: ValidatedContainerLogConfigChoice, + pub vector_container: Option, + pub enable_vector_agent: bool, +} + +/// Cluster-wide configuration that applies to every role and role group. +/// +/// Carries the dereferenced external references, so every downstream build step reads them from +/// here rather than from the raw cluster object. +#[derive(Clone, Debug)] +pub struct ValidatedClusterConfig { + pub authentication_config: SupersetClientAuthenticationDetailsResolved, + pub opa_config: Option, + /// Name of the Secret holding the admin user credentials. + pub credentials_secret_name: String, + /// Name of the auto-generated Secret holding the Flask `SECRET_KEY`. + pub secret_key_secret_name: String, + /// Name of the Secret holding the Mapbox API key, if configured. + pub mapbox_secret: Option, + /// Connection to the metadata database. + pub metadata_database: MetadataDatabaseConnection, + /// Connection to the Celery results backend, if configured. + pub celery_results_backend: Option, + /// Connection to the Celery broker, if configured. + pub celery_broker: Option, +} + +/// The validated cluster: proves that config merging succeeded for every role and role group +/// before any Kubernetes resources are created. +#[derive(Clone, Debug)] +pub struct ValidatedCluster { + /// `ObjectMeta` carrying `name`, `namespace` and `uid`, captured during validation, so this + /// struct can stand in as the owner [`Resource`] for child objects. + metadata: ObjectMeta, + pub name: ClusterName, + pub namespace: NamespaceName, + pub uid: Uid, + pub product_version: ProductVersion, + pub image: ResolvedProductImage, + pub cluster_config: ValidatedClusterConfig, + pub role_groups: BTreeMap>, + pub role_configs: BTreeMap, +} + +impl ValidatedCluster { + pub fn new( + name: ClusterName, + namespace: NamespaceName, + uid: Uid, + image: ResolvedProductImage, + cluster_config: ValidatedClusterConfig, + role_groups: BTreeMap>, + role_configs: BTreeMap, + ) -> Self { + let product_version = ProductVersion::from_str(&image.app_version_label_value) + .expect("the app version label value is a valid product version"); + Self { + // Capture only the identity fields needed to own child objects, derived from the + // typed cluster identity rather than the raw CRD. + metadata: ObjectMeta { + name: Some(name.to_string()), + namespace: Some(namespace.to_string()), + uid: Some(uid.to_string()), + ..ObjectMeta::default() + }, + image, + cluster_config, + role_groups, + role_configs, + name, + namespace, + uid, + product_version, + } + } + + pub fn resource_names( + &self, + role: &SupersetRole, + role_group_name: &RoleGroupName, + ) -> ResourceNames { + ResourceNames { + cluster_name: self.name.clone(), + role_name: role.role_name(), + role_group_name: role_group_name.clone(), + } + } + + pub fn recommended_labels( + &self, + role: &SupersetRole, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_for(&role.role_name(), role_group_name) + } + + pub fn recommended_labels_for( + &self, + role_name: &RoleName, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_with(&self.product_version, role_name, role_group_name) + } + + /// Recommended labels with a constant `none` version, for PVC templates that cannot be modified + /// after deployment (keeps the labels stable across version upgrades). + pub fn unversioned_recommended_labels( + &self, + role: &SupersetRole, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_with( + &ProductVersion::from_str("none").expect("'none' is a valid product version"), + &role.role_name(), + role_group_name, + ) + } + + fn recommended_labels_with( + &self, + product_version: &ProductVersion, + role_name: &RoleName, + role_group_name: &RoleGroupName, + ) -> Labels { + recommended_labels( + self, + &product_name(), + product_version, + &operator_name(), + &controller_name(), + role_name, + role_group_name, + ) + } + + pub fn role_group_selector( + &self, + role: &SupersetRole, + role_group_name: &RoleGroupName, + ) -> Labels { + role_group_selector(self, &product_name(), &role.role_name(), role_group_name) + } + + /// Returns an [`ObjectMetaBuilder`] pre-filled with the namespace, an owner reference back to + /// this cluster, and the recommended labels for a resource named `name` in `role`/ + /// `role_group_name`. + /// + /// Consolidates the metadata chain repeated by the role-group child-resource builders. Call + /// sites that need extra labels/annotations chain them onto the returned builder. + pub(crate) fn object_meta( + &self, + name: impl Into, + role: &SupersetRole, + role_group_name: &RoleGroupName, + ) -> ObjectMetaBuilder { + let mut builder = ObjectMetaBuilder::new(); + builder + .name_and_namespace(self) + .name(name) + .ownerreference(ownerreference_from_resource(self, None, Some(true))) + .with_labels(self.recommended_labels(role, role_group_name)); + builder + } +} + +/// Lets [`ValidatedCluster`] stand in for the raw [`SupersetCluster`] when building owner +/// references and metadata for child objects. Kind/group/version are delegated to the CRD; the +/// `metadata` (name, namespace, uid) is captured during validation. +impl Resource for ValidatedCluster { + type DynamicType = ::DynamicType; + type Scope = ::Scope; + + fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + SupersetCluster::kind(dt) + } + + fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + SupersetCluster::group(dt) + } + + fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + SupersetCluster::version(dt) + } + + fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + SupersetCluster::plural(dt) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} + +impl HasName for ValidatedCluster { + fn to_name(&self) -> String { + self.name_any() + } +} + +impl HasUid for ValidatedCluster { + fn to_uid(&self) -> Uid { + Uid::from_str( + &self + .metadata + .uid + .clone() + .expect("the uid is captured during validation"), + ) + .expect("the uid is a valid Kubernetes UID") + } +} + +impl NameIsValidLabelValue for ValidatedCluster { + fn to_label_value(&self) -> String { + self.name.to_label_value() + } +} + +/// The product name (`superset`) as a type-safe label value. +pub(crate) fn product_name() -> ProductName { + ProductName::from_str(APP_NAME).expect("'superset' 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(SUPERSET_CONTROLLER_NAME) + .expect("the controller name is a valid label value") +} + #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] #[allow(clippy::enum_variant_names)] @@ -63,38 +367,33 @@ pub enum Error { #[snafu(display("failed to validate cluster"))] Validate { source: validate::Error }, - #[snafu(display("failed to create cluster resources"))] - CreateClusterResources { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to delete orphaned resources"))] DeleteOrphanedResources { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to apply Service for {rolegroup}"))] + #[snafu(display("failed to apply Service for role group {role_group_name}"))] ApplyRoleGroupService { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + role_group_name: RoleGroupName, }, - #[snafu(display("failed to apply ConfigMap for {rolegroup}"))] + #[snafu(display("failed to apply ConfigMap for role group {role_group_name}"))] ApplyRoleGroupConfig { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + role_group_name: RoleGroupName, }, - #[snafu(display("failed to apply StatefulSet for {rolegroup}"))] + #[snafu(display("failed to apply StatefulSet for role group {role_group_name}"))] ApplyRoleGroupStatefulSet { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + role_group_name: RoleGroupName, }, - #[snafu(display("failed to apply Deployment for {rolegroup}"))] + #[snafu(display("failed to apply Deployment for role group {role_group_name}"))] ApplyRoleGroupDeployment { source: stackable_operator::cluster_resources::Error, - rolegroup: RoleGroupRef, + role_group_name: RoleGroupName, }, #[snafu(display("failed to update status"))] @@ -117,9 +416,9 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to create PodDisruptionBudget"))] - FailedToCreatePdb { - source: crate::operations::pdb::Error, + #[snafu(display("failed to apply PodDisruptionBudget"))] + ApplyPdb { + source: stackable_operator::cluster_resources::Error, }, #[snafu(display("failed to get required Labels"))] @@ -138,29 +437,19 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to build listener"))] - BuildListener { - source: crate::resources::listener::Error, - }, - - #[snafu(display("failed to build service"))] - BuildService { - source: crate::resources::service::Error, - }, - #[snafu(display("failed to build statefulset"))] BuildStatefulSet { - source: crate::resources::statefulset::Error, + source: crate::controller::build::resource::statefulset::Error, }, #[snafu(display("failed to build deployment"))] BuildDeployment { - source: crate::resources::deployment::Error, + source: crate::controller::build::resource::deployment::Error, }, #[snafu(display("failed to build configmap"))] BuildConfigMap { - source: crate::resources::configmap::Error, + source: crate::controller::build::resource::config_map::Error, }, #[snafu(display("failed to create SECRET_KEY secret"))] @@ -202,22 +491,19 @@ pub async fn reconcile_superset( superset, dereferenced, &ctx.operator_environment.image_repository, - &ctx.product_config, ) .context(ValidateSnafu)?; - let resolved_product_image = &validated.image; - let auth_config = &validated.authentication_config; - let superset_opa_config = &validated.opa_config; - let mut cluster_resources = ClusterResources::new( - APP_NAME, - OPERATOR_NAME, - SUPERSET_CONTROLLER_NAME, - &superset.object_ref(&()), + let mut cluster_resources = cluster_resources_new( + &product_name(), + &operator_name(), + &controller_name(), + &validated.name, + &validated.namespace, + &validated.uid, ClusterResourceApplyStrategy::from(&superset.spec.cluster_config.cluster_operation), &superset.spec.object_overrides, - ) - .context(CreateClusterResourcesSnafu)?; + ); let (rbac_sa, rbac_rolebinding) = build_rbac_resources( superset, @@ -252,61 +538,53 @@ pub async fn reconcile_superset( for (superset_role, rolegroup_configs) in validated.role_groups.iter() { for (rolegroup_name, validated_rolegroup) in rolegroup_configs.iter() { - let rolegroup = superset.rolegroup_ref(superset_role, rolegroup_name); - let rolegroup_config = &validated_rolegroup.product_config_properties; - let config = &validated_rolegroup.merged_config; - - let rg_configmap = build_rolegroup_config_map( - superset, - resolved_product_image, - &rolegroup, - rolegroup_config, - auth_config, - superset_opa_config, + let config = &validated_rolegroup.config; + + let rg_configmap = build::resource::config_map::build_rolegroup_config_map( + &validated, + superset_role, + rolegroup_name, + &config.config, + &validated_rolegroup.config_overrides, &config.logging, ) .context(BuildConfigMapSnafu)?; let rg_metrics_service = - build_node_rolegroup_metrics_service(superset, resolved_product_image, &rolegroup) - .context(BuildServiceSnafu)?; + build_node_rolegroup_metrics_service(&validated, rolegroup_name); let rg_headless_service = - build_node_rolegroup_headless_service(superset, resolved_product_image, &rolegroup) - .context(BuildServiceSnafu)?; + build_node_rolegroup_headless_service(&validated, rolegroup_name); cluster_resources .add(client, rg_metrics_service) .await .with_context(|_| ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), + role_group_name: rolegroup_name.clone(), })?; cluster_resources .add(client, rg_headless_service) .await .with_context(|_| ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), + role_group_name: rolegroup_name.clone(), })?; cluster_resources .add(client, rg_configmap) .await .with_context(|_| ApplyRoleGroupConfigSnafu { - rolegroup: rolegroup.clone(), + role_group_name: rolegroup_name.clone(), })?; match superset_role { SupersetRole::Node => { let rg_statefulset = build_server_rolegroup_statefulset( - superset, - resolved_product_image, + &validated, superset_role, - &rolegroup, - rolegroup_config, - auth_config, + rolegroup_name, + validated_rolegroup, &rbac_sa.name_any(), - config, ) .context(BuildStatefulSetSnafu)?; @@ -318,43 +596,17 @@ pub async fn reconcile_superset( .add(client, rg_statefulset.clone()) .await .with_context(|_| ApplyRoleGroupStatefulSetSnafu { - rolegroup: rolegroup.clone(), - })?, - ); - } - SupersetRole::Worker => { - let rg_worker_deployment = build_worker_rolegroup_deployment( - superset, - resolved_product_image, - superset_role, - &rolegroup, - rolegroup_config, - &rbac_sa.name_any(), - config, - ) - .context(BuildDeploymentSnafu)?; - - // Note: The Deployment needs to be applied after all ConfigMaps and Secrets it mounts - // to prevent unnecessary Pod restarts. - // See https://github.com/stackabletech/commons-operator/issues/111 for details. - deployment_cond_builder.add( - cluster_resources - .add(client, rg_worker_deployment.clone()) - .await - .with_context(|_| ApplyRoleGroupDeploymentSnafu { - rolegroup: rolegroup.clone(), + role_group_name: rolegroup_name.clone(), })?, ); } - SupersetRole::Beat => { - let rg_beat_deployment = build_beat_rolegroup_deployment( - superset, - resolved_product_image, + SupersetRole::Worker | SupersetRole::Beat => { + let rg_deployment = build_rolegroup_deployment( + &validated, superset_role, - &rolegroup, - rolegroup_config, + rolegroup_name, + validated_rolegroup, &rbac_sa.name_any(), - config, ) .context(BuildDeploymentSnafu)?; @@ -363,44 +615,42 @@ pub async fn reconcile_superset( // See https://github.com/stackabletech/commons-operator/issues/111 for details. deployment_cond_builder.add( cluster_resources - .add(client, rg_beat_deployment.clone()) + .add(client, rg_deployment.clone()) .await .with_context(|_| ApplyRoleGroupDeploymentSnafu { - rolegroup: rolegroup.clone(), + role_group_name: rolegroup_name.clone(), })?, ); } } + } - if let Some(role_config) = validated.role_configs.get(superset_role) { - if let (Some(listener_class), Some(listener_group_name)) = ( - &role_config.listener_class, - &role_config.group_listener_name, - ) { - let group_listener = build_group_listener( - superset, - build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.product_version, - &superset_role.to_string(), - "none", - ), - listener_class.clone(), - listener_group_name.clone(), - ) - .context(BuildListenerSnafu)?; - cluster_resources - .add(client, group_listener) - .await - .context(ApplyGroupListenerSnafu)?; - } + // Role-level resources (group listener, PDB) are built once per role, after + // its role groups — not once per role group. + if let Some(role_config) = validated.role_configs.get(superset_role) { + if let (Some(listener_class), Some(listener_group_name)) = ( + &role_config.listener_class, + &role_config.group_listener_name, + ) { + let group_listener = build_group_listener( + &validated, + superset_role, + listener_class.clone(), + listener_group_name.to_string(), + ); + cluster_resources + .add(client, group_listener) + .await + .context(ApplyGroupListenerSnafu)?; + } - if let Some(pdb) = &role_config.pdb { - add_pdbs(pdb, superset, superset_role, client, &mut cluster_resources) - .await - .context(FailedToCreatePdbSnafu)?; - } + if let Some(pdb) = &role_config.pdb + && let Some(pdb) = build_pdb(pdb, &validated, superset_role) + { + cluster_resources + .add(client, pdb) + .await + .context(ApplyPdbSnafu)?; } } } diff --git a/rust/operator-binary/src/config/commands.rs b/rust/operator-binary/src/controller/build/command.rs similarity index 76% rename from rust/operator-binary/src/config/commands.rs rename to rust/operator-binary/src/controller/build/command.rs index 0b1cb7cb..b896fbf1 100644 --- a/rust/operator-binary/src/config/commands.rs +++ b/rust/operator-binary/src/controller/build/command.rs @@ -1,3 +1,5 @@ +//! Shell-command snippets injected into the container start-up commands. + /// Adds a CA file from `cert_file` to the root certificates of Python Certifi pub fn add_cert_to_python_certifi_command(cert_file: &str) -> String { format!("cat {cert_file} >> \"$(python -c 'import certifi; print(certifi.where())')\"") 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/mod.rs b/rust/operator-binary/src/controller/build/mod.rs new file mode 100644 index 00000000..a648ac80 --- /dev/null +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -0,0 +1,14 @@ +//! Builders that assemble Kubernetes resources for superset rolegroups. + +use std::str::FromStr; + +use stackable_operator::v2::types::operator::RoleGroupName; + +pub mod command; +pub mod graceful_shutdown; +pub mod properties; +pub mod resource; + +// Placeholder role-group name used for the recommended labels of the role-level `Listener` +// (which is not tied to a single role group). +stackable_operator::constant!(pub(crate) PLACEHOLDER_LISTENER_ROLE_GROUP: RoleGroupName = "none"); diff --git a/rust/operator-binary/src/config/superset.rs b/rust/operator-binary/src/controller/build/properties/authentication.rs similarity index 67% rename from rust/operator-binary/src/config/superset.rs rename to rust/operator-binary/src/controller/build/properties/authentication.rs index c1097813..73ad71b6 100644 --- a/rust/operator-binary/src/config/superset.rs +++ b/rust/operator-binary/src/controller/build/properties/authentication.rs @@ -1,22 +1,17 @@ -use std::{collections::BTreeMap, io::Write}; +//! Renders the authentication-related `superset_config.py` properties (LDAP / OIDC). + +use std::collections::BTreeMap; use indoc::formatdoc; use snafu::{ResultExt, Snafu}; use stackable_operator::crd::authentication::{ldap, oidc}; -use crate::{ - crd::{ - SupersetConfigOptions, - authentication::{ - self, DEFAULT_OIDC_PROVIDER, SupersetAuthenticationClassResolved, - SupersetClientAuthenticationDetailsResolved, - }, - }, - resources::{ - celery_broker_connection_details, celery_results_backend_connection_details, - metadata_database_connection_details, +use crate::crd::{ + SupersetConfigOptions, + authentication::{ + self, DEFAULT_OIDC_PROVIDER, SupersetAuthenticationClassResolved, + SupersetClientAuthenticationDetailsResolved, }, - v1alpha1::SupersetCluster, }; #[derive(Snafu, Debug)] @@ -37,118 +32,16 @@ pub enum Error { }, } -pub const PYTHON_IMPORTS: &[&str] = &[ - "import os", - "from superset.stats_logger import StatsdStatsLogger", - "from flask_appbuilder.security.manager import (AUTH_DB, AUTH_LDAP, AUTH_OAUTH, AUTH_REMOTE_USER)", - "from log_config import StackableLoggingConfigurator", -]; - -pub fn add_superset_config( - config: &mut BTreeMap, - superset: &SupersetCluster, - authentication_config: &SupersetClientAuthenticationDetailsResolved, -) -> Result<(), Error> { - let metadata_database_url_template = - metadata_database_connection_details(superset).url_template; - - config.insert( - SupersetConfigOptions::SecretKey.to_string(), - "os.environ.get('SECRET_KEY')".to_owned(), - ); - config.insert( - SupersetConfigOptions::SqlalchemyDatabaseUri.to_string(), - format!("os.path.expandvars('{metadata_database_url_template}')"), - ); - config.insert( - SupersetConfigOptions::StatsLogger.to_string(), - "StatsdStatsLogger(host='0.0.0.0', port=9125)".to_owned(), - ); - config.insert( - SupersetConfigOptions::MapboxApiKey.to_string(), - "os.environ.get('MAPBOX_API_KEY', '')".to_owned(), - ); - config.insert( - SupersetConfigOptions::LoggingConfigurator.to_string(), - "StackableLoggingConfigurator()".to_owned(), - ); - // Flask AppBuilder requires this to be set, otherwise the web ui cannot be used. - // We chose to make it an expression in case the user wants to override it through - // configurationOverrides (though it would require other settings like the private key too). - config.insert( - SupersetConfigOptions::RecaptchaPublicKey.to_string(), - "''".to_owned(), - ); - - append_authentication_config(config, authentication_config)?; - Ok(()) -} - -pub(crate) fn append_celery_connection_config( - config_file: &mut Vec, - superset: &SupersetCluster, -) { - let ( - Some(additional_celery_results_backend_connection_details), - Some(celery_results_backend_connection_details), - ) = celery_results_backend_connection_details(superset) - else { - return; - }; - - let Some(celery_broker_connection_details) = celery_broker_connection_details(superset) else { - return; - }; - - let result_backend_username_env = celery_results_backend_connection_details - .username_env - .map(|env| env.name) - .unwrap_or("".to_string()); - let result_backend_password_env = celery_results_backend_connection_details - .password_env - .map(|env| env.name) - .unwrap_or("".to_string()); - let result_backend_url_template = celery_results_backend_connection_details.url_template; - let result_backend_host = additional_celery_results_backend_connection_details.host; - let result_backend_port = additional_celery_results_backend_connection_details.port; - let result_backend_db = additional_celery_results_backend_connection_details.database_id; - let broker_url_template = celery_broker_connection_details.url_template; - - let celery_config = formatdoc!( - r#" - # CELERY ASYNC - from flask_caching.backends.rediscache import RedisCache - RESULTS_BACKEND = RedisCache(host='{result_backend_host}', port={result_backend_port}, db={result_backend_db}, key_prefix='superset_results', username=os.path.expandvars('${{{result_backend_username_env}}}'), password=os.path.expandvars('${{{result_backend_password_env}}}')) - class CeleryConfig(object): - broker_url = os.path.expandvars('{broker_url_template}') - imports = ( - "superset.sql_lab", - "superset.tasks.scheduler", - ) - result_backend = os.path.expandvars('{result_backend_url_template}') - worker_prefetch_multiplier = 10 - task_acks_late = True - task_annotations = {{ - "sql_lab.get_sql_results": {{ - "rate_limit": "100/s", - }}, - }} - - CELERY_CONFIG = CeleryConfig - "#, - ); - - writeln!(config_file, "{celery_config}").expect("Writing to vec always works."); -} - -fn append_authentication_config( - config: &mut BTreeMap, +/// Renders the authentication key/value properties for `superset_config.py`. +pub fn authentication_properties( auth_config: &SupersetClientAuthenticationDetailsResolved, -) -> Result<(), Error> { +) -> Result, Error> { // SupersetClientAuthenticationDetailsResolved ensures that there // are either only LDAP or OIDC providers configured. It is not // necessary to check this here again. + let mut config = BTreeMap::new(); + let ldap_providers = auth_config .authentication_classes_resolved .iter() @@ -178,11 +71,11 @@ fn append_authentication_config( .collect::>(); if let Some(ldap_provider) = ldap_providers.first() { - append_ldap_config(config, ldap_provider)?; + config.extend(ldap_properties(ldap_provider)?); } if !oidc_providers.is_empty() { - append_oidc_config(config, &oidc_providers)?; + config.extend(oidc_properties(&oidc_providers)?); } config.insert( @@ -199,13 +92,14 @@ fn append_authentication_config( .to_string(), ); - Ok(()) + Ok(config) } -fn append_ldap_config( - config: &mut BTreeMap, +fn ldap_properties( ldap: &ldap::v1alpha1::AuthenticationProvider, -) -> Result<(), Error> { +) -> Result, Error> { + let mut config = BTreeMap::new(); + config.insert( SupersetConfigOptions::AuthType.to_string(), "AUTH_LDAP".into(), @@ -273,18 +167,19 @@ fn append_ldap_config( ); } - Ok(()) + Ok(config) } -fn append_oidc_config( - config: &mut BTreeMap, +fn oidc_properties( providers: &[( &oidc::v1alpha1::AuthenticationProvider, &oidc::v1alpha1::ClientAuthenticationOptions< oidc::v1alpha1::ClientAuthenticationMethodOption, >, )], -) -> Result<(), Error> { +) -> Result, Error> { + let mut config = BTreeMap::new(); + config.insert( SupersetConfigOptions::AuthType.to_string(), "AUTH_OAUTH".into(), @@ -358,7 +253,7 @@ fn append_oidc_config( ), ); - Ok(()) + Ok(config) } #[cfg(test)] @@ -394,14 +289,13 @@ mod tests { "https://keycloak.mycorp.org/realms/sdp/protocol/", "https://keycloak.mycorp.org/realms/sdp/.well-known/openid-configuration" )] - fn test_append_oidc_config( + fn test_oidc_properties( #[case] root_path: String, #[case] expected_api_base_url: &str, #[case] expected_server_metadata_url: &str, ) { use stackable_operator::commons::tls_verification::{CaCert, Tls, TlsServerVerification}; - let mut properties = BTreeMap::new(); let provider = oidc::v1alpha1::AuthenticationProvider::new( "keycloak.mycorp.org".to_owned().try_into().unwrap(), Some(443), @@ -425,8 +319,7 @@ mod tests { }, }; - append_oidc_config(&mut properties, &[(&provider, &oidc)]) - .expect("OIDC config adding failed"); + let properties = oidc_properties(&[(&provider, &oidc)]).expect("OIDC config adding failed"); assert_eq!(properties.get("AUTH_TYPE"), Some(&"AUTH_OAUTH".to_owned())); let oauth_providers = properties diff --git a/rust/operator-binary/src/controller/build/properties/authorization.rs b/rust/operator-binary/src/controller/build/properties/authorization.rs new file mode 100644 index 00000000..487d6509 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/authorization.rs @@ -0,0 +1,33 @@ +//! Renders the OPA authorization properties (and required Python imports) for +//! `superset_config.py`. + +use std::collections::BTreeMap; + +use crate::crd::authorization::SupersetOpaConfigResolved; + +/// Python imports injected into `superset_config.py` when OPA authorization is enabled. +pub const OPA_IMPORTS: &[&str] = + &["from opa_authorizer.opa_manager import OpaSupersetSecurityManager"]; + +/// Renders the OPA-related key/value properties for `superset_config.py`. +pub fn opa_properties(opa_config: &SupersetOpaConfigResolved) -> BTreeMap { + BTreeMap::from([ + ( + "CUSTOM_SECURITY_MANAGER".to_string(), + "OpaSupersetSecurityManager".to_string(), + ), + ( + "AUTH_OPA_REQUEST_URL".to_string(), + opa_config.opa_endpoint.to_owned(), + ), + ( + "AUTH_OPA_CACHE_MAX_ENTRIES".to_string(), + opa_config.cache_max_entries.to_string(), + ), + ( + "AUTH_OPA_CACHE_TTL_IN_SEC".to_string(), + opa_config.cache_ttl.as_secs().to_string(), + ), + ("AUTH_OPA_RULE".to_string(), "user_roles".to_string()), + ]) +} 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 00000000..01eb409e --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -0,0 +1,33 @@ +//! Per-file builders for the Superset config files. + +pub mod authentication; +pub mod authorization; +pub mod product_logging; +pub mod superset_config; + +/// The names of the Superset config files assembled into the rolegroup `ConfigMap`. +/// +/// This is the single source of truth for the on-disk file names; nothing else should +/// hard-code them (the Vector agent config is the exception — its name comes from the +/// upstream `product_logging::framework::VECTOR_CONFIG_FILE` constant). +#[derive(Clone, Copy, Debug, strum::Display)] +pub enum ConfigFileName { + #[strum(serialize = "superset_config.py")] + SupersetConfig, + #[strum(serialize = "log_config.py")] + LogConfig, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn file_names_match_the_superset_on_disk_names() { + assert_eq!( + ConfigFileName::SupersetConfig.to_string(), + "superset_config.py" + ); + assert_eq!(ConfigFileName::LogConfig.to_string(), "log_config.py"); + } +} diff --git a/rust/operator-binary/src/config/product_logging.rs b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs similarity index 50% rename from rust/operator-binary/src/config/product_logging.rs rename to rust/operator-binary/src/controller/build/properties/product_logging/mod.rs index 2995e56b..d40521bc 100644 --- a/rust/operator-binary/src/config/product_logging.rs +++ b/rust/operator-binary/src/controller/build/properties/product_logging/mod.rs @@ -1,63 +1,53 @@ -use std::fmt::{Display, Write}; +//! Renders the logging config files (`log_config.py` and the Vector agent config) +//! assembled into the rolegroup `ConfigMap`. + +use std::fmt::Write; use indoc::formatdoc; use stackable_operator::{ - builder::configmap::ConfigMapBuilder, - kube::Resource, - product_logging::{ - self, - spec::{ - AutomaticContainerLogConfig, ContainerLogConfig, ContainerLogConfigChoice, Logging, - }, - }, - role_utils::RoleGroupRef, + product_logging::spec::AutomaticContainerLogConfig, + v2::product_logging::framework::{STACKABLE_LOG_DIR, ValidatedContainerLogConfigChoice}, }; -use crate::crd::STACKABLE_LOG_DIR; - -pub const LOG_CONFIG_FILE: &str = "log_config.py"; +use crate::crd::v1alpha1::Container; +/// The rotating log file the generated `log_config.py` writes to (consumed by the Vector agent). const LOG_FILE: &str = "superset.py.json"; -/// Extend the ConfigMap with logging and Vector configurations -pub fn extend_config_map_with_log_config( - rolegroup: &RoleGroupRef, - logging: &Logging, - main_container: &C, - vector_container: &C, - cm_builder: &mut ConfigMapBuilder, -) where - C: Clone + Ord + Display, - K: Resource, -{ - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(main_container) - { - let log_dir = format!("{STACKABLE_LOG_DIR}/{main_container}"); - cm_builder.add_data( - LOG_CONFIG_FILE, - create_superset_config(log_config, &log_dir), - ); - } +/// The Vector agent configuration (`vector.yaml`). +const VECTOR_CONFIG: &str = include_str!("vector.yaml"); - let vector_log_config = if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(vector_container) - { - Some(log_config) - } else { - None - }; - - if logging.enable_vector_agent { - cm_builder.add_data( - product_logging::framework::VECTOR_CONFIG_FILE, - product_logging::framework::create_vector_config(rolegroup, vector_log_config), - ); +/// Returns the Vector agent config (`vector.yaml`) content. +pub fn vector_config_file_content() -> String { + VECTOR_CONFIG.to_owned() +} + +/// Renders `log_config.py` for the Superset container. +/// +/// Returns `None` when the Superset container uses a custom log ConfigMap instead of the operator's +/// automatic logging configuration, in which case no `log_config.py` should be added to the +/// rolegroup `ConfigMap`. +pub fn build_log_config(log_config: &ValidatedContainerLogConfigChoice) -> Option { + match log_config { + ValidatedContainerLogConfigChoice::Automatic(log_config) => { + let log_dir = format!( + "{STACKABLE_LOG_DIR}/{container}", + container = Container::Superset + ); + Some(create_superset_config(log_config, &log_dir)) + } + ValidatedContainerLogConfigChoice::Custom(_) => None, } } +/// Renders the Vector agent config (`vector.yaml`). +/// +/// Returns `None` when the Vector agent is disabled for this role group. The returned config is the +/// vendored, env-var-parameterized `vector.yaml`. +pub fn build_vector_config(enable_vector_agent: bool) -> Option { + enable_vector_agent.then(vector_config_file_content) +} + fn create_superset_config(log_config: &AutomaticContainerLogConfig, log_dir: &str) -> String { let mut loggers_config = String::new(); log_config @@ -144,3 +134,36 @@ fn create_superset_config(log_config: &AutomaticContainerLogConfig, log_dir: &st .to_python_expression(), ) } + +#[cfg(test)] +mod tests { + use super::*; + + /// The vendored `vector.yaml` keeps only the sources Superset produces and drops the ones it + /// does not (log4j/log4j2/airlift/opa/tracing). Guards against accidental drift. + #[test] + fn test_vector_config_file_content() { + let content = vector_config_file_content(); + assert!(!content.is_empty()); + // Superset logs JSON to `superset.py.json`, so the Python-JSON source must be present. + assert!(content.contains("files_py")); + assert!(content.contains("*.py.json")); + // Sources Superset does not emit must have been trimmed out. + for dropped in [ + "files_log4j", + "files_log4j2", + "files_airlift", + "files_opa_json", + "files_tracing_rs", + ] { + assert!( + !content.contains(dropped), + "vendored vector.yaml should not contain the dropped source {dropped}" + ); + } + // The config is env-var-parameterized (resolved at runtime by the Vector container), not + // baked, so the role-group identity must appear as placeholders. + assert!(content.contains("${ROLE_NAME}")); + assert!(content.contains("${VECTOR_AGGREGATOR_ADDRESS}")); + } +} 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 00000000..90ea59c0 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/test-vector.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# `TZ=UTC` so the Python-JSON `asctime` (which has no timezone) parses deterministically. +TZ=UTC \ +DATA_DIR=/stackable/log/_vector-state \ +LOG_DIR=/stackable/log \ +NAMESPACE=default \ +CLUSTER_NAME=superset \ +ROLE_NAME=node \ +ROLE_GROUP_NAME=default \ +VECTOR_AGGREGATOR_ADDRESS=vector-aggregator \ +VECTOR_FILE_LOG_LEVEL=info \ +vector test vector.yaml vector-test.yaml diff --git a/rust/operator-binary/src/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 00000000..84a04f98 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml @@ -0,0 +1,157 @@ +# 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. 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/superset/superset.stdout.log + message: Starting gunicorn + pod: superset-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:28.582Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "superset", + "file": "superset.stdout.log", + "level": "INFO", + "logger": "ROOT", + "message": "Starting gunicorn", + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:27:28.582Z" + } + + assert_eq!(expected_log_event, .) + - name: Test stderr log entry + inputs: + - type: log + insert_at: processed_files_stderr + log_fields: + file: /stackable/log/superset/superset.stderr.log + message: "Traceback (most recent call last):" + pod: superset-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:28.582Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "superset", + "file": "superset.stderr.log", + "level": "ERROR", + "logger": "ROOT", + "message": "Traceback (most recent call last):", + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:27:28.582Z" + } + + assert_eq!(expected_log_event, .) + - name: Test Python JSON log entry + inputs: + - type: log + insert_at: processed_files_py + log_fields: + file: /stackable/log/superset/superset.py.json + message: '{"asctime": "2025-10-02 09:27:28,582", "levelname": "INFO", "name": "superset.app", "message": "Loading app"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2025-10-02T09:27:29.473487331Z + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "superset", + "file": "superset.py.json", + "level": "INFO", + "logger": "superset.app", + "message": "Loading app", + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": t'2025-10-02T09:27:28.582Z' + } + + assert_eq!(expected_log_event, .) + - name: Test Vector internal logs + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + arch: x86_64 + message: Vector has started. + metadata: + kind: event + level: INFO + module_path: vector::internal_events::process + target: vector + pid: 14 + pod: superset-node-default-0 + source_type: internal_logs + timestamp: 2025-10-02T09:46:14.479381097Z + version: 0.56.0 + outputs: + - extract_from: extended_logs + conditions: + - type: vrl + source: | + expected_log_event = { + "arch": "x86_64", + "cluster": "superset", + "container": "vector", + "level": "INFO", + "logger": "vector::internal_events::process", + "message": "Vector has started.", + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default", + "timestamp": "2025-10-02T09:46:14.479381097Z", + "version": "0.56.0" + } + + assert_eq!(expected_log_event, .) + - name: Test Vector internal log level filtering - INFO passes + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + metadata: + level: INFO + outputs: + - extract_from: filtered_logs_vector + conditions: + - type: vrl + source: | + assert_eq!("INFO", .metadata.level) + - name: Test Vector internal log level filtering - DEBUG dropped + inputs: + - type: log + insert_at: filtered_logs_vector + log_fields: + metadata: + level: DEBUG + no_outputs_from: + - filtered_logs_vector diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml new file mode 100644 index 00000000..18d0cd4b --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml @@ -0,0 +1,166 @@ +--- +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 + + files_py: + type: file + include: + - ${LOG_DIR}/*/*.py.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_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.") + } + } + + # 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} diff --git a/rust/operator-binary/src/controller/build/properties/superset_config.rs b/rust/operator-binary/src/controller/build/properties/superset_config.rs new file mode 100644 index 00000000..627f1000 --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/superset_config.rs @@ -0,0 +1,271 @@ +//! Builds the `superset_config.py` configuration file from the resolved +//! authentication/OPA config plus the per-rolegroup config-file properties. + +use std::{collections::BTreeMap, io::Write}; + +use indoc::formatdoc; +use snafu::{ResultExt, Snafu}; +use stackable_operator::v2::flask_config_writer; + +use crate::{ + controller::{ + ValidatedCluster, + build::{ + properties::{authentication, authorization}, + resource::{ + celery_broker_connection_details, celery_results_backend_connection_details, + metadata_database_connection_details, + }, + }, + }, + crd::{ + SupersetConfigOptions, SupersetRole, + databases::{ + CeleryBrokerConnection, CeleryResultsBackendConnection, MetadataDatabaseConnection, + }, + v1alpha1::{SupersetConfig, SupersetConfigOverrides}, + }, +}; + +/// Marks arbitrary Python code to prepend verbatim to the generated file. +const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; +/// Marks arbitrary Python code to append verbatim to the generated file. +const CONFIG_OVERRIDE_FILE_FOOTER_KEY: &str = "FILE_FOOTER"; + +/// Operator default for `SUPERSET_WEBSERVER_TIMEOUT` (seconds), applied to the `Node` role. +/// Superset's own 60s default is too low for "big data" queries. +pub const DEFAULT_WEBSERVER_TIMEOUT: u32 = 300; +/// Operator default for `ROW_LIMIT`, applied to the `Node` role. +pub const DEFAULT_ROW_LIMIT: i32 = 50000; + +const PYTHON_IMPORTS: &[&str] = &[ + "import os", + "from superset.stats_logger import StatsdStatsLogger", + "from flask_appbuilder.security.manager import (AUTH_DB, AUTH_LDAP, AUTH_OAUTH, AUTH_REMOTE_USER)", + "from log_config import StackableLoggingConfigurator", +]; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to add authentication config settings"))] + AddAuthenticationConfig { source: authentication::Error }, + + #[snafu(display("failed to write the superset config file"))] + WriteConfigFile { + source: flask_config_writer::FlaskAppConfigWriterError, + }, + + #[snafu(display("failed to write the header/footer to the superset config file"))] + WriteHeaderFooter { source: std::io::Error }, +} + +/// Renders the `superset_config.py` contents: operator defaults (derived from the +/// resolved authentication/OPA config) with the per-rolegroup config-file properties +/// applied last, wrapped by the optional `FILE_HEADER`/`FILE_FOOTER` Python blocks. +pub fn build( + validated: &ValidatedCluster, + role: &SupersetRole, + merged_config: &SupersetConfig, + config_overrides: &SupersetConfigOverrides, +) -> Result { + let mut config_properties = BTreeMap::new(); + let mut imports = PYTHON_IMPORTS.to_vec(); + // TODO: this is true per default for versions 3.0.0 and up. + // We deactivate it here to keep existing functionality. + // However this is a security issue and should be configured properly + // Issue: https://github.com/stackabletech/superset-operator/issues/416 + config_properties.insert("TALISMAN_ENABLED".to_string(), "False".to_string()); + + config_properties.extend(core_config_properties( + &validated.cluster_config.metadata_database, + &validated.cluster_config.authentication_config, + )?); + + // Adding opa configuration properties to config_properties. + // This will be injected as key/value pair in superset_config.py + if let Some(opa_config) = &validated.cluster_config.opa_config { + // If opa role mapping is configured, insert CustomOpaSecurityManager import + imports.extend(authorization::OPA_IMPORTS); + + config_properties.extend(authorization::opa_properties(opa_config)); + } + + // The order here should be kept in order to preserve overrides. + // No properties should be added after this extend. + config_properties.extend(rolegroup_properties(role, merged_config, config_overrides)); + + let mut config_file = Vec::new(); + + // By removing the keys from `config_properties`, we avoid pasting the Python code into a Python variable as well + // (which would be bad) + if let Some(header) = config_properties.remove(CONFIG_OVERRIDE_FILE_HEADER_KEY) { + writeln!(config_file, "{header}").context(WriteHeaderFooterSnafu)?; + } + let temp_file_footer = config_properties.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); + + flask_config_writer::write::( + &mut config_file, + config_properties.iter(), + &imports, + ) + .context(WriteConfigFileSnafu)?; + + // We have to add a python class (no key) and cannot use the superset::config machinery. + if let Some(celery_config) = celery_connection_config( + validated.cluster_config.celery_results_backend.as_ref(), + validated.cluster_config.celery_broker.as_ref(), + ) { + writeln!(config_file, "{celery_config}").expect("Writing to vec always works."); + } + + if let Some(footer) = temp_file_footer { + writeln!(config_file, "{footer}").context(WriteHeaderFooterSnafu)?; + } + + Ok(String::from_utf8(config_file).expect("the Flask config writer only emits valid UTF-8")) +} + +/// Renders the core operator-managed `superset_config.py` properties (database connection, +/// logging configurator, recaptcha, …) including the resolved authentication properties. +fn core_config_properties( + metadata_database: &MetadataDatabaseConnection, + authentication_config: &crate::crd::authentication::SupersetClientAuthenticationDetailsResolved, +) -> Result, Error> { + let metadata_database_url_template = + metadata_database_connection_details(metadata_database).url_template; + + let mut config = BTreeMap::new(); + + config.insert( + SupersetConfigOptions::SecretKey.to_string(), + "os.environ.get('SECRET_KEY')".to_owned(), + ); + config.insert( + SupersetConfigOptions::SqlalchemyDatabaseUri.to_string(), + format!("os.path.expandvars('{metadata_database_url_template}')"), + ); + config.insert( + SupersetConfigOptions::StatsLogger.to_string(), + "StatsdStatsLogger(host='0.0.0.0', port=9125)".to_owned(), + ); + config.insert( + SupersetConfigOptions::MapboxApiKey.to_string(), + "os.environ.get('MAPBOX_API_KEY', '')".to_owned(), + ); + config.insert( + SupersetConfigOptions::LoggingConfigurator.to_string(), + "StackableLoggingConfigurator()".to_owned(), + ); + // Flask AppBuilder requires this to be set, otherwise the web ui cannot be used. + // We chose to make it an expression in case the user wants to override it through + // configurationOverrides (though it would require other settings like the private key too). + config.insert( + SupersetConfigOptions::RecaptchaPublicKey.to_string(), + "''".to_owned(), + ); + + config.extend( + authentication::authentication_properties(authentication_config) + .context(AddAuthenticationConfigSnafu)?, + ); + + Ok(config) +} + +/// Renders the Celery async config (a bare Python class, hence rendered separately from the +/// key/value properties). Returns `None` when no Celery results backend or broker is configured. +fn celery_connection_config( + celery_results_backend: Option<&CeleryResultsBackendConnection>, + celery_broker: Option<&CeleryBrokerConnection>, +) -> Option { + let ( + Some(additional_celery_results_backend_connection_details), + Some(celery_results_backend_connection_details), + ) = celery_results_backend_connection_details(celery_results_backend) + else { + return None; + }; + + let celery_broker_connection_details = celery_broker_connection_details(celery_broker)?; + + let result_backend_username_env = celery_results_backend_connection_details + .username_env + .map(|env| env.name) + .unwrap_or("".to_string()); + let result_backend_password_env = celery_results_backend_connection_details + .password_env + .map(|env| env.name) + .unwrap_or("".to_string()); + let result_backend_url_template = celery_results_backend_connection_details.url_template; + let result_backend_host = additional_celery_results_backend_connection_details.host; + let result_backend_port = additional_celery_results_backend_connection_details.port; + let result_backend_db = additional_celery_results_backend_connection_details.database_id; + let broker_url_template = celery_broker_connection_details.url_template; + + Some(formatdoc!( + r#" + # CELERY ASYNC + from flask_caching.backends.rediscache import RedisCache + RESULTS_BACKEND = RedisCache(host='{result_backend_host}', port={result_backend_port}, db={result_backend_db}, key_prefix='superset_results', username=os.path.expandvars('${{{result_backend_username_env}}}'), password=os.path.expandvars('${{{result_backend_password_env}}}')) + class CeleryConfig(object): + broker_url = os.path.expandvars('{broker_url_template}') + imports = ( + "superset.sql_lab", + "superset.tasks.scheduler", + ) + result_backend = os.path.expandvars('{result_backend_url_template}') + worker_prefetch_multiplier = 10 + task_acks_late = True + task_annotations = {{ + "sql_lab.get_sql_results": {{ + "rate_limit": "100/s", + }}, + }} + + CELERY_CONFIG = CeleryConfig + "#, + )) +} + +/// Assembles the product-specific `superset_config.py` key/value properties for a role group. +/// +/// Layered in precedence order (each step may override the previous one): +/// 1. Operator recommended values — `Node` role only +/// role-scoping): `ROW_LIMIT` and `SUPERSET_WEBSERVER_TIMEOUT` (Superset's 60s default is too +/// low for "big data" queries). +/// 2. Config-derived values (all roles) — user-set typed CRD fields override the recommended +/// values above. +/// 3. User `configOverrides` — plain string key/values, already merged role<-role-group +/// (role-group wins) by `with_validated_config`. +fn rolegroup_properties( + role: &SupersetRole, + merged_config: &SupersetConfig, + config_overrides: &SupersetConfigOverrides, +) -> BTreeMap { + let mut properties: BTreeMap = BTreeMap::new(); + + if *role == SupersetRole::Node { + properties.insert( + SupersetConfigOptions::RowLimit.to_string(), + DEFAULT_ROW_LIMIT.to_string(), + ); + properties.insert( + SupersetConfigOptions::SupersetWebserverTimeout.to_string(), + DEFAULT_WEBSERVER_TIMEOUT.to_string(), + ); + } + + if let Some(v) = merged_config.row_limit { + properties.insert(SupersetConfigOptions::RowLimit.to_string(), v.to_string()); + } + if let Some(v) = merged_config.webserver_timeout { + properties.insert( + SupersetConfigOptions::SupersetWebserverTimeout.to_string(), + v.to_string(), + ); + } + + properties.extend(config_overrides.superset_config_py.overrides.clone()); + + properties +} diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs new file mode 100644 index 00000000..4b28d430 --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -0,0 +1,163 @@ +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::configmap::ConfigMapBuilder, k8s_openapi::api::core::v1::ConfigMap, + product_logging::framework::VECTOR_CONFIG_FILE, v2::types::operator::RoleGroupName, +}; + +use crate::{ + controller::{ + ValidatedCluster, ValidatedLogging, + build::properties::{ConfigFileName, product_logging, superset_config}, + }, + crd::{ + SupersetRole, + v1alpha1::{SupersetConfig, SupersetConfigOverrides}, + }, +}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build {config_file} for role group {role_group_name}", config_file = ConfigFileName::SupersetConfig))] + SupersetConfig { + source: superset_config::Error, + role_group_name: RoleGroupName, + }, + + #[snafu(display("failed to build ConfigMap for role group {role_group_name}"))] + RoleGroupConfig { + source: stackable_operator::builder::configmap::Error, + role_group_name: RoleGroupName, + }, +} + +type Result = std::result::Result; + +/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator +pub fn build_rolegroup_config_map( + validated: &ValidatedCluster, + role: &SupersetRole, + role_group_name: &RoleGroupName, + merged_config: &SupersetConfig, + config_overrides: &SupersetConfigOverrides, + logging: &ValidatedLogging, +) -> Result { + let config_file = superset_config::build(validated, role, merged_config, config_overrides) + .with_context(|_| SupersetConfigSnafu { + role_group_name: role_group_name.clone(), + })?; + + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder + .metadata( + validated + .object_meta( + validated + .resource_names(role, role_group_name) + .role_group_config_map() + .to_string(), + role, + role_group_name, + ) + .build(), + ) + .add_data(ConfigFileName::SupersetConfig.to_string(), config_file); + + if let Some(log_config) = product_logging::build_log_config(&logging.superset_container) { + cm_builder.add_data(ConfigFileName::LogConfig.to_string(), log_config); + } + if let Some(vector_config) = product_logging::build_vector_config(logging.enable_vector_agent) { + cm_builder.add_data(VECTOR_CONFIG_FILE, vector_config); + } + + cm_builder.build().with_context(|_| RoleGroupConfigSnafu { + role_group_name: role_group_name.clone(), + }) +} + +#[cfg(test)] +mod tests { + use stackable_operator::utils::yaml_from_str_singleton_map; + + use super::*; + use crate::{ + controller::{dereference::DereferencedObjects, validate::validate_cluster}, + crd::{ + authentication::{ + SupersetClientAuthenticationDetailsResolved, v1alpha1::FlaskRolesSyncMoment, + }, + v1alpha1, + }, + }; + + fn default_dereferenced() -> DereferencedObjects { + DereferencedObjects { + authentication_config: SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: vec![], + user_registration: true, + user_registration_role: "Public".to_string(), + sync_roles_at: FlaskRolesSyncMoment::default(), + }, + opa_config: None, + } + } + + /// The rolegroup ConfigMap carries `superset_config.py` and (for automatic logging) + /// `log_config.py`, and omits `vector.yaml` while the Vector agent is disabled (the default). + #[test] + fn build_rolegroup_config_map_renders_expected_data() { + let input = r#" + apiVersion: superset.stackable.tech/v1alpha1 + kind: SupersetCluster + metadata: + name: simple-superset + namespace: default + uid: 01234567-89ab-cdef-0123-456789abcdef + spec: + image: + productVersion: 4.1.4 + clusterConfig: + credentialsSecretName: superset-admin-credentials + metadataDatabase: + postgresql: + host: superset-postgresql + database: superset + credentialsSecretName: superset-postgresql-credentials + nodes: + roleGroups: + default: + replicas: 1 + "#; + let superset: v1alpha1::SupersetCluster = + yaml_from_str_singleton_map(input).expect("illegal test input"); + let validated = + validate_cluster(&superset, default_dereferenced(), "test-repo").expect("validated"); + + let role_group_name: RoleGroupName = "default".parse().expect("valid role group name"); + let rolegroup_config = validated + .role_groups + .get(&SupersetRole::Node) + .and_then(|groups| groups.get(&role_group_name)) + .expect("node default rolegroup"); + + let config_map = build_rolegroup_config_map( + &validated, + &SupersetRole::Node, + &role_group_name, + &rolegroup_config.config.config, + &rolegroup_config.config_overrides, + &rolegroup_config.config.logging, + ) + .expect("config map built"); + + let data = config_map.data.expect("config map has data"); + assert!(data.contains_key("superset_config.py")); + assert!(data.contains_key("log_config.py")); + // The Vector agent is disabled by default, so no `vector.yaml` is rendered. + assert!(!data.contains_key("vector.yaml")); + + let superset_config = &data["superset_config.py"]; + assert!(superset_config.contains("SQLALCHEMY_DATABASE_URI")); + assert!(superset_config.contains("StatsdStatsLogger")); + } +} diff --git a/rust/operator-binary/src/controller/build/resource/deployment.rs b/rust/operator-binary/src/controller/build/resource/deployment.rs new file mode 100644 index 00000000..2143604f --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/deployment.rs @@ -0,0 +1,240 @@ +use indoc::formatdoc; +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{PodBuilder, security::PodSecurityContextBuilder}, + }, + k8s_openapi::{ + DeepMerge, + api::{ + apps::v1::{Deployment, DeploymentSpec}, + core::v1::{ExecAction, Probe}, + }, + apimachinery::pkg::apis::meta::v1::LabelSelector, + }, + product_logging::framework::{ + create_vector_shutdown_file_command, remove_vector_shutdown_file_command, + }, + utils::COMMON_BASH_TRAP_FUNCTIONS, + v2::{product_logging::framework::STACKABLE_LOG_DIR, types::operator::RoleGroupName}, +}; + +use crate::{ + controller::{ + SupersetRoleGroupConfig, ValidatedCluster, + build::{graceful_shutdown::add_graceful_shutdown_config, properties::ConfigFileName}, + }, + crd::{PYTHONPATH, STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR, SupersetRole}, +}; + +/// PID file written by the Celery `beat` process; its liveness probe checks the same path, so both +/// must agree. +const CELERY_BEAT_PIDFILE: &str = "/tmp/celerybeat.pid"; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build container"))] + BuildContainer { source: super::Error }, + + #[snafu(display("failed to configure graceful shutdown"))] + GracefulShutdown { + source: crate::controller::build::graceful_shutdown::Error, + }, + + #[snafu(display("failed to build Labels"))] + LabelBuild { + source: stackable_operator::kvp::LabelError, + }, + + #[snafu(display("failed to build Metadata"))] + MetadataBuild { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to 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, + }, +} + +type Result = std::result::Result; + +/// The rolegroup [`Deployment`] runs a Celery `worker` or `beat` rolegroup, as configured by the +/// administrator. The `Node` role is deployed as a +/// [`StatefulSet`](super::statefulset::build_server_rolegroup_statefulset) instead. +pub fn build_rolegroup_deployment( + validated: &ValidatedCluster, + superset_role: &SupersetRole, + role_group_name: &RoleGroupName, + rolegroup_config: &SupersetRoleGroupConfig, + sa_name: &str, +) -> Result { + let resolved_product_image = &validated.image; + let merged_config = &rolegroup_config.config.config; + + let resource_names = validated.resource_names(superset_role, role_group_name); + let recommended_object_labels = validated.recommended_labels(superset_role, role_group_name); + + // The Celery process command, liveness probe and replica policy are the only differences + // between the `worker` and `beat` rolegroups. + let (celery_command, liveness_probe, replicas) = match superset_role { + SupersetRole::Worker => ( + "celery --app=superset.tasks.celery_app:app worker --task-events".to_string(), + worker_liveness_probe(), + rolegroup_config.replicas, + ), + SupersetRole::Beat => { + // Beat must only ever run a single instance, so the replica count is never left + // unset (no HorizontalPodAutoscaler): an explicit `0` is honoured (to stop Beat), + // any value `> 1` is clamped to `1`, and an unset value defaults to `1`. + let replicas = match rolegroup_config.replicas { + Some(0) => Some(0), + Some(replicas) if replicas > 1 => { + tracing::warn! {"replicas for role `beat` set to greater `1`. Multiple beat instances are not allowed. Setting to `1` replica."} + Some(1) + } + _ => Some(1), + }; + ( + format!( + "celery --app=superset.tasks.celery_app:app beat --pidfile {CELERY_BEAT_PIDFILE}" + ), + beat_liveness_probe(), + replicas, + ) + } + SupersetRole::Node => { + unreachable!("the `Node` role is deployed as a StatefulSet, not a Deployment") + } + }; + + let metadata = ObjectMetaBuilder::new() + .with_labels(recommended_object_labels) + .build(); + + let mut pb = PodBuilder::new(); + pb.metadata(metadata) + .image_pull_secrets_from_product_image(resolved_product_image) + .security_context( + PodSecurityContextBuilder::new() + .fs_group(super::SECRET_OPERATOR_FS_GROUP) // Needed for secret-operator + .build(), + ) + .affinity(&merged_config.affinity) + .service_account_name(sa_name); + + let mut superset_cb = super::build_superset_container_builder(validated, rolegroup_config) + .context(BuildContainerSnafu)?; + + superset_cb + .command(super::bash_wrapper_command()) + .args(vec![formatdoc! {" + {COMMON_BASH_TRAP_FUNCTIONS} + + mkdir --parents {PYTHONPATH} + cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} + cp {STACKABLE_LOG_CONFIG_DIR}/{log_config_file} {PYTHONPATH} + + {remove_vector_shutdown_file_command} + prepare_signal_handlers + containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & + + {celery_command} & + + wait_for_termination $! + {create_vector_shutdown_file_command} + ", + log_config_file = ConfigFileName::LogConfig, + remove_vector_shutdown_file_command = + remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), + create_vector_shutdown_file_command = + create_vector_shutdown_file_command(STACKABLE_LOG_DIR), + }]) + .liveness_probe(liveness_probe) + .resources(merged_config.resources.clone().into()); + + pb.add_container(superset_cb.build()); + add_graceful_shutdown_config(merged_config, &mut pb).context(GracefulShutdownSnafu)?; + + pb.add_volumes(super::create_volumes( + resource_names.role_group_config_map().as_ref(), + &rolegroup_config.config.logging.superset_container, + )) + .context(AddVolumeSnafu)?; + pb.add_container(super::build_metrics_container(resolved_product_image)); + + if let Some(vector_container) = + super::build_vector_container(validated, superset_role, role_group_name, rolegroup_config) + { + pb.add_container(vector_container); + } + + let mut pod_template = pb.build_template(); + pod_template.merge_from(rolegroup_config.pod_overrides.clone()); + + Ok(Deployment { + // `ResourceNames` has no Deployment helper; the qualified name (which is also the + // StatefulSet name) is the correct `--` Deployment name. + metadata: validated + .object_meta( + resource_names.stateful_set_name().to_string(), + superset_role, + role_group_name, + ) + .with_label(super::restarter_enabled_label().context(LabelBuildSnafu)?) + .build(), + spec: Some(DeploymentSpec { + replicas: replicas.map(i32::from), + selector: LabelSelector { + match_labels: Some( + validated + .role_group_selector(superset_role, role_group_name) + .into(), + ), + ..LabelSelector::default() + }, + template: pod_template, + ..DeploymentSpec::default() + }), + status: None, + }) +} + +/// Liveness probe for the Celery `worker` process. +fn worker_liveness_probe() -> Probe { + Probe { + exec: Some(ExecAction { + command: Some(vec![ + "celery --app=superset.tasks.celery_app:app inspect ping -d celery@$HOSTNAME" + .to_string(), + ]), + }), + initial_delay_seconds: Some(30), + period_seconds: Some(30), + timeout_seconds: Some(30), + failure_threshold: Some(3), + ..Default::default() + } +} + +/// Liveness probe for the Celery `beat` process. +fn beat_liveness_probe() -> Probe { + Probe { + exec: Some(ExecAction { + command: Some(vec![format!( + "[ -f {CELERY_BEAT_PIDFILE} ] && kill -0 $(cat {CELERY_BEAT_PIDFILE})" + )]), + }), + initial_delay_seconds: Some(30), + period_seconds: Some(30), + timeout_seconds: Some(30), + failure_threshold: Some(3), + ..Default::default() + } +} diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs new file mode 100644 index 00000000..0e1b753b --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -0,0 +1,42 @@ +use stackable_operator::crd::listener; + +use crate::{ + controller::{ValidatedCluster, build::PLACEHOLDER_LISTENER_ROLE_GROUP}, + crd::{APP_PORT, APP_PORT_NAME, SupersetRole}, +}; + +pub const LISTENER_VOLUME_NAME: &str = "listener"; +pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; + +pub fn build_group_listener( + validated: &ValidatedCluster, + role: &SupersetRole, + listener_class: String, + listener_group_name: String, +) -> listener::v1alpha1::Listener { + // The group listener is a role-level object, so the constant `none` placeholder role-group is + // used for the recommended labels. + let metadata = validated + .object_meta(listener_group_name, role, &PLACEHOLDER_LISTENER_ROLE_GROUP) + .build(); + + let spec = listener::v1alpha1::ListenerSpec { + class_name: Some(listener_class), + ports: Some(listener_ports()), + ..Default::default() + }; + + listener::v1alpha1::Listener { + metadata, + spec, + status: None, + } +} + +pub fn listener_ports() -> Vec { + vec![listener::v1alpha1::ListenerPort { + name: APP_PORT_NAME.to_owned(), + port: APP_PORT.into(), + protocol: Some("TCP".to_owned()), + }] +} 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 00000000..c07f474d --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -0,0 +1,305 @@ +use std::str::FromStr; + +use indoc::formatdoc; +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::pod::{ + container::ContainerBuilder, resources::ResourceRequirementsBuilder, volume::VolumeBuilder, + }, + commons::product_image_selection::ResolvedProductImage, + database_connections::{ + TemplatingMechanism, + drivers::{ + celery::CeleryDatabaseConnectionDetails, + sqlalchemy::SqlAlchemyDatabaseConnectionDetails, + }, + }, + k8s_openapi::api::core::v1::{ + ConfigMapVolumeSource, Container as K8sContainer, EmptyDirVolumeSource, Volume, + }, + kvp::Label, + product_logging, + utils::COMMON_BASH_TRAP_FUNCTIONS, + v2::{ + builder::pod::container::{EnvVarSet, new_container_builder}, + product_logging::framework::{ + STACKABLE_LOG_DIR, ValidatedContainerLogConfigChoice, vector_container, + }, + types::{ + kubernetes::{ContainerName, PersistentVolumeClaimName, VolumeName}, + operator::RoleGroupName, + }, + }, +}; + +use crate::{ + controller::{SupersetRoleGroupConfig, ValidatedCluster}, + crd::{ + APP_PORT, APP_PORT_NAME, MAX_LOG_FILES_SIZE, METRICS_PORT, METRICS_PORT_NAME, + STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR, SupersetRole, + databases::{ + CeleryBrokerConnection, CeleryResultsBackendConnection, + CeleryResultsBackendConnectionDetails, MetadataDatabaseConnection, + }, + }, +}; + +pub mod config_map; +pub mod deployment; +pub mod listener; +pub mod pdb; +pub mod service; +pub mod statefulset; + +pub const CONFIG_VOLUME_NAME: &str = "config"; +pub const LOG_CONFIG_VOLUME_NAME: &str = "log-config"; +pub const LOG_VOLUME_NAME: &str = "log"; + +/// Directory the `SSL_CERT_DIR` env var points the Superset container at for trusted CA certs. +const STACKABLE_CERTS_DIR: &str = "/stackable/certs/"; +/// Path of the statsd-exporter binary launched by the `metrics` sidecar. +const STATSD_EXPORTER_BINARY: &str = "/stackable/statsd_exporter"; + +stackable_operator::constant!(SUPERSET_CONTAINER_NAME: ContainerName = "superset"); +stackable_operator::constant!(METRICS_CONTAINER_NAME: ContainerName = "metrics"); +stackable_operator::constant!(VECTOR_CONTAINER_NAME: ContainerName = "vector"); + +// Typed volume-name constants required by the v2 `vector_container` (which mounts the Vector +// config and log volumes). Their values match the `&str` constants used by `create_volumes`. +stackable_operator::constant!(CONFIG_VOLUME_NAME_TYPED: VolumeName = "config"); +stackable_operator::constant!(LOG_VOLUME_NAME_TYPED: VolumeName = "log"); + +// PVC name for the listener volume, required by the v2 listener-volume builder. Its value matches +// `LISTENER_VOLUME_NAME` in `resource::listener`. +stackable_operator::constant!(pub(crate) LISTENER_VOLUME_NAME_PVC: PersistentVolumeClaimName = "listener"); + +/// The `fsGroup` the Pods run as, required by secret-operator-provided volumes. +pub(crate) const SECRET_OPERATOR_FS_GROUP: i64 = 1000; + +/// The shell wrapper used to launch the long-running product containers +/// (`/bin/bash -x -euo pipefail -c `). +pub(crate) fn bash_wrapper_command() -> Vec { + vec![ + "/bin/bash".to_string(), + "-x".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ] +} + +/// The label that opts a workload into the Stackable restart controller (so it restarts on mounted +/// ConfigMap/Secret changes). +pub(crate) fn restarter_enabled_label() -> Result { + Label::try_from(("restarter.stackable.tech/enabled", "true")) +} + +/// Errors shared by the container builders below. +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to add needed volumeMount"))] + AddVolumeMount { + source: stackable_operator::builder::pod::container::Error, + }, +} + +pub(crate) fn create_volumes( + config_map_name: &str, + log_config: &ValidatedContainerLogConfigChoice, +) -> Vec { + let mut volumes = Vec::new(); + + volumes.push( + VolumeBuilder::new(CONFIG_VOLUME_NAME) + .with_config_map(config_map_name) + .build(), + ); + volumes.push(Volume { + name: LOG_VOLUME_NAME.into(), + empty_dir: Some(EmptyDirVolumeSource { + medium: None, + size_limit: Some(product_logging::framework::calculate_log_volume_size_limit( + &[MAX_LOG_FILES_SIZE], + )), + }), + ..Volume::default() + }); + + // A custom log config references its own ConfigMap; automatic logging uses the rolegroup + // ConfigMap (which carries the operator-generated `log_config.py`). + let log_config_map = match log_config { + ValidatedContainerLogConfigChoice::Custom(custom_config_map) => { + custom_config_map.to_string() + } + ValidatedContainerLogConfigChoice::Automatic(_) => config_map_name.to_owned(), + }; + volumes.push(Volume { + name: LOG_CONFIG_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: log_config_map, + ..ConfigMapVolumeSource::default() + }), + ..Volume::default() + }); + + volumes +} + +/// Builds the `superset` main container builder with the configuration shared by every role: +/// database/celery connection details, env overrides, the optional Mapbox key, the Flask +/// `SECRET_KEY`, the product image, the HTTP port, the config/log volume mounts, the +/// admin-credential env vars and the `containerdebug`/SSL env vars. +/// +/// The returned builder is finished by the caller with the role-specific command, args and probes +/// (and, for the `Node` role, the authentication volumes/mounts and listener volume mount). +pub(crate) fn build_superset_container_builder( + validated: &ValidatedCluster, + rolegroup_config: &SupersetRoleGroupConfig, +) -> Result { + let mut superset_cb = new_container_builder(&SUPERSET_CONTAINER_NAME); + + metadata_database_connection_details(&validated.cluster_config.metadata_database) + .add_to_container(&mut superset_cb); + let celery_results_backend_connection_details = celery_results_backend_connection_details( + validated.cluster_config.celery_results_backend.as_ref(), + ); + if let (_, Some(celery_results_backend_connection_details)) = + &celery_results_backend_connection_details + { + celery_results_backend_connection_details.add_to_container(&mut superset_cb); + } + if let Some(celery_broker_connection_details) = + celery_broker_connection_details(validated.cluster_config.celery_broker.as_ref()) + { + celery_broker_connection_details.add_to_container(&mut superset_cb); + } + + superset_cb.add_env_vars(rolegroup_config.env_overrides.clone()); + if let Some(mapbox_secret) = &validated.cluster_config.mapbox_secret { + superset_cb.add_env_var_from_secret( + "MAPBOX_API_KEY", + mapbox_secret, + "connections.mapboxApiKey", + ); + } + + // SECRET_KEY from auto-generated secret + superset_cb.add_env_var_from_secret( + "SECRET_KEY", + validated.cluster_config.secret_key_secret_name.clone(), + crate::crd::INTERNAL_SECRET_SECRET_KEY, + ); + + let secret = &validated.cluster_config.credentials_secret_name; + superset_cb + .image_from_product_image(&validated.image) + .add_container_port(APP_PORT_NAME, APP_PORT.into()) + .add_volume_mount(CONFIG_VOLUME_NAME, STACKABLE_CONFIG_DIR) + .context(AddVolumeMountSnafu)? + .add_volume_mount(LOG_CONFIG_VOLUME_NAME, STACKABLE_LOG_CONFIG_DIR) + .context(AddVolumeMountSnafu)? + .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) + .context(AddVolumeMountSnafu)? + .add_env_var_from_secret("ADMIN_USERNAME", secret, "adminUser.username") + .add_env_var_from_secret("ADMIN_FIRSTNAME", secret, "adminUser.firstname") + .add_env_var_from_secret("ADMIN_LASTNAME", secret, "adminUser.lastname") + .add_env_var_from_secret("ADMIN_EMAIL", secret, "adminUser.email") + .add_env_var_from_secret("ADMIN_PASSWORD", secret, "adminUser.password") + // Needed by the `containerdebug` process to log it's tracing information to. + .add_env_var( + "CONTAINERDEBUG_LOG_DIRECTORY", + format!("{STACKABLE_LOG_DIR}/containerdebug"), + ) + .add_env_var("SSL_CERT_DIR", STACKABLE_CERTS_DIR); + + Ok(superset_cb) +} + +/// Builds the `metrics` (statsd exporter) sidecar container, shared by the StatefulSet and +/// Deployment rolegroup builders. +pub(crate) fn build_metrics_container( + resolved_product_image: &ResolvedProductImage, +) -> K8sContainer { + new_container_builder(&METRICS_CONTAINER_NAME) + .image_from_product_image(resolved_product_image) + .command(bash_wrapper_command()) + .args(vec![formatdoc! {" + {COMMON_BASH_TRAP_FUNCTIONS} + prepare_signal_handlers + {STATSD_EXPORTER_BINARY} & + wait_for_termination $! + "}]) + .add_container_port(METRICS_PORT_NAME, METRICS_PORT.into()) + .resources( + ResourceRequirementsBuilder::new() + .with_cpu_request("100m") + .with_cpu_limit("200m") + .with_memory_request("64Mi") + .with_memory_limit("64Mi") + .build(), + ) + .build() +} + +/// Builds the Vector agent sidecar container for the rolegroup, or `None` if vector logging is +/// disabled. Shared by the StatefulSet and Deployment rolegroup builders. +pub(crate) fn build_vector_container( + validated: &ValidatedCluster, + superset_role: &SupersetRole, + role_group_name: &RoleGroupName, + rolegroup_config: &SupersetRoleGroupConfig, +) -> Option { + rolegroup_config + .config + .logging + .vector_container + .as_ref() + .map(|vector_log_config| { + vector_container( + &VECTOR_CONTAINER_NAME, + &validated.image, + vector_log_config, + &validated.resource_names(superset_role, role_group_name), + &CONFIG_VOLUME_NAME_TYPED, + &LOG_VOLUME_NAME_TYPED, + EnvVarSet::new(), + ) + }) +} + +pub(crate) fn metadata_database_connection_details( + metadata_database: &MetadataDatabaseConnection, +) -> SqlAlchemyDatabaseConnectionDetails { + metadata_database.sqlalchemy_connection_details_with_templating( + crate::crd::METADATA_DATABASE_ENV_PREFIX, + &TemplatingMechanism::BashEnvSubstitution, + ) +} + +pub(crate) fn celery_results_backend_connection_details( + celery_results_backend: Option<&CeleryResultsBackendConnection>, +) -> ( + Option, + Option, +) { + ( + celery_results_backend.map(|backend| backend.as_python_parameters()), + celery_results_backend.map(|backend| { + backend.celery_connection_details_with_templating( + "CELERY_RESULTS_BACKEND", + &TemplatingMechanism::BashEnvSubstitution, + ) + }), + ) +} + +pub(crate) fn celery_broker_connection_details( + celery_broker: Option<&CeleryBrokerConnection>, +) -> Option { + celery_broker.map(|broker| { + broker.celery_connection_details_with_templating( + "CELERY_BROKER", + &TemplatingMechanism::BashEnvSubstitution, + ) + }) +} diff --git a/rust/operator-binary/src/controller/build/resource/pdb.rs b/rust/operator-binary/src/controller/build/resource/pdb.rs new file mode 100644 index 00000000..002e73ca --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/pdb.rs @@ -0,0 +1,48 @@ +use stackable_operator::{ + commons::pdb::PdbConfig, k8s_openapi::api::policy::v1::PodDisruptionBudget, + v2::builder::pdb::pod_disruption_budget_builder_with_role, +}; + +use crate::{ + controller::{ValidatedCluster, controller_name, operator_name, product_name}, + crd::SupersetRole, +}; + +/// Builds the [`PodDisruptionBudget`] for the given `role`, or `None` if PDBs are disabled. +pub fn build_pdb( + pdb: &PdbConfig, + validated: &ValidatedCluster, + role: &SupersetRole, +) -> Option { + if !pdb.enabled { + return None; + } + let max_unavailable = pdb.max_unavailable.unwrap_or(match role { + SupersetRole::Node => max_unavailable_nodes(), + SupersetRole::Worker => max_unavailable_workers(), + SupersetRole::Beat => max_unavailable_beat(), + }); + let pdb = pod_disruption_budget_builder_with_role( + validated, + &product_name(), + &role.role_name(), + &operator_name(), + &controller_name(), + ) + .with_max_unavailable(max_unavailable) + .build(); + + Some(pdb) +} + +fn max_unavailable_nodes() -> u16 { + 1 +} + +fn max_unavailable_workers() -> u16 { + 1 +} + +fn max_unavailable_beat() -> u16 { + 1 +} diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs new file mode 100644 index 00000000..4575cff2 --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -0,0 +1,115 @@ +use stackable_operator::{ + k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, + kvp::{Annotations, Labels}, + v2::types::operator::RoleGroupName, +}; + +use crate::{ + controller::ValidatedCluster, + crd::{APP_PORT, APP_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME, SupersetRole}, +}; + +/// The rolegroup [`Service`] is a headless 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_node_rolegroup_headless_service( + validated: &ValidatedCluster, + role_group_name: &RoleGroupName, +) -> Service { + Service { + metadata: validated + .object_meta( + validated + .resource_names(&SupersetRole::Node, role_group_name) + .headless_service_name() + .to_string(), + &SupersetRole::Node, + role_group_name, + ) + .build(), + spec: Some(ServiceSpec { + // Internal communication does not need to be exposed + type_: Some("ClusterIP".to_owned()), + cluster_ip: Some("None".to_owned()), + ports: Some(service_ports()), + selector: Some( + validated + .role_group_selector(&SupersetRole::Node, role_group_name) + .into(), + ), + publish_not_ready_addresses: Some(true), + ..ServiceSpec::default() + }), + status: None, + } +} + +/// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label +pub fn build_node_rolegroup_metrics_service( + validated: &ValidatedCluster, + role_group_name: &RoleGroupName, +) -> Service { + let resource_names = validated.resource_names(&SupersetRole::Node, role_group_name); + Service { + metadata: validated + .object_meta( + resource_names.metrics_service_name().to_string(), + &SupersetRole::Node, + role_group_name, + ) + .with_labels(prometheus_labels()) + .with_annotations(prometheus_annotations()) + .build(), + spec: Some(ServiceSpec { + // Internal communication does not need to be exposed + type_: Some("ClusterIP".to_owned()), + cluster_ip: Some("None".to_owned()), + ports: Some(metrics_ports()), + selector: Some( + validated + .role_group_selector(&SupersetRole::Node, role_group_name) + .into(), + ), + publish_not_ready_addresses: Some(true), + ..ServiceSpec::default() + }), + status: None, + } +} + +fn metrics_ports() -> Vec { + vec![ServicePort { + name: Some(METRICS_PORT_NAME.to_string()), + port: METRICS_PORT.into(), + protocol: Some("TCP".to_string()), + ..ServicePort::default() + }] +} + +fn service_ports() -> Vec { + vec![ServicePort { + name: Some(APP_PORT_NAME.to_string()), + port: APP_PORT.into(), + protocol: Some("TCP".to_string()), + ..ServicePort::default() + }] +} +/// Common labels for Prometheus +fn prometheus_labels() -> Labels { + Labels::try_from([("prometheus.io/scrape", "true")]).expect("should be a valid label") +} + +/// Common annotations for Prometheus +/// +/// These annotations can be used in a ServiceMonitor. +/// +/// see also +fn prometheus_annotations() -> Annotations { + Annotations::try_from([ + ("prometheus.io/path".to_owned(), "/metrics".to_owned()), + ("prometheus.io/port".to_owned(), METRICS_PORT.to_string()), + ("prometheus.io/scheme".to_owned(), "http".to_owned()), + ("prometheus.io/scrape".to_owned(), "true".to_owned()), + ]) + .expect("should be valid annotations") +} 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 00000000..4e1fe633 --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -0,0 +1,387 @@ +use std::collections::BTreeSet; + +use indoc::formatdoc; +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::{ + meta::ObjectMetaBuilder, + pod::{ + PodBuilder, container::ContainerBuilder, probe::ProbeBuilder, + security::PodSecurityContextBuilder, + }, + }, + k8s_openapi::{ + DeepMerge, + api::{ + apps::v1::{StatefulSet, StatefulSetSpec}, + core::v1::{EnvVar, Probe}, + }, + apimachinery::pkg::apis::meta::v1::LabelSelector, + }, + product_logging::framework::{ + create_vector_shutdown_file_command, remove_vector_shutdown_file_command, + }, + shared::time::Duration, + utils::COMMON_BASH_TRAP_FUNCTIONS, + v2::{ + builder::pod::volume::{ + ListenerReference, listener_operator_volume_source_builder_build_pvc, + }, + product_logging::framework::STACKABLE_LOG_DIR, + types::operator::RoleGroupName, + }, +}; + +use crate::{ + controller::{ + SupersetRoleGroupConfig, ValidatedCluster, + build::{ + command::add_cert_to_python_certifi_command, + graceful_shutdown::add_graceful_shutdown_config, + properties::{ConfigFileName, superset_config::DEFAULT_WEBSERVER_TIMEOUT}, + resource::listener::{LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME}, + }, + }, + crd::{ + APP_PORT, PYTHONPATH, STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR, SupersetRole, + authentication::{ + SupersetAuthenticationClassResolved, SupersetClientAuthenticationDetailsResolved, + }, + }, +}; + +/// `StatefulSet` pod management policy: start Pods one after another so the init commands don't run +/// in parallel. +const POD_MANAGEMENT_POLICY_ORDERED_READY: &str = "OrderedReady"; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build container"))] + BuildContainer { source: super::Error }, + + #[snafu(display("failed to configure graceful shutdown"))] + GracefulShutdown { + source: crate::controller::build::graceful_shutdown::Error, + }, + + #[snafu(display("failed to build Labels"))] + LabelBuild { + source: stackable_operator::kvp::LabelError, + }, + + #[snafu(display("failed to build Metadata"))] + MetadataBuild { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to add LDAP Volumes and VolumeMounts"))] + AddLdapVolumesAndVolumeMounts { + source: stackable_operator::crd::authentication::ldap::v1alpha1::Error, + }, + + #[snafu(display("failed to add TLS Volumes and VolumeMounts"))] + AddTlsVolumesAndVolumeMounts { + source: stackable_operator::commons::tls_verification::TlsClientDetailsError, + }, + + #[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, + }, +} + +type Result = std::result::Result; + +/// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator. +pub fn build_server_rolegroup_statefulset( + validated: &ValidatedCluster, + superset_role: &SupersetRole, + role_group_name: &RoleGroupName, + rolegroup_config: &SupersetRoleGroupConfig, + sa_name: &str, +) -> Result { + let merged_config = &rolegroup_config.config.config; + + let resource_names = validated.resource_names(superset_role, role_group_name); + let recommended_object_labels = validated.recommended_labels(superset_role, role_group_name); + // Used for PVC templates that cannot be modified once they are deployed (a constant "none" + // version keeps the labels stable across version upgrades). + let unversioned_recommended_labels = + validated.unversioned_recommended_labels(superset_role, role_group_name); + + let metadata = ObjectMetaBuilder::new() + .with_labels(recommended_object_labels) + .build(); + + let mut pb = PodBuilder::new(); + pb.metadata(metadata) + .image_pull_secrets_from_product_image(&validated.image) + .security_context( + PodSecurityContextBuilder::new() + .fs_group(super::SECRET_OPERATOR_FS_GROUP) // Needed for secret-operator + .build(), + ) + .affinity(&merged_config.affinity) + .service_account_name(sa_name); + + let mut superset_cb = super::build_superset_container_builder(validated, rolegroup_config) + .context(BuildContainerSnafu)?; + + // The `Node` role serves the Superset web UI, so it additionally mounts the authentication + // volumes and sets the authentication env vars. These mounts are added after the common config + // volume mounts (volume mount order is not significant). + add_authentication_volumes_and_volume_mounts( + &validated.cluster_config.authentication_config, + &mut superset_cb, + &mut pb, + )?; + + // The gunicorn worker timeout mirrors the `SUPERSET_WEBSERVER_TIMEOUT` written into + // `superset_config.py` (see `controller::build::properties::superset_config`). + let webserver_timeout = merged_config + .webserver_timeout + .unwrap_or(DEFAULT_WEBSERVER_TIMEOUT); + + superset_cb + .add_env_vars(authentication_env_vars(&validated.cluster_config.authentication_config)) + .command(super::bash_wrapper_command()) + .args(vec![formatdoc! {" + {COMMON_BASH_TRAP_FUNCTIONS} + + mkdir --parents {PYTHONPATH} + cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} + cp {STACKABLE_LOG_CONFIG_DIR}/{log_config_file} {PYTHONPATH} + + {auth_commands} + + superset db upgrade + set +x + echo 'Running \"superset fab create-admin [...]\", which is not shown as it leaks the Superset admin credentials' + superset fab create-admin --username \"$ADMIN_USERNAME\" --firstname \"$ADMIN_FIRSTNAME\" --lastname \"$ADMIN_LASTNAME\" --email \"$ADMIN_EMAIL\" --password \"$ADMIN_PASSWORD\" + set -x + superset init + + {remove_vector_shutdown_file_command} + prepare_signal_handlers + containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & + gunicorn --bind 0.0.0.0:${{SUPERSET_PORT}} --worker-class gthread --threads 20 --timeout {webserver_timeout} --limit-request-line 0 --limit-request-field_size 0 'superset.app:create_app()' & + wait_for_termination $! + + {create_vector_shutdown_file_command} + ", + log_config_file = ConfigFileName::LogConfig, + auth_commands = authentication_start_commands(&validated.cluster_config.authentication_config), + remove_vector_shutdown_file_command = + remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), + create_vector_shutdown_file_command = + create_vector_shutdown_file_command(STACKABLE_LOG_DIR), + }]) + .resources(merged_config.resources.clone().into()); + let (startup_probe, readiness_probe, liveness_probe) = superset_container_probes(); + superset_cb + .startup_probe(startup_probe) + .readiness_probe(readiness_probe) + .liveness_probe(liveness_probe); + + // listener endpoints will use persistent volumes + // so that load balancers can hard-code the target addresses and + // that it is possible to connect to a consistent address + let pvcs = if let Some(group_listener_name) = validated + .role_configs + .get(superset_role) + .and_then(|role_config| role_config.group_listener_name.clone()) + { + let pvc = listener_operator_volume_source_builder_build_pvc( + &ListenerReference::Listener(group_listener_name), + &unversioned_recommended_labels, + &super::LISTENER_VOLUME_NAME_PVC, + ); + Some(vec![pvc]) + } else { + None + }; + + superset_cb + .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) + .context(AddVolumeMountSnafu)?; + + pb.add_container(superset_cb.build()); + add_graceful_shutdown_config(merged_config, &mut pb).context(GracefulShutdownSnafu)?; + + pb.add_volumes(super::create_volumes( + resource_names.role_group_config_map().as_ref(), + &rolegroup_config.config.logging.superset_container, + )) + .context(AddVolumeSnafu)?; + pb.add_container(super::build_metrics_container(&validated.image)); + + if let Some(vector_container) = + super::build_vector_container(validated, superset_role, role_group_name, rolegroup_config) + { + pb.add_container(vector_container); + } + + let mut pod_template = pb.build_template(); + pod_template.merge_from(rolegroup_config.pod_overrides.clone()); + + Ok(StatefulSet { + metadata: validated + .object_meta( + resource_names.stateful_set_name().to_string(), + superset_role, + role_group_name, + ) + .with_label(super::restarter_enabled_label().context(LabelBuildSnafu)?) + .build(), + spec: Some(StatefulSetSpec { + pod_management_policy: Some(POD_MANAGEMENT_POLICY_ORDERED_READY.to_string()), + replicas: rolegroup_config.replicas.map(i32::from), + selector: LabelSelector { + match_labels: Some( + validated + .role_group_selector(superset_role, role_group_name) + .into(), + ), + ..LabelSelector::default() + }, + service_name: Some(resource_names.headless_service_name().to_string()), + template: pod_template, + volume_claim_templates: pvcs, + ..StatefulSetSpec::default() + }), + status: None, + }) +} + +/// Builds the startup, readiness and liveness probes for the `superset` container, all derived from +/// a common HTTP `/health` check. Returned (rather than applied to a builder) so the caller owns the +/// container assembly. +fn superset_container_probes() -> (Probe, Probe, Probe) { + let common = + ProbeBuilder::http_get_port_scheme_path(APP_PORT.0, None, Some("/health".to_owned())) + .with_period(Duration::from_secs(5)); + + let startup_probe = common + .clone() + .with_failure_threshold_duration(Duration::from_minutes_unchecked(10)) + .expect("const period is non-zero") + .build() + .expect("const duration does not overflow"); + + // Remove it from the Service immediately + let readiness_probe = common + .clone() + .build() + .expect("const duration does not overflow"); + + // But only restart it after 3 failures + let liveness_probe = common + .with_failure_threshold(3) + .build() + .expect("const duration does not overflow"); + + (startup_probe, readiness_probe, liveness_probe) +} + +fn add_authentication_volumes_and_volume_mounts( + auth_config: &SupersetClientAuthenticationDetailsResolved, + cb: &mut ContainerBuilder, + pb: &mut PodBuilder, +) -> Result<()> { + // Different authentication entries can reference the same secret + // class or TLS certificate. It must be ensured that the volumes + // and volume mounts are only added once in such a case. + + let mut ldap_authentication_providers = BTreeSet::new(); + let mut tls_client_credentials = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Ldap { provider } => { + ldap_authentication_providers.insert(provider); + } + SupersetAuthenticationClassResolved::Oidc { provider, .. } => { + tls_client_credentials.insert(&provider.tls); + } + } + } + + for provider in ldap_authentication_providers { + provider + .add_volumes_and_mounts(pb, vec![cb]) + .context(AddLdapVolumesAndVolumeMountsSnafu)?; + } + + for tls in tls_client_credentials { + tls.add_volumes_and_mounts(pb, vec![cb]) + .context(AddTlsVolumesAndVolumeMountsSnafu)?; + } + + Ok(()) +} + +fn authentication_env_vars( + auth_config: &SupersetClientAuthenticationDetailsResolved, +) -> Vec { + // Different OIDC authentication entries can reference the same + // client secret. It must be ensured that the env variables are only + // added once in such a case. + + let mut oidc_client_credentials_secrets = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Ldap { .. } => {} + SupersetAuthenticationClassResolved::Oidc { + client_auth_options: oidc, + .. + } => { + oidc_client_credentials_secrets + .insert(oidc.client_credentials_secret_ref.to_owned()); + } + } + } + + oidc_client_credentials_secrets + .iter() + .cloned() + .flat_map(stackable_operator::crd::authentication::oidc::v1alpha1::AuthenticationProvider::client_credentials_env_var_mounts) + .collect() +} + +fn authentication_start_commands( + auth_config: &SupersetClientAuthenticationDetailsResolved, +) -> String { + let mut commands = Vec::new(); + + let mut tls_client_credentials = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Oidc { provider, .. } => { + tls_client_credentials.insert(&provider.tls); + + // WebPKI will be handled implicitly + } + SupersetAuthenticationClassResolved::Ldap { .. } => {} + } + } + + for tls in tls_client_credentials { + commands.push(tls.tls_ca_cert_mount_path().map(|tls_ca_cert_mount_path| { + add_cert_to_python_certifi_command(&tls_ca_cert_mount_path) + })); + } + + commands + .iter() + .flatten() + .cloned() + .collect::>() + .join("\n") +} diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index bfd53c55..c775c228 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -1,15 +1,14 @@ -//! The dereference step in the SupersetCluster controller. -//! -//! Fetches all Kubernetes objects referenced by the [`SupersetCluster`] spec and returns them -//! in [`DereferencedObjects`]. Synchronous validation of the fetched objects (image resolution, -//! product-config validation, per-rolegroup config merging) happens in the -//! [validate](`super::validate`) step. - -use snafu::{ResultExt, Snafu}; - -use crate::{ - authorization::opa::SupersetOpaConfigResolved, - crd::{authentication::SupersetClientAuthenticationDetailsResolved, v1alpha1::SupersetCluster}, +//! The dereference step in the SupersetCluster controller. +//! +//! Fetches all Kubernetes objects referenced by the [`SupersetCluster`] spec and returns them +//! in [`DereferencedObjects`]. Synchronous validation (image resolution, config fragment +//! validation and per-rolegroup config merging) happens in the [validate](`super::validate`) step. + +use snafu::{ResultExt, Snafu}; + +use crate::crd::{ + authentication::SupersetClientAuthenticationDetailsResolved, + authorization::SupersetOpaConfigResolved, v1alpha1::SupersetCluster, }; #[derive(Snafu, Debug)] diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 9beb9a82..5bfb12a6 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,33 +1,41 @@ //! The validate step in the SupersetCluster controller. //! //! Synchronously validates inputs that don't require a Kubernetes client. Produces -//! [`ValidatedSupersetCluster`], consumed by the rest of `reconcile_superset`. +//! [`ValidatedCluster`], consumed by the rest of `reconcile_superset`. -use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, -}; +use std::{collections::BTreeMap, str::FromStr}; -use product_config::{ProductConfigManager, types::PropertyNameKind}; -use snafu::{ResultExt, Snafu}; +use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - commons::product_image_selection::{self, ResolvedProductImage}, - product_config_utils::{ - ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config, - validate_all_roles_and_groups_config, - }, + commons::product_image_selection, + config::fragment, + kube::ResourceExt, + product_logging::spec::Logging, role_utils::GenericRoleConfig, + v2::{ + builder::pod::container::{EnvVarName, EnvVarSet}, + controller_utils::{get_cluster_name, get_namespace, get_uid}, + product_logging::framework::{ + VectorContainerLogConfig, validate_logging_configuration_for_container, + }, + role_utils::{GenericCommonConfig, with_validated_config}, + types::{kubernetes::ConfigMapName, operator::RoleGroupName}, + }, }; use strum::IntoEnumIterator; use crate::{ - authorization::opa::SupersetOpaConfigResolved, built_info::PKG_VERSION, - controller::{CONTAINER_IMAGE_BASE_NAME, dereference::DereferencedObjects}, + controller::{ + CONTAINER_IMAGE_BASE_NAME, SupersetRoleGroupConfig, ValidatedCluster, + ValidatedClusterConfig, ValidatedLogging, ValidatedRoleConfig, ValidatedSupersetConfig, + dereference::DereferencedObjects, + }, crd::{ - SUPERSET_CONFIG_FILENAME, SupersetRole, - authentication::SupersetClientAuthenticationDetailsResolved, - v1alpha1::{SupersetCluster, SupersetConfig}, + SupersetRole, + v1alpha1::{ + Container, SupersetCluster, SupersetConfig, SupersetConfigFragment, SupersetRoleConfig, + }, }, }; @@ -38,59 +46,91 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("failed to generate product config"))] - GenerateProductConfig { - source: stackable_operator::product_config_utils::Error, + #[snafu(display("failed to resolve cluster name"))] + ResolveClusterName { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, + #[snafu(display("failed to resolve namespace"))] + ResolveNamespace { + source: stackable_operator::v2::controller_utils::Error, }, - #[snafu(display("failed to resolve and merge config for role and role group"))] - FailedToResolveConfig { source: crate::crd::Error }, + #[snafu(display("failed to resolve uid"))] + ResolveUid { + source: stackable_operator::v2::controller_utils::Error, + }, - #[snafu(display("failed to parse Superset role [{role}]"))] - ParseRole { - source: strum::ParseError, - role: String, + #[snafu(display("failed to resolve and merge config for role group {role_group}"))] + FailedToResolveConfig { + source: fragment::ValidationError, + role_group: String, }, -} -/// Per-role configuration extracted during validation. -#[derive(Clone, Debug)] -pub struct ValidatedRoleConfig { - pub pdb: Option, - pub listener_class: Option, - pub group_listener_name: Option, -} + #[snafu(display("failed to parse environment variable name"))] + ParseEnvVarName { + source: stackable_operator::v2::builder::pod::container::Error, + }, + + #[snafu(display("invalid role group name {role_group}"))] + ParseRoleGroupName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + role_group: String, + }, + + #[snafu(display("failed to 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, -/// Per-rolegroup configuration: the merged CRD config plus the product-config properties. -#[derive(Clone, Debug)] -pub struct ValidatedRoleGroupConfig { - pub merged_config: SupersetConfig, - pub product_config_properties: HashMap>, + #[snafu(display("invalid Vector aggregator discovery ConfigMap name"))] + ParseVectorAggregatorConfigMapName { + source: stackable_operator::v2::macros::attributed_string_type::Error, + }, } -/// The validated cluster: proves that product-config validation and config merging -/// succeeded for every role and role group before any Kubernetes resources are created. -/// Carries the dereferenced external objects so downstream code has a single "ready to use" -/// view of the cluster. -pub struct ValidatedSupersetCluster { - pub image: ResolvedProductImage, - pub role_groups: HashMap>, - pub role_configs: HashMap, - pub authentication_config: SupersetClientAuthenticationDetailsResolved, - pub opa_config: Option, +/// Validates the logging configuration for the Superset (and optional Vector) container. +/// +/// `vector_aggregator_config_map_name` is the discovery ConfigMap name of the Vector aggregator; +/// it is required (and validated) only when the Vector agent is enabled. +fn validate_logging( + logging: &Logging, + vector_aggregator_config_map_name: &Option, +) -> Result { + let superset_container = + validate_logging_configuration_for_container(logging, &Container::Superset) + .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 { + superset_container, + vector_container, + enable_vector_agent: logging.enable_vector_agent, + }) } pub fn validate_cluster( superset: &SupersetCluster, dereferenced: DereferencedObjects, image_repository: &str, - product_config_manager: &ProductConfigManager, -) -> Result { +) -> Result { let DereferencedObjects { authentication_config, opa_config, @@ -102,78 +142,243 @@ pub fn validate_cluster( .resolve(CONTAINER_IMAGE_BASE_NAME, image_repository, PKG_VERSION) .context(ResolveProductImageSnafu)?; - let mut roles = HashMap::new(); - for role in SupersetRole::iter() { - if let Some(resolved_role) = superset.get_role(&role) { - roles.insert( - role.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::File(SUPERSET_CONFIG_FILENAME.into()), - ], - resolved_role.clone(), - ), - ); - } - } + // 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 = superset + .spec + .cluster_config + .vector_aggregator_config_map_name + .as_deref() + .map(ConfigMapName::from_str) + .transpose() + .context(ParseVectorAggregatorConfigMapNameSnafu)?; - let role_config = transform_all_roles_to_config(superset, &roles); - let validated_role_config: ValidatedRoleConfigByPropertyKind = - validate_all_roles_and_groups_config( - &resolved_product_image.product_version, - &role_config.context(GenerateProductConfigSnafu)?, - product_config_manager, - false, - false, - ) - .context(InvalidProductConfigSnafu)?; - - let mut role_groups = HashMap::new(); - let mut role_configs = HashMap::new(); - - for (role_name, rolegroup_configs) in validated_role_config.iter() { - let superset_role = SupersetRole::from_str(role_name).context(ParseRoleSnafu { - role: role_name.to_string(), - })?; + let mut role_groups = BTreeMap::new(); + let mut role_configs = BTreeMap::new(); + + for role in SupersetRole::iter() { + let Some(resolved_role) = superset.get_role(&role) else { + continue; + }; role_configs.insert( - superset_role.clone(), + role.clone(), ValidatedRoleConfig { - pdb: superset.generic_role_config(&superset_role).map( + pdb: superset.generic_role_config(&role).map( |GenericRoleConfig { pod_disruption_budget, }| pod_disruption_budget, ), - listener_class: superset_role.listener_class_name(superset), - group_listener_name: superset.group_listener_name(&superset_role), + listener_class: role.listener_class_name(superset), + group_listener_name: superset.group_listener_name(&role).map(|name| { + name.parse() + .expect("the group listener name is a valid ListenerName") + }), }, ); + let default_config = SupersetConfig::default_config(&superset.name_any(), &role); + let mut group_configs = BTreeMap::new(); - for (rolegroup_name, rolegroup_config) in rolegroup_configs.iter() { - let rolegroup_ref = superset.rolegroup_ref(&superset_role, rolegroup_name); - let merged_config = superset - .merged_config(&superset_role, &rolegroup_ref) - .context(FailedToResolveConfigSnafu)?; + for (rolegroup_name, rolegroup) in &resolved_role.role_groups { + let validated_rg = with_validated_config::< + SupersetConfig, + GenericCommonConfig, + SupersetConfigFragment, + SupersetRoleConfig, + crate::crd::v1alpha1::SupersetConfigOverrides, + >(rolegroup, resolved_role, &default_config) + .with_context(|_| FailedToResolveConfigSnafu { + role_group: rolegroup_name.clone(), + })?; + + let mut env_overrides = EnvVarSet::new(); + for (name, value) in validated_rg.config.env_overrides { + env_overrides = env_overrides.with_value( + &EnvVarName::from_str(&name).context(ParseEnvVarNameSnafu)?, + value, + ); + } + + let role_group_name = RoleGroupName::from_str(rolegroup_name).with_context(|_| { + ParseRoleGroupNameSnafu { + role_group: rolegroup_name.clone(), + } + })?; + + let logging = validate_logging( + &validated_rg.config.config.logging, + &vector_aggregator_config_map_name, + )?; group_configs.insert( - rolegroup_name.clone(), - ValidatedRoleGroupConfig { - merged_config, - product_config_properties: rolegroup_config.clone(), + role_group_name, + SupersetRoleGroupConfig { + replicas: validated_rg.replicas, + config: ValidatedSupersetConfig::from_merged( + validated_rg.config.config, + logging, + ), + config_overrides: validated_rg.config.config_overrides, + env_overrides, + cli_overrides: validated_rg.config.cli_overrides, + pod_overrides: validated_rg.config.pod_overrides, + product_specific_common_config: validated_rg + .config + .product_specific_common_config, }, ); } - role_groups.insert(superset_role, group_configs); + role_groups.insert(role, group_configs); } - Ok(ValidatedSupersetCluster { - image: resolved_product_image, + let cluster_config = &superset.spec.cluster_config; + + let cluster_name = get_cluster_name(superset).context(ResolveClusterNameSnafu)?; + let namespace = get_namespace(superset).context(ResolveNamespaceSnafu)?; + let uid = get_uid(superset).context(ResolveUidSnafu)?; + + Ok(ValidatedCluster::new( + cluster_name, + namespace, + uid, + resolved_product_image, + ValidatedClusterConfig { + authentication_config, + opa_config, + credentials_secret_name: cluster_config.credentials_secret_name.clone(), + secret_key_secret_name: superset.shared_secret_key_secret_name(), + mapbox_secret: cluster_config.mapbox_secret.clone(), + metadata_database: cluster_config.metadata_database.clone(), + celery_results_backend: cluster_config.celery_results_backend.clone(), + celery_broker: cluster_config.celery_broker.clone(), + }, role_groups, role_configs, - authentication_config, - opa_config, - }) + )) +} + +#[cfg(test)] +mod tests { + + use stackable_operator::utils::yaml_from_str_singleton_map; + + use super::validate_cluster; + use crate::{ + controller::dereference::DereferencedObjects, + crd::{ + SupersetRole, + authentication::{ + SupersetClientAuthenticationDetailsResolved, v1alpha1::FlaskRolesSyncMoment, + }, + v1alpha1, + }, + }; + + fn default_dereferenced() -> DereferencedObjects { + DereferencedObjects { + authentication_config: SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: vec![], + user_registration: true, + user_registration_role: "Public".to_string(), + sync_roles_at: FlaskRolesSyncMoment::default(), + }, + opa_config: None, + } + } + + /// Characterises the `superset_config.py` override resolution: role-level overrides are merged + /// with role-group overrides, the role group winning on shared keys, and unique keys from both + /// levels surviving. + #[test] + fn config_overrides_merge_role_group_over_role() { + let input = r#" + apiVersion: superset.stackable.tech/v1alpha1 + kind: SupersetCluster + metadata: + name: simple-superset + namespace: default + uid: 01234567-89ab-cdef-0123-456789abcdef + spec: + image: + productVersion: 4.1.4 + clusterConfig: + credentialsSecretName: superset-admin-credentials + metadataDatabase: + postgresql: + host: superset-postgresql + database: superset + credentialsSecretName: superset-postgresql-credentials + nodes: + configOverrides: + superset_config.py: + ROLE_ONLY: role + SHARED: role + roleGroups: + default: + replicas: 1 + configOverrides: + superset_config.py: + GROUP_ONLY: group + SHARED: group + "#; + let superset: v1alpha1::SupersetCluster = + yaml_from_str_singleton_map(input).expect("illegal test input"); + + let dereferenced = default_dereferenced(); + + let validated = validate_cluster(&superset, dereferenced, "test-repo").expect("validated"); + let node = validated + .role_groups + .get(&SupersetRole::Node) + .and_then(|groups| groups.get(&"default".parse().expect("valid role group name"))) + .expect("node default rolegroup"); + let overrides = &node.config_overrides.superset_config_py.overrides; + + assert_eq!(overrides.get("ROLE_ONLY"), Some(&"role".to_string())); + assert_eq!(overrides.get("GROUP_ONLY"), Some(&"group".to_string())); + assert_eq!( + overrides.get("SHARED"), + Some(&"group".to_string()), + "role-group override should win over the role-level value" + ); + } + + /// A `null` configOverrides value is rejected by the CRD. Values are typed as `String` + /// (operator-rs `KeyValueConfigOverrides`; the `Option` was removed in op-rs #1219), + /// so `null` can no longer express "unset"/"inherit". + #[test] + fn config_overrides_null_value_is_rejected() { + let input = r#" + apiVersion: superset.stackable.tech/v1alpha1 + kind: SupersetCluster + metadata: + name: simple-superset + namespace: default + uid: 01234567-89ab-cdef-0123-456789abcdef + spec: + image: + productVersion: 4.1.4 + clusterConfig: + credentialsSecretName: superset-admin-credentials + metadataDatabase: + postgresql: + host: superset-postgresql + database: superset + credentialsSecretName: superset-postgresql-credentials + nodes: + configOverrides: + superset_config.py: + KEY: null + roleGroups: + default: + replicas: 1 + "#; + let result: Result = yaml_from_str_singleton_map(input); + assert!( + result.is_err(), + "a `null` configOverrides value must be rejected: values are typed as String" + ); + } } diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index 178746de..f4d20acd 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -25,10 +25,12 @@ mod tests { use stackable_operator::{ commons::affinity::StackableAffinity, + config::fragment, k8s_openapi::{ api::core::v1::{PodAffinityTerm, PodAntiAffinity, WeightedPodAffinityTerm}, apimachinery::pkg::apis::meta::v1::LabelSelector, }, + kube::ResourceExt, utils::yaml_from_str_singleton_map, }; @@ -59,12 +61,12 @@ mod tests { "#; let superset: v1alpha1::SupersetCluster = yaml_from_str_singleton_map(input).expect("illegal test input"); - let merged_config = superset - .merged_config( - &SupersetRole::Node, - &superset.rolegroup_ref(&SupersetRole::Node, "default"), - ) - .unwrap(); + // The role group carries no resource/affinity overrides, so the merged config is just the + // validated default config. + let merged_config: v1alpha1::SupersetConfig = fragment::validate( + v1alpha1::SupersetConfig::default_config(&superset.name_any(), &SupersetRole::Node), + ) + .expect("default config should validate"); assert_eq!( merged_config.affinity, diff --git a/rust/operator-binary/src/crd/authentication.rs b/rust/operator-binary/src/crd/authentication.rs index c2ba9a07..2051dfa6 100644 --- a/rust/operator-binary/src/crd/authentication.rs +++ b/rust/operator-binary/src/crd/authentication.rs @@ -292,9 +292,7 @@ impl SupersetClientAuthenticationDetailsResolved { let oidc_provider = match &provider.provider_hint { None => { info!( - "No OIDC provider hint given in AuthClass {auth_class_name}, assuming {default_oidc_provider_name}", - default_oidc_provider_name = - serde_json::to_string(&DEFAULT_OIDC_PROVIDER).unwrap() + "No OIDC provider hint given in AuthClass {auth_class_name}, assuming {DEFAULT_OIDC_PROVIDER:?}" ); DEFAULT_OIDC_PROVIDER } @@ -305,7 +303,7 @@ impl SupersetClientAuthenticationDetailsResolved { SUPPORTED_OIDC_PROVIDERS.contains(&oidc_provider), OidcProviderNotSupportedSnafu { auth_class_name, - oidc_provider: serde_json::to_string(&oidc_provider).unwrap(), + oidc_provider: format!("{oidc_provider:?}"), } ); diff --git a/rust/operator-binary/src/crd/authorization.rs b/rust/operator-binary/src/crd/authorization.rs new file mode 100644 index 00000000..3e500899 --- /dev/null +++ b/rust/operator-binary/src/crd/authorization.rs @@ -0,0 +1,35 @@ +//! The resolved OPA authorization config, dereferenced against the Kubernetes API. +//! +//! The rendering into `superset_config.py` properties lives in +//! [`crate::controller::build::properties::authorization`], mirroring how +//! [`crate::crd::authentication`] pairs with `build::properties::authentication`. + +use stackable_operator::{client::Client, commons::opa::OpaApiVersion, shared::time::Duration}; + +use crate::crd::v1alpha1; + +#[derive(Clone, Debug)] +pub struct SupersetOpaConfigResolved { + pub opa_endpoint: String, + pub cache_max_entries: u32, + pub cache_ttl: Duration, +} + +impl SupersetOpaConfigResolved { + pub async fn from_opa_config( + client: &Client, + superset: &v1alpha1::SupersetCluster, + opa_config: &v1alpha1::SupersetOpaRoleMappingConfig, + ) -> Result { + let opa_endpoint = opa_config + .opa + .full_document_url_from_config_map(client, superset, None, &OpaApiVersion::V1) + .await?; + + Ok(SupersetOpaConfigResolved { + opa_endpoint, + cache_max_entries: opa_config.cache.max_entries.to_owned(), + cache_ttl: opa_config.cache.entry_time_to_live.to_owned(), + }) + } +} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index a1e780c5..24bf9d36 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -1,8 +1,5 @@ -use std::collections::BTreeMap; - -use product_config::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}; use serde::{Deserialize, Serialize}; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::Snafu; use stackable_operator::{ commons::{ affinity::StackableAffinity, @@ -15,37 +12,41 @@ use stackable_operator::{ Resources, ResourcesFragment, }, }, - config::{ - fragment::{self, Fragment, ValidationError}, - merge::Merge, - }, - config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, + config::{fragment::Fragment, merge::Merge}, deep_merger::ObjectOverrides, k8s_openapi::apimachinery::pkg::api::resource::Quantity, - kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef}, + kube::{CustomResource, ResourceExt}, memory::{BinaryMultiple, MemoryQuantity}, - product_config_utils::{self, Configuration}, product_logging::{self, spec::Logging}, - role_utils::{GenericRoleConfig, Role, RoleGroupRef}, + role_utils::{GenericRoleConfig, Role}, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, + v2::{ + config_overrides::KeyValueConfigOverrides, + flask_config_writer::{FlaskAppConfigOptions, PythonType}, + role_utils::GenericCommonConfig, + types::common::Port, + }, versioned::versioned, }; -use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; +use strum::{Display, EnumIter, EnumString}; -use crate::{ - crd::{ - databases::{ - CeleryBrokerConnection, CeleryResultsBackendConnection, MetadataDatabaseConnection, - }, - v1alpha1::SupersetRoleConfig, +use crate::crd::{ + databases::{ + CeleryBrokerConnection, CeleryResultsBackendConnection, MetadataDatabaseConnection, }, - resources::listener::default_listener_class, + v1alpha1::SupersetRoleConfig, }; +/// Default listener class used by the rolegroup listener. +fn default_listener_class() -> String { + "cluster-internal".to_string() +} + pub mod affinity; pub mod authentication; +pub mod authorization; pub mod databases; pub mod druidconnection; @@ -53,9 +54,7 @@ pub const FIELD_MANAGER: &str = "superset-operator"; pub const APP_NAME: &str = "superset"; pub const STACKABLE_CONFIG_DIR: &str = "/stackable/config"; pub const STACKABLE_LOG_CONFIG_DIR: &str = "/stackable/log_config"; -pub const STACKABLE_LOG_DIR: &str = "/stackable/log"; pub const PYTHONPATH: &str = "/stackable/app/pythonpath"; -pub const SUPERSET_CONFIG_FILENAME: &str = "superset_config.py"; pub const MAX_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { value: 10.0, unit: BinaryMultiple::Mebi, @@ -63,24 +62,25 @@ pub const MAX_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { pub const INTERNAL_SECRET_SECRET_KEY: &str = "SECRET_KEY"; +/// Env-var prefix for the metadata database connection credentials (e.g. `METADATA_DATABASE_*`). +pub const METADATA_DATABASE_ENV_PREFIX: &str = "METADATA"; + pub const APP_PORT_NAME: &str = "http"; -pub const APP_PORT: u16 = 8088; +pub const APP_PORT: Port = Port(8088); pub const METRICS_PORT_NAME: &str = "metrics"; -pub const METRICS_PORT: u16 = 9102; +pub const METRICS_PORT: Port = Port(9102); const DEFAULT_NODE_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_minutes_unchecked(2); -pub type SupersetRoleType = - Role; +pub type SupersetRoleType = Role< + v1alpha1::SupersetConfigFragment, + v1alpha1::SupersetConfigOverrides, + SupersetRoleConfig, + GenericCommonConfig, +>; #[derive(Debug, Snafu)] pub enum Error { - #[snafu(display("unknown Superset role found {role}. Should be one of {roles:?}"))] - UnknownSupersetRole { role: String, roles: Vec }, - - #[snafu(display("fragment validation failure"))] - FragmentValidationFailure { source: ValidationError }, - #[snafu(display("Configuration/Executor conflict!"))] NoRoleForExecutorFailure, @@ -189,15 +189,11 @@ pub mod versioned { pub listener_class: String, } - #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SupersetConfigOverrides { - #[serde( - default, - rename = "superset_config.py", - skip_serializing_if = "Option::is_none" - )] - pub superset_config_py: Option, + #[serde(default, rename = "superset_config.py")] + pub superset_config_py: KeyValueConfigOverrides, } #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] @@ -372,19 +368,6 @@ impl Default for v1alpha1::SupersetRoleConfig { } } -impl KeyValueOverridesProvider for v1alpha1::SupersetConfigOverrides { - fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - match file { - SUPERSET_CONFIG_FILENAME => self - .superset_config_py - .as_ref() - .map(KeyValueConfigOverrides::as_product_config_overrides) - .unwrap_or_default(), - _ => BTreeMap::new(), - } - } -} - #[derive( Clone, Debug, @@ -395,7 +378,9 @@ impl KeyValueOverridesProvider for v1alpha1::SupersetConfigOverrides { Eq, Hash, JsonSchema, + Ord, PartialEq, + PartialOrd, Serialize, )] pub enum SupersetRole { @@ -419,30 +404,10 @@ impl SupersetRole { } } - pub fn roles() -> Vec { - let mut roles = vec![]; - for role in Self::iter() { - roles.push(role.to_string()) - } - roles - } -} - -impl SupersetConfigOptions { - /// Mapping from `SupersetConfigOptions` to the values set in `SupersetConfigFragment`. - /// `None` is returned if either the according option is not set or is not exposed in the - /// `SupersetConfig`. - fn config_type_to_string( - &self, - superset_config: &v1alpha1::SupersetConfigFragment, - ) -> Option { - match self { - SupersetConfigOptions::RowLimit => superset_config.row_limit.map(|v| v.to_string()), - SupersetConfigOptions::SupersetWebserverTimeout => { - superset_config.webserver_timeout.map(|v| v.to_string()) - } - _ => None, - } + pub fn role_name(&self) -> stackable_operator::v2::types::operator::RoleName { + self.to_string() + .parse() + .expect("a Superset serialises to a valid RoleName") } } @@ -494,9 +459,10 @@ impl FlaskAppConfigOptions for SupersetConfigOptions { } impl v1alpha1::SupersetConfig { - pub const MAPBOX_SECRET_PROPERTY: &'static str = "mapboxSecret"; - - fn default_config(cluster_name: &str, role: &SupersetRole) -> v1alpha1::SupersetConfigFragment { + pub(crate) fn default_config( + cluster_name: &str, + role: &SupersetRole, + ) -> v1alpha1::SupersetConfigFragment { match role { SupersetRole::Node => v1alpha1::SupersetConfigFragment { resources: ResourcesFragment { @@ -556,52 +522,6 @@ impl v1alpha1::SupersetConfig { } } -impl Configuration for v1alpha1::SupersetConfigFragment { - type Configurable = v1alpha1::SupersetCluster; - - fn compute_env( - &self, - cluster: &Self::Configurable, - _role_name: &str, - ) -> Result>, product_config_utils::Error> { - let mut result = BTreeMap::new(); - if let Some(msec) = &cluster.spec.cluster_config.mapbox_secret { - result.insert( - v1alpha1::SupersetConfig::MAPBOX_SECRET_PROPERTY.to_string(), - Some(msec.clone()), - ); - } - Ok(result) - } - - fn compute_cli( - &self, - _cluster: &Self::Configurable, - _role_name: &str, - ) -> Result>, product_config_utils::Error> { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - _cluster: &Self::Configurable, - _role_name: &str, - file: &str, - ) -> Result>, product_config_utils::Error> { - let mut result = BTreeMap::new(); - - if file == SUPERSET_CONFIG_FILENAME { - for option in SupersetConfigOptions::iter() { - if let Some(value) = option.config_type_to_string(self) { - result.insert(option.to_string(), Some(value)); - } - } - } - - Ok(result) - } -} - impl HasStatusCondition for v1alpha1::SupersetCluster { fn conditions(&self) -> Vec { match &self.status { @@ -616,6 +536,11 @@ impl v1alpha1::SupersetCluster { format!("{}-secret-key", &self.name_any()) } + /// The connection to the metadata database. + pub fn metadata_database(&self) -> &MetadataDatabaseConnection { + &self.spec.cluster_config.metadata_database + } + /// The name of the group-listener provided for a specific role. /// Nodes will use this group listener so that only one load balancer /// is needed for that role. @@ -645,19 +570,6 @@ impl v1alpha1::SupersetCluster { } } - /// Metadata about a node rolegroup - pub fn rolegroup_ref( - &self, - role: &SupersetRole, - group_name: impl Into, - ) -> RoleGroupRef { - RoleGroupRef { - cluster: ObjectRef::from_obj(self), - role: role.to_string(), - role_group: group_name.into(), - } - } - pub fn get_opa_config(&self) -> Option<&v1alpha1::SupersetOpaRoleMappingConfig> { self.spec .cluster_config @@ -665,42 +577,6 @@ impl v1alpha1::SupersetCluster { .as_ref() .map(|a| &a.role_mapping_from_opa) } - - /// Retrieve and merge resource configs for role and role groups - pub fn merged_config( - &self, - role: &SupersetRole, - rolegroup_ref: &RoleGroupRef, - ) -> Result { - // Initialize the result with all default values as baseline - let conf_defaults = v1alpha1::SupersetConfig::default_config(&self.name_any(), role); - - let role = self.get_role(role).context(UnknownSupersetRoleSnafu { - role: role.to_string(), - roles: SupersetRole::roles(), - })?; - - // Retrieve role resource config - let mut conf_role = role.config.config.to_owned(); - - // Retrieve rolegroup specific resource config - let mut conf_rolegroup = role - .role_groups - .get(&rolegroup_ref.role_group) - .map(|rg| rg.config.config.clone()) - .unwrap_or_default(); - - // 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) - } } #[cfg(test)] diff --git a/rust/operator-binary/src/operations/job_state.rs b/rust/operator-binary/src/druid_connection_controller/job_state.rs similarity index 100% rename from rust/operator-binary/src/operations/job_state.rs rename to rust/operator-binary/src/druid_connection_controller/job_state.rs diff --git a/rust/operator-binary/src/druid_connection_controller.rs b/rust/operator-binary/src/druid_connection_controller/mod.rs similarity index 95% rename from rust/operator-binary/src/druid_connection_controller.rs rename to rust/operator-binary/src/druid_connection_controller/mod.rs index 6dfc0668..f947e270 100644 --- a/rust/operator-binary/src/druid_connection_controller.rs +++ b/rust/operator-binary/src/druid_connection_controller/mod.rs @@ -28,14 +28,14 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ APP_NAME, OPERATOR_NAME, - controller::CONTAINER_IMAGE_BASE_NAME, - crd::{ - INTERNAL_SECRET_SECRET_KEY, PYTHONPATH, SUPERSET_CONFIG_FILENAME, druidconnection, v1alpha1, - }, - operations::job_state::{JobState, get_job_state}, - resources::rbac, + controller::{CONTAINER_IMAGE_BASE_NAME, build::properties::ConfigFileName}, + crd::{INTERNAL_SECRET_SECRET_KEY, PYTHONPATH, druidconnection, v1alpha1}, + druid_connection_controller::job_state::{JobState, get_job_state}, }; +mod job_state; +mod rbac; + pub const DRUID_CONNECTION_CONTROLLER_NAME: &str = "druid-connection"; pub const DRUID_CONNECTION_FULL_CONTROLLER_NAME: &str = concatcp!(DRUID_CONNECTION_CONTROLLER_NAME, '.', OPERATOR_NAME); @@ -327,7 +327,8 @@ async fn build_import_job( let config = "import os; SQLALCHEMY_DATABASE_URI = os.path.expandvars(os.environ.get('SQLALCHEMY_DATABASE_URI'))"; commands.push(format!("mkdir -p {PYTHONPATH}")); commands.push(format!( - "echo \"{config}\" > {PYTHONPATH}/{SUPERSET_CONFIG_FILENAME}" + "echo \"{config}\" > {PYTHONPATH}/{config_file}", + config_file = ConfigFileName::SupersetConfig )); let druid_info = build_druid_db_yaml(&druid_connection.spec.druid.name, sqlalchemy_str)?; @@ -336,15 +337,16 @@ async fn build_import_job( "superset import_datasources -p /tmp/druids.yaml", )); - // "METADATA" is the prefix for the env vars that hold the database credentials - // (e.g. METADATA_DATABASE_USERNAME, METADATA_DATABASE_PASSWORD). It should match + // `METADATA_DATABASE_ENV_PREFIX` is the prefix for the env vars that hold the database + // credentials (e.g. METADATA_DATABASE_USERNAME, METADATA_DATABASE_PASSWORD). It should match // the prefix used by the airflow-operator for consistency. let templating_mechanism = TemplatingMechanism::BashEnvSubstitution; let metadata_database_connection_details = superset_cluster - .spec - .cluster_config - .metadata_database - .sqlalchemy_connection_details_with_templating("METADATA", &templating_mechanism); + .metadata_database() + .sqlalchemy_connection_details_with_templating( + crate::crd::METADATA_DATABASE_ENV_PREFIX, + &templating_mechanism, + ); let mut container_builder = ContainerBuilder::new("superset-import-druid-connection") .expect("ContainerBuilder not created"); diff --git a/rust/operator-binary/src/resources/rbac.rs b/rust/operator-binary/src/druid_connection_controller/rbac.rs similarity index 100% rename from rust/operator-binary/src/resources/rbac.rs rename to rust/operator-binary/src/druid_connection_controller/rbac.rs diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 6709b422..dd2fdbd5 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -43,13 +43,9 @@ use crate::{ webhooks::conversion::create_webhook_server, }; -mod authorization; -mod config; mod controller; mod crd; mod druid_connection_controller; -mod operations; -mod resources; mod webhooks; mod built_info { @@ -78,9 +74,9 @@ async fn main() -> anyhow::Result<()> { Command::Run(RunArguments { operator_environment, watch_namespace, - product_config, maintenance, common, + .. }) => { // NOTE (@NickLarsenNZ): Before stackable-telemetry was used: // - The console log level was set by `SUPERSET_OPERATOR_LOG`, and is now `CONSOLE_LOG` (when using Tracing::pre_configured). @@ -125,11 +121,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/superset-operator/config-spec/properties.yaml", - ])?; - let superset_event_recorder = Arc::new(Recorder::new( client.as_kube_client(), Reporter { @@ -188,7 +179,6 @@ async fn main() -> anyhow::Result<()> { Arc::new(controller::Ctx { client: client.clone(), operator_environment: operator_environment.clone(), - product_config, }), ) // We can let the reporting happen in the background diff --git a/rust/operator-binary/src/operations/mod.rs b/rust/operator-binary/src/operations/mod.rs deleted file mode 100644 index 8da9c1c5..00000000 --- a/rust/operator-binary/src/operations/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod graceful_shutdown; -pub mod job_state; -pub mod pdb; diff --git a/rust/operator-binary/src/operations/pdb.rs b/rust/operator-binary/src/operations/pdb.rs deleted file mode 100644 index e2416802..00000000 --- a/rust/operator-binary/src/operations/pdb.rs +++ /dev/null @@ -1,73 +0,0 @@ -use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - builder::pdb::PodDisruptionBudgetBuilder, client::Client, cluster_resources::ClusterResources, - commons::pdb::PdbConfig, kube::ResourceExt, -}; - -use crate::{ - OPERATOR_NAME, - controller::SUPERSET_CONTROLLER_NAME, - crd::{APP_NAME, SupersetRole, v1alpha1}, -}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("Cannot create PodDisruptionBudget for role [{role}]"))] - CreatePdb { - source: stackable_operator::builder::pdb::Error, - role: String, - }, - #[snafu(display("Cannot apply PodDisruptionBudget [{name}]"))] - ApplyPdb { - source: stackable_operator::cluster_resources::Error, - name: String, - }, -} - -pub async fn add_pdbs( - pdb: &PdbConfig, - superset: &v1alpha1::SupersetCluster, - role: &SupersetRole, - client: &Client, - cluster_resources: &mut ClusterResources<'_>, -) -> Result<(), Error> { - if !pdb.enabled { - return Ok(()); - } - let max_unavailable = pdb.max_unavailable.unwrap_or(match role { - SupersetRole::Node => max_unavailable_nodes(), - SupersetRole::Worker => max_unavailable_workers(), - SupersetRole::Beat => max_unavailable_beat(), - }); - let pdb = PodDisruptionBudgetBuilder::new_with_role( - superset, - APP_NAME, - &role.to_string(), - OPERATOR_NAME, - SUPERSET_CONTROLLER_NAME, - ) - .with_context(|_| CreatePdbSnafu { - role: role.to_string(), - })? - .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(()) -} - -fn max_unavailable_nodes() -> u16 { - 1 -} - -fn max_unavailable_workers() -> u16 { - 1 -} - -fn max_unavailable_beat() -> u16 { - 1 -} diff --git a/rust/operator-binary/src/resources/configmap.rs b/rust/operator-binary/src/resources/configmap.rs deleted file mode 100644 index 5f3e4b20..00000000 --- a/rust/operator-binary/src/resources/configmap.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - io::Write, -}; - -use product_config::{ - flask_app_config_writer::{self}, - types::PropertyNameKind, -}; -use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, - commons::product_image_selection::ResolvedProductImage, - k8s_openapi::api::core::v1::ConfigMap, - product_config_utils::{CONFIG_OVERRIDE_FILE_FOOTER_KEY, CONFIG_OVERRIDE_FILE_HEADER_KEY}, - product_logging::spec::Logging, - role_utils::RoleGroupRef, -}; - -use crate::{ - authorization::opa::{OPA_IMPORTS, SupersetOpaConfigResolved}, - config::{self, product_logging::extend_config_map_with_log_config, superset::PYTHON_IMPORTS}, - controller::SUPERSET_CONTROLLER_NAME, - crd::{ - SUPERSET_CONFIG_FILENAME, SupersetConfigOptions, - authentication::SupersetClientAuthenticationDetailsResolved, - v1alpha1::{Container, SupersetCluster}, - }, - resources::build_recommended_labels, -}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("failed to add Superset config settings"))] - AddSupersetConfig { source: config::superset::Error }, - - #[snafu(display( - "failed to write to String (Vec to be precise) containing superset config" - ))] - WriteToConfigFileString { source: std::io::Error }, - - #[snafu(display("failed to build Metadata"))] - BuildMetadata { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("failed to build config file for {rolegroup}"))] - BuildRoleGroupConfigFile { - source: flask_app_config_writer::FlaskAppConfigWriterError, - rolegroup: RoleGroupRef, - }, - - #[snafu(display("failed to build ConfigMap for {rolegroup}"))] - BuildRoleGroupConfig { - source: stackable_operator::builder::configmap::Error, - rolegroup: RoleGroupRef, - }, -} - -type Result = std::result::Result; - -/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator -#[allow(clippy::too_many_arguments)] -pub fn build_rolegroup_config_map( - superset: &SupersetCluster, - resolved_product_image: &ResolvedProductImage, - rolegroup: &RoleGroupRef, - rolegroup_config: &HashMap>, - authentication_config: &SupersetClientAuthenticationDetailsResolved, - superset_opa_config: &Option, - logging: &Logging, -) -> Result { - let mut config_properties = BTreeMap::new(); - let mut imports = PYTHON_IMPORTS.to_vec(); - // TODO: this is true per default for versions 3.0.0 and up. - // We deactivate it here to keep existing functionality. - // However this is a security issue and should be configured properly - // Issue: https://github.com/stackabletech/superset-operator/issues/416 - config_properties.insert("TALISMAN_ENABLED".to_string(), "False".to_string()); - - config::superset::add_superset_config(&mut config_properties, superset, authentication_config) - .context(AddSupersetConfigSnafu)?; - - // Adding opa configuration properties to config_properties. - // This will be injected as key/value pair in superset_config.py - if let Some(opa_config) = superset_opa_config { - // If opa role mapping is configured, insert CustomOpaSecurityManager import - imports.extend(OPA_IMPORTS); - - config_properties.extend(opa_config.as_config()); - } - - // The order here should be kept in order to preserve overrides. - // No properties should be added after this extend. - config_properties.extend( - rolegroup_config - .get(&PropertyNameKind::File( - SUPERSET_CONFIG_FILENAME.to_string(), - )) - .cloned() - .unwrap_or_default(), - ); - - let mut config_file = Vec::new(); - - // By removing the keys from `config_properties`, we avoid pasting the Python code into a Python variable as well - // (which would be bad) - if let Some(header) = config_properties.remove(CONFIG_OVERRIDE_FILE_HEADER_KEY) { - writeln!(config_file, "{header}").context(WriteToConfigFileStringSnafu)?; - } - let temp_file_footer = config_properties.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - - flask_app_config_writer::write::( - &mut config_file, - config_properties.iter(), - &imports, - ) - .with_context(|_| BuildRoleGroupConfigFileSnafu { - rolegroup: rolegroup.clone(), - })?; - - // We have to add a python class (no key) and cannot use the superset::config machinery. - config::superset::append_celery_connection_config(&mut config_file, superset); - - if let Some(footer) = temp_file_footer { - writeln!(config_file, "{footer}").context(WriteToConfigFileStringSnafu)?; - } - - let mut cm_builder = ConfigMapBuilder::new(); - - cm_builder - .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup.object_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup.role, - &rolegroup.role_group, - )) - .context(BuildMetadataSnafu)? - .build(), - ) - .add_data( - SUPERSET_CONFIG_FILENAME, - String::from_utf8(config_file).unwrap(), - ); - - extend_config_map_with_log_config( - rolegroup, - logging, - &Container::Superset, - &Container::Vector, - &mut cm_builder, - ); - - cm_builder - .build() - .with_context(|_| BuildRoleGroupConfigSnafu { - rolegroup: rolegroup.clone(), - }) -} diff --git a/rust/operator-binary/src/resources/deployment.rs b/rust/operator-binary/src/resources/deployment.rs deleted file mode 100644 index 55fc209b..00000000 --- a/rust/operator-binary/src/resources/deployment.rs +++ /dev/null @@ -1,639 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use indoc::formatdoc; -use product_config::types::PropertyNameKind; -use snafu::{OptionExt, ResultExt, Snafu}; -use stackable_operator::{ - builder::{ - meta::ObjectMetaBuilder, - pod::{ - PodBuilder, container::ContainerBuilder, resources::ResourceRequirementsBuilder, - security::PodSecurityContextBuilder, - }, - }, - commons::product_image_selection::ResolvedProductImage, - k8s_openapi::{ - DeepMerge, - api::{ - apps::v1::{Deployment, DeploymentSpec}, - core::v1::{ExecAction, Probe}, - }, - apimachinery::pkg::apis::meta::v1::LabelSelector, - }, - kvp::{Label, Labels}, - product_logging::{ - self, - framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command}, - }, - role_utils::RoleGroupRef, - utils::COMMON_BASH_TRAP_FUNCTIONS, -}; - -use crate::{ - config::product_logging::LOG_CONFIG_FILE, - controller::SUPERSET_CONTROLLER_NAME, - crd::{ - APP_NAME, APP_PORT, METRICS_PORT, METRICS_PORT_NAME, PYTHONPATH, STACKABLE_CONFIG_DIR, - STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, SUPERSET_CONFIG_FILENAME, - SupersetConfigOptions, SupersetRole, - v1alpha1::{Container, SupersetCluster, SupersetConfig}, - }, - operations::graceful_shutdown::add_graceful_shutdown_config, - resources::{ - CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME, build_recommended_labels, - }, -}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object defines no '{role}' role"))] - MissingRole { role: String }, - - #[snafu(display("object defines no '{role}' rolegroup"))] - MissingRoleGroup { role: String }, - - #[snafu(display("invalid container name"))] - InvalidContainerName { - source: stackable_operator::builder::pod::container::Error, - }, - - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - - #[snafu(display("failed to configure graceful shutdown"))] - GracefulShutdown { - source: crate::operations::graceful_shutdown::Error, - }, - - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display( - "failed to get the {SUPERSET_CONFIG_FILENAME} file from node or product config" - ))] - MissingSupersetConfigInNodeConfig, - - #[snafu(display("failed to get {timeout} from {SUPERSET_CONFIG_FILENAME} file. It should be set in the product config or by user input", timeout = SupersetConfigOptions::SupersetWebserverTimeout))] - MissingWebServerTimeoutInSupersetConfig, - - #[snafu(display("failed to configure logging"))] - ConfigureLogging { - source: product_logging::framework::LoggingError, - }, - - #[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, - }, -} - -type Result = std::result::Result; - -/// The rolegroup [`Deployment`] runs the rolegroup, as configured by the administrator. -pub fn build_worker_rolegroup_deployment( - superset: &SupersetCluster, - resolved_product_image: &ResolvedProductImage, - superset_role: &SupersetRole, - rolegroup_ref: &RoleGroupRef, - node_config: &HashMap>, - sa_name: &str, - merged_config: &SupersetConfig, -) -> Result { - let role = superset - .get_role(superset_role) - .with_context(|| MissingRoleSnafu { - role: superset_role.to_string(), - })?; - let role_group = role - .role_groups - .get(&rolegroup_ref.role_group) - .with_context(|| MissingRoleGroupSnafu { - role: superset_role.to_string(), - })?; - - let recommended_object_labels = build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ); - - let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .build(); - - let mut pb = &mut PodBuilder::new(); - - pb = pb - .metadata(metadata) - .image_pull_secrets_from_product_image(resolved_product_image) - .security_context( - PodSecurityContextBuilder::new() - .fs_group(1000) // Needed for secret-operator - .build(), - ) - .affinity(&merged_config.affinity) - .service_account_name(sa_name); - - let mut superset_cb = ContainerBuilder::new(&Container::Superset.to_string()) - .context(InvalidContainerNameSnafu)?; - - let metadata_database_connection_details = - super::metadata_database_connection_details(superset); - let celery_results_backend_connection_details = - super::celery_results_backend_connection_details(superset); - let celery_broker_connection_details = super::celery_broker_connection_details(superset); - - metadata_database_connection_details.add_to_container(&mut superset_cb); - if let (_, Some(celery_results_backend_connection_details)) = - &celery_results_backend_connection_details - { - celery_results_backend_connection_details.add_to_container(&mut superset_cb); - } - if let Some(celery_broker_connection_details) = celery_broker_connection_details { - celery_broker_connection_details.add_to_container(&mut superset_cb); - } - - for (name, value) in node_config - .get(&PropertyNameKind::Env) - .cloned() - .unwrap_or_default() - { - if name == SupersetConfig::MAPBOX_SECRET_PROPERTY { - superset_cb.add_env_var_from_secret( - "MAPBOX_API_KEY", - &value, - "connections.mapboxApiKey", - ); - } else { - superset_cb.add_env_var(name, value); - }; - } - - // SECRET_KEY from auto-generated secret - superset_cb.add_env_var_from_secret( - "SECRET_KEY", - superset.shared_secret_key_secret_name(), - crate::crd::INTERNAL_SECRET_SECRET_KEY, - ); - - let secret = &superset.spec.cluster_config.credentials_secret_name; - - superset_cb - .image_from_product_image(resolved_product_image) - .add_container_port("http", APP_PORT.into()) - .add_volume_mount(CONFIG_VOLUME_NAME, STACKABLE_CONFIG_DIR) - .context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_CONFIG_VOLUME_NAME, STACKABLE_LOG_CONFIG_DIR) - .context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) - .context(AddVolumeMountSnafu)? - .add_env_var_from_secret("ADMIN_USERNAME", secret, "adminUser.username") - .add_env_var_from_secret("ADMIN_FIRSTNAME", secret, "adminUser.firstname") - .add_env_var_from_secret("ADMIN_LASTNAME", secret, "adminUser.lastname") - .add_env_var_from_secret("ADMIN_EMAIL", secret, "adminUser.email") - .add_env_var_from_secret("ADMIN_PASSWORD", secret, "adminUser.password") - // Needed by the `containerdebug` process to log it's tracing information to. - .add_env_var( - "CONTAINERDEBUG_LOG_DIRECTORY", - format!("{STACKABLE_LOG_DIR}/containerdebug"), - ) - .add_env_var("SSL_CERT_DIR", "/stackable/certs/") - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - - mkdir --parents {PYTHONPATH} - cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} - cp {STACKABLE_LOG_CONFIG_DIR}/{LOG_CONFIG_FILE} {PYTHONPATH} - - {remove_vector_shutdown_file_command} - prepare_signal_handlers - containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & - - celery --app=superset.tasks.celery_app:app worker --task-events & - - wait_for_termination $! - {create_vector_shutdown_file_command} - ", - remove_vector_shutdown_file_command = - remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), - create_vector_shutdown_file_command = - create_vector_shutdown_file_command(STACKABLE_LOG_DIR), - }]) - .liveness_probe(Probe { - exec: Some(ExecAction { - command: Some(vec![ - "celery --app=superset.tasks.celery_app:app inspect ping -d celery@$HOSTNAME" - .to_string(), - ]), - }), - initial_delay_seconds: Some(30), - period_seconds: Some(30), - timeout_seconds: Some(30), - failure_threshold: Some(3), - ..Default::default() - }) - .resources(merged_config.resources.clone().into()); - - pb.add_container(superset_cb.build()); - add_graceful_shutdown_config(merged_config, pb).context(GracefulShutdownSnafu)?; - - let metrics_container = ContainerBuilder::new("metrics") - .context(InvalidContainerNameSnafu)? - .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![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - prepare_signal_handlers - /stackable/statsd_exporter & - wait_for_termination $! - "}]) - .add_container_port(METRICS_PORT_NAME, METRICS_PORT.into()) - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("100m") - .with_cpu_limit("200m") - .with_memory_request("64Mi") - .with_memory_limit("64Mi") - .build(), - ) - .build(); - - pb.add_volumes(crate::resources::create_volumes( - &rolegroup_ref.object_name(), - merged_config.logging.containers.get(&Container::Superset), - )) - .context(AddVolumeSnafu)?; - pb.add_container(metrics_container); - - if merged_config.logging.enable_vector_agent { - match &superset - .spec - .cluster_config - .vector_aggregator_config_map_name - { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - product_logging::framework::vector_container( - resolved_product_image, - CONFIG_VOLUME_NAME, - LOG_VOLUME_NAME, - 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(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } - } - - let mut pod_template = pb.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - pod_template.merge_from(role_group.config.pod_overrides.clone()); - - Ok(Deployment { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup_ref.object_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .with_label( - Label::try_from(("restarter.stackable.tech/enabled", "true")) - .context(LabelBuildSnafu)?, - ) - .build(), - spec: Some(DeploymentSpec { - replicas: role_group.replicas.map(i32::from), - selector: LabelSelector { - match_labels: Some( - Labels::role_group_selector( - superset, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), - ..LabelSelector::default() - }, - template: pod_template, - ..DeploymentSpec::default() - }), - status: None, - }) -} - -/// The rolegroup [`Deployment`] runs the rolegroup, as configured by the administrator. -pub fn build_beat_rolegroup_deployment( - superset: &SupersetCluster, - resolved_product_image: &ResolvedProductImage, - superset_role: &SupersetRole, - rolegroup_ref: &RoleGroupRef, - node_config: &HashMap>, - sa_name: &str, - merged_config: &SupersetConfig, -) -> Result { - let role = superset - .get_role(superset_role) - .with_context(|| MissingRoleSnafu { - role: superset_role.to_string(), - })?; - let role_group = role - .role_groups - .get(&rolegroup_ref.role_group) - .with_context(|| MissingRoleGroupSnafu { - role: superset_role.to_string(), - })?; - - let recommended_object_labels = build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ); - - let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .build(); - - let mut pb = &mut PodBuilder::new(); - - pb = pb - .metadata(metadata) - .image_pull_secrets_from_product_image(resolved_product_image) - .security_context( - PodSecurityContextBuilder::new() - .fs_group(1000) // Needed for secret-operator - .build(), - ) - .affinity(&merged_config.affinity) - .service_account_name(sa_name); - - let mut superset_cb = ContainerBuilder::new(&Container::Superset.to_string()) - .context(InvalidContainerNameSnafu)?; - - let metadata_database_connection_details = - super::metadata_database_connection_details(superset); - let celery_results_backend_connection_details = - super::celery_results_backend_connection_details(superset); - let celery_broker_connection_details = super::celery_broker_connection_details(superset); - - metadata_database_connection_details.add_to_container(&mut superset_cb); - if let (_, Some(celery_results_backend_connection_details)) = - &celery_results_backend_connection_details - { - celery_results_backend_connection_details.add_to_container(&mut superset_cb); - } - if let Some(celery_broker_connection_details) = celery_broker_connection_details { - celery_broker_connection_details.add_to_container(&mut superset_cb); - } - - for (name, value) in node_config - .get(&PropertyNameKind::Env) - .cloned() - .unwrap_or_default() - { - if name == SupersetConfig::MAPBOX_SECRET_PROPERTY { - superset_cb.add_env_var_from_secret( - "MAPBOX_API_KEY", - &value, - "connections.mapboxApiKey", - ); - } else { - superset_cb.add_env_var(name, value); - }; - } - - // SECRET_KEY from auto-generated secret - superset_cb.add_env_var_from_secret( - "SECRET_KEY", - superset.shared_secret_key_secret_name(), - crate::crd::INTERNAL_SECRET_SECRET_KEY, - ); - - let secret = &superset.spec.cluster_config.credentials_secret_name; - - superset_cb - .image_from_product_image(resolved_product_image) - .add_container_port("http", APP_PORT.into()) - .add_volume_mount(CONFIG_VOLUME_NAME, STACKABLE_CONFIG_DIR) - .context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_CONFIG_VOLUME_NAME, STACKABLE_LOG_CONFIG_DIR) - .context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) - .context(AddVolumeMountSnafu)? - .add_env_var_from_secret("ADMIN_USERNAME", secret, "adminUser.username") - .add_env_var_from_secret("ADMIN_FIRSTNAME", secret, "adminUser.firstname") - .add_env_var_from_secret("ADMIN_LASTNAME", secret, "adminUser.lastname") - .add_env_var_from_secret("ADMIN_EMAIL", secret, "adminUser.email") - .add_env_var_from_secret("ADMIN_PASSWORD", secret, "adminUser.password") - // Needed by the `containerdebug` process to log it's tracing information to. - .add_env_var( - "CONTAINERDEBUG_LOG_DIRECTORY", - format!("{STACKABLE_LOG_DIR}/containerdebug"), - ) - .add_env_var("SSL_CERT_DIR", "/stackable/certs/") - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - - mkdir --parents {PYTHONPATH} - cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} - cp {STACKABLE_LOG_CONFIG_DIR}/{LOG_CONFIG_FILE} {PYTHONPATH} - - {remove_vector_shutdown_file_command} - prepare_signal_handlers - containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & - - celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid & - - wait_for_termination $! - {create_vector_shutdown_file_command} - ", - remove_vector_shutdown_file_command = - remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), - create_vector_shutdown_file_command = - create_vector_shutdown_file_command(STACKABLE_LOG_DIR), - }]) - .liveness_probe(Probe { - exec: Some(ExecAction { - command: Some(vec![ - "[ -f /tmp/celerybeat.pid ] && kill -0 $(cat /tmp/celerybeat.pid)".to_string(), - ]), - }), - initial_delay_seconds: Some(30), - period_seconds: Some(30), - timeout_seconds: Some(30), - failure_threshold: Some(3), - ..Default::default() - }) - .resources(merged_config.resources.clone().into()); - - pb.add_container(superset_cb.build()); - add_graceful_shutdown_config(merged_config, pb).context(GracefulShutdownSnafu)?; - - let metrics_container = ContainerBuilder::new("metrics") - .context(InvalidContainerNameSnafu)? - .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![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - prepare_signal_handlers - /stackable/statsd_exporter & - wait_for_termination $! - "}]) - .add_container_port(METRICS_PORT_NAME, METRICS_PORT.into()) - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("100m") - .with_cpu_limit("200m") - .with_memory_request("64Mi") - .with_memory_limit("64Mi") - .build(), - ) - .build(); - - pb.add_volumes(crate::resources::create_volumes( - &rolegroup_ref.object_name(), - merged_config.logging.containers.get(&Container::Superset), - )) - .context(AddVolumeSnafu)?; - pb.add_container(metrics_container); - - if merged_config.logging.enable_vector_agent { - match &superset - .spec - .cluster_config - .vector_aggregator_config_map_name - { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - product_logging::framework::vector_container( - resolved_product_image, - CONFIG_VOLUME_NAME, - LOG_VOLUME_NAME, - 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(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } - } - - let mut pod_template = pb.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - pod_template.merge_from(role_group.config.pod_overrides.clone()); - - let replicas = if let Some(replicas) = role_group.replicas { - if replicas > 1 { - tracing::warn! {"replicas for role `beat` set to greater `1`. Multiple beat instances are not allowed. Setting to `1` replica."} - } - 1 - } else { - 0 - }; - - Ok(Deployment { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup_ref.object_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .with_label( - Label::try_from(("restarter.stackable.tech/enabled", "true")) - .context(LabelBuildSnafu)?, - ) - .build(), - spec: Some(DeploymentSpec { - // Beat should always only be one Beat instance at a time. - // We ignore values > 1, 0 is a possible value still. - replicas: Some(replicas), - selector: LabelSelector { - match_labels: Some( - Labels::role_group_selector( - superset, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), - ..LabelSelector::default() - }, - template: pod_template, - ..DeploymentSpec::default() - }), - status: None, - }) -} diff --git a/rust/operator-binary/src/resources/listener.rs b/rust/operator-binary/src/resources/listener.rs deleted file mode 100644 index 1ef716c7..00000000 --- a/rust/operator-binary/src/resources/listener.rs +++ /dev/null @@ -1,61 +0,0 @@ -use snafu::{ResultExt, Snafu}; -use stackable_operator::{builder::meta::ObjectMetaBuilder, crd::listener, kvp::ObjectLabels}; - -use crate::crd::{APP_PORT, APP_PORT_NAME, v1alpha1}; - -pub const LISTENER_VOLUME_NAME: &str = "listener"; -pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, -} - -pub fn build_group_listener( - superset: &v1alpha1::SupersetCluster, - object_labels: ObjectLabels, - listener_class: String, - listener_group_name: String, -) -> Result { - let metadata = ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(listener_group_name) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&object_labels) - .context(MetadataBuildSnafu)? - .build(); - - let spec = listener::v1alpha1::ListenerSpec { - class_name: Some(listener_class), - ports: Some(listener_ports()), - ..Default::default() - }; - - let listener = listener::v1alpha1::Listener { - metadata, - spec, - status: None, - }; - - Ok(listener) -} - -pub fn listener_ports() -> Vec { - vec![listener::v1alpha1::ListenerPort { - name: APP_PORT_NAME.to_owned(), - port: APP_PORT.into(), - protocol: Some("TCP".to_owned()), - }] -} - -pub fn default_listener_class() -> String { - "cluster-internal".to_string() -} diff --git a/rust/operator-binary/src/resources/mod.rs b/rust/operator-binary/src/resources/mod.rs deleted file mode 100644 index 3e94d08e..00000000 --- a/rust/operator-binary/src/resources/mod.rs +++ /dev/null @@ -1,164 +0,0 @@ -use stackable_operator::{ - builder::pod::volume::VolumeBuilder, - database_connections::{ - TemplatingMechanism, - drivers::{ - celery::CeleryDatabaseConnectionDetails, - sqlalchemy::SqlAlchemyDatabaseConnectionDetails, - }, - }, - k8s_openapi::api::core::v1::{ConfigMapVolumeSource, EmptyDirVolumeSource, Volume}, - kvp::ObjectLabels, - product_logging::{ - self, - spec::{ - ConfigMapLogConfig, ContainerLogConfig, ContainerLogConfigChoice, - CustomContainerLogConfig, - }, - }, -}; - -use crate::{ - OPERATOR_NAME, - crd::{APP_NAME, databases::CeleryResultsBackendConnectionDetails}, - v1alpha1::SupersetCluster, -}; - -pub mod configmap; -pub mod deployment; -pub mod listener; -pub mod rbac; -pub mod service; -pub mod statefulset; - -use crate::crd::MAX_LOG_FILES_SIZE; - -pub const CONFIG_VOLUME_NAME: &str = "config"; -pub const LOG_CONFIG_VOLUME_NAME: &str = "log-config"; -pub const LOG_VOLUME_NAME: &str = "log"; - -/// Creates recommended `ObjectLabels` to be used in deployed resources -pub fn build_recommended_labels<'a, T>( - owner: &'a T, - controller_name: &'a str, - 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, - role, - role_group, - } -} - -pub(crate) fn create_volumes( - config_map_name: &str, - log_config: Option<&ContainerLogConfig>, -) -> Vec { - let mut volumes = Vec::new(); - - volumes.push( - VolumeBuilder::new(CONFIG_VOLUME_NAME) - .with_config_map(config_map_name) - .build(), - ); - volumes.push(Volume { - name: LOG_VOLUME_NAME.into(), - empty_dir: Some(EmptyDirVolumeSource { - medium: None, - size_limit: Some(product_logging::framework::calculate_log_volume_size_limit( - &[MAX_LOG_FILES_SIZE], - )), - }), - ..Volume::default() - }); - - if let Some(ContainerLogConfig { - choice: - Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { - custom: ConfigMapLogConfig { config_map }, - })), - }) = log_config - { - volumes.push(Volume { - name: LOG_CONFIG_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: config_map.into(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }); - } else { - volumes.push(Volume { - name: LOG_CONFIG_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: config_map_name.into(), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }); - } - - volumes -} - -pub(crate) fn metadata_database_connection_details( - superset: &SupersetCluster, -) -> SqlAlchemyDatabaseConnectionDetails { - superset - .spec - .cluster_config - .metadata_database - .sqlalchemy_connection_details_with_templating( - "METADATA", - &TemplatingMechanism::BashEnvSubstitution, - ) -} - -pub(crate) fn celery_results_backend_connection_details( - superset: &SupersetCluster, -) -> ( - Option, - Option, -) { - ( - superset - .spec - .cluster_config - .celery_results_backend - .as_ref() - .map(|backend| backend.as_python_parameters()), - superset - .spec - .cluster_config - .celery_results_backend - .as_ref() - .map(|backend| { - backend.celery_connection_details_with_templating( - "CELERY_RESULTS_BACKEND", - &TemplatingMechanism::BashEnvSubstitution, - ) - }), - ) -} - -pub(crate) fn celery_broker_connection_details( - superset: &SupersetCluster, -) -> Option { - superset - .spec - .cluster_config - .celery_broker - .as_ref() - .map(|broker| { - broker.celery_connection_details_with_templating( - "CELERY_BROKER", - &TemplatingMechanism::BashEnvSubstitution, - ) - }) -} diff --git a/rust/operator-binary/src/resources/service.rs b/rust/operator-binary/src/resources/service.rs deleted file mode 100644 index 3d677a64..00000000 --- a/rust/operator-binary/src/resources/service.rs +++ /dev/null @@ -1,160 +0,0 @@ -use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - builder::meta::ObjectMetaBuilder, - commons::product_image_selection::ResolvedProductImage, - k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, - kvp::{Annotations, Labels}, - role_utils::RoleGroupRef, -}; - -use crate::{ - controller::SUPERSET_CONTROLLER_NAME, - crd::{APP_NAME, APP_PORT, APP_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME, v1alpha1}, - resources::build_recommended_labels, -}; - -#[derive(Debug, Snafu)] -pub enum Error { - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, -} - -/// The rolegroup [`Service`] is a headless 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_node_rolegroup_headless_service( - superset: &v1alpha1::SupersetCluster, - resolved_product_image: &ResolvedProductImage, - rolegroup_ref: &RoleGroupRef, -) -> Result { - let headless_service = Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup_ref.rolegroup_headless_service_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .context(MetadataBuildSnafu)? - .build(), - spec: Some(ServiceSpec { - // Internal communication does not need to be exposed - type_: Some("ClusterIP".to_owned()), - cluster_ip: Some("None".to_owned()), - ports: Some(service_ports()), - selector: Some( - Labels::role_group_selector( - superset, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), - publish_not_ready_addresses: Some(true), - ..ServiceSpec::default() - }), - status: None, - }; - Ok(headless_service) -} - -/// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label -pub fn build_node_rolegroup_metrics_service( - superset: &v1alpha1::SupersetCluster, - resolved_product_image: &ResolvedProductImage, - rolegroup_ref: &RoleGroupRef, -) -> Result { - let metrics_service = Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup_ref.rolegroup_metrics_service_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .context(MetadataBuildSnafu)? - .with_labels(prometheus_labels()) - .with_annotations(prometheus_annotations()) - .build(), - spec: Some(ServiceSpec { - // Internal communication does not need to be exposed - type_: Some("ClusterIP".to_owned()), - cluster_ip: Some("None".to_owned()), - ports: Some(metrics_ports()), - selector: Some( - Labels::role_group_selector( - superset, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), - publish_not_ready_addresses: Some(true), - ..ServiceSpec::default() - }), - status: None, - }; - - Ok(metrics_service) -} - -fn metrics_ports() -> Vec { - vec![ServicePort { - name: Some(METRICS_PORT_NAME.to_string()), - port: METRICS_PORT.into(), - protocol: Some("TCP".to_string()), - ..ServicePort::default() - }] -} - -fn service_ports() -> Vec { - vec![ServicePort { - name: Some(APP_PORT_NAME.to_string()), - port: APP_PORT.into(), - protocol: Some("TCP".to_string()), - ..ServicePort::default() - }] -} -/// Common labels for Prometheus -fn prometheus_labels() -> Labels { - Labels::try_from([("prometheus.io/scrape", "true")]).expect("should be a valid label") -} - -/// Common annotations for Prometheus -/// -/// These annotations can be used in a ServiceMonitor. -/// -/// see also -fn prometheus_annotations() -> Annotations { - Annotations::try_from([ - ("prometheus.io/path".to_owned(), "/metrics".to_owned()), - ("prometheus.io/port".to_owned(), METRICS_PORT.to_string()), - ("prometheus.io/scheme".to_owned(), "http".to_owned()), - ("prometheus.io/scrape".to_owned(), "true".to_owned()), - ]) - .expect("should be valid annotations") -} diff --git a/rust/operator-binary/src/resources/statefulset.rs b/rust/operator-binary/src/resources/statefulset.rs deleted file mode 100644 index 751c5d87..00000000 --- a/rust/operator-binary/src/resources/statefulset.rs +++ /dev/null @@ -1,561 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -use indoc::formatdoc; -use product_config::types::PropertyNameKind; -use snafu::{OptionExt, ResultExt, Snafu}; -use stackable_operator::{ - builder::{ - meta::ObjectMetaBuilder, - pod::{ - PodBuilder, - container::ContainerBuilder, - probe::ProbeBuilder, - resources::ResourceRequirementsBuilder, - security::PodSecurityContextBuilder, - volume::{ - ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, - ListenerReference, - }, - }, - }, - commons::product_image_selection::ResolvedProductImage, - k8s_openapi::{ - DeepMerge, - api::{ - apps::v1::{StatefulSet, StatefulSetSpec}, - core::v1::EnvVar, - }, - apimachinery::pkg::apis::meta::v1::LabelSelector, - }, - kvp::{Label, Labels}, - product_logging::{ - self, - framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command}, - }, - role_utils::RoleGroupRef, - shared::time::Duration, - utils::COMMON_BASH_TRAP_FUNCTIONS, -}; - -use crate::{ - config::{commands::add_cert_to_python_certifi_command, product_logging::LOG_CONFIG_FILE}, - controller::SUPERSET_CONTROLLER_NAME, - crd::{ - APP_NAME, APP_PORT, METRICS_PORT, METRICS_PORT_NAME, PYTHONPATH, STACKABLE_CONFIG_DIR, - STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, SUPERSET_CONFIG_FILENAME, - SupersetConfigOptions, SupersetRole, - authentication::{ - SupersetAuthenticationClassResolved, SupersetClientAuthenticationDetailsResolved, - }, - v1alpha1::{Container, SupersetCluster, SupersetConfig}, - }, - operations::graceful_shutdown::add_graceful_shutdown_config, - resources::{ - CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME, build_recommended_labels, - listener::{LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME}, - }, -}; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("object defines no '{role}' role"))] - MissingRole { role: String }, - - #[snafu(display("object defines no '{role}' rolegroup"))] - MissingRoleGroup { role: String }, - - #[snafu(display("invalid container name"))] - InvalidContainerName { - source: stackable_operator::builder::pod::container::Error, - }, - - #[snafu(display("object is missing metadata to build owner reference"))] - ObjectMissingMetadataForOwnerRef { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] - VectorAggregatorConfigMapMissing, - - #[snafu(display("failed to configure graceful shutdown"))] - GracefulShutdown { - source: crate::operations::graceful_shutdown::Error, - }, - - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, - - #[snafu(display("failed to build Metadata"))] - MetadataBuild { - source: stackable_operator::builder::meta::Error, - }, - - #[snafu(display( - "failed to get the {SUPERSET_CONFIG_FILENAME} file from node or product config" - ))] - MissingSupersetConfigInNodeConfig, - - #[snafu(display("failed to get {timeout} from {SUPERSET_CONFIG_FILENAME} file. It should be set in the product config or by user input", timeout = SupersetConfigOptions::SupersetWebserverTimeout))] - MissingWebServerTimeoutInSupersetConfig, - - #[snafu(display("failed to configure logging"))] - ConfigureLogging { - source: product_logging::framework::LoggingError, - }, - - #[snafu(display("failed to add LDAP Volumes and VolumeMounts"))] - AddLdapVolumesAndVolumeMounts { - source: stackable_operator::crd::authentication::ldap::v1alpha1::Error, - }, - - #[snafu(display("failed to add TLS Volumes and VolumeMounts"))] - AddTlsVolumesAndVolumeMounts { - source: stackable_operator::commons::tls_verification::TlsClientDetailsError, - }, - - #[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("failed to build listener volume"))] - BuildListenerVolume { - source: ListenerOperatorVolumeSourceBuilderError, - }, -} - -type Result = std::result::Result; - -/// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator. -#[allow(clippy::too_many_arguments)] -pub fn build_server_rolegroup_statefulset( - superset: &SupersetCluster, - resolved_product_image: &ResolvedProductImage, - superset_role: &SupersetRole, - rolegroup_ref: &RoleGroupRef, - node_config: &HashMap>, - authentication_config: &SupersetClientAuthenticationDetailsResolved, - sa_name: &str, - merged_config: &SupersetConfig, -) -> Result { - let role = superset - .get_role(superset_role) - .with_context(|| MissingRoleSnafu { - role: superset_role.to_string(), - })?; - let role_group = role - .role_groups - .get(&rolegroup_ref.role_group) - .with_context(|| MissingRoleGroupSnafu { - role: superset_role.to_string(), - })?; - - let recommended_object_labels = build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ); - // Used for PVC templates that cannot be modified once they are deployed - let unversioned_recommended_labels = Labels::recommended(&build_recommended_labels( - superset, - SUPERSET_CONTROLLER_NAME, - // A version value is required, and we do want to use the "recommended" format for the other desired labels - "none", - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .context(LabelBuildSnafu)?; - - let metadata = ObjectMetaBuilder::new() - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .build(); - - let mut pb = &mut PodBuilder::new(); - - pb = pb - .metadata(metadata) - .image_pull_secrets_from_product_image(resolved_product_image) - .security_context( - PodSecurityContextBuilder::new() - .fs_group(1000) // Needed for secret-operator - .build(), - ) - .affinity(&merged_config.affinity) - .service_account_name(sa_name); - - let mut superset_cb = ContainerBuilder::new(&Container::Superset.to_string()) - .context(InvalidContainerNameSnafu)?; - - let metadata_database_connection_details = - super::metadata_database_connection_details(superset); - let celery_results_backend_connection_details = - super::celery_results_backend_connection_details(superset); - let celery_broker_connection_details = super::celery_broker_connection_details(superset); - - metadata_database_connection_details.add_to_container(&mut superset_cb); - if let (_, Some(celery_results_backend_connection_details)) = - &celery_results_backend_connection_details - { - celery_results_backend_connection_details.add_to_container(&mut superset_cb); - } - if let Some(celery_broker_connection_details) = celery_broker_connection_details { - celery_broker_connection_details.add_to_container(&mut superset_cb); - } - - for (name, value) in node_config - .get(&PropertyNameKind::Env) - .cloned() - .unwrap_or_default() - { - if name == SupersetConfig::MAPBOX_SECRET_PROPERTY { - superset_cb.add_env_var_from_secret( - "MAPBOX_API_KEY", - &value, - "connections.mapboxApiKey", - ); - } else { - superset_cb.add_env_var(name, value); - }; - } - - // SECRET_KEY from auto-generated secret - superset_cb.add_env_var_from_secret( - "SECRET_KEY", - superset.shared_secret_key_secret_name(), - crate::crd::INTERNAL_SECRET_SECRET_KEY, - ); - - add_authentication_volumes_and_volume_mounts(authentication_config, &mut superset_cb, pb)?; - - let webserver_timeout = node_config - .get(&PropertyNameKind::File( - SUPERSET_CONFIG_FILENAME.to_string(), - )) - .context(MissingSupersetConfigInNodeConfigSnafu)? - .get(&SupersetConfigOptions::SupersetWebserverTimeout.to_string()) - .context(MissingWebServerTimeoutInSupersetConfigSnafu)?; - - let secret = &superset.spec.cluster_config.credentials_secret_name; - - superset_cb - .image_from_product_image(resolved_product_image) - .add_container_port("http", APP_PORT.into()) - .add_volume_mount(CONFIG_VOLUME_NAME, STACKABLE_CONFIG_DIR).context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_CONFIG_VOLUME_NAME, STACKABLE_LOG_CONFIG_DIR).context(AddVolumeMountSnafu)? - .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR).context(AddVolumeMountSnafu)? - .add_env_var_from_secret("ADMIN_USERNAME", secret, "adminUser.username") - .add_env_var_from_secret("ADMIN_FIRSTNAME", secret, "adminUser.firstname") - .add_env_var_from_secret("ADMIN_LASTNAME", secret, "adminUser.lastname") - .add_env_var_from_secret("ADMIN_EMAIL", secret, "adminUser.email") - .add_env_var_from_secret("ADMIN_PASSWORD", secret, "adminUser.password") - // Needed by the `containerdebug` process to log it's tracing information to. - .add_env_var("CONTAINERDEBUG_LOG_DIRECTORY", format!("{STACKABLE_LOG_DIR}/containerdebug")) - .add_env_var("SSL_CERT_DIR", "/stackable/certs/") - .add_env_vars(authentication_env_vars(authentication_config)) - .command(vec![ - "/bin/bash".to_string(), - "-x".to_string(), - "-euo".to_string(), - "pipefail".to_string(), - "-c".to_string(), - ]) - .args(vec![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - - mkdir --parents {PYTHONPATH} - cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} - cp {STACKABLE_LOG_CONFIG_DIR}/{LOG_CONFIG_FILE} {PYTHONPATH} - - {auth_commands} - - superset db upgrade - set +x - echo 'Running \"superset fab create-admin [...]\", which is not shown as it leaks the Superset admin credentials' - superset fab create-admin --username \"$ADMIN_USERNAME\" --firstname \"$ADMIN_FIRSTNAME\" --lastname \"$ADMIN_LASTNAME\" --email \"$ADMIN_EMAIL\" --password \"$ADMIN_PASSWORD\" - set -x - superset init - - {remove_vector_shutdown_file_command} - prepare_signal_handlers - containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & - gunicorn --bind 0.0.0.0:${{SUPERSET_PORT}} --worker-class gthread --threads 20 --timeout {webserver_timeout} --limit-request-line 0 --limit-request-field_size 0 'superset.app:create_app()' & - wait_for_termination $! - - {create_vector_shutdown_file_command} - ", - auth_commands = authentication_start_commands(authentication_config), - remove_vector_shutdown_file_command = - remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), - create_vector_shutdown_file_command = - create_vector_shutdown_file_command(STACKABLE_LOG_DIR), - }]) - .resources(merged_config.resources.clone().into()); - add_superset_container_probes(&mut superset_cb); - - // listener endpoints will use persistent volumes - // so that load balancers can hard-code the target addresses and - // that it is possible to connect to a consistent address - let pvcs = if let Some(group_listener_name) = superset.group_listener_name(superset_role) { - let pvc = ListenerOperatorVolumeSourceBuilder::new( - &ListenerReference::ListenerName(group_listener_name), - &unversioned_recommended_labels, - ) - .build_pvc(LISTENER_VOLUME_NAME.to_owned()) - .context(BuildListenerVolumeSnafu)?; - Some(vec![pvc]) - } else { - None - }; - - superset_cb - .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) - .context(AddVolumeMountSnafu)?; - - pb.add_container(superset_cb.build()); - add_graceful_shutdown_config(merged_config, pb).context(GracefulShutdownSnafu)?; - - let metrics_container = ContainerBuilder::new("metrics") - .context(InvalidContainerNameSnafu)? - .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![formatdoc! {" - {COMMON_BASH_TRAP_FUNCTIONS} - - prepare_signal_handlers - /stackable/statsd_exporter & - wait_for_termination $! - "}]) - .add_container_port(METRICS_PORT_NAME, METRICS_PORT.into()) - .resources( - ResourceRequirementsBuilder::new() - .with_cpu_request("100m") - .with_cpu_limit("200m") - .with_memory_request("64Mi") - .with_memory_limit("64Mi") - .build(), - ) - .build(); - - pb.add_volumes(crate::resources::create_volumes( - &rolegroup_ref.object_name(), - merged_config.logging.containers.get(&Container::Superset), - )) - .context(AddVolumeSnafu)?; - pb.add_container(metrics_container); - - if merged_config.logging.enable_vector_agent { - match &superset - .spec - .cluster_config - .vector_aggregator_config_map_name - { - Some(vector_aggregator_config_map_name) => { - pb.add_container( - product_logging::framework::vector_container( - resolved_product_image, - CONFIG_VOLUME_NAME, - LOG_VOLUME_NAME, - 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(ConfigureLoggingSnafu)?, - ); - } - None => { - VectorAggregatorConfigMapMissingSnafu.fail()?; - } - } - } - - let mut pod_template = pb.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - pod_template.merge_from(role_group.config.pod_overrides.clone()); - - Ok(StatefulSet { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(superset) - .name(rolegroup_ref.object_name()) - .ownerreference_from_resource(superset, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&recommended_object_labels) - .context(MetadataBuildSnafu)? - .with_label( - Label::try_from(("restarter.stackable.tech/enabled", "true")) - .context(LabelBuildSnafu)?, - ) - .build(), - spec: Some(StatefulSetSpec { - // Set to `OrderedReady`, to make sure Pods start after another and the init commands don't run in parallel - pod_management_policy: Some("OrderedReady".to_string()), - replicas: role_group.replicas.map(i32::from), - selector: LabelSelector { - match_labels: Some( - Labels::role_group_selector( - superset, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - ) - .context(LabelBuildSnafu)? - .into(), - ), - ..LabelSelector::default() - }, - service_name: Some(rolegroup_ref.rolegroup_headless_service_name()), - template: pod_template, - volume_claim_templates: pvcs, - ..StatefulSetSpec::default() - }), - status: None, - }) -} - -fn add_superset_container_probes(superset_cb: &mut ContainerBuilder) { - let common = - ProbeBuilder::http_get_port_scheme_path(APP_PORT, None, Some("/health".to_owned())) - .with_period(Duration::from_secs(5)); - - superset_cb.startup_probe( - common - .clone() - .with_failure_threshold_duration(Duration::from_minutes_unchecked(10)) - .expect("const period is non-zero") - .build() - .expect("const duration does not overflow"), - ); - - // Remove it from the Service immediately - superset_cb.readiness_probe( - common - .clone() - .build() - .expect("const duration does not overflow"), - ); - // But only restart it after 3 failures - superset_cb.liveness_probe( - common - .with_failure_threshold(3) - .build() - .expect("const duration does not overflow"), - ); -} - -fn add_authentication_volumes_and_volume_mounts( - auth_config: &SupersetClientAuthenticationDetailsResolved, - cb: &mut ContainerBuilder, - pb: &mut PodBuilder, -) -> Result<()> { - // Different authentication entries can reference the same secret - // class or TLS certificate. It must be ensured that the volumes - // and volume mounts are only added once in such a case. - - let mut ldap_authentication_providers = BTreeSet::new(); - let mut tls_client_credentials = BTreeSet::new(); - - for auth_class_resolved in &auth_config.authentication_classes_resolved { - match auth_class_resolved { - SupersetAuthenticationClassResolved::Ldap { provider } => { - ldap_authentication_providers.insert(provider); - } - SupersetAuthenticationClassResolved::Oidc { provider, .. } => { - tls_client_credentials.insert(&provider.tls); - } - } - } - - for provider in ldap_authentication_providers { - provider - .add_volumes_and_mounts(pb, vec![cb]) - .context(AddLdapVolumesAndVolumeMountsSnafu)?; - } - - for tls in tls_client_credentials { - tls.add_volumes_and_mounts(pb, vec![cb]) - .context(AddTlsVolumesAndVolumeMountsSnafu)?; - } - - Ok(()) -} - -fn authentication_env_vars( - auth_config: &SupersetClientAuthenticationDetailsResolved, -) -> Vec { - // Different OIDC authentication entries can reference the same - // client secret. It must be ensured that the env variables are only - // added once in such a case. - - let mut oidc_client_credentials_secrets = BTreeSet::new(); - - for auth_class_resolved in &auth_config.authentication_classes_resolved { - match auth_class_resolved { - SupersetAuthenticationClassResolved::Ldap { .. } => {} - SupersetAuthenticationClassResolved::Oidc { - client_auth_options: oidc, - .. - } => { - oidc_client_credentials_secrets - .insert(oidc.client_credentials_secret_ref.to_owned()); - } - } - } - - oidc_client_credentials_secrets - .iter() - .cloned() - .flat_map(stackable_operator::crd::authentication::oidc::v1alpha1::AuthenticationProvider::client_credentials_env_var_mounts) - .collect() -} - -fn authentication_start_commands( - auth_config: &SupersetClientAuthenticationDetailsResolved, -) -> String { - let mut commands = Vec::new(); - - let mut tls_client_credentials = BTreeSet::new(); - - for auth_class_resolved in &auth_config.authentication_classes_resolved { - match auth_class_resolved { - SupersetAuthenticationClassResolved::Oidc { provider, .. } => { - tls_client_credentials.insert(&provider.tls); - - // WebPKI will be handled implicitly - } - SupersetAuthenticationClassResolved::Ldap { .. } => {} - } - } - - for tls in tls_client_credentials { - commands.push(tls.tls_ca_cert_mount_path().map(|tls_ca_cert_mount_path| { - add_cert_to_python_certifi_command(&tls_ca_cert_mount_path) - })); - } - - commands - .iter() - .flatten() - .cloned() - .collect::>() - .join("\n") -} diff --git a/tests/templates/kuttl/logging/21-assert.yaml b/tests/templates/kuttl/logging/21-assert.yaml index 5c631e27..0e874b95 100644 --- a/tests/templates/kuttl/logging/21-assert.yaml +++ b/tests/templates/kuttl/logging/21-assert.yaml @@ -8,7 +8,7 @@ timeout: 300 apiVersion: apps/v1 kind: StatefulSet metadata: - name: superset-node-automatic-log-config + name: superset-node-automatic-log status: readyReplicas: 1 replicas: 1 @@ -16,7 +16,7 @@ status: apiVersion: apps/v1 kind: StatefulSet metadata: - name: superset-node-custom-log-config + name: superset-node-custom-log status: readyReplicas: 1 replicas: 1 diff --git a/tests/templates/kuttl/logging/21-install-superset.yaml.j2 b/tests/templates/kuttl/logging/21-install-superset.yaml.j2 index c0124276..643f7b60 100644 --- a/tests/templates/kuttl/logging/21-install-superset.yaml.j2 +++ b/tests/templates/kuttl/logging/21-install-superset.yaml.j2 @@ -76,7 +76,7 @@ spec: vectorAggregatorConfigMapName: superset-vector-aggregator-discovery nodes: roleGroups: - automatic-log-config: + automatic-log: replicas: 1 config: logging: @@ -109,7 +109,7 @@ spec: - name: prepared-logs configMap: name: prepared-logs - custom-log-config: + custom-log: replicas: 1 config: logging: diff --git a/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 b/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 index 7e20db1c..c9a51996 100644 --- a/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 +++ b/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 @@ -28,25 +28,25 @@ customConfig: type: filter inputs: [validEvents] condition: >- - .pod == "superset-node-automatic-log-config-0" && + .pod == "superset-node-automatic-log-0" && .container == "superset" filteredAutomaticLogConfigVector: type: filter inputs: [validEvents] condition: >- - .pod == "superset-node-automatic-log-config-0" && + .pod == "superset-node-automatic-log-0" && .container == "vector" filteredCustomLogConfigSuperset: type: filter inputs: [validEvents] condition: >- - .pod == "superset-node-custom-log-config-0" && + .pod == "superset-node-custom-log-0" && .container == "superset" filteredCustomLogConfigVector: type: filter inputs: [validEvents] condition: >- - .pod == "superset-node-custom-log-config-0" && + .pod == "superset-node-custom-log-0" && .container == "vector" filteredInvalidEvents: type: filter diff --git a/tests/templates/kuttl/resources/20-assert.yaml.j2 b/tests/templates/kuttl/resources/20-assert.yaml.j2 index 1aafc064..4d3ec463 100644 --- a/tests/templates/kuttl/resources/20-assert.yaml.j2 +++ b/tests/templates/kuttl/resources/20-assert.yaml.j2 @@ -8,7 +8,7 @@ timeout: 300 apiVersion: apps/v1 kind: StatefulSet metadata: - name: superset-node-resources-from-role + name: superset-node-from-role spec: template: spec: @@ -32,7 +32,7 @@ status: apiVersion: apps/v1 kind: StatefulSet metadata: - name: superset-node-resources-from-rolegroup + name: superset-node-from-rolegroup spec: template: spec: @@ -56,7 +56,7 @@ status: apiVersion: apps/v1 kind: StatefulSet metadata: - name: superset-node-resources-from-pod-overrides + name: superset-node-from-overrides spec: template: spec: diff --git a/tests/templates/kuttl/resources/20-install-superset.yaml.j2 b/tests/templates/kuttl/resources/20-install-superset.yaml.j2 index 98e226ea..165f3998 100644 --- a/tests/templates/kuttl/resources/20-install-superset.yaml.j2 +++ b/tests/templates/kuttl/resources/20-install-superset.yaml.j2 @@ -57,9 +57,9 @@ spec: memory: limit: 1Gi roleGroups: - resources-from-role: + from-role: replicas: 1 - resources-from-rolegroup: + from-rolegroup: replicas: 1 config: resources: @@ -68,7 +68,7 @@ spec: max: "3" memory: limit: 3Gi - resources-from-pod-overrides: + from-overrides: podOverrides: spec: containers: diff --git a/tests/templates/kuttl/smoke/33-assert.yaml.j2 b/tests/templates/kuttl/smoke/33-assert.yaml.j2 index 21956629..46e5d8ad 100644 --- a/tests/templates/kuttl/smoke/33-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/33-assert.yaml.j2 @@ -62,7 +62,36 @@ spec: - name: superset - name: metrics {% if vector_enabled %} + # Unlike the superset container (whose env the operator interleaves), the vector + # container's env is set entirely by the v2 `vector_container` builder in a fixed, + # alphabetically-sorted order, so it can be asserted positionally. - name: vector + env: + - name: CLUSTER_NAME + value: superset + - name: DATA_DIR + value: /stackable/log/_vector-state + - name: LOG_DIR + value: /stackable/log + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: ROLE_GROUP_NAME + value: default + - name: ROLE_NAME + value: node + - name: VECTOR_AGGREGATOR_ADDRESS + valueFrom: + configMapKeyRef: + key: ADDRESS + name: vector-aggregator-discovery + - name: VECTOR_CONFIG_YAML + value: /stackable/config/vector.yaml + - name: VECTOR_FILE_LOG_LEVEL + value: info + - name: VECTOR_LOG + value: info {% endif %} status: readyReplicas: 1 diff --git a/tests/templates/kuttl/smoke/34-assert.yaml.j2 b/tests/templates/kuttl/smoke/34-assert.yaml.j2 index abc822dc..c652bb8d 100644 --- a/tests/templates/kuttl/smoke/34-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/34-assert.yaml.j2 @@ -18,7 +18,7 @@ kind: TestAssert timeout: 60 commands: - script: | - expected=$(cat <<'YAMLEOF' | envsubst '$NAMESPACE' | yq -o=json + expected=$(cat <<'YAMLEOF' | yq -o=json log_config.py: |+ import flask.config import logging @@ -87,6 +87,7 @@ commands: LOGGING_CONFIGURATOR = StackableLoggingConfigurator() MAPBOX_API_KEY = os.environ.get('MAPBOX_API_KEY', '') RECAPTCHA_PUBLIC_KEY = '' + ROW_LIMIT = 50000 SECRET_KEY = os.environ.get('SECRET_KEY') SQLALCHEMY_DATABASE_URI = os.path.expandvars('postgresql+psycopg2://${METADATA_DATABASE_USERNAME}:${METADATA_DATABASE_PASSWORD}@superset-postgresql:5432/superset') STATS_LOGGER = StatsdStatsLogger(host='0.0.0.0', port=9125) @@ -96,211 +97,33 @@ commands: {% 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 @@ -584,69 +207,7 @@ commands: } } - 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_* @@ -656,14 +217,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 +245,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 = "superset" - .role = "node" - .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 )