diff --git a/config/samples/gateway_v1.yaml b/config/samples/gateway_v1.yaml new file mode 100644 index 00000000..71da6e53 --- /dev/null +++ b/config/samples/gateway_v1.yaml @@ -0,0 +1,18 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: blixt-tcproute-sample +spec: + controllerName: gateway.networking.k8s.io/blixt +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: blixt-tcproute-sample +spec: + gatewayClassName: blixt-tcproute-sample + listeners: + - name: tcp + protocol: TCP + port: 8080 diff --git a/controlplane/.dockerignore b/controlplane/.dockerignore new file mode 100644 index 00000000..91e25c5d --- /dev/null +++ b/controlplane/.dockerignore @@ -0,0 +1,3 @@ +.vim/ +target/ +*.md diff --git a/controlplane/.gitignore b/controlplane/.gitignore new file mode 100644 index 00000000..9db7029f --- /dev/null +++ b/controlplane/.gitignore @@ -0,0 +1,9 @@ +### https://raw.github.com/github/gitignore/master/Rust.gitignore + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/controlplane/Cargo.lock b/controlplane/Cargo.lock new file mode 100644 index 00000000..833f92b7 --- /dev/null +++ b/controlplane/Cargo.lock @@ -0,0 +1,1940 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.5", +] + +[[package]] +name = "controlplane" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "futures", + "gateway-api", + "k8s-openapi", + "kube", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[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 = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gateway-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd691a9e618b3e89a56ec8b0c00191cf69236398142e147dffbcf6b61ca7cf9e" +dependencies = [ + "k8s-openapi", + "kube", + "schemars", + "serde", + "serde_json", + "serde_yaml", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "jsonpath-rust" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96acbc6188d3bd83519d053efec756aa4419de62ec47be7f28dec297f7dc9eb0" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "k8s-openapi" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550f99d93aa4c2b25de527bce492d772caf5e21d7ac9bd4b508ba781c8d91e30" +dependencies = [ + "base64 0.21.7", + "chrono", + "schemars", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fe330a0617b276ec864c2255810adcdf519ecb6844253c54074b2086a97bc" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0d65dd6f3adba29cfb84f19dfe55449c7f6c35425f9d8294bec40313e0b64" +dependencies = [ + "base64 0.21.7", + "bytes", + "chrono", + "either", + "futures", + "home", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-timeout", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "pin-project", + "rustls", + "rustls-pemfile", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6b42844e9172f631b8263ea9ce003b9251da13beb1401580937ad206dd82f4c" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "once_cell", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b5a111ee287bd237b8190b8c39543ea9fd22f79e9c32a36c24e08234bcda22" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.60", +] + +[[package]] +name = "kube-runtime" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc06275064c81056fbb28ea876b3fb339d970e8132282119359afca0835c0ea" +dependencies = [ + "ahash", + "async-trait", + "backoff", + "derivative", + "futures", + "hashbrown", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[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 = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.60", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.200" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.200" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/controlplane/Cargo.toml b/controlplane/Cargo.toml new file mode 100644 index 00000000..a236dd37 --- /dev/null +++ b/controlplane/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "controlplane" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +doc = false +name = "controller" +path = "src/main.rs" + +[dependencies] +futures = "0.3.28" +tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] } +kube = { version = "^0.88.0", default-features = false, features = ["runtime", "client", "derive", "rustls-tls"] } +k8s-openapi = { version = "0.21.1", features = ["latest"] } +serde = { version = "1.0.185", features = ["derive"] } +chrono = { version = "0.4.33", features = ["serde"] } +serde_json = "1.0.105" +serde_yaml = "0.9.25" +tracing = "0.1.37" +tracing-subscriber = "0.3" +thiserror = "1.0.47" +anyhow = "1.0.75" +gateway-api = "0.9.0" + diff --git a/controlplane/Dockerfile b/controlplane/Dockerfile new file mode 100644 index 00000000..3ba7f876 --- /dev/null +++ b/controlplane/Dockerfile @@ -0,0 +1,15 @@ +FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx + +FROM --platform=$BUILDPLATFORM rust:alpine +ARG TARGETPLATFORM + +RUN apk add clang lld +COPY --from=xx / / + +WORKDIR /workspace + +RUN --mount=type=bind,source=src,target=src \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + xx-cargo build --release --target-dir ./build && \ + xx-verify ./build/$(xx-cargo --print-target-triple)/release/controller diff --git a/controlplane/Makefile b/controlplane/Makefile new file mode 100644 index 00000000..18ade1bd --- /dev/null +++ b/controlplane/Makefile @@ -0,0 +1,25 @@ +IMAGE ?= ghcr.io/kubernetes-sigs/blixt-controlplane-rs +TAG ?= latest +KIND_CLUSTER ?= blixt-dev + +all: build + +.PHONY: +clean: + cargo clean + +.PHONY: build +build: + cargo build + +.PHONY: build.release +build.release: + cargo build --release + +.PHONY: run +run: + cargo run + +.PHONY: build.image +build.image: + DOCKER_BUILDKIT=1 docker build -t $(IMAGE):$(TAG) ./ diff --git a/controlplane/README.md b/controlplane/README.md new file mode 100644 index 00000000..ef3d9e34 --- /dev/null +++ b/controlplane/README.md @@ -0,0 +1,33 @@ +# Rust Controlplane + +This directory hosts the code for the WIP Rust controlplane. It aims to port the Golang reconcilers in `blixt/controllers` in Rust. +For more context behind the decision, please see https://github.com/kubernetes-sigs/blixt/issues/176 and https://github.com/kubernetes-sigs/blixt/discussions/150. + +## Progress + +- [ ] GatewayClass reconciler +- [x] Gateway reconciler +- [ ] TCPRoute reconciler +- [ ] UDPRoute reconciler + +## Getting started + +* Create a Kubernetes cluster + +```bash +make build.cluster +``` + +* Install Gateway API CRDS; create a `GatewayClass` and `Gateway` + +```bash +kubectl apply -k https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental\?ref\=v1.0.0 +kubectl apply -f config/samples/gateway_v1.yaml +``` + +* Run the reconciler + +```bash +cd controlplane +make run +``` diff --git a/controlplane/src/gateway_controller.rs b/controlplane/src/gateway_controller.rs new file mode 100644 index 00000000..06c5a089 --- /dev/null +++ b/controlplane/src/gateway_controller.rs @@ -0,0 +1,228 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +use futures::StreamExt; +use std::{ + ops::Sub, + sync::Arc, + time::{Duration, Instant}, +}; + +use crate::*; +use gateway_api::apis::standard::gateways::{Gateway, GatewayStatus}; +use gateway_api::apis::standard::{ + constants::{GatewayConditionReason, GatewayConditionType}, + gatewayclasses::GatewayClass, +}; +use k8s_openapi::api::core::v1::{Service, ServiceSpec, ServiceStatus}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; +use kube::{ + api::{Api, ListParams, Patch, PatchParams}, + runtime::{controller::Action, watcher::Config, Controller}, + Resource, ResourceExt, +}; + +use chrono::Utc; +use gateway_utils::*; +use tracing::*; + +pub async fn reconcile(gateway: Arc, ctx: Arc) -> Result { + let start = Instant::now(); + let client = ctx.client.clone(); + let name = gateway + .metadata + .name + .clone() + .ok_or(Error::InvalidConfigError("invalid name".to_string()))?; + let ns = gateway + .metadata + .namespace + .clone() + .ok_or(Error::InvalidConfigError("invalid namespace".to_string()))?; + + let gateway_api: Api = Api::namespaced(client.clone(), &ns); + let mut gw = Gateway { + metadata: gateway.metadata.clone(), + spec: gateway.spec.clone(), + status: gateway.status.clone(), + }; + + let gateway_class_api = Api::::all(client.clone()); + let gateway_class = gateway_class_api + .get(gateway.spec.gateway_class_name.as_str()) + .await + .map_err(Error::KubeError)?; + + // Only reconcile the Gateway object if it belongs to our controller's gateway class. + if gateway_class.spec.controller_name.as_str() != GATEWAY_CLASS_CONTROLLER_NAME { + return Ok(Action::await_change()); + } + debug!( + "found a supported GatewayClass: {:?}", + gateway_class.name_any() + ); + + set_listener_status(&mut gw)?; + let accepted_cond = get_accepted_condition(&gw); + set_condition(&mut gw, accepted_cond.clone()); + + // If the controller can't accept responsibility, then set the Condition of type "Programmed" to False and error out. + if accepted_cond.status == "False" { + let programmed_cond = metav1::Condition { + last_transition_time: accepted_cond.last_transition_time.clone(), + observed_generation: accepted_cond.observed_generation, + type_: GatewayConditionType::Programmed.to_string(), + status: "False".to_string(), + message: accepted_cond.message.clone(), + reason: GatewayConditionReason::Programmed.to_string(), + }; + set_condition(&mut gw, programmed_cond); + patch_status( + &gateway_api, + name, + gw.status.as_ref().unwrap_or(&GatewayStatus::default()), + ) + .await?; + return Err(Error::InvalidConfigError(accepted_cond.message)); + } + + // Try to fetch any existing Loadbalancer service(s) for this Gateway. + let service_api: Api = Api::namespaced(client, &ns); + let services = service_api + .list(&ListParams::default().labels(&format!("{}={}", GATEWAY_SERVICE_LABEL, name))) + .await + .map_err(Error::KubeError)?; + + if services.items.len() > 1 { + let mut names: Vec = vec![]; + for svc in services.items { + if let Some(name) = &svc.meta().name { + names.push(name.clone()); + } + } + error!(services = ?names, "found multiple Services"); + return Err(Error::LoadBalancerError( + "found more than 1 Service for this Gateway; multiple services are not supported" + .to_string(), + )); + } + + // If we found a Loadbalancer service, then correct any drift if necessary, else create the service. + let mut service: Service; + if let Some(val) = services.items.first() { + service = val.clone(); + let updated = update_service_for_gateway(gateway.as_ref(), &mut service)?; + if updated { + info!("drift detected; updating loadbalancer service"); + let patch_parmas = PatchParams::default(); + service_api + .patch( + val.name_any().as_str(), + &patch_parmas, + &Patch::Strategic(&service), + ) + .await + .map_err(Error::KubeError)?; + } + } else { + info!("creating loadbalancer service"); + service = create_svc_for_gateway(ctx.clone(), gateway.as_ref()).await?; + } + + // invalid_lb_condition is a Condition that signfies that the Loadbalancer service is invalid. + let mut invalid_lb_condition = metav1::Condition { + last_transition_time: metav1::Time(Utc::now()), + observed_generation: gateway.meta().generation, + message: "".to_string(), + reason: GatewayConditionReason::AddressNotAssigned.to_string(), + status: "False".to_string(), + type_: GatewayConditionType::Programmed.to_string(), + }; + + let svc_spec: &ServiceSpec = match service.spec.as_ref().ok_or(Error::LoadBalancerError( + "Loadbalancer service spec not found".to_string(), + )) { + Ok(spec) => spec, + Err(error) => { + invalid_lb_condition.message = error.to_string(); + set_condition(&mut gw, invalid_lb_condition); + patch_status(&gateway_api, name, &gw.status.unwrap_or_default()).await?; + return Err(error); + } + }; + + let svc_status: &ServiceStatus = match service.status.as_ref().ok_or(Error::LoadBalancerError( + "Loadbalancer service status not found".to_string(), + )) { + Ok(status) => status, + Err(error) => { + invalid_lb_condition.message = error.to_string(); + set_condition(&mut gw, invalid_lb_condition); + patch_status(&gateway_api, name, &gw.status.unwrap_or_default()).await?; + return Err(error); + } + }; + + let svc_key = get_service_key(&service)?; + if get_ingress_ip_len(svc_status) == 0 || svc_spec.cluster_ip.is_none() { + let msg = "LoadBalancer does not have a ingress IP address".to_string(); + invalid_lb_condition.message.clone_from(&msg); + set_condition(&mut gw, invalid_lb_condition); + patch_status(&gateway_api, name, &gw.status.unwrap_or_default()).await?; + return Err(Error::LoadBalancerError(msg)); + } + + create_endpoint_if_not_exists(ctx.clone(), &svc_key, svc_spec, svc_status).await?; + set_gateway_status_addresses(&mut gw, svc_status); + + let programmed_cond = metav1::Condition { + last_transition_time: metav1::Time(Utc::now()), + observed_generation: gateway.meta().generation, + type_: GatewayConditionType::Programmed.to_string(), + status: "True".to_string(), + reason: GatewayConditionReason::Programmed.to_string(), + message: "Dataplane configured for gateway".to_string(), + }; + set_condition(&mut gw, programmed_cond); + + patch_status(&gateway_api, name, &gw.status.unwrap_or_default()).await?; + + let duration = Instant::now().sub(start); + info!("finished reconciling in {:?} ms", duration.as_millis()); + Ok(Action::requeue(Duration::from_secs(60))) +} + +pub async fn controller(ctx: Context) -> Result<()> { + let gateway = Api::::all(ctx.client.clone()); + gateway + .list(&ListParams::default().limit(1)) + .await + .map_err(Error::CRDNotFoundError)?; + + Controller::new(gateway, Config::default().any_semantic()) + .shutdown_on_signal() + .run(reconcile, error_policy, Arc::new(ctx)) + .filter_map(|x| async move { std::result::Result::ok(x) }) + .for_each(|_| futures::future::ready(())) + .await; + + Ok(()) +} + +fn error_policy(_: Arc, error: &Error, _: Arc) -> Action { + warn!("reconcile failed: {:?}", error); + Action::requeue(Duration::from_secs(5)) +} diff --git a/controlplane/src/gateway_utils.rs b/controlplane/src/gateway_utils.rs new file mode 100644 index 00000000..06432e7b --- /dev/null +++ b/controlplane/src/gateway_utils.rs @@ -0,0 +1,643 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#![allow(clippy::field_reassign_with_default)] + +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; + +use crate::*; +use gateway_api::apis::standard::{ + constants::{ + GatewayConditionReason, GatewayConditionType, ListenerConditionReason, + ListenerConditionType, + }, + gateways::{ + Gateway, GatewayListeners, GatewayListenersAllowedRoutesKinds, GatewaySpec, GatewayStatus, + GatewayStatusAddresses, GatewayStatusListeners, GatewayStatusListenersSupportedKinds, + }, +}; +use kube::{ + api::{Api, Patch, PatchParams, PostParams}, + core::ObjectMeta, + Resource, ResourceExt, +}; + +use k8s_openapi::api::core::v1::{ + EndpointAddress, EndpointPort, EndpointSubset, Endpoints, Service, ServicePort, ServiceSpec, + ServiceStatus, +}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; + +use chrono::Utc; +use serde_json::json; +use tracing::*; + +// Modifies the Gateway's status to reflect the LoadBalancer Service's ingress IP address. +pub fn set_gateway_status_addresses(gateway: &mut Gateway, svc_status: &ServiceStatus) { + let mut gw_addrs: Vec = vec![]; + + if let Some(load_balancer) = &svc_status.load_balancer { + if let Some(ingress) = &load_balancer.ingress { + for addr in ingress { + if let Some(ip) = &addr.ip { + gw_addrs.push(GatewayStatusAddresses { + r#type: Some("IPAddress".to_string()), + value: ip.clone(), + }); + } + } + } + } + + if let Some(status) = gateway.status.as_mut() { + status.addresses = Some(gw_addrs); + } else { + let mut status = GatewayStatus::default(); + status.addresses = Some(gw_addrs); + gateway.status = Some(status); + } +} + +// Creates an Endpoints object for the provided Service pointing to it's ingress IP address. +// Since we don't set a selector on the Service (because we don't need to route incoming traffic +// to a particular pod), no Endpoints object is created for it. An Endpoints object is required +// because MetalLB does not respond to ARP packets until one exists for the LoadBalancer Service +// causing traffic to never reach the node. +// Ref: https://github.com/metallb/metallb/issues/1640 +pub async fn create_endpoint_if_not_exists( + ctx: Arc, + key: &NamespacedName, + svc_spec: &ServiceSpec, + svc_status: &ServiceStatus, +) -> Result<()> { + let mut lb_addr = None; + let lb_status = svc_status + .load_balancer + .as_ref() + .ok_or(Error::LoadBalancerError( + "Load balancer not found in service status".to_string(), + ))?; + let ingress = lb_status.ingress.as_ref().ok_or(Error::LoadBalancerError( + "Ingress not found in service status".to_string(), + ))?; + for addr in ingress { + if let Some(ip) = &addr.ip { + lb_addr = Some(ip.clone()); + break; + } + } + let lb_addr_ip = lb_addr.ok_or(Error::LoadBalancerError( + "LoadBalancer ingress ip not found in service status".to_string(), + ))?; + + let endpoints_api: Api = Api::namespaced(ctx.client.clone(), &key.namespace); + + if let Some(err) = endpoints_api.get(&key.name).await.err() { + if check_if_not_found_err(err) { + let mut ep_ports: Vec = vec![]; + if let Some(ports) = &svc_spec.ports { + for port in ports { + let mut ep_port = EndpointPort::default(); + ep_port.port = port.port; + ep_port.protocol.clone_from(&port.protocol); + ep_ports.push(ep_port); + } + } + + let mut obj_meta = ObjectMeta::default(); + obj_meta.name = Some(key.name.clone()); + obj_meta.namespace = Some(key.namespace.clone()); + + let mut ep_addr = EndpointAddress::default(); + ep_addr.ip = lb_addr_ip; + + let endpoints = Endpoints { + metadata: obj_meta, + subsets: Some(vec![EndpointSubset { + addresses: Some(vec![ep_addr]), + not_ready_addresses: None, + ports: Some(ep_ports), + }]), + }; + let ep = endpoints_api + .create(&PostParams::default(), &endpoints) + .await + .map_err(Error::KubeError)?; + info!("created Endpoints object {}", ep.name_any()); + } + } + + Ok(()) +} + +// Returns true if the provided error is a not found error. +pub fn check_if_not_found_err(error: kube::Error) -> bool { + if let kube::Error::Api(response) = error { + if response.code == 404 { + return true; + } + } + false +} + +// Returns the number of ingresses set on the LoadBalancer Service. +pub fn get_ingress_ip_len(svc_status: &ServiceStatus) -> usize { + if let Some(lb) = &svc_status.load_balancer { + if let Some(ingress) = &lb.ingress { + return ingress.len(); + } + } + 0 +} + +// Creates a LoadBalancer Service for the provided Gateway. +pub async fn create_svc_for_gateway(ctx: Arc, gateway: &Gateway) -> Result { + let mut svc_meta = ObjectMeta::default(); + let ns = gateway.namespace().unwrap_or("default".to_string()); + svc_meta.namespace = Some(ns.clone()); + svc_meta.generate_name = Some(format!("service-for-gateway-{}-", gateway.name_any())); + + let mut labels = BTreeMap::new(); + labels.insert(GATEWAY_SERVICE_LABEL.to_string(), gateway.name_any()); + svc_meta.labels = Some(labels); + + let mut svc = Service { + metadata: svc_meta, + spec: Some(ServiceSpec::default()), + status: Some(ServiceStatus::default()), + }; + update_service_for_gateway(gateway, &mut svc)?; + + let svc_api: Api = Api::namespaced(ctx.client.clone(), ns.as_str()); + let service = svc_api + .create(&PostParams::default(), &svc) + .await + .map_err(Error::KubeError)?; + + Ok(service) +} + +// Updates the provided Service to match the desired state according to the provided Gateway. +// Returns true if Service was modified. +pub fn update_service_for_gateway(gateway: &Gateway, svc: &mut Service) -> Result { + let mut updated = false; + let mut ports: Vec = vec![]; + for listener in &gateway.spec.listeners { + let mut port = ServicePort::default(); + port.name = Some(listener.name.clone()); + port.port = listener.port; + match listener.protocol.as_str() { + "TCP" | "HTTP" | "HTTPS" => { + port.protocol = Some("TCP".to_string()); + ports.push(port); + } + "UDP" => { + port.protocol = Some("UDP".to_string()); + ports.push(port); + } + _ => { + continue; + } + } + } + let mut address = None; + if let Some(addresses) = &gateway.spec.addresses { + if !addresses.is_empty() { + let addr = addresses[0].clone(); + if let Some(t) = addr.r#type { + if t != "IPAddress" { + return Err(Error::InvalidConfigError(format!("addresses of type {} are not supported; only type IPAddress is supported", t).to_string())); + } + } + address = Some(addresses[0].clone()); + } + if addresses.len() > 1 { + warn!("multiple addresses"); + } + } + let svc_spec = svc.spec.as_mut().ok_or(Error::LoadBalancerError( + "Loadbalancer service does not have a spec".to_string(), + ))?; + + let lb_ip: Option = svc_spec.load_balancer_ip.clone(); + if let Some((addr, ip)) = address.clone().zip(lb_ip.clone()) { + if ip != addr.value { + svc_spec.load_balancer_ip = Some(addr.value.clone()); + updated = true; + } + } + if address.is_none() && lb_ip.is_some() { + svc_spec.load_balancer_ip = None; + updated = true; + } + if let Some(ref mut t) = svc_spec.type_ { + if t != "LoadBalancer" { + *t = "LoadBalancer".to_string(); + updated = true; + } + } else { + svc_spec.type_ = Some("LoadBalancer".to_string()); + } + if let Some(ref mut svc_ports) = svc_spec.ports { + let mut diff = false; + if svc_ports.len() != ports.len() { + diff = true; + } + + let mut iter = svc_ports.iter().zip(ports.iter()); + for (p1, p2) in &mut iter { + if p1.name != p2.name || p1.port != p2.port || p1.protocol != p2.protocol { + diff = true; + break; + } + } + + if diff { + *svc_ports = ports; + updated = true; + } + } else { + svc_spec.ports = Some(ports); + } + + Ok(updated) +} + +// Patch the provided status on the Gateway object. +pub async fn patch_status( + gateway_api: &Api, + name: String, + status: &GatewayStatus, +) -> Result<()> { + let mut listeners = &vec![]; + if let Some(l) = status.listeners.as_ref() { + listeners = l; + } + let mut conditions = &vec![]; + if let Some(c) = status.conditions.as_ref() { + conditions = c; + } + let mut addresses = &vec![]; + if let Some(a) = status.addresses.as_ref() { + addresses = a; + } + let patch = Patch::Apply(json!({ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "status": { + "listeners": listeners, + "conditions": conditions, + "addresses": addresses + } + })); + let params = PatchParams::apply(BLIXT_FIELD_MANAGER).force(); + gateway_api + .patch_status(name.as_str(), ¶ms, &patch) + .await + .map_err(Error::KubeError)?; + Ok(()) +} + +// Sets the provided condition on the Gateway object. The condition on the Gateway is only updated +// if the new condition has a different status (except for the observed generation which is always +// updated). +pub fn set_condition(gateway: &mut Gateway, new_cond: metav1::Condition) { + if let Some(ref mut status) = gateway.status { + if let Some(ref mut conditions) = status.conditions { + for condition in conditions.iter_mut() { + if condition.type_ == new_cond.type_ { + if condition.status == new_cond.status { + // always update the observed generation + condition.observed_generation = new_cond.observed_generation; + return; + } + *condition = new_cond; + return; + } + } + conditions.push(new_cond); + } else { + status.conditions = Some(vec![new_cond]); + } + } +} + +// Inspects the provided Gateway and returns a Condition of type "Accepted" with appropriate reason and status. +// Ideally, this should be called after the Gateway object reflects the latest status of its +// listeners. +pub fn get_accepted_condition(gateway: &Gateway) -> metav1::Condition { + let now = metav1::Time(Utc::now()); + let mut accepted = metav1::Condition { + type_: GatewayConditionType::Accepted.to_string(), + status: String::from("True"), + reason: GatewayConditionReason::Accepted.to_string(), + observed_generation: gateway.metadata.generation, + last_transition_time: now, + message: String::from("Blixt accepts responsibility for this Gateway"), + }; + let gateway_spec: &GatewaySpec = &gateway.spec; + + if let Some(status) = &gateway.status { + if let Some(listeners) = &status.listeners { + for listener in listeners { + for conditon in &listener.conditions { + if conditon.status == "False" { + accepted.status = String::from("False"); + accepted.reason = GatewayConditionReason::ListenersNotValid.to_string(); + accepted.message = format!("listener {} is invalid", listener.name); + } + } + } + } + } + + if let Some(addresses) = &gateway_spec.addresses { + for addr in addresses { + if let Some(addr_type) = &addr.r#type { + if addr_type.as_str() != "IPAddress" { + accepted.status = String::from("False"); + accepted.reason = GatewayConditionReason::UnsupportedAddress.to_string(); + accepted.message = format!( + "found an addres of type {}, only type IPAddress is supported", + addr_type + ); + break; + } + } + } + } + accepted +} + +// Inspects the provided Gateway and sets the status of its listeners accordingly. +pub fn set_listener_status(gateway: &mut Gateway) -> Result<()> { + let gateway_spec: &GatewaySpec = &gateway.spec; + let mut statuses: Vec = vec![]; + let mut current_listener_statuses: HashMap = HashMap::new(); + + if let Some(gw_status) = &gateway.status { + if let Some(listeners) = &gw_status.listeners { + for listener in listeners { + current_listener_statuses.insert(listener.name.clone(), listener.clone()); + } + } + } + + let gen = gateway + .metadata + .generation + .ok_or(Error::InvalidConfigError( + "Gateway generation not found".to_string(), + ))?; + for listener in &gateway_spec.listeners { + let mut final_conditions = vec![]; + let (supported_kinds, conditions) = get_listener_status(listener, gen); + if let Some(current_listener_status) = current_listener_statuses.get(&listener.name) { + for condition in conditions { + let mut present = false; + for current_condition in ¤t_listener_status.conditions { + if condition.type_ == current_condition.type_ { + present = true; + if condition.status == current_condition.status { + let mut updated_condition = current_condition.clone(); + updated_condition.observed_generation = gateway.metadata.generation; + final_conditions.push(updated_condition); + } else { + final_conditions.push(condition.clone()); + } + } + } + if !present { + final_conditions.push(condition.clone()); + } + } + } else { + final_conditions = conditions; + } + + statuses.push(GatewayStatusListeners { + name: listener.name.clone(), + attached_routes: 0, + supported_kinds, + conditions: final_conditions, + }); + } + + if let Some(ref mut status) = gateway.status { + status.listeners = Some(statuses); + } + Ok(()) +} + +pub fn get_service_key(service: &Service) -> Result { + let svc_name = service.meta().name.clone().ok_or(Error::LoadBalancerError( + "Loadbalancer service name not found".to_string(), + ))?; + let svc_ns = service.namespace().ok_or(Error::LoadBalancerError( + "Loadblancer service namespace not found".to_string(), + ))?; + Ok(NamespacedName { + name: svc_name, + namespace: svc_ns, + }) +} + +// Inspects the provided Listener and returns the list GroupKind objects for support Routes and a +// list of Conditions accordingly. +fn get_listener_status( + listener: &GatewayListeners, + generation: i64, +) -> ( + Vec, + Vec, +) { + let now = metav1::Time(Utc::now()); + let mut supported_kinds: Vec = vec![]; + let mut conditions: Vec = vec![ + metav1::Condition { + type_: ListenerConditionType::ResolvedRefs.to_string(), + status: String::from("True"), + reason: ListenerConditionReason::ResolvedRefs.to_string(), + observed_generation: Some(generation), + last_transition_time: now.clone(), + message: String::from("All references resolved"), + }, + metav1::Condition { + type_: ListenerConditionType::Accepted.to_string(), + status: String::from("True"), + reason: ListenerConditionReason::Accepted.to_string(), + observed_generation: Some(generation), + last_transition_time: now.clone(), + message: String::from("Listener is valid"), + }, + metav1::Condition { + type_: ListenerConditionType::Programmed.to_string(), + status: String::from("True"), + reason: ListenerConditionType::Programmed.to_string(), + observed_generation: Some(generation), + last_transition_time: now, + message: String::from("Listener is valid"), + }, + ]; + + let mut update_listener_condition = + |status: String, reason: String, message: String, idx: usize| { + conditions[idx].status = status; + conditions[idx].reason = reason; + conditions[idx].message = message; + }; + + match listener.protocol.as_str() { + // Accept HTTP and HTTPS protocol types even though we don't support + // HTTPRoute so that Gateway API conformance tests pass. + "TCP" | "HTTP" | "HTTPS" => { + supported_kinds.push(GatewayStatusListenersSupportedKinds { + group: Some("gateway.networking.k8s.io".to_string()), + kind: "TCPRoute".to_string(), + }); + if let Some(routes) = &listener.allowed_routes { + if let Some(rgks) = &routes.kinds { + if let Some(msg) = check_route_kinds(Some("TCPRoute"), rgks) { + update_listener_condition( + String::from("False"), + ListenerConditionReason::InvalidRouteKinds.to_string(), + msg.clone(), + 0, + ); + update_listener_condition( + String::from("False"), + ListenerConditionReason::InvalidRouteKinds.to_string(), + msg.clone(), + 1, + ); + update_listener_condition( + String::from("False"), + ListenerConditionReason::Invalid.to_string(), + msg.clone(), + 2, + ); + } + } + } + } + "UDP" => { + supported_kinds.push(GatewayStatusListenersSupportedKinds { + group: Some("gateway.networking.k8s.io".to_string()), + kind: "UDPRoute".to_string(), + }); + if let Some(routes) = &listener.allowed_routes { + if let Some(rgks) = &routes.kinds { + if let Some(msg) = check_route_kinds(Some("UDPRoute"), rgks) { + update_listener_condition( + String::from("False"), + ListenerConditionReason::InvalidRouteKinds.to_string(), + msg.clone(), + 0, + ); + update_listener_condition( + String::from("False"), + ListenerConditionReason::InvalidRouteKinds.to_string(), + msg.clone(), + 1, + ); + update_listener_condition( + String::from("False"), + ListenerConditionReason::Invalid.to_string(), + msg.clone(), + 2, + ); + } + } + } + } + _ => { + update_listener_condition( + String::from("False"), + ListenerConditionReason::UnsupportedProtocol.to_string(), + format!( + "Unsupported protocol: {}, must be one of TCP or UDP", + listener.protocol + ), + 1, + ); + + if let Some(routes) = &listener.allowed_routes { + if let Some(rgks) = &routes.kinds { + if let Some(msg) = check_route_kinds(Some("UDPRoute"), rgks) { + update_listener_condition( + String::from("False"), + ListenerConditionReason::InvalidRouteKinds.to_string(), + msg.clone(), + 0, + ); + } + } + } + + update_listener_condition( + String::from("False"), + ListenerConditionReason::Invalid.to_string(), + format!( + "Unsupported protocol: {}, must be one of TCP or UDP", + listener.protocol + ), + 2, + ); + } + } + + (supported_kinds, conditions) +} + +fn check_route_kinds( + kind: Option<&str>, + rgks: &[GatewayListenersAllowedRoutesKinds], +) -> Option { + if rgks.is_empty() { + return None; + } + + if rgks.len() > 1 { + return Some(String::from( + "Multiple route kinds for a single listener is unsupported", + )); + } + + let rgk = &rgks[0]; + if let Some(k) = kind { + if rgk.kind != k { + return Some(format!( + "Unsupported route kind {}; only {} is supported", + rgk.kind, k + )); + } + } else if rgk.kind != "TCPRoute" || rgk.kind != "UDPRoute" { + return Some(format!( + "Unsupported route kind {}; can be one of TCPRoute or UDPRoute", + rgk.kind + )); + } + + if let Some(group) = &rgk.group { + if group.as_str() != "gateway.networking.k8s.io" { + return Some(format!("Unsupported API group: {}", group)); + } + } + None +} diff --git a/controlplane/src/lib.rs b/controlplane/src/lib.rs new file mode 100644 index 00000000..63f8d6a2 --- /dev/null +++ b/controlplane/src/lib.rs @@ -0,0 +1,51 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +use kube::Client; +use thiserror::Error; + +pub mod gateway_controller; +pub mod gateway_utils; + +// Context for our reconciler +#[derive(Clone)] +pub struct Context { + /// Kubernetes client + pub client: Client, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("kube error: {0}")] + KubeError(#[source] kube::Error), + #[error("invalid configuration: `{0}`")] + InvalidConfigError(String), + #[error("error reconciling loadbalancer service: `{0}`")] + LoadBalancerError(String), + #[error("error querying Gateway API CRDs: `{0}`; are the CRDs installed?")] + CRDNotFoundError(#[source] kube::Error), +} + +pub type Result = std::result::Result; + +pub const GATEWAY_CLASS_CONTROLLER_NAME: &str = "gateway.networking.k8s.io/blixt"; +pub const BLIXT_FIELD_MANAGER: &str = "blixt-field-manager"; +pub const GATEWAY_SERVICE_LABEL: &str = "blixt.gateway.networking.k8s.io/owned-by-gateway"; + +pub struct NamespacedName { + pub name: String, + pub namespace: String, +} diff --git a/controlplane/src/main.rs b/controlplane/src/main.rs new file mode 100644 index 00000000..f57e88c6 --- /dev/null +++ b/controlplane/src/main.rs @@ -0,0 +1,42 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +use controlplane::*; +use kube::Client; +use tracing::*; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + run().await; + Ok(()) +} + +pub async fn run() { + let subscriber = tracing_subscriber::FmtSubscriber::new(); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + let client = Client::try_default() + .await + .expect("failed to create kube Client"); + let ctx = Context { + client: client.clone(), + }; + + if let Err(error) = gateway_controller::controller(ctx).await { + error!("failed to start Gateway contoller: {error:?}"); + std::process::exit(1); + } +}