From 58e8f8a2b2b21968df21dcb9651c7c6e39b8a0ef Mon Sep 17 00:00:00 2001 From: Sehyo Chang Date: Tue, 4 Feb 2020 21:59:06 -0800 Subject: [PATCH] migrated code from fluvio repo updated and license file for all crates --- .gitignore | 6 + Cargo.lock | 1680 +++++++++++++++++ Cargo.toml | 9 + LICENSE-APACHE | 201 ++ src/k8-client/Cargo.toml | 40 + src/k8-client/Makefile | 6 + src/k8-client/README.md | 34 + src/k8-client/k8-fixtures/Cargo.toml | 14 + src/k8-client/k8-fixtures/data/topic.json | 3 + src/k8-client/k8-fixtures/src/lib.rs | 5 + .../k8-fixtures/src/test_fixtures.rs | 90 + src/k8-client/src/client.rs | 379 ++++ src/k8-client/src/config.rs | 121 ++ src/k8-client/src/config_map.rs | 39 + src/k8-client/src/error.rs | 109 ++ src/k8-client/src/fixture.rs | 3 + src/k8-client/src/lib.rs | 20 + src/k8-client/src/pod.rs | 203 ++ src/k8-client/src/secret.rs | 38 + src/k8-client/src/service.rs | 103 + src/k8-client/src/stateful.rs | 209 ++ src/k8-client/src/stream.rs | 133 ++ src/k8-client/src/wstream.rs | 137 ++ src/k8-client/tests/service.rs | 77 + src/k8-client/tests/stateful.rs | 19 + src/k8-client/tests/test.yaml | 5 + src/k8-client/tests/topic-stream.rs | 58 + src/k8-client/tests/topic.rs | 162 ++ src/k8-config/Cargo.toml | 16 + src/k8-config/LICENSE-APACHE | 201 ++ src/k8-config/README.md | 13 + src/k8-config/data/k8config.yaml | 27 + src/k8-config/src/config.rs | 149 ++ src/k8-config/src/error.rs | 34 + src/k8-config/src/lib.rs | 70 + src/k8-config/src/pod.rs | 63 + src/k8-diff/Cargo.toml | 13 + src/k8-diff/LICENSE-APACHE | 201 ++ src/k8-diff/README.md | 13 + src/k8-diff/k8-dderive/Cargo.toml | 14 + src/k8-diff/k8-dderive/src/diff.rs | 61 + src/k8-diff/k8-dderive/src/lib.rs | 19 + src/k8-diff/src/json/diff.rs | 79 + src/k8-diff/src/json/mod.rs | 43 + src/k8-diff/src/json/se.rs | 104 + src/k8-diff/src/lib.rs | 74 + src/k8-metadata-client/Cargo.toml | 21 + src/k8-metadata-client/LICENSE-APACHE | 201 ++ src/k8-metadata-client/README.md | 13 + src/k8-metadata-client/src/client.rs | 241 +++ src/k8-metadata-client/src/diff.rs | 51 + src/k8-metadata-client/src/lib.rs | 13 + src/k8-metadata-client/src/nothing.rs | 168 ++ src/k8-metadata-core/Cargo.toml | 17 + src/k8-metadata-core/LICENSE-APACHE | 201 ++ src/k8-metadata-core/README.md | 13 + src/k8-metadata-core/src/crd.rs | 21 + src/k8-metadata-core/src/lib.rs | 42 + src/k8-metadata-core/src/metadata.rs | 667 +++++++ src/k8-metadata-core/src/options.rs | 137 ++ 60 files changed, 6903 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 src/k8-client/Cargo.toml create mode 100644 src/k8-client/Makefile create mode 100644 src/k8-client/README.md create mode 100644 src/k8-client/k8-fixtures/Cargo.toml create mode 100644 src/k8-client/k8-fixtures/data/topic.json create mode 100644 src/k8-client/k8-fixtures/src/lib.rs create mode 100644 src/k8-client/k8-fixtures/src/test_fixtures.rs create mode 100644 src/k8-client/src/client.rs create mode 100644 src/k8-client/src/config.rs create mode 100644 src/k8-client/src/config_map.rs create mode 100644 src/k8-client/src/error.rs create mode 100644 src/k8-client/src/fixture.rs create mode 100644 src/k8-client/src/lib.rs create mode 100644 src/k8-client/src/pod.rs create mode 100644 src/k8-client/src/secret.rs create mode 100644 src/k8-client/src/service.rs create mode 100644 src/k8-client/src/stateful.rs create mode 100644 src/k8-client/src/stream.rs create mode 100644 src/k8-client/src/wstream.rs create mode 100644 src/k8-client/tests/service.rs create mode 100644 src/k8-client/tests/stateful.rs create mode 100644 src/k8-client/tests/test.yaml create mode 100644 src/k8-client/tests/topic-stream.rs create mode 100644 src/k8-client/tests/topic.rs create mode 100644 src/k8-config/Cargo.toml create mode 100644 src/k8-config/LICENSE-APACHE create mode 100644 src/k8-config/README.md create mode 100644 src/k8-config/data/k8config.yaml create mode 100644 src/k8-config/src/config.rs create mode 100644 src/k8-config/src/error.rs create mode 100644 src/k8-config/src/lib.rs create mode 100644 src/k8-config/src/pod.rs create mode 100644 src/k8-diff/Cargo.toml create mode 100644 src/k8-diff/LICENSE-APACHE create mode 100644 src/k8-diff/README.md create mode 100644 src/k8-diff/k8-dderive/Cargo.toml create mode 100644 src/k8-diff/k8-dderive/src/diff.rs create mode 100644 src/k8-diff/k8-dderive/src/lib.rs create mode 100644 src/k8-diff/src/json/diff.rs create mode 100644 src/k8-diff/src/json/mod.rs create mode 100644 src/k8-diff/src/json/se.rs create mode 100644 src/k8-diff/src/lib.rs create mode 100644 src/k8-metadata-client/Cargo.toml create mode 100644 src/k8-metadata-client/LICENSE-APACHE create mode 100644 src/k8-metadata-client/README.md create mode 100644 src/k8-metadata-client/src/client.rs create mode 100644 src/k8-metadata-client/src/diff.rs create mode 100644 src/k8-metadata-client/src/lib.rs create mode 100644 src/k8-metadata-client/src/nothing.rs create mode 100644 src/k8-metadata-core/Cargo.toml create mode 100644 src/k8-metadata-core/LICENSE-APACHE create mode 100644 src/k8-metadata-core/README.md create mode 100644 src/k8-metadata-core/src/crd.rs create mode 100644 src/k8-metadata-core/src/lib.rs create mode 100644 src/k8-metadata-core/src/metadata.rs create mode 100644 src/k8-metadata-core/src/options.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8a8f4236 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +**/*.rs.bk +.DS_Store +.docker-cargo +target-docker +.vscode/tasks.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..0fa15964 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1680 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "async-std" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +dependencies = [ + "async-task", + "broadcaster", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "futures-core", + "futures-io", + "futures-timer", + "kv-log-macro", + "log", + "memchr", + "mio", + "mio-uds", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "async-task" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +dependencies = [ + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "async-test-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79eaca20af2d258ad49915f9c6ed080c510afef79c005d3c8993dfdcaec6fc43" +dependencies = [ + "log", + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "async-trait" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" +dependencies = [ + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f80256bc78f67e7df7e36d77366f636ed976895d91fe2ab9efa3973e8fe8c4f" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "broadcaster" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "futures-util", + "parking_lot 0.10.0", + "slab", +] + +[[package]] +name = "bumpalo" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +dependencies = [ + "ppv-lite86", +] + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +dependencies = [ + "autocfg 0.1.7", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +dependencies = [ + "autocfg 0.1.7", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "curl" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06aa71e9208a54def20792d877bc663d6aae0732b9852e612c4a933177c31283" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi 0.3.8", +] + +[[package]] +name = "curl-sys" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c38ca47d60b86d0cc9d42caa90a0885669c2abc9791f871c81f58cdf39e979b" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "mesalink", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi 0.3.8", +] + +[[package]] +name = "data-encoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +dependencies = [ + "cfg-if", + "libc", + "redox_users", + "winapi 0.3.8", +] + +[[package]] +name = "dtoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" + +[[package]] +name = "enum_to_u8_slice_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8479a225129affae259452fd418b67af025ac86f60663a893baa407bc9897f43" +dependencies = [ + "quote 0.3.15", + "syn 0.11.11", +] + +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "error-chain" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" +dependencies = [ + "backtrace", + "version_check", +] + +[[package]] +name = "flv-future-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a968e689fd27f04505d8fdf73432b62565e377693f92acd55fede4ad125dc173" +dependencies = [ + "async-std", + "async-test-derive", + "bytes 0.5.4", + "futures", + "futures-timer", + "log", + "pin-utils", +] + +[[package]] +name = "flv-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63dfd9a699e5b73d0163b7abd4019c0e37c7293c68b822dfb12cae2ca753703d" +dependencies = [ + "chrono", + "env_logger", + "log", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda826c2f9351e68bc87b9037d91d818f24384993ecbb37f711e1f71a83182b5" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92c2137e8e1ebf1ac99453550ab46eb4f35c5c53476d57d75eb782fb4d71e84" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfb301b0b09e940a67376cf40d1b0ac4db9366ee737f65c02edea225057e91e" + +[[package]] +name = "futures-executor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f085a4c6508baef1f70ec2e0232a09edc649a3b86551fe92555e3a9e43939e4c" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff098c09c30cc42b88a67d9fb27435e8456fb8b2483c904340ed499736931c" + +[[package]] +name = "futures-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebecc4204c719ca7140a3253676f1321e6fa17b1752a94a4a436d308be293e87" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "futures-sink" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0485279d763e8a3669358f500e805339138b7bbe90f5718c80eedfdcb2ea36a4" + +[[package]] +name = "futures-task" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefffab2aacc73845afd3f202e09fc775a55e2e96f46c8b1a46c117ae1c126ca" + +[[package]] +name = "futures-timer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" + +[[package]] +name = "futures-util" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3f8c59707f898b8b6f0b54c2aef5408ae90a560b7bf0fbf1b95b3c652b0171" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +dependencies = [ + "bytes 0.4.12", + "fnv", + "itoa", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "isahc" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45d8c41e6f0b5aa495fd2577e6068e100f57eb87c4b353b0dab20bb53a56035" +dependencies = [ + "bytes 0.4.12", + "chrono", + "crossbeam-channel", + "crossbeam-utils", + "curl", + "curl-sys", + "futures-channel", + "futures-io", + "futures-util", + "http", + "lazy_static", + "log", + "serde", + "serde_json", + "slab", + "sluice", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "js-sys" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k8-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes 0.5.4", + "curl", + "flv-future-core", + "flv-util", + "futures", + "http", + "isahc", + "k8-config", + "k8-diff", + "k8-metadata-client", + "k8-metadata-core", + "log", + "openssl-sys", + "pin-utils", + "rand", + "serde", + "serde_json", + "serde_qs 0.5.2", +] + +[[package]] +name = "k8-config" +version = "0.1.0" +dependencies = [ + "dirs", + "log", + "serde", + "serde_yaml", +] + +[[package]] +name = "k8-diff" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "k8-metadata-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "flv-future-core", + "futures", + "k8-diff", + "k8-metadata-core", + "log", + "pin-utils", + "serde", + "serde_json", + "serde_qs 0.5.2", +] + +[[package]] +name = "k8-metadata-core" +version = "0.1.2" +dependencies = [ + "http", + "log", + "serde", + "serde_json", + "serde_qs 0.4.6", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +dependencies = [ + "log", +] + +[[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.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02254d44f4435dd79e695f2c2b83cd06a47919adea30216ceaf0c57ca0a72463" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" + +[[package]] +name = "lock_api" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" + +[[package]] +name = "memoffset" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "mesalink" +version = "1.1.0-cratesio" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05616fdd96cc48e233f660ce28e936950163b21f28bde25649acf55de411970a" +dependencies = [ + "base64 0.10.1", + "bitflags", + "enum_to_u8_slice_derive", + "env_logger", + "lazy_static", + "libc", + "parking_lot 0.9.0", + "ring", + "rustls", + "sct", + "untrusted", + "walkdir", + "webpki", + "webpki-roots", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num_cpus" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-src" +version = "111.6.1+1.1.1d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91b04cb43c1a8a90e934e0cd612e2a5715d976d2d6cff4490278a0cddf35005" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" +dependencies = [ + "autocfg 1.0.0", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core 0.6.2", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +dependencies = [ + "lock_api", + "parking_lot_core 0.7.0", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec 0.6.13", + "winapi 0.3.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec 1.2.0", + "winapi 0.3.8", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "pin-project-lite" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "proc-macro-hack" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" +dependencies = [ + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "proc-macro-nested" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" + +[[package]] +name = "proc-macro2" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +dependencies = [ + "unicode-xid 0.2.0", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +dependencies = [ + "c2-chacha", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "regex" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06" + +[[package]] +name = "ring" +version = "0.16.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" +dependencies = [ + "cc", + "lazy_static", + "libc", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.8", +] + +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" +dependencies = [ + "base64 0.10.1", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" +dependencies = [ + "lazy_static", + "winapi 0.3.8", +] + +[[package]] +name = "scopeguard" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "serde_json" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b01d7f0288608a01dca632cf1df859df6fd6ffa885300fc275ce2ba6221953" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35965fa1d2413717053d67c2df1f5c3e1763fbf77200ea7e767523707bd5a0af" +dependencies = [ + "data-encoding", + "error-chain", + "percent-encoding", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43eef44996bbe16e99ac720e1577eefa16f7b76b5172165c98ced20ae9903e1" +dependencies = [ + "data-encoding", + "error-chain", + "percent-encoding", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "sluice" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6428a51b12f86b02672d0aabc26141e37828d4f05dfeb3b6f45831de9f292360" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", +] + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" + +[[package]] +name = "socket2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "sourcefile" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", +] + +[[package]] +name = "syn" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +dependencies = [ + "proc-macro2", + "quote 1.0.2", + "unicode-xid 0.2.0", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid 0.0.4", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +dependencies = [ + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "untrusted" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi 0.3.8", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5205e9afdf42282b192e2310a5b463a6d1c1d774e30dc3c791ac37ab42d2616c" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574094772ce6921576fb6f2e3f7497b8a76273b6db092be18fc48a082de09dc3" +dependencies = [ + "quote 1.0.2", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668" +dependencies = [ + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e7e61fc929f4c0dddb748b102ebf9f632e2b8d739f2016542b4de2965a9601" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef012a0d93fc0432df126a8eaf547b2dce25a8ce9212e1d3cbeef5c11157975d" +dependencies = [ + "anyhow", + "heck", + "log", + "proc-macro2", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-backend", + "weedle", +] + +[[package]] +name = "web-sys" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf97caf6aa8c2b1dac90faf0db529d9d63c93846cca4911856f78a83cebf53b" +dependencies = [ + "anyhow", + "js-sys", + "sourcefile", + "wasm-bindgen", + "wasm-bindgen-webidl", +] + +[[package]] +name = "webpki" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b" +dependencies = [ + "webpki", +] + +[[package]] +name = "weedle" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +dependencies = [ + "nom", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[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-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +dependencies = [ + "winapi 0.3.8", +] + +[[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 = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..5ca8e084 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = [ + "src/k8-metadata-core", + "src/k8-metadata-client", + "src/k8-diff", + "src/k8-config", + "src/k8-client" + +] \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/k8-client/Cargo.toml b/src/k8-client/Cargo.toml new file mode 100644 index 00000000..6e84bafb --- /dev/null +++ b/src/k8-client/Cargo.toml @@ -0,0 +1,40 @@ +[package] +edition = "2018" +name = "k8-client" +version = "0.1.0" +authors = ["Fluvio Contributors "] +description = "Core Kubernetes metadata traits" +repository = "https://github.com/infinyon/k8-api" +license = "Apache-2.0" +categories = ["api-bindings","asynchronous","encoding","network-programming"] +readme = "README.md" + +[features] +k8 = [] +k8_stream = ["k8"] + + +[dependencies] +log = "0.4.8" +bytes = "0.5.3" +http = "0.1.16" +futures = { version = "0.3.1"} +isahc = { version = "0.8.1", features = ["json","static-curl"]} +curl = { version = "0.4.25", features = ["mesalink"]} +openssl-sys = { version = "0.9.43", features = ["vendored"] } +pin-utils = "0.1.0-alpha.4" +serde = { version ="1.0.103", features = ['derive'] } +serde_json = "1.0.40" +serde_qs = "0.5.0" +async-trait = "0.1.21" +k8-metadata-core = { version = "0.1.0", path = "../k8-metadata-core"} +k8-metadata-client = { version = "0.1.0", path = "../k8-metadata-client"} +flv-future-core = { version = "0.1.0" } +k8-diff = { version = "0.1.0", path = "../k8-diff"} +k8-config = { version = "0.1.0", path = "../k8-config"} + + +[dev-dependencies] +rand = "0.7.2" +flv-future-core = { version = "0.1.0", features=["fixture"]} +flv-util = { version = "0.1.0", features=["fixture"]} \ No newline at end of file diff --git a/src/k8-client/Makefile b/src/k8-client/Makefile new file mode 100644 index 00000000..79cf411f --- /dev/null +++ b/src/k8-client/Makefile @@ -0,0 +1,6 @@ + run-integration-test: + cargo test --features=k8 + +# set up cluster roles +set-anonymous: + create clusterrolebinding cluster-system-anonymous --clusterrole=cluster-admin --user=system:anonymous \ No newline at end of file diff --git a/src/k8-client/README.md b/src/k8-client/README.md new file mode 100644 index 00000000..953df0da --- /dev/null +++ b/src/k8-client/README.md @@ -0,0 +1,34 @@ +# Kubernetes Rust Client + + +This is similar to Kubernetes Go Client: https://github.com/kubernetes/client-go + +Example of using the client: + +``` +use k8_client::K8Client; +use k8_client::pod::{PodSpec,PodStatus}; + +async fn main() { + + let client = K8Client::default().expect("cluster not initialized"); + + let pods = client.retrieve_item().await.expect("pods should exist"); + + for pod in pods { + println!("pod: {:#?}",pod); + } +} + +``` + + +## License + +This project is licensed under the [Apache license](LICENSE-APACHE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Fluvio by you, shall be licensed as Apache, without any additional +terms or conditions. diff --git a/src/k8-client/k8-fixtures/Cargo.toml b/src/k8-client/k8-fixtures/Cargo.toml new file mode 100644 index 00000000..5edb6592 --- /dev/null +++ b/src/k8-client/k8-fixtures/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "k8-fixtures" +edition = "2018" +version = "0.2.1" +authors = ["fluvio.io"] + +[dependencies] +rand = "0.7.2" +serde = "1.0.76" +serde_derive = "1.0.76" +serde_json = "1.0.27" +k8-client = { path = "../../k8-client"} +k8-metadata = { path = "../../k8-metadata"} + diff --git a/src/k8-client/k8-fixtures/data/topic.json b/src/k8-client/k8-fixtures/data/topic.json new file mode 100644 index 00000000..c7a3fb0c --- /dev/null +++ b/src/k8-client/k8-fixtures/data/topic.json @@ -0,0 +1,3 @@ +"{\"apiVersion\":\"fluvio.infinyon.com/v1\",\"items\": + [{\"apiVersion\":\"fluvio.infinyon.com/v1\",\"kind\":\"Topic\",\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"fluvio.infinyon.com/v1\\\",\\\"kind\\\":\\\"Topic\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"topic1\\\",\\\"namespace\\\":\\\"test\\\"},\\\"spec\\\":{\\\"partitions\\\":1,\\\"replicationFactor\\\":1}}\\n\"},\"creationTimestamp\":\"2019-12-01T07:10:50Z\",\"generation\":1,\"name\":\"topic1\",\"namespace\":\"test\",\"resourceVersion\":\"67676\",\"selfLink\":\"/apis/fluvio.infinyon.com/v1/namespaces/test/topics/topic1\",\"uid\":\"b4763b22-1409-11ea-a548-267327c1c713\"},\"spec\":{\"partitions\":1,\"replicationFactor\":1},\"status\":{\"reason\":\"need 1 more SPU\",\"replicaMap\":{},\"resolution\":\"InsufficientResources\"}}],\"kind\":\"TopicList\",\"metadata\":{\"continue\":\"\", + \"resourceVersion\":\"67961\",\"selfLink\":\"/apis/fluvio.infinyon.com/v1/namespaces/test/topics\"}}\n" \ No newline at end of file diff --git a/src/k8-client/k8-fixtures/src/lib.rs b/src/k8-client/k8-fixtures/src/lib.rs new file mode 100644 index 00000000..2a2e018e --- /dev/null +++ b/src/k8-client/k8-fixtures/src/lib.rs @@ -0,0 +1,5 @@ +mod test_fixtures; + +pub use self::test_fixtures::create_topic_stream_result; +pub use self::test_fixtures::TestTopicWatch; +pub use self::test_fixtures::TestTopicWatchList; diff --git a/src/k8-client/k8-fixtures/src/test_fixtures.rs b/src/k8-client/k8-fixtures/src/test_fixtures.rs new file mode 100644 index 00000000..4621304c --- /dev/null +++ b/src/k8-client/k8-fixtures/src/test_fixtures.rs @@ -0,0 +1,90 @@ +use rand::prelude::*; +use std::env; +use std::ffi::OsStr; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use k8_metadata_core::metadata::K8Watch; +use k8_metadata::client::TokenStreamResult; +use k8_metadata::topic::{TopicSpec, TopicStatus}; +use k8_client::ClientError; + + +// +// Topic Watch Fixtures +// + +pub type TestTopicWatchList = Vec; + +pub struct TestTopicWatch { + pub operation: String, + pub name: String, + pub partitions: i32, + pub replication: i32, + pub ignore_rack_assignment: Option, +} + +pub fn create_topic_watch(ttw: &TestTopicWatch) -> K8Watch { + let target_dir = get_target_dir(); + let path = get_top_dir(&target_dir); + let mut contents = String::new(); + let (filename, file_has_options) = if ttw.ignore_rack_assignment.is_none() { + ( + String::from("k8-client/k8-fixtures/data/topic_no_options.tmpl"), + false, + ) + } else { + ( + String::from("k8-client/k8-fixtures/data/topic_all.tmpl"), + true, + ) + }; + let f = File::open(path.join(filename)); + f.unwrap().read_to_string(&mut contents).unwrap(); + + contents = contents.replace("{type}", &*ttw.operation); + contents = contents.replace("{name}", &*ttw.name); + contents = contents.replace("{partitions}", &*ttw.partitions.to_string()); + contents = contents.replace("{replication}", &*ttw.replication.to_string()); + contents = contents.replace( + "{12_digit_rand}", + &*format!("{:012}", thread_rng().gen_range(0, 999999)), + ); + if file_has_options { + contents = contents.replace( + "{rack_assignment}", + &*ttw.ignore_rack_assignment.unwrap().to_string(), + ); + } + serde_json::from_str(&contents).unwrap() +} + +pub fn create_topic_stream_result( + ttw_list: &TestTopicWatchList, +) -> TokenStreamResult { + let mut topic_watch_list = vec![]; + for ttw in ttw_list { + topic_watch_list.push(Ok(create_topic_watch(&ttw))); + } + Ok(topic_watch_list) +} + +// +// Utility APIs +// + +// Get absolute path to the "target" directory ("build" dir) +fn get_target_dir() -> PathBuf { + let bin = env::current_exe().expect("exe path"); + let mut target_dir = PathBuf::from(bin.parent().expect("bin parent")); + while target_dir.file_name() != Some(OsStr::new("target")) { + target_dir.pop(); + } + target_dir +} + +// Get absolute path to the project's top dir, given target dir +fn get_top_dir<'a>(target_dir: &'a Path) -> &'a Path { + target_dir.parent().expect("target parent") +} diff --git a/src/k8-client/src/client.rs b/src/k8-client/src/client.rs new file mode 100644 index 00000000..c321ba3b --- /dev/null +++ b/src/k8-client/src/client.rs @@ -0,0 +1,379 @@ +use std::fmt::Debug; +use std::fmt::Display; + +use log::debug; +use log::error; +use log::trace; +use futures::future::FutureExt; +use futures::stream::Stream; +use futures::stream::StreamExt; +use futures::stream::BoxStream; +use async_trait::async_trait; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json; +use serde_json::Value; +use http::Uri; +use http::status::StatusCode; +use http::header::ACCEPT; +use http::header::CONTENT_TYPE; +use http::header::AUTHORIZATION; +use http::header::HeaderValue; +use isahc::prelude::*; +use isahc::HttpClient; + +use k8_metadata_core::Spec; +use k8_metadata_core::metadata::item_uri; +use k8_metadata_core::metadata::K8Meta; +use k8_metadata_core::metadata::items_uri; +use k8_metadata_core::metadata::InputK8Obj; +use k8_metadata_core::metadata::UpdateK8ObjStatus; +use k8_metadata_core::metadata::K8List; +use k8_metadata_core::metadata::K8Obj; +use k8_metadata_core::metadata::K8Status; +use k8_metadata_core::metadata::K8Watch; +use k8_metadata_core::options::ListOptions; +use k8_metadata_client::PatchMergeType; +use k8_metadata_client::MetadataClient; +use k8_metadata_client::TokenStreamResult; + +use k8_config::K8Config; + +use crate::stream::BodyStream; +use crate::ClientError; +use crate::K8HttpClientBuilder; + +// For error mapping: see: https://doc.rust-lang.org/nightly/core/convert/trait.From.html + + + + + + + +/// K8 Cluster accessible thru API +#[derive(Debug)] +pub struct K8Client { + client: HttpClient, + host: String, + token: Option +} + + + +impl K8Client { + + + // load using default k8 config + pub fn default() -> Result { + let config = K8Config::load()?; + Self::new(config) + } + + pub fn new(config: K8Config) -> Result { + + + let helper = K8HttpClientBuilder::new(config); + let client = helper.build()?; + let host = helper.config().api_path().to_owned(); + let token = helper.token(); + debug!("using k8 token: {:#?}",token); + Ok( + Self { + client, + host, + token + } + ) + } + + + fn hostname(&self) -> &str { + &self.host + } + + fn finish_request(&self,request: &mut Request) -> Result<(),ClientError> + where B: Into + { + if let Some(ref token) = self.token { + let full_token = format!("Bearer {}",token); + request.headers_mut().insert(AUTHORIZATION,HeaderValue::from_str(&full_token)?); + } + Ok(()) + } + + /// handle request. this is async function + async fn handle_request( + &self, + mut request: Request + ) -> Result + where + T: DeserializeOwned, + B: Into + { + self.finish_request(&mut request)?; + + let mut resp = self.client.send_async(request).await?; + + let status = resp.status(); + debug!("response status: {:#?}", status); + + if status == StatusCode::NOT_FOUND { + return Err(ClientError::NotFound); + } + + /* + let text = resp.text()?; + trace!("text: {}",text); + + serde_json::from_str(&text).map_err(|err| err.into()) + */ + resp.json().map_err(|err| err.into()) + } + + + /// return stream of chunks, chunk is a bytes that are stream thru http channel + fn stream_of_chunks(&self,uri: Uri) -> impl Stream< Item = Vec> + '_ + where + K8Watch: DeserializeOwned, + S: Spec + Debug, + S::Status: Debug, + { + debug!("streaming: {}", uri); + + + let ft = async move { + + let mut request = match http::Request::get(uri).body(Body::empty()) { + Ok(req) => req, + Err(err) => { + error!("error uri err: {}",err); + return BodyStream::empty(); + } + }; + + if let Err(err) = self.finish_request(&mut request) { + error!("error finish request: {}",err); + return BodyStream::empty() + }; + + match self.client.send_async(request).await { + Ok(response) => { + trace!("res status: {}", response.status()); + trace!("res header: {:#?}", response.headers()); + BodyStream::new(response.into_body()) + }, + Err(err) => { + error!("error getting streaming: {}",err); + BodyStream::empty() + } + } + + }; + + ft.flatten_stream() + } + + + fn stream(&self, uri: Uri) -> impl Stream> + '_ + where + K8Watch: DeserializeOwned, + S: Spec + Debug + 'static, + S::Status: Debug + { + + self.stream_of_chunks(uri).map(|chunk| { + + trace!("decoding raw stream : {}", String::from_utf8_lossy(&chunk).to_string()); + + let result: Result, serde_json::Error> = + serde_json::from_slice(&chunk).map_err(|err| { + error!("parsing error: {}", err); + error!("error raw stream {}", String::from_utf8_lossy(&chunk).to_string()); + err + }); + Ok(vec![match result { + Ok(obj) => { + trace!("de serialized: {:#?}", obj); + Ok(obj) + } + Err(err) => Err(err.into()), + }]) + }) + } + +} + + + + +#[async_trait] +impl MetadataClient for K8Client { + + type MetadataClientError = ClientError; + + /// retrieval a single item + async fn retrieve_item( + &self, + metadata: &M + ) -> Result, ClientError> + where + K8Obj: DeserializeOwned, + S: Spec, + M: K8Meta + Send + Sync + { + let uri = metadata.item_uri(self.hostname()); + debug!("retrieving item: {}", uri); + + self.handle_request(http::Request::get(uri).body(Body::empty())?).await + } + + async fn retrieve_items( + &self, + namespace: &str, + ) -> Result, ClientError> + where + K8List: DeserializeOwned, + S: Spec, + { + let uri = items_uri::(self.hostname(), namespace, None); + + debug!("retrieving items: {}", uri); + + self.handle_request(http::Request::get(uri).body(Body::empty())?).await + } + + async fn delete_item( + &self, + metadata: &M, + ) -> Result + where + S: Spec, + M: K8Meta + Send + Sync + { + let uri = metadata.item_uri(self.hostname()); + debug!("delete item on url: {}", uri); + + self.handle_request(http::Request::delete(uri).body(Body::empty())?).await + } + + /// create new object + async fn create_item( + &self, + value: InputK8Obj + ) -> Result, ClientError> + where + InputK8Obj: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send, + { + let uri = items_uri::(self.hostname(), &value.metadata.namespace, None); + debug!("creating '{}'", uri); + trace!("creating RUST {:#?}", &value); + + + let bytes = serde_json::to_vec(&value)?; + + trace!( + "create raw: {}", + String::from_utf8_lossy(&bytes).to_string() + ); + + let request = Request::post(uri) + .header(CONTENT_TYPE,"application/json") + .body(bytes)?; + + self.handle_request(request).await + } + + /// update status + async fn update_status( + &self, + value: &UpdateK8ObjStatus, + ) -> Result, ClientError> + where + UpdateK8ObjStatus: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send + Sync, + S::Status: Send + Sync + { + let uri = item_uri::( + self.hostname(), + &value.metadata.name, + &value.metadata.namespace, + Some("/status"), + ); + debug!("updating '{}' status - uri: {}", value.metadata.name, uri); + trace!("update: {:#?}", &value); + + + let bytes = serde_json::to_vec(&value)?; + trace!( + "update raw: {}", + String::from_utf8_lossy(&bytes).to_string() + ); + + let request = Request::put(uri) + .header(CONTENT_TYPE,"application/json") + .body(bytes)?; + + self.handle_request(request).await + } + + /// patch existing with spec + async fn patch_spec( + &self, + metadata: &M, + patch: &Value, + ) -> Result, ClientError> + where + K8Obj: DeserializeOwned, + S: Spec + Debug, + M: K8Meta + Display + Send + Sync + { + debug!("patching item at '{}'", metadata); + trace!("patch json value: {:#?}", patch); + let uri = metadata.item_uri(self.hostname()); + let merge_type = PatchMergeType::for_spec(S::metadata()); + + + let bytes = serde_json::to_vec(&patch)?; + + trace!("patch raw: {}", String::from_utf8_lossy(&bytes).to_string()); + + let request = Request::patch(uri) + .header(ACCEPT,"application/json") + .header( + CONTENT_TYPE, + merge_type.content_type(), + ) + .body(bytes)?; + + self.handle_request(request).await + } + + + + /// stream items since resource versions + fn watch_stream_since( + &self, + namespace: &str, + resource_version: Option, + ) -> BoxStream<'_,TokenStreamResult> + where + K8Watch: DeserializeOwned, + S: Spec + Debug + 'static, + S::Status: Debug + { + + let opt = ListOptions { + watch: Some(true), + resource_version, + timeout_seconds: Some(3600), + ..Default::default() + }; + let uri = items_uri::(self.hostname(), namespace, Some(&opt)); + self.stream(uri).boxed() + } + +} diff --git a/src/k8-client/src/config.rs b/src/k8-client/src/config.rs new file mode 100644 index 00000000..58b9ba45 --- /dev/null +++ b/src/k8-client/src/config.rs @@ -0,0 +1,121 @@ + +use std::io::Error as IoError; +use std::io::ErrorKind; +use std::path::Path; + + +use log::debug; +use log::trace; +use isahc::HttpClient; +use isahc::HttpClientBuilder; +use isahc::config::ClientCertificate; +use isahc::config::PrivateKey; +use isahc::config::CaCertificate; + +use k8_config::K8Config; +use k8_config::PodConfig; +use k8_config::KubeConfig; + +use crate::ClientError; + +/// load client certificate +fn load_client_certificate

(client_crt_path: P,client_key_path: P) -> ClientCertificate + where P: AsRef +{ + ClientCertificate::pem_file( + client_crt_path.as_ref().to_owned(), + PrivateKey::pem_file(client_key_path.as_ref().to_owned(), String::from("")), + ) +} + +fn load_ca_certificate

(ca_path: P) -> CaCertificate + where P: AsRef +{ + CaCertificate::file(ca_path.as_ref().to_owned()) +} + + + + +/// Build and configure HTTP connection for K8 +#[derive(Debug)] +pub struct K8HttpClientBuilder(K8Config); + +impl K8HttpClientBuilder { + + pub fn new(config: K8Config) -> Self { + Self(config) + } + + pub fn config(&self) -> &K8Config { + &self.0 + } + + /// build HttpClient, for now we use isahc + pub fn build(&self) -> Result { + + let builder = HttpClient::builder(); + + let builder = match &self.0 { + K8Config::Pod(pod_config) => configure_in_cluster(pod_config,builder), + K8Config::KubeConfig(kube_config) => configure_out_of_cluster(&kube_config.config,builder), + }?; + + builder.build().map_err(|err| err.into()) + } + + /// get any additional auth token that is required + pub fn token(&self) -> Option { + match &self.0 { + K8Config::Pod(pod) => Some(pod.token.to_owned()), + _ => None + } + } +} + +/// Configure builder if we are out of cluster, +/// in this case, we need to use kubeconfig to get certificate locations +fn configure_out_of_cluster(kube_config: &KubeConfig, builder: HttpClientBuilder) -> Result { + + let current_user = kube_config.current_user().ok_or_else( + || IoError::new(ErrorKind::InvalidInput,"config must have current user".to_owned()))?; + + // get certs for client-certificate + let client_crt_path = current_user.user.client_certificate.as_ref().ok_or_else( + || IoError::new(ErrorKind::InvalidInput,"no client cert crt path founded".to_owned()))?; + + let client_key_path = ¤t_user.user.client_key.as_ref().ok_or_else( + || IoError::new(ErrorKind::InvalidInput,"no client cert key founded".to_owned()))?; + + trace!("loading client crt: {} and client key: {}",client_crt_path,client_key_path); + + let client_certificate = load_client_certificate(client_crt_path,client_key_path); + + debug!("retrieved client certs from kubeconfig"); + let builder = builder.ssl_client_certificate(client_certificate); + + let current_cluster = kube_config.current_cluster().ok_or_else( + || IoError::new(ErrorKind::InvalidInput,"config must have current cluster".to_owned()))?; + + let ca_certificate_path = current_cluster.cluster.certificate_authority.as_ref().ok_or_else( + || IoError::new(ErrorKind::InvalidInput,"current cluster must have CA crt path".to_owned()))?; + + let ca_certificate = load_ca_certificate(ca_certificate_path); + + Ok(builder.ssl_ca_certificate(ca_certificate)) +} + + +/// Configure builder if it is in cluster +/// we need to get token and configure client +fn configure_in_cluster(pod: &PodConfig,builder: HttpClientBuilder) -> Result { + + + let ca_certificate = load_ca_certificate(pod.ca_path()); + + debug!("retrieve ca.crt"); + Ok(builder.ssl_ca_certificate(ca_certificate)) +} + + + diff --git a/src/k8-client/src/config_map.rs b/src/k8-client/src/config_map.rs new file mode 100644 index 00000000..0ba152a8 --- /dev/null +++ b/src/k8-client/src/config_map.rs @@ -0,0 +1,39 @@ +use serde::Deserialize; +use serde::Serialize; + +use k8_metadata_core::Crd; +use k8_metadata_core::CrdNames; +use k8_metadata_core::Spec; +use k8_metadata_core::Status; + + +// +// ConfigMap Object +const CONFIG_MAP_API: Crd = Crd { + group: "core", + version: "v1", + names: CrdNames { + kind: "ConfigMap", + plural: "configmaps", + singular: "configmap", + }, +}; + +impl Spec for ConfigMapSpec { + + type Status = ConfigMapStatus; + + fn metadata() -> &'static Crd { + &CONFIG_MAP_API + } +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ConfigMapSpec {} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ConfigMapStatus {} + +impl Status for ConfigMapStatus{} \ No newline at end of file diff --git a/src/k8-client/src/error.rs b/src/k8-client/src/error.rs new file mode 100644 index 00000000..d30a9d1a --- /dev/null +++ b/src/k8-client/src/error.rs @@ -0,0 +1,109 @@ +use std::io::Error as IoError; +use std::env; +use std::fmt; + +use http; +use http::header::InvalidHeaderValue; +use isahc::Error as HttpError; + +use k8_diff::DiffError; +use k8_config::ConfigError; + +use k8_metadata_client::MetadataClientError; + +// For error mapping: see: https://doc.rust-lang.org/nightly/core/convert/trait.From.html + +#[derive(Debug)] +pub enum ClientError { + IoError(IoError), + HttpError(http::Error), + InvalidHttpHeader(InvalidHeaderValue), + EnvError(env::VarError), + JsonError(serde_json::Error), + DiffError(DiffError), + HttpClientError(HttpError), + K8ConfigError(ConfigError), + PatchError, + NotFound, +} + +impl From for ClientError { + fn from(error: InvalidHeaderValue) -> Self { + Self::InvalidHttpHeader(error) + } +} + +impl From for ClientError { + fn from(error: IoError) -> Self { + Self::IoError(error) + } +} + +impl From for ClientError { + fn from(error: http::Error) -> Self { + Self::HttpError(error) + } +} + +impl From for ClientError { + fn from(error: env::VarError) -> Self { + Self::EnvError(error) + } +} + + +impl From for ClientError { + fn from(error: serde_json::Error) -> Self { + Self::JsonError(error) + } +} + +impl From for ClientError { + fn from(error: DiffError) -> Self { + Self::DiffError(error) + } +} + +impl From for ClientError { + fn from(error: HttpError) -> Self { + Self::HttpClientError(error) + } +} + +impl From for ClientError { + fn from(error: ConfigError) -> Self { + Self::K8ConfigError(error) + } +} + +impl fmt::Display for ClientError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::IoError(err) => write!(f, "{}", err), + Self::HttpError(err) => write!(f, "{}", err), + Self::EnvError(err) => write!(f, "{}", err), + Self::JsonError(err) => write!(f, "{}", err), + Self::NotFound => write!(f, "not found"), + Self::DiffError(err) => write!(f, "{:#?}", err), + Self::PatchError => write!(f, "patch error"), + Self::HttpClientError(err) => write!(f,"{}",err), + Self::K8ConfigError(err) => write!(f,"{}",err), + Self::InvalidHttpHeader(err) => write!(f,"{}",err) + } + } +} + +impl MetadataClientError for ClientError { + + fn patch_error() -> Self { + Self::PatchError + } + + fn not_founded(&self) -> bool { + match self { + Self::NotFound => true, + _ => false + } + } + +} \ No newline at end of file diff --git a/src/k8-client/src/fixture.rs b/src/k8-client/src/fixture.rs new file mode 100644 index 00000000..8f35d597 --- /dev/null +++ b/src/k8-client/src/fixture.rs @@ -0,0 +1,3 @@ +/// common test fixtures + +pub const TEST_NS: &'static str = "test"; \ No newline at end of file diff --git a/src/k8-client/src/lib.rs b/src/k8-client/src/lib.rs new file mode 100644 index 00000000..be8262c5 --- /dev/null +++ b/src/k8-client/src/lib.rs @@ -0,0 +1,20 @@ +mod client; +mod error; +//mod wstream; +mod config; +mod stream; + +pub mod config_map; +pub mod secret; +pub mod pod; +pub mod service; +pub mod stateful; + +#[cfg(feature = "k8")] +pub mod fixture; + + +pub use self::client::K8Client; +pub use self::config::K8HttpClientBuilder; +pub use self::error::ClientError; + diff --git a/src/k8-client/src/pod.rs b/src/k8-client/src/pod.rs new file mode 100644 index 00000000..80b340af --- /dev/null +++ b/src/k8-client/src/pod.rs @@ -0,0 +1,203 @@ +use serde::Deserialize; +use serde::Serialize; + +use k8_metadata_core::metadata::Env; +use k8_metadata_core::Crd; +use k8_metadata_core::CrdNames; +use k8_metadata_core::Spec; +use k8_metadata_core::Status; + + +// +// Pod Object + +const POD_API: Crd = Crd { + group: "core", + version: "v1", + names: CrdNames { + kind: "Pod", + plural: "pods", + singular: "pod", + }, +}; + +impl Spec for PodSpec { + + type Status = PodStatus; + + fn metadata() -> &'static Crd { + &POD_API + } +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PodSpec { + pub volumes: Option>, + pub containers: Vec, + pub restart_policy: Option, // TODO; should be enum + pub service_account_name: Option, + pub service_account: Option, + pub node_name: Option, + pub termination_grace_period_seconds: Option, + pub dns_policy: Option, + pub security_context: Option, + pub scheduler_name: Option +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PodSecurityContext { + pub fs_group: Option, + pub run_as_group: Option, + pub run_as_non_root: Option, + pub run_as_user: Option +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContainerSpec { + pub name: String, + pub ports: Vec, + pub image: Option, + pub image_pull_policy: Option, // TODO: should be enum + pub volume_mounts: Vec, + pub env: Option>, + pub resource: Option, + pub termination_mssage_path: Option, + pub termination_message_policy: Option, + pub tty: Option +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ResourceRequirements { + pub api_groups: Vec, + pub resource_names: Vec, + pub resources: Vec, + pub verbs: Vec +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContainerPortSpec { + pub container_port: u16, + pub name: Option, + pub protocol: Option, // TODO: This should be enum +} + +impl ContainerPortSpec { + pub fn new>(container_port: u16, name: T) -> Self { + ContainerPortSpec { + container_port, + name: Some(name.into()), + protocol: None, + } + } +} + + + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +pub struct VolumeSpec { + pub name: String, + pub secret: Option, + pub persistent_volume_claim: Option, +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct VolumeMount { + pub mount_path: String, + pub mount_propagation: Option, + pub name: String, + pub read_only: Option, + pub sub_path: Option, +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SecretVolumeSpec { + pub default_mode: u16, + pub secret_name: String, + pub optional: Option, +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PersistentVolumeClaimVolumeSource { + claim_name: String, + read_only: bool, +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PodStatus { + pub phase: String, + #[serde(rename = "hostIP")] + pub host_ip: String, + #[serde(rename = "podIP")] + pub pod_ip: String, + pub start_time: String, + pub container_statuses: Vec, +} + +impl Status for PodStatus{} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContainerStatus { + pub name: String, + pub state: ContainerState, + pub ready: bool, + pub restart_count: u8, + pub image: String, + #[serde(rename = "imageID")] + pub image_id: String, + #[serde(rename = "containerID")] + pub container_id: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContainerState { + pub running: ContainerStateRunning, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContainerStateRunning { + pub started_at: String, +} + +// +// Test Cases +// +#[cfg(test)] +mod test { + + use k8_metadata_core::metadata::item_uri; + use k8_metadata_core::metadata::items_uri; + use k8_metadata_core::metadata::DEFAULT_NS; + use crate::pod::PodSpec; + + #[test] + fn test_pod_item_uri() { + let uri = item_uri::("https://localhost", "test", DEFAULT_NS, None); + assert_eq!( + uri, + "https://localhost/api/v1/namespaces/default/pods/test" + ); + } + + #[test] + fn test_pod_items_uri() { + let uri = items_uri::("https://localhost", DEFAULT_NS, None); + assert_eq!( + uri, + "https://localhost/api/v1/namespaces/default/pods" + ); + } + + +} diff --git a/src/k8-client/src/secret.rs b/src/k8-client/src/secret.rs new file mode 100644 index 00000000..fc3466f6 --- /dev/null +++ b/src/k8-client/src/secret.rs @@ -0,0 +1,38 @@ +use serde::Deserialize; +use serde::Serialize; + +use k8_metadata_core::Crd; +use k8_metadata_core::CrdNames; +use k8_metadata_core::Spec; +use k8_metadata_core::Status; + +// +// Secret Object +const SECRET_API: Crd = Crd { + group: "core", + version: "v1", + names: CrdNames { + kind: "Secret", + plural: "secrets", + singular: "secret", + }, +}; + +impl Spec for SecretSpec { + + type Status = SecretStatus; + + fn metadata() -> &'static Crd { + &SECRET_API + } +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SecretSpec {} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SecretStatus {} + +impl Status for SecretStatus{} diff --git a/src/k8-client/src/service.rs b/src/k8-client/src/service.rs new file mode 100644 index 00000000..ed13ed70 --- /dev/null +++ b/src/k8-client/src/service.rs @@ -0,0 +1,103 @@ +use serde::Deserialize; +use serde::Serialize; +use std::collections::HashMap; + +use k8_metadata_core::Crd; +use k8_metadata_core::CrdNames; +use k8_metadata_core::Spec; +use k8_metadata_core::Status; + + + +const SERVICE_API: Crd = Crd { + group: "core", + version: "v1", + names: CrdNames { + kind: "Service", + plural: "services", + singular: "service", + }, +}; + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ServiceSpec { + #[serde(rename = "clusterIP")] + pub cluster_ip: String, + #[serde(rename = "externalIPs")] + pub external_ips: Option>, + #[serde(rename = "loadBalancerIP")] + pub load_balancer_ip: Option, + pub r#type: Option, + pub external_name: Option, + pub external_traffic_policy: Option, + pub ports: Vec, + pub selector: Option>, +} + +impl Spec for ServiceSpec { + + type Status = ServiceStatus; + + fn metadata() -> &'static Crd { + &SERVICE_API + } + + + fn make_same(&mut self,other: &Self) { + if other.cluster_ip == "" { + self.cluster_ip = "".to_owned(); + } + } + +} + + + + + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ServicePort { + pub name: Option, + pub node_port: Option, + pub port: u16, + pub target_port: Option, +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase",default)] +pub struct ServiceStatus { + pub load_balancer: LoadBalancerStatus +} + +impl Status for ServiceStatus{} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub enum ExternalTrafficPolicy { + Local, + Cluster +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub enum LoadBalancerType { + ExternalName, + ClusterIP, + NodePort, + LoadBalancer +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase",default)] +pub struct LoadBalancerStatus { + pub ingress: Vec +} + + + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct LoadBalancerIngress { + pub hostname: Option, + pub ip: Option +} \ No newline at end of file diff --git a/src/k8-client/src/stateful.rs b/src/k8-client/src/stateful.rs new file mode 100644 index 00000000..c1437eba --- /dev/null +++ b/src/k8-client/src/stateful.rs @@ -0,0 +1,209 @@ +use serde::Deserialize; +use serde::Serialize; + +use k8_metadata_core::metadata::LabelSelector; +use k8_metadata_core::metadata::TemplateSpec; + +use crate::pod::PodSpec; + + +use k8_metadata_core::Crd; +use k8_metadata_core::CrdNames; +use k8_metadata_core::Spec; +use k8_metadata_core::Status; + + +const STATEFUL_API: Crd = Crd { + group: "apps", + version: "v1", + names: CrdNames { + kind: "StatefulSet", + plural: "statefulsets", + singular: "statefulset", + }, +}; + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct StatefulSetSpec { + pub pod_management_policy: Option, + pub replicas: Option, + pub revision_history_limit: Option, + pub selector: LabelSelector, + pub service_name: String, + pub template: TemplateSpec, + pub volume_claim_templates: Vec>, + pub update_strategy: Option +} + +impl Spec for StatefulSetSpec { + + type Status = StatefulSetStatus; + + fn metadata() -> &'static Crd { + &STATEFUL_API + } +} + + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct StatefulSetUpdateStrategy { + pub _type: String, + pub rolling_ipdate: Option + +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RollingUpdateStatefulSetStrategy { + partition: u32 +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub enum PodMangementPolicy { + OrderedReady, + Parallel, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PersistentVolumeClaim { + pub access_modes: Vec, + pub storage_class_name: String, + pub resources: ResourceRequirements, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub enum VolumeAccessMode { + ReadWriteOnce, + ReadWrite, + ReadOnlyMany, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct ResourceRequirements { + pub requests: VolumeRequest, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct VolumeRequest { + pub storage: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct StatefulSetStatus { + pub replicas: u16, + pub collision_count: Option, + pub conditions: Option>, + pub current_replicas: Option, + pub current_revision: Option, + pub observed_generation: Option, + pub ready_replicas: Option, + pub update_revision: Option, + pub updated_replicas: Option, +} + +impl Status for StatefulSetStatus{} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub enum StatusEnum { + True, + False, + Unknown, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct StatefulSetCondition { + pub message: String, + pub reason: StatusEnum, + pub _type: String, +} + + +/* +#[cfg(test)] +mod test { + + use serde_json; + use serde_json::json; + + use super::LabelSelector; + use super::StatefulSetSpec; + use k8_diff::Changes; + use k8_metadata::cluster::ClusterSpec; + use k8_metadata::cluster::Configuration; + use k8_metadata::cluster::Cluster; + use k8_metadata::cluster::ClusterEndpoint; + + #[test] + fn test_label_selector() { + let selector = LabelSelector::new_labels(vec![("app".to_owned(), "test".to_owned())]); + + let maps = selector.match_labels; + assert_eq!(maps.len(), 1); + assert_eq!(maps.get("app").unwrap(), "test"); + } + + #[test] + fn test_cluster_to_stateful() { + let cluster = ClusterSpec { + cluster: Cluster { + replicas: Some(3), + rack: Some("rack1".to_string()), + public_endpoint: Some(ClusterEndpoint::new(9005)), + private_endpoint: Some(ClusterEndpoint::new(9006)), + controller_endpoint: Some(ClusterEndpoint::new(9004)), + }, + configuration: Some(Configuration::default()), + env: None, + }; + + let stateful: StatefulSetSpec = (&cluster).into(); + assert_eq!(stateful.replicas, Some(3)); + let mut stateful2 = stateful.clone(); + stateful2.replicas = Some(2); + + let state1_json = serde_json::to_value(stateful).expect("json"); + let state2_json = serde_json::to_value(stateful2).expect("json"); + let diff = state1_json.diff(&state2_json).expect("diff"); + let json_diff = serde_json::to_value(diff).unwrap(); + assert_eq!( + json_diff, + json!({ + "replicas": 2 + }) + ); + } + + + /* + * TODO: make this as utility + use std::io::Read; + use std::fs::File; + use k8_metadata_core::metadata::ObjectMeta; + use k8_metadata_core::metadata::K8Obj; + use super::StatefulSetStatus; + use super::TemplateSpec; + use super::PodSpec; + use super::ContainerSpec; + use super::ContainerPortSpec; + + #[test] + fn test_decode_statefulset() { + let file_name = "/private/tmp/f1.json"; + + let mut f = File::open(file_name).expect("open failed"); + let mut contents = String::new(); + f.read_to_string(&mut contents).expect("read file"); + // let st: StatefulSetSpec = serde_json::from_slice(&buffer).expect("error"); + let st: K8Obj = serde_json::from_str(&contents).expect("error"); + println!("st: {:#?}",st); + assert!(true); + } + */ + +} +*/ \ No newline at end of file diff --git a/src/k8-client/src/stream.rs b/src/k8-client/src/stream.rs new file mode 100644 index 00000000..17527b40 --- /dev/null +++ b/src/k8-client/src/stream.rs @@ -0,0 +1,133 @@ +use std::pin::Pin; +use std::marker::Unpin; +use std::task::Context; +use std::task::Poll; + +use log::trace; +use futures::stream::Stream; +use futures::io::AsyncRead; +use isahc::Body; +use pin_utils::unsafe_pinned; + + + +const BUFFER_SIZE: usize = 1000; + +pub struct BodyStream { + body: Body, + done: bool +} + + +impl Unpin for BodyStream{} + +impl BodyStream { + + unsafe_pinned!(body: Body); + + pub fn new(body: Body) -> Self { + Self { + body, + done: false + } + } + + pub fn empty() -> Self { + Self { + body: Body::empty(), + done: true + } + } +} + + +impl Stream for BodyStream { + + type Item = Vec; + + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + + if self.done { + return Poll::Ready(None); + } + + // we maintain two buffer. outer is accumulate buffer + // buf is temporary buffer. this allow us to read unlimited size of chunks + // after each polling, we move temp buffer into accumulated buffer + let mut outer: Option> = None; + let mut total_bytes: usize = 0; + loop { + let mut buf = vec![0;BUFFER_SIZE]; + match self.as_mut().body().poll_read(cx,&mut buf) { + Poll::Pending => { + if total_bytes == 0 { + trace!("no prior read, pending, returning pending"); + return Poll::Pending + } else { + trace!("encountered pending but prior bytes: {}, returning as next item",total_bytes); + return match outer { + Some(buf) => Poll::Ready(Some(buf)), + None => Poll::Pending // should not reach here!! + } + } + }, + Poll::Ready(read_result) => { + match read_result { + Err(err) => { + trace!("error reading: {}",err); + return Poll::Ready(None) + }, + Ok(bytes_read) => { + buf.split_off(bytes_read); // discard unread bytes + total_bytes += bytes_read; + trace!("bytes read: {}",bytes_read); + if bytes_read == 0 { + self.done = true; + if total_bytes == 0 { + trace!("end reached but no accumulated bytes"); + return Poll::Ready(None) + } else { + trace!("end reached,but we have accumulated bytes"); + trace!("total bytes accumulated: {}",total_bytes); + return match outer { + Some(outer_buf) => Poll::Ready(Some(outer_buf)), + None => Poll::Ready(Some(buf)) + } + } + + } else { + // if bytes read exactly same as buffer size, then we may more bytes to read + if bytes_read == BUFFER_SIZE { + trace!("bytes read is same as buffer size,may have more bytes. looping"); + match outer.as_mut() { + Some(outer_buf) => { + outer_buf.append(&mut buf); + }, + None => { + outer.replace(buf); + } + } + } else { + trace!("no more bytes, total bytes: {}",total_bytes); + return match outer { + Some(mut outer_buf) => { + outer_buf.append(&mut buf); + Poll::Ready(Some(outer_buf)) + }, + None => Poll::Ready(Some(buf)) + } + } + + } + } + } + } + } + } + + } + +} + + diff --git a/src/k8-client/src/wstream.rs b/src/k8-client/src/wstream.rs new file mode 100644 index 00000000..fbb839ad --- /dev/null +++ b/src/k8-client/src/wstream.rs @@ -0,0 +1,137 @@ + +use std::pin::Pin; +use std::marker::Unpin; +use std::task::Context; +use std::task::Poll; + +use bytes::Bytes; +use futures::stream::Stream; + +use log::error; +use log::trace; +use pin_utils::unsafe_pinned; +use pin_utils::unsafe_unpinned; +use std::mem; + +//type ChunkList = Vec, HyperError>>; + +pub struct WatchStream +where + S: Stream, +{ + stream: S, + last_buffer: Vec, + chunks: ChunkList, +} + +impl Unpin for WatchStream where S: Stream {} + +impl WatchStream +where + S: Stream>, +{ + unsafe_pinned!(stream: S); + unsafe_unpinned!(last_buffer: Vec); + unsafe_unpinned!(chunks: ChunkList); + + pub fn new(stream: S) -> Self { + WatchStream { + stream, + last_buffer: Vec::new(), + chunks: Vec::new(), + } + } +} + +const SEPARATOR: u8 = b'\n'; + +impl Stream for WatchStream +where + S: Stream>, +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let mut chunks = mem::replace(self.as_mut().chunks(), Vec::new()); + let last_buffer = mem::replace(self.as_mut().last_buffer(), Vec::new()); + let mut buf: Bytes = last_buffer.into(); + trace!( + "entering poll next with prev buf: {}, chunk: {}", + buf.len(), + chunks.len() + ); + + loop { + trace!( + "polling chunk with prev buf len: {}, chunk: {}", + buf.len(), + chunks.len() + ); + let poll_result = self.as_mut().stream().poll_next(cx); + match poll_result { + Poll::Pending => { + trace!( + "stream is pending. returning pending. saving buf len: {}", + buf.len() + ); + if buf.len() > 0 { + mem::replace(self.as_mut().last_buffer(), buf.to_vec()); + } + if chunks.len() > 0 { + mem::replace(self.as_mut().chunks(), chunks); + } + return Poll::Pending; + } + Poll::Ready(next_item) => match next_item { + None => { + trace!("none from stream. ready to return items"); + return Poll::Ready(None); + } + Some(chunk_result) => match chunk_result { + Ok(chunk) => { + trace!("chunk: {}", String::from_utf8_lossy(&chunk).to_string()); + buf.extend_from_slice(&chunk); + trace!( + "parsing chunk with len: {} accum buffer: {}", + chunk.len(), + buf.len() + ); + loop { + if let Some(i) = buf.iter().position(|&c| c == SEPARATOR) { + trace!("found separator at: {}", i); + let head = buf.slice(0, i); + buf = buf.slice(i + 1, buf.len()); + + chunks.push(Ok(head.to_vec())); + } else { + trace!("no separator found"); + break; + } + } + if buf.len() > 0 { + trace!( + "remainder chars count: {}. they will be added to accum buffer", + buf.len() + ); + trace!( + "current buf: {}", + String::from_utf8_lossy(&buf).to_string() + ); + } else { + trace!( + "end of loop buf is empty. returning {} chunks", + chunks.len() + ); + return Poll::Ready(Some(Ok(chunks))); + } + } + Err(err) => { + error!("error: {}", err); + return Poll::Ready(Some(Err(err.into()))); + } + }, + }, + } + } + } +} diff --git a/src/k8-client/tests/service.rs b/src/k8-client/tests/service.rs new file mode 100644 index 00000000..b89b06b2 --- /dev/null +++ b/src/k8-client/tests/service.rs @@ -0,0 +1,77 @@ +#[cfg(feature = "k8")] +#[cfg(not(feature = "k8_stream"))] +mod integration_tests { + + use std::collections::HashMap; + + use log::debug; + use rand::distributions::Alphanumeric; + use rand::{thread_rng, Rng}; + + use flv_future_core::test_async; + use k8_client::fixture::TEST_NS; + use k8_client::ClientError; + use k8_metadata_core::metadata::InputK8Obj; + use k8_metadata_core::metadata::InputObjectMeta; + use k8_client::K8Client; + use k8_client::service::ServicePort; + use k8_client::service::ServiceSpec; + use k8_metadata_core::Spec; + use k8_metadata::client::MetadataClient; + use types::defaults::SPU_DEFAULT_NAME; + + fn create_client() -> K8Client { + K8Client::default().expect("cluster not initialized") + } + + fn new_service() -> InputK8Obj { + let rng = thread_rng(); + let rname: String = rng.sample_iter(&Alphanumeric).take(5).collect(); + let name = format!("test{}", rname); + + let mut labels = HashMap::new(); + labels.insert("app".to_owned(), SPU_DEFAULT_NAME.to_owned()); + let mut selector = HashMap::new(); + selector.insert("app".to_owned(), SPU_DEFAULT_NAME.to_owned()); + + let service_spec = ServiceSpec { + cluster_ip: "None".to_owned(), + ports: vec![ServicePort { + port: 9092, + ..Default::default() + }], + selector: Some(selector), + ..Default::default() + }; + + let new_item: InputK8Obj = InputK8Obj { + api_version: ServiceSpec::api_version(), + kind: ServiceSpec::kind(), + metadata: InputObjectMeta { + name: name.to_lowercase(), + labels, + namespace: TEST_NS.to_string(), + ..Default::default() + }, + spec: service_spec, + ..Default::default() + }; + + new_item + } + + #[test_async] + async fn test_client_create_and_delete_service() -> Result<(), ClientError> { + let new_item = new_service(); + debug!("item: {:#?}", &new_item); + let client = create_client(); + let item = client.create_item::(new_item).await?; + debug!("deleting: {:#?}", item); + let input_metadata: InputObjectMeta = item.metadata.into(); + client + .delete_item::(&input_metadata) + .await?; + assert!(true, "passed"); + Ok(()) + } +} diff --git a/src/k8-client/tests/stateful.rs b/src/k8-client/tests/stateful.rs new file mode 100644 index 00000000..dd7ac9de --- /dev/null +++ b/src/k8-client/tests/stateful.rs @@ -0,0 +1,19 @@ +#[cfg(feature = "k8")] +#[cfg(not(feature = "k8_stream"))] +mod integration_tests { + + use k8_client::K8Client; + use k8_client::stateful::StatefulSetSpec; + use k8_client::ClientError; + use flv_future_core::test_async; + use k8_client::fixture::TEST_NS; + use k8_metadata::client::MetadataClient; + + // this assume we have at least one statefulset + #[test_async] + async fn test_client_get_statefulset() -> Result<(), ClientError> { + let client = K8Client::default().expect("cluster could not be configured"); + client.retrieve_items::(TEST_NS).await?; + Ok(()) as Result<(), ClientError> + } +} diff --git a/src/k8-client/tests/test.yaml b/src/k8-client/tests/test.yaml new file mode 100644 index 00000000..20e922b4 --- /dev/null +++ b/src/k8-client/tests/test.yaml @@ -0,0 +1,5 @@ +# Test namespace +apiVersion: v1 +kind: Namespace +metadata: + name: test \ No newline at end of file diff --git a/src/k8-client/tests/topic-stream.rs b/src/k8-client/tests/topic-stream.rs new file mode 100644 index 00000000..773d99b0 --- /dev/null +++ b/src/k8-client/tests/topic-stream.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "k8_stream")] +mod integration_tests { + + use log::debug; + use futures::stream::StreamExt; + use flv_future_core::test_async; + use k8_client::fixture::TEST_NS; + use k8_client::ClientError; + use k8_client::K8Client; + use k8_metadata::topic::TopicSpec; + use k8_metadata::client::MetadataClient; + + // way to get static lifetime which is requirement for cluster + fn create_client() -> K8Client { + K8Client::default().expect("cluster not initialized") + } + + // print first 10 topics of topic stream, this should be only run as part of indiv test + #[test_async] + async fn test_client_print_stream() -> Result<(), ClientError> { + let client = create_client(); + let mut stream = client.watch_stream_now::(TEST_NS.to_owned()); + let mut count: u16 = 0; + let mut end = false; + while count < 10 && !end { + match stream.next().await { + Some(topic) => { + count = count + 1; + debug!("topic event: {} {:#?}", count, topic); + } + _ => { + end = true; + } + } + } + Ok(()) + } + + /* + #[test_async] + async fn test_client_stream_topics() -> Result<(), ClientError> { + let stream = K8CLIENT.watch_stream_since::(TEST_NS, None); + pin_mut!(stream); + let result = stream.next().await; + match result { + Some(topic_result) => { + let topics = topic_result.expect("topics"); + assert!(topics.len() > 0, "there should be at least 1 topic"); + } + None => { + assert!(false, "there should be at least 1 topics"); + } + } + + Ok(()) + } + */ +} diff --git a/src/k8-client/tests/topic.rs b/src/k8-client/tests/topic.rs new file mode 100644 index 00000000..3d4e97d1 --- /dev/null +++ b/src/k8-client/tests/topic.rs @@ -0,0 +1,162 @@ +/// This assumes you have set up the CRD for topics +#[cfg(feature = "k8")] +#[cfg(not(feature = "k8_stream"))] +mod integration_tests { + + use log::debug; + use rand::distributions::Alphanumeric; + use rand::{thread_rng, Rng}; + + use flv_future_core::test_async; + use k8_client::fixture::TEST_NS; + use k8_metadata::client::ApplyResult; + use k8_client::ClientError; + use k8_metadata_core::metadata::InputK8Obj; + use k8_metadata_core::metadata::InputObjectMeta; + use k8_metadata_core::metadata::UpdateK8ObjStatus; + use k8_metadata::client::MetadataClient; + use k8_client::K8Client; + use k8_client::pod::PodSpec; + use k8_metadata_core::metadata::K8Obj; + use k8_metadata_core::Spec; + use k8_metadata::topic::{TopicSpec, TopicStatus, TopicStatusResolution}; + + // way to get static lifetime which is requirement for cluster + fn create_client() -> K8Client { + K8Client::default().expect("cluster not initialized") + } + + fn new_topic() -> InputK8Obj { + let rng = thread_rng(); + let rname: String = rng.sample_iter(&Alphanumeric).take(5).collect(); + let name = format!("test{}", rname); + debug!("create topic with name: {}", &name); + let topic_spec = TopicSpec { + partitions: Some(2), + replication_factor: Some(5), + ..Default::default() + }; + + let new_item: InputK8Obj = InputK8Obj { + api_version: TopicSpec::api_version(), + kind: TopicSpec::kind(), + metadata: InputObjectMeta::named(name.to_lowercase(), TEST_NS.to_owned()), + spec: topic_spec, + ..Default::default() + }; + + new_item + } + + /// get topics. this assumes there exists topic named "topic1" + #[test_async] + async fn test_client_get_topics() -> Result<(), ClientError> { + let client = create_client(); + let topics = client.retrieve_items::(TEST_NS).await?; + assert!(topics.items.len() > 0); + assert_eq!(topics.kind, "TopicList"); + let topic = &topics.items[0]; + assert_eq!(topic.kind, "Topic"); + assert_eq!(topic.metadata.name, "topic1"); + assert_eq!(topic.spec.partitions, Some(1)); + Ok(()) + } + + /// get specific topic named topic1. this assumes there exists topic named "topic1" + #[test_async] + async fn test_client_get_single_topic() -> Result<(), ClientError> { + let client = create_client(); + let topic: K8Obj = client + .retrieve_item(&InputObjectMeta::named("topic1", TEST_NS)) + .await?; + assert_eq!(topic.kind, "Topic"); + assert_eq!(topic.metadata.name, "topic1"); + assert_eq!(topic.spec.partitions, Some(1)); + + Ok(()) + } + + /// create and delete topic + #[test_async] + async fn test_client_create_and_delete_topic() -> Result<(), ClientError> { + let new_item = new_topic(); + debug!("creating and delete topic: {}", new_item.metadata.name); + let client = create_client(); + let created_item = client.create_item::(new_item).await?; + client + .delete_item::(&created_item.metadata.as_input()) + .await?; + Ok(()) + } + + #[test_async] + async fn test_client_create_update_status() -> Result<(), ClientError> { + let new_item = new_topic(); + let client = create_client(); + let topic = client.create_item::(new_item).await?; + + assert!(topic.status.is_none()); + // create simple status + let status = TopicStatus { + resolution: TopicStatusResolution::Init, + ..Default::default() + }; + + let update_status = UpdateK8ObjStatus::new(status, topic.metadata.as_update()); + client.update_status::(&update_status).await?; + + let exist_topic: K8Obj = + client.retrieve_item(&topic.metadata.as_input()).await?; + + let test_status = exist_topic.status.expect("status should exists"); + assert_eq!(test_status.resolution, TopicStatusResolution::Init); + + client + .delete_item::(&exist_topic.metadata.as_input()) + .await?; + + Ok(()) + } + + #[test_async] + async fn test_client_get_pods() -> Result<(), ClientError> { + let client = create_client(); + client.retrieve_items::(TEST_NS).await?; + Ok(()) + } + + #[test_async] + async fn test_client_apply_topic() -> Result<(), ClientError> { + let topic = new_topic(); + let client = create_client(); + match client.apply(topic).await? { + ApplyResult::Created(new_topic) => { + assert!(true, "created"); + // check to ensure item exists + client + .exists::(&new_topic.metadata.as_input()) + .await?; + let mut update_topic = new_topic.as_input(); + update_topic.spec.partitions = Some(5); + match client.apply(update_topic).await? { + ApplyResult::None => assert!(false, "no change"), + ApplyResult::Created(_) => assert!(false, "created, should not happen"), + ApplyResult::Patched(_) => { + let patch_item: K8Obj = + client.retrieve_item(&new_topic.metadata.as_input()).await?; + assert_eq!(patch_item.spec.partitions, Some(5)); + } + } + + client + .delete_item::(&new_topic.metadata.as_input()) + .await?; + } + _ => { + assert!(false, "expected created"); + } + } + + Ok(()) + } +} diff --git a/src/k8-config/Cargo.toml b/src/k8-config/Cargo.toml new file mode 100644 index 00000000..7882e4da --- /dev/null +++ b/src/k8-config/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "k8-config" +version = "0.1.0" +authors = ["Fluvio Contributors "] +edition = "2018" +description = "Read Kubernetes config" +repository = "https://github.com/infinyon/k8-api" +license = "Apache-2.0" + + +[dependencies] +log = "0.4.8" +dirs = "2.0.2" +serde = { version ="1.0.103", features = ['derive'] } +serde_yaml = "0.8.9" + diff --git a/src/k8-config/LICENSE-APACHE b/src/k8-config/LICENSE-APACHE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/src/k8-config/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/k8-config/README.md b/src/k8-config/README.md new file mode 100644 index 00000000..040a3c08 --- /dev/null +++ b/src/k8-config/README.md @@ -0,0 +1,13 @@ +# Kubernetes Configuration Utility + +This crate is used to read K8 Configuration. + +## License + +This project is licensed under the [Apache license](LICENSE-APACHE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Fluvio by you, shall be licensed as Apache, without any additional +terms or conditions. diff --git a/src/k8-config/data/k8config.yaml b/src/k8-config/data/k8config.yaml new file mode 100644 index 00000000..3ca48766 --- /dev/null +++ b/src/k8-config/data/k8config.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority: /Users/test/.minikube/ca.crt + server: https://192.168.0.0:8443 + name: minikube +contexts: +- context: + cluster: minikube + namespace: flv + user: minikube + name: flv +- context: + cluster: minikube + user: minikube + name: minikube +current-context: flv +kind: Config +preferences: {} +users: +- name: minikube + user: + client-certificate: /Users/test/.minikube/client.crt + client-key: /Users/test/.minikube/client.key +- name: testuser + user: + exec: dummy \ No newline at end of file diff --git a/src/k8-config/src/config.rs b/src/k8-config/src/config.rs new file mode 100644 index 00000000..ab1296e5 --- /dev/null +++ b/src/k8-config/src/config.rs @@ -0,0 +1,149 @@ +use std::path::Path; +use std::fs::File; +use std::fs::read_to_string; +use std::io::Result as IoResult; + +use serde::Deserialize; +use dirs::home_dir; + +use crate::ConfigError; + +#[derive(Debug, PartialEq, Deserialize)] +pub struct Cluster { + pub name: String, + pub cluster: ClusterDetail, +} + + +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct ClusterDetail { + pub insecure_skip_tls_verify: Option, + pub certificate_authority: Option, + pub certificate_authority_data: Option, + pub server: String, +} + +impl ClusterDetail { + pub fn ca(&self) -> Option> { + + self.certificate_authority.as_ref().map(|ca| read_to_string(ca)) + } +} + +#[derive(Debug, PartialEq, Deserialize)] +pub struct Context { + pub name: String, + pub context: ContextDetail, +} + + +#[derive(Debug, PartialEq, Deserialize)] +pub struct ContextDetail { + pub cluster: String, + pub user: String, + pub namespace: Option +} + +impl ContextDetail { + pub fn namespace(&self) -> &str { + match &self.namespace { + Some(nm) => &nm, + None => "default" + } + } +} + + +#[derive(Debug, PartialEq, Deserialize)] +pub struct User { + pub name: String, + pub user: UserDetail +} + +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct UserDetail { + pub client_certificate: Option, + pub client_key: Option +} + + +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct KubeConfig { + #[serde(rename = "apiVersion")] + pub api_version: String, + pub clusters: Vec, + pub contexts: Vec, + pub current_context: String, + pub kind: String, + pub users: Vec, +} + + +impl KubeConfig { + + /// read from default home directory + pub fn from_home() -> Result { + let home_dir = home_dir().unwrap(); + Self::from_file(home_dir.join(".kube").join("config")) + } + + pub fn from_file>(path: T) -> Result { + let file = File::open(path)?; + Ok(serde_yaml::from_reader(file)?) + } + + pub fn current_context(&self) -> Option<&Context> { + self.contexts.iter().find(|c| c.name == self.current_context) + } + + pub fn current_cluster(&self) -> Option<&Cluster> { + if let Some(ctx) = self.current_context() { + self.clusters.iter().find(|c| c.name == ctx.context.cluster) + } else { + None + } + } + + pub fn current_user(&self) -> Option<&User> { + if let Some(ctx) = self.current_context() { + self.users.iter().find(|c| c.name == ctx.context.user) + } else { + None + } + } + + +} + + + + +#[cfg(test)] +mod test { + + use super::KubeConfig; + + #[test] + fn test_decode_default_config() { + let config = KubeConfig::from_file("data/k8config.yaml").expect("read"); + assert_eq!(config.api_version,"v1"); + assert_eq!(config.kind,"Config"); + assert_eq!(config.current_context,"flv"); + assert_eq!(config.clusters.len(),1); + let cluster = &config.clusters[0].cluster; + assert_eq!(cluster.server,"https://192.168.0.0:8443"); + assert_eq!(cluster.certificate_authority,Some("/Users/test/.minikube/ca.crt".to_owned())); + assert_eq!(config.contexts.len(),2); + let ctx = &config.contexts[0].context; + assert_eq!(ctx.cluster,"minikube"); + assert_eq!(ctx.namespace.as_ref().unwrap(),"flv"); + + let current_cluster = config.current_cluster().expect("current"); + assert_eq!(current_cluster.name,"minikube"); + + } +} + diff --git a/src/k8-config/src/error.rs b/src/k8-config/src/error.rs new file mode 100644 index 00000000..9e0d3e5e --- /dev/null +++ b/src/k8-config/src/error.rs @@ -0,0 +1,34 @@ +use std::fmt; +use std::io::Error as StdIoError; + +use serde_yaml::Error as SerdYamlError; + + +#[derive(Debug)] +pub enum ConfigError { + IoError(StdIoError), + SerdeError(SerdYamlError), + NoCurrentContext +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::IoError(err) => write!(f, "{}", err), + Self::SerdeError(err) => write!(f,"{}",err), + Self::NoCurrentContext => write!(f,"no current context") + } + } +} + +impl From for ConfigError { + fn from(error: StdIoError) -> Self { + Self::IoError(error) + } +} + +impl From for ConfigError { + fn from(error: SerdYamlError) -> Self { + Self::SerdeError(error) + } +} diff --git a/src/k8-config/src/lib.rs b/src/k8-config/src/lib.rs new file mode 100644 index 00000000..53f7ec5c --- /dev/null +++ b/src/k8-config/src/lib.rs @@ -0,0 +1,70 @@ + +mod config; +mod error; +mod pod; + +pub use error::ConfigError; +pub use config::KubeConfig; +pub use pod::PodConfig; + +use log::debug; + +#[derive(Debug)] +pub struct KubeContext { + pub namespace: String, + pub api_path: String, + pub config: KubeConfig +} + +#[derive(Debug)] +pub enum K8Config { + Pod(PodConfig), + KubeConfig(KubeContext) +} + +impl Default for K8Config { + fn default() -> Self { + Self::Pod(PodConfig::default()) + } +} + +impl K8Config { + pub fn load() -> Result { + if let Some(pod_config) = PodConfig::load() { + debug!("found pod config: {:#?}",pod_config); + Ok(K8Config::Pod(pod_config)) + } else { + debug!("no pod config is found. trying to read kubeconfig"); + let config = KubeConfig::from_home()?; + debug!("kube config: {:#?}",config); + // check if we have current cluster + + if let Some(current_cluster) = config.current_cluster() { + let ctx = config.current_context().expect("current context should exists"); + Ok(K8Config::KubeConfig(KubeContext { + namespace: ctx.context.namespace().to_owned(), + api_path: current_cluster.cluster.server.clone(), + config + })) + } else { + Err(ConfigError::NoCurrentContext) + } + + } + } + + pub fn api_path(&self) -> &str { + match self { + Self::Pod(pod) => pod.api_path(), + Self::KubeConfig(config) => &config.api_path + } + } + + pub fn namespace(&self) -> &str { + match self { + Self::Pod(pod) => &pod.namespace, + Self::KubeConfig(config) => &config.namespace + } + } + +} \ No newline at end of file diff --git a/src/k8-config/src/pod.rs b/src/k8-config/src/pod.rs new file mode 100644 index 00000000..220035b2 --- /dev/null +++ b/src/k8-config/src/pod.rs @@ -0,0 +1,63 @@ +use std::path::Path; +use std::fs::read_to_string; + +use log::debug; +use log::trace; +use log::error; + +const BASE_DIR: &'static str = "/var/run/secrets/kubernetes.io/serviceaccount"; +const API_SERVER: &'static str = "https://kubernetes.default.svc"; + +///var/run/secrets/kubernetes.io/serviceaccount + +/// Configuration as Pod +#[derive(Debug, Default, Clone)] +pub struct PodConfig { + pub namespace: String, + pub token: String, +} + +impl PodConfig { + pub fn load() -> Option { + // first try to see if this base dir account exists, otherwise return non + let path = Path::new(BASE_DIR); + if !path.exists() { + debug!( + "pod config dir: {} is not founded, skipping pod config", + BASE_DIR + ); + return None; + } + + let namespace = read_file("namespace")?; + let token = read_file("token")?; + + Some(Self { + namespace, + token, + }) + } + + pub fn api_path(&self) -> &'static str { + API_SERVER + } + + /// path to CA certificate + pub fn ca_path(&self) -> String { + format!("{}/{}", BASE_DIR, "ca.crt") + } + +} + +// read file +fn read_file(name: &str) -> Option { + let full_path = format!("{}/{}", BASE_DIR, name); + match read_to_string(&full_path) { + Ok(value) => Some(value), + Err(err) => { + error!("no {} founded as pod in {}", name,full_path); + trace!("unable to read pod: {} value: {}", name, err); + None + } + } +} diff --git a/src/k8-diff/Cargo.toml b/src/k8-diff/Cargo.toml new file mode 100644 index 00000000..ec74acf8 --- /dev/null +++ b/src/k8-diff/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "k8-diff" +edition = "2018" +version = "0.1.0" +authors = ["Fluvio Contributors "] +description = "Used for computing diff between Kubernetes objects" +repository = "https://github.com/infinyon/k8-api" +license = "Apache-2.0" + +[dependencies] +log = "0.4.8" +serde = "1.0.103" +serde_json = "1.0.40" diff --git a/src/k8-diff/LICENSE-APACHE b/src/k8-diff/LICENSE-APACHE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/src/k8-diff/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/k8-diff/README.md b/src/k8-diff/README.md new file mode 100644 index 00000000..f3e2ed13 --- /dev/null +++ b/src/k8-diff/README.md @@ -0,0 +1,13 @@ +# kf-diff + +Used for computing diff between Kubernetes objects + +## License + +This project is licensed under the [Apache license](LICENSE-APACHE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Fluvio by you, shall be licensed as Apache, without any additional +terms or conditions. diff --git a/src/k8-diff/k8-dderive/Cargo.toml b/src/k8-diff/k8-dderive/Cargo.toml new file mode 100644 index 00000000..d1b931ae --- /dev/null +++ b/src/k8-diff/k8-dderive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "k8-dderive" +version = "0.2.1" +edition = "2018" +authors = ["fluvio.io"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "0.4.24" +quote = "0.6.10" +syn = "0.15.21" +log = "0.4.8" diff --git a/src/k8-diff/k8-dderive/src/diff.rs b/src/k8-diff/k8-dderive/src/diff.rs new file mode 100644 index 00000000..2d529127 --- /dev/null +++ b/src/k8-diff/k8-dderive/src/diff.rs @@ -0,0 +1,61 @@ +use quote::quote; +use proc_macro2::TokenStream; +use syn::Data; +use syn::DeriveInput; +use syn::Fields; + + + +pub fn geneate_diff_trait(input: &DeriveInput) -> TokenStream { + let name = &input.ident; + let decoded_field_tokens = decode_fields(&input.data); + + quote! { + + impl <'a>k8_diff::Changes<'a> for #name { + + fn diff(&self, new: &'a Self) -> k8_diff::Diff { + + let mut s_diff = k8_diff::DiffStruct::new(); + + #decoded_field_tokens + + if s_diff.no_change() { + return k8_diff::Diff::None + } + + k8_diff::Diff::Change(k8_diff::DiffValue::Struct(s_diff)) + } + } + + } +} + +fn decode_fields(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + let recurse = fields.named.iter().map(|f| { + let fname = &f.ident; + + quote! { + // s_diff.insert("replicas".to_owned(), self.replicas.diff(&new.replicas)); + s_diff.insert(stringify!(#fname).to_owned(), self.#fname.diff(&new.#fname)); + + } + + }); + + quote! { + #(#recurse)* + } + } + _ => unimplemented!(), + } + } + _ => unimplemented!(), + } +} + + diff --git a/src/k8-diff/k8-dderive/src/lib.rs b/src/k8-diff/k8-dderive/src/lib.rs new file mode 100644 index 00000000..39f2b24f --- /dev/null +++ b/src/k8-diff/k8-dderive/src/lib.rs @@ -0,0 +1,19 @@ +extern crate proc_macro; + +mod diff; + +use proc_macro::TokenStream as TokenStream1; +use syn::DeriveInput; + + +#[proc_macro_derive(Difference)] +pub fn diff(input: TokenStream1) -> TokenStream1 { + + // Parse the string representation + let ast: DeriveInput = syn::parse(input).unwrap(); + + let expanded = diff::geneate_diff_trait(&ast); + expanded.into() +} + + diff --git a/src/k8-diff/src/json/diff.rs b/src/k8-diff/src/json/diff.rs new file mode 100644 index 00000000..fa3eba05 --- /dev/null +++ b/src/k8-diff/src/json/diff.rs @@ -0,0 +1,79 @@ +use serde_json::Value; + + +use crate::Changes; +use crate::Diff; +use crate::DiffError; +use super::PatchObject; +use super::JsonDiff; + +impl Changes for Value { + + type Replace = Value; + type Patch = PatchObject; + + fn diff(&self, new: &Self) -> Result { + if *self == *new { + return Ok(Diff::None); + } + match self { + Value::Null => Ok(Diff::Replace(new.clone())), + _ => { + match new { + Value::Null => Ok(Diff::Delete), + Value::Bool(ref _val) => Ok(Diff::Replace(new.clone())), // for now, we only support replace + Value::Number(ref _val) => Ok(Diff::Replace(new.clone())), + Value::String(ref _val) => Ok(Diff::Replace(new.clone())), + Value::Array(ref _val) => Ok(Diff::Replace(new.clone())), + Value::Object(ref new_val) => match self { + Value::Object(ref old_val) => { + let patch = PatchObject::diff(old_val, new_val)?; + Ok(Diff::Patch(patch)) + } + _ => Err(DiffError::DiffValue), + }, + } + } + } + } +} + +#[cfg(test)] +mod test { + + use serde_json::json; + use serde_json::Value; + + use super::Changes; + + #[test] + fn test_null_comparision() { + let n1 = Value::Null; + let str1 = Value::String("test".to_owned()); + let str2 = Value::String("test".to_owned()); + + assert!(n1.diff(&str1).expect("diff").is_replace()); + assert!(str1.diff(&str2).expect("diff").is_none()); + } + + #[test] + fn test_object_comparision() { + let old_spec = json!({ + "replicas": 2, + "apple": 5 + }); + let new_spec = json!({ + "replicas": 3, + "apple": 5 + }); + + let diff = old_spec.diff(&new_spec).expect("diff"); + assert!(diff.is_patch()); + let patch = diff.as_patch_ref().get_inner_ref(); + assert_eq!(patch.len(), 1); + let diff_replicas = patch.get("replicas").unwrap(); + assert!(diff_replicas.is_replace()); + assert_eq!(*diff_replicas.as_replace_ref(), 3); + } + +} diff --git a/src/k8-diff/src/json/mod.rs b/src/k8-diff/src/json/mod.rs new file mode 100644 index 00000000..ea962e4c --- /dev/null +++ b/src/k8-diff/src/json/mod.rs @@ -0,0 +1,43 @@ +mod diff; +mod se; + +use serde_json::Map; +use serde_json::Value; +use std::collections::HashMap; + +use crate::Changes; +use crate::Diff; +use crate::DiffError; + +type SerdeObj = Map; +pub type JsonDiff = Diff; + +#[derive(Debug)] +pub struct PatchObject(HashMap); + +impl PatchObject { + // diff { "a": 1,"b": 2}, { "a": 3, "b": 2} => { "a": 1} + fn diff(old: &SerdeObj, new: &SerdeObj) -> Result { + let mut map: HashMap = HashMap::new(); + + for (key, new_val) in new.iter() { + match old.get(key) { + Some(old_val) => { + if old_val != new_val { + let diff_value = old_val.diff(new_val)?; + map.insert(key.clone(), diff_value); + } + } + _ => { + map.insert(key.clone(), Diff::Replace(new_val.clone())); // just replace with new if key doesn't match + } + } + } + + Ok(PatchObject(map)) + } + + fn get_inner_ref(&self) -> &HashMap { + &self.0 + } +} diff --git a/src/k8-diff/src/json/se.rs b/src/k8-diff/src/json/se.rs new file mode 100644 index 00000000..45b981a5 --- /dev/null +++ b/src/k8-diff/src/json/se.rs @@ -0,0 +1,104 @@ +use serde::Serialize; +use serde_json::Value; + +use super::PatchObject; +use crate::Diff; + +impl Serialize for PatchObject { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + let diff_maps = self.get_inner_ref(); + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(diff_maps.len()))?; + for (key, val) in diff_maps { + match val { + Diff::None => {} + Diff::Delete => {} + Diff::Patch(ref v) => { + map.serialize_entry(key, v)? + }, + Diff::Replace(ref v) => { + map.serialize_entry(key, v)?; + } + Diff::Merge(ref v ) => { + map.serialize_entry(key, v)?; + } + } + } + + map.end() + } +} + +impl Serialize for Diff { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + match self { + Diff::None => serializer.serialize_unit(), + Diff::Delete => serializer.serialize_unit(), + Diff::Patch(ref p) => p.serialize(serializer), + Diff::Replace(ref v) => v.serialize(serializer), + Diff::Merge(ref v) => v.serialize(serializer), + } + } +} + +#[cfg(test)] +mod test { + + use serde_json::json; + + use crate::Changes; + + #[test] + fn test_patch_to_simple() { + let old_spec = json!({ + "replicas": 2, + "apple": 5 + }); + let new_spec = json!({ + "replicas": 3, + "apple": 5 + }); + + let diff = old_spec.diff(&new_spec).expect("diff"); + assert!(diff.is_patch()); + + let expected = json!({ + "replicas": 3 + }); + let json_diff = serde_json::to_value(diff).unwrap(); + assert_eq!(json_diff, expected); + } + + #[test] + fn test_patch_to_hierarchy() { + let old_spec = json!({ + "spec": { + "replicas": 2, + "apple": 5 + } + }); + let new_spec = json!({ + "spec": { + "replicas": 3, + "apple": 5 + } + }); + + let diff = old_spec.diff(&new_spec).expect("diff"); + assert!(diff.is_patch()); + println!("final diff: {:#?}", diff); + let expected = json!({ + "spec": { + "replicas": 3 + }}); + let json_diff = serde_json::to_value(diff).unwrap(); + assert_eq!(json_diff, expected); + } + +} diff --git a/src/k8-diff/src/lib.rs b/src/k8-diff/src/lib.rs new file mode 100644 index 00000000..78a5a203 --- /dev/null +++ b/src/k8-diff/src/lib.rs @@ -0,0 +1,74 @@ +mod json; + +pub trait Changes { + type Replace; + type Patch; + + fn diff(&self, new: &Self) -> Result, DiffError>; +} + +#[derive(Debug)] +pub enum DiffError { + DiffValue, // json values are different +} + +// use Option as inspiration +#[derive(Debug)] +pub enum Diff { + None, + Delete, + Patch(P), // for non primitive type + Replace(R), // can be used for map and list (with our without tag), works on ordered list + Merge(R), // need tag, works on unorderd list +} + +impl Diff { + pub fn is_none(&self) -> bool { + match self { + Diff::None => true, + _ => false, + } + } + + pub fn is_delete(&self) -> bool { + match self { + Diff::Delete => true, + _ => false, + } + } + + pub fn is_replace(&self) -> bool { + match self { + Diff::Replace(_) => true, + _ => false, + } + } + + pub fn is_patch(&self) -> bool { + match self { + Diff::Patch(_) => true, + _ => false, + } + } + + pub fn is_merge(&self) -> bool { + match self { + Diff::Replace(_) => true, + _ => false, + } + } + + pub fn as_replace_ref(&self) -> &R { + match self { + Diff::Replace(ref val) => val, + _ => panic!("no change value"), + } + } + + pub fn as_patch_ref(&self) -> &P { + match self { + Diff::Patch(ref val) => val, + _ => panic!("no change value"), + } + } +} diff --git a/src/k8-metadata-client/Cargo.toml b/src/k8-metadata-client/Cargo.toml new file mode 100644 index 00000000..e433c5a5 --- /dev/null +++ b/src/k8-metadata-client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2018" +name = "k8-metadata-client" +version = "0.1.0" +authors = ["Fluvio Contributors "] +description = "Trait for interfacing kubernetes metadata service" +repository = "https://github.com/infinyon/k8-api" +license = "Apache-2.0" + +[dependencies] +log = "0.4.8" +futures = { version = "0.3.1"} +pin-utils = "0.1.0-alpha.4" +serde = { version ="1.0.103", features = ['derive'] } +serde_json = "1.0.40" +serde_qs = "0.5.0" +async-trait = "0.1.21" +flv-future-core = { version = "0.1.0" } +k8-diff = { version = "0.1.0", path = "../k8-diff"} +k8-metadata-core = { version = "0.1.0", path = "../k8-metadata-core" } + diff --git a/src/k8-metadata-client/LICENSE-APACHE b/src/k8-metadata-client/LICENSE-APACHE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/src/k8-metadata-client/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/k8-metadata-client/README.md b/src/k8-metadata-client/README.md new file mode 100644 index 00000000..fc9280f0 --- /dev/null +++ b/src/k8-metadata-client/README.md @@ -0,0 +1,13 @@ +# kf-metadata-client + +Trait for interfacing kubernetes metadata service + +## License + +This project is licensed under the [Apache license](LICENSE-APACHE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Fluvio by you, shall be licensed as Apache, without any additional +terms or conditions. diff --git a/src/k8-metadata-client/src/client.rs b/src/k8-metadata-client/src/client.rs new file mode 100644 index 00000000..e96e036b --- /dev/null +++ b/src/k8-metadata-client/src/client.rs @@ -0,0 +1,241 @@ +use std::fmt::Debug; +use std::fmt::Display; +use std::io::Error as IoError; + +use log::debug; +use log::trace; +use futures::stream::BoxStream; +use futures::stream::once; +use futures::future::ready; +use futures::future::FutureExt; +use futures::stream::StreamExt; +use async_trait::async_trait; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json::Error as SerdeJsonError; +use serde_json; +use serde_json::Value; + +use k8_diff::Changes; +use k8_diff::Diff; +use k8_diff::DiffError; +use k8_metadata_core::Spec; +use k8_metadata_core::metadata::K8Meta; +use k8_metadata_core::metadata::InputK8Obj; +use k8_metadata_core::metadata::UpdateK8ObjStatus; +use k8_metadata_core::metadata::K8List; +use k8_metadata_core::metadata::K8Obj; +use k8_metadata_core::metadata::K8Status; +use k8_metadata_core::metadata::K8Watch; + +use crate::DiffSpec; +use crate::ApplyResult; + +/// trait for metadata client +pub trait MetadataClientError: Debug + Display { + /// is not founded + fn not_founded(&self) -> bool; + + // create new patch error + fn patch_error() -> Self; +} + +// For error mapping: see: https://doc.rust-lang.org/nightly/core/convert/trait.From.html + +pub type TokenStreamResult = Result, E>>, E>; + +pub fn as_token_stream_result( + events: Vec>, +) -> TokenStreamResult +where + S: Spec, +{ + Ok(events.into_iter().map(|event| Ok(event)).collect()) +} + +#[async_trait] +pub trait MetadataClient: Send + Sync { + type MetadataClientError: MetadataClientError + + Send + + Display + + From + + From + + From; + + /// retrieval a single item + async fn retrieve_item( + &self, + metadata: &M, + ) -> Result, Self::MetadataClientError> + where + K8Obj: DeserializeOwned, + S: Spec, + M: K8Meta + Send + Sync; + + async fn retrieve_items( + &self, + namespace: &str, + ) -> Result, Self::MetadataClientError> + where + K8List: DeserializeOwned, + S: Spec; + + async fn delete_item(&self, metadata: &M) -> Result + where + S: Spec, + M: K8Meta + Send + Sync; + + /// create new object + async fn create_item( + &self, + value: InputK8Obj, + ) -> Result, Self::MetadataClientError> + where + InputK8Obj: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send; + + /// apply object, this is similar to ```kubectl apply``` + /// for now, this doesn't do any optimization + /// if object doesn't exist, it will be created + /// if object exist, it will be patched by using strategic merge diff + async fn apply( + &self, + value: InputK8Obj, + ) -> Result, Self::MetadataClientError> + where + InputK8Obj: Serialize + Debug, + K8Obj: DeserializeOwned + Debug, + S: Spec + Serialize + Debug + Clone + Send, + S::Status: Send, + Self::MetadataClientError: From + From + Send, + { + debug!("applying '{}' changes", value.metadata.name); + trace!("applying {:#?}", value); + match self.retrieve_item(&value.metadata).await { + Ok(item) => { + let mut old_spec = item.spec; + old_spec.make_same(&value.spec); + // we don't care about status + let new_spec = serde_json::to_value(DiffSpec::from(value.spec.clone()))?; + let old_spec = serde_json::to_value(DiffSpec::from(old_spec))?; + let diff = old_spec.diff(&new_spec)?; + match diff { + Diff::None => { + debug!("no diff detected, doing nothing"); + Ok(ApplyResult::None) + } + Diff::Patch(p) => { + let json_diff = serde_json::to_value(p)?; + debug!("detected diff: old vs. new spec"); + trace!("new spec: {:#?}", &new_spec); + trace!("old spec: {:#?}", &old_spec); + trace!("new/old diff: {:#?}", json_diff); + let patch_result = self.patch_spec(&value.metadata, &json_diff).await?; + Ok(ApplyResult::Patched(patch_result)) + } + _ => Err(Self::MetadataClientError::patch_error()), + } + } + Err(err) => { + if err.not_founded() { + debug!("item '{}' not found, creating ...", value.metadata.name); + let created_item = self.create_item(value.into()).await?; + Ok(ApplyResult::Created(created_item)) + } else { + Err(err) + } + } + } + } + + /// update status + async fn update_status( + &self, + value: &UpdateK8ObjStatus, + ) -> Result, Self::MetadataClientError> + where + UpdateK8ObjStatus: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send + Sync, + S::Status: Send + Sync; + + /// patch existing with spec + async fn patch_spec( + &self, + metadata: &M, + patch: &Value, + ) -> Result, Self::MetadataClientError> + where + K8Obj: DeserializeOwned, + S: Spec + Debug, + M: K8Meta + Display + Send + Sync; + + /// stream items since resource versions + fn watch_stream_since( + &self, + namespace: &str, + resource_version: Option, + ) -> BoxStream<'_, TokenStreamResult> + where + K8Watch: DeserializeOwned, + S: Spec + Debug + Send + 'static, + S::Status: Debug + Send; + + fn watch_stream_now( + &self, + ns: String, + ) -> BoxStream<'_, TokenStreamResult> + where + K8Watch: DeserializeOwned, + K8List: DeserializeOwned, + S: Spec + Debug + 'static + Send, + S::Status: Debug + Send, + { + let ft_stream = async move { + + let namespace = ns.as_ref(); + match self.retrieve_items(namespace).await { + Ok(item_now_list) => { + let resource_version = item_now_list.metadata.resource_version; + + let items_watch_stream = + self.watch_stream_since(namespace, Some(resource_version)); + + let items_list = item_now_list + .items + .into_iter() + .map(|item| Ok(K8Watch::ADDED(item))) + .collect(); + let list_stream = once(ready(Ok(items_list))); + + list_stream.chain(items_watch_stream).left_stream() + // list_stream + } + Err(err) => once(ready(Err(err))).right_stream(), + } + }; + + ft_stream.flatten_stream().boxed() + } + + /// Check if the object exists, return true or false. + async fn exists(&self, metadata: &M) -> Result + where + K8Obj: DeserializeOwned + Serialize + Debug + Clone, + S: Spec + Serialize + Debug, + M: K8Meta + Display + Send + Sync, + { + debug!("check if '{}' exists", metadata); + match self.retrieve_item(metadata).await { + Ok(_) => Ok(true), + Err(err) => { + if err.not_founded() { + Ok(false) + } else { + Err(err) + } + } + } + } +} diff --git a/src/k8-metadata-client/src/diff.rs b/src/k8-metadata-client/src/diff.rs new file mode 100644 index 00000000..539086fe --- /dev/null +++ b/src/k8-metadata-client/src/diff.rs @@ -0,0 +1,51 @@ +use serde::Serialize; + +use k8_metadata_core::Crd; +use k8_metadata_core::metadata::K8Obj; + +#[derive(Debug)] +pub enum ApplyResult { + None, + Created(K8Obj), + Patched(K8Obj), +} + +#[allow(dead_code)] +pub enum PatchMergeType { + Json, + JsonMerge, + StrategicMerge, // for aggegration API +} + +impl PatchMergeType { + pub fn for_spec(crd: &Crd) -> Self { + match crd.group { + "core" => PatchMergeType::StrategicMerge, + "apps" => PatchMergeType::StrategicMerge, + _ => PatchMergeType::JsonMerge, + } + } + + pub fn content_type(&self) -> &'static str { + match self { + PatchMergeType::Json => "application/json-patch+json", + PatchMergeType::JsonMerge => "application/merge-patch+json", + PatchMergeType::StrategicMerge => "application/strategic-merge-patch+json", + } + } +} + +/// used for comparing spec, +#[derive(Serialize, Debug, Clone)] +pub struct DiffSpec { + spec: S, +} + +impl DiffSpec +where + S: Serialize, +{ + pub fn from(spec: S) -> Self { + DiffSpec { spec } + } +} diff --git a/src/k8-metadata-client/src/lib.rs b/src/k8-metadata-client/src/lib.rs new file mode 100644 index 00000000..7f6ee45e --- /dev/null +++ b/src/k8-metadata-client/src/lib.rs @@ -0,0 +1,13 @@ +mod client; +mod diff; +mod nothing; +pub use diff::*; + +pub use client::MetadataClient; +pub use client::MetadataClientError; +pub use client::TokenStreamResult; +pub use client::as_token_stream_result; +pub use nothing::DoNothingClient; +pub use nothing::DoNothingError; + +pub type SharedClient = std::sync::Arc; \ No newline at end of file diff --git a/src/k8-metadata-client/src/nothing.rs b/src/k8-metadata-client/src/nothing.rs new file mode 100644 index 00000000..3b182a37 --- /dev/null +++ b/src/k8-metadata-client/src/nothing.rs @@ -0,0 +1,168 @@ +// implementation of metadata client do nothing +// it is used for testing where to satisfy metadata contract +use std::io::Error as IoError; +use std::fmt; +use std::fmt::Debug; +use std::fmt::Display; + +use futures::stream::StreamExt; +use futures::stream::BoxStream; +use async_trait::async_trait; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json::Value; + +use k8_diff::DiffError; +use k8_metadata_core::Spec; +use k8_metadata_core::metadata::K8Meta; +use k8_metadata_core::metadata::InputK8Obj; +use k8_metadata_core::metadata::UpdateK8ObjStatus; +use k8_metadata_core::metadata::K8List; +use k8_metadata_core::metadata::K8Obj; +use k8_metadata_core::metadata::K8Status; +use k8_metadata_core::metadata::K8Watch; + +use crate::MetadataClient; +use crate::MetadataClientError; +use crate::TokenStreamResult; + +#[derive(Debug)] +pub enum DoNothingError { + IoError(IoError), + DiffError(DiffError), + JsonError(serde_json::Error), + PatchError, + NotFound, +} + +impl From for DoNothingError { + fn from(error: IoError) -> Self { + Self::IoError(error) + } +} + +impl From for DoNothingError { + fn from(error: serde_json::Error) -> Self { + Self::JsonError(error) + } +} + +impl From for DoNothingError { + fn from(error: DiffError) -> Self { + Self::DiffError(error) + } +} + +impl fmt::Display for DoNothingError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::IoError(err) => write!(f, "io: {}", err), + Self::JsonError(err) => write!(f, "{}", err), + Self::NotFound => write!(f, "not found"), + Self::DiffError(err) => write!(f, "{:#?}", err), + Self::PatchError => write!(f, "patch error"), + } + } +} + +impl MetadataClientError for DoNothingError { + fn patch_error() -> Self { + Self::PatchError + } + + fn not_founded(&self) -> bool { + match self { + Self::NotFound => true, + _ => false, + } + } +} + +pub struct DoNothingClient(); + +#[async_trait] +impl MetadataClient for DoNothingClient { + type MetadataClientError = DoNothingError; + + async fn retrieve_item( + &self, + _metadata: &M, + ) -> Result, Self::MetadataClientError> + where + K8Obj: DeserializeOwned, + S: Spec, + M: K8Meta + Send + Sync, + { + Err(DoNothingError::NotFound) as Result, Self::MetadataClientError> + } + + async fn retrieve_items( + &self, + _namespace: &str, + ) -> Result, Self::MetadataClientError> + where + K8List: DeserializeOwned, + S: Spec, + { + Err(DoNothingError::NotFound) as Result, Self::MetadataClientError> + } + + async fn delete_item(&self, _metadata: &M) -> Result + where + S: Spec, + M: K8Meta + Send + Sync, + { + Err(DoNothingError::NotFound) as Result + } + + async fn create_item( + &self, + _value: InputK8Obj, + ) -> Result, Self::MetadataClientError> + where + InputK8Obj: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send, + { + Err(DoNothingError::NotFound) as Result, Self::MetadataClientError> + } + + async fn update_status( + &self, + _value: &UpdateK8ObjStatus, + ) -> Result, Self::MetadataClientError> + where + UpdateK8ObjStatus: Serialize + Debug, + K8Obj: DeserializeOwned, + S: Spec + Send + Sync, + S::Status: Send + Sync, + { + Err(DoNothingError::NotFound) as Result, Self::MetadataClientError> + } + + async fn patch_spec( + &self, + _metadata: &M, + _patch: &Value, + ) -> Result, Self::MetadataClientError> + where + K8Obj: DeserializeOwned, + S: Spec + Debug, + M: K8Meta + Display + Send + Sync, + { + Err(DoNothingError::NotFound) as Result, Self::MetadataClientError> + } + + fn watch_stream_since( + &self, + _namespace: &str, + _resource_version: Option, + ) -> BoxStream<'_, TokenStreamResult> + where + K8Watch: DeserializeOwned, + S: Spec + Debug + Send + 'static, + S::Status: Debug + Send, + { + futures::stream::empty().boxed() + } +} diff --git a/src/k8-metadata-core/Cargo.toml b/src/k8-metadata-core/Cargo.toml new file mode 100644 index 00000000..022090e0 --- /dev/null +++ b/src/k8-metadata-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +edition = "2018" +name = "k8-metadata-core" +version = "0.1.2" +authors = ["Fluvio Contributors "] +description = "Core Kubernetes metadata traits" +repository = "https://github.com/infinyon/k8-api" +license = "Apache-2.0" +categories = ["encoding"] + + +[dependencies] +log = "0.4.8" +serde = { version ="1.0.103", features = ['derive'] } +serde_json = "1.0.27" +serde_qs = "0.4.1" +http = { version = "0.1.15" } diff --git a/src/k8-metadata-core/LICENSE-APACHE b/src/k8-metadata-core/LICENSE-APACHE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/src/k8-metadata-core/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/k8-metadata-core/README.md b/src/k8-metadata-core/README.md new file mode 100644 index 00000000..14f3d67c --- /dev/null +++ b/src/k8-metadata-core/README.md @@ -0,0 +1,13 @@ +# kf-metadata-core + +Core Kubernetes metadata traits + +## License + +This project is licensed under the [Apache license](LICENSE-APACHE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Fluvio by you, shall be licensed as Apache, without any additional +terms or conditions. diff --git a/src/k8-metadata-core/src/crd.rs b/src/k8-metadata-core/src/crd.rs new file mode 100644 index 00000000..50bb4902 --- /dev/null +++ b/src/k8-metadata-core/src/crd.rs @@ -0,0 +1,21 @@ +//! +//! # CRD Definition +//! +//! Interface to the CRD header definition in K8 key value store +//! +#[derive(Debug)] +pub struct Crd { + pub group: &'static str, + pub version: &'static str, + pub names: CrdNames, +} + +#[derive(Debug)] +pub struct CrdNames { + pub kind: &'static str, + pub plural: &'static str, + pub singular: &'static str, +} + +pub const GROUP: &'static str = "fluvio.infinyon.com"; +pub const V1: &'static str = "v1"; diff --git a/src/k8-metadata-core/src/lib.rs b/src/k8-metadata-core/src/lib.rs new file mode 100644 index 00000000..01d81b94 --- /dev/null +++ b/src/k8-metadata-core/src/lib.rs @@ -0,0 +1,42 @@ +//! +//! # CRD traits +//! +//! Trait for CRD Spec/Status definition +//! +mod crd; +pub mod metadata; +pub mod options; + +pub use self::crd::Crd; +pub use self::crd::CrdNames; +pub use self::crd::GROUP; +pub use self::crd::V1; + +pub trait Status: Sized{} + +/// Kubernetes Spec +pub trait Spec: Sized { + + type Status: Status; + + /// return uri for single instance + fn metadata() -> &'static Crd; + + fn api_version() -> String { + let metadata = Self::metadata(); + if metadata.group == "core" { + return metadata.version.to_owned(); + } + format!("{}/{}", metadata.group, metadata.version) + } + + fn kind() -> String { + Self::metadata().names.kind.to_owned() + } + + /// in case of applying, we have some fields that are generated + /// or override. So need to special logic to reset them so we can do proper comparison + fn make_same(&mut self,_other: &Self) { + } + +} diff --git a/src/k8-metadata-core/src/metadata.rs b/src/k8-metadata-core/src/metadata.rs new file mode 100644 index 00000000..5854c2be --- /dev/null +++ b/src/k8-metadata-core/src/metadata.rs @@ -0,0 +1,667 @@ +use std::collections::HashMap; +use std::collections::BTreeMap; +use std::marker::PhantomData; +use std::fmt; + +use http::Uri; +use serde::de::Deserializer; +use serde::Deserialize; +use serde::Serialize; + +use crate::options::prefix_uri; +use crate::options::ListOptions; +use crate::Spec; + +pub const DEFAULT_NS: &'static str = "default"; +pub const TYPE_OPAQUE: &'static str = "Opaque"; + +pub trait K8Meta where S: Spec { + + // return uri for given host name + fn item_uri(&self,host_name: &str) -> Uri; +} + +pub trait LabelProvider: Sized { + + + fn set_label_map(self, labels: HashMap) -> Self; + + /// helper for setting list of labels + fn set_labels(self, labels: Vec<(T, T)>) -> Self { + let mut label_map = HashMap::new(); + for (key, value) in labels { + label_map.insert(key.to_string(), value.to_string()); + } + self.set_label_map(label_map) + } + +} + +/// metadata associated with object when returned +/// here name and namespace must be populated +#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone)] +#[serde(rename_all = "camelCase",default)] +pub struct ObjectMeta { + // mandatory fields + pub name: String, + pub namespace: String, + pub uid: String, + pub self_link: String, + pub creation_timestamp: String, + pub generation: Option, + pub resource_version: String, + // optional + pub cluster_name: Option, + pub deletion_timestamp: Option, + pub deletion_grace_period_seconds: Option, + pub labels: HashMap, + pub owner_references: Vec, +} + +impl LabelProvider for ObjectMeta { + + fn set_label_map(mut self, labels: HashMap) -> Self { + self.labels = labels; + self + } +} + + +impl ObjectMeta { + + pub fn new(name: S,name_space: S) -> Self + where S: Into { + Self { + name: name.into(), + namespace: name_space.into(), + ..Default::default() + } + } + + /// provide builder pattern setter + pub fn set_labels>(mut self, labels: Vec<(T, T)>) -> Self { + let mut label_map = HashMap::new(); + for (key, value) in labels { + label_map.insert(key.into(), value.into()); + } + self.labels = label_map; + self + } + + /// create with name and default namespace + pub fn named(name: S) -> Self where S: Into{ + Self { + name: name.into(), + ..Default::default() + } + } + + /// create owner references point to this metadata + /// if name or uid doesn't exists return none + pub fn make_owner_reference(&self) -> OwnerReferences { + + OwnerReferences { + api_version: S::api_version(), + kind: S::kind(), + name: self.name.clone(), + uid: self.uid.clone(), + controller: Some(true), + ..Default::default() + } + + } + + pub fn namespace(&self) -> &str { + &self.namespace + } + + /// create child references that points to this + pub fn make_child_input_metadata(&self,childname: String) -> InputObjectMeta { + + let mut owner_refs: Vec = vec![]; + owner_refs.push(self.make_owner_reference::()); + + InputObjectMeta { + name: childname, + namespace: self.namespace().to_owned(), + owner_references: owner_refs, + ..Default::default() + } + + } + + + pub fn as_input(&self) -> InputObjectMeta { + + InputObjectMeta { + name: self.name.clone(), + namespace: self.namespace.clone(), + ..Default::default() + } + } + + pub fn as_item(&self) -> ItemMeta { + ItemMeta { + name: self.name.clone(), + namespace: self.namespace.clone(), + } + } + + pub fn as_update(&self) -> UpdateItemMeta { + UpdateItemMeta { + name: self.name.clone(), + namespace: self.namespace.clone(), + resource_version: self.resource_version.clone() + } + } +} + + + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct InputObjectMeta { + pub name: String, + pub labels: HashMap, + pub namespace: String, + pub owner_references: Vec, +} + +impl LabelProvider for InputObjectMeta { + + fn set_label_map(mut self, labels: HashMap) -> Self { + self.labels = labels; + self + } +} + + +impl fmt::Display for InputObjectMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}",self.name,self.namespace) + } +} + +impl K8Meta for InputObjectMeta where S: Spec { + + fn item_uri(&self,host_name: &str) -> Uri { + + item_uri::( + host_name, + &self.name, + &self.namespace, + None, + ) + } +} + + + +impl InputObjectMeta { + // shorthand to create just with name and metadata + pub fn named>(name: S, namespace: S) -> Self { + InputObjectMeta { + name: name.into(), + namespace: namespace.into(), + ..Default::default() + } + } +} + +impl From for InputObjectMeta { + fn from(meta: ObjectMeta) -> Self { + Self { + name: meta.name, + namespace: meta.namespace, + ..Default::default() + } + } +} + + +/// used for retrieving,updating and deleting item +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ItemMeta { + pub name: String, + pub namespace: String, +} + + +impl From for ItemMeta { + fn from(meta: ObjectMeta) -> Self { + Self { + name: meta.name, + namespace: meta.namespace + } + } +} + +/// used for updating item +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdateItemMeta { + pub name: String, + pub namespace: String, + pub resource_version: String, +} + + +impl From for UpdateItemMeta { + fn from(meta: ObjectMeta) -> Self { + Self { + name: meta.name, + namespace: meta.namespace, + resource_version: meta.resource_version + } + } +} + + + +#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct OwnerReferences { + pub api_version: String, + pub block_owner_deletion: Option, + pub controller: Option, + pub kind: String, + pub name: String, + pub uid: String, +} + +/// items uri +pub fn item_uri(host: &str, name: &str, namespace: &str, sub_resource: Option<&str>) -> Uri +where + S: Spec + Sized, +{ + let crd = S::metadata(); + let prefix = prefix_uri(crd, host, namespace, None); + let uri_value = format!("{}/{}{}", prefix, name, sub_resource.unwrap_or("")); + let uri: Uri = uri_value.parse().unwrap(); + uri +} + +/// items uri +pub fn items_uri(host: &str, namespace: &str, list_options: Option<&ListOptions>) -> Uri +where + S: Spec, +{ + let crd = S::metadata(); + let uri_value = prefix_uri(crd, host, namespace, list_options); + let uri: Uri = uri_value.parse().unwrap(); + uri +} + +#[derive(Deserialize, Debug, Eq, PartialEq, Clone)] +pub enum StatusEnum { + SUCCESS, + FAILURE, +} + +impl DeserializeWith for StatusEnum { + fn deserialize_with<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(de)?; + + match s.as_ref() { + "Success" => Ok(StatusEnum::SUCCESS), + "Failure" => Ok(StatusEnum::FAILURE), + _ => Err(serde::de::Error::custom( + "error trying to deserialize status type", + )), + } + } +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct K8Status { + pub api_version: String, + pub code: Option, + pub details: Option, + pub kind: String, + pub message: Option, + pub reason: Option, + #[serde(deserialize_with = "StatusEnum::deserialize_with")] + pub status: StatusEnum, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct StatusDetails { + pub name: String, + pub group: Option, + pub kind: String, + pub uid: String, +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct K8Obj { + pub api_version: String, + pub kind: String, + pub metadata: ObjectMeta, + pub spec: S, + pub status: Option

, + #[serde(default)] + pub data: Option>, +} + + + + + +impl K8Obj + where S: Spec + Default, + S::Status: Default +{ + + #[allow(dead_code)] + pub fn new(name: N,spec: S) -> Self where N: Into { + Self { + api_version: S::api_version(), + kind: S::kind(), + metadata: ObjectMeta::named(name), + spec, + status: None, + ..Default::default() + } + } + + #[allow(dead_code)] + pub fn set_status(mut self,status: S::Status) -> Self { + self.status = Some(status); + self + } + + pub fn as_status_update(&self,status: S::Status) -> UpdateK8ObjStatus { + + UpdateK8ObjStatus { + api_version: S::api_version(), + kind: S::kind(), + metadata: self.metadata.as_update(), + status, + ..Default::default() + } + } +} + +impl K8Obj + where S: Spec + Default + Clone, +{ + + pub fn as_input(&self) -> InputK8Obj { + K8SpecObj { + api_version: self.api_version.clone(), + kind: self.kind.clone(), + metadata: self.metadata.as_input(), + spec: self.spec.clone(), + ..Default::default() + } + } + +} + +/// For creating, only need spec +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct K8SpecObj { + pub api_version: String, + pub kind: String, + pub metadata: M, + pub spec: S, + #[serde(default)] + pub data: BTreeMap, +} + +impl K8SpecObj + where S: Spec + Default +{ + pub fn new(spec: S,metadata: M) -> Self where M: Default { + Self { + api_version: S::api_version(), + kind: S::kind(), + metadata, + spec, + ..Default::default() + } + } +} + +pub type InputK8Obj = K8SpecObj; +pub type UpdateK8Obj = K8SpecObj; + + +/// Used for updating k8obj +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdateK8ObjStatus { + pub api_version: String, + pub kind: String, + pub metadata: UpdateItemMeta, + pub status: P, + pub data: PhantomData +} + + +impl UpdateK8ObjStatus + where S: Spec + Default, + S::Status: Default +{ + + pub fn new(status: S::Status, metadata: UpdateItemMeta) -> Self { + Self { + api_version: S::api_version(), + kind: S::kind(), + metadata, + status, + ..Default::default() + } + } +} + + +impl From> for InputK8Obj where S: Default { + fn from(update: UpdateK8Obj) -> Self { + Self { + api_version: update.api_version, + kind: update.kind, + metadata: update.metadata.into(), + spec: update.spec, + ..Default::default() + } + } +} + + + +impl From for InputObjectMeta { + fn from(update: ItemMeta) -> Self { + Self { + name: update.name, + namespace: update.namespace, + ..Default::default() + } + } +} + +/// name is optional for template +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase",default)] +pub struct TemplateMeta { + pub name: Option, + pub creation_timestamp: Option, + pub labels: HashMap, +} + + +impl LabelProvider for TemplateMeta { + + fn set_label_map(mut self, labels: HashMap) -> Self { + self.labels = labels; + self + } +} + +impl TemplateMeta { + + /// create with name and default namespace + pub fn named(name: S) -> Self where S: Into{ + Self { + name: Some(name.into()), + ..Default::default() + } + } +} + + + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateSpec { + pub metadata: Option, + pub spec: S, +} + +impl TemplateSpec { + pub fn new(spec: S) -> Self { + TemplateSpec { + metadata: None, + spec + } + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct K8List { + pub api_version: String, + pub items: Vec>, + pub kind: String, + pub metadata: ListMetadata +} + +impl K8List where S: Spec { + + #[allow(dead_code)] + pub fn new() -> Self { + K8List { + api_version: S::api_version(), + items: vec![], + kind: S::kind(), + metadata: ListMetadata { + _continue: None, + resource_version: S::api_version(), + self_link: "".to_owned() + } + } + } +} + + + +pub trait DeserializeWith: Sized { + fn deserialize_with<'de, D>(de: D) -> Result + where + D: Deserializer<'de>; +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(tag = "type", content = "object")] +pub enum K8Watch { + ADDED(K8Obj), + MODIFIED(K8Obj), + DELETED(K8Obj), +} + + + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ListMetadata { + pub _continue: Option, + pub resource_version: String, + pub self_link: String, +} + +#[derive(Deserialize, Serialize, Default, Debug, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct LabelSelector { + pub match_labels: HashMap, +} + +impl LabelSelector { + pub fn new_labels>(labels: Vec<(T, T)>) -> Self { + let mut match_labels = HashMap::new(); + for (key, value) in labels { + match_labels.insert(key.into(), value.into()); + } + LabelSelector { match_labels } + } +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Env { + pub name: String, + pub value: Option, + pub value_from: Option +} + +impl Env { + pub fn key_value>(name: T, value: T) -> Self { + Env { + name: name.into(), + value: Some(value.into()), + value_from: None + } + } + + pub fn key_field_ref>(name: T,field_path: T) -> Self { + Env { + name: name.into(), + value: None, + value_from: Some(EnvVarSource{ + field_ref: Some(ObjectFieldSelector{ field_path: field_path.into()}) + }) + } + } +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EnvVarSource { + field_ref: Option +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ObjectFieldSelector { + pub field_path: String +} + +#[cfg(test)] +mod test { + + use super::Env; + use super::ObjectMeta; + + #[test] + fn test_metadata_label() { + let metadata = ObjectMeta::default().set_labels(vec![("app".to_owned(), "test".to_owned())]); + + let maps = metadata.labels; + assert_eq!(maps.len(), 1); + assert_eq!(maps.get("app").unwrap(), "test"); + } + + #[test] + fn test_env() { + let env = Env::key_value("lang", "english"); + assert_eq!(env.name, "lang"); + assert_eq!(env.value, Some("english".to_owned())); + } + +} diff --git a/src/k8-metadata-core/src/options.rs b/src/k8-metadata-core/src/options.rs new file mode 100644 index 00000000..d03569bd --- /dev/null +++ b/src/k8-metadata-core/src/options.rs @@ -0,0 +1,137 @@ +use crate::Crd; +use serde::Serialize; + +/// related to query parameters and uri +/// +/// +/// +/// generate prefix for given crd +/// if crd group is core then /api is used otherwise /apis + group + +pub fn prefix_uri(crd: &Crd, host: &str, namespace: &str, options: Option<&ListOptions>) -> String { + let version = crd.version; + let plural = crd.names.plural; + let group = crd.group.as_ref(); + let api_prefix = match group { + "core" => "api".to_owned(), + _ => format!("apis/{}", group), + }; + + let query = if let Some(opt) = options { + let mut query = "?".to_owned(); + let qs = serde_qs::to_string(opt).unwrap(); + query.push_str(&qs); + query + } else { + "".to_owned() + }; + + format!( + "{}/{}/{}/namespaces/{}/{}{}", + host, api_prefix, version, namespace, plural, query + ) +} + +/// goes as query parameter +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct ListOptions { + pub pretty: Option, + #[serde(rename = "continue")] + pub continu: Option, + pub field_selector: Option, + pub include_uninitialized: Option, + pub label_selector: Option, + pub limit: Option, + pub resource_version: Option, + pub timeout_seconds: Option, + pub watch: Option, +} + +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct DeleteOptions { + pub api_version: Option, + pub grace_period_seconds: Option, + pub kind: Option, + pub orphan_dependents: Option, + pub preconditions: Vec, + pub propagation_policy: Option, +} + +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct Precondition { + pub uid: String, +} + +#[cfg(test)] +mod test { + + use super::prefix_uri; + use super::ListOptions; + use crate::metadata::DEFAULT_NS; + use crate::Crd; + use crate::CrdNames; + + const G1: Crd = Crd { + group: "test.com", + version: "v1", + names: CrdNames { + kind: "Item", + plural: "items", + singular: "item", + }, + }; + + const C1: Crd = Crd { + group: "core", + version: "v1", + names: CrdNames { + kind: "Item", + plural: "items", + singular: "item", + }, + }; + + #[test] + fn test_api_prefix_group() { + let uri = prefix_uri(&G1, "https://localhost", DEFAULT_NS, None); + assert_eq!( + uri, + "https://localhost/apis/test.com/v1/namespaces/default/items" + ); + } + + #[test] + fn test_api_prefix_core() { + let uri = prefix_uri(&C1, "https://localhost", DEFAULT_NS, None); + assert_eq!(uri, "https://localhost/api/v1/namespaces/default/items"); + } + + #[test] + fn test_api_prefix_watch() { + let opt = ListOptions { + watch: Some(true), + ..Default::default() + }; + let uri = prefix_uri(&C1, "https://localhost", DEFAULT_NS, Some(&opt)); + assert_eq!( + uri, + "https://localhost/api/v1/namespaces/default/items?watch=true" + ); + } + + #[test] + fn test_list_query() { + let opt = ListOptions { + pretty: Some(true), + watch: Some(true), + ..Default::default() + }; + + let qs = serde_qs::to_string(&opt).unwrap(); + assert_eq!(qs, "pretty=true&watch=true") + } + +}