diff --git a/Cargo.lock b/Cargo.lock index 1e911faf..1f53ed3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arc-swap" @@ -179,18 +179,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -207,9 +207,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "zeroize", @@ -217,9 +217,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", @@ -315,7 +315,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -326,7 +326,7 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -337,9 +337,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -374,15 +374,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", "clap_derive", @@ -439,9 +439,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -451,14 +451,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -483,12 +483,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "crossbeam-utils", + "bytes", + "memchr", ] [[package]] @@ -587,7 +588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d4ddf7139e64dc916b11d434421031bcc5ba02e521a49a011652a0f68775188" dependencies = [ "anyhow", - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "libgssapi", "windows", @@ -659,7 +660,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -670,7 +671,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -710,7 +711,7 @@ dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -719,7 +720,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -730,7 +731,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -754,7 +755,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -781,7 +782,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -804,7 +805,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -862,14 +863,14 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -935,7 +936,7 @@ checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -956,11 +957,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -977,9 +977,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "ff" @@ -1067,9 +1067,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1082,9 +1082,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1092,15 +1092,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1109,38 +1109,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -1206,7 +1206,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "libc", "libgit2-sys", "log", @@ -1214,9 +1214,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "gloo-timers" @@ -1232,9 +1232,9 @@ dependencies = [ [[package]] name = "granit-parser" -version = "0.0.3" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50ba32164f9e098d5da618776a32afbb32270adcbe3d3d006107dae11e37c91" +checksum = "d03f81ad4732830d85cfd417a9f62cde6dadda4354d37d078a6084a19560aa2d" dependencies = [ "arraydeque", "smallvec", @@ -1331,9 +1331,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1341,9 +1341,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -1372,9 +1372,9 @@ checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1615,6 +1615,23 @@ dependencies = [ "rustversion", ] +[[package]] +name = "info-fetcher-commons" +version = "0.0.0-dev" +dependencies = [ + "axum", + "hyper", + "native-tls", + "reqwest", + "rustls-pki-types", + "serde", + "serde_json", + "snafu 0.9.2", + "stackable-operator", + "tokio", + "tracing", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1636,6 +1653,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.15.0" @@ -1664,11 +1690,12 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt", + "jiff-core", "jiff-static", "jiff-tzdb-platform", "log", @@ -1678,22 +1705,32 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + [[package]] name = "jiff-static" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "jiff-tzdb" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142247df1a93c2b3587402a19710be3e6e942f1581a1702e76408f2c21d6590" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" [[package]] name = "jiff-tzdb-platform" @@ -1704,6 +1741,55 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.19", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "jobserver" version = "0.1.35" @@ -1735,20 +1821,20 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "jsonpath-rust" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633a7320c4bb672863a3782e89b9094ad70285e097ff6832cddd0ec615beadfa" +checksum = "a2dbe0623574defe58ba113596c848797f131727e8f54d4b4d10793e1fe14d40" dependencies = [ "pest", "pest_derive", "regex", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1781,7 +1867,7 @@ source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-ope dependencies = [ "darling", "regex", - "snafu 0.9.1", + "snafu 0.9.2", ] [[package]] @@ -1819,9 +1905,9 @@ dependencies = [ [[package]] name = "kube" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb9108095346a7096d11feeaff419c75dddcac1b2f59acb38d7bf3d13c3e146" +checksum = "208d7fe1380066abb194812a8a8e303cb896a33bb3d7b3177b71bd03ff39bf18" dependencies = [ "k8s-openapi", "kube-client", @@ -1832,9 +1918,9 @@ dependencies = [ [[package]] name = "kube-client" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f628e05bc2264c21fe10d3d675117dc9b43ea3bf4fb07262a222679757537b" +checksum = "31e940a73033a7c5c7918b5ece7851d84571b58be25e937198da37fa13f116d3" dependencies = [ "base64", "bytes", @@ -1853,11 +1939,12 @@ dependencies = [ "kube-core", "pem", "rustls", + "rustls-platform-verifier", "secrecy", "serde", "serde-saphyr", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-util", "tower", @@ -1867,9 +1954,9 @@ dependencies = [ [[package]] name = "kube-core" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b02f5933ba06140d58c7d6727f6c319f0962ec6a344aa5e21e475e891deaa8" +checksum = "a9d2353c118cf3462c352ee0b5bd5b0cf17990af456dfd8662d136ff9812eeb4" dependencies = [ "derive_more", "form_urlencoded", @@ -1881,28 +1968,28 @@ dependencies = [ "serde", "serde-value", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "kube-derive" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe171898707dadf1818ef94e81ef57f6beb7edf9ba87b9e814c045dad356c7aa" +checksum = "92141c19e1fa83bf91633c1234c66b03375c29aa8ca5486331ddb47e3a3da9c0" dependencies = [ "darling", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "kube-runtime" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ddec66c540c7cf29a5b41fe4a657a53687f95c346e03bdf00585b70a1bab21" +checksum = "3eb064ef71c7bea55e934a443960da9e9ec31fbb2ba63e47e4aeca32603d5cc7" dependencies = [ "ahash", "async-broadcast", @@ -1919,7 +2006,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-util", "tracing", @@ -1962,7 +2049,7 @@ dependencies = [ "nom", "percent-encoding", "ring", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-native-tls", "tokio-stream", @@ -1972,15 +2059,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libgit2-sys" -version = "0.18.5+1.9.4" +version = "0.18.7+1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" +checksum = "23c7391e4b9f4ffab1a624223cc1d7385ff9a678f490768add717de7ea2f4d89" dependencies = [ "cc", "libc", @@ -1994,7 +2081,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "834339e86b2561169d45d3b01741967fee3e5716c7d0b6e33cd4e3b34c9558cd" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "lazy_static", "libgssapi-sys", @@ -2080,6 +2167,12 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + [[package]] name = "memchr" version = "2.8.3" @@ -2110,9 +2203,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -2192,7 +2285,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -2260,7 +2353,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -2276,7 +2369,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2307,7 +2400,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", ] @@ -2349,7 +2442,7 @@ dependencies = [ "opentelemetry_sdk", "prost", "reqwest", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tonic", "tonic-types", @@ -2386,8 +2479,8 @@ dependencies = [ "opentelemetry", "percent-encoding", "portable-atomic", - "rand 0.9.4", - "thiserror 2.0.18", + "rand 0.9.5", + "thiserror 2.0.19", "tokio", "tokio-stream", ] @@ -2469,9 +2562,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -2479,9 +2572,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -2489,22 +2582,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -2526,7 +2619,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2564,9 +2657,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" @@ -2608,7 +2701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2622,9 +2715,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2646,10 +2739,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2663,9 +2756,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2684,9 +2777,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -2694,9 +2787,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2746,34 +2839,34 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2783,9 +2876,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -2906,7 +2999,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -2915,9 +3008,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -2943,13 +3036,40 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -2974,6 +3094,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -3006,7 +3135,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3044,7 +3173,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3069,9 +3198,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -3079,9 +3208,9 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "0.0.27" +version = "0.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3" +checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4" dependencies = [ "ahash", "annotate-snippets", @@ -3091,7 +3220,7 @@ dependencies = [ "granit-parser", "nohash-hasher", "num-traits", - "serde", + "serde_core", "smallvec", "zmij", ] @@ -3108,22 +3237,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -3134,14 +3263,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -3188,9 +3317,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures", @@ -3251,9 +3380,25 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" @@ -3288,11 +3433,11 @@ dependencies = [ [[package]] name = "snafu" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522" +checksum = "e45cb604038abb7b926b679887b3226d8d0f23874b66623625a0454be425a4b7" dependencies = [ - "snafu-derive 0.9.1", + "snafu-derive 0.9.2", ] [[package]] @@ -3315,26 +3460,26 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "snafu-derive" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" +checksum = "287f59010008f0d7cf5e3b03196d666c1acc46c8d3e9cf34c28a1a7157601e72" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3372,12 +3517,12 @@ dependencies = [ "k8s-openapi", "kube", "p256", - "rand 0.9.4", + "rand 0.9.5", "rand_core 0.6.4", "rsa", "sha2", "signature", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-shared", "tokio", "tokio-rustls", @@ -3397,7 +3542,7 @@ dependencies = [ "futures", "hyper", "rustls", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-opa-regorule-library", "stackable-operator", "tar", @@ -3421,7 +3566,7 @@ dependencies = [ "semver", "serde", "serde_json", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-operator", "strum", "tokio", @@ -3432,6 +3577,29 @@ dependencies = [ name = "stackable-opa-regorule-library" version = "0.0.0-dev" +[[package]] +name = "stackable-opa-resource-info-fetcher" +version = "0.0.0-dev" +dependencies = [ + "axum", + "built", + "clap", + "futures", + "hyper", + "info-fetcher-commons", + "md5", + "moka", + "reqwest", + "serde", + "serde_json", + "snafu 0.9.2", + "stackable-opa-operator", + "stackable-operator", + "tokio", + "tracing", + "url", +] + [[package]] name = "stackable-opa-user-info-fetcher" version = "0.0.0-dev" @@ -3443,6 +3611,7 @@ dependencies = [ "clap", "futures", "hyper", + "info-fetcher-commons", "krb5", "ldap3", "moka", @@ -3453,7 +3622,7 @@ dependencies = [ "semver", "serde", "serde_json", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-opa-operator", "stackable-operator", "tokio", @@ -3483,7 +3652,7 @@ dependencies = [ "json-patch", "k8s-openapi", "kube", - "rand 0.9.4", + "rand 0.9.5", "regex", "schemars", "semver", @@ -3491,7 +3660,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha2", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-operator-derive", "stackable-shared", "stackable-telemetry", @@ -3515,7 +3684,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3530,7 +3699,7 @@ dependencies = [ "semver", "serde", "serde_yaml", - "snafu 0.9.1", + "snafu 0.9.2", "strum", "time", ] @@ -3549,7 +3718,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", - "snafu 0.9.1", + "snafu 0.9.2", "strum", "tokio", "tower", @@ -3569,7 +3738,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-versioned-macros", ] @@ -3588,7 +3757,7 @@ dependencies = [ "kube", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3607,10 +3776,10 @@ dependencies = [ "kube", "opentelemetry", "opentelemetry-semantic-conventions", - "rand 0.9.4", + "rand 0.9.5", "serde", "serde_json", - "snafu 0.9.1", + "snafu 0.9.2", "stackable-certs", "stackable-shared", "stackable-telemetry", @@ -3647,7 +3816,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3675,9 +3844,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3701,7 +3881,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3710,7 +3890,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3766,11 +3946,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3781,34 +3961,34 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.53" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", "num-conv", @@ -3826,9 +4006,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -3862,14 +4042,14 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3884,13 +4064,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3915,9 +4095,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -3926,13 +4106,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "slab", "tokio", @@ -4013,7 +4194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-util", "http", @@ -4033,7 +4214,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "http", "http-body", @@ -4076,7 +4257,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "tracing-subscriber", ] @@ -4089,7 +4270,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4241,9 +4422,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -4268,6 +4449,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4334,7 +4525,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -4367,6 +4558,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows" version = "0.61.3" @@ -4434,7 +4643,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4445,7 +4654,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4692,28 +4901,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4733,7 +4942,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] @@ -4754,7 +4963,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4787,11 +4996,11 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.nix b/Cargo.nix index 52460d0c..818f9822 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -43,6 +43,16 @@ rec { # You can override the features with # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }. workspaceMembers = { + "info-fetcher-commons" = rec { + packageId = "info-fetcher-commons"; + build = internal.buildRustCrateWithFeatures { + packageId = "info-fetcher-commons"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; "stackable-opa-bundle-builder" = rec { packageId = "stackable-opa-bundle-builder"; build = internal.buildRustCrateWithFeatures { @@ -73,6 +83,16 @@ rec { # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; }; + "stackable-opa-resource-info-fetcher" = rec { + packageId = "stackable-opa-resource-info-fetcher"; + build = internal.buildRustCrateWithFeatures { + packageId = "stackable-opa-resource-info-fetcher"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; "stackable-opa-user-info-fetcher" = rec { packageId = "stackable-opa-user-info-fetcher"; build = internal.buildRustCrateWithFeatures { @@ -380,9 +400,9 @@ rec { }; "anyhow" = rec { crateName = "anyhow"; - version = "1.0.103"; + version = "1.0.104"; edition = "2021"; - sha256 = "1wsav2g6vxcvf2c0fv3jhxfr55l0p2g8nygy7rmmvcsfwgi8ahra"; + sha256 = "0w34jjcm02p5g9kvsjr1dvpw0zs2fi7igi6nr414fkm5gz85w2ik"; authors = [ "David Tolnay " ]; @@ -559,7 +579,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "visit-mut" ]; } ]; @@ -567,9 +587,9 @@ rec { }; "async-trait" = rec { crateName = "async-trait"; - version = "0.1.89"; + version = "0.1.91"; edition = "2021"; - sha256 = "1fsxxmz3rzx1prn1h3rs7kyjhkap60i7xvi0ldapkvbb14nssdch"; + sha256 = "1v3cm8mzg66037wm392p1vsdx0lq8bid6y2ivr7z03lpfx0xqdmf"; procMacro = true; libName = "async_trait"; authors = [ @@ -586,7 +606,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 3.0.3"; usesDefaultFeatures = false; features = [ "clone-impls" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } @@ -619,10 +639,10 @@ rec { }; "aws-lc-rs" = rec { crateName = "aws-lc-rs"; - version = "1.17.1"; + version = "1.17.3"; edition = "2021"; - links = "aws_lc_rs_1_17_1_sys"; - sha256 = "1b9j3rrl3bxi27y9qwfi5hm8h337q1hj4ab03jdxvrf7gy9xhhj3"; + links = "aws_lc_rs_1_17_3_sys"; + sha256 = "1wbj1n78iqsf38xd2q93isjkb1iyaf7akm3wrji8ri6s33dbbg80"; libName = "aws_lc_rs"; authors = [ "AWS-LibCrypto" @@ -655,10 +675,10 @@ rec { }; "aws-lc-sys" = rec { crateName = "aws-lc-sys"; - version = "0.42.0"; + version = "0.43.0"; edition = "2021"; - links = "aws_lc_0_42_0"; - sha256 = "0i0l6q3xf0p5v1raxd718ccxl06dviws9kpwyh97ll1im4fyp73d"; + links = "aws_lc_0_43_0"; + sha256 = "0k12q9axgpzhqj5q5ics2m302fnbr4pp4pipi9kn5zknril32423"; build = "builder/main.rs"; libName = "aws_lc_sys"; authors = [ @@ -1034,7 +1054,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "cexpr"; @@ -1086,7 +1106,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "extra-traits" "visit-mut" ]; } ]; @@ -1116,11 +1136,11 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "bitflags 2.13.0" = rec { + "bitflags 2.13.1" = rec { crateName = "bitflags"; - version = "2.13.0"; + version = "2.13.1"; edition = "2021"; - sha256 = "1y239gpvl061rfvav7jds8mjs42kmwi39is7yx5d1qw3hvp8nf5l"; + sha256 = "1nl76mpykmwmb8rq1l5vw1azdh1wvxdrnsk4sy3rdrzx01nvg25m"; authors = [ "The Rust Project Developers" ]; @@ -1212,9 +1232,9 @@ rec { }; "bytes" = rec { crateName = "bytes"; - version = "1.12.0"; + version = "1.12.1"; edition = "2021"; - sha256 = "14xmxm8imyvw675bsgyadmzm9k63js1sdqh7099p0hlj2p9zbqwa"; + sha256 = "017z19dpg4f942h051m7bpnzcgng042hhcpd7bmg7bjjqd42lrgw"; authors = [ "Carl Lerche " "Sean McArthur " @@ -1228,9 +1248,9 @@ rec { }; "cc" = rec { crateName = "cc"; - version = "1.2.66"; + version = "1.4.0"; edition = "2018"; - sha256 = "15nr9bpbcinb9z7zvr1d70xyivv8969v490001qdjywrjg3wmmpm"; + sha256 = "1fc26n76n7gr37m2q0xw5l8jpn4sd33hvyppmwhv6v4fcyxq3pas"; authors = [ "Alex Crichton " ]; @@ -1408,10 +1428,10 @@ rec { }; "clap" = rec { crateName = "clap"; - version = "4.6.1"; + version = "4.6.4"; edition = "2024"; crateBin = []; - sha256 = "0lcf88l7vlg796rrqr7wipbbmfa5sgsgx4211b7xmxxv8dz13nqx"; + sha256 = "1dz5brl39xchkml0khm348i87w89misprlinmnk77vwjawa0q7nr"; dependencies = [ { name = "clap_builder"; @@ -1450,9 +1470,9 @@ rec { }; "clap_builder" = rec { crateName = "clap_builder"; - version = "4.6.0"; + version = "4.6.2"; edition = "2024"; - sha256 = "17q6np22yxhh5y5v53y4l31ps3hlaz45mvz2n2nicr7n3c056jki"; + sha256 = "12sl6fyj6w2djxj0lsc1lkj1h3wpx74fjhb37izvaf65vjpji5ph"; dependencies = [ { name = "anstream"; @@ -1489,9 +1509,9 @@ rec { }; "clap_derive" = rec { crateName = "clap_derive"; - version = "4.6.1"; + version = "4.6.4"; edition = "2024"; - sha256 = "1acpz49hi00iv9jkapixjzcv7s51x8qkfaqscjm36rqgf428dkpj"; + sha256 = "0qd0v7pa2arwxjjinmjim6xrjy61bc28m1yryhc7zjjssswx44nh"; procMacro = true; dependencies = [ { @@ -1508,7 +1528,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 3.0.3"; features = [ "full" ]; } ]; @@ -1549,30 +1569,52 @@ rec { sha256 = "0w75k89hw39p0mnnhlrwr23q50rza1yjki44qvh2mgrnj065a1qx"; }; - "concurrent-queue" = rec { - crateName = "concurrent-queue"; - version = "2.5.0"; - edition = "2021"; - sha256 = "0wrr3mzq2ijdkxwndhf79k952cp4zkz35ray8hvsxl96xrx1k82c"; - libName = "concurrent_queue"; + "combine" = rec { + crateName = "combine"; + version = "4.6.7"; + edition = "2018"; + sha256 = "1z8rh8wp59gf8k23ar010phgs0wgf5i8cx4fg01gwcnzfn5k0nms"; authors = [ - "Stjepan Glavina " - "Taiki Endo " - "John Nunley " + "Markus Westerlind " ]; dependencies = [ { - name = "crossbeam-utils"; - packageId = "crossbeam-utils"; + name = "bytes"; + packageId = "bytes"; + optional = true; + } + { + name = "memchr"; + packageId = "memchr"; usesDefaultFeatures = false; } ]; + devDependencies = [ + { + name = "bytes"; + packageId = "bytes"; + } + ]; features = { + "bytes" = [ "dep:bytes" ]; + "bytes_05" = [ "dep:bytes_05" ]; "default" = [ "std" ]; - "loom" = [ "dep:loom" ]; - "portable-atomic" = [ "dep:portable-atomic" ]; + "futures-03" = [ "pin-project" "std" "futures-core-03" "futures-io-03" "pin-project-lite" ]; + "futures-core-03" = [ "dep:futures-core-03" ]; + "futures-io-03" = [ "dep:futures-io-03" ]; + "pin-project" = [ "pin-project-lite" ]; + "pin-project-lite" = [ "dep:pin-project-lite" ]; + "regex" = [ "dep:regex" ]; + "std" = [ "memchr/std" "bytes" "alloc" ]; + "tokio" = [ "tokio-dep" "tokio-util/io" "futures-core-03" "pin-project-lite" ]; + "tokio-02" = [ "pin-project" "std" "tokio-02-dep" "futures-core-03" "pin-project-lite" "bytes_05" ]; + "tokio-02-dep" = [ "dep:tokio-02-dep" ]; + "tokio-03" = [ "pin-project" "std" "tokio-03-dep" "futures-core-03" "pin-project-lite" ]; + "tokio-03-dep" = [ "dep:tokio-03-dep" ]; + "tokio-dep" = [ "dep:tokio-dep" ]; + "tokio-util" = [ "dep:tokio-util" ]; }; - resolvedDefaultFeatures = [ "std" ]; + resolvedDefaultFeatures = [ "alloc" "bytes" "default" "std" ]; }; "const-oid" = rec { crateName = "const-oid"; @@ -1840,7 +1882,7 @@ rec { } { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "bytes"; @@ -2050,7 +2092,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "extra-traits" ]; } ]; @@ -2081,7 +2123,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; @@ -2191,7 +2233,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "extra-traits" ]; } ]; @@ -2210,7 +2252,7 @@ rec { dependencies = [ { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } ]; features = { @@ -2237,7 +2279,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "visit-mut" ]; } ]; @@ -2314,7 +2356,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "extra-traits" ]; } ]; @@ -2408,7 +2450,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; buildDependencies = [ @@ -2505,7 +2547,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; features = { @@ -2681,13 +2723,13 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; @@ -2699,9 +2741,9 @@ rec { }; "either" = rec { crateName = "either"; - version = "1.16.0"; + version = "1.17.0"; edition = "2021"; - sha256 = "17k7jfbdz7k440h6lws9baz8p9zlxgb41sig3w81h80nwzsjyqli"; + sha256 = "07dagpwcfdzpkb1n7fxkx0q3nv80rnf81v7gwlz9ljx22mn8yply"; features = { "default" = [ "std" ]; "serde" = [ "dep:serde" ]; @@ -2901,7 +2943,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; features = { @@ -2957,20 +2999,15 @@ rec { }; "event-listener" = rec { crateName = "event-listener"; - version = "5.4.1"; + version = "5.4.2"; edition = "2021"; - sha256 = "1asnp3agbr8shcl001yd935m167ammyi8hnvl0q1ycajryn6cfz1"; + sha256 = "1lk9sv7r07l58jk263s18896l55mx9jv0g1rm4hj2mpi3paas8ss"; libName = "event_listener"; authors = [ "Stjepan Glavina " "John Nunley " ]; dependencies = [ - { - name = "concurrent-queue"; - packageId = "concurrent-queue"; - usesDefaultFeatures = false; - } { name = "parking"; packageId = "parking"; @@ -2985,12 +3022,12 @@ rec { features = { "critical-section" = [ "dep:critical-section" ]; "default" = [ "std" ]; - "loom" = [ "concurrent-queue/loom" "parking?/loom" "dep:loom" ]; + "loom" = [ "parking?/loom" "dep:loom" ]; "parking" = [ "dep:parking" ]; - "portable-atomic" = [ "portable-atomic-util" "portable_atomic_crate" "concurrent-queue/portable-atomic" ]; + "portable-atomic" = [ "portable-atomic-util" "portable_atomic_crate" ]; "portable-atomic-util" = [ "dep:portable-atomic-util" ]; "portable_atomic_crate" = [ "dep:portable_atomic_crate" ]; - "std" = [ "concurrent-queue/std" "parking" ]; + "std" = [ "parking" ]; }; resolvedDefaultFeatures = [ "default" "parking" "std" ]; }; @@ -3024,9 +3061,9 @@ rec { }; "fastrand" = rec { crateName = "fastrand"; - version = "2.4.1"; + version = "2.5.0"; edition = "2018"; - sha256 = "1mnqxxnxvd69ma9mczabpbbsgwlhd6l78yv3vd681453a9s247wz"; + sha256 = "08q2r30y62winysimnlpbvw9kiwn0rmdlidqlmzd6z90mv764z6s"; authors = [ "Stjepan Glavina " ]; @@ -3251,9 +3288,9 @@ rec { }; "futures" = rec { crateName = "futures"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "0b9q86r5ar18v5xjiyqn7sb8sa32xv98qqnfz779gl7ns7lpw54b"; + sha256 = "066j5aqz8an05xh4hn5ljdnjn80z3g335v4grx4gaifr57wg3358"; dependencies = [ { name = "futures-channel"; @@ -3313,9 +3350,9 @@ rec { }; "futures-channel" = rec { crateName = "futures-channel"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "07fcyzrmbmh7fh4ainilf1s7gnwvnk07phdq77jkb9fpa2ffifq7"; + sha256 = "1bn5hlhfkl1sgypmiachaqcgwmr6wmjal7dyhfyb1zkazvs90996"; libName = "futures_channel"; dependencies = [ { @@ -3341,9 +3378,9 @@ rec { }; "futures-core" = rec { crateName = "futures-core"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "07bbvwjbm5g2i330nyr1kcvjapkmdqzl4r6mqv75ivvjaa0m0d3y"; + sha256 = "1iqdbvcdlplfr2g43h7xrfkv2sg5p1a26x8acz1xgxl07i3hrm9c"; libName = "futures_core"; features = { "default" = [ "std" ]; @@ -3354,9 +3391,9 @@ rec { }; "futures-executor" = rec { crateName = "futures-executor"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "17aplz3ns74qn7a04qg7qlgsdx5iwwwkd4jvdfra6hl3h4w9rwms"; + sha256 = "0n3lpkmcfrsnh40i4armn040gnqbpd257hz5qs46zipjr6f8fm37"; libName = "futures_executor"; dependencies = [ { @@ -3384,9 +3421,9 @@ rec { }; "futures-io" = rec { crateName = "futures-io"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "063pf5m6vfmyxj74447x8kx9q8zj6m9daamj4hvf49yrg9fs7jyf"; + sha256 = "0yjx13qdm9b2p4w00ddw85k6yccnnmqrlrrz8yfmi5jg7jmfqxs5"; libName = "futures_io"; features = { "default" = [ "std" ]; @@ -3395,9 +3432,9 @@ rec { }; "futures-macro" = rec { crateName = "futures-macro"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "0ys4b1lk7s0bsj29pv42bxsaavalch35rprp64s964p40c1bfdg8"; + sha256 = "02xiyd5y1nk9b805aympj4wq2czgvxnhcml9w9xkc665d3g3qv9d"; procMacro = true; libName = "futures_macro"; dependencies = [ @@ -3411,7 +3448,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; @@ -3419,9 +3456,9 @@ rec { }; "futures-sink" = rec { crateName = "futures-sink"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "14q8ml7hn5a6gyy9ri236j28kh0svqmrk4gcg0wh26rkazhm95y3"; + sha256 = "01z38z344hpryw84b6r0rbwcb669d8pyvl2szg10aqwx96n1hi73"; libName = "futures_sink"; features = { "default" = [ "std" ]; @@ -3431,9 +3468,9 @@ rec { }; "futures-task" = rec { crateName = "futures-task"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "14s3vqf8llz3kjza33vn4ixg6kwxp61xrysn716h0cwwsnri2xq3"; + sha256 = "02f1y1yvjg1cv998zkgl1706pi9y4fyc9045l1hlmyqyhclfscdj"; libName = "futures_task"; features = { "default" = [ "std" ]; @@ -3443,9 +3480,9 @@ rec { }; "futures-util" = rec { crateName = "futures-util"; - version = "0.3.32"; + version = "0.3.33"; edition = "2018"; - sha256 = "1mn60lw5kh32hz9isinjlpw34zx708fk5q1x0m40n6g6jq9a971q"; + sha256 = "1anyg40j5www5l22r2jbn1birsafz4q1w9qmcjk4vqzwasi90ym7"; libName = "futures_util"; dependencies = [ { @@ -3515,7 +3552,10 @@ rec { "io-compat" = [ "io" "compat" "tokio-io" "libc" ]; "libc" = [ "dep:libc" ]; "memchr" = [ "dep:memchr" ]; - "portable-atomic" = [ "futures-core/portable-atomic" ]; + "portable-atomic" = [ "futures-core/portable-atomic" "portable_atomic_crate" ]; + "portable-atomic-alloc" = [ "portable-atomic-util/alloc" "portable-atomic" ]; + "portable-atomic-util" = [ "dep:portable-atomic-util" ]; + "portable_atomic_crate" = [ "dep:portable_atomic_crate" ]; "sink" = [ "futures-sink" ]; "slab" = [ "dep:slab" ]; "spin" = [ "dep:spin" ]; @@ -3775,7 +3815,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "libc"; @@ -3804,9 +3844,9 @@ rec { }; "glob" = rec { crateName = "glob"; - version = "0.3.3"; - edition = "2015"; - sha256 = "106jpd3syfzjfj2k70mwm0v436qbx96wig98m4q8x071yrq35hhc"; + version = "0.3.4"; + edition = "2021"; + sha256 = "02zby4rsidb2ksrnysyrsaap7rk6wpp7vl5chflndafhl5gaisz4"; authors = [ "The Rust Project Developers" ]; @@ -3850,10 +3890,10 @@ rec { }; "granit-parser" = rec { crateName = "granit-parser"; - version = "0.0.3"; + version = "0.0.7"; edition = "2021"; crateBin = []; - sha256 = "14bwwc8swz8h0v8d7qybmmq25czv5aipd1v1vbariq7rchhs62zm"; + sha256 = "0bdac2as3130i83pvlsl8gdasvfy5kvaj5ylry2hv0rj8ynq2gyh"; libName = "granit_parser"; authors = [ "Ethiraric " @@ -4146,9 +4186,9 @@ rec { }; "http-body" = rec { crateName = "http-body"; - version = "1.0.1"; + version = "1.1.0"; edition = "2018"; - sha256 = "111ir5k2b9ihz5nr9cz7cwm7fnydca7dx4hc7vr16scfzghxrzhy"; + sha256 = "0b5wj0rdj8p03k20q8x0jy249amg2db919fnmh7zcrgf2clqyana"; libName = "http_body"; authors = [ "Carl Lerche " @@ -4169,9 +4209,9 @@ rec { }; "http-body-util" = rec { crateName = "http-body-util"; - version = "0.1.3"; + version = "0.1.4"; edition = "2018"; - sha256 = "0jm6jv4gxsnlsi1kzdyffjrj8cfr3zninnxpw73mvkxy4qzdj8dh"; + sha256 = "1wizkqx9a75x8v5lm7cawpammz8sfvd7cngnkp34wkcfl3b1zx79"; libName = "http_body_util"; authors = [ "Carl Lerche " @@ -4240,9 +4280,9 @@ rec { }; "hyper" = rec { crateName = "hyper"; - version = "1.10.1"; + version = "1.11.0"; edition = "2021"; - sha256 = "1624nwrh1ci34psqcl3q8q266kha8kd6fmqjj14qck49l59iqa2m"; + sha256 = "0wha96biivgpj0fpf80a2aar5dfbff1lk62i9x9i2bl53wl5686j"; authors = [ "Sean McArthur " ]; @@ -4337,7 +4377,7 @@ rec { "ffi" = [ "dep:http-body-util" "dep:futures-util" ]; "full" = [ "client" "http1" "http2" "server" ]; "http1" = [ "dep:atomic-waker" "dep:futures-channel" "dep:futures-core" "dep:httparse" "dep:itoa" ]; - "http2" = [ "dep:futures-channel" "dep:futures-core" "dep:h2" ]; + "http2" = [ "dep:atomic-waker" "dep:futures-channel" "dep:futures-core" "dep:h2" ]; "server" = [ "dep:httpdate" "dep:pin-project-lite" "dep:smallvec" ]; "tracing" = [ "dep:tracing" ]; }; @@ -5160,6 +5200,68 @@ rec { } ]; + }; + "info-fetcher-commons" = rec { + crateName = "info-fetcher-commons"; + version = "0.0.0-dev"; + edition = "2024"; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/info-fetcher-commons; }; + libName = "info_fetcher_commons"; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "axum"; + packageId = "axum"; + } + { + name = "hyper"; + packageId = "hyper"; + } + { + name = "native-tls"; + packageId = "native-tls"; + } + { + name = "reqwest"; + packageId = "reqwest"; + usesDefaultFeatures = false; + features = [ "json" "form" "query" "native-tls" "charset" "http2" "system-proxy" ]; + } + { + name = "rustls-pki-types"; + packageId = "rustls-pki-types"; + } + { + name = "serde"; + packageId = "serde"; + features = [ "derive" ]; + } + { + name = "serde_json"; + packageId = "serde_json"; + } + { + name = "snafu"; + packageId = "snafu 0.9.2"; + } + { + name = "stackable-operator"; + packageId = "stackable-operator"; + features = [ "webhook" ]; + } + { + name = "tokio"; + packageId = "tokio"; + features = [ "full" ]; + } + { + name = "tracing"; + packageId = "tracing"; + } + ]; + }; "ipnet" = rec { crateName = "ipnet"; @@ -5209,6 +5311,26 @@ rec { "default" = [ "use_std" ]; "use_std" = [ "use_alloc" "either/use_std" ]; }; + }; + "itertools 0.14.0" = rec { + crateName = "itertools"; + version = "0.14.0"; + edition = "2018"; + sha256 = "118j6l1vs2mx65dqhwyssbrxpawa90886m3mzafdvyip41w2q69b"; + authors = [ + "bluss" + ]; + dependencies = [ + { + name = "either"; + packageId = "either"; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "use_std" ]; + "use_std" = [ "use_alloc" "either/use_std" ]; + }; resolvedDefaultFeatures = [ "default" "use_alloc" "use_std" ]; }; "itertools 0.15.0" = rec { @@ -5271,9 +5393,9 @@ rec { }; "jiff" = rec { crateName = "jiff"; - version = "0.2.31"; + version = "0.2.35"; edition = "2021"; - sha256 = "0d6nvffabb1v34x2s2a3xjca4zny1dfdhqm3xy0wyl77rchn3znc"; + sha256 = "1k1d1n8k46192xz6ph8km43lcg68ql65phzmhm49mbq7pn1p32v6"; authors = [ "Andrew Gallant " ]; @@ -5283,6 +5405,12 @@ rec { packageId = "defmt"; optional = true; } + { + name = "jiff-core"; + packageId = "jiff-core"; + rename = "jcore"; + usesDefaultFeatures = false; + } { name = "jiff-static"; packageId = "jiff-static"; @@ -5337,16 +5465,16 @@ rec { } ]; features = { - "alloc" = [ "serde_core?/alloc" "portable-atomic-util/alloc" "defmt?/alloc" ]; + "alloc" = [ "jcore/alloc" "serde_core?/alloc" "portable-atomic-util/alloc" "defmt?/alloc" ]; "default" = [ "std" "tz-system" "tz-fat" "tzdb-bundle-platform" "tzdb-zoneinfo" "tzdb-concatenated" "perf-inline" ]; - "defmt" = [ "dep:defmt" ]; + "defmt" = [ "dep:defmt" "jcore/defmt" ]; "js" = [ "dep:wasm-bindgen" "dep:js-sys" ]; - "logging" = [ "dep:log" ]; + "logging" = [ "dep:log" "jcore/logging" ]; "serde" = [ "dep:serde_core" ]; "static" = [ "static-tz" "jiff-static?/tzdb" ]; "static-tz" = [ "dep:jiff-static" ]; - "std" = [ "alloc" "log?/std" "serde_core?/std" ]; - "tz-fat" = [ "jiff-static?/tz-fat" ]; + "std" = [ "alloc" "jcore/std" "log?/std" "serde_core?/std" ]; + "tz-fat" = [ "jcore/tz-fat" "jiff-static?/tz-fat" ]; "tz-system" = [ "std" "dep:windows-link" ]; "tzdb-bundle-always" = [ "dep:jiff-tzdb" "alloc" ]; "tzdb-bundle-platform" = [ "dep:jiff-tzdb-platform" "alloc" ]; @@ -5355,17 +5483,47 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "perf-inline" "serde" "std" "tz-fat" "tz-system" "tzdb-bundle-platform" "tzdb-concatenated" "tzdb-zoneinfo" ]; }; + "jiff-core" = rec { + crateName = "jiff-core"; + version = "0.1.0"; + edition = "2021"; + sha256 = "02axx56pkh2w4bw5rp94qlvcpwzd3n2w2025fnikvrgg762aiv3z"; + libName = "jiff_core"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "defmt"; + packageId = "defmt"; + optional = true; + } + ]; + features = { + "alloc" = [ "defmt?/alloc" ]; + "default" = [ "std" "tz-fat" ]; + "defmt" = [ "dep:defmt" ]; + "logging" = [ "dep:log" ]; + "std" = [ "alloc" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" "std" "tz-fat" ]; + }; "jiff-static" = rec { crateName = "jiff-static"; - version = "0.2.31"; + version = "0.2.35"; edition = "2021"; - sha256 = "1lpdrmzd5yafwzniw0xi5gac4rz0vccsja1qrprjim32ysbyhrg1"; + sha256 = "014jli8v46c8hzkndmvdfvq4la6a6y9icmnh3k735yqwlarxqs9s"; procMacro = true; libName = "jiff_static"; authors = [ "Andrew Gallant " ]; dependencies = [ + { + name = "jiff-core"; + packageId = "jiff-core"; + rename = "jcore"; + } { name = "proc-macro2"; packageId = "proc-macro2"; @@ -5376,7 +5534,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; features = { @@ -5386,9 +5544,9 @@ rec { }; "jiff-tzdb" = rec { crateName = "jiff-tzdb"; - version = "0.1.7"; + version = "0.1.8"; edition = "2021"; - sha256 = "14353p1g4234ww11f6jqy51fkrp31dqijaj0hwsjng59y5yj8hk1"; + sha256 = "07hl9sgzfb9as1x0n5bjk1qxishzcriapy9xa481y8xd6acx6aql"; libName = "jiff_tzdb"; libPath = "lib.rs"; authors = [ @@ -5413,6 +5571,134 @@ rec { } ]; + }; + "jni" = rec { + crateName = "jni"; + version = "0.22.4"; + edition = "2024"; + sha256 = "161lza8gz071h22pgyqyx4n91ixd691z2dbb1pq2g97k5i49mzay"; + authors = [ + "jni team" + ]; + dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "combine"; + packageId = "combine"; + } + { + name = "jni-macros"; + packageId = "jni-macros"; + } + { + name = "jni-sys"; + packageId = "jni-sys"; + } + { + name = "log"; + packageId = "log"; + } + { + name = "simd_cesu8"; + packageId = "simd_cesu8"; + } + { + name = "thiserror"; + packageId = "thiserror 2.0.19"; + } + { + name = "windows-link"; + packageId = "windows-link 0.2.1"; + target = { target, features }: (target."windows" or false); + } + ]; + buildDependencies = [ + { + name = "walkdir"; + packageId = "walkdir"; + } + ]; + features = { + "invocation" = [ "dep:java-locator" "dep:libloading" ]; + }; + }; + "jni-macros" = rec { + crateName = "jni-macros"; + version = "0.22.4"; + edition = "2024"; + sha256 = "18v02mcn5c7mb2yw6r930xg6ynsn7hwkxv8z2kdhn3qprjn0j0d0"; + procMacro = true; + libName = "jni_macros"; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "simd_cesu8"; + packageId = "simd_cesu8"; + } + { + name = "syn"; + packageId = "syn 2.0.119"; + features = [ "full" ]; + } + ]; + buildDependencies = [ + { + name = "rustc_version"; + packageId = "rustc_version"; + } + ]; + + }; + "jni-sys" = rec { + crateName = "jni-sys"; + version = "0.4.1"; + edition = "2021"; + sha256 = "1wlahx6f2zhczdjqyn8mk7kshb8x5vsd927sn3lvw41rrf47ldy6"; + libName = "jni_sys"; + authors = [ + "Steven Fackler " + "Robert Bragg " + ]; + dependencies = [ + { + name = "jni-sys-macros"; + packageId = "jni-sys-macros"; + } + ]; + + }; + "jni-sys-macros" = rec { + crateName = "jni-sys-macros"; + version = "0.4.1"; + edition = "2021"; + sha256 = "0r32gbabrak15a7p487765b5wc0jcna2yv88mk6m1zjqyi1bkh1q"; + procMacro = true; + libName = "jni_sys_macros"; + authors = [ + "Robert Bragg " + ]; + dependencies = [ + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 2.0.119"; + features = [ "full" ]; + } + ]; + }; "jobserver" = rec { crateName = "jobserver"; @@ -5501,7 +5787,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } ]; devDependencies = [ @@ -5524,9 +5810,9 @@ rec { }; "jsonpath-rust" = rec { crateName = "jsonpath-rust"; - version = "1.0.4"; + version = "1.0.6"; edition = "2021"; - sha256 = "1ymdpqawc3nxrlr6izwpw22h5msa16wqjbkqldijhrxvqhh76fk3"; + sha256 = "0h2dw4gkwy8h9m5lvxg84wbi6zvr9349cd8ip9cgxpkl6mif1nx2"; libName = "jsonpath_rust"; authors = [ "BorisZhguchev " @@ -5550,7 +5836,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } ]; @@ -5667,7 +5953,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } ]; features = { @@ -5776,9 +6062,9 @@ rec { }; "kube" = rec { crateName = "kube"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "0ip1qc9kvgyp735rmxdjq75dspf737sazvhzs6b70siljn011fab"; + sha256 = "065z77zh7gbigcbv7mxk7firdf1w6278lal1jjqsnrh073hpz390"; authors = [ "clux " "Natalie Klestrup Röijezon " @@ -5851,9 +6137,9 @@ rec { }; "kube-client" = rec { crateName = "kube-client"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "0yskaybnf8m2c9rb0kxzlczb9jbx25sxdlqhzqhlq9n2bgh2ixnh"; + sha256 = "1lqny49zldysk1qr6pp2ifsp2ifqa5wcwplbj73wb9rk62kl1s9i"; libName = "kube_client"; authors = [ "clux " @@ -5953,6 +6239,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "rustls-platform-verifier"; + packageId = "rustls-platform-verifier"; + optional = true; + } { name = "secrecy"; packageId = "secrecy"; @@ -5973,7 +6264,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } { name = "tokio"; @@ -6063,7 +6354,7 @@ rec { "pem" = [ "dep:pem" ]; "ring" = [ "hyper-rustls?/ring" ]; "rustls" = [ "dep:rustls" ]; - "rustls-tls" = [ "rustls" "hyper-rustls" ]; + "rustls-tls" = [ "rustls" "hyper-rustls" "dep:rustls-platform-verifier" ]; "serde-saphyr" = [ "dep:serde-saphyr" ]; "socks5" = [ "hyper-util/client-proxy" ]; "tame-oauth" = [ "dep:tame-oauth" ]; @@ -6080,9 +6371,9 @@ rec { }; "kube-core" = rec { crateName = "kube-core"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "1a7a3n4mwiqywajlld3axii0k7ridizp5mn7b06i81ms6dcjzc61"; + sha256 = "1d7f2acgydnica3gsva5my87kw8cbfyvbq1f6ln4dwwc24y3blm9"; libName = "kube_core"; authors = [ "clux " @@ -6139,7 +6430,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } ]; devDependencies = [ @@ -6163,9 +6454,9 @@ rec { }; "kube-derive" = rec { crateName = "kube-derive"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "1an7av9xlif02klbk1xsz7nvggpnazpq2kpris0z3bbxf2c1h5zy"; + sha256 = "1h597lx7xd6x65ili9ccm8lmqdq3dg3384iwcf8vz0zsw4ciq54j"; procMacro = true; libName = "kube_derive"; authors = [ @@ -6197,7 +6488,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "extra-traits" ]; } ]; @@ -6212,9 +6503,9 @@ rec { }; "kube-runtime" = rec { crateName = "kube-runtime"; - version = "4.0.0"; + version = "4.2.0"; edition = "2024"; - sha256 = "08db3c5bg185y2yh6vilbkwqfdm5aykf87xllllwzis0qmkfrpcr"; + sha256 = "1isw7mh35jmfwi3kx9ibpcgw77lyv9h3ji2ajdgabgn7f7pn9c1y"; libName = "kube_runtime"; authors = [ "clux " @@ -6291,7 +6582,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } { name = "tokio"; @@ -6436,7 +6727,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } { name = "tokio"; @@ -6486,12 +6777,9 @@ rec { }; "libc" = rec { crateName = "libc"; - version = "0.2.186"; + version = "0.2.189"; edition = "2021"; - sha256 = "0rnyhzjyqq9x56skkllbjzzzwym3r61lq3l4hqj64v71gw0r3av8"; - authors = [ - "The Rust Project Developers" - ]; + sha256 = "1whjfs375vlng2q6yrbzs73cvp5lm3w1n2gfqajb2vgf7zg3xbry"; features = { "default" = [ "std" ]; "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ]; @@ -6502,10 +6790,10 @@ rec { }; "libgit2-sys" = rec { crateName = "libgit2-sys"; - version = "0.18.5+1.9.4"; + version = "0.18.7+1.9.6"; edition = "2021"; links = "git2"; - sha256 = "18lwqnhy7qxg4iw24s1a0n7aj7qbnryry1iy0w32k4f1xbk6lp80"; + sha256 = "12ad5zmffzbivn57d47lg2kgjprqsz0kq8i4lsqzlkwz9cg3kir3"; libName = "libgit2_sys"; libPath = "lib.rs"; authors = [ @@ -6555,7 +6843,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "bytes"; @@ -6788,6 +7076,28 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "md5" = rec { + crateName = "md5"; + version = "0.8.1"; + edition = "2021"; + sha256 = "032pi1dmpk8aimpc8p5rxw07wx7w3j8q4ah3iwyxz9n66a3qvfvy"; + authors = [ + "Daniel McKenna " + "Ivan Ukhov " + "Kamal Ahmad " + "Konstantin Stepanov " + "Lukas Kalbertodt " + "Nathan Musoke " + "Scott Mabin " + "Tony Arcieri " + "Wim de With " + "Yosef Dinerstein " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "memchr" = rec { crateName = "memchr"; version = "2.8.3"; @@ -6867,9 +7177,9 @@ rec { }; "mio" = rec { crateName = "mio"; - version = "1.2.1"; + version = "1.2.2"; edition = "2021"; - sha256 = "1nkggmrlnjs93w8rja4lvjj4aml1xqahgimv1h0p7d373kvhmg82"; + sha256 = "09y4b7gc42ymgssshh8sz6gs3y5r8bbigqaw2c4snh6fy5qmrmih"; authors = [ "Carl Lerche " "Thomas de Zeeuw " @@ -6965,7 +7275,7 @@ rec { "quanta" = [ "dep:quanta" ]; "unstable-debug-counters" = [ "future" ]; }; - resolvedDefaultFeatures = [ "default" "future" ]; + resolvedDefaultFeatures = [ "future" ]; }; "native-tls" = rec { crateName = "native-tls"; @@ -7143,7 +7453,7 @@ rec { } { name = "rand"; - packageId = "rand 0.8.6"; + packageId = "rand 0.8.7"; optional = true; usesDefaultFeatures = false; } @@ -7162,7 +7472,7 @@ rec { devDependencies = [ { name = "rand"; - packageId = "rand 0.8.6"; + packageId = "rand 0.8.7"; features = [ "small_rng" ]; } ]; @@ -7329,7 +7639,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "cfg-if"; @@ -7380,7 +7690,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; @@ -7467,7 +7777,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; optional = true; usesDefaultFeatures = false; } @@ -7637,7 +7947,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; usesDefaultFeatures = false; } { @@ -7837,14 +8147,14 @@ rec { } { name = "rand"; - packageId = "rand 0.9.4"; + packageId = "rand 0.9.5"; optional = true; usesDefaultFeatures = false; features = [ "std" "std_rng" "small_rng" "os_rng" "thread_rng" ]; } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; usesDefaultFeatures = false; } { @@ -8145,9 +8455,9 @@ rec { }; "pest" = rec { crateName = "pest"; - version = "2.8.7"; + version = "2.8.8"; edition = "2021"; - sha256 = "1sc2jzy3hjvj7qqwbygl4psbnzf1lk2j9kbbiin2ssjw63bpsqj7"; + sha256 = "18jhl2zpxvl6kikc0jgp7gi7i7cy9s634z5bnvx70w1whjz2ixvx"; authors = [ "Dragoș Tiselice " ]; @@ -8174,9 +8484,9 @@ rec { }; "pest_derive" = rec { crateName = "pest_derive"; - version = "2.8.7"; + version = "2.8.8"; edition = "2021"; - sha256 = "0n4xs953qz7yyl4f3iibcflh3fh3v98vl9wyd2midm6abqr58hjb"; + sha256 = "1zcijlfdf6sk2s6l1qnm3j7kj7d4ymqcial8w4p4dcr67gydcbcy"; procMacro = true; authors = [ "Dragoș Tiselice " @@ -8202,9 +8512,9 @@ rec { }; "pest_generator" = rec { crateName = "pest_generator"; - version = "2.8.7"; + version = "2.8.8"; edition = "2021"; - sha256 = "19z0jlls9aqn5yfrrpg2vfqq50ifzh1z19mwxjngga6pxa8hwk3c"; + sha256 = "1dkmk6r6bb2hh5wayymfmwd7mswwbyhw12dnx2lrdxdnrw2r4yka"; authors = [ "Dragoș Tiselice " ]; @@ -8228,7 +8538,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; features = { @@ -8240,9 +8550,9 @@ rec { }; "pest_meta" = rec { crateName = "pest_meta"; - version = "2.8.7"; + version = "2.8.8"; edition = "2021"; - sha256 = "0462h8zrm7vr1fdy49mxi5gfs7nlpbrbajwj6iiy1zhnh724nx7r"; + sha256 = "0z7m54jc3nj3nxbbk4kyjfa06d8s24vhf6krzj2867nyq18x7aw5"; authors = [ "Dragoș Tiselice " ]; @@ -8288,7 +8598,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "parsing" "printing" "clone-impls" "proc-macro" "full" "visit-mut" ]; } @@ -8384,9 +8694,9 @@ rec { }; "portable-atomic" = rec { crateName = "portable-atomic"; - version = "1.13.1"; + version = "1.14.0"; edition = "2018"; - sha256 = "0j8vlar3n5acyigq8q6f4wjx3k3s5yz0rlpqrv76j73gi5qr8fn3"; + sha256 = "1hyfma9n2cs2ibazpfwrbv61zwg7cv86g0pr5yjkg07qgr4xa81x"; libName = "portable_atomic"; features = { "critical-section" = [ "dep:critical-section" ]; @@ -8493,7 +8803,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "full" ]; } @@ -8506,7 +8816,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "clone-impls" "extra-traits" "parsing" "printing" "visit-mut" ]; } @@ -8541,9 +8851,9 @@ rec { }; "proc-macro2" = rec { crateName = "proc-macro2"; - version = "1.0.106"; + version = "1.0.107"; edition = "2021"; - sha256 = "0d09nczyaj67x4ihqr5p7gxbkz38gxhk4asc0k8q23g9n85hzl4g"; + sha256 = "1nb6ly8kp65f724kj73ippc7lvydss24sm2vagk6qpklpg4pwplq"; libName = "proc_macro2"; authors = [ "David Tolnay " @@ -8609,7 +8919,7 @@ rec { } { name = "itertools"; - packageId = "itertools 0.13.0"; + packageId = "itertools 0.14.0"; } { name = "proc-macro2"; @@ -8621,7 +8931,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "extra-traits" ]; } ]; @@ -8657,9 +8967,9 @@ rec { }; "quote" = rec { crateName = "quote"; - version = "1.0.46"; + version = "1.0.47"; edition = "2021"; - sha256 = "0s034glrlav8nzqy2yskqzv52ncy82k126sm2jk5j1vs1iylbg6z"; + sha256 = "00ch0yyzvv6s671ik0kcsbw8nigdaj2g3fr61kcahwx48aqlvgqz"; authors = [ "David Tolnay " ]; @@ -8699,11 +9009,11 @@ rec { "rustc-dep-of-std" = [ "core" ]; }; }; - "rand 0.8.6" = rec { + "rand 0.8.7" = rec { crateName = "rand"; - version = "0.8.6"; + version = "0.8.7"; edition = "2018"; - sha256 = "12kd4rljn86m00rcaz4c1rcya4mb4gk5ig6i8xq00a8wjgxfr82w"; + sha256 = "06iaf16fr0z8zly7anmn8ky0p80xnx9yv0gdcm30fwn9vqmigxi2"; authors = [ "The Rand Project Developers" "The Rust Project Developers" @@ -8733,11 +9043,11 @@ rec { }; resolvedDefaultFeatures = [ "rand_chacha" "std_rng" ]; }; - "rand 0.9.4" = rec { + "rand 0.9.5" = rec { crateName = "rand"; - version = "0.9.4"; + version = "0.9.5"; edition = "2021"; - sha256 = "1sknbxgs6nfg0nxdd7689lwbyr2i4vaswchrv4b34z8vpc3azia4"; + sha256 = "0hbvllk8g28mqjld6hqmckk69w296qpzg95whm3didsyg46ivvxr"; authors = [ "The Rand Project Developers" "The Rust Project Developers" @@ -8890,7 +9200,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } ]; features = { @@ -8902,9 +9212,9 @@ rec { }; "ref-cast" = rec { crateName = "ref-cast"; - version = "1.0.25"; + version = "1.0.26"; edition = "2021"; - sha256 = "0zdzc34qjva9xxgs889z5iz787g81hznk12zbk4g2xkgwq530m7k"; + sha256 = "0vdra0766jcc2czzqwhql41kkfyajdnai1pbkjxbq8vr7mvqyvi1"; libName = "ref_cast"; authors = [ "David Tolnay " @@ -8919,9 +9229,9 @@ rec { }; "ref-cast-impl" = rec { crateName = "ref-cast-impl"; - version = "1.0.25"; + version = "1.0.26"; edition = "2021"; - sha256 = "1nkhn1fklmn342z5c4mzfzlxddv3x8yhxwwk02cj06djvh36065p"; + sha256 = "0g70ff9an5i97cw9kijgzqrqydz7smcfic2zyydddizfbxl874ic"; procMacro = true; libName = "ref_cast_impl"; authors = [ @@ -8938,16 +9248,16 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 3.0.3"; } ]; }; "regex" = rec { crateName = "regex"; - version = "1.12.4"; + version = "1.13.1"; edition = "2021"; - sha256 = "1fm6si2xpmhwqflabdqsakc0qkq718wx2ljl37nbj75fb5vjnagi"; + sha256 = "1391a0a4100ik8cp7l577p3ip3haqq03rd9c5vdr7vcfdixj687h"; authors = [ "The Rust Project Developers" "Andrew Gallant " @@ -9003,9 +9313,9 @@ rec { }; "regex-automata" = rec { crateName = "regex-automata"; - version = "0.4.14"; + version = "0.4.16"; edition = "2021"; - sha256 = "13xf7hhn4qmgfh784llcp2kzrvljd13lb2b1ca0mwnf15w9d87bf"; + sha256 = "1b8ihxq99g3hr8mr37bvhib4bfn8rlmpmp0wjg2q1j50plvdpkwg"; libName = "regex_automata"; authors = [ "The Rust Project Developers" @@ -9595,7 +9905,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; usesDefaultFeatures = false; } { @@ -9692,9 +10002,9 @@ rec { }; "rustls" = rec { crateName = "rustls"; - version = "0.23.41"; + version = "0.23.42"; edition = "2021"; - sha256 = "07vbs2935a7xjqqvy8w3ndzmmw8dg769d9zcgdg7k6sdccjv34kb"; + sha256 = "0f619dq1izpl40glcqgfjbqzpmwg8g5iffjx4429sh4v06mzqm1w"; dependencies = [ { name = "aws-lc-rs"; @@ -9789,34 +10099,167 @@ rec { target = { target, features }: (target."windows" or false); } { - name = "security-framework"; - packageId = "security-framework"; - target = { target, features }: ("macos" == target."os" or null); + name = "security-framework"; + packageId = "security-framework"; + target = { target, features }: ("macos" == target."os" or null); + } + ]; + + }; + "rustls-pki-types" = rec { + crateName = "rustls-pki-types"; + version = "1.15.1"; + edition = "2021"; + sha256 = "15hakk4pcvr5278cazgw9qf2r7gdg09rg5pivbyd3dbyih12aj9g"; + libName = "rustls_pki_types"; + dependencies = [ + { + name = "zeroize"; + packageId = "zeroize"; + optional = true; + } + ]; + features = { + "alloc" = [ "dep:zeroize" ]; + "default" = [ "alloc" ]; + "std" = [ "alloc" ]; + "web" = [ "web-time" ]; + "web-time" = [ "dep:web-time" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" "std" ]; + }; + "rustls-platform-verifier" = rec { + crateName = "rustls-platform-verifier"; + version = "0.7.0"; + edition = "2021"; + sha256 = "181v4d0vl53vdh2wq56vghal1zyhdgqvy4xa8r45zwz4di9y5l96"; + libName = "rustls_platform_verifier"; + dependencies = [ + { + name = "core-foundation"; + packageId = "core-foundation 0.10.1"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "core-foundation-sys"; + packageId = "core-foundation-sys"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "jni"; + packageId = "jni"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "jni"; + packageId = "jni"; + usesDefaultFeatures = false; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "log"; + packageId = "log"; + } + { + name = "once_cell"; + packageId = "once_cell"; + optional = true; + } + { + name = "once_cell"; + packageId = "once_cell"; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "rustls"; + packageId = "rustls"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + { + name = "rustls-native-certs"; + packageId = "rustls-native-certs"; + target = { target, features }: ((target."unix" or false) && (!("android" == target."os" or null)) && (!("apple" == target."vendor" or null)) && (!("wasm32" == target."arch" or null))); + } + { + name = "rustls-platform-verifier-android"; + packageId = "rustls-platform-verifier-android"; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ((target."unix" or false) && (!("android" == target."os" or null)) && (!("apple" == target."vendor" or null)) && (!("wasm32" == target."arch" or null))); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ("wasm32" == target."arch" or null); + } + { + name = "rustls-webpki"; + packageId = "rustls-webpki"; + rename = "webpki"; + usesDefaultFeatures = false; + target = { target, features }: ("android" == target."os" or null); + } + { + name = "security-framework"; + packageId = "security-framework"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "security-framework-sys"; + packageId = "security-framework-sys"; + target = { target, features }: (("apple" == target."vendor" or null)); + } + { + name = "webpki-root-certs"; + packageId = "webpki-root-certs"; + target = { target, features }: ("wasm32" == target."arch" or null); + } + { + name = "windows-sys"; + packageId = "windows-sys 0.61.2"; + usesDefaultFeatures = false; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Security_Cryptography" ]; + } + ]; + devDependencies = [ + { + name = "rustls"; + packageId = "rustls"; + usesDefaultFeatures = false; + features = [ "ring" ]; } - ]; - - }; - "rustls-pki-types" = rec { - crateName = "rustls-pki-types"; - version = "1.15.0"; - edition = "2021"; - sha256 = "0imhb5d0m4hinavcgqxzmqpb55zjahv19g0lxrkh167k9ai9jj3n"; - libName = "rustls_pki_types"; - dependencies = [ { - name = "zeroize"; - packageId = "zeroize"; - optional = true; + name = "webpki-root-certs"; + packageId = "webpki-root-certs"; } ]; features = { - "alloc" = [ "dep:zeroize" ]; - "default" = [ "alloc" ]; - "std" = [ "alloc" ]; - "web" = [ "web-time" ]; - "web-time" = [ "dep:web-time" ]; + "android_logger" = [ "dep:android_logger" ]; + "base64" = [ "dep:base64" ]; + "cert-logging" = [ "base64" ]; + "docsrs" = [ "jni" "once_cell" ]; + "ffi-testing" = [ "android_logger" "rustls/ring" ]; + "jni" = [ "dep:jni" ]; + "once_cell" = [ "dep:once_cell" ]; }; - resolvedDefaultFeatures = [ "alloc" "default" "std" ]; + }; + "rustls-platform-verifier-android" = rec { + crateName = "rustls-platform-verifier-android"; + version = "0.1.1"; + edition = "2021"; + sha256 = "13vq6sxsgz9547xm2zbdxiw8x7ad1g8n8ax6xvxsjqszk7q6awgq"; + libName = "rustls_platform_verifier_android"; + }; "rustls-webpki" = rec { crateName = "rustls-webpki"; @@ -9883,6 +10326,24 @@ rec { "no-panic" = [ "dep:no-panic" ]; }; }; + "same-file" = rec { + crateName = "same-file"; + version = "1.0.6"; + edition = "2018"; + sha256 = "00h5j1w87dmhnvbv9l8bic3y7xxsnjmssvifw2ayvgx9mb1ivz4k"; + libName = "same_file"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "winapi-util"; + packageId = "winapi-util"; + target = { target, features }: (target."windows" or false); + } + ]; + + }; "schannel" = rec { crateName = "schannel"; version = "0.1.29"; @@ -10011,13 +10472,13 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "extra-traits" ]; } ]; @@ -10128,7 +10589,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "core-foundation"; @@ -10198,9 +10659,9 @@ rec { }; "serde" = rec { crateName = "serde"; - version = "1.0.228"; + version = "1.0.229"; edition = "2021"; - sha256 = "17mf4hhjxv5m90g42wmlbc61hdhlm6j9hwfkpcnd72rpgzm993ls"; + sha256 = "1fp04fq4a79bpm61xz1zy0pbz4kpc7d771zii1k3inmszq55jj21"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -10231,10 +10692,10 @@ rec { }; "serde-saphyr" = rec { crateName = "serde-saphyr"; - version = "0.0.27"; + version = "0.0.29"; edition = "2024"; crateBin = []; - sha256 = "1crx4kmmm88y4dbaramdsn0w8fqzchaz12b6vdgx7bddzb1v95sq"; + sha256 = "196f2kjh4c9zayliykx9s5jyr2zir1rz15fsxdnwmq0xj60jglkv"; libName = "serde_saphyr"; dependencies = [ { @@ -10279,9 +10740,8 @@ rec { optional = true; } { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; + name = "serde_core"; + packageId = "serde_core"; } { name = "smallvec"; @@ -10294,16 +10754,9 @@ rec { optional = true; } ]; - devDependencies = [ - { - name = "serde"; - packageId = "serde"; - features = [ "derive" "rc" ]; - } - ]; features = { "default" = [ "serialize" "deserialize" ]; - "deserialize" = [ "dep:base64" "dep:num-traits" "dep:annotate-snippets" "dep:granit-parser" "dep:smallvec" "dep:encoding_rs_io" "dep:ahash" ]; + "deserialize" = [ "dep:num-traits" "dep:annotate-snippets" "dep:granit-parser" "dep:smallvec" "dep:encoding_rs_io" "dep:ahash" ]; "figment" = [ "dep:figment" "deserialize" ]; "figment2" = [ "dep:figment2" "deserialize" ]; "garde" = [ "dep:garde" "deserialize" ]; @@ -10312,6 +10765,7 @@ rec { "miette" = [ "dep:miette" "deserialize" ]; "properties" = [ "deserialize" ]; "robotics" = [ "deserialize" ]; + "serde_derived_types" = [ "dep:serde" ]; "serialize" = [ "dep:base64" "dep:num-traits" "dep:zmij" "dep:nohash-hasher" ]; "validator" = [ "dep:validator" "deserialize" ]; }; @@ -10340,9 +10794,9 @@ rec { }; "serde_core" = rec { crateName = "serde_core"; - version = "1.0.228"; + version = "1.0.229"; edition = "2021"; - sha256 = "1bb7id2xwx8izq50098s5j2sqrrvk31jbbrjqygyan6ask3qbls1"; + sha256 = "0j1ajiha76h3nmd976il9li6975k121xa7jb39ws8n0yqp4s5p37"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -10367,9 +10821,9 @@ rec { }; "serde_derive" = rec { crateName = "serde_derive"; - version = "1.0.228"; + version = "1.0.229"; edition = "2021"; - sha256 = "0y8xm7fvmr2kjcd029g9fijpndh8csv5m20g4bd76w8qschg4h6m"; + sha256 = "0j4k63i7h1bikxwz2c89ig0hrwbnl9mz1czn85xx99x5cc9dg9g7"; procMacro = true; authors = [ "Erick Tryzelaar " @@ -10390,7 +10844,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 3.0.3"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ]; } @@ -10422,7 +10876,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" ]; } @@ -10431,9 +10885,9 @@ rec { }; "serde_json" = rec { crateName = "serde_json"; - version = "1.0.150"; + version = "1.0.151"; edition = "2021"; - sha256 = "1ffgfhy9kndjnrz8lmy95pr758p2zk8dxv6yi99x0vkkni24w0g8"; + sha256 = "051zww7lvpw147vvwss1ng6w587qyrkzg75fvj08q2dfrmgbahf8"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -10576,9 +11030,9 @@ rec { }; "sha1" = rec { crateName = "sha1"; - version = "0.10.6"; + version = "0.10.7"; edition = "2018"; - sha256 = "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3"; + sha256 = "1f632d529qzz95yrprr632w1fxqkrv6b6jksjc11vnzl049lay59"; authors = [ "RustCrypto Developers" ]; @@ -10605,10 +11059,8 @@ rec { } ]; features = { - "asm" = [ "sha1-asm" ]; "default" = [ "std" ]; "oid" = [ "digest/oid" ]; - "sha1-asm" = [ "dep:sha1-asm" ]; "std" = [ "digest/std" ]; }; resolvedDefaultFeatures = [ "default" "std" ]; @@ -10762,9 +11214,9 @@ rec { }; "simd-adler32" = rec { crateName = "simd-adler32"; - version = "0.3.9"; + version = "0.3.10"; edition = "2018"; - sha256 = "0532ysdwcvzyp2bwpk8qz0hijplcdwpssr5gy5r7qwqqy5z5qgbh"; + sha256 = "1sny4y2qa5mwyxx5x59ln2p02vsdh92004njlslnx98imjc9489s"; libName = "simd_adler32"; authors = [ "Marvin Countryman " @@ -10773,6 +11225,46 @@ rec { "default" = [ "std" "const-generics" ]; }; }; + "simd_cesu8" = rec { + crateName = "simd_cesu8"; + version = "1.2.0"; + edition = "2021"; + sha256 = "0865mv3nmd35f1dccjcfj7dncjmmvvdij3j61z4131mz38jiw0qi"; + authors = [ + "Sean C. Roach " + ]; + dependencies = [ + { + name = "simdutf8"; + packageId = "simdutf8"; + usesDefaultFeatures = false; + } + ]; + buildDependencies = [ + { + name = "rustc_version"; + packageId = "rustc_version"; + } + ]; + features = { + "default" = [ "std" ]; + "std" = [ "simdutf8/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; + "simdutf8" = rec { + crateName = "simdutf8"; + version = "0.1.5"; + edition = "2018"; + sha256 = "0vmpf7xaa0dnaikib5jlx6y4dxd3hxqz6l830qb079g7wcsgxag3"; + authors = [ + "Hans Kratz " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "slab" = rec { crateName = "slab"; version = "0.4.12"; @@ -10873,18 +11365,18 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "rust_1_61" "rust_1_65" "std" ]; }; - "snafu 0.9.1" = rec { + "snafu 0.9.2" = rec { crateName = "snafu"; - version = "0.9.1"; + version = "0.9.2"; edition = "2018"; - sha256 = "08k5yfydxdlshivfhrdq9km8qn02r93q28gkyvazbqz2icr1586i"; + sha256 = "1dx44pj4nid04lv64rjbhwihz3bd4arqg637df97pfwa0c2bcp74"; authors = [ "Jake Goulding " ]; dependencies = [ { name = "snafu-derive"; - packageId = "snafu-derive 0.9.1"; + packageId = "snafu-derive 0.9.2"; } ]; features = { @@ -10951,7 +11443,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; @@ -10959,11 +11451,11 @@ rec { }; resolvedDefaultFeatures = [ "rust_1_61" ]; }; - "snafu-derive 0.9.1" = rec { + "snafu-derive 0.9.2" = rec { crateName = "snafu-derive"; - version = "0.9.1"; + version = "0.9.2"; edition = "2018"; - sha256 = "1nkfi7bis72pz3w7vb64m79w49qsv20sbf19jkd471vbhr83q42z"; + sha256 = "0whyc1bp26laq8sczsfkr13cq6kccrnij0rvbv7xgw08000mjzr8"; procMacro = true; libName = "snafu_derive"; authors = [ @@ -10987,7 +11479,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } @@ -10997,9 +11489,9 @@ rec { }; "socket2" = rec { crateName = "socket2"; - version = "0.6.4"; + version = "0.6.5"; edition = "2021"; - sha256 = "0ldyp5rhba15spwxj1n94xh7sjks1398c3vwpwkxkd1087nwzlaj"; + sha256 = "1m7diygswpvlpvrxd6ap169nxgax014jr8220nqlr3bzyb3y5lf3"; authors = [ "Alex Crichton " "Thomas de Zeeuw " @@ -11135,7 +11627,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.4"; + packageId = "rand 0.9.5"; } { name = "rand_core"; @@ -11157,7 +11649,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-shared"; @@ -11236,7 +11728,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-opa-regorule-library"; @@ -11334,7 +11826,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-operator"; @@ -11375,6 +11867,103 @@ rec { "Stackable GmbH " ]; + }; + "stackable-opa-resource-info-fetcher" = rec { + crateName = "stackable-opa-resource-info-fetcher"; + version = "0.0.0-dev"; + edition = "2024"; + crateBin = [ + { + name = "stackable-opa-resource-info-fetcher"; + path = "src/main.rs"; + requiredFeatures = [ ]; + } + ]; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/resource-info-fetcher; }; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "axum"; + packageId = "axum"; + } + { + name = "clap"; + packageId = "clap"; + } + { + name = "futures"; + packageId = "futures"; + } + { + name = "hyper"; + packageId = "hyper"; + } + { + name = "info-fetcher-commons"; + packageId = "info-fetcher-commons"; + } + { + name = "md5"; + packageId = "md5"; + } + { + name = "moka"; + packageId = "moka"; + usesDefaultFeatures = false; + features = [ "future" ]; + } + { + name = "reqwest"; + packageId = "reqwest"; + usesDefaultFeatures = false; + features = [ "json" "form" "query" "native-tls" "charset" "http2" "system-proxy" ]; + } + { + name = "serde"; + packageId = "serde"; + features = [ "derive" ]; + } + { + name = "serde_json"; + packageId = "serde_json"; + } + { + name = "snafu"; + packageId = "snafu 0.9.2"; + } + { + name = "stackable-opa-operator"; + packageId = "stackable-opa-operator"; + } + { + name = "stackable-operator"; + packageId = "stackable-operator"; + features = [ "webhook" ]; + } + { + name = "tokio"; + packageId = "tokio"; + features = [ "full" ]; + } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "url"; + packageId = "url"; + } + ]; + buildDependencies = [ + { + name = "built"; + packageId = "built"; + features = [ "chrono" "git2" ]; + } + ]; + }; "stackable-opa-user-info-fetcher" = rec { crateName = "stackable-opa-user-info-fetcher"; @@ -11416,6 +12005,10 @@ rec { name = "hyper"; packageId = "hyper"; } + { + name = "info-fetcher-commons"; + packageId = "info-fetcher-commons"; + } { name = "krb5"; packageId = "krb5"; @@ -11428,6 +12021,7 @@ rec { { name = "moka"; packageId = "moka"; + usesDefaultFeatures = false; features = [ "future" ]; } { @@ -11463,7 +12057,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-opa-operator"; @@ -11592,7 +12186,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.4"; + packageId = "rand 0.9.5"; } { name = "regex"; @@ -11627,7 +12221,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-operator-derive"; @@ -11732,7 +12326,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; @@ -11789,7 +12383,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "strum"; @@ -11877,7 +12471,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "strum"; @@ -11969,7 +12563,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-versioned-macros"; @@ -12041,7 +12635,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; @@ -12113,7 +12707,7 @@ rec { } { name = "rand"; - packageId = "rand 0.9.4"; + packageId = "rand 0.9.5"; } { name = "serde"; @@ -12126,7 +12720,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.1"; + packageId = "snafu 0.9.2"; } { name = "stackable-certs"; @@ -12236,7 +12830,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "parsing" ]; } ]; @@ -12300,11 +12894,11 @@ rec { }; resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "full" "parsing" "printing" "proc-macro" "quote" ]; }; - "syn 2.0.118" = rec { + "syn 2.0.119" = rec { crateName = "syn"; - version = "2.0.118"; + version = "2.0.119"; edition = "2021"; - sha256 = "08hlbc32lqd5d67p26ck7chg0rkclsw9as6f96vfn4s2j1zyb6hv"; + sha256 = "15vjy620l91a3q4n4f4gzhnflmdr6pnm38v2m6cpk86i8av32a47"; authors = [ "David Tolnay " ]; @@ -12333,6 +12927,39 @@ rec { }; resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "fold" "full" "parsing" "printing" "proc-macro" "visit" "visit-mut" ]; }; + "syn 3.0.3" = rec { + crateName = "syn"; + version = "3.0.3"; + edition = "2021"; + sha256 = "18srnql3cd39j9q6hf1az02p67rlr1rf6njx9zx4vxj9i3jvmsak"; + authors = [ + "David Tolnay " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + usesDefaultFeatures = false; + } + { + name = "quote"; + packageId = "quote"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "unicode-ident"; + packageId = "unicode-ident"; + } + ]; + features = { + "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ]; + "printing" = [ "dep:quote" ]; + "proc-macro" = [ "proc-macro2/proc-macro" "quote?/proc-macro" ]; + "test" = [ "syn-test-suite/all-features" ]; + }; + resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; + }; "sync_wrapper" = rec { crateName = "sync_wrapper"; version = "1.0.2"; @@ -12376,7 +13003,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "derive" "parsing" "printing" "clone-impls" "visit" "extra-traits" ]; } @@ -12399,7 +13026,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "core-foundation"; @@ -12538,18 +13165,18 @@ rec { ]; }; - "thiserror 2.0.18" = rec { + "thiserror 2.0.19" = rec { crateName = "thiserror"; - version = "2.0.18"; + version = "2.0.19"; edition = "2021"; - sha256 = "1i7vcmw9900bvsmay7mww04ahahab7wmr8s925xc083rpjybb222"; + sha256 = "1ngwxsjsa64v1n7vb90h2b0i3fqk1piwaf0z6fqdacqfhjc3b909"; authors = [ "David Tolnay " ]; dependencies = [ { name = "thiserror-impl"; - packageId = "thiserror-impl 2.0.18"; + packageId = "thiserror-impl 2.0.19"; } ]; features = { @@ -12578,16 +13205,16 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; } ]; }; - "thiserror-impl 2.0.18" = rec { + "thiserror-impl 2.0.19" = rec { crateName = "thiserror-impl"; - version = "2.0.18"; + version = "2.0.19"; edition = "2021"; - sha256 = "1mf1vrbbimj1g6dvhdgzjmn6q09yflz2b92zs1j9n3k7cxzyxi7b"; + sha256 = "1ka10pqy1g8zy5al9m8yadg30jp8hx0q80j8awmd8131yw6gxjs3"; procMacro = true; libName = "thiserror_impl"; authors = [ @@ -12604,16 +13231,16 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 3.0.3"; } ]; }; "thread_local" = rec { crateName = "thread_local"; - version = "1.1.9"; + version = "1.1.10"; edition = "2021"; - sha256 = "1191jvl8d63agnq06pcnarivf63qzgpws5xa33hgc92gjjj4c0pn"; + sha256 = "0w20g2pfdcp8pz3gds0bzksv6mxk802szca8qlr3701jdm69rn8s"; authors = [ "Amanieu d'Antras " ]; @@ -12628,9 +13255,9 @@ rec { }; "time" = rec { crateName = "time"; - version = "0.3.53"; + version = "0.3.54"; edition = "2024"; - sha256 = "0l4aans0kv47y53736cjs0pnvdz91iyywrkqbrxk6cmrvknsmpqq"; + sha256 = "0i12170vw516jprmbv385krw75nyn7kwfp48nqybgfpnkximw79y"; authors = [ "Jacob Pratt " "Time contributors" @@ -12711,9 +13338,9 @@ rec { }; "time-macros" = rec { crateName = "time-macros"; - version = "0.2.31"; + version = "0.2.32"; edition = "2024"; - sha256 = "0pq8y9bm1zr008dmjs62qdfwsigv2kwkga5sj0d4jvk625qvhcf4"; + sha256 = "11gdd3b81mj8i0h114qfjjzm8j2rz2mhr9byr0ksjbldli196s3y"; procMacro = true; libName = "time_macros"; authors = [ @@ -12816,7 +13443,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "parsing" ]; } ]; @@ -12828,9 +13455,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.52.3"; + version = "1.53.1"; edition = "2021"; - sha256 = "1zpzazypkg61sw91na1m85x5s4rsjym335fwwhwm1hcs70dz1iwg"; + sha256 = "1v8b3b45pkpbibls75yniqbvx5dlks2708141ljni5mnf6lawb10"; authors = [ "Tokio Contributors " ]; @@ -12949,9 +13576,9 @@ rec { }; "tokio-macros" = rec { crateName = "tokio-macros"; - version = "2.7.0"; + version = "2.7.1"; edition = "2021"; - sha256 = "15m4f37mdafs0gg36sh0rskm1i768lb7zmp8bw67kaxr3avnqniq"; + sha256 = "1fj2h3gysqzwqchyhcyyvslwdj7qjgyzlc20d6sajwqf949sya33"; procMacro = true; libName = "tokio_macros"; authors = [ @@ -12968,7 +13595,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; @@ -13044,9 +13671,9 @@ rec { }; "tokio-stream" = rec { crateName = "tokio-stream"; - version = "0.1.18"; + version = "0.1.19"; edition = "2021"; - sha256 = "0w3cj33605ab58wqd382gnla5pnd9hnr00xgg333np5bka04knij"; + sha256 = "02s2ag7j40z8kx3yjy2g28l1wangawp3f1wlnwk7r99b105nzl53"; libName = "tokio_stream"; authors = [ "Tokio Contributors " @@ -13076,9 +13703,10 @@ rec { features = { "default" = [ "time" ]; "fs" = [ "tokio/fs" ]; - "full" = [ "time" "net" "io-util" "fs" "sync" "signal" ]; + "full" = [ "time" "net" "io-util" "fs" "rt" "sync" "signal" ]; "io-util" = [ "tokio/io-util" ]; "net" = [ "tokio/net" ]; + "rt" = [ "tokio/rt" ]; "signal" = [ "tokio/signal" ]; "sync" = [ "tokio/sync" "tokio-util" ]; "time" = [ "tokio/time" ]; @@ -13088,9 +13716,9 @@ rec { }; "tokio-util" = rec { crateName = "tokio-util"; - version = "0.7.18"; + version = "0.7.19"; edition = "2021"; - sha256 = "1600rd47pylwn7cap1k7s5nvdaa9j7w8kqigzp1qy7mh0p4cxscs"; + sha256 = "0licqrhrawysjrsr0qw3cgzkkjph7090hlcqcm45aazmkg81aj29"; libName = "tokio_util"; authors = [ "Tokio Contributors " @@ -13108,6 +13736,12 @@ rec { name = "futures-sink"; packageId = "futures-sink"; } + { + name = "libc"; + packageId = "libc"; + optional = true; + target = { target, features }: (target."unix" or false); + } { name = "pin-project-lite"; packageId = "pin-project-lite"; @@ -13132,6 +13766,7 @@ rec { ]; features = { "__docs_rs" = [ "futures-util" ]; + "codec" = [ "libc" ]; "compat" = [ "futures-io" ]; "full" = [ "codec" "compat" "io-util" "time" "net" "rt" "join-map" ]; "futures-io" = [ "dep:futures-io" ]; @@ -13139,13 +13774,14 @@ rec { "hashbrown" = [ "dep:hashbrown" ]; "io-util" = [ "io" "tokio/rt" "tokio/io-util" ]; "join-map" = [ "rt" "hashbrown" ]; + "libc" = [ "dep:libc" ]; "net" = [ "tokio/net" ]; "rt" = [ "tokio/rt" "tokio/sync" "futures-util" ]; "slab" = [ "dep:slab" ]; "time" = [ "tokio/time" "slab" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "codec" "default" "io" "slab" "time" ]; + resolvedDefaultFeatures = [ "codec" "default" "io" "libc" "slab" "time" ]; }; "tonic" = rec { crateName = "tonic"; @@ -13464,7 +14100,7 @@ rec { } { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "bytes"; @@ -13589,7 +14225,7 @@ rec { dependencies = [ { name = "bitflags"; - packageId = "bitflags 2.13.0"; + packageId = "bitflags 2.13.1"; } { name = "bytes"; @@ -13764,7 +14400,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.18"; + packageId = "thiserror 2.0.19"; } { name = "time"; @@ -13806,7 +14442,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ]; } @@ -14299,9 +14935,9 @@ rec { }; "uuid" = rec { crateName = "uuid"; - version = "1.23.4"; + version = "1.24.0"; edition = "2021"; - sha256 = "0lws65rrqncssdz1rk8g8ww7xg6k4d3l6avzkslzwni78llag05z"; + sha256 = "0faj5x0zgri8m3i8dv9qgyhiwqwdyhbl2g351cp3iin4ynk26fdz"; authors = [ "Ashley Mannix" "Dylan DPC" @@ -14395,6 +15031,27 @@ rec { "Sergio Benitez " ]; + }; + "walkdir" = rec { + crateName = "walkdir"; + version = "2.5.0"; + edition = "2018"; + sha256 = "0jsy7a710qv8gld5957ybrnc07gavppp963gs32xk4ag8130jy99"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "same-file"; + packageId = "same-file"; + } + { + name = "winapi-util"; + packageId = "winapi-util"; + target = { target, features }: (target."windows" or false); + } + ]; + }; "want" = rec { crateName = "want"; @@ -14575,7 +15232,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "visit" "visit-mut" "full" "extra-traits" ]; } { @@ -15125,6 +15782,41 @@ rec { "serde" = [ "dep:serde" ]; }; }; + "webpki-root-certs" = rec { + crateName = "webpki-root-certs"; + version = "1.0.9"; + edition = "2021"; + sha256 = "16qw59hxn1lln1615kb9rjy16pfxd1x8m9f9w6vwv36c5am58rdr"; + libName = "webpki_root_certs"; + dependencies = [ + { + name = "rustls-pki-types"; + packageId = "rustls-pki-types"; + rename = "pki-types"; + usesDefaultFeatures = false; + } + ]; + + }; + "winapi-util" = rec { + crateName = "winapi-util"; + version = "0.1.11"; + edition = "2021"; + sha256 = "08hdl7mkll7pz8whg869h58c1r9y7in0w0pk8fm24qc77k0b39y2"; + libName = "winapi_util"; + authors = [ + "Andrew Gallant " + ]; + dependencies = [ + { + name = "windows-sys"; + packageId = "windows-sys 0.61.2"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Storage_FileSystem" "Win32_System_Console" "Win32_System_SystemInformation" ]; + } + ]; + + }; "windows" = rec { crateName = "windows"; version = "0.61.3"; @@ -16001,7 +16693,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -16028,7 +16720,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "printing" "full" "clone-impls" ]; } @@ -17114,7 +17806,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "fold" ]; } { @@ -17126,9 +17818,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.53"; + version = "0.8.55"; edition = "2021"; - sha256 = "1qcdv45iz4499bafwcnflvzf7adfvbnvgfc5w8cx8mk12d9n0wkm"; + sha256 = "1swncvj53zi9yr08b9ddhfrcmlrmh6ijxzxcr3p6w3qlgg6hb8dm"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -17162,9 +17854,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.53"; + version = "0.8.55"; edition = "2021"; - sha256 = "0wgxvsnv44x5xdli270xh085458m76dkl0iqjpa3624hry9gs527"; + sha256 = "1sr8w9zc62lxmw7v6n89nxvqlki48b0nyfpyri6dd367f3xpds8g"; procMacro = true; libName = "zerocopy_derive"; authors = [ @@ -17182,14 +17874,14 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" ]; } ]; devDependencies = [ { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "visit" ]; } ]; @@ -17238,7 +17930,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "fold" ]; } { @@ -17292,7 +17984,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "full" "extra-traits" "visit" ]; } ]; @@ -17405,7 +18097,7 @@ rec { } { name = "syn"; - packageId = "syn 2.0.118"; + packageId = "syn 2.0.119"; features = [ "extra-traits" ]; } ]; @@ -17413,9 +18105,9 @@ rec { }; "zmij" = rec { crateName = "zmij"; - version = "1.0.21"; + version = "1.0.23"; edition = "2021"; - sha256 = "1amb5i6gz7yjb0dnmz5y669674pqmwbj44p4yfxfv2ncgvk8x15q"; + sha256 = "06zwri21nnrl34rwinmvbciap8yk1mrl8qfg9pff7lgspc56sri9"; authors = [ "David Tolnay " ]; diff --git a/Cargo.toml b/Cargo.toml index ba2d1719..d5b7ceb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ futures = { version = "0.3" } hyper = "1.4" indoc = "2.0" ldap3 = { version = "0.12", features = ["gssapi", "tls"] } -moka = { version = "0.12", features = ["future"] } +md5 = "0.8" +moka = { version = "0.12", default-features = false, features = ["future"] } native-tls = "0.2.12" pin-project = "1.1" # `default-features = false` drops reqwest 0.13's `default-tls = ["rustls"]`; we select @@ -47,6 +48,7 @@ tar = "0.4" tokio = { version = "1.52", features = ["full"] } tracing = "0.1" url = "2.5" +urlencoding = "2.1" uuid = "1.10" wiremock = "0.6" diff --git a/_TEST/CURLS.sh b/_TEST/CURLS.sh new file mode 100644 index 00000000..700655fa --- /dev/null +++ b/_TEST/CURLS.sh @@ -0,0 +1,13 @@ +curl 'localhost:9477/metadata/database?system=trino&instance=kuttl-secure-hermit-wf84/my-trino&database=tpch' | jq +curl 'localhost:9477/metadata/schema?system=trino&instance=kuttl-secure-hermit-wf84/my-trino&database=tpch&schema=sf1' | jq +curl 'localhost:9477/metadata/table?system=trino&instance=kuttl-secure-hermit-wf84/my-trino&database=tpch&schema=sf1&table=customer' | jq +curl 'localhost:9477/metadata/stream?system=kafka&instance=kuttl-secure-hermit-wf84/test-kafka&queue=orders' | jq +curl 'localhost:9477/metadata/dashboard?system=superset&instance=kuttl-secure-hermit-wf84/my-superset&id=1' | jq +curl 'localhost:9477/metadata/chart?system=superset&instance=kuttl-secure-hermit-wf84/my-superset&id=1' | jq + +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:container:792ce6aace288712a1ef036fbca2bb37' | jq +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:container:ae5d10cb199e26e8df70c7563e6d1c58' | jq +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:dataset:(urn:li:dataPlatform:trino,kuttl-secure-hermit-wf84/my-trino.tpch.sf1.customer,PROD)' | jq +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:dataset:(urn:li:dataPlatform:kafka,kuttl-secure-hermit-wf84/test-kafka.orders,PROD)' | jq +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:dashboard:(superset,kuttl-secure-hermit-wf84/my-superset.1)' | jq +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:chart:(superset,kuttl-secure-hermit-wf84/my-superset.1)' | jq diff --git a/docs/modules/opa/pages/usage-guide/resource-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/resource-info-fetcher.adoc new file mode 100644 index 00000000..e90a3eb8 --- /dev/null +++ b/docs/modules/opa/pages/usage-guide/resource-info-fetcher.adoc @@ -0,0 +1,165 @@ += Resource info fetcher +:description: Resource Info Fetcher for OPA retrieves data from backends like DataHub. Integrate extra resource details into Rego rules for enhanced policy management. + +The _Resource info fetcher_ allows for additional information to be obtained from the configured backend (for example, DataHub). +You can then write Rego rules for OpenPolicyAgent which make an HTTP request to the Resource info fetcher and make use of the additional information returned for the resource, such as Trino tables or Apache Kafka topics. + +You can enable the Resource info fetcher sidecar as follows: + +[source,yaml] +---- +apiVersion: opa.stackable.tech/v1alpha2 +kind: OpaCluster +metadata: + name: opa +spec: + image: + productVersion: 1.0.0 + clusterConfig: + resourceInfo: # <1> + backend: + dataHub: + hostname: datahub-gms.my-namespace.svc.cluster.local + port: 8080 + tls: + verification: + server: + caCert: + secretClass: tls # <2> + credentialsSecretName: resource-info-fetcher-credentials # <3> + env: PROD # <4> + cache: # optional, enabled by default + entryTimeToLive: 60s # optional, defaults to 60s + servers: + roleGroups: + default: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: resource-info-fetcher-credentials +stringData: + token: # <3> +---- + +<1> Enable the `resource-info-fetcher` sidecar +<2> Enable TLS verification using the CA from the `tls` SecretClass. +<3> Authenticate to DataHub with a Personal Access Token (PAT) read from the specified Secret. The Secret must have a `token` entry. See the DataHub documentation on https://docs.datahub.com/docs/authentication/personal-access-tokens[Personal Access Tokens] for how to create one, and make sure https://docs.datahub.com/docs/authentication/introducing-metadata-service-authentication[Metadata Service Authentication] is enabled on your DataHub. +<4> The DataHub environment (fabric) to query, e.g. `PROD` or `DEV`. Defaults to `PROD`, which is also what DataHub's ingestion sources default to. + +Currently the following backends are supported: + +* xref:#backend-data-hub[] + +[#backends] +== Backends + +The initial version is tightly modeled to the only supported backend, namely DataHub. +The API might change as soon as we add support for other backends. + + +[#backend-data-hub] +=== DataHub + +DataHub is currently only supported backend, so the general documentation from above applies. + +== Resource info fetcher API + +Resource information can be retrieved from regorules using the functions in `data.stackable.opa.resourceinfo.v1`. +There is one function per kind of resource: + +[source,rego] +---- +databaseResourceInfo(system, instance, database) +schemaResourceInfo(system, instance, database, schema) +tableResourceInfo(system, instance, database, schema, table) +streamResourceInfo(system, instance, queue) +dashboardResourceInfo(system, instance, id) +chartResourceInfo(system, instance, id) + +rawIdentifierResourceInfo(identifier) +---- + +The naming is intentionally product-agnostic, so that one function serves the equivalent resource of every product. +`databaseResourceInfo` addresses what Trino calls a catalog, and `streamResourceInfo` what Apache Kafka calls a topic. + +`rawIdentifierResourceInfo` is the escape hatch for resources the functions above do not cover: it passes the identifier to the backend as-is. +For DataHub that is a URN, such as `urn:li:chart:(superset,my-namespace/my-superset.1)`. + +The first two arguments are the same everywhere: + +* `system` is the kind of product the resource lives in, for example `trino`, `kafka` or `superset`. DataHub calls this the _data platform_. +* `instance` identifies _which_ deployment of that product, for example `my-namespace/my-trino`. DataHub calls this the _platform instance_, and the value must match the `platform_instance` of the ingestion source that produced the metadata. + +The DataHub environment (fabric) is deliberately *not* an argument: it describes how the catalog was populated rather than the resource being authorized, so it is configured once on the OpaCluster (see `env` above) instead of being passed in by every Rego rule. + +An example of the returned structure: + +[source,json] +---- +{ + "dataProducts": [], + "domain": null, + "owners": { + "urn:li:ownershipType:__system__business_owner": { + "groups": [ + { + "description": "Customer Service/Analytics (mirrored from the Keycloak demo realm)", + "displayName": "Customer Service/Analytics", + "urn": "urn:li:corpGroup:customer-service-analytics" + } + ], + "ownershipTypeName": "Business Owner", + "users": [] + } + }, + "tags": [ + { + "name": "PII", + "urn": "urn:li:tag:PII" + } + ], + "urn": "urn:li:container:c8531e5a52cacf56768d0bf77ca8787c" +} +---- + +=== Debug request + +To debug the resource-info-fetcher you can `curl` its API for a given resource. +Every Rego function above maps to a `GET /metadata/` endpoint that takes its arguments as query parameters. +To achieve this shell into the `opa` container and execute + +[source,bash] +---- +curl 'localhost:9477/metadata/schema?system=trino&instance=my-namespace/my-trino&database=lakehouse&schema=customer_analytics' | jq + +curl 'localhost:9477/metadata/chart?system=superset&instance=my-namespace/my-superset&id=1' | jq + +curl 'localhost:9477/metadata/rawIdentifier?identifier=urn:li:chart:(superset,my-namespace/my-superset.1)' | jq +---- + +=== Rego rule library + +The HTTP API exposed by the resource-info-fetcher can be called directly using the rego function `http.send`. +However, we provide a convenience rego rule library, which we ship with `OpaClusters` by default. + +For example, the following rule allows access to tables tagged as `public`: + +[source,rego] +---- +package test + +import data.stackable.opa.resourceinfo.v1 as resourceinfo + +default allow := false + +allow if { + table := resourceinfo.tableResourceInfo("trino", "my-namespace/my-trino", input.catalog, input.schema, input.table) + some tag in table.tags + tag.urn == "urn:li:tag:public" +} +---- + +A resource the backend does not know about is not reported as an error: the resource-info-fetcher returns a record with empty `tags`, `owners` and `dataProducts` and a `null` `domain`. +Prefer rules that require a positive signal, like the one above, which denies access in that case. +A rule that merely excludes a tag would instead grant access to every resource missing from the backend. diff --git a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc index 7eb9b212..da570a8d 100644 --- a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc +++ b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc @@ -46,7 +46,7 @@ stringData: <1> Enable the `user-info-fetcher` sidecar <2> Enable TLS verification using the CA from the `tls` SecretClass. -<3> Obtain Keycloak API credentials from the specified secret. The Secret must have `clientId` and `clientSecret` entries. +<3> Obtain Keycloak API credentials from the specified Secret. The Secret must have `clientId` and `clientSecret` entries. <4> Refer to the applicable realm in your Keycloak server. Currently the following backends are supported: diff --git a/extra/crds.yaml b/extra/crds.yaml index f5ce8061..0f714c82 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -6,7 +6,6 @@ metadata: spec: group: opa.stackable.tech names: - categories: [] kind: OpaCluster plural: opaclusters shortNames: @@ -14,8 +13,7 @@ spec: singular: opacluster scope: Namespaced versions: - - additionalPrinterColumns: [] - name: v1alpha2 + - name: v1alpha2 schema: openAPIV3Schema: description: An Open Policy Agent (OPA) cluster stacklet. This resource is managed by the Stackable operator for OPA. @@ -29,6 +27,7 @@ spec: clusterConfig: default: listenerClass: cluster-internal + resourceInfo: null tls: null userInfo: null description: Global OPA cluster configuration that applies to all roles and role groups. @@ -52,6 +51,145 @@ spec: - external-unstable - external-stable type: string + resourceInfo: + description: |- + Configures how to fetch additional metadata about resource information from a data + catalog. + + Data catalog could e.g. be DataHub and resources could be Trino catalogs, schemas, + tables or Kafka topics etc. + nullable: true + properties: + backend: + description: The backend directory service to use. + oneOf: + - required: + - dataHub + properties: + dataHub: + description: Backend that fetches resource information from DataHub. + properties: + credentialsSecretName: + description: |- + Name of a Secret containing a DataHub Personal Access Token (PAT) that is authorized + to read resource metadata. + + Must contain the field `token`. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + env: + default: PROD + description: |- + The DataHub environment (in DataHub terms: the fabric) the resources live in. + + This must match the `env` of the DataHub ingestion source that produced the metadata, + because `env` is part of the dataset URN (and of the container key that database and + schema URNs are hashed from). A mismatch is not reported as an error: the constructed URN + simply does not resolve and the resource-info-fetcher returns empty metadata. + + This is configured here rather than passed in by the Rego rule on purpose. `env` + describes how the catalog was populated, not the resource that is being authorized, and + the Rego rule has no way of knowing it. It is also DataHub-specific: no other metadata + catalog we know of has this concept. + + `env` is largely superseded by DataHub's platform instance, which the Rego rules already + pass as `instance` — within a single DataHub, a platform instance belongs to exactly one + fabric. Should a single resource-info-fetcher ever need to serve multiple fabrics, we + would add per-instance `envOverrides` here instead of moving `env` back into the API. + enum: + - DEV + - TEST + - QA + - UAT + - EI + - PRE + - STG + - NON_PROD + - PROD + - CORP + - RVW + - PRD + - TST + - SIT + - SBX + - SANDBOX + - CERT + type: string + hostname: + description: Hostname of DataHub + type: string + port: + description: Port of DataHub. If TLS is used defaults to `443`, otherwise to `80`. + format: uint16 + maximum: 65535.0 + minimum: 0.0 + nullable: true + type: integer + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: |- + Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. + Note that a SecretClass does not need to have a key but can also work with just a CA certificate, + so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: |- + Use TLS and the CA certificates trusted by the common web browsers to verify the server. + This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - credentialsSecretName + - hostname + type: object + type: object + cache: + default: + entryTimeToLive: 1m + description: Caching configuration. + properties: + entryTimeToLive: + default: 1m + description: How long responses should be cached for. + type: string + type: object + required: + - backend + type: object tls: description: |- TLS encryption settings for the OPA server. @@ -518,7 +656,7 @@ spec: properties: entryTimeToLive: default: 1m - description: How long metadata about each user should be cached for. + description: How long responses should be cached for. type: string type: object type: object @@ -925,6 +1063,86 @@ spec: description: Configuration per logger type: object type: object + resource-info-fetcher: + anyOf: + - required: + - custom + - {} + - {} + description: Log configuration of the container + properties: + console: + description: Configuration for the console appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + custom: + description: Log configuration provided in a ConfigMap + properties: + configMap: + description: ConfigMap containing the log configuration files + nullable: true + type: string + type: object + file: + description: Configuration for the file appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + loggers: + additionalProperties: + description: Configuration of a logger + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + default: {} + description: Configuration per logger + type: object + type: object user-info-fetcher: anyOf: - required: @@ -1546,6 +1764,86 @@ spec: description: Configuration per logger type: object type: object + resource-info-fetcher: + anyOf: + - required: + - custom + - {} + - {} + description: Log configuration of the container + properties: + console: + description: Configuration for the console appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + custom: + description: Log configuration provided in a ConfigMap + properties: + configMap: + description: ConfigMap containing the log configuration files + nullable: true + type: string + type: object + file: + description: Configuration for the file appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + loggers: + additionalProperties: + description: Configuration of a logger + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + default: {} + description: Configuration per logger + type: object + type: object user-info-fetcher: anyOf: - required: @@ -1945,8 +2243,7 @@ spec: storage: true subresources: status: {} - - additionalPrinterColumns: [] - name: v1alpha1 + - name: v1alpha1 schema: openAPIV3Schema: description: An Open Policy Agent (OPA) cluster stacklet. This resource is managed by the Stackable operator for OPA. @@ -1960,6 +2257,7 @@ spec: clusterConfig: default: listenerClass: cluster-internal + resourceInfo: null tls: null userInfo: null description: Global OPA cluster configuration that applies to all roles and role groups. @@ -1983,6 +2281,145 @@ spec: - external-unstable - external-stable type: string + resourceInfo: + description: |- + Configures how to fetch additional metadata about resource information from a data + catalog. + + Data catalog could e.g. be DataHub and resources could be Trino catalogs, schemas, + tables or Kafka topics etc. + nullable: true + properties: + backend: + description: The backend directory service to use. + oneOf: + - required: + - dataHub + properties: + dataHub: + description: Backend that fetches resource information from DataHub. + properties: + credentialsSecretName: + description: |- + Name of a Secret containing a DataHub Personal Access Token (PAT) that is authorized + to read resource metadata. + + Must contain the field `token`. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + env: + default: PROD + description: |- + The DataHub environment (in DataHub terms: the fabric) the resources live in. + + This must match the `env` of the DataHub ingestion source that produced the metadata, + because `env` is part of the dataset URN (and of the container key that database and + schema URNs are hashed from). A mismatch is not reported as an error: the constructed URN + simply does not resolve and the resource-info-fetcher returns empty metadata. + + This is configured here rather than passed in by the Rego rule on purpose. `env` + describes how the catalog was populated, not the resource that is being authorized, and + the Rego rule has no way of knowing it. It is also DataHub-specific: no other metadata + catalog we know of has this concept. + + `env` is largely superseded by DataHub's platform instance, which the Rego rules already + pass as `instance` — within a single DataHub, a platform instance belongs to exactly one + fabric. Should a single resource-info-fetcher ever need to serve multiple fabrics, we + would add per-instance `envOverrides` here instead of moving `env` back into the API. + enum: + - DEV + - TEST + - QA + - UAT + - EI + - PRE + - STG + - NON_PROD + - PROD + - CORP + - RVW + - PRD + - TST + - SIT + - SBX + - SANDBOX + - CERT + type: string + hostname: + description: Hostname of DataHub + type: string + port: + description: Port of DataHub. If TLS is used defaults to `443`, otherwise to `80`. + format: uint16 + maximum: 65535.0 + minimum: 0.0 + nullable: true + type: integer + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: |- + Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. + Note that a SecretClass does not need to have a key but can also work with just a CA certificate, + so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: |- + Use TLS and the CA certificates trusted by the common web browsers to verify the server. + This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - credentialsSecretName + - hostname + type: object + type: object + cache: + default: + entryTimeToLive: 1m + description: Caching configuration. + properties: + entryTimeToLive: + default: 1m + description: How long responses should be cached for. + type: string + type: object + required: + - backend + type: object tls: description: |- TLS encryption settings for the OPA server. @@ -2449,7 +2886,7 @@ spec: properties: entryTimeToLive: default: 1m - description: How long metadata about each user should be cached for. + description: How long responses should be cached for. type: string type: object type: object @@ -2856,6 +3293,86 @@ spec: description: Configuration per logger type: object type: object + resource-info-fetcher: + anyOf: + - required: + - custom + - {} + - {} + description: Log configuration of the container + properties: + console: + description: Configuration for the console appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + custom: + description: Log configuration provided in a ConfigMap + properties: + configMap: + description: ConfigMap containing the log configuration files + nullable: true + type: string + type: object + file: + description: Configuration for the file appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + loggers: + additionalProperties: + description: Configuration of a logger + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + default: {} + description: Configuration per logger + type: object + type: object user-info-fetcher: anyOf: - required: @@ -3477,6 +3994,86 @@ spec: description: Configuration per logger type: object type: object + resource-info-fetcher: + anyOf: + - required: + - custom + - {} + - {} + description: Log configuration of the container + properties: + console: + description: Configuration for the console appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + custom: + description: Log configuration provided in a ConfigMap + properties: + configMap: + description: ConfigMap containing the log configuration files + nullable: true + type: string + type: object + file: + description: Configuration for the file appender + nullable: true + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + loggers: + additionalProperties: + description: Configuration of a logger + properties: + level: + description: |- + The log level threshold. + Log events with a lower log level are discarded. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - NONE + - null + nullable: true + type: string + type: object + default: {} + description: Configuration per logger + type: object + type: object user-info-fetcher: anyOf: - required: diff --git a/rust/info-fetcher-commons/Cargo.toml b/rust/info-fetcher-commons/Cargo.toml new file mode 100644 index 00000000..aa246ee2 --- /dev/null +++ b/rust/info-fetcher-commons/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "info-fetcher-commons" +description = "Common shared code between info fetchers" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +publish = false + +[dependencies] +stackable-operator.workspace = true + +axum.workspace = true +hyper.workspace = true +native-tls.workspace = true +reqwest.workspace = true +rustls-pki-types.workspace = true +serde.workspace = true +serde_json.workspace = true +snafu.workspace = true +tracing.workspace = true +tokio.workspace = true diff --git a/rust/info-fetcher-commons/src/config.rs b/rust/info-fetcher-commons/src/config.rs new file mode 100644 index 00000000..33633860 --- /dev/null +++ b/rust/info-fetcher-commons/src/config.rs @@ -0,0 +1,33 @@ +use std::{ + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; + +use serde::de::DeserializeOwned; +use snafu::{ResultExt, Snafu}; + +#[derive(Snafu, Debug)] +pub enum ConfigError { + #[snafu(display("failed to open config file from {path:?}"))] + OpenFile { + source: std::io::Error, + path: PathBuf, + }, + + #[snafu(display("unable to read config file from {path:?}"))] + ParseConfigFile { + source: serde_json::Error, + path: PathBuf, + }, +} + +pub fn read_config_file(path: &Path) -> Result +where + C: DeserializeOwned, +{ + let file = File::open(path).with_context(|_| OpenFileSnafu { path })?; + let reader = BufReader::new(file); + + serde_json::from_reader(reader).with_context(|_| ParseConfigFileSnafu { path }) +} diff --git a/rust/user-info-fetcher/src/http_error.rs b/rust/info-fetcher-commons/src/http_error.rs similarity index 100% rename from rust/user-info-fetcher/src/http_error.rs rename to rust/info-fetcher-commons/src/http_error.rs diff --git a/rust/info-fetcher-commons/src/lib.rs b/rust/info-fetcher-commons/src/lib.rs new file mode 100644 index 00000000..143215d6 --- /dev/null +++ b/rust/info-fetcher-commons/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod http_error; +pub mod utils; diff --git a/rust/user-info-fetcher/src/utils/http.rs b/rust/info-fetcher-commons/src/utils/http.rs similarity index 88% rename from rust/user-info-fetcher/src/utils/http.rs rename to rust/info-fetcher-commons/src/utils/http.rs index eb171d55..31ea5f2b 100644 --- a/rust/user-info-fetcher/src/utils/http.rs +++ b/rust/info-fetcher-commons/src/utils/http.rs @@ -4,6 +4,7 @@ use hyper::StatusCode; use reqwest::{ClientBuilder, RequestBuilder, Response}; use serde::de::DeserializeOwned; use snafu::{ResultExt, Snafu}; +use tracing::{instrument, trace}; /// Overall deadline for a single outbound HTTP request. /// Backends can issue several requests per lookup, so this bounds each one, not the lookup @@ -26,7 +27,7 @@ pub enum Error { HttpRequest { source: reqwest::Error }, #[snafu(display("failed to parse json response"))] - ParseJson { source: reqwest::Error }, + ParseJson { source: serde_json::Error }, #[snafu(display("http response {status:?} for {url:?} with response body {text:?}"))] HttpErrorResponse { @@ -43,14 +44,19 @@ pub enum Error { }, } +#[instrument(skip_all)] pub async fn send_json_request(req: RequestBuilder) -> Result { // make the request let response = req.send().await.context(HttpRequestSnafu)?; // check for client or server errors + let url = response.url().clone(); let non_error_response = error_for_status(response).await?; // parse the result - let result = non_error_response.json().await.context(ParseJsonSnafu)?; - Ok(result) + let json = non_error_response.text().await.context(HttpRequestSnafu)?; + + trace!(%url, json, "Got HTTP JSON response"); + + serde_json::from_str(&json).context(ParseJsonSnafu) } /// Wraps a Response into a Result. If there is an HTTP Client or Server error, diff --git a/rust/user-info-fetcher/src/utils/mod.rs b/rust/info-fetcher-commons/src/utils/mod.rs similarity index 100% rename from rust/user-info-fetcher/src/utils/mod.rs rename to rust/info-fetcher-commons/src/utils/mod.rs diff --git a/rust/user-info-fetcher/src/utils/tls.rs b/rust/info-fetcher-commons/src/utils/tls.rs similarity index 100% rename from rust/user-info-fetcher/src/utils/tls.rs rename to rust/info-fetcher-commons/src/utils/tls.rs diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index a87f1bfe..ed99ec72 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -54,6 +54,7 @@ pub fn build( service_account_name: &str, opa_bundle_builder_image: &str, user_info_fetcher_image: &str, + resource_info_fetcher_image: &str, cluster_info: &KubernetesClusterInfo, ) -> Result { let mut daemon_sets = vec![]; @@ -81,6 +82,7 @@ pub fn build( role_group, opa_bundle_builder_image, user_info_fetcher_image, + resource_info_fetcher_image, service_account_name, cluster_info, ) @@ -155,6 +157,7 @@ mod tests { "test-opa-serviceaccount", "bundle-builder-image", "user-info-fetcher-image", + "resource-info-fetcher-image", &cluster_info(), ) .expect("build succeeds"); diff --git a/rust/operator-binary/src/controller/build/properties/mod.rs b/rust/operator-binary/src/controller/build/properties/mod.rs index 44d83b46..7f7b21dd 100644 --- a/rust/operator-binary/src/controller/build/properties/mod.rs +++ b/rust/operator-binary/src/controller/build/properties/mod.rs @@ -2,6 +2,7 @@ pub mod config_json; pub mod product_logging; +pub mod resource_info_fetcher; pub mod user_info_fetcher; /// The names of the config files assembled into the rolegroup `ConfigMap`. @@ -14,6 +15,8 @@ pub enum ConfigFileName { ConfigJson, #[strum(serialize = "user-info-fetcher.json")] UserInfoFetcher, + #[strum(serialize = "resource-info-fetcher.json")] + ResourceInfoFetcher, } #[cfg(test)] diff --git a/rust/operator-binary/src/controller/build/properties/resource_info_fetcher.rs b/rust/operator-binary/src/controller/build/properties/resource_info_fetcher.rs new file mode 100644 index 00000000..85dbf28a --- /dev/null +++ b/rust/operator-binary/src/controller/build/properties/resource_info_fetcher.rs @@ -0,0 +1,18 @@ +//! Builds the OPA `resource-info-fetcher.json` file. + +use snafu::{ResultExt, Snafu}; + +use crate::crd::resource_info_fetcher; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to serialize resource info fetcher configuration"))] + SerializeResourceInfoFetcherConfig { source: serde_json::Error }, +} + +type Result = std::result::Result; + +/// Serializes the resource-info-fetcher configuration into the `resource-info-fetcher.json` file content. +pub fn build(resource_info: &resource_info_fetcher::v1alpha1::Config) -> Result { + serde_json::to_string_pretty(resource_info).context(SerializeResourceInfoFetcherConfigSnafu) +} diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index bc9aee78..9e3f0878 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -9,7 +9,9 @@ use stackable_operator::{ use crate::controller::{ OpaRoleGroupConfig, RoleGroupName, ValidatedCluster, - build::properties::{ConfigFileName, config_json, product_logging, user_info_fetcher}, + build::properties::{ + ConfigFileName, config_json, product_logging, resource_info_fetcher, user_info_fetcher, + }, }; #[derive(Snafu, Debug)] @@ -20,6 +22,11 @@ pub enum Error { #[snafu(display("failed to build user-info-fetcher.json"))] BuildUserInfoFetcher { source: user_info_fetcher::Error }, + #[snafu(display("failed to build resource-info-fetcher.json"))] + BuildResourceInfoFetcher { + source: resource_info_fetcher::Error, + }, + #[snafu(display("failed to assemble ConfigMap for role group {role_group}"))] Assemble { source: stackable_operator::builder::configmap::Error, @@ -63,6 +70,12 @@ pub fn build_rolegroup_config_map( user_info_fetcher::build(user_info).context(BuildUserInfoFetcherSnafu)?, ); } + if let Some(resource_info) = &cluster.cluster_config.resource_info { + cm_builder.add_data( + ConfigFileName::ResourceInfoFetcher.to_string(), + resource_info_fetcher::build(resource_info).context(BuildResourceInfoFetcherSnafu)?, + ); + } if rolegroup_config.config.logging.vector_container.is_some() { cm_builder.add_data( diff --git a/rust/operator-binary/src/controller/build/resource/daemonset/mod.rs b/rust/operator-binary/src/controller/build/resource/daemonset/mod.rs index 2846e488..5df9cc07 100644 --- a/rust/operator-binary/src/controller/build/resource/daemonset/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/daemonset/mod.rs @@ -49,12 +49,19 @@ use super::service::{self, APP_PORT, APP_PORT_NAME}; use crate::{ controller::{ OpaRoleGroupConfig, RoleGroupName, ValidatedCluster, ValidatedOpaConfig, - build::{self, resource::daemonset::user_info_fetcher::add_user_info_fetcher_sidecar}, + build::{ + self, + resource::daemonset::{ + resource_info_fetcher::add_resource_info_fetcher_sidecar, + user_info_fetcher::add_user_info_fetcher_sidecar, + }, + }, }, crd::{Container, DEFAULT_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT}, operations::graceful_shutdown::add_graceful_shutdown_config, }; +mod resource_info_fetcher; mod user_info_fetcher; pub const BUNDLES_ACTIVE_DIR: &str = "/bundles/active"; @@ -66,10 +73,13 @@ const CONFIG_DIR: &str = "/stackable/config"; stackable_operator::constant!(LOG_VOLUME_NAME: VolumeName = "log"); stackable_operator::constant!(BUNDLES_VOLUME_NAME: VolumeName = "bundles"); const BUNDLES_DIR: &str = "/bundles"; -stackable_operator::constant!(USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME: VolumeName = "credentials"); +stackable_operator::constant!(USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME: VolumeName = "user-info-fetcher-credentials"); +// As UIF and RIF run in two different containers, the directories won't clash const USER_INFO_FETCHER_CREDENTIALS_DIR: &str = "/stackable/credentials"; stackable_operator::constant!(USER_INFO_FETCHER_KERBEROS_VOLUME_NAME: VolumeName = "kerberos"); const USER_INFO_FETCHER_KERBEROS_DIR: &str = "/stackable/kerberos"; +stackable_operator::constant!(RESOURCE_INFO_FETCHER_CREDENTIALS_VOLUME_NAME: VolumeName = "resource-info-fetcher-credentials"); +const RESOURCE_INFO_FETCHER_CREDENTIALS_DIR: &str = "/stackable/credentials"; stackable_operator::constant!(TLS_VOLUME_NAME: VolumeName = "tls"); const TLS_STORE_DIR: &str = "/stackable/tls"; @@ -145,6 +155,11 @@ pub enum Error { #[snafu(display("failed to build User Info Fetcher sidecar"))] BuildUserInfoFetcherSidecar { source: user_info_fetcher::Error }, + + #[snafu(display("failed to build Resource Info Fetcher sidecar"))] + BuildResourceInfoFetcherSidecar { + source: resource_info_fetcher::Error, + }, } type Result = std::result::Result; @@ -221,6 +236,7 @@ pub fn build_server_rolegroup_daemonset( role_group: &OpaRoleGroupConfig, opa_bundle_builder_image: &str, user_info_fetcher_image: &str, + resource_info_fetcher_image: &str, service_account_name: &str, cluster_info: &KubernetesClusterInfo, ) -> Result { @@ -422,6 +438,14 @@ pub fn build_server_rolegroup_daemonset( cluster_info, ) .context(BuildUserInfoFetcherSidecarSnafu)?; + add_resource_info_fetcher_sidecar( + &mut pb, + cluster, + merged_config, + resource_info_fetcher_image, + cluster_info, + ) + .context(BuildResourceInfoFetcherSidecarSnafu)?; // The Vector logging config was validated up-front (see `ValidatedLogging`); a `Some` here means // the Vector agent is enabled and the aggregator discovery ConfigMap name is valid. @@ -734,6 +758,7 @@ mod tests { role_group, "bundle-builder-image", "user-info-fetcher-image", + "resource-info-fetcher-image", "test-opa-serviceaccount", &cluster_info(), ) @@ -1103,9 +1128,9 @@ mod tests { }))); // The client credentials secret is projected into the sidecar's credentials dir. - assert!(volume_names(&ds).contains(&"credentials".to_owned())); + assert!(volume_names(&ds).contains(&"user-info-fetcher-credentials".to_owned())); assert_eq!( - mount_path(&uif_container(&ds), "credentials"), + mount_path(&uif_container(&ds), "user-info-fetcher-credentials"), "/stackable/credentials" ); } diff --git a/rust/operator-binary/src/controller/build/resource/daemonset/resource_info_fetcher.rs b/rust/operator-binary/src/controller/build/resource/daemonset/resource_info_fetcher.rs new file mode 100644 index 00000000..4e7cf9f9 --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/daemonset/resource_info_fetcher.rs @@ -0,0 +1,107 @@ +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::{ + self, + pod::{PodBuilder, volume::VolumeBuilder}, + }, + commons::tls_verification::TlsClientDetailsError, + k8s_openapi::api::core::v1::SecretVolumeSource, + utils::cluster_info::KubernetesClusterInfo, + v2::builder::pod::container::new_container_builder, +}; + +use crate::{ + controller::{ + ValidatedCluster, ValidatedOpaConfig, + build::{ + self, + resource::daemonset::{ + CONFIG_DIR, CONFIG_VOLUME_NAME, RESOURCE_INFO_FETCHER_CREDENTIALS_DIR, + RESOURCE_INFO_FETCHER_CREDENTIALS_VOLUME_NAME, add_stackable_rust_cli_env_vars, + container_name, sidecar_container_log_level, sidecar_resource_requirements, + }, + }, + }, + crd::{Container, resource_info_fetcher}, +}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display( + "failed to build volume or volume mount spec for the Resource Info Fetcher TLS config" + ))] + TlsVolumeAndMounts { source: TlsClientDetailsError }, + + #[snafu(display("failed to add needed volume"))] + AddVolume { source: builder::pod::Error }, + + #[snafu(display("failed to add needed volumeMount"))] + AddVolumeMount { + source: builder::pod::container::Error, + }, +} + +type Result = std::result::Result; + +pub fn add_resource_info_fetcher_sidecar( + pb: &mut PodBuilder, + cluster: &ValidatedCluster, + merged_config: &ValidatedOpaConfig, + resource_info_fetcher_image: &str, + cluster_info: &KubernetesClusterInfo, +) -> Result<()> { + if let Some(resource_info) = &cluster.cluster_config.resource_info { + let rif_container_name = container_name(&Container::ResourceInfoFetcher); + let mut cb_rif = new_container_builder(&rif_container_name); + + cb_rif + .image_from_product_image(&cluster.image) // inherit the pull policy and pull secrets, and then... + .image(resource_info_fetcher_image) // ...override the image + .command(vec!["stackable-opa-resource-info-fetcher".to_string()]) + .add_env_var( + "CONFIG", + format!( + "{CONFIG_DIR}/{file}", + file = build::properties::ConfigFileName::ResourceInfoFetcher + ), + ) + .add_env_var("CREDENTIALS_DIR", RESOURCE_INFO_FETCHER_CREDENTIALS_DIR) + .add_volume_mount(CONFIG_VOLUME_NAME.as_ref(), CONFIG_DIR) + .context(AddVolumeMountSnafu)? + .resources(sidecar_resource_requirements()); + add_stackable_rust_cli_env_vars( + &mut cb_rif, + cluster_info, + sidecar_container_log_level(merged_config, &Container::ResourceInfoFetcher).to_string(), + &Container::ResourceInfoFetcher, + ); + + match &resource_info.backend { + resource_info_fetcher::v1alpha1::Backend::DataHub(data_hub) => { + pb.add_volume( + VolumeBuilder::new(RESOURCE_INFO_FETCHER_CREDENTIALS_VOLUME_NAME.as_ref()) + .secret(SecretVolumeSource { + secret_name: Some(data_hub.credentials_secret_name.to_string()), + ..Default::default() + }) + .build(), + ) + .context(AddVolumeSnafu)?; + cb_rif + .add_volume_mount( + RESOURCE_INFO_FETCHER_CREDENTIALS_VOLUME_NAME.as_ref(), + RESOURCE_INFO_FETCHER_CREDENTIALS_DIR, + ) + .context(AddVolumeMountSnafu)?; + data_hub + .tls + .add_volumes_and_mounts(pb, vec![&mut cb_rif]) + .context(TlsVolumeAndMountsSnafu)?; + } + } + + pb.add_container(cb_rif.build()); + } + + Ok(()) +} diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index c7e50986..7b1c9e4f 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -35,7 +35,7 @@ use stackable_operator::{ use crate::{ crd::{ APP_NAME, OPERATOR_NAME, OpaConfig, OpaConfigOverrides, OpaRole, OpaStorageConfig, - user_info_fetcher, v1alpha2, + resource_info_fetcher, user_info_fetcher, v1alpha2, }, opa_controller::OPA_CONTROLLER_NAME, }; @@ -252,6 +252,7 @@ pub struct KubernetesResources { /// raw `OpaCluster` to render config (except for owner references). pub struct ValidatedClusterConfig { pub user_info: Option, + pub resource_info: Option, pub tls: Option, pub listener_class: v1alpha2::CurrentlySupportedListenerClasses, } diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index aef9c278..9698e950 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -231,6 +231,7 @@ pub fn validate( image, ValidatedClusterConfig { user_info: opa.spec.cluster_config.user_info.clone(), + resource_info: opa.spec.cluster_config.resource_info.clone(), tls: opa.spec.cluster_config.tls.clone(), listener_class: opa.spec.cluster_config.listener_class.clone(), }, diff --git a/rust/operator-binary/src/crd/cache.rs b/rust/operator-binary/src/crd/cache.rs new file mode 100644 index 00000000..fae0f2c2 --- /dev/null +++ b/rust/operator-binary/src/crd/cache.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; +use stackable_operator::{ + schemars::{self, JsonSchema}, + shared::time::Duration, +}; + +/// Default time-to-live for cached responses. +pub(crate) const DEFAULT_CACHE_ENTRY_TIME_TO_LIVE: Duration = Duration::from_minutes_unchecked(1); + +#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Cache { + /// How long responses should be cached for. + #[serde(default = "default_entry_time_to_live")] + pub entry_time_to_live: Duration, +} + +impl Default for Cache { + fn default() -> Self { + Self { + entry_time_to_live: DEFAULT_CACHE_ENTRY_TIME_TO_LIVE, + } + } +} + +const fn default_entry_time_to_live() -> Duration { + DEFAULT_CACHE_ENTRY_TIME_TO_LIVE +} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 1c090312..29ed7771 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -27,6 +27,8 @@ use stackable_operator::{ }; use strum::{Display, EnumIter, EnumString}; +pub mod cache; +pub mod resource_info_fetcher; pub mod user_info_fetcher; pub const APP_NAME: &str = "opa"; @@ -119,6 +121,14 @@ pub mod versioned { #[serde(default)] pub user_info: Option, + /// Configures how to fetch additional metadata about resource information from a data + /// catalog. + /// + /// Data catalog could e.g. be DataHub and resources could be Trino catalogs, schemas, + /// tables or Kafka topics etc. + #[serde(default)] + pub resource_info: Option, + /// TLS encryption settings for the OPA server. /// When configured, OPA will use HTTPS (port 8443) instead of HTTP (port 8081). /// Clients must connect using HTTPS and trust the certificates provided by the configured SecretClass. @@ -200,6 +210,7 @@ pub enum Container { BundleBuilder, Opa, UserInfoFetcher, + ResourceInfoFetcher, } // NOTE (@Techassi): This struct can currently NOT be versioned because it is used via Role which diff --git a/rust/operator-binary/src/crd/resource_info_fetcher/mod.rs b/rust/operator-binary/src/crd/resource_info_fetcher/mod.rs new file mode 100644 index 00000000..0976862f --- /dev/null +++ b/rust/operator-binary/src/crd/resource_info_fetcher/mod.rs @@ -0,0 +1,136 @@ +use serde::{Deserialize, Serialize}; +use stackable_operator::{ + commons::{networking::HostName, tls_verification::TlsClientDetails}, + schemars::{self, JsonSchema}, + v2::types::kubernetes::SecretName, + versioned::versioned, +}; + +use crate::crd::cache::Cache; + +#[versioned(version(name = "v1alpha1"))] +pub mod versioned { + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct Config { + /// The backend directory service to use. + pub backend: Backend, + + /// Caching configuration. + #[serde(default)] + pub cache: Cache, + } + + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub enum Backend { + /// Backend that fetches resource information from DataHub. + DataHub(DataHubBackend), + } + + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct DataHubBackend { + /// Hostname of DataHub + pub hostname: HostName, + + /// Port of DataHub. If TLS is used defaults to `443`, otherwise to `80`. + pub port: Option, + + /// Use a TLS connection. If not specified then no TLS will be used. + #[serde(flatten)] + pub tls: TlsClientDetails, + + /// Name of a Secret containing a DataHub Personal Access Token (PAT) that is authorized + /// to read resource metadata. + /// + /// Must contain the field `token`. + pub credentials_secret_name: SecretName, + + /// The DataHub environment (in DataHub terms: the fabric) the resources live in. + /// + /// This must match the `env` of the DataHub ingestion source that produced the metadata, + /// because `env` is part of the dataset URN (and of the container key that database and + /// schema URNs are hashed from). A mismatch is not reported as an error: the constructed URN + /// simply does not resolve and the resource-info-fetcher returns empty metadata. + /// + /// This is configured here rather than passed in by the Rego rule on purpose. `env` + /// describes how the catalog was populated, not the resource that is being authorized, and + /// the Rego rule has no way of knowing it. It is also DataHub-specific: no other metadata + /// catalog we know of has this concept. + /// + /// `env` is largely superseded by DataHub's platform instance, which the Rego rules already + /// pass as `instance` — within a single DataHub, a platform instance belongs to exactly one + /// fabric. Should a single resource-info-fetcher ever need to serve multiple fabrics, we + /// would add per-instance `envOverrides` here instead of moving `env` back into the API. + #[serde(default)] + pub env: FabricType, + } + + /// DataHub's `FabricType`: the environments (in DataHub terms: fabrics) a resource can live in. + /// + /// The variants serialize exactly the way DataHub spells them, so a value can be copied from an + /// ingestion recipe or the DataHub UI as-is. + /// + /// See the [`FabricType` definition] in DataHub's metadata model. + /// + /// [`FabricType` definition]: https://github.com/datahub-project/datahub/blob/master/li-utils/src/main/pegasus/com/linkedin/common/FabricType.pdl + #[derive( + Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display, + )] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] + pub enum FabricType { + /// Development fabrics. + Dev, + + /// Testing fabrics. + Test, + + /// Quality assurance fabrics. + Qa, + + /// User acceptance testing fabrics. + Uat, + + /// Early-integration fabrics. + Ei, + + /// Pre-production fabrics. + Pre, + + /// Staging fabrics. + Stg, + + /// Non-production fabrics. + NonProd, + + /// Production fabrics. DataHub's ingestion sources default to this fabric, so we do as well. + #[default] + Prod, + + /// Corporation fabrics. + Corp, + + /// Review fabrics. + Rvw, + + /// Alternative spelling of [`Self::Prod`], which DataHub accepts as well. + Prd, + + /// Alternative spelling of [`Self::Test`], which DataHub accepts as well. + Tst, + + /// System integration testing fabrics. + Sit, + + /// Alternative spelling of [`Self::Sandbox`], which DataHub accepts as well. + Sbx, + + /// Sandbox fabrics. + Sandbox, + + /// Certification fabrics. + Cert, + } +} diff --git a/rust/operator-binary/src/crd/user_info_fetcher/mod.rs b/rust/operator-binary/src/crd/user_info_fetcher/mod.rs index 085844a9..3de2d163 100644 --- a/rust/operator-binary/src/crd/user_info_fetcher/mod.rs +++ b/rust/operator-binary/src/crd/user_info_fetcher/mod.rs @@ -8,11 +8,12 @@ use stackable_operator::{ tls_verification::{CaCert, Tls, TlsClientDetails, TlsServerVerification, TlsVerification}, }, schemars::{self, JsonSchema}, - shared::time::Duration, v2::types::kubernetes::{SecretClassName, SecretName}, versioned::versioned, }; +use crate::crd::cache::Cache; + mod v1alpha1_impl; mod v1alpha2_impl; @@ -208,21 +209,6 @@ pub mod versioned { #[serde(default)] pub custom_attribute_mappings: BTreeMap, } - - #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] - #[serde(rename_all = "camelCase")] - pub struct Cache { - /// How long metadata about each user should be cached for. - #[serde(default = "default_entry_time_to_live")] - pub entry_time_to_live: Duration, - } -} - -/// Default time-to-live for cached user metadata. -pub(crate) const DEFAULT_CACHE_ENTRY_TIME_TO_LIVE: Duration = Duration::from_minutes_unchecked(1); - -const fn default_entry_time_to_live() -> Duration { - DEFAULT_CACHE_ENTRY_TIME_TO_LIVE } fn default_root_path() -> String { diff --git a/rust/operator-binary/src/crd/user_info_fetcher/v1alpha1_impl.rs b/rust/operator-binary/src/crd/user_info_fetcher/v1alpha1_impl.rs index 7ebb1e0a..b1b0d28d 100644 --- a/rust/operator-binary/src/crd/user_info_fetcher/v1alpha1_impl.rs +++ b/rust/operator-binary/src/crd/user_info_fetcher/v1alpha1_impl.rs @@ -1,6 +1,4 @@ -use stackable_operator::shared::time::Duration; - -use crate::crd::user_info_fetcher::{DEFAULT_CACHE_ENTRY_TIME_TO_LIVE, v1alpha1}; +use crate::crd::user_info_fetcher::v1alpha1; // TODO (@Techassi): Most of these impls are the exact same across v1alpha1 and v1alpha2. Explore // and design a more elegant solution for it. @@ -9,17 +7,3 @@ impl Default for v1alpha1::Backend { Self::None {} } } - -impl Default for v1alpha1::Cache { - fn default() -> Self { - Self { - entry_time_to_live: Self::default_entry_time_to_live(), - } - } -} - -impl v1alpha1::Cache { - pub const fn default_entry_time_to_live() -> Duration { - DEFAULT_CACHE_ENTRY_TIME_TO_LIVE - } -} diff --git a/rust/operator-binary/src/crd/user_info_fetcher/v1alpha2_impl.rs b/rust/operator-binary/src/crd/user_info_fetcher/v1alpha2_impl.rs index e7c2958c..1f2ad195 100644 --- a/rust/operator-binary/src/crd/user_info_fetcher/v1alpha2_impl.rs +++ b/rust/operator-binary/src/crd/user_info_fetcher/v1alpha2_impl.rs @@ -1,6 +1,6 @@ -use stackable_operator::{crd::authentication::ldap, shared::time::Duration}; +use stackable_operator::crd::authentication::ldap; -use crate::crd::user_info_fetcher::{DEFAULT_CACHE_ENTRY_TIME_TO_LIVE, v1alpha2}; +use crate::crd::user_info_fetcher::v1alpha2; // TODO (@Techassi): Most of these impls are the exact same across v1alpha1 and v1alpha2. Explore // and design a more elegant solution for it. @@ -10,20 +10,6 @@ impl Default for v1alpha2::Backend { } } -impl Default for v1alpha2::Cache { - fn default() -> Self { - Self { - entry_time_to_live: Self::default_entry_time_to_live(), - } - } -} - -impl v1alpha2::Cache { - pub const fn default_entry_time_to_live() -> Duration { - DEFAULT_CACHE_ENTRY_TIME_TO_LIVE - } -} - impl v1alpha2::OpenLdapBackend { /// Returns an LDAP [`AuthenticationProvider`](ldap::v1alpha1::AuthenticationProvider) for /// connecting to the OpenLDAP server. diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index d99c404b..7a95fe37 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -164,7 +164,8 @@ async fn main() -> anyhow::Result<()> { Arc::new(opa_controller::Ctx { client: client.clone(), opa_bundle_builder_image: operator_image.clone(), - user_info_fetcher_image: operator_image, + user_info_fetcher_image: operator_image.clone(), + resource_info_fetcher_image: operator_image, operator_environment, cluster_info: kubernetes_cluster_info, }), diff --git a/rust/operator-binary/src/opa_controller.rs b/rust/operator-binary/src/opa_controller.rs index 92138745..5fc41dd6 100644 --- a/rust/operator-binary/src/opa_controller.rs +++ b/rust/operator-binary/src/opa_controller.rs @@ -40,6 +40,7 @@ pub struct Ctx { pub client: stackable_operator::client::Client, pub opa_bundle_builder_image: String, pub user_info_fetcher_image: String, + pub resource_info_fetcher_image: String, pub cluster_info: KubernetesClusterInfo, pub operator_environment: OperatorEnvironmentOptions, } @@ -159,6 +160,7 @@ pub async fn reconcile_opa( &service_account_name, &ctx.opa_bundle_builder_image, &ctx.user_info_fetcher_image, + &ctx.resource_info_fetcher_image, &ctx.cluster_info, ) .context(BuildResourcesSnafu)?; diff --git a/rust/regorule-library/src/lib.rs b/rust/regorule-library/src/lib.rs index b032dade..d222e555 100644 --- a/rust/regorule-library/src/lib.rs +++ b/rust/regorule-library/src/lib.rs @@ -1,4 +1,10 @@ -pub const REGORULES: &[(&str, &str)] = &[( - "stackable/opa/userinfo/v1.rego", - include_str!("userinfo/v1.rego"), -)]; +pub const REGORULES: &[(&str, &str)] = &[ + ( + "stackable/opa/userinfo/v1.rego", + include_str!("userinfo/v1.rego"), + ), + ( + "stackable/opa/resourceinfo/v1.rego", + include_str!("resourceinfo/v1.rego"), + ), +]; diff --git a/rust/regorule-library/src/resourceinfo/v1.rego b/rust/regorule-library/src/resourceinfo/v1.rego new file mode 100644 index 00000000..c08afd42 --- /dev/null +++ b/rust/regorule-library/src/resourceinfo/v1.rego @@ -0,0 +1,58 @@ +package stackable.opa.resourceinfo.v1 + +# Database +databaseResourceInfo(system, instance, database) := resourceInfo( + "database", + {"system": system, "instance": instance, "database": database} +) + +# Schema +schemaResourceInfo(system, instance, database, schema) := resourceInfo( + "schema", + {"system": system, "instance": instance, "database": database, "schema": schema} +) + +# Table +tableResourceInfo(system, instance, database, schema, table) := resourceInfo( + "table", + { + "system": system, + "instance": instance, + "database": database, + "schema": schema, + "table": table + } +) + +# Stream +streamResourceInfo(system, instance, queue) := resourceInfo( + "stream", + {"system": system, "instance": instance, "queue": queue} +) + +# Dashboard +dashboardResourceInfo(system, instance, id) := resourceInfo( + "dashboard", + {"system": system, "instance": instance, "id": sprintf("%v", [id])} +) + +# Chart +chartResourceInfo(system, instance, id) := resourceInfo( + "chart", + {"system": system, "instance": instance, "id": sprintf("%v", [id])} +) + +# Raw identifier +rawIdentifierResourceInfo(identifier) := resourceInfo( + "rawIdentifier", + {"identifier": identifier} +) + +# Each resource type has its own `GET /metadata/` endpoint; the parameters are passed as a URL +# query string. `urlquery.encode_object` URL-encodes the values (e.g. the `:`, `(` and `,` in a raw +# DataHub URN). `id` is stringified first because the query encoder only accepts string values. +resourceInfo(endpoint, params) := http.send({ + "method": "GET", + "url": sprintf("http://127.0.0.1:9477/metadata/%s?%s", [endpoint, urlquery.encode_object(params)]), + "raise_error": true +}).body diff --git a/rust/resource-info-fetcher/Cargo.toml b/rust/resource-info-fetcher/Cargo.toml new file mode 100644 index 00000000..684bd835 --- /dev/null +++ b/rust/resource-info-fetcher/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "stackable-opa-resource-info-fetcher" +description = "Fetches resource metadata" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +publish = false + +[dependencies] +stackable-opa-operator = { path = "../operator-binary" } +info-fetcher-commons = { path = "../info-fetcher-commons" } +stackable-operator.workspace = true + +axum.workspace = true +clap.workspace = true +futures.workspace = true +hyper.workspace = true +md5.workspace = true +moka.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +snafu.workspace = true +tokio.workspace = true +tracing.workspace = true +url.workspace = true + +[build-dependencies] +built.workspace = true diff --git a/rust/resource-info-fetcher/build.rs b/rust/resource-info-fetcher/build.rs new file mode 100644 index 00000000..fa809bfd --- /dev/null +++ b/rust/resource-info-fetcher/build.rs @@ -0,0 +1,3 @@ +fn main() { + built::write_built_file().unwrap(); +} diff --git a/rust/resource-info-fetcher/src/api.rs b/rust/resource-info-fetcher/src/api.rs new file mode 100644 index 00000000..b270c2ed --- /dev/null +++ b/rust/resource-info-fetcher/src/api.rs @@ -0,0 +1,143 @@ +use hyper::StatusCode; +use info_fetcher_commons::http_error; +use serde::{Deserialize, Serialize}; +use snafu::Snafu; + +use crate::backend; + +pub trait ResourceInfoBackend { + type Response: Serialize; + + async fn get_resource_info( + &self, + request: &ResourceInfoRequest, + ) -> Result; +} + +/// The resource whose metadata was requested, in a backend-agnostic form. +/// +/// There is one HTTP endpoint per variant (e.g. `GET /metadata/trinoTable`). Each endpoint +/// deserializes its query parameters into the variant's payload struct and the backend then maps +/// the request to whatever the concrete backend needs (for DataHub: a URN, see [`urn_for_request`]). +/// +/// Each variant wraps its own parameter struct rather than inlining the fields, so a single struct +/// serves as both the HTTP query-parameter target ([`axum::extract::Query`]) and the enum payload — +/// there is no second copy of the field list to keep in sync. +/// +/// [`urn_for_request`]: crate::backend::data_hub::resource_to_urn_mapping::urn_for_request +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ResourceInfoRequest { + Database(Database), + Schema(Schema), + Table(Table), + Stream(Stream), + Dashboard(Dashboard), + Chart(Chart), + + /// Generic fallback to support arbitrary identifiers, e.g. URNs in the case of DataHub. + RawIdentifier(RawIdentifier), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Database { + pub system: String, + pub instance: String, + pub database: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Schema { + pub system: String, + pub instance: String, + pub database: String, + pub schema: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Table { + pub system: String, + pub instance: String, + pub database: String, + pub schema: String, + pub table: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Stream { + pub system: String, + pub instance: String, + + /// AKA topic + pub queue: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Dashboard { + pub system: String, + pub instance: String, + pub id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct Chart { + pub system: String, + pub instance: String, + pub id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +pub struct RawIdentifier { + pub identifier: String, +} + +/// Generates the trivial `From for ResourceInfoRequest` conversions, so each HTTP handler +/// can turn its deserialized query parameters into a [`ResourceInfoRequest`] via `.into()`. Adding a +/// resource type means adding its struct above and one entry here — no hand-written conversion. +macro_rules! impl_into_resource_info_request { + ($($variant:ident),+ $(,)?) => { + $( + impl From<$variant> for ResourceInfoRequest { + fn from(params: $variant) -> Self { + Self::$variant(params) + } + } + )+ + }; +} + +impl_into_resource_info_request!( + Database, + Schema, + Table, + Stream, + Dashboard, + Chart, + RawIdentifier +); + +#[derive(Snafu, Debug)] +pub enum GetResourceInfoError { + #[snafu(display("failed to serialize response as JSON"))] + SerializeResponseAsJson { source: serde_json::Error }, + + #[snafu( + context(false), + display("failed to get resource information from DataHub") + )] + DataHub { source: backend::data_hub::Error }, +} + +impl http_error::Error for GetResourceInfoError { + fn status_code(&self) -> StatusCode { + // todo: the warn here loses context about the scope in which the error occurred, eg: stackable_opa_resource_info_fetcher::backend::DATA_HUB + // Also, we should make the log level (warn vs error) more dynamic in the backend's impl `http_error::Error for Error` + tracing::warn!( + error = self as &dyn std::error::Error, + "Error while processing request" + ); + match self { + Self::SerializeResponseAsJson { .. } => StatusCode::INTERNAL_SERVER_ERROR, + Self::DataHub { source } => source.status_code(), + } + } +} diff --git a/rust/resource-info-fetcher/src/backend/data_hub/graphql.rs b/rust/resource-info-fetcher/src/backend/data_hub/graphql.rs new file mode 100644 index 00000000..95c23509 --- /dev/null +++ b/rust/resource-info-fetcher/src/backend/data_hub/graphql.rs @@ -0,0 +1,377 @@ +//! DataHub GraphQL query and the types used to (de)serialize it. +//! +//! A single `POST /api/graphql` fetches the entity together with its tags and owners, and DataHub +//! resolves the referenced tag/user/group and ownership-type entities server-side. The [`Entity`] +//! response is then flattened into the crate's public [`DataHubResourceInfoResponse`]. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use crate::backend::data_hub::{ + DataHubResourceInfoResponse, DataProduct, Domain, Group, Owners, Tag, Urn, User, +}; + +/// A single query covering every entity kind we build URNs for. We use the generic `entity(urn:)` +/// resolver plus per-type inline fragments, because a request can target a dataset (Trino table or +/// Kafka topic), a container (Trino catalog or schema), a chart or a dashboard. +const RESOURCE_INFO_QUERY: &str = r#" +query ResourceInfo($urn: String!) { + entity(urn: $urn) { + ...ResourceInfo + } +} +fragment ResourceInfo on Entity { + # DataHub has no direct "dataProduct" field on assets; membership is a graph edge that points from + # the data product to its assets. From the asset's side it is therefore an INCOMING relationship. + dataProducts: relationships(input: {types: ["DataProductContains"], direction: INCOMING, count: 10}) { + relationships { ...DataProduct } + } + ... on Dataset { tags { ...Tags } ownership { ...Owners } domain { ...Domain } } + ... on Container { tags { ...Tags } ownership { ...Owners } domain { ...Domain } } + ... on Chart { tags { ...Tags } ownership { ...Owners } domain { ...Domain } } + ... on Dashboard { tags { ...Tags } ownership { ...Owners } domain { ...Domain } } +} +fragment Tags on GlobalTags { + tags { tag { urn properties { name } } } +} +fragment Domain on DomainAssociation { + domain { urn properties { name description } } +} +fragment DataProduct on EntityRelationship { + entity { + urn + ... on DataProduct { properties { name description } } + } +} +fragment Owners on Ownership { + owners { + owner { + __typename + ... on CorpUser { urn properties { fullName displayName email active } } + ... on CorpGroup { urn properties { displayName description } } + } + ownershipType { urn info { name } } + type + } +} +"#; + +/// Builds the request body for the [`RESOURCE_INFO_QUERY`], parameterized by the entity's URN. +pub fn request(urn: &Urn) -> GraphQlRequest<'_> { + GraphQlRequest { + query: RESOURCE_INFO_QUERY, + variables: Variables { urn: &urn.0 }, + } +} + +#[derive(Debug, Serialize)] +pub struct GraphQlRequest<'a> { + query: &'static str, + variables: Variables<'a>, +} + +#[derive(Debug, Serialize)] +struct Variables<'a> { + urn: &'a str, +} + +/// A GraphQL server answers `200 OK` even when the query fails; the failures are reported in +/// `errors`. Callers must therefore inspect `errors` explicitly rather than relying on the HTTP +/// status code. +#[derive(Debug, Deserialize)] +pub struct GraphQlResponse { + pub data: Option, + + #[serde(default)] + pub errors: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct GraphQlError { + pub message: String, +} + +#[derive(Debug, Deserialize)] +pub struct ResponseData { + pub entity: Option, +} + +/// The resolved entity. `tags` and `ownership` come from the per-type inline fragments; GraphQL +/// merges them onto the entity object, so a single flat struct reads them regardless of the +/// concrete entity type. [`Default`] yields the "no metadata" entity used when DataHub does not +/// return an entity for a URN. +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Entity { + tags: Option, + ownership: Option, + domain: Option, + data_products: Option, +} + +#[derive(Debug, Deserialize)] +struct GlobalTags { + tags: Vec, +} + +#[derive(Debug, Deserialize)] +struct TagAssociation { + tag: TagNode, +} + +#[derive(Debug, Deserialize)] +struct TagNode { + urn: Urn, + properties: Option, +} + +#[derive(Debug, Deserialize)] +struct TagProperties { + name: String, +} + +#[derive(Debug, Deserialize)] +struct DomainAssociation { + domain: DomainNode, +} + +#[derive(Debug, Deserialize)] +struct DomainNode { + urn: Urn, + properties: Option, +} + +#[derive(Debug, Deserialize)] +struct DomainProperties { + name: String, + description: Option, +} + +/// The result of the `DataProductContains` relationship query. Each relationship's related entity is +/// a data product the resource belongs to. +#[derive(Debug, Deserialize)] +struct EntityRelationships { + relationships: Vec, +} + +#[derive(Debug, Deserialize)] +struct EntityRelationship { + entity: DataProductNode, +} + +#[derive(Debug, Deserialize)] +struct DataProductNode { + urn: Urn, + properties: Option, +} + +#[derive(Debug, Deserialize)] +struct DataProductProperties { + name: String, + description: Option, +} + +#[derive(Debug, Deserialize)] +struct Ownership { + owners: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Owner { + owner: OwnerEntity, + + /// The modern ownership type entity, e.g. `urn:li:ownershipType:__system__technical_owner`. + ownership_type: Option, + + /// The legacy ownership type enum, e.g. `TECHNICAL_OWNER`. Used as a fallback key for owners + /// that predate ownership type entities. + #[serde(rename = "type")] + legacy_type: Option, +} + +#[derive(Debug, Deserialize)] +struct OwnershipType { + urn: Urn, + info: Option, +} + +#[derive(Debug, Deserialize)] +struct OwnershipTypeInfo { + name: String, +} + +/// The resolved owner. DataHub only ever resolves owners to users or groups. +#[derive(Debug, Deserialize)] +#[serde(tag = "__typename")] +enum OwnerEntity { + CorpUser { + urn: Urn, + properties: Option, + }, + CorpGroup { + urn: Urn, + properties: Option, + }, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CorpUserProperties { + full_name: Option, + display_name: Option, + email: Option, + active: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CorpGroupProperties { + display_name: Option, + description: Option, +} + +impl Entity { + /// Flattens the GraphQL response into the crate's public [`DataHubResourceInfoResponse`]. + pub fn into_response(self, urn: Urn) -> DataHubResourceInfoResponse { + let tags = self + .tags + .into_iter() + .flat_map(|global_tags| global_tags.tags) + .map(|association| { + let TagNode { urn, properties } = association.tag; + // A tag without a properties aspect has no name; fall back to its URN. + let name = properties + .map(|properties| properties.name) + .unwrap_or_else(|| urn.0.clone()); + Tag { urn, name } + }) + .collect(); + + let domain = self.domain.map(|association| { + let DomainNode { urn, properties } = association.domain; + // A domain without a properties aspect has no name; fall back to its URN. + let (name, description) = match properties { + Some(properties) => (properties.name, properties.description), + None => (urn.0.clone(), None), + }; + Domain { + urn, + name, + description, + } + }); + + let data_products = self + .data_products + .into_iter() + .flat_map(|relationships| relationships.relationships) + .map(|relationship| { + let DataProductNode { urn, properties } = relationship.entity; + // A data product without a properties aspect has no name; fall back to its URN. + let (name, description) = match properties { + Some(properties) => (properties.name, properties.description), + None => (urn.0.clone(), None), + }; + DataProduct { + urn, + name, + description, + } + }) + .collect(); + + let mut owners: BTreeMap = BTreeMap::new(); + for owner in self + .ownership + .into_iter() + .flat_map(|ownership| ownership.owners) + { + let (type_urn, type_name) = owner_type(owner.ownership_type, owner.legacy_type); + let bucket = owners.entry(type_urn).or_default(); + if bucket.ownership_type_name.is_none() { + bucket.ownership_type_name = type_name; + } + match owner.owner { + OwnerEntity::CorpUser { urn, properties } => { + bucket.users.push(user(urn, properties)) + } + OwnerEntity::CorpGroup { urn, properties } => { + bucket.groups.push(group(urn, properties)) + } + } + } + + DataHubResourceInfoResponse { + urn, + tags, + domain, + data_products, + owners, + } + } +} + +/// Resolves the map key and human-readable name for an owner's ownership type, preferring the +/// modern ownership type entity and falling back to the legacy `type` enum. Unlike the previous +/// REST implementation, this handles arbitrary (including user-defined) ownership types instead of +/// assuming a fixed enum. +fn owner_type( + ownership_type: Option, + legacy_type: Option, +) -> (Urn, Option) { + match ownership_type { + Some(ownership_type) => ( + ownership_type.urn, + ownership_type.info.map(|info| info.name), + ), + // Fallback for owners where DataHub only populated the legacy `type` field. + None => ( + Urn(legacy_type.unwrap_or_else(|| "unknown".to_owned())), + None, + ), + } +} + +fn user(urn: Urn, properties: Option) -> User { + match properties { + Some(properties) => User { + full_name: properties.full_name, + display_name: properties + .display_name + .unwrap_or_else(|| strip_user_urn(&urn)), + email: properties.email, + active: properties.active.unwrap_or(true), + urn, + }, + // An owner reference without a corpUserInfo aspect: derive a display name from the URN. + None => User { + full_name: None, + display_name: strip_user_urn(&urn), + email: None, + active: true, + urn, + }, + } +} + +fn group(urn: Urn, properties: Option) -> Group { + let (display_name, description) = match properties { + Some(properties) => ( + properties.display_name.unwrap_or_else(|| urn.0.clone()), + properties.description, + ), + None => (urn.0.clone(), None), + }; + + Group { + urn, + display_name, + description, + } +} + +fn strip_user_urn(urn: &Urn) -> String { + urn.0.trim_start_matches("urn:li:corpuser:").to_owned() +} diff --git a/rust/resource-info-fetcher/src/backend/data_hub/mod.rs b/rust/resource-info-fetcher/src/backend/data_hub/mod.rs new file mode 100644 index 00000000..c9eec9c2 --- /dev/null +++ b/rust/resource-info-fetcher/src/backend/data_hub/mod.rs @@ -0,0 +1,274 @@ +use std::{ + collections::BTreeMap, + fmt::{self, Display}, + path::Path, +}; + +use hyper::StatusCode; +use info_fetcher_commons::{ + http_error, + utils::{self, http::send_json_request}, +}; +use reqwest::{ClientBuilder, Url}; +use serde::{Deserialize, Serialize}; +use snafu::{ResultExt, Snafu}; +use stackable_opa_operator::crd::resource_info_fetcher::v1alpha1; +use tracing::{debug, instrument, trace}; + +use crate::{ + api::{GetResourceInfoError, ResourceInfoBackend, ResourceInfoRequest}, + backend::data_hub::resource_to_urn_mapping::urn_for_request, +}; + +mod graphql; +mod resource_to_urn_mapping; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to read DataHub token from {path:?}"))] + ReadToken { + source: std::io::Error, + path: String, + }, + + #[snafu(display("failed to configure TLS"))] + ConfigureTls { source: utils::tls::Error }, + + #[snafu(display("failed to construct HTTP client"))] + ConstructHttpClient { source: reqwest::Error }, + + #[snafu(display("failed to build DataHub GraphQL endpoint {endpoint:?}"))] + BuildDataHubEndpoint { + source: url::ParseError, + endpoint: String, + }, + + #[snafu(display("failed to execute GraphQL query for URN {urn:?}"))] + ExecuteGraphQlQuery { + source: utils::http::Error, + urn: Urn, + }, + + #[snafu(display("DataHub returned GraphQL errors for URN {urn:?}: {messages}"))] + GraphQlErrors { messages: String, urn: Urn }, +} + +impl http_error::Error for Error { + fn status_code(&self) -> StatusCode { + match self { + Self::ReadToken { .. } => StatusCode::SERVICE_UNAVAILABLE, + Self::ConfigureTls { .. } => StatusCode::SERVICE_UNAVAILABLE, + Self::ConstructHttpClient { .. } => StatusCode::SERVICE_UNAVAILABLE, + Self::BuildDataHubEndpoint { .. } => StatusCode::BAD_REQUEST, + Self::ExecuteGraphQlQuery { .. } => StatusCode::INTERNAL_SERVER_ERROR, + Self::GraphQlErrors { .. } => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +/// A DataHub URN, e.g. `urn:li:corpuser:alice` or `urn:li:tag:pii`. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct Urn(pub String); + +impl Display for Urn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// The information we return for a single resource. Assembled from a single DataHub GraphQL query +/// that resolves the resource's tags and owners (with their user/group details) server-side. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataHubResourceInfoResponse { + urn: Urn, + tags: Vec, + + /// The domain the resource belongs to, e.g. `urn:li:domain:marketing`. A resource is assigned to + /// at most one domain, so this is an [`Option`] rather than a list. [`None`] if the resource is + /// not assigned to any domain. + domain: Option, + + /// The data products the resource is part of, e.g. `urn:li:dataProduct:orders`. Modelled as a + /// list because DataHub allows an asset to belong to more than one data product. + data_products: Vec, + + /// Owners grouped by their ownership type URN, e.g. + /// `urn:li:ownershipType:__system__technical_owner`. DataHub has no fixed set of ownership + /// types — users can define custom ones — so this is an open map, not an enum. + owners: BTreeMap, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct Owners { + /// Human-readable name of the ownership type, e.g. "Technical Owner". [`None`] if DataHub did + /// not resolve a name for the type. + ownership_type_name: Option, + users: Vec, + groups: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Tag { + urn: Urn, + name: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Domain { + urn: Urn, + name: String, + description: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataProduct { + urn: Urn, + name: String, + description: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct User { + urn: Urn, + full_name: Option, + display_name: String, + email: Option, + active: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Group { + urn: Urn, + display_name: String, + description: Option, +} + +/// This struct combines the CRD configuration with credentials loaded from the filesystem. +/// Credentials, the HTTP client and the GraphQL endpoint are initialized once at startup and stored +/// internally. +pub struct ResolvedDataHubBackend { + token: String, + http_client: reqwest::Client, + graphql_url: Url, + + /// The DataHub fabric the requested resources live in, see [`v1alpha1::DataHubBackend::env`]. + env: v1alpha1::FabricType, +} + +impl ResolvedDataHubBackend { + /// Resolves a DataHub backend by loading credentials from the filesystem. + #[instrument(skip_all)] + pub async fn resolve( + config: v1alpha1::DataHubBackend, + credentials_dir: &Path, + ) -> Result { + let token_path = credentials_dir.join("token"); + + // Trim trailing whitespace/newlines so the value is safe to use in an HTTP header. + let token = tokio::fs::read_to_string(&token_path) + .await + .with_context(|_| ReadTokenSnafu { + path: token_path.display().to_string(), + })? + .trim() + .to_owned(); + + let mut client_builder = ClientBuilder::new(); + client_builder = utils::tls::configure_reqwest(&config.tls, client_builder) + .await + .context(ConfigureTlsSnafu)?; + let http_client = client_builder.build().context(ConstructHttpClientSnafu)?; + + let schema = if config.tls.uses_tls() { + "https" + } else { + "http" + }; + let port = config + .port + .unwrap_or(if config.tls.uses_tls() { 443 } else { 80 }); + let graphql = format!( + "{schema}://{hostname}:{port}/api/graphql", + hostname = config.hostname + ); + let graphql_url = Url::parse(&graphql) + .with_context(|_| BuildDataHubEndpointSnafu { endpoint: graphql })?; + + Ok(Self { + token, + http_client, + graphql_url, + env: config.env, + }) + } + + /// Fetches a resource, its tags and its owners (including the owning users' and groups' details) + /// in a single DataHub GraphQL query. DataHub resolves the referenced tag/user/group entities + /// server-side, so there is no client-side fan-out. + #[instrument(skip(self))] + async fn query_entity(&self, urn: &Urn) -> Result { + trace!(%urn, %self.graphql_url, "Sending GraphQL query to DataHub"); + + let response: graphql::GraphQlResponse = send_json_request( + self.http_client + .post(self.graphql_url.clone()) + // Authenticate with a DataHub Personal Access Token (a bearer JWT). DataHub's + // Metadata Service Authentication verifies it and resolves it to the token's actor. + .bearer_auth(&self.token) + .json(&graphql::request(urn)), + ) + .await + .with_context(|_| ExecuteGraphQlQuerySnafu { urn: urn.clone() })?; + + // GraphQL reports genuine problems (unresolvable URNs, resolver failures) in `errors` while + // still returning `200 OK`. These are actual errors — not a missing resource — so we fail + // the request rather than returning incomplete data. + if !response.errors.is_empty() { + let messages = response + .errors + .iter() + .map(|error| error.message.as_str()) + .collect::>() + .join("; "); + return GraphQlErrorsSnafu { + messages, + urn: urn.clone(), + } + .fail(); + } + + // A resource that is unknown to DataHub is reported as a null entity (with no errors). It + // simply has no metadata, so we return an empty response rather than failing the request. + let Some(entity) = response.data.and_then(|data| data.entity) else { + debug!(%urn, "DataHub returned no entity; responding with empty resource info"); + return Ok(graphql::Entity::default()); + }; + + debug!(%urn, "Fetched entity from DataHub via GraphQL"); + trace!(?entity, "DataHub entity payload"); + + Ok(entity) + } +} + +impl ResourceInfoBackend for ResolvedDataHubBackend { + type Response = DataHubResourceInfoResponse; + + #[instrument(skip(self))] + async fn get_resource_info( + &self, + request: &ResourceInfoRequest, + ) -> Result { + let urn = urn_for_request(request, &self.env); + let entity = self.query_entity(&urn).await?; + + Ok(entity.into_response(urn)) + } +} diff --git a/rust/resource-info-fetcher/src/backend/data_hub/resource_to_urn_mapping.rs b/rust/resource-info-fetcher/src/backend/data_hub/resource_to_urn_mapping.rs new file mode 100644 index 00000000..f42d6a28 --- /dev/null +++ b/rust/resource-info-fetcher/src/backend/data_hub/resource_to_urn_mapping.rs @@ -0,0 +1,89 @@ +use std::collections::BTreeMap; + +use serde::Serialize; +use stackable_opa_operator::crd::resource_info_fetcher::v1alpha1; + +use crate::{ + api::{Chart, Dashboard, Database, RawIdentifier, ResourceInfoRequest, Schema, Stream, Table}, + backend::data_hub::Urn, +}; + +/// Maps a request to the URN of the DataHub entity that holds the resource's metadata. +/// +/// `env` is DataHub's fabric (e.g. `PROD`) and comes from the backend configuration rather than from +/// the request - see [`v1alpha1::DataHubBackend::env`] for why. It is part of every dataset URN, so +/// it has to match the `env` the metadata was ingested with, otherwise the URN does not resolve. +pub fn urn_for_request(request: &ResourceInfoRequest, env: &v1alpha1::FabricType) -> Urn { + let urn = match request { + ResourceInfoRequest::Database(Database { + system, + instance, + database, + }) => container_urn(&BTreeMap::from([ + ("platform", system), + ("instance", instance), + ("database", database), + ])), + ResourceInfoRequest::Schema(Schema { + system, + instance, + database, + schema, + }) => container_urn(&BTreeMap::from([ + ("platform", system), + ("instance", instance), + ("database", database), + ("schema", schema), + ])), + ResourceInfoRequest::Table(Table { + system, + instance, + database, + schema, + table, + }) => { + format!( + "urn:li:dataset:(urn:li:dataPlatform:{system},{instance}.{database}.{schema}.{table},{env})" + ) + } + ResourceInfoRequest::Stream(Stream { + system, + instance, + queue, + }) => format!("urn:li:dataset:(urn:li:dataPlatform:{system},{instance}.{queue},{env})"), + ResourceInfoRequest::Dashboard(Dashboard { + system, + instance, + id, + }) => { + format!("urn:li:dashboard:({system},{instance}.{id})") + } + ResourceInfoRequest::Chart(Chart { + system, + instance, + id, + }) => { + format!("urn:li:chart:({system},{instance}.{id})") + } + ResourceInfoRequest::RawIdentifier(RawIdentifier { identifier }) => identifier.clone(), + }; + + Urn(urn) +} + +/// Reproduces DataHub's `datahub_guid`: the container key is serialized to compact, key-sorted JSON +/// and MD5-hashed. A [`BTreeMap`] yields sorted keys and `serde_json` emits no whitespace, which +/// matches Python's `json.dumps(key, sort_keys=True, separators=(",", ":"))`. +/// +/// Note that the SQL ingestion source sets `backcompat_env_as_instance`, so the `env` configured for +/// the ingestion (e.g. `PROD`) ends up in the `instance` field and no `env` field is present in the +/// key. This is why the callers below build container keys without an `env`, even though the +/// configured [`v1alpha1::FabricType`] is part of the dataset URNs. The `platform` is the bare +/// platform name (e.g. `trino`), *not* the `urn:li:dataPlatform:` form. +fn container_urn( + container_key: &BTreeMap + Serialize, impl AsRef + Serialize>, +) -> String { + let key_json = serde_json::to_string(container_key) + .expect("serializing a BTreeMap<&str, &str> cannot fail"); + format!("urn:li:container:{:x}", md5::compute(key_json.as_bytes())) +} diff --git a/rust/resource-info-fetcher/src/backend/mod.rs b/rust/resource-info-fetcher/src/backend/mod.rs new file mode 100644 index 00000000..9fa83c04 --- /dev/null +++ b/rust/resource-info-fetcher/src/backend/mod.rs @@ -0,0 +1 @@ +pub mod data_hub; diff --git a/rust/resource-info-fetcher/src/main.rs b/rust/resource-info-fetcher/src/main.rs new file mode 100644 index 00000000..6907b2d2 --- /dev/null +++ b/rust/resource-info-fetcher/src/main.rs @@ -0,0 +1,251 @@ +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use axum::{ + Json, Router, + extract::{FromRequestParts, Query, State}, + http::request::Parts, + routing::get, +}; +use clap::Parser; +use futures::{FutureExt, future}; +use hyper::StatusCode; +use info_fetcher_commons::{ + config::{ConfigError, read_config_file}, + http_error, +}; +use moka::future::Cache; +use serde::de::DeserializeOwned; +use snafu::{ResultExt, Snafu}; +use stackable_opa_operator::crd::resource_info_fetcher::v1alpha1::{self}; +use stackable_operator::{cli::CommonOptions, telemetry::Tracing}; +use tokio::net::TcpListener; + +use crate::api::{GetResourceInfoError, ResourceInfoBackend, ResourceInfoRequest}; + +mod api; +mod backend; + +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +pub const APP_NAME: &str = "opa-resource-info-fetcher"; + +#[derive(clap::Parser)] +pub struct Args { + #[clap(flatten)] + common: CommonOptions, + + #[clap(long, env)] + config: PathBuf, + + #[clap(long, env)] + credentials_dir: PathBuf, +} + +#[derive(Clone)] +struct AppState { + backend: Arc, + // Note: Although we might no talk JSON to the underlying backend, we always return JSON as a + // result to the caller, so we can cache that. + resource_info_cache: Cache, +} + +/// Backend with resolved credentials. +/// +/// This enum wraps backend-specific implementations that have already loaded their credentials +/// and initialized their HTTP clients. +enum ResolvedBackend { + DataHub(backend::data_hub::ResolvedDataHubBackend), +} + +#[derive(Snafu, Debug)] +enum StartupError { + #[snafu(display("failed to initialize stackable-telemetry"))] + TracingInit { + source: stackable_operator::telemetry::tracing::Error, + }, + + #[snafu(display("failed to register SIGTERM handler"))] + RegisterSigterm { source: std::io::Error }, + + #[snafu(display("unable to parse config file from {path:?}"))] + ParseConfigFile { source: ConfigError, path: PathBuf }, + + #[snafu(display("failed to bind listener"))] + BindListener { source: std::io::Error }, + + #[snafu(display("failed to run server"))] + RunServer { source: std::io::Error }, + + #[snafu(display("failed to resolve DataHub backend"))] + ResolveDataHubBackend { source: backend::data_hub::Error }, +} + +/// Resolves a backend configuration by loading credentials and creating the appropriate backend implementation. +/// +/// This function reads credentials from the filesystem once at startup and returns a backend that +/// contains both the configuration and the resolved credentials. +async fn resolve_backend( + backend: v1alpha1::Backend, + credentials_dir: &Path, +) -> Result { + match backend { + v1alpha1::Backend::DataHub(config) => { + let resolved = + backend::data_hub::ResolvedDataHubBackend::resolve(config, credentials_dir) + .await + .context(ResolveDataHubBackendSnafu)?; + Ok(ResolvedBackend::DataHub(resolved)) + } + } +} + +#[tokio::main] +#[snafu::report] +async fn main() -> Result<(), StartupError> { + let args = Args::parse(); + + let _tracing_guard = Tracing::pre_configured(built_info::PKG_NAME, args.common.telemetry) + .init() + .context(TracingInitSnafu)?; + + tracing::info!( + built_info.pkg_version = built_info::PKG_VERSION, + built_info.git_version = built_info::GIT_VERSION, + built_info.target = built_info::TARGET, + built_info.built_time_utc = built_info::BUILT_TIME_UTC, + built_info.rustc_version = built_info::RUSTC_VERSION, + "Starting resource-info-fetcher", + ); + + let shutdown_requested = tokio::signal::ctrl_c().map(|_| ()); + #[cfg(unix)] + let shutdown_requested = { + let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .context(RegisterSigtermSnafu)?; + async move { + use futures::pin_mut; + + let sigterm = sigterm.recv().map(|_| ()); + pin_mut!(shutdown_requested, sigterm); + future::select(shutdown_requested, sigterm).await; + } + }; + + let config: v1alpha1::Config = read_config_file(&args.config) + .with_context(|_| ParseConfigFileSnafu { path: args.config })?; + let backend = Arc::new(resolve_backend(config.backend, &args.credentials_dir).await?); + let resource_info_cache = { + Cache::builder() + .name("resource-info") + .time_to_live(*config.cache.entry_time_to_live) + .build() + }; + // One GET endpoint per resource type. They all share the same generic `metadata` handler; only + // the query-parameter struct (and thus the resulting `ResourceInfoRequest` variant) differs. + let app = Router::new() + .route("/metadata/database", get(metadata::)) + .route("/metadata/schema", get(metadata::)) + .route("/metadata/table", get(metadata::)) + .route("/metadata/stream", get(metadata::)) + .route("/metadata/dashboard", get(metadata::)) + .route("/metadata/chart", get(metadata::)) + .route( + "/metadata/rawIdentifier", + get(metadata::), + ) + .with_state(AppState { + backend, + resource_info_cache, + }); + let listener = TcpListener::bind("127.0.0.1:9477") + .await + .context(BindListenerSnafu)?; + + axum::serve(listener, app.into_make_service()) + .with_graceful_shutdown(shutdown_requested) + .await + .context(RunServerSnafu)?; + Ok(()) +} + +/// The single handler backing every `GET /metadata/` route. It deserializes the request's +/// query parameters into the per-type params struct `P`, converts them into a [`ResourceInfoRequest`] +/// and delegates to [`get_resource_info`]. Adding a resource type is therefore just a new params +/// struct (in [`crate::api`]) plus one route line — there is no per-type handler body. +async fn metadata

( + State(state): State, + MetadataQuery(params): MetadataQuery

, +) -> Result, http_error::JsonResponse>> +where + P: DeserializeOwned + Into, +{ + get_resource_info(state, params.into()).await +} + +/// Like [`axum::extract::Query`], but renders deserialization failures (a missing or malformed query +/// parameter) through the same JSON error envelope as the rest of the API instead of axum's default +/// plain-text `400` response. +struct MetadataQuery

(P); + +impl FromRequestParts for MetadataQuery

+where + P: DeserializeOwned, + S: Send + Sync, +{ + type Rejection = http_error::JsonResponse; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let Query(params) = + Query::

::from_request_parts(parts, state) + .await + .map_err(|rejection| InvalidQueryParameters { + message: rejection.body_text(), + })?; + Ok(Self(params)) + } +} + +/// Returned when a request's query parameters cannot be deserialized into the endpoint's params +/// struct, e.g. a required parameter is missing or a numeric one is malformed. +#[derive(Snafu, Debug)] +#[snafu(display("invalid query parameters: {message}"))] +struct InvalidQueryParameters { + message: String, +} + +impl http_error::Error for InvalidQueryParameters { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } +} + +/// Shared request handling: serve the resource info from cache, or query the backend and cache the +/// resulting JSON. Independent of how the request was parsed, so all endpoints reuse it. +async fn get_resource_info( + state: AppState, + request: ResourceInfoRequest, +) -> Result, http_error::JsonResponse>> { + let AppState { + backend, + resource_info_cache, + } = state; + let resource_info = resource_info_cache + .try_get_with_by_ref(&request, async { + match backend.as_ref() { + ResolvedBackend::DataHub(data_hub) => { + let response = data_hub.get_resource_info(&request).await?; + serde_json::to_value(&response).map_err(|err| { + GetResourceInfoError::SerializeResponseAsJson { source: err } + }) + } + } + }) + .await?; + + Ok(Json(resource_info)) +} diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index a591ac29..361adfe7 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] stackable-opa-operator = { path = "../operator-binary" } +info-fetcher-commons = { path = "../info-fetcher-commons" } stackable-operator.workspace = true krb5.workspace = true diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 93fc487a..d339ba16 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -8,13 +8,14 @@ use std::{ use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use hyper::StatusCode; +use info_fetcher_commons::utils; use krb5::KrbContext; use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry, ldap_escape}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::commons::tls_verification::TlsClientDetails; use uuid::Uuid; -use crate::{ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest, http_error, utils}; +use crate::{ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest, http_error}; #[derive(Snafu, Debug)] pub enum Error { diff --git a/rust/user-info-fetcher/src/backend/entra.rs b/rust/user-info-fetcher/src/backend/entra.rs index bc02da04..3cc9dd76 100644 --- a/rust/user-info-fetcher/src/backend/entra.rs +++ b/rust/user-info-fetcher/src/backend/entra.rs @@ -1,31 +1,29 @@ use std::{collections::HashMap, path::Path}; use hyper::StatusCode; +use info_fetcher_commons::utils::{self, http::send_json_request}; use serde::Deserialize; use snafu::{ResultExt, Snafu}; use stackable_opa_operator::crd::user_info_fetcher::v1alpha2; use stackable_operator::commons::{networking::HostName, tls_verification::TlsClientDetails}; use url::Url; -use crate::{ - UserInfo, UserInfoRequest, http_error, - utils::{self, http::send_json_request}, -}; +use crate::{UserInfo, UserInfoRequest, http_error}; #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to get access_token"))] - AccessToken { source: crate::utils::http::Error }, + AccessToken { source: utils::http::Error }, #[snafu(display("failed to search for user with username {username:?}"))] SearchForUser { - source: crate::utils::http::Error, + source: utils::http::Error, username: String, }, #[snafu(display("failed to search for user with id {user_id:?}"))] UserNotFoundById { - source: crate::utils::http::Error, + source: utils::http::Error, user_id: String, }, @@ -33,13 +31,13 @@ pub enum Error { "failed to request groups for user with username {username:?} (user_id: {user_id:?})" ))] RequestUserGroups { - source: crate::utils::http::Error, + source: utils::http::Error, username: String, user_id: String, }, #[snafu(display("failed to to build entra endpoint for {endpoint}"))] - BuildEntraEndpointFailed { + BuildEntraEndpoint { source: url::ParseError, endpoint: String, }, @@ -70,7 +68,7 @@ impl http_error::Error for Error { Self::SearchForUser { .. } => StatusCode::BAD_GATEWAY, Self::UserNotFoundById { .. } => StatusCode::NOT_FOUND, Self::RequestUserGroups { .. } => StatusCode::BAD_GATEWAY, - Self::BuildEntraEndpointFailed { .. } => StatusCode::BAD_REQUEST, + Self::BuildEntraEndpoint { .. } => StatusCode::BAD_REQUEST, Self::ConstructHttpClient { .. } => StatusCode::SERVICE_UNAVAILABLE, Self::ConfigureTls { .. } => StatusCode::SERVICE_UNAVAILABLE, Self::ReadClientId { .. } => StatusCode::SERVICE_UNAVAILABLE, @@ -287,14 +285,13 @@ impl EntraBackend { let token_endpoint = format!("{schema}://{token_endpoint}:{port}/{tenant_id}/oauth2/v2.0/token"); - let token_endpoint_url = - Url::parse(&token_endpoint).context(BuildEntraEndpointFailedSnafu { - endpoint: token_endpoint, - })?; + let token_endpoint_url = Url::parse(&token_endpoint).context(BuildEntraEndpointSnafu { + endpoint: token_endpoint, + })?; let user_info_endpoint = format!("{schema}://{user_info_endpoint}:{port}"); let user_info_endpoint_url = - Url::parse(&user_info_endpoint).context(BuildEntraEndpointFailedSnafu { + Url::parse(&user_info_endpoint).context(BuildEntraEndpointSnafu { endpoint: user_info_endpoint, })?; @@ -333,7 +330,7 @@ impl EntraBackend { /// verbatim would ignore a configured endpoint and send the access token to whichever host /// the response names. Only the path and query are taken from the link itself. pub fn next_page(&self, next_link: &str) -> Result { - let next_link_url = Url::parse(next_link).context(BuildEntraEndpointFailedSnafu { + let next_link_url = Url::parse(next_link).context(BuildEntraEndpointSnafu { endpoint: next_link, })?; diff --git a/rust/user-info-fetcher/src/backend/keycloak.rs b/rust/user-info-fetcher/src/backend/keycloak.rs index 6af2a334..e26cf9b1 100644 --- a/rust/user-info-fetcher/src/backend/keycloak.rs +++ b/rust/user-info-fetcher/src/backend/keycloak.rs @@ -1,27 +1,25 @@ use std::{collections::HashMap, path::Path}; use hyper::StatusCode; +use info_fetcher_commons::utils::{self, http::send_json_request}; use serde::Deserialize; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_opa_operator::crd::user_info_fetcher::v1alpha2; use stackable_operator::crd::authentication::oidc; -use crate::{ - UserInfo, UserInfoRequest, http_error, - utils::{self, http::send_json_request}, -}; +use crate::{UserInfo, UserInfoRequest, http_error}; #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to get access_token"))] - AccessToken { source: crate::utils::http::Error }, + AccessToken { source: utils::http::Error }, #[snafu(display("failed to search for user"))] - SearchForUser { source: crate::utils::http::Error }, + SearchForUser { source: utils::http::Error }, #[snafu(display("unable to find user with id {user_id:?}"))] UserNotFoundById { - source: crate::utils::http::Error, + source: utils::http::Error, user_id: String, }, @@ -35,7 +33,7 @@ pub enum Error { "failed to request groups for user with username {username:?} (user_id: {user_id:?})" ))] RequestUserGroups { - source: crate::utils::http::Error, + source: utils::http::Error, username: String, user_id: String, }, diff --git a/rust/user-info-fetcher/src/backend/openldap.rs b/rust/user-info-fetcher/src/backend/openldap.rs index 07ed34f8..51386912 100644 --- a/rust/user-info-fetcher/src/backend/openldap.rs +++ b/rust/user-info-fetcher/src/backend/openldap.rs @@ -1,12 +1,13 @@ use std::collections::{BTreeMap, HashMap}; use hyper::StatusCode; +use info_fetcher_commons::utils; use ldap3::{LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry, ldap_escape}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_opa_operator::crd::user_info_fetcher::v1alpha2; use stackable_operator::crd::authentication::ldap; -use crate::{ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest, http_error, utils}; +use crate::{ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest, http_error}; #[derive(Snafu, Debug)] pub enum Error { diff --git a/rust/user-info-fetcher/src/backend/xfsc_aas.rs b/rust/user-info-fetcher/src/backend/xfsc_aas.rs index 9a5aa121..4fc62ec1 100644 --- a/rust/user-info-fetcher/src/backend/xfsc_aas.rs +++ b/rust/user-info-fetcher/src/backend/xfsc_aas.rs @@ -13,12 +13,13 @@ use std::collections::HashMap; use hyper::StatusCode; +use info_fetcher_commons::utils::{self, http::send_json_request}; use serde::Deserialize; use snafu::{ResultExt, Snafu}; use stackable_opa_operator::crd::user_info_fetcher::v1alpha2; use url::Url; -use crate::{UserInfo, UserInfoRequest, http_error, utils::http::send_json_request}; +use crate::{UserInfo, UserInfoRequest, http_error}; static API_PATH: &str = "/cip/claims"; static SUB_CLAIM: &str = "sub"; @@ -34,7 +35,7 @@ pub enum Error { }, #[snafu(display("request failed"))] - Request { source: crate::utils::http::Error }, + Request { source: utils::http::Error }, #[snafu(display("the XFSC AAS does not support querying by username, only by user ID"))] UserInfoByUsernameNotSupported {}, @@ -90,7 +91,7 @@ pub struct ResolvedXfscAasBackend { impl ResolvedXfscAasBackend { /// Resolves an XFSC AAS backend by initializing the HTTP client. pub fn resolve(config: v1alpha2::AasBackend) -> Result { - let http_client = crate::utils::http::client_builder() + let http_client = utils::http::client_builder() .build() .context(ConstructHttpClientSnafu)?; diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index 92179b11..3552773e 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -8,6 +8,10 @@ use std::{ use axum::{Json, Router, extract::State, routing::post}; use clap::Parser; use futures::{FutureExt, future, pin_mut}; +use info_fetcher_commons::{ + config::{ConfigError, read_config_file}, + http_error, +}; use moka::future::Cache; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; @@ -16,8 +20,6 @@ use stackable_operator::{cli::CommonOptions, telemetry::Tracing}; use tokio::net::TcpListener; mod backend; -mod http_error; -mod utils; pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); @@ -64,14 +66,8 @@ enum ResolvedBackend { #[derive(Snafu, Debug)] enum StartupError { - #[snafu(display("unable to read config file from {path:?}"))] - ReadConfigFile { - source: std::io::Error, - path: PathBuf, - }, - - #[snafu(display("failed to parse config file"))] - ParseConfig { source: serde_json::Error }, + #[snafu(display("unable to parse config file from {path:?}"))] + ParseConfigFile { source: ConfigError, path: PathBuf }, #[snafu(display("failed to register SIGTERM handler"))] RegisterSigterm { source: std::io::Error }, @@ -100,12 +96,6 @@ enum StartupError { ResolveXfscAasBackend { source: backend::xfsc_aas::Error }, } -async fn read_config_file(path: &Path) -> Result { - tokio::fs::read_to_string(path) - .await - .context(ReadConfigFileSnafu { path }) -} - /// Resolves a backend configuration by loading credentials and creating the appropriate backend implementation. /// /// This function reads credentials from the filesystem once at startup and returns a backend that @@ -184,16 +174,14 @@ async fn main() -> Result<(), StartupError> { } }; - let config: v1alpha2::Config = - serde_json::from_str(&read_config_file(&args.config).await?).context(ParseConfigSnafu)?; - + let config: v1alpha2::Config = read_config_file(&args.config) + .with_context(|_| ParseConfigFileSnafu { path: args.config })?; let backend = Arc::new(resolve_backend(config.backend, &args.credentials_dir).await?); let user_info_cache = { - let v1alpha2::Cache { entry_time_to_live } = config.cache; Cache::builder() .name("user-info") - .time_to_live(*entry_time_to_live) + .time_to_live(*config.cache.entry_time_to_live) .build() }; let app = Router::new() diff --git a/tests/release.yaml b/tests/release.yaml index 6df27b86..6b397bbb 100644 --- a/tests/release.yaml +++ b/tests/release.yaml @@ -14,3 +14,9 @@ releases: operatorVersion: 0.0.0-dev opa: operatorVersion: 0.0.0-dev + trino: + operatorVersion: 0.0.0-dev + kafka: + operatorVersion: 0.0.0-dev + superset: + operatorVersion: 0.0.0-dev diff --git a/tests/templates/kuttl/data-hub-resource-info/00-patch-ns.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/data-hub-resource-info/01-assert.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/01-assert.yaml.j2 new file mode 100644 index 00000000..50b1d4c3 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/01-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/data-hub-resource-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 new file mode 100644 index 00000000..2d6a0df5 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/data-hub-resource-info/02-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/02-assert.yaml new file mode 100644 index 00000000..19771a3c --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/02-assert.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# OpenSearch + Kafka can be slow to become ready on a cold cluster (image pulls, JVM start). +timeout: 900 +commands: + # Wait for the stateful prerequisites to be ready before installing DataHub itself. + # (Pod/StatefulSet names come from the `prerequisites` release; adjust if a chart bump + # renames them.) + - script: kubectl -n $NAMESPACE rollout status --timeout=590s statefulset/opensearch-cluster-master + - script: kubectl -n $NAMESPACE rollout status --timeout=590s statefulset/prerequisites-mysql + - script: kubectl -n $NAMESPACE rollout status --timeout=590s statefulset/prerequisites-kafka-controller diff --git a/tests/templates/kuttl/data-hub-resource-info/02-install-datahub-prerequisites.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/02-install-datahub-prerequisites.yaml.j2 new file mode 100644 index 00000000..b8a1edee --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/02-install-datahub-prerequisites.yaml.j2 @@ -0,0 +1,31 @@ +--- +# Secrets consumed by both the prerequisites chart (MySQL root password) and the +# DataHub chart (token service signing key + system client secret for metadata auth). +# These are the canonical secret names/keys from the datahub-helm quickstart. +apiVersion: v1 +kind: Secret +metadata: + name: mysql-secrets +stringData: + mysql-root-password: datahub +--- +apiVersion: v1 +kind: Secret +metadata: + name: datahub-auth-secrets +stringData: + # In a real deployment these would be strong random values. + token_service_signing_key: "WnEdIeTG/VVCLQqGwC/BAkqyY0k+H8NEAtWGejrBI94=" + system_client_secret: "changemeplease" +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # Release name is `prerequisites` (the datahub chart's defaults point at it); + # the chart itself is named `datahub-prerequisites`. + - script: >- + helm install prerequisites datahub-prerequisites + --namespace $NAMESPACE + --version {{ test_scenario['values']['data-hub-prerequisites'] }} + --repo https://helm.datahubproject.io + --values 02_datahub-prerequisites-values.yaml diff --git a/tests/templates/kuttl/data-hub-resource-info/02_datahub-prerequisites-values.yaml b/tests/templates/kuttl/data-hub-resource-info/02_datahub-prerequisites-values.yaml new file mode 100644 index 00000000..bb611430 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/02_datahub-prerequisites-values.yaml @@ -0,0 +1,64 @@ +# Values for the DataHub "prerequisites" Helm chart (chart 0.3.0). +# +# We keep the chart defaults where they are already minimal: +# - opensearch.enabled: true (Apache-2.0 licensed; used as both search AND graph store) +# - elasticsearch.enabled: false (avoided: Elastic/SSPL license is not Apache-2.0 compatible) +# - neo4j.enabled: false (graph is served by OpenSearch, saves a pod) +# - mysql.enabled: true +# - kafka.enabled: true (Bitnami Kafka in KRaft mode, so no separate ZooKeeper pod) +# - postgresql.enabled: false +# - cp-helm-charts.enabled: false +# +# This is a throwaway test backend: single replica everywhere, no persistence guarantees, +# no HA, small resources. +# +# HEADS UP (verify on first run): the `mysql` and `kafka` subcharts pull Bitnami images. +# Bitnami paywalled its free Docker Hub images in 2025, so the pinned tags may fail to pull. +# If they do, override the image repos to `docker.io/bitnamilegacy/*` here, e.g.: +# mysql: +# image: +# registry: docker.io +# repository: bitnamilegacy/mysql +# kafka: +# image: +# registry: docker.io +# repository: bitnamilegacy/kafka + +opensearch: + singleNode: true + replicas: 1 + # Single-node OpenSearch: no dedicated master election needed. + opensearchJavaOpts: "-Xms512m -Xmx512m" + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 1Gi + +mysql: + auth: + # Root password is read from the `mysql-secrets` Secret created in 02-install-datahub-prerequisites. + # This is the same Secret the datahub chart reads (its default global.sql.datasource.password). + existingSecret: mysql-secrets + database: datahub + primary: + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 512Mi + +kafka: + controller: + replicaCount: 1 + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: "1" + memory: 1Gi diff --git a/tests/templates/kuttl/data-hub-resource-info/03-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/03-assert.yaml new file mode 100644 index 00000000..ad6615ba --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/03-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# GMS waits for the system-update bootstrap job (schema + indices) before it goes ready, +# so give this plenty of headroom. +timeout: 900 +commands: + - script: kubectl -n $NAMESPACE rollout status --timeout=890s deployment/datahub-datahub-gms + - script: kubectl -n $NAMESPACE rollout status --timeout=890s deployment/datahub-datahub-frontend diff --git a/tests/templates/kuttl/data-hub-resource-info/03-install-datahub.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/03-install-datahub.yaml.j2 new file mode 100644 index 00000000..484d3bb6 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/03-install-datahub.yaml.j2 @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # The chart runs a `datahub-system-update` bootstrap job (creates the MySQL schema and + # the OpenSearch indices) before GMS starts; 03-assert waits for GMS to become ready. + - script: >- + helm install datahub datahub + --namespace $NAMESPACE + --version {{ test_scenario['values']['data-hub'] }} + --repo https://helm.datahubproject.io + --values 03_datahub-values.yaml diff --git a/tests/templates/kuttl/data-hub-resource-info/03_datahub-values.yaml b/tests/templates/kuttl/data-hub-resource-info/03_datahub-values.yaml new file mode 100644 index 00000000..56cd6d37 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/03_datahub-values.yaml @@ -0,0 +1,65 @@ +# Values for the DataHub Helm chart (chart 1.0.3, app v1.6.0). +# +# Installed with release name `datahub`, so the resulting Services are: +# - datahub-datahub-gms:8080 (GMS / metadata service, what the resource-info-fetcher talks to) +# - datahub-datahub-frontend:9002 (React frontend; needed here to mint a Personal Access Token) +# +# The chart's default connection settings already point at a `prerequisites`-named release +# (opensearch / mysql / kafka), so we only override what we need for a minimal, secured test. + +global: + # Use OpenSearch for the graph store too, so we don't need Neo4j. + graph_service_impl: elasticsearch + + datahub: + # THE point of this test: require authentication on every GMS request. + # With this on, GMS returns 401 for unauthenticated calls and only accepts a valid + # bearer token (a DataHub Personal Access Token, minted in 04-mint-pat). + metadata_service_authentication: + enabled: true + # Signing key + system client secret are read from `datahub-auth-secrets` + # (created in 02-install-datahub-prerequisites). These are the chart's default + # secretRef names/keys; kept explicit for clarity. + systemClientSecret: + secretRef: datahub-auth-secrets + secretKey: system_client_secret + tokenService: + signingKey: + secretRef: datahub-auth-secrets + secretKey: token_service_signing_key + +# --- Trim optional components to keep the test light --- +acryl-datahub-actions: + enabled: false +datahub-ingestion-cron: + enabled: false + +# --- Small resources for the core components --- +datahub-gms: + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 2Gi + extraEnvs: + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" + +datahub-frontend: + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + extraEnvs: + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" + +datahubSystemUpdate: + extraEnvs: + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" diff --git a/tests/templates/kuttl/data-hub-resource-info/04-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/04-assert.yaml new file mode 100644 index 00000000..d96f2315 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/04-assert.yaml @@ -0,0 +1,6 @@ +--- +# The credentials Secret for the resource-info-fetcher exists. +apiVersion: v1 +kind: Secret +metadata: + name: datahub-rif-credentials diff --git a/tests/templates/kuttl/data-hub-resource-info/04-mint-pat.yaml b/tests/templates/kuttl/data-hub-resource-info/04-mint-pat.yaml new file mode 100644 index 00000000..ef8974e4 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/04-mint-pat.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +timeout: 900 +commands: + # 1) Mint a DataHub Personal Access Token (PAT). + # GMS has metadata_service_authentication enabled, so we authenticate to the frontend + # with the default bootstrap user (datahub/datahub) to get a session, then call the + # createAccessToken GraphQL mutation. Run inside a throwaway pod because the frontend + # Service is only reachable in-cluster. + - script: | + set -euo pipefail + + # The frontend Deployment reports "available" before GMS finishes bootstrapping the default + # policies that authorize the root user to mint tokens, so early attempts come back empty. + # Retry until a token appears and emit it on a marker line for reliable capture. + kubectl -n "$NAMESPACE" run pat-minter --restart=Never \ + --image=oci.stackable.tech/sdp/testing-tools:0.3.0-stackable0.0.0-dev \ + --command -- bash -ceu ' + FE="http://datahub-datahub-frontend:9002" + for i in $(seq 1 60); do + curl -s -c /tmp/cookies.txt -X POST "$FE/logIn" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"datahub\",\"password\":\"datahub\"}" >/dev/null || true + resp="$(curl -s -b /tmp/cookies.txt -X POST "$FE/api/v2/graphql" \ + -H "Content-Type: application/json" \ + -d "{\"query\":\"mutation {createAccessToken(input:{type:PERSONAL,actorUrn:\\\"urn:li:corpuser:datahub\\\",duration:ONE_DAY,name:\\\"resource-info-fetcher-test\\\"}){accessToken}}\"}" || true)" + token="$(echo "$resp" | jq -r ".data.createAccessToken.accessToken // empty" 2>/dev/null || true)" + if [ -n "$token" ]; then echo "PAT_TOKEN:$token"; exit 0; fi + echo "attempt $i: DataHub not ready to mint a PAT yet: $resp" >&2 + sleep 5 + done + echo "gave up waiting for DataHub to mint a PAT" >&2 + exit 1 + ' + kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded pod/pat-minter --timeout=360s || true + TOKEN="$(kubectl -n "$NAMESPACE" logs pat-minter | sed -n 's/^PAT_TOKEN://p' | tail -n 1)" + if [ -z "$TOKEN" ]; then + echo "Failed to mint a DataHub PAT; pat-minter logs:" >&2 + kubectl -n "$NAMESPACE" logs pat-minter >&2 || true + kubectl -n "$NAMESPACE" delete pod pat-minter --ignore-not-found + exit 1 + fi + kubectl -n "$NAMESPACE" delete pod pat-minter --ignore-not-found + + # 2) Store the PAT as the Secret the resource-info-fetcher (OPA sidecar) will read. + # A PAT is a single bearer token -> single `token` key (NOT clientId/clientSecret). + # Later steps reuse this same Secret to authenticate against GMS. + kubectl -n "$NAMESPACE" create secret generic datahub-rif-credentials \ + --from-literal=token="$TOKEN" diff --git a/tests/templates/kuttl/data-hub-resource-info/10-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/10-assert.yaml new file mode 100644 index 00000000..f5f3cf1e --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/10-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: install-opa +timeout: 600 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s diff --git a/tests/templates/kuttl/data-hub-resource-info/10-install-opa.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/10-install-opa.yaml.j2 new file mode 100644 index 00000000..2ec4ec19 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/10-install-opa.yaml.j2 @@ -0,0 +1,71 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + # Quoted heredoc delimiter: the Rego below must reach kubectl verbatim, without the shell + # expanding backticks, $ or backslashes in it. + kubectl apply -n $NAMESPACE -f - <<'EOF' + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test + labels: + opa.stackable.tech/bundle: "true" + data: + test.rego: | + package test + + import data.stackable.opa.resourceinfo.v1 as resourceinfo + + # One wrapper per resource type of the shipped library, so test-regorule.py can address + # each of them as its own rule (data.test.) and pass the coordinates as input. + # Note that the DataHub fabric (`env`) is not a parameter: it is backend configuration + # (clusterConfig.resourceInfo.backend.dataHub.env) and applied by the fetcher. + database := resourceinfo.databaseResourceInfo(input.system, input.instance, input.database) + schema := resourceinfo.schemaResourceInfo(input.system, input.instance, input.database, input.schema) + table := resourceinfo.tableResourceInfo(input.system, input.instance, input.database, input.schema, input.table) + stream := resourceinfo.streamResourceInfo(input.system, input.instance, input.queue) + dashboard := resourceinfo.dashboardResourceInfo(input.system, input.instance, input.id) + chart := resourceinfo.chartResourceInfo(input.system, input.instance, input.id) + + rawIdentifier := resourceinfo.rawIdentifierResourceInfo(input.identifier) + EOF + + # Unquoted delimiter here, because the GMS hostname needs $NAMESPACE expanded. + kubectl apply -n $NAMESPACE -f - < 0 %} + custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['opa-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + resourceInfo: + backend: + dataHub: + hostname: datahub-datahub-gms.$NAMESPACE.svc.cluster.local + port: 8080 + credentialsSecretName: datahub-rif-credentials + env: PROD + cache: + entryTimeToLive: 60s +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + servers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: {} + EOF diff --git a/tests/templates/kuttl/data-hub-resource-info/20-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/20-assert.yaml new file mode 100644 index 00000000..1faffa0e --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/20-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 610 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available trinoclusters.trino.stackable.tech/my-trino --timeout=600s diff --git a/tests/templates/kuttl/data-hub-resource-info/20-install-trino.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/20-install-trino.yaml.j2 new file mode 100644 index 00000000..d7cb9483 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/20-install-trino.yaml.j2 @@ -0,0 +1,93 @@ +--- +apiVersion: authentication.stackable.tech/v1alpha1 +kind: AuthenticationClass +metadata: + name: trino-users +spec: + provider: + static: + userCredentialsSecret: + name: trino-users +--- +apiVersion: v1 +kind: Secret +metadata: + name: trino-users +type: kubernetes.io/opaque +stringData: + admin: adminadmin + # Used by the DataHub Trino ingestion (21-ingest-trino). + datahub: datahubdatahub + # Test users mirrored 1:1 from the DataHub corpusers created in 22-create-metadata (username == + # DataHub corpuser id), so a later end-to-end OPA-on-Trino authz test can authenticate as them + # and have the RIF resolve their DataHub ownership. Passwords follow the doubled-username + # convention above; they are throwaway test credentials. + alice.turner: alice.turneralice.turner + bob.ramirez: bob.ramirezbob.ramirez + carla.nowak: carla.nowakcarla.nowak + david.okoye: david.okoyedavid.okoye + erin.fischer: erin.fischererin.fischer +--- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCatalog +metadata: + name: tpch + labels: + trino: trino +spec: + connector: + tpch: {} +--- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCatalog +metadata: + name: tpcds + labels: + trino: trino +spec: + connector: + tpcds: {} +--- +# We need to create the TrinoCluster last, so that the ConfigMaps/Secrets it mounts are already +# existing to prevent unnecessary Pod restarts. +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCluster +metadata: + name: my-trino +spec: + image: +{% if test_scenario['values']['trino-latest'].find(",") > 0 %} + custom: "{{ test_scenario['values']['trino-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['trino-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['trino-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + catalogLabelSelector: + matchLabels: + trino: trino + authentication: + - authenticationClass: trino-users +# authorization: +# opa: +# configMapName: opa +# package: trino +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + coordinators: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 + workers: + config: + gracefulShutdownTimeout: 5s # Let the test run faster + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 diff --git a/tests/templates/kuttl/data-hub-resource-info/21-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/21-assert.yaml new file mode 100644 index 00000000..506cb26d --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/21-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 610 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available kafkaclusters.kafka.stackable.tech/test-kafka --timeout=600s + # The seed Job (kafka-seed) creates the test topics; wait for it so the ingestion (32) has data. + - script: kubectl -n $NAMESPACE wait --for=condition=complete job/kafka-seed --timeout=300s diff --git a/tests/templates/kuttl/data-hub-resource-info/21-install-kafka.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/21-install-kafka.yaml.j2 new file mode 100644 index 00000000..f6f0289e --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/21-install-kafka.yaml.j2 @@ -0,0 +1,120 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +timeout: 600 +--- +apiVersion: kafka.stackable.tech/v1alpha1 +kind: KafkaCluster +metadata: + name: test-kafka +spec: + image: +{% if test_scenario['values']['kafka-latest'].find(",") > 0 %} + custom: "{{ test_scenario['values']['kafka-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['kafka-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['kafka-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + controllers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 + brokers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 +--- +# Test data for the DataHub Kafka ingestion (32-ingest-kafka): a couple of topics, since a fresh +# KafkaCluster has none and the ingestion would otherwise scrape nothing. Runs as a Job with the +# Kafka image (for kafka-topics.sh). The broker listener is TLS-only (no auth), so a PKCS12 +# truststore from the `tls` SecretClass is mounted; fsGroup 1000 matches the image user so the +# store is readable. The script retries until the broker is up, so this can be applied alongside +# the KafkaCluster above. +apiVersion: batch/v1 +kind: Job +metadata: + name: kafka-seed +spec: + backoffLimit: 5 + template: + spec: + restartPolicy: OnFailure + securityContext: + fsGroup: 1000 + containers: + - name: seed +{% if test_scenario['values']['kafka-latest'].find(",") > 0 %} + image: "{{ test_scenario['values']['kafka-latest'].split(',')[1] }}" +{% else %} + image: "oci.stackable.tech/sdp/kafka:{{ test_scenario['values']['kafka-latest'] }}-stackable0.0.0-dev" +{% endif %} + command: + - /bin/bash + - -c + - | + set -euo pipefail + cat > /tmp/client.properties </dev/null 2>&1; then + ok=1; break + fi + echo "attempt $i: Kafka not ready yet, retrying in 5s"; sleep 5 + done + [ "$ok" = 1 ] || { echo "Kafka never became ready"; exit 1; } + for topic in orders page-views; do + "$BIN" --bootstrap-server "$BOOT" --command-config /tmp/client.properties \ + --create --if-not-exists --partitions 1 --replication-factor 1 --topic "$topic" + done + echo "--- topics ---" + "$BIN" --bootstrap-server "$BOOT" --command-config /tmp/client.properties --list + volumeMounts: + - name: tls + mountPath: /stackable/tls + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + # secret-operator ephemeral volume: PKCS12 truststore from the `tls` CA that signs the Kafka + # broker's server cert. No password annotation -> empty PKCS12 password. + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: tls + secrets.stackable.tech/format: tls-pkcs12 + secrets.stackable.tech/scope: pod + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem diff --git a/tests/templates/kuttl/data-hub-resource-info/22-install-superset-postgresql.yaml b/tests/templates/kuttl/data-hub-resource-info/22-install-superset-postgresql.yaml new file mode 100644 index 00000000..aa897ea3 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/22-install-superset-postgresql.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: >- + helm install postgresql-superset + --namespace $NAMESPACE + --version 12.5.6 + -f 22_postgresql-superset-values.yaml + --repo https://charts.bitnami.com/bitnami postgresql + --wait + timeout: 600 diff --git a/tests/templates/kuttl/data-hub-resource-info/22_postgresql-superset-values.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/22_postgresql-superset-values.yaml.j2 new file mode 100644 index 00000000..2e851682 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/22_postgresql-superset-values.yaml.j2 @@ -0,0 +1,44 @@ +--- +global: + security: + allowInsecureImages: true # needed starting with Chart version 16.3.0 if modifying images + +image: + repository: bitnamilegacy/postgresql + +volumePermissions: + enabled: false + image: + repository: bitnamilegacy/os-shell + securityContext: + runAsUser: auto + +metrics: + image: + repository: bitnamilegacy/postgres-exporter + +primary: + podSecurityContext: +{% if test_scenario['values']['openshift'] == 'true' %} + enabled: false +{% else %} + enabled: true +{% endif %} + containerSecurityContext: + enabled: false + resources: + requests: + memory: "128Mi" + cpu: "512m" + limits: + memory: "128Mi" + cpu: "1" + +shmVolume: + chmod: + enabled: false + +auth: + username: superset + password: superset + database: superset diff --git a/tests/templates/kuttl/data-hub-resource-info/23-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/23-assert.yaml new file mode 100644 index 00000000..1f8980d0 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/23-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 610 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available supersetclusters.superset.stackable.tech/my-superset --timeout=600s + # The seed Job (superset-seed) creates the test database/dataset/chart/dashboard; wait for it so + # the ingestion (33) has data. + - script: kubectl -n $NAMESPACE wait --for=condition=complete job/superset-seed --timeout=300s diff --git a/tests/templates/kuttl/data-hub-resource-info/23-install-superset.yaml.j2 b/tests/templates/kuttl/data-hub-resource-info/23-install-superset.yaml.j2 new file mode 100644 index 00000000..18464694 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/23-install-superset.yaml.j2 @@ -0,0 +1,213 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +timeout: 600 +--- +apiVersion: superset.stackable.tech/v1alpha1 +kind: SupersetCluster +metadata: + name: my-superset +spec: + image: +{% if test_scenario['values']['superset-latest'].find(",") > 0 %} + custom: "{{ test_scenario['values']['superset-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['superset-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['superset-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + credentialsSecret: superset-admin-credentials + metadataDatabase: + postgresql: + host: postgresql-superset + database: superset + credentialsSecretName: superset-postgresql-credentials +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + nodes: + envOverrides: + COMMON_VAR: role-value # overridden by role group below + ROLE_VAR: role-value # only defined here at role level + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-admin-credentials +type: Opaque +stringData: + adminUser.username: admin + adminUser.firstname: Superset + adminUser.lastname: Admin + adminUser.email: admin@superset.com + adminUser.password: admin +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-postgresql-credentials +stringData: + username: superset + password: superset +--- +# Test data for the DataHub Superset ingestion (33-ingest-superset): a database + dataset + chart + +# dashboard, created via the Superset REST API as the admin user above. A fresh Superset is empty, +# so the ingestion would otherwise scrape nothing. The script is shipped inline (wrapped in raw so +# Jinja leaves the Python braces alone) and run as a Job below. It is idempotent and retries until +# Superset is reachable, so it can be applied alongside the SupersetCluster above. +apiVersion: v1 +kind: ConfigMap +metadata: + name: superset-seed-script +data: + seed-superset.py: | +{% raw %} + #!/usr/bin/env python + """Seed Superset with a database + dataset + chart + dashboard so the DataHub superset + ingestion has something to scrape. Idempotent; waits until Superset is reachable.""" + import json + import sys + import time + import urllib.parse + + import requests + + BASE = "http://my-superset-node:8088" + SESSION = requests.Session() + + + def login(): + for attempt in range(60): + try: + resp = SESSION.post( + f"{BASE}/api/v1/security/login", + json={"username": "admin", "password": "admin", "provider": "db", "refresh": True}, + timeout=10, + ) + if resp.ok: + return resp.json()["access_token"] + except requests.RequestException: + pass + print(f"Superset not ready yet (attempt {attempt + 1}/60), retrying in 5s") + time.sleep(5) + sys.exit("Superset did not become ready in time") + + + def find(path, col, value): + """Return the id of an existing entity matching col == value, or None (for idempotency).""" + q = f"(filters:!((col:{col},opr:eq,value:'{value}')))" + resp = SESSION.get(f"{BASE}{path}?q={urllib.parse.quote(q)}") + resp.raise_for_status() + results = resp.json().get("result", []) + return results[0]["id"] if results else None + + + def create(path, body): + resp = SESSION.post(f"{BASE}{path}", json=body) + if not resp.ok: + sys.exit(f"POST {path} -> {resp.status_code}: {resp.text}") + return resp.json()["id"] + + + def main(): + token = login() + SESSION.headers.update({"Authorization": f"Bearer {token}"}) + csrf = SESSION.get(f"{BASE}/api/v1/security/csrf_token/").json()["result"] + SESSION.headers.update({"X-CSRFToken": csrf, "Referer": BASE}) + + # Database -> the Superset metadata Postgres itself (any reachable DB with tables works). + db_id = find("/api/v1/database/", "database_name", "rif-pg") or create( + "/api/v1/database/", + { + "database_name": "rif-pg", + "sqlalchemy_uri": "postgresql://superset:superset@postgresql-superset:5432/superset", + "expose_in_sqllab": True, + }, + ) + print("database id:", db_id) + + # Dataset on an existing table in that DB. + ds_id = find("/api/v1/dataset/", "table_name", "logs") or create( + "/api/v1/dataset/", + {"database": db_id, "schema": "public", "table_name": "logs"}, + ) + print("dataset id:", ds_id) + + # Chart on the dataset. + chart_id = find("/api/v1/chart/", "slice_name", "rif-test-chart") or create( + "/api/v1/chart/", + { + "slice_name": "rif-test-chart", + "datasource_id": ds_id, + "datasource_type": "table", + "viz_type": "table", + "params": json.dumps({"viz_type": "table", "datasource": f"{ds_id}__table"}), + }, + ) + print("chart id:", chart_id) + + # Dashboard with the chart placed on it. + dash_id = find("/api/v1/dashboard/", "dashboard_title", "rif-test-dashboard") + if not dash_id: + position = { + "DASHBOARD_VERSION_KEY": "v2", + "ROOT_ID": {"type": "ROOT", "id": "ROOT_ID", "children": ["GRID_ID"]}, + "GRID_ID": {"type": "GRID", "id": "GRID_ID", "children": ["CHART-1"], "parents": ["ROOT_ID"]}, + "CHART-1": { + "type": "CHART", + "id": "CHART-1", + "children": [], + "parents": ["ROOT_ID", "GRID_ID"], + "meta": {"chartId": chart_id, "width": 4, "height": 50, "sliceName": "rif-test-chart"}, + }, + } + dash_id = create( + "/api/v1/dashboard/", + { + "dashboard_title": "rif-test-dashboard", + "published": True, + "position_json": json.dumps(position), + }, + ) + print("dashboard id:", dash_id) + print("SEED OK") + + + if __name__ == "__main__": + main() +{% endraw %} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: superset-seed +spec: + backoffLimit: 5 + template: + spec: + restartPolicy: OnFailure + containers: + - name: seed + image: oci.stackable.tech/sdp/testing-tools:0.3.0-stackable0.0.0-dev + command: ["python", "/scripts/seed-superset.py"] + volumeMounts: + - name: script + mountPath: /scripts + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: "1" + memory: 256Mi + volumes: + - name: script + configMap: + name: superset-seed-script diff --git a/tests/templates/kuttl/data-hub-resource-info/31-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/31-assert.yaml new file mode 100644 index 00000000..de4876c8 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/31-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# Metadata-only ingestion of a single schema is quick, but the image is large and the first +# query can wait on a worker; give it headroom. Surface the logs regardless of outcome. +timeout: 600 +commands: + - script: | + if kubectl -n "$NAMESPACE" wait --for=condition=complete --timeout=590s job/datahub-ingest-trino; then + kubectl -n "$NAMESPACE" logs job/datahub-ingest-trino --tail=40 + else + echo "Trino ingestion Job did not complete:" + kubectl -n "$NAMESPACE" logs job/datahub-ingest-trino --tail=80 || true + exit 1 + fi diff --git a/tests/templates/kuttl/data-hub-resource-info/31-ingest-trino.yaml b/tests/templates/kuttl/data-hub-resource-info/31-ingest-trino.yaml new file mode 100644 index 00000000..e94ea2e2 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/31-ingest-trino.yaml @@ -0,0 +1,149 @@ +--- +# DataHub ingestion job: scrapes Trino catalog metadata into DataHub GMS so that the +# resource-info-fetcher (RIF) can resolve Trino catalogs/schemas/tables by URN. +# +# Scope is intentionally narrow to keep the run fast and deterministic: +# - `database: tpch` -> only the tpch catalog (the DataHub trino source takes a +# single catalog per run; tpcds is left out). +# - `schema_pattern.allow: sf1` -> only the sf1 schema (TPC-H exposes tiny/sf1/sf100/...; +# sf1 is the smallest real scale factor and has the 8 +# standard tables customer/orders/lineitem/...). +# +# `env: PROD` matters: the SQL ingestion source sets `backcompat_env_as_instance`, so PROD lands +# in the container key's `instance` field. This must match the OpaCluster's `resourceInfo` env +# (10-install-opa) and the URN derivation in resource_to_urn_mapping.rs, or RIF lookups miss. +apiVersion: v1 +kind: ConfigMap +metadata: + name: datahub-trino-recipe +data: + recipe.yaml: | + source: + type: trino + config: + # Must be an FQDN present in Trino's server-cert SAN (see TLS note on the Job below). + # ${TRINO_HOST} is expanded by DataHub from the ingest container's env. + host_port: '${TRINO_HOST}:8443' + database: tpch + username: datahub + password: datahubdatahub + env: PROD + platform_instance: '${POD_NAMESPACE}/my-trino' + schema_pattern: + allow: ["^sf1$"] + include_views: true + include_tables: true + profiling: + enabled: false + options: + connect_args: + http_scheme: https + sink: + type: datahub-rest + config: + server: 'http://datahub-datahub-gms:8080' + # Same DataHub PAT the RIF sidecar uses, minted in 04-mint-pat. + token: '${DATAHUB_GMS_TOKEN}' +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: datahub-ingest-trino +spec: + # Trino may briefly reject queries right after the cluster reports available (worker still + # registering); a couple of retries lets the ingestion self-heal. + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + initContainers: + # Trino serves HTTPS with a cert signed by the SDP `tls` SecretClass CA. DataHub's trino + # source (v1.6.0) silently ignores `verify: false`, and PYTHONHTTPSVERIFY=0 is overridden + # by the Trino Python client's session-level verify. The reliable fix is to trust the CA: + # extract it from the secret-operator PKCS12 truststore and point Python's HTTP libs at it + # via SSL_CERT_FILE / REQUESTS_CA_BUNDLE below. + - name: extract-ca + image: acryldata/datahub-ingestion:v1.6.0 + # secret-operator writes truststore.p12 as root:0 mode 0640, so reading it needs root. + # This container only reads the store and writes /trust/ca.crt. + securityContext: + runAsUser: 0 + command: + - /bin/bash + - -c + - | + set -euo pipefail + # autoTls issues PKCS12 stores with an EMPTY password and RC2-40-CBC encryption; + # OpenSSL 3.x needs `-legacy` to read RC2. `pass:` == empty password. + openssl pkcs12 -legacy \ + -in /stackable/tls/truststore.p12 \ + -passin pass: \ + -nokeys \ + -out /trust/ca.crt + chmod 0644 /trust/ca.crt + volumeMounts: + - name: tls + mountPath: /stackable/tls + - name: trust + mountPath: /trust + containers: + - name: ingest + image: acryldata/datahub-ingestion:v1.6.0 + command: ["datahub", "ingest", "-c", "/recipe/recipe.yaml"] + env: + # Build the coordinator FQDN (a cert SAN) without templating the recipe: the pod's + # namespace comes from the downward API, then $(POD_NAMESPACE) is interpolated here. + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: TRINO_HOST + value: "my-trino-coordinator.$(POD_NAMESPACE).svc.cluster.local" + # Make every Python HTTP request trust the extracted SDP CA. + - name: SSL_CERT_FILE + value: /trust/ca.crt + - name: REQUESTS_CA_BUNDLE + value: /trust/ca.crt + - name: DATAHUB_GMS_TOKEN + valueFrom: + secretKeyRef: + name: datahub-rif-credentials + key: token + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" + volumeMounts: + - name: recipe + mountPath: /recipe + - name: trust + mountPath: /trust + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + - name: recipe + configMap: + name: datahub-trino-recipe + - name: trust + emptyDir: {} + # secret-operator ephemeral volume: PKCS12 truststore from the same `tls` CA that signs + # Trino's server cert. No password annotation -> empty PKCS12 password (see extract-ca). + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: tls + secrets.stackable.tech/format: tls-pkcs12 + secrets.stackable.tech/scope: pod + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem diff --git a/tests/templates/kuttl/data-hub-resource-info/32-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/32-assert.yaml new file mode 100644 index 00000000..2ccc8af1 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/32-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# Metadata-only ingestion of the Kafka topics is quick, but the datahub-ingestion image is large; +# give the first pull headroom. Surface the logs regardless of outcome. +timeout: 600 +commands: + - script: | + if kubectl -n "$NAMESPACE" wait --for=condition=complete --timeout=590s job/datahub-ingest-kafka; then + kubectl -n "$NAMESPACE" logs job/datahub-ingest-kafka --tail=40 + else + echo "Kafka ingestion Job did not complete:" + kubectl -n "$NAMESPACE" logs job/datahub-ingest-kafka --tail=80 || true + exit 1 + fi diff --git a/tests/templates/kuttl/data-hub-resource-info/32-ingest-kafka.yaml b/tests/templates/kuttl/data-hub-resource-info/32-ingest-kafka.yaml new file mode 100644 index 00000000..ecccb88b --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/32-ingest-kafka.yaml @@ -0,0 +1,135 @@ +--- +# DataHub ingestion job: scrapes Kafka topic metadata into DataHub GMS so that the +# resource-info-fetcher (RIF) can resolve Kafka topics by URN. +# +# Unlike Trino, Kafka needs no auth here (the KafkaCluster has an empty `authentication` list), but +# the SDP default still serves the broker listener over TLS (server cert from the `tls` SecretClass, +# port 9093). So the same CA-trust dance as the Trino ingestion applies: extract the CA and hand it +# to librdkafka via `ssl.ca.location`. Hostname verification is disabled because the broker +# advertises a per-broker listener FQDN (test-kafka-broker-default-0-listener-broker...) that we +# don't want to pin here; trusting the CA is enough for this test. +# +# `env: PROD` matters: it lands in the topic dataset URN's `env` position and must match the +# OpaCluster's `resourceInfo` env and the URN derivation in resource_to_urn_mapping.rs, or RIF +# lookups miss. `platform_instance` (${POD_NAMESPACE}/test-kafka) is likewise embedded in the URN. +apiVersion: v1 +kind: ConfigMap +metadata: + name: datahub-kafka-recipe +data: + recipe.yaml: | + source: + type: kafka + config: + platform_instance: '${POD_NAMESPACE}/test-kafka' + env: PROD + connection: + # Bootstrap FQDN:port comes from the Kafka discovery ConfigMap (see the Job env below). + bootstrap: '${KAFKA_BOOTSTRAP}' + consumer_config: + security.protocol: SSL + ssl.ca.location: /trust/ca.crt + # The advertised broker listener FQDN is not worth pinning here; trusting the SDP CA + # (above) is sufficient for this test, so skip hostname verification. + ssl.endpoint.identification.algorithm: none + sink: + type: datahub-rest + config: + server: 'http://datahub-datahub-gms:8080' + # Same DataHub PAT the RIF sidecar uses, minted in 04-mint-pat. + token: '${DATAHUB_GMS_TOKEN}' +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: datahub-ingest-kafka +spec: + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + initContainers: + # Same pattern as the Trino ingestion: extract the `tls` SecretClass CA from the + # secret-operator PKCS12 truststore so librdkafka can trust the broker's server cert. + - name: extract-ca + image: acryldata/datahub-ingestion:v1.6.0 + # secret-operator writes truststore.p12 as root:0 mode 0640, so reading it needs root. + securityContext: + runAsUser: 0 + command: + - /bin/bash + - -c + - | + set -euo pipefail + # autoTls issues PKCS12 stores with an EMPTY password and RC2-40-CBC encryption; + # OpenSSL 3.x needs `-legacy` to read RC2. `pass:` == empty password. + openssl pkcs12 -legacy \ + -in /stackable/tls/truststore.p12 \ + -passin pass: \ + -nokeys \ + -out /trust/ca.crt + chmod 0644 /trust/ca.crt + volumeMounts: + - name: tls + mountPath: /stackable/tls + - name: trust + mountPath: /trust + containers: + - name: ingest + image: acryldata/datahub-ingestion:v1.6.0 + command: ["datahub", "ingest", "-c", "/recipe/recipe.yaml"] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # Kafka bootstrap address, read straight from the SDP discovery ConfigMap so the broker + # listener naming does not have to be reproduced here. + - name: KAFKA_BOOTSTRAP + valueFrom: + configMapKeyRef: + name: test-kafka + key: KAFKA + - name: DATAHUB_GMS_TOKEN + valueFrom: + secretKeyRef: + name: datahub-rif-credentials + key: token + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" + volumeMounts: + - name: recipe + mountPath: /recipe + - name: trust + mountPath: /trust + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + - name: recipe + configMap: + name: datahub-kafka-recipe + - name: trust + emptyDir: {} + # secret-operator ephemeral volume: PKCS12 truststore from the same `tls` CA that signs the + # Kafka broker's server cert. No password annotation -> empty PKCS12 password (see extract-ca). + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: tls + secrets.stackable.tech/format: tls-pkcs12 + secrets.stackable.tech/scope: pod + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem diff --git a/tests/templates/kuttl/data-hub-resource-info/33-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/33-assert.yaml new file mode 100644 index 00000000..d09fe490 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/33-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# The datahub-ingestion image is already cached by the earlier ingestions, and scraping Superset +# over plain HTTP is near-instant, so a shorter timeout is fine. Surface the logs either way. +timeout: 300 +commands: + - script: | + if kubectl -n "$NAMESPACE" wait --for=condition=complete --timeout=290s job/datahub-ingest-superset; then + kubectl -n "$NAMESPACE" logs job/datahub-ingest-superset --tail=40 + else + echo "Superset ingestion Job did not complete:" + kubectl -n "$NAMESPACE" logs job/datahub-ingest-superset --tail=80 || true + exit 1 + fi diff --git a/tests/templates/kuttl/data-hub-resource-info/33-ingest-superset.yaml b/tests/templates/kuttl/data-hub-resource-info/33-ingest-superset.yaml new file mode 100644 index 00000000..bb0bc99b --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/33-ingest-superset.yaml @@ -0,0 +1,73 @@ +--- +# DataHub ingestion job: scrapes Superset dashboards and charts into DataHub GMS so that the +# resource-info-fetcher (RIF) can resolve Superset dashboards/charts by URN. +# +# Simpler than the Trino/Kafka ingestions: Superset serves plain HTTP on my-superset-node:8088 (no +# TLS), so there is no CA to extract. Auth is the Superset admin user from 23-install-superset +# (admin/admin, database provider). +# +# `platform_instance` (${POD_NAMESPACE}/my-superset) is embedded in the chart/dashboard URNs and +# must match the OpaCluster's `resourceInfo` env and the URN derivation in +# resource_to_urn_mapping.rs, or RIF lookups miss. +apiVersion: v1 +kind: ConfigMap +metadata: + name: datahub-superset-recipe +data: + recipe.yaml: | + source: + type: superset + config: + connect_uri: 'http://my-superset-node:8088' + # Admin user created in 23-install-superset (superset-admin-credentials Secret). + username: admin + password: admin + provider: db + platform_instance: '${POD_NAMESPACE}/my-superset' + env: PROD + sink: + type: datahub-rest + config: + server: 'http://datahub-datahub-gms:8080' + # Same DataHub PAT the RIF sidecar uses, minted in 04-mint-pat. + token: '${DATAHUB_GMS_TOKEN}' +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: datahub-ingest-superset +spec: + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + containers: + - name: ingest + image: acryldata/datahub-ingestion:v1.6.0 + command: ["datahub", "ingest", "-c", "/recipe/recipe.yaml"] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: DATAHUB_GMS_TOKEN + valueFrom: + secretKeyRef: + name: datahub-rif-credentials + key: token + - name: DATAHUB_TELEMETRY_ENABLED + value: "false" + volumeMounts: + - name: recipe + mountPath: /recipe + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + - name: recipe + configMap: + name: datahub-superset-recipe diff --git a/tests/templates/kuttl/data-hub-resource-info/34-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/34-assert.yaml new file mode 100644 index 00000000..72ec8664 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/34-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# Includes a wait for data-product membership edges to index, so allow some headroom. +timeout: 360 +commands: + - script: | + if kubectl -n "$NAMESPACE" wait --for=condition=complete --timeout=350s job/datahub-create-metadata; then + kubectl -n "$NAMESPACE" logs job/datahub-create-metadata --tail=40 + else + echo "datahub-create-metadata Job did not complete:" + kubectl -n "$NAMESPACE" logs job/datahub-create-metadata --tail=80 || true + exit 1 + fi diff --git a/tests/templates/kuttl/data-hub-resource-info/34-create-metadata.yaml b/tests/templates/kuttl/data-hub-resource-info/34-create-metadata.yaml new file mode 100644 index 00000000..1d263ff7 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/34-create-metadata.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # Ship the metadata-creation script into the cluster as a ConfigMap (keeps + # create-datahub-metadata.py a real, lintable file instead of inlining Python into YAML). + # Idempotent so re-runs are safe. The Trino/Kafka/Superset entities it annotates already exist: + # kuttl only advances here once 31/32/33-assert confirmed the ingestion Jobs completed. + - script: | + kubectl -n "$NAMESPACE" create configmap datahub-metadata-script \ + --from-file=create-datahub-metadata.py \ + --dry-run=client -o yaml | kubectl -n "$NAMESPACE" apply -f - +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: datahub-create-metadata +spec: + backoffLimit: 2 + template: + spec: + restartPolicy: Never + containers: + - name: create-metadata + image: oci.stackable.tech/sdp/testing-tools:0.3.0-stackable0.0.0-dev + command: ["python", "/scripts/create-datahub-metadata.py"] + env: + # Every URN embeds the DataHub `instance` (${POD_NAMESPACE}/, set as + # platform_instance in the 31/32/33 ingestion recipes), so the script needs the + # namespace at runtime. + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # Same DataHub PAT the RIF sidecar uses, minted in 04-mint-pat. + - name: DATAHUB_GMS_TOKEN + valueFrom: + secretKeyRef: + name: datahub-rif-credentials + key: token + volumeMounts: + - name: script + mountPath: /scripts + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: "1" + memory: 256Mi + volumes: + - name: script + configMap: + name: datahub-metadata-script diff --git a/tests/templates/kuttl/data-hub-resource-info/40-assert.yaml b/tests/templates/kuttl/data-hub-resource-info/40-assert.yaml new file mode 100644 index 00000000..8c750c5f --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/40-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +# The Job's exit code is the verdict. Wait for it to complete and surface its logs either +# way (on success they show the fetcher response; on failure, the assertion error). +timeout: 310 +commands: + - script: | + if kubectl -n "$NAMESPACE" wait --for=condition=complete --timeout=300s job/test-resource-info; then + kubectl -n "$NAMESPACE" logs job/test-resource-info + else + echo "resource-info assertion Job did not complete:" + kubectl -n "$NAMESPACE" logs job/test-resource-info || true + exit 1 + fi diff --git a/tests/templates/kuttl/data-hub-resource-info/40-run-test.yaml b/tests/templates/kuttl/data-hub-resource-info/40-run-test.yaml new file mode 100644 index 00000000..feabc559 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/40-run-test.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # Ship the assertion script into the cluster as a ConfigMap (keeps test-regorule.py a + # real, lintable file instead of inlining Python into YAML). Idempotent so re-runs are safe. + - script: | + kubectl -n "$NAMESPACE" create configmap test-resource-info-script \ + --from-file=test-regorule.py \ + --dry-run=client -o yaml | kubectl -n "$NAMESPACE" apply -f - +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: test-resource-info +spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - name: test + image: oci.stackable.tech/sdp/testing-tools:0.3.0-stackable0.0.0-dev + command: + - python + - /scripts/test-regorule.py + - -u + - http://test-opa-server:8081/v1/data/test + env: + # The Trino URNs the test asserts on embed the namespace (instance = + # ${POD_NAMESPACE}/my-trino, see 31-ingest-trino), so the script derives them at runtime. + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: script + mountPath: /scripts + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: "1" + memory: 128Mi + volumes: + - name: script + configMap: + name: test-resource-info-script diff --git a/tests/templates/kuttl/data-hub-resource-info/create-datahub-metadata.py b/tests/templates/kuttl/data-hub-resource-info/create-datahub-metadata.py new file mode 100644 index 00000000..a8c5270e --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/create-datahub-metadata.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python +"""Populate DataHub with the users, groups, tags, domains, data products and assignments that the +resource-info-fetcher (RIF) test asserts on (see test-regorule.py). + +The metadata is attached to the entities ingested in 31/32/33: the Trino `tpch.sf1` +catalog/schema/tables, the Kafka topics and the Superset chart/dashboard. Writes go to the GMS +OpenAPI v3 entity endpoint as UPSERTs (createIfNotExists=false), so the script is re-runnable; +async=false makes a rejected write fail loudly. + +Every resource type gets a *distinct* set of tags/domain/owners on purpose: RIF answers with an +empty record when a URN resolves to nothing, so assertions on all-empty metadata would pass even +for a wrong URN. Distinguishable metadata is what makes the URN derivation in +resource_to_urn_mapping.rs testable. +""" + +import hashlib +import json +import os +import sys +import time + +import requests + +GMS = os.environ.get("GMS", "http://datahub-datahub-gms:8080") +SESSION = requests.Session() +SESSION.headers.update( + { + "Authorization": f"Bearer {os.environ['DATAHUB_GMS_TOKEN']}", + "Content-Type": "application/json", + } +) + +BUSINESS = "urn:li:ownershipType:__system__business_owner" +TECHNICAL = "urn:li:ownershipType:__system__technical_owner" + +# The ingestion recipes in 31/32/33 set a `platform_instance: ${POD_NAMESPACE}/`, so the +# DataHub `instance` is that value (not the `env`, which is PROD throughout) and it appears in the +# container GUIDs and as the entity-name prefix. The namespace is only known at runtime, so every +# URN is computed here rather than hardcoded. +NAMESPACE = os.environ["POD_NAMESPACE"] +TRINO = f"{NAMESPACE}/my-trino" +KAFKA = f"{NAMESPACE}/test-kafka" +SUPERSET = f"{NAMESPACE}/my-superset" + + +def group(gid): + return f"urn:li:corpGroup:{gid}" + + +def user(username): + return f"urn:li:corpuser:{username}" + + +def tag(tid): + return f"urn:li:tag:{tid}" + + +def domain(did): + return f"urn:li:domain:{did}" + + +def dataset(table): + return f"urn:li:dataset:(urn:li:dataPlatform:trino,{TRINO}.tpch.sf1.{table},PROD)" + + +def topic(name): + """A Kafka topic is a dataset too, just without the database/schema levels.""" + return f"urn:li:dataset:(urn:li:dataPlatform:kafka,{KAFKA}.{name},PROD)" + + +def container_urn(container_key): + """Reproduce DataHub's `datahub_guid` (mirrors `container_urn` in resource_to_urn_mapping.rs): + serialize the container key to compact, key-sorted JSON and MD5-hash it.""" + key_json = json.dumps(container_key, sort_keys=True, separators=(",", ":")) + return f"urn:li:container:{hashlib.md5(key_json.encode()).hexdigest()}" + + +CATALOG = container_urn({"platform": "trino", "instance": TRINO, "database": "tpch"}) +SCHEMA = container_urn( + {"platform": "trino", "instance": TRINO, "database": "tpch", "schema": "sf1"} +) + +# The Superset seed in 23-install-superset creates exactly one chart and one dashboard in a fresh +# Superset, so both get id 1. test-regorule.py asserts on the same ids. +CHART = f"urn:li:chart:(superset,{SUPERSET}.1)" +DASHBOARD = f"urn:li:dashboard:(superset,{SUPERSET}.1)" + + +def entity(urn, **aspects): + """An UPSERT object: `{urn, : {"value": }, ...}`.""" + return {"urn": urn, **{name: {"value": value} for name, value in aspects.items()}} + + +def ownership(owners): + """An `ownership` aspect value from a list of (owner urn, ownership type urn) pairs.""" + owner_types = {} + for owner_urn, type_urn in owners: + owner_types.setdefault(type_urn, []).append(owner_urn) + return { + "owners": [ + {"owner": o, "typeUrn": t, "type": "NONE", "source": {"type": "MANUAL"}} + for o, t in owners + ], + "ownerTypes": owner_types, + "lastModified": {"actor": user("datahub"), "time": 0}, + } + + +def upsert(entity_type, objects): + resp = SESSION.post( + f"{GMS}/openapi/v3/entity/{entity_type}", + params={"async": "false", "createIfNotExists": "false"}, + json=objects, + ) + if not resp.ok: + sys.exit(f"ERROR upserting {entity_type}: HTTP {resp.status_code}: {resp.text}") + + +def wait_for(what, check, attempts=60, delay=5): + for attempt in range(attempts): + if check(): + print(f" {what}: ready") + return + print( + f" {what}: not ready (attempt {attempt + 1}/{attempts}), retrying in {delay}s" + ) + time.sleep(delay) + sys.exit(f"{what}: not ready in time") + + +def gms_healthy(): + try: + return SESSION.get(f"{GMS}/health", timeout=10).ok + except requests.RequestException: + return False + + +def data_product_edge_indexed(asset_urn): + query = ( + '{ entity(urn: "%s") { relationships(input: {types: ["DataProductContains"], ' + "direction: INCOMING, count: 10}) { total } } }" % asset_urn + ) + resp = SESSION.post(f"{GMS}/api/graphql", json={"query": query}, timeout=20) + if not resp.ok: + return False + node = resp.json().get("data") or {} + for key in ("entity", "relationships"): + node = node.get(key) or {} + return bool(node.get("total")) + + +# --- The test fixture ---------------------------------------------------------------------------- + +# group id -> display name +GROUPS = { + "sales-analytics": "Sales Analytics", + "procurement": "Procurement", + "data-platform": "Data Platform", +} + +# username -> (full name, group id) +USERS = { + "alice.turner": ("Alice Turner", "sales-analytics"), + "bob.ramirez": ("Bob Ramirez", "sales-analytics"), + "carla.nowak": ("Carla Nowak", "procurement"), + "david.okoye": ("David Okoye", "procurement"), + "erin.fischer": ("Erin Fischer", "data-platform"), +} + +# tag id -> display name +TAGS = {"pii": "PII", "public": "Public"} + +# domain id -> (display name, description) +DOMAINS = { + "sales": ("Sales", "Sales and order data"), + "supply-chain": ("Supply Chain", "Suppliers, parts and procurement data"), +} + +# data product id -> (display name, description, domain id, [asset tables]) +DATA_PRODUCTS = { + "order-analytics": ( + "Order Analytics", + "Curated order analytics datasets", + "sales", + ["customer", "orders", "lineitem"], + ), + "supplier-360": ( + "Supplier 360", + "Supplier, part and procurement datasets", + "supply-chain", + ["supplier", "part", "partsupp"], + ), +} + +# table -> (tag ids, domain id or None, [(owner urn, ownership type urn)]) +TABLES = { + "customer": (["pii"], "sales", [(group("sales-analytics"), BUSINESS)]), + "orders": ([], "sales", [(group("sales-analytics"), BUSINESS)]), + "lineitem": ([], "sales", [(group("sales-analytics"), BUSINESS)]), + "supplier": (["pii"], "supply-chain", [(group("procurement"), BUSINESS)]), + "part": ([], "supply-chain", [(group("procurement"), BUSINESS)]), + "partsupp": ([], "supply-chain", [(group("procurement"), BUSINESS)]), + "nation": (["public"], None, [(group("data-platform"), TECHNICAL)]), + "region": (["public"], None, [(group("data-platform"), TECHNICAL)]), +} + +# Kafka topic -> same shape as TABLES. The topics come from the `kafka-seed` Job in +# 21-install-kafka; `page-views` is intentionally absent so the test has an ingested-but-unannotated +# resource to compare against. +TOPICS = { + "orders": (["pii"], "sales", [(group("sales-analytics"), BUSINESS)]), +} + +# Superset chart/dashboard, again the same shape. The dashboard mixes a group and a user owner (like +# the Trino schema does), the chart carries no tags and no domain - each resource type ends up with +# a combination no other one has, so a wrong URN cannot accidentally satisfy the assertions. +DASHBOARD_METADATA = ( + ["public"], + "supply-chain", + [(group("procurement"), BUSINESS), (user("carla.nowak"), BUSINESS)], +) +CHART_METADATA = ([], None, [(group("data-platform"), TECHNICAL)]) + +# Container ownership is technical; the schema deliberately mixes a group and a user owner. +CATALOG_OWNERS = [(group("data-platform"), TECHNICAL)] +SCHEMA_OWNERS = CATALOG_OWNERS + [(user("erin.fischer"), TECHNICAL)] + +# `admins`/`members`/`groups` are required by corpGroupInfo, membership is set on the user instead. +EMPTY_MEMBERSHIP = {"admins": [], "members": [], "groups": []} + + +def group_object(gid, name): + info = {"displayName": name, "description": f"{name} team", **EMPTY_MEMBERSHIP} + return entity(group(gid), corpGroupInfo=info) + + +def user_object(username, name, gid): + email = f"{username}@example.com" + info = {"active": True, "displayName": name, "fullName": name, "email": email} + return entity( + user(username), + corpUserInfo=info, + corpUserEditableInfo={"email": email}, + groupMembership={"groups": [group(gid)]}, + ) + + +def data_product_object(pid, name, desc, domain_id, tables): + assets = [{"destinationUrn": dataset(table)} for table in tables] + return entity( + f"urn:li:dataProduct:{pid}", + dataProductProperties={"name": name, "description": desc, "assets": assets}, + domains={"domains": [domain(domain_id)]}, + ) + + +def asset_object(urn, tags, domain_id, owners): + """The aspects RIF reads off an asset (dataset, chart or dashboard). Tags and domain are only + sent when set, so an asset can deliberately have none.""" + aspects = {"ownership": ownership(owners)} + if tags: + aspects["globalTags"] = {"tags": [{"tag": tag(tid)} for tid in tags]} + if domain_id: + aspects["domains"] = {"domains": [domain(domain_id)]} + return entity(urn, **aspects) + + +def main(): + print("==> Waiting for DataHub GMS to be healthy") + wait_for("GMS", gms_healthy) + + print("==> Groups, users, tags and domains") + upsert("corpgroup", [group_object(gid, name) for gid, name in GROUPS.items()]) + upsert("corpuser", [user_object(name, *info) for name, info in USERS.items()]) + upsert( + "tag", [entity(tag(tid), tagProperties={"name": n}) for tid, n in TAGS.items()] + ) + upsert( + "domain", + [ + entity(domain(did), domainProperties={"name": name, "description": desc}) + for did, (name, desc) in DOMAINS.items() + ], + ) + + # The data product `assets` are what creates the DataProductContains edges the RIF reads. + print("==> Data products") + upsert( + "dataproduct", + [data_product_object(pid, *info) for pid, info in DATA_PRODUCTS.items()], + ) + + print( + "==> Tags / domains / ownership of tables, topics, chart, dashboard, containers" + ) + # Trino tables and Kafka topics are both `dataset` entities, so they go in one call. + upsert( + "dataset", + [asset_object(dataset(table), *metadata) for table, metadata in TABLES.items()] + + [asset_object(topic(name), *metadata) for name, metadata in TOPICS.items()], + ) + upsert("chart", [asset_object(CHART, *CHART_METADATA)]) + upsert("dashboard", [asset_object(DASHBOARD, *DASHBOARD_METADATA)]) + upsert( + "container", + [ + entity(CATALOG, ownership=ownership(CATALOG_OWNERS)), + entity(SCHEMA, ownership=ownership(SCHEMA_OWNERS)), + ], + ) + + # DataProductContains edges are graph-indexed asynchronously (unlike the aspects above), so wait + # until at least one asset per data product resolves the incoming edge. This makes the metadata + # state deterministic for the RIF assertions in the next step. + print("==> Waiting for data-product membership edges to index") + for pid, (_, _, _, tables) in DATA_PRODUCTS.items(): + probe = dataset(tables[0]) + wait_for(f"{pid} edge", lambda: data_product_edge_indexed(probe), attempts=24) + + print( + "==> Successfully created all DataHub users, groups, tags, domains, data products" + ) + print(" and attached them to the Trino, Kafka and Superset resources") + + +if __name__ == "__main__": + main() diff --git a/tests/templates/kuttl/data-hub-resource-info/test-regorule.py b/tests/templates/kuttl/data-hub-resource-info/test-regorule.py new file mode 100644 index 00000000..64a16f83 --- /dev/null +++ b/tests/templates/kuttl/data-hub-resource-info/test-regorule.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +"""Assert the resource-info-fetcher (RIF) answers for every resource type of the rego library. + +Each case resolves a Stackable abstraction (system + coordinates -> URN, see the bundle in +10-install-opa) and compares the full RIF record. The metadata is created in 34-create-metadata and +differs per resource, which is what makes the URN derivation in resource_to_urn_mapping.rs testable: +RIF answers with an empty record for a URN that resolves to nothing, so a wrong derivation shows up +as missing metadata rather than as an error. +""" + +import argparse +import hashlib +import json +import os + +import requests + +BUSINESS = "urn:li:ownershipType:__system__business_owner" +TECHNICAL = "urn:li:ownershipType:__system__technical_owner" + +# The ingestion recipes in 31/32/33 set the DataHub `instance` (platform instance) to +# `${POD_NAMESPACE}/`, so the instances - and every URN derived from them - are only known +# at runtime. +NAMESPACE = os.environ["POD_NAMESPACE"] +TRINO = f"{NAMESPACE}/my-trino" +KAFKA = f"{NAMESPACE}/test-kafka" +SUPERSET = f"{NAMESPACE}/my-superset" + +TRINO_DB = {"system": "trino", "instance": TRINO, "database": "tpch"} +TRINO_SCHEMA = {**TRINO_DB, "schema": "sf1"} +# The Superset seed in 23-install-superset creates exactly one chart and one dashboard in a fresh +# Superset, so both have id 1. +SUPERSET_ITEM = {"system": "superset", "instance": SUPERSET, "id": 1} + + +def container_urn(opa_input): + """Reproduce DataHub's `datahub_guid` for a database/schema (mirrors `container_urn` in + resource_to_urn_mapping.rs): the container key is the rule input with `system` renamed to + `platform`, serialized to compact, key-sorted JSON and MD5-hashed.""" + key = {("platform" if k == "system" else k): v for k, v in opa_input.items()} + key_json = json.dumps(key, sort_keys=True, separators=(",", ":")) + return f"urn:li:container:{hashlib.md5(key_json.encode()).hexdigest()}" + + +def dataset_urn(platform, instance, name): + return f"urn:li:dataset:(urn:li:dataPlatform:{platform},{instance}.{name},PROD)" + + +def owned_by(type_urn, users=(), groups=()): + return {type_urn: {"users": sorted(users), "groups": sorted(groups)}} + + +def case(rule, opa_input, urn, tags=(), domain=None, data_products=(), owners=None): + """A (rule, input, expected record) triple, expected in the shape `normalize` produces.""" + return ( + rule, + opa_input, + { + "urn": urn, + "tags": sorted(tags), + "domain": domain, + "dataProducts": sorted(data_products), + "owners": owners or {}, + }, + ) + + +def trino_table(table, **expected): + return case( + "table", + {**TRINO_SCHEMA, "table": table}, + dataset_urn("trino", TRINO, f"tpch.sf1.{table}"), + **expected, + ) + + +def kafka_topic(queue, **expected): + """A Kafka topic is a dataset too, just without the database/schema levels.""" + return case( + "stream", + {"system": "kafka", "instance": KAFKA, "queue": queue}, + dataset_urn("kafka", KAFKA, queue), + **expected, + ) + + +def normalize(record): + """Reduce a RIF record to the URNs the cases compare against.""" + return { + "urn": record["urn"], + "tags": sorted(tag["urn"] for tag in record["tags"]), + # `domain` is optional (at most one per resource); None when unassigned. + "domain": record["domain"]["urn"] if record["domain"] else None, + "dataProducts": sorted(product["urn"] for product in record["dataProducts"]), + "owners": { + type_urn: { + "users": sorted(user["urn"] for user in bucket["users"]), + "groups": sorted(group["urn"] for group in bucket["groups"]), + } + for type_urn, bucket in record["owners"].items() + }, + } + + +PLATFORM_OWNED = owned_by(TECHNICAL, groups=["urn:li:corpGroup:data-platform"]) + +CASES = [ + case("database", TRINO_DB, container_urn(TRINO_DB), owners=PLATFORM_OWNED), + case( + "schema", + TRINO_SCHEMA, + container_urn(TRINO_SCHEMA), + # The schema deliberately mixes a group and a user owner (both technical). + owners=owned_by( + TECHNICAL, + users=["urn:li:corpuser:erin.fischer"], + groups=["urn:li:corpGroup:data-platform"], + ), + ), + trino_table( + "customer", + tags=["urn:li:tag:pii"], + domain="urn:li:domain:sales", + data_products=["urn:li:dataProduct:order-analytics"], + owners=owned_by(BUSINESS, groups=["urn:li:corpGroup:sales-analytics"]), + ), + trino_table( + "supplier", + tags=["urn:li:tag:pii"], + domain="urn:li:domain:supply-chain", + data_products=["urn:li:dataProduct:supplier-360"], + owners=owned_by(BUSINESS, groups=["urn:li:corpGroup:procurement"]), + ), + # A reference table: tagged public, no domain, in no data product. + trino_table("nation", tags=["urn:li:tag:public"], owners=PLATFORM_OWNED), + kafka_topic( + "orders", + tags=["urn:li:tag:pii"], + domain="urn:li:domain:sales", + owners=owned_by(BUSINESS, groups=["urn:li:corpGroup:sales-analytics"]), + ), + # Ingested but deliberately left unannotated in 34-create-metadata: a resource DataHub knows + # about answers with an empty record, it is not an error. + kafka_topic("page-views"), + case( + "dashboard", + SUPERSET_ITEM, + f"urn:li:dashboard:(superset,{SUPERSET}.1)", + tags=["urn:li:tag:public"], + domain="urn:li:domain:supply-chain", + owners=owned_by( + BUSINESS, + users=["urn:li:corpuser:carla.nowak"], + groups=["urn:li:corpGroup:procurement"], + ), + ), + case( + "chart", + SUPERSET_ITEM, + f"urn:li:chart:(superset,{SUPERSET}.1)", + owners=PLATFORM_OWNED, + ), +] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-u", + "--url", + required=True, + help="OPA data API base URL for the 'test' package", + ) + url = parser.parse_args().url + + def query(rule, opa_input): + # strict-builtin-errors turns a failing resource-info-fetcher call (e.g. rejected GMS + # authentication) into a 500 rather than a silently undefined result. + response = requests.post( + f"{url}/{rule}", + params={"strict-builtin-errors": "true"}, + json={"input": opa_input}, + ) + assert response.status_code == 200, ( + f"{rule}: expected 200 from OPA, got {response.status_code}: {response.text}" + ) + body = response.json() + # A missing 'result' means the rule was undefined - typically an auth/connection problem. + assert "result" in body, f"{rule}: rule did not evaluate: {body}" + return body["result"] + + for rule, opa_input, expected in CASES: + print(f"Checking {rule}: {opa_input}") + record = query(rule, opa_input) + assert normalize(record) == expected, ( + f"{rule} {opa_input}: got {normalize(record)}, expected {expected}" + ) + + # The raw-identifier lookup bypasses the URN derivation and asks for the very URN the + # abstraction resolved to, so both must return the identical record. + raw = query("rawIdentifier", {"identifier": expected["urn"]}) + assert raw == record, ( + f"{expected['urn']}: rawIdentifier returned {raw}, but {rule} returned {record}" + ) + + print("Test successful!") + + +if __name__ == "__main__": + main() diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 79a48411..a25b1a32 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -11,9 +11,30 @@ dimensions: # To use a custom image, add a comma and the full name after the product version # 0.67.1,oci.stackable.tech/sdp/opa:0.67.1-stackable0.0.0-dev - 1.16.2 + - name: trino-latest + values: + # To use a custom image, add a comma and the full name after the product version + # 481,oci.stackable.tech/sdp/trino:481-stackable0.0.0-dev + - "481" + - name: kafka-latest + values: + # To use a custom image, add a comma and the full name after the product version + # 4.2.1,oci.stackable.tech/sdp/kafka:4.2.1-stackable0.0.0-dev + - "4.2.1" + - name: superset-latest + values: + # To use a custom image, add a comma and the full name after the product version + # 6.1.0,oci.stackable.tech/sdp/superset:6.1.0-stackable0.0.0-dev + - "6.1.0" - name: keycloak values: - 23.0.1 + - name: data-hub + values: + - 1.0.3 + - name: data-hub-prerequisites + values: + - 0.3.0 - name: openshift values: - "false" @@ -62,6 +83,15 @@ tests: dimensions: - opa-latest - openshift + - name: data-hub-resource-info + dimensions: + - opa-latest + - trino-latest + - kafka-latest + - superset-latest + - data-hub + - data-hub-prerequisites + - openshift suites: - name: nightly patch: