diff --git a/Cargo.lock b/Cargo.lock index 4b456f1..a442375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,19 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +39,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -74,12 +105,29 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backon" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -95,6 +143,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "binascii" version = "0.1.4" @@ -107,6 +161,21 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytemuck" version = "1.23.2" @@ -135,6 +204,25 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cookie" version = "0.18.1" @@ -147,14 +235,75 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.3" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "devise" version = "0.4.2" @@ -188,6 +337,28 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" @@ -203,6 +374,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -219,6 +410,27 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -251,6 +463,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -287,6 +514,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -308,6 +546,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -329,6 +568,16 @@ dependencies = [ "windows", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -364,6 +613,29 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.0" @@ -376,6 +648,26 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.1.3", +] + [[package]] name = "http" version = "0.2.12" @@ -409,6 +701,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -432,7 +747,7 @@ dependencies = [ "futures-core", "futures-util", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -444,6 +759,79 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper 1.7.0", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.7.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.7.0", + "libc", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "indexmap" version = "2.11.4" @@ -451,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", "serde", "serde_core", ] @@ -490,15 +878,168 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonpath-rust" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror 2.0.16", +] + +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "k8s-openapi" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d13f06d5326a915becaffabdfab75051b8cdc260c2a5c06c0e90226ede89a692" +dependencies = [ + "base64", + "chrono", + "serde", + "serde_json", +] + [[package]] name = "k8s-reboot-controller" version = "0.1.0" dependencies = [ + "k8s-openapi", + "kube", + "kube-runtime", "rocket", "tracing", "tracing-subscriber", ] +[[package]] +name = "kube" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e7bb0b6a46502cc20e4575b6ff401af45cfea150b34ba272a3410b78aa014e" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4987d57a184d2b5294fdad3d7fc7f278899469d21a4da39a8f6ca16426567a36" +dependencies = [ + "base64", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.7.0", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rustls", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914bbb770e7bb721a06e3538c0edd2babed46447d128f7c21caa68747060ee73" +dependencies = [ + "chrono", + "derive_more", + "form_urlencoded", + "http 1.3.1", + "json-patch", + "k8s-openapi", + "serde", + "serde-value", + "serde_json", + "thiserror 2.0.16", +] + +[[package]] +name = "kube-runtime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aea4de4b562c5cc89ab10300bb63474ae1fa57ff5a19275f2e26401a323e3fd" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "backon", + "educe", + "futures", + "hashbrown 0.15.5", + "hostname", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -507,9 +1048,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "linux-raw-sys" @@ -623,6 +1164,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.17.0" @@ -648,6 +1198,27 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -694,12 +1265,86 @@ dependencies = [ "syn", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +dependencies = [ + "memchr", + "thiserror 2.0.16", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -823,6 +1468,18 @@ dependencies = [ "syn", ] +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.10" @@ -840,6 +1497,20 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rocket" version = "0.5.1" @@ -904,7 +1575,7 @@ dependencies = [ "either", "futures", "http 0.2.12", - "hyper", + "hyper 0.14.32", "indexmap", "log", "memchr", @@ -940,6 +1611,53 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -952,6 +1670,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -965,29 +1692,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serde" -version = "1.0.225" +name = "secrecy" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ "serde_core", "serde_derive", ] [[package]] -name = "serde_core" -version = "1.0.225" +name = "serde-value" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", @@ -1016,6 +1785,30 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1096,6 +1889,12 @@ dependencies = [ "loom", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.106" @@ -1108,10 +1907,16 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.22.0" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -1120,6 +1925,46 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -1190,6 +2035,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -1211,6 +2066,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project-lite", + "slab", "tokio", ] @@ -1255,6 +2111,47 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "base64", + "bitflags", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1267,6 +2164,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1328,6 +2226,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "ubyte" version = "0.10.4" @@ -1337,6 +2241,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uncased" version = "0.9.10" @@ -1359,6 +2269,18 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "valuable" version = "0.1.1" @@ -1404,6 +2326,65 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows" version = "0.48.0" @@ -1413,6 +2394,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.0" @@ -1443,7 +2430,7 @@ version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -1610,3 +2597,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index dbf5091..4bd65d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] +k8s-openapi = { version = "0.26.0", features = ["earliest"] } +kube = "2.0.1" rocket = { version = "0.5.1", default-features = false } tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } + +[dev-dependencies] +kube = { version = "2.0.1", features = ["runtime"] } +kube-runtime = "2.0.1" diff --git a/src/lib.rs b/src/lib.rs index 57b84e9..9d90bc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod lock; + use rocket::Request; use rocket::http::Status; @@ -17,6 +19,9 @@ fn healthz() -> &'static str { #[rocket::launch] pub fn rocket() -> _ { rocket::build() - .mount("/", rocket::routes![healthz]) + .mount( + "/", + rocket::routes![healthz, lock::lock_v1, lock::unlock_v1], + ) .register("/", rocket::catchers![not_found]) } diff --git a/src/lock.rs b/src/lock.rs new file mode 100644 index 0000000..18e9c67 --- /dev/null +++ b/src/lock.rs @@ -0,0 +1,178 @@ +use k8s_openapi::api::coordination::v1::{Lease, LeaseSpec}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use kube::Client; +use kube::api::{Api, Patch, PatchParams, WatchEvent, WatchParams}; +use rocket::form::Form; +use rocket::futures::{StreamExt, TryStreamExt}; +use rocket::http::Status; +use rocket::request::{self, FromRequest, Request}; +use tracing::{error, info, trace, warn}; + +#[derive(Debug, rocket::Responder)] +pub enum LockError { + #[response(status = 500, content_type = "plain")] + ServerError(String), + #[response(status = 400, content_type = "plain")] + InvalidHeader(String), + #[response(status = 409, content_type = "plain")] + Conflict(String), + #[response(status = 422, content_type = "plain")] + FormError(String), +} + +impl From for LockError { + fn from(error: kube::Error) -> Self { + Self::ServerError(format!("{error}\n")) + } +} + +impl From for LockError { + fn from(_h: InvalidHeader) -> Self { + Self::InvalidHeader("Invalid lock header\n".into()) + } +} + +impl From> for LockError { + fn from(errors: rocket::form::Errors<'_>) -> Self { + let mut message = String::from("Error processing request:\n"); + for error in errors { + if let Some(name) = error.name { + message.push_str(&format!("{name}: ")); + } + message.push_str(&error.kind.to_string()); + message.push('\n'); + } + Self::FormError(message) + } +} + +pub struct LockRequestHeader; + +#[derive(Debug)] +pub struct InvalidHeader; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for LockRequestHeader { + type Error = InvalidHeader; + + async fn from_request( + req: &'r Request<'_>, + ) -> request::Outcome { + match req.headers().get_one("K8s-Reboot-Lock") { + Some("lock") => request::Outcome::Success(Self), + _ => request::Outcome::Error((Status::BadRequest, InvalidHeader)), + } + } +} + +#[derive(rocket::FromForm)] +pub struct LockRequest { + hostname: String, + #[field(default = String::from("default"))] + group: String, + #[field(default = true)] + wait: bool, +} + +async fn update_lease( + client: Client, + name: &str, + identity: &str, + holder: Option<&str>, +) -> Result { + let apply = PatchParams::apply(identity); + let leases: Api = Api::default_namespaced(client); + let lease = Lease { + metadata: ObjectMeta { + name: Some(name.into()), + ..Default::default() + }, + spec: Some(LeaseSpec { + holder_identity: holder.map(|i| i.into()), + ..Default::default() + }), + }; + leases.patch(name, &apply, &Patch::Apply(&lease)).await +} + +async fn wait_lease(client: Client, name: &str) -> Result<(), kube::Error> { + let leases: Api = Api::default_namespaced(client); + let params = + WatchParams::default().fields(&format!("metadata.name={name}")); + let mut stream = leases.watch(¶ms, "0").await?.boxed(); + while let Some(event) = stream.try_next().await? { + trace!("Watch lease event: {event:?}"); + match event { + WatchEvent::Added(l) | WatchEvent::Modified(l) => match l.spec { + Some(spec) if spec.holder_identity.is_some() => (), + _ => break, + }, + WatchEvent::Bookmark(_) => (), + WatchEvent::Deleted(_) => break, + WatchEvent::Error(e) => return Err(kube::Error::Api(e)), + } + } + Ok(()) +} + +#[rocket::post("/api/v1/lock", data = "")] +pub async fn lock_v1( + lockheader: Result, + data: rocket::form::Result<'_, Form>, +) -> Result<(), LockError> { + lockheader?; + let data = data?; + let client = Client::try_default().await.inspect_err(|e| { + error!("Could not connect to Kubernetes API server: {e}") + })?; + let lock_name = format!("reboot-lock-{}", data.group); + loop { + match update_lease( + client.clone(), + &lock_name, + &data.hostname, + Some(&data.hostname), + ) + .await + { + Ok(_) => break, + Err(kube::Error::Api(e)) => { + if e.code == 409 { + warn!("Lock already held: {}", e.message); + if !data.wait { + return Err(LockError::Conflict(format!( + "Another system is already rebooting: {}\n", + e.message, + ))); + } else { + info!("Waiting for lease {lock_name}"); + if let Err(e) = + wait_lease(client.clone(), &lock_name).await + { + error!("Error while waiting for lease: {e}"); + } + } + } else { + return Err(kube::Error::Api(e).into()); + } + }, + Err(e) => return Err(e.into()), + } + } + Ok(()) +} + +#[rocket::post("/api/v1/unlock", data = "")] +pub async fn unlock_v1( + lockheader: Result, + data: rocket::form::Result<'_, Form>, +) -> Result<(), LockError> { + lockheader?; + let data = data?; + let client = Client::try_default().await.inspect_err(|e| { + error!("Could not connect to Kubernetes API server: {e}") + })?; + let lock_name = format!("reboot-lock-{}", data.group); + update_lease(client.clone(), &lock_name, &data.hostname, None).await?; + Ok(()) +} diff --git a/tests/integration/lock.rs b/tests/integration/lock.rs new file mode 100644 index 0000000..60c108f --- /dev/null +++ b/tests/integration/lock.rs @@ -0,0 +1,328 @@ +use std::sync::LazyLock; + +use k8s_openapi::api::coordination::v1::Lease; +use kube::Client; +use kube::api::Api; +use rocket::async_test; +use rocket::futures::FutureExt; +use rocket::http::{ContentType, Header, Status}; +use rocket::tokio; +use rocket::tokio::sync::Mutex; + +static LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +async fn delete_lease(name: &str) { + let client = Client::try_default().await.unwrap(); + let leases: Api = Api::default_namespaced(client); + let _ = kube::runtime::wait::delete::delete_and_finalize( + leases, + name, + &Default::default(), + ) + .await; +} + +async fn get_lease(name: &str) -> Result { + let client = Client::try_default().await.unwrap(); + let leases: Api = Api::default_namespaced(client); + leases.get(name).await +} + +#[async_test] +async fn test_lock_v1_success() { + super::setup(); + let _lock = &*LOCK.lock().await; + + delete_lease("reboot-lock-default").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); +} + +#[async_test] +async fn test_lock_v1_custom_group() { + super::setup(); + + delete_lease("reboot-lock-testgroup").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org&group=testgroup") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let lease = get_lease("reboot-lock-testgroup").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); +} + +#[async_test] +async fn test_lock_v1_conflict() { + super::setup(); + let _lock = &*LOCK.lock().await; + + delete_lease("reboot-lock-default").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test2.example.org&wait=false") + .dispatch() + .await; + assert_eq!(response.status(), Status::Conflict); + let want_msg = concat!( + "Another system is already rebooting:", + " Apply failed with 1 conflict:", + " conflict with \"test1.example.org\":", + " .spec.holderIdentity", + "\n", + ); + assert_eq!(response.into_string().await.as_deref(), Some(want_msg)); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); +} + +#[async_test] +async fn test_lock_v1_conflict_wait() { + super::setup(); + let _lock = &*LOCK.lock().await; + + tracing::info!("Deleting existing lease"); + delete_lease("reboot-lock-default").await; + tracing::info!("Creating first lease"); + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); + let timer = std::time::Instant::now(); + let _task = tokio::spawn(async { + tokio::time::sleep(std::time::Duration::from_secs(1)) + .then(|_| async { + tracing::info!("Deleting first lease"); + delete_lease("reboot-lock-default").await + }) + .await + }); + tracing::info!("Creating second lease"); + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test2.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let duration = timer.elapsed().as_millis(); + assert!(duration > 1000 && duration < 2000); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test2.example.org") + ); +} + +#[test] +fn test_lock_v1_no_header() { + super::setup(); + + let client = super::client(); + let response = client + .post("/api/v1/lock") + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch(); + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().as_deref(), + Some("Invalid lock header\n") + ); +} + +#[test] +fn test_lock_v1_no_data() { + super::setup(); + + let client = super::client(); + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("") + .dispatch(); + assert_eq!(response.status(), Status::UnprocessableEntity); + assert_eq!( + response.into_string().as_deref(), + Some("Error processing request:\nhostname: missing\n") + ); +} + +#[async_test] +async fn test_unlock_v1_success() { + super::setup(); + let _lock = &*LOCK.lock().await; + + delete_lease("reboot-lock-default").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); + let response = client + .post("/api/v1/unlock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + let status = response.status(); + assert_eq!(response.into_string().await, None); + assert_eq!(status, Status::Ok); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!(lease.spec.unwrap().holder_identity, None); +} + +#[async_test] +async fn test_unlock_v1_not_locked() { + super::setup(); + let _lock = &*LOCK.lock().await; + + delete_lease("reboot-lock-default").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/unlock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + let status = response.status(); + assert_eq!(response.into_string().await, None); + assert_eq!(status, Status::Ok); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!(lease.spec.unwrap().holder_identity.as_deref(), None); +} + +#[async_test] +async fn test_unlock_v1_not_mine() { + super::setup(); + let _lock = &*LOCK.lock().await; + + delete_lease("reboot-lock-default").await; + let client = super::async_client().await; + let response = client + .post("/api/v1/lock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string().await, None); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); + let response = client + .post("/api/v1/unlock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("hostname=test2.example.org") + .dispatch() + .await; + let status = response.status(); + assert_eq!(response.into_string().await, None); + assert_eq!(status, Status::Ok); + let lease = get_lease("reboot-lock-default").await.unwrap(); + assert_eq!( + lease.spec.unwrap().holder_identity.as_deref(), + Some("test1.example.org") + ); +} + +#[test] +fn test_unlock_v1_no_header() { + super::setup(); + + let client = super::client(); + let response = client + .post("/api/v1/unlock") + .header(ContentType::Form) + .body("hostname=test1.example.org") + .dispatch(); + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().as_deref(), + Some("Invalid lock header\n") + ); +} + +#[test] +fn test_unlock_v1_no_data() { + super::setup(); + + let client = super::client(); + let response = client + .post("/api/v1/unlock") + .header(Header::new("K8s-Reboot-Lock", "lock")) + .header(ContentType::Form) + .body("") + .dispatch(); + assert_eq!(response.status(), Status::UnprocessableEntity); + assert_eq!( + response.into_string().as_deref(), + Some("Error processing request:\nhostname: missing\n") + ); +} diff --git a/tests/integration/main.rs b/tests/integration/main.rs index a950a9a..9797498 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -1,7 +1,9 @@ mod basic; +mod lock; use std::sync::LazyLock; +use rocket::local::asynchronous::Client as AsyncClient; use rocket::local::blocking::Client; static SETUP: LazyLock<()> = LazyLock::new(|| { @@ -23,6 +25,12 @@ fn setup() { LazyLock::force(&SETUP); } +async fn async_client() -> AsyncClient { + AsyncClient::tracked(k8s_reboot_controller::rocket()) + .await + .unwrap() +} + fn client() -> Client { Client::tracked(k8s_reboot_controller::rocket()).unwrap() }