From dbb9689fbafe2a72af9e292089625f879ea928d3 Mon Sep 17 00:00:00 2001 From: Dan Norris Date: Mon, 18 Mar 2024 15:20:49 +0100 Subject: [PATCH] Initial commit --- .dockerignore | 6 + .github/workflows/build.yml | 108 + .gitignore | 10 + Cargo.lock | 4746 +++++++++++++++++ Cargo.toml | 94 + Dockerfile | 7 + Dockerfile.local | 12 + LICENSE | 202 + Makefile | 13 + README.md | 164 + crates/types/Cargo.toml | 11 + crates/types/src/lib.rs | 1 + crates/types/src/v1alpha1/mod.rs | 2 + .../src/v1alpha1/wasmcloud_host_config.rs | 74 + deploy/base/deployment.yaml | 156 + deploy/base/kustomization.yaml | 6 + deploy/base/namespace.yaml | 4 + deploy/local/kustomization.yaml | 8 + deploy/local/local-registry.yaml | 3 + sample.yaml | 20 + src/controller.rs | 745 +++ src/crdgen.rs | 9 + src/discovery.rs | 142 + src/docker_secret.rs | 35 + src/header.rs | 90 + src/lib.rs | 57 + src/main.rs | 185 + src/openapi.rs | 779 +++ src/resources/application.rs | 874 +++ src/resources/mod.rs | 1 + src/router.rs | 233 + src/table.rs | 55 + 32 files changed, 8852 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 Dockerfile.local create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 crates/types/Cargo.toml create mode 100644 crates/types/src/lib.rs create mode 100644 crates/types/src/v1alpha1/mod.rs create mode 100644 crates/types/src/v1alpha1/wasmcloud_host_config.rs create mode 100644 deploy/base/deployment.yaml create mode 100644 deploy/base/kustomization.yaml create mode 100644 deploy/base/namespace.yaml create mode 100644 deploy/local/kustomization.yaml create mode 100644 deploy/local/local-registry.yaml create mode 100644 sample.yaml create mode 100644 src/controller.rs create mode 100644 src/crdgen.rs create mode 100644 src/discovery.rs create mode 100644 src/docker_secret.rs create mode 100644 src/header.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/openapi.rs create mode 100644 src/resources/application.rs create mode 100644 src/resources/mod.rs create mode 100644 src/router.rs create mode 100644 src/table.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d656f27 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +target/ +Dockerfile +Makefile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..10869f8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,108 @@ +name: Build and Test +on: + push: + branches: + - "main" + tags: + - "v*" + pull_request: + branches: + - "main" +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Lint + run: | + cargo clippy -- --no-deps + - name: Test + run: | + cargo test + build: + needs: + - check + strategy: + matrix: + arch: ["x86_64", "aarch64"] + runs-on: ubuntu-latest + if: startswith(github.ref, 'refs/tags/v') # Only run on tag push + steps: + - uses: actions/checkout@v4 + - uses: goto-bus-stop/setup-zig@v2 + + - name: Add musl targets + run: | + rustup target add ${{ matrix.arch }}-unknown-linux-musl + + - name: Install cargo-zigbuild + run: | + cargo install cargo-zigbuild + + - name: Build + run: | + cargo zigbuild --release --target ${{matrix.arch}}-unknown-linux-musl + + - name: Store artifact + uses: actions/upload-artifact@v4 + with: + name: wasmcloud-operator-${{matrix.arch}} + path: target/${{matrix.arch}}-unknown-linux-musl/release/wasmcloud-operator + + release: + needs: + - build + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + if: startswith(github.ref, 'refs/tags/v') # Only run on tag push + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix= + type=semver,pattern={{version}} + + - name: Load artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Fix permissions and architectures + run: | + mv artifacts/wasmcloud-operator-x86_64/wasmcloud-operator artifacts/wasmcloud-operator-amd64 + mv artifacts/wasmcloud-operator-aarch64/wasmcloud-operator artifacts/wasmcloud-operator-arm64 + chmod +x artifacts/wasmcloud-operator* + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + push: true + context: . + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + build-args: "BIN_PATH=artifacts/wasmcloud-operator" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73fab07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..639c658 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4746 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "serde", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "any_ascii" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e" + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "ascii_tree" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6c635b3aa665c649ad1415f1573c85957dfa47690ec27aebe7ec17efe3c643" + +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-nats" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc1f1a75fd07f0f517322d103211f12d757658e91676def9a2e688774656c60" +dependencies = [ + "base64 0.21.4", + "bytes", + "futures", + "http", + "memchr", + "nkeys", + "nuid 0.5.0", + "once_cell", + "rand", + "regex", + "ring 0.17.5", + "rustls 0.21.7", + "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki", + "serde", + "serde_json", + "serde_nanos", + "serde_repr", + "thiserror", + "time", + "tokio", + "tokio-retry", + "tokio-rustls 0.24.1", + "tracing", + "url", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "atelier_assembler" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feecf8364a7d1c80f2323ea0d1abee09d7256a91dc5d29a1d171a52c17be36a" +dependencies = [ + "atelier_core", + "atelier_json", + "atelier_smithy", + "atelier_test", + "log", +] + +[[package]] +name = "atelier_core" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c05c6353a26a3e1b7017058a0a99c8f61131cf9a0ecb604dd9b62069b1be75" +dependencies = [ + "error-chain", + "heck 0.3.3", + "lazy_static", + "log", + "paste", + "regex", +] + +[[package]] +name = "atelier_json" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86bad50f33038fa4dec1f5c4e83bc0a1174c17767defeff76b2eca0028ec4bf" +dependencies = [ + "atelier_core", + "serde_json", +] + +[[package]] +name = "atelier_smithy" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56140a8133b56c021ee62e64d35015d1f51b71e0b5cdf401edf78a2a77f2f7e3" +dependencies = [ + "atelier_core", + "log", + "pest", + "pest_ascii_tree", + "pest_derive", +] + +[[package]] +name = "atelier_test" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81fd306eb92079fce9b7089abaf178c306877b72ec6e679ad331253ea9bfb00" +dependencies = [ + "atelier_core", + "pretty_assertions", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-server" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bace45b270e36e3c27a190c65883de6dfc9f1d18c829907c127464815dc67b24" +dependencies = [ + "arc-swap", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "rustls 0.20.9", + "rustls-pemfile", + "tokio", + "tokio-rustls 0.23.4", + "tower-service", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecount" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a12477b7237a01c11a80a51278165f9ba0edd28fa6db00a65ab230320dc58c" + +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cargo_toml" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +dependencies = [ + "serde", + "toml 0.7.8", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.48.5", +] + +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "cloudevents-sdk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801713078518ab05d7c78508c14cf55173a14a1a6659421d3352c2576a6167bf" +dependencies = [ + "base64 0.12.3", + "bitflags 1.3.2", + "chrono", + "delegate-attr", + "hostname", + "serde", + "serde_json", + "snafu", + "url", + "uuid", + "web-sys", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "command-group" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" +dependencies = [ + "async-trait", + "nix 0.22.3", + "tokio", + "winapi", +] + +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml 0.5.11", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ctrlc" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +dependencies = [ + "nix 0.27.1", + "windows-sys 0.48.0", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.38", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "delegate-attr" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee7e7ea0dba407429d816e8e38dda1a467cd74737722f2ccc8eae60429a1a3ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downloader" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05213e96f184578b5f70105d4d0a644a168e99e12d7bea0b200c15d67b5c182" +dependencies = [ + "futures", + "rand", + "reqwest", + "thiserror", + "tokio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2", + "signature", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + +[[package]] +name = "escape_string" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e867569975c88fdf73833a30bd6e0978aa6ab6bd784b648fcece07450951ba" + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fiat-crypto" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fraction" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "handlebars" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "handlebars" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.4", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-auth" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5430cacd7a1f9a02fbeb350dfc81a0e5ed42d81f3398cb0ba184017f85bdcfbc" +dependencies = [ + "memchr", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls 0.21.7", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_executable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +dependencies = [ + "winapi", +] + +[[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonschema" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978" +dependencies = [ + "ahash", + "anyhow", + "base64 0.21.4", + "bytecount", + "clap", + "fancy-regex", + "fraction", + "getrandom", + "iso8601", + "itoa", + "memchr", + "num-cmp", + "once_cell", + "parking_lot", + "percent-encoding", + "regex", + "reqwest", + "serde", + "serde_json", + "time", + "url", + "uuid", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "k8s-openapi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc3606fd16aca7989db2f84bb25684d0270c6d6fa1dbcd0025af7b4130523a6" +dependencies = [ + "base64 0.21.4", + "bytes", + "chrono", + "schemars", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.87.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34392aea935145070dcd5b39a6dea689ac6534d7d117461316c3d157b1d0fc3" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.87.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7266548b9269d9fa19022620d706697e64f312fb2ba31b93e6986453fcc82c92" +dependencies = [ + "base64 0.21.4", + "bytes", + "chrono", + "either", + "futures", + "home", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-timeout", + "jsonpath_lib", + "k8s-openapi", + "kube-core", + "pem", + "pin-project", + "rustls 0.21.7", + "rustls-pemfile", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.87.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8321c315b96b59f59ef6b33f604b84b905ab8f9ff114a4f909d934c520227b1" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "once_cell", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.87.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54591e1f37fc329d412c0fdaced010cc1305b546a39f283fc51700f8fb49421" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.38", +] + +[[package]] +name = "kube-runtime" +version = "0.87.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e511e2c1a368d9d4bf6e70db58197e535d818df355b5a2007a8aeb17a370a8ba" +dependencies = [ + "ahash", + "async-trait", + "backoff", + "derivative", + "futures", + "hashbrown 0.14.1", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "lexical-sort" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09e4591611e231daf4d4c685a66cb0410cc1e502027a20ae55f2bb9e997207a" +dependencies = [ + "any_ascii", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + +[[package]] +name = "nix-nar" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e412c3138d9fcb384da86b4a068c11bad373441eb5c4b89406de4faf649fa02" +dependencies = [ + "is_executable", + "thiserror", +] + +[[package]] +name = "nkeys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad178aad32087b19042ee36dfd450b73f5f934fbfb058b59b198684dfec4c47" +dependencies = [ + "byteorder 1.5.0", + "data-encoding", + "ed25519", + "ed25519-dalek", + "getrandom", + "log", + "rand", + "signatory", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "nuid" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b61b1710432e483e6a67b20b6c60c6afe0e2fad67aabba3bdb912f3f70ff6ae" +dependencies = [ + "once_cell", + "rand", +] + +[[package]] +name = "nuid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83" +dependencies = [ + "rand", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.3", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "oci-distribution" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac5b780ce1bd6c3c2ff72a3013f4b2d56d53ae03b20d424e99d2f6556125138" +dependencies = [ + "futures", + "futures-util", + "http", + "http-auth", + "jwt", + "lazy_static", + "olpc-cjson", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-util", + "tracing", + "unicase", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "opentelemetry" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_api", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "opentelemetry_api" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" +dependencies = [ + "futures-channel", + "futures-util", + "indexmap 1.9.3", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api", + "ordered-float 3.9.2", + "percent-encoding", + "rand", + "regex", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pem" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +dependencies = [ + "base64 0.21.4", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_ascii_tree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152b393638a9816cd3dedb5aea2fb60ab0b641315aa133cea219c8797e768fc" +dependencies = [ + "ascii_tree", + "escape_string", + "pest", + "pest_derive", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platforms" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_assertions" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "provider-archive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e73940ae67588b3e7f1d29798ee5b76b32217fcfb3d9f64d28bd5d6d72c113" +dependencies = [ + "async-compression", + "data-encoding", + "ring 0.16.20", + "serde_json", + "tokio", + "tokio-stream", + "tokio-tar", + "wascap 0.11.2", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.4", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.7", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder 1.5.0", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder 1.5.0", + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring 0.16.20", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.4", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "schemars" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-transcode" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "indexmap 2.0.2", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_nanos" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae801b7733ca8d6a2b580debe99f67f36826a0f5b8a36055dc6bc40f8d6bc71" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_yaml" +version = "0.9.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +dependencies = [ + "indexmap 2.0.2", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signatory" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" +dependencies = [ + "pkcs8", + "rand_core", + "signature", + "zeroize", +] + +[[package]] +name = "signature" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spdx" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" +dependencies = [ + "smallvec", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.4.1", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.6", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.2", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "axum", + "base64 0.21.4", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "base64 0.21.4", + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc09e402904a5261e42cf27aea09ccb7d5318c6717a9eec3d8e2e65c56b18f19" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "ulid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e37c4b6cbcc59a8dcd09a6429fbc7890286bcbb79215cea7b38a3c4c0921d93" +dependencies = [ + "rand", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "utoipa" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff05e3bac2c9428f57ade702667753ca3f5cf085e2011fe697de5bfd49aa72d" +dependencies = [ + "indexmap 2.0.2", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0b6f4667edd64be0e820d6631a60433a269710b6ee89ac39525b872b76d61d" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.38", +] + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +dependencies = [ + "getrandom", + "serde", + "sha1_smol", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wadm" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde964607c5c094365ba1694d1213f6db06b101213cc3c83e289489f53725517" +dependencies = [ + "anyhow", + "async-nats", + "async-trait", + "base64 0.21.4", + "bytes", + "chrono", + "cloudevents-sdk", + "futures", + "indexmap 2.0.2", + "jsonschema", + "lazy_static", + "nkeys", + "rand", + "regex", + "semver", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "uuid", + "wasmcloud-control-interface 0.32.0", +] + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wascap" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc48258fe9b2c61bf78d21f7fd84a22769de339a79b5ef82bcdf7cebd42b7eb6" +dependencies = [ + "data-encoding", + "env_logger", + "humantime", + "lazy_static", + "log", + "nkeys", + "nuid 0.4.1", + "ring 0.16.20", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.29.0", + "wasm-gen", + "wasmparser 0.107.0", +] + +[[package]] +name = "wascap" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6552cf47da47fd35f0ee096df899f30c57edbf4a333d46f6d26b4f2ae3d60d4" +dependencies = [ + "data-encoding", + "humantime", + "log", + "nkeys", + "nuid 0.4.1", + "ring 0.17.5", + "serde", + "serde_json", + "wasm-encoder 0.36.2", + "wasm-gen", + "wasmparser 0.116.1", +] + +[[package]] +name = "wash-lib" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949b0771272896db8f0f305f846602198af91bf0176653947208befff0f47376" +dependencies = [ + "anyhow", + "async-compression", + "async-nats", + "bytes", + "cargo_metadata", + "cargo_toml", + "chrono", + "cloudevents-sdk", + "command-group", + "config", + "dirs", + "futures", + "nkeys", + "oci-distribution", + "provider-archive", + "regex", + "reqwest", + "rmp-serde", + "semver", + "serde", + "serde-transcode", + "serde_cbor", + "serde_json", + "serde_with", + "serde_yaml", + "tempfile", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "toml 0.7.8", + "tracing", + "url", + "wadm", + "walkdir", + "wascap 0.12.0", + "wasm-encoder 0.39.0", + "wasmcloud-component-adapters", + "wasmcloud-control-interface 0.33.0", + "wasmcloud-core", + "wasmparser 0.118.1", + "wat", + "weld-codegen", + "wit-bindgen-core", + "wit-bindgen-go", + "wit-component", + "wit-parser", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-encoder" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822b645bf4f2446b949776ffca47e2af60b167209ffb70814ef8779d299cd421" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-gen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b854b1461005a7b3365742310f7faa3cac3add809d66928c64a40c7e9e842ebb" +dependencies = [ + "byteorder 0.5.3", + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d835d67708f6374937c550ad8dd1d17c616ae009e3f00d7a0ac9f7825e78c36a" +dependencies = [ + "anyhow", + "indexmap 2.0.2", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.38.1", + "wasmparser 0.118.1", +] + +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmcloud-component-adapters" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723e26862f961cd3a2ed48689d4644cffcfea3fe6930093446b6f827d0cf8d09" +dependencies = [ + "anyhow", + "base64 0.21.4", + "futures", + "nix-nar", + "once_cell", + "reqwest", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tokio-util", +] + +[[package]] +name = "wasmcloud-control-interface" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c67369563f873509343dd6bad5f2bcd07ccb4db09d882dcf85efff581ed8a0" +dependencies = [ + "async-nats", + "bytes", + "cloudevents-sdk", + "futures", + "opentelemetry", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "wasmcloud-control-interface" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4513791a2a28f4be7e128bf00afae3cefa05dd84f3594a80afd5b2afae071f27" +dependencies = [ + "anyhow", + "async-nats", + "bytes", + "cloudevents-sdk", + "futures", + "oci-distribution", + "opentelemetry", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "wasmcloud-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c2e23c8783f19a578576f5315681f3d08aa493b392e6b79bb1c61130d78c8f" +dependencies = [ + "anyhow", + "async-nats", + "futures", + "hex", + "nkeys", + "serde", + "serde_bytes", + "sha2", + "tokio", + "tracing", + "ulid", + "uuid", + "wascap 0.12.0", +] + +[[package]] +name = "wasmcloud-operator" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-nats", + "atty", + "axum", + "axum-server", + "ctrlc", + "futures", + "handlebars 5.1.0", + "json-patch", + "k8s-openapi", + "kube", + "opentelemetry", + "opentelemetry-otlp", + "rcgen", + "schemars", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "time", + "tokio", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "utoipa", + "uuid", + "wadm", + "wash-lib", + "wasmcloud-operator-types", +] + +[[package]] +name = "wasmcloud-operator-types" +version = "0.1.0" +dependencies = [ + "k8s-openapi", + "kube", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "wasmparser" +version = "0.107.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +dependencies = [ + "indexmap 1.9.3", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.0.2", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.118.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9" +dependencies = [ + "indexmap 2.0.2", + "semver", +] + +[[package]] +name = "wast" +version = "69.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ee37317321afde358e4d7593745942c48d6d17e0e6e943704de9bbee121e7a" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.38.1", +] + +[[package]] +name = "wat" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb338ee8dee4d4cd05e6426683f21c5087dc7cfc8903e839ccf48d43332da3c" +dependencies = [ + "wast", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "weld-codegen" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66385fd8affabb09b678d2f826c78d995789cdb2fbb2dd02e6010dffe298797e" +dependencies = [ + "Inflector", + "anyhow", + "atelier_assembler", + "atelier_core", + "atelier_json", + "atelier_smithy", + "bytes", + "cfg-if", + "clap", + "directories", + "downloader", + "handlebars 4.5.0", + "lazy_static", + "lexical-sort", + "reqwest", + "rustc-hash", + "semver", + "serde", + "serde_json", + "tempfile", + "thiserror", + "toml 0.7.8", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-c" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e75780118d295fc82b33e7417e2e2f43082424c5f6e2d3965fa8cfc062f5a" +dependencies = [ + "anyhow", + "heck 0.4.1", + "wasm-encoder 0.38.1", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d55e1a488af2981fb0edac80d8d20a51ac36897a1bdef4abde33c29c1b6d0d" +dependencies = [ + "anyhow", + "wit-component", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-go" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "218f35e08713515d8b6f7fb8f823e1b503e833738b674b391a65787a472fc1df" +dependencies = [ + "anyhow", + "heck 0.4.1", + "wit-bindgen-c", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-component" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2" +dependencies = [ + "anyhow", + "bitflags 2.4.1", + "indexmap 2.0.2", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.38.1", + "wasm-metadata", + "wasmparser 0.118.1", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.0.2", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "xattr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7acc36a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "wasmcloud-operator" +version = "0.1.0" +edition = "2021" + +[[bin]] +doc = false +name = "wasmcloud-operator" +path = "src/main.rs" + +[[bin]] +doc = false +name = "crdgen" +path = "src/crdgen.rs" + +[lib] +name = "controller" +path = "src/lib.rs" + +[workspace.package] +edition = "2021" + +[dependencies] +async-nats = {workspace = true} +axum = {workspace = true} +axum-server = {workspace = true} +anyhow = {workspace = true} +atty = {workspace = true} +ctrlc = {workspace = true} +futures = {workspace = true} +handlebars = {workspace = true} +json-patch = {workspace = true} +k8s-openapi = {workspace = true, features = ["v1_28", "schemars"]} +kube = {workspace = true, features = ["runtime", "derive", "default"]} +opentelemetry = {workspace = true} +opentelemetry-otlp = {workspace = true} +rcgen = {workspace = true} +schemars = {workspace = true} +secrecy = {workspace = true} +serde = {workspace = true} +serde_json = {workspace = true} +serde_yaml = {workspace = true} +thiserror = {workspace = true} +time = {workspace = true} +tokio = {workspace = true} +tracing = {workspace = true} +tracing-opentelemetry = {workspace = true} +tracing-subscriber = {workspace = true} +utoipa = {workspace = true} +uuid = {workspace = true} +wadm = {workspace = true} +wash-lib = {workspace = true} +wasmcloud-operator-types = {workspace = true} + +[workspace.dependencies] +async-nats = "0.33" +axum = { version = "0.6", features = ["headers"] } +axum-server = { version = "0.4", features = ["tls-rustls"] } +anyhow = "1" +atty = "0.2" +ctrlc = "3" +futures = "0.3" +handlebars = "5.1" +json-patch = "1.2.0" +k8s-openapi = { version = "0.20", default-features = false} +kube = { version = "0.87", default-features = false} +opentelemetry = { version = "0.20", features = ["metrics", "trace", "rt-tokio"] } +opentelemetry-otlp = { version = "0.13", features = ["tokio"] } +rcgen = "0.11" +schemars = "0.8" +secrecy = "0.8" +serde = "1" +serde_json = "1" +serde_yaml = "0.9" +thiserror = "1" +time = "0.3" +tokio = { version = "1", features = ["full"] } +tracing = "0.1" +tracing-opentelemetry = "0.20" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +utoipa = { version = "4.1", features = ["axum_extras"] } +uuid = { version = "1", features = ["v5"] } +wadm = "0.10" +wash-lib = "0.17" +wasmcloud-operator-types = { version="*", path = "./crates/types" } + +[workspace] +members = [ + "crates/*" +] +resolver = "2" + +[profile.release] +strip = true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..168949b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +# syntax=docker/dockerfile:1 +FROM gcr.io/distroless/cc-debian12 +ARG BIN_PATH +ARG TARGETARCH + +COPY ${BIN_PATH}-${TARGETARCH} /usr/local/bin/wasmcloud-operator +ENTRYPOINT ["/usr/local/bin/wasmcloud-operator"] diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 0000000..44263d5 --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,12 @@ +# syntax=docker/dockerfile:1 +FROM rust:1.75-bookworm as builder + +WORKDIR /app +COPY . . +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/app/target \ + cargo build --release && cp target/release/wasmcloud-operator . + +FROM gcr.io/distroless/cc-debian12 +COPY --from=builder /app/wasmcloud-operator /usr/local/bin/ +ENTRYPOINT ["/usr/local/bin/wasmcloud-operator"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + 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/Makefile b/Makefile new file mode 100644 index 0000000..0759b1a --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +repo := ghcr.io/wasmcloud/wasmcloud-operator +version := $(shell git rev-parse --short HEAD) +platforms := linux/amd64,linux/arm64 + +.PHONY: build-dev-image build-image buildx-image +build-image: + docker build -t $(repo):$(version) . + +buildx-image: + docker buildx build --platform $(platforms) -t $(repo):$(version) --load . + +build-dev-image: + docker build -t $(repo):$(version)-dev -f Dockerfile.local . diff --git a/README.md b/README.md new file mode 100644 index 0000000..910200f --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# wasmcloud-operator + +An operator for managing a set of wasmCloud hosts running on Kubernetes and +manage wasmCloud appliations using WADM. +The goal is to easily be able to run WasmCloud hosts on a Kubernetes cluster. + +## CusterConfig Custom Resource Definition (CRD) + +The WasmCloudHostConfig CRD describes the desired state of a set of wasmCloud +hosts connected to the same lattice. + +```yaml +apiVersion: k8s.wasmcloud.dev/v1alpha1 +kind: WasmCloudHostConfig +metadata: + name: my-wasmcloud-cluster +spec: + # The number of wasmCloud host pods to run + hostReplicas: 2 + # The cluster issuers to use for each host + issuers: + - CDKF6OKPOBQKAX57UOXO7SCHURTOZWKWIVPC2HFJTGFXY5VJX44ECEHH + # The lattice to connect the hosts to + lattice: 83a5b52e-17cf-4080-bac8-f844099f142e + # Additional labels to apply to the host other than the defaults set in the operator + hostLabels: + some-label: value + # Which wasmCloud version to use + version: 0.81.0 + # The name of a secret in the same namespace that provides the required secrets. + secretName: cluster-secrets +``` + +The CRD requires a Kubernetes Secret with the following keys: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-wasmcloud-cluster +data: + # You can generate this with wash: + # wash keys gen cluster + WASMCLOUD_CLUSTER_SEED: + # Only required if using a NATS creds file + # nats.creds: + # Only required if using OCI private registry + # OCI_REGISTRY_PASSWORD: +``` + +The operator will fail to provision the wasmCloud Deployment if any of these +secrets are missing! + +## Deploying the operator + +A wasmCloud cluster requires a few things to run: + +- A NATS cluster with Jetstream enabled +- WADM connected to the NATS cluster in order to support applications + +If you are running locally, you can use the following commands to start a +NATS cluster and WADM in your Kubernetes cluster. + +### Running NATS + +Use the upstream NATS Helm chart to start a cluster with the following +values.yaml file: + +```yaml +config: + cluster: + enabled: true + replicas: 3 + leafnodes: + enabled: true + jetstream: + enabled: true + fileStore: + pvc: + size: 10Gi + merge: + domain: default +``` + +```sh +helm upgrade --install -f values.yaml nats-cluster nats/nats +``` + +### Running WADM + +WADM can be run as a standalone binary or as a container. The following +command will start WADM as a Kubernetes deployment: + +```sh +``` + +### Start the operator + +```sh +kubectl kustomize build deploy/local | kubectl apply -f - +``` + +## Argo CD Health Check + +Argo CD provides a way to define a [custom health +check](https://argo-cd.readthedocs.io/en/stable/operator-manual/health/#custom-health-checks) +that it then runs against a given resource to determine whether or not the +resource is in healthy state. + +For this purpose, we specifically expose a `status.phase` field, which exposes +the underlying status information from wadm. + +With the following ConfigMap, a custom health check can be added to an existing +Argo CD installation for tracking the health of wadm applications. + +```yaml +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-cm + namespace: argocd + labels: + app.kubernetes.io/name: argocd-cm + app.kubernetes.io/part-of: argocd +data: + resource.customizations: | + core.oam.dev/Application: + health.lua: | + hs = {} + hs.status = "Progressing" + hs.message = "Reconciling application state" + if obj.status ~= nil and obj.status.phase ~= nil then + if obj.status.phase == "Deployed" then + hs.status = "Healthy" + hs.message = "Application is ready" + end + if obj.status.phase == "Reconciling" then + hs.status = "Progressing" + hs.message = "Application has been deployed" + end + if obj.status.phase == "Failed" then + hs.status = "Degraded" + hs.message = "Application failed to deploy" + end + if obj.status.phase == "Undeployed" then + hs.status = "Suspended" + hs.message = "Application is undeployed" + end + end + return hs +``` + +## Testing + +- Make sure you have a Kubernetes cluster running locally. Some good options + include [Kind](https://kind.sigs.k8s.io/) or Docker Desktop. +- `RUST_LOG=info cargo run` + + +## Types crate + +This repo stores the types for any CRDs used by the operator in a separate +crate (`wasmcloud-operator-types`) so that they can be reused in other projects. diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 0000000..fdd974b --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wasmcloud-operator-types" +version = "0.1.0" +edition = "2021" + +[dependencies] +k8s-openapi = {workspace = true} +kube = {workspace = true, features = ["derive"]} +schemars = {workspace = true} +serde = {workspace = true} +serde_json = {workspace = true} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 0000000..32a5a9d --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1 @@ +pub mod v1alpha1; diff --git a/crates/types/src/v1alpha1/mod.rs b/crates/types/src/v1alpha1/mod.rs new file mode 100644 index 0000000..f3906a4 --- /dev/null +++ b/crates/types/src/v1alpha1/mod.rs @@ -0,0 +1,2 @@ +pub mod wasmcloud_host_config; +pub use wasmcloud_host_config::*; diff --git a/crates/types/src/v1alpha1/wasmcloud_host_config.rs b/crates/types/src/v1alpha1/wasmcloud_host_config.rs new file mode 100644 index 0000000..8eeac7d --- /dev/null +++ b/crates/types/src/v1alpha1/wasmcloud_host_config.rs @@ -0,0 +1,74 @@ +use k8s_openapi::api::core::v1::ResourceRequirements; +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)] +#[cfg_attr(test, derive(Default))] +#[kube( + kind = "WasmCloudHostConfig", + group = "k8s.wasmcloud.dev", + version = "v1alpha1", + shortname = "chc", + namespaced, + status = "WasmCloudHostConfigStatus", + printcolumn = r#"{"name":"App Count", "type":"integer", "jsonPath":".status.app_count"}"# +)] +#[serde(rename_all = "camelCase")] +pub struct WasmCloudHostConfigSpec { + pub host_replicas: u32, + pub issuers: Vec, + pub lattice: String, + pub host_labels: Option>, + pub version: String, + pub secret_name: String, + pub enable_structured_logging: Option, + pub registry_credentials_secret: Option, + pub resources: Option, + pub control_topic_prefix: Option, + #[serde(default = "default_leaf_node_domain")] + pub leaf_node_domain: String, + #[serde(default)] + pub config_service_enabled: bool, + #[serde(default = "default_nats_address")] + pub nats_address: String, + #[serde(default = "default_jetstream_domain")] + pub jetstream_domain: String, + #[serde(default = "default_log_level")] + pub log_level: String, +} + +fn default_jetstream_domain() -> String { + "default".to_string() +} + +fn default_nats_address() -> String { + "nats://nats.default.svc.cluster.local".to_string() +} + +fn default_leaf_node_domain() -> String { + "leaf".to_string() +} + +fn default_log_level() -> String { + "INFO".to_string() +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +pub struct WasmCloudHostConfigResources { + pub nats: Option, + pub wasmcloud: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)] +pub struct WasmCloudHostConfigStatus { + pub apps: Vec, + pub app_count: u32, +} + +#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)] +pub struct AppStatus { + pub name: String, + pub version: String, +} diff --git a/deploy/base/deployment.yaml b/deploy/base/deployment.yaml new file mode 100644 index 0000000..0e4e477 --- /dev/null +++ b/deploy/base/deployment.yaml @@ -0,0 +1,156 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: wasmcloud-operator + name: wasmcloud-operator +spec: + selector: + matchLabels: + app: wasmcloud-operator + template: + metadata: + labels: + app: wasmcloud-operator + spec: + serviceAccountName: wasmcloud-operator + containers: + - image: ghcr.io/wasmcloud/wasmcloud-operator:latest + imagePullPolicy: Always + name: wasmcloud-operator + ports: + - containerPort: 8443 + name: https + protocol: TCP + env: + - name: RUST_LOG + value: info + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: wasmcloud-operator + labels: + app: wasmcloud-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: wasmcloud-operator +rules: + - apiGroups: + - "" + resources: + - secrets + - services + - configmaps + - serviceaccounts + verbs: + - get + - list + - watch + - create + - delete + - patch + - update + - apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - watch + - create + - delete + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - get + - list + - watch + - create + - delete + - patch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch + - create + - delete + - patch + - apiGroups: + - apiregistration.k8s.io + resources: + - apiservices + verbs: + - create + - delete + - get + - list + - patch + - update + - apiGroups: + - k8s.wasmcloud.dev + resources: + - wasmcloudhostconfigs + - wasmcloudhostconfigs/status + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: wasmcloud-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: wasmcloud-operator +subjects: + - apiGroup: "" + kind: ServiceAccount + name: wasmcloud-operator + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: wasmcloud-operator-delegator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: + - apiGroup: "" + kind: ServiceAccount + name: wasmcloud-operator + namespace: default +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: wasmcloud-operator + name: wasmcloud-operator +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + app: wasmcloud-operator + type: ClusterIP diff --git a/deploy/base/kustomization.yaml b/deploy/base/kustomization.yaml new file mode 100644 index 0000000..464e7b3 --- /dev/null +++ b/deploy/base/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - deployment.yaml +namespace: wasmcloud-operator diff --git a/deploy/base/namespace.yaml b/deploy/base/namespace.yaml new file mode 100644 index 0000000..93f3c61 --- /dev/null +++ b/deploy/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: wasmcloud-operator diff --git a/deploy/local/kustomization.yaml b/deploy/local/kustomization.yaml new file mode 100644 index 0000000..0caba34 --- /dev/null +++ b/deploy/local/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ../base +patches: + - path: local-registry.yaml + target: + kind: Deployment diff --git a/deploy/local/local-registry.yaml b/deploy/local/local-registry.yaml new file mode 100644 index 0000000..3770763 --- /dev/null +++ b/deploy/local/local-registry.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/template/spec/containers/0/image + value: localhost:5001/wasmcloud-operator:latest diff --git a/sample.yaml b/sample.yaml new file mode 100644 index 0000000..da4fd67 --- /dev/null +++ b/sample.yaml @@ -0,0 +1,20 @@ +apiVersion: k8s.wasmcloud.dev/v1alpha1 +kind: WasmCloudHostConfig +metadata: + name: my-wasmcloud-cluster + namespace: default +spec: + hostReplicas: 2 + issuers: + - CDKF6OKPOBQKAX57UOXO7SCHURTOZWKWIVPC2HFJTGFXY5VJX44ECEHH + # The lattice to connect the hosts to + lattice: 83a5b52e-17cf-4080-bac8-f844099f142e + # Additional labels to apply to the host other than the defaults set in the controller + hostLabels: + test: value + # Which wasmCloud version to use + version: 0.81.0 + # The name of a secret in the same namespace that provides the required secrets. + secretName: cluster-secrets + logLevel: INFO + natsAddress: nats://nats-cluster.default.svc.cluster.local diff --git a/src/controller.rs b/src/controller.rs new file mode 100644 index 0000000..0792c25 --- /dev/null +++ b/src/controller.rs @@ -0,0 +1,745 @@ +use crate::docker_secret::DockerConfigJson; +use crate::{Error, Result}; +use anyhow::bail; +use futures::StreamExt; +use handlebars::Handlebars; +use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec}; +use k8s_openapi::api::core::v1::{ + ConfigMap, ConfigMapVolumeSource, Container, ContainerPort, EnvVar, EnvVarSource, ExecAction, + Lifecycle, LifecycleHandler, PodSpec, PodTemplateSpec, Secret, SecretKeySelector, + SecretVolumeSource, Service, ServiceAccount, ServicePort, ServiceSpec, Volume, VolumeMount, +}; +use k8s_openapi::api::rbac::v1::{PolicyRule, Role, RoleBinding, RoleRef, Subject}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector; +use kube::{ + api::{Api, ObjectMeta, Patch, PatchParams}, + client::Client, + runtime::{ + controller::{Action, Config, Controller}, + finalizer::{finalizer, Event as Finalizer}, + watcher, + }, + Resource, ResourceExt, +}; +use secrecy::{ExposeSecret, SecretString}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::{BTreeMap, HashMap}; +use std::str::from_utf8; +use std::sync::Arc; +use tokio::{sync::RwLock, time::Duration}; +use tracing::{info, warn}; +use wasmcloud_operator_types::v1alpha1::{ + AppStatus, WasmCloudHostConfig, WasmCloudHostConfigStatus, +}; + +pub static CLUSTER_CONFIG_FINALIZER: &str = "wasmcloudhost.k8s.wasmcloud.dev"; + +/// Default host that identifies a host that is running in kubernetes +pub const K8S_HOST_TAG: &str = "kubernetes"; +/// wasmCloud custom label for controlling routing +pub const WASMCLOUD_ROUTE_TO_LABEL: &str = "wasmcloud.dev/route-to"; + +#[derive(Clone)] +pub struct Context { + pub client: Client, + pub wasmcloud_config: WasmcloudConfig, + pub nats_creds: Arc>>, +} + +#[derive(Clone, Default)] +pub struct Secrets { + pub wasmcloud_cluster_seed: String, + pub nats_creds: Option, +} + +impl Secrets { + fn from_k8s_secret(secret: &Secret) -> Result { + if secret.data.as_ref().is_none() { + bail!( + "Secret {} has no data", + secret.metadata.name.as_ref().unwrap() + ); + }; + let data = secret.data.as_ref().unwrap(); + let wasmcloud_cluster_seed = data.get("WASMCLOUD_CLUSTER_SEED"); + let nats_creds = data.get("nats.creds"); + + if wasmcloud_cluster_seed.is_none() { + bail!( + "Secret {} has no WASMCLOUD_CLUSTER_SEED", + secret.metadata.name.as_ref().unwrap() + ); + }; + + Ok(Self { + wasmcloud_cluster_seed: from_utf8(&wasmcloud_cluster_seed.unwrap().0)?.to_string(), + nats_creds: match &nats_creds { + Some(c) => from_utf8(&c.0).ok().map(|s| s.to_string()), + None => None, + }, + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] +pub struct WasmcloudConfig {} + +pub async fn reconcile(cluster: Arc, ctx: Arc) -> Result { + let cluster_configs: Api = + Api::namespaced(ctx.client.clone(), &cluster.namespace().unwrap()); + + info!( + "Reconciling WasmCloudHostConfig \"{}\" in {}", + cluster.name_any(), + cluster.namespace().unwrap() + ); + finalizer( + &cluster_configs, + CLUSTER_CONFIG_FINALIZER, + cluster, + |event| async { + match event { + Finalizer::Apply(config) => reconcile_crd(&config, ctx.clone()).await, + Finalizer::Cleanup(config) => cleanup(&config, ctx.clone()).await, + } + }, + ) + .await + .map_err(|e| Error::FinalizerError(Box::new(e))) +} + +async fn reconcile_crd(config: &WasmCloudHostConfig, ctx: Arc) -> Result { + let client = ctx.client.clone(); + let ns = config.namespace().unwrap(); + let name = config.name_any(); + let config: Api = Api::namespaced(client.clone(), &ns); + let mut cfg = config.get(&name).await?; + let secrets = Api::::namespaced(client, &ns); + + let secret = secrets.get(&cfg.spec.secret_name).await.map_err(|e| { + warn!("Failed to read secrets: {}", e); + e + })?; + let s = Secrets::from_k8s_secret(&secret).map_err(|e| { + warn!("Failed to read secrets: {}", e); + Error::SecretError(format!( + "Failed to read all secrets from {}: {}", + secret.metadata.name.unwrap(), + e + )) + })?; + + if let Err(e) = configmap(&cfg, ctx.clone(), s.nats_creds.is_some()).await { + warn!("Failed to reconcile configmap: {}", e); + return Err(e); + }; + + if let Some(nats_creds) = &s.nats_creds { + if let Err(e) = store_nats_creds(&cfg, ctx.clone(), nats_creds.clone()).await { + warn!("Failed to reconcile secret: {}", e); + return Err(e); + }; + } + + // TODO(protochron) these could be split out into separate functions. + if let Err(e) = configure_auth(&cfg, ctx.clone()).await { + warn!("Failed to configure auth: {}", e); + return Err(e); + }; + + if let Err(e) = configure_deployment(&cfg, ctx.clone()).await { + warn!("Failed to configure deployment: {}", e); + return Err(e); + }; + + if let Err(e) = configure_service(&cfg, ctx.clone()).await { + warn!("Failed to configure service: {}", e); + return Err(e); + }; + + let nc = s.nats_creds.map(SecretString::new); + let apps = crate::resources::application::list_apps( + &cfg.spec.nats_address, + nc.as_ref(), + cfg.spec.lattice.clone(), + ) + .await + .map_err(|e| { + warn!("Failed to list apps: {}", e); + Error::NatsError(format!("Failed to list apps: {}", e)) + })?; + + let app_names = apps + .iter() + .map(|a| AppStatus { + name: a.name.clone(), + version: a.version.clone(), + }) + .collect::>(); + info!("Found apps: {:?}", app_names); + cfg.status = Some(WasmCloudHostConfigStatus { + apps: app_names.clone(), + app_count: app_names.len() as u32, + }); + + config + .patch_status( + &name, + &PatchParams::default(), + &Patch::Merge(serde_json::json!({ "status": cfg.status.unwrap() })), + ) + .await + .map_err(|e| { + warn!("Failed to update status: {}", e); + e + })?; + + Ok(Action::requeue(Duration::from_secs(5 * 60))) +} + +async fn cleanup(config: &WasmCloudHostConfig, ctx: Arc) -> Result { + let client = ctx.client.clone(); + let ns = config.namespace().unwrap(); + let name = config.name_any(); + let _configs: Api = Api::namespaced(client, &ns); + + let nst = NameNamespace::new(name.clone(), ns.clone()); + ctx.nats_creds.write().await.remove(&nst); + + Ok(Action::await_change()) +} + +fn deployment_spec(config: &WasmCloudHostConfig, _ctx: Arc) -> DeploymentSpec { + let mut labels = BTreeMap::new(); + labels.insert("app.kubernetes.io/name".to_string(), config.name_any()); + labels.insert("app.kubernetes.io/instance".to_string(), config.name_any()); + let ls = LabelSelector { + match_labels: Some(labels.clone()), + ..Default::default() + }; + + let mut wasmcloud_env = vec![ + EnvVar { + name: "WASMCLOUD_CLUSTER_SEED".to_string(), + value_from: Some(EnvVarSource { + secret_key_ref: Some(SecretKeySelector { + name: Some(config.spec.secret_name.clone()), + key: "WASMCLOUD_CLUSTER_SEED".to_string(), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_STRUCTURED_LOGGING_ENABLED".to_string(), + value: Some( + config + .spec + .enable_structured_logging + .unwrap_or(true) + .to_string(), + ), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_LOG_LEVEL".to_string(), + value: Some(config.spec.log_level.clone()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_JS_DOMAIN".to_string(), + value: Some(config.spec.jetstream_domain.clone()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_LATTICE".to_string(), + value: Some(config.spec.lattice.clone()), + ..Default::default() + }, + // TODO: remove this after wasmCloud 1.0 is released + EnvVar { + name: "WASMCLOUD_LATTICE_PREFIX".to_string(), + value: Some(config.spec.lattice.clone()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_CLUSTER_ISSUERS".to_string(), + value: Some(config.spec.issuers.join(",")), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_NATS_HOST".to_string(), + value: Some("127.0.0.1".to_string()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_NATS_PORT".to_string(), + value: Some("4222".to_string()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_RPC_TIMEOUT_MS".to_string(), + value: Some("4000".to_string()), + ..Default::default() + }, + EnvVar { + name: "WASMCLOUD_LABEL_kubernetes".to_string(), + value: Some("true".to_string()), + ..Default::default() + }, + ]; + + if let Some(prefix) = &config.spec.control_topic_prefix { + wasmcloud_env.push(EnvVar { + name: "WASMCLOUD_CTL_TOPIC_PREFIX".to_string(), + value: Some(prefix.clone()), + ..Default::default() + }); + } + + if config.spec.config_service_enabled { + wasmcloud_env.push(EnvVar { + name: "WASMCLOUD_CONFIG_SERVICE".to_string(), + value: Some("true".to_string()), + ..Default::default() + }); + } + + if let Some(labels) = &config.spec.host_labels { + for (k, v) in labels.iter() { + wasmcloud_env.push(EnvVar { + name: format!("WASMCLOUD_LABEL_{}", k), + value: Some(v.clone()), + ..Default::default() + }); + } + } + + let mut nats_resources: Option = None; + let mut wasmcloud_resources: Option = None; + if let Some(resources) = &config.spec.resources { + nats_resources = resources.nats.clone(); + wasmcloud_resources = resources.wasmcloud.clone(); + } + + let containers = vec![ + Container { + name: "nats-leaf".to_string(), + image: Some("nats:2.10-alpine".to_string()), + args: Some(vec![ + "-js".to_string(), + "--config".to_string(), + "/nats/nats.conf".to_string(), + ]), + image_pull_policy: Some("IfNotPresent".to_string()), + ports: Some(vec![ContainerPort { + container_port: 4222, + ..Default::default() + }]), + // This is a hack to ensure that the wasmcloud host relays a host stopped event + lifecycle: Some(Lifecycle { + pre_stop: Some(LifecycleHandler { + exec: Some(ExecAction { + command: Some(vec!["sleep".to_string(), "10".to_string()]), + }), + ..Default::default() + }), + ..Default::default() + }), + resources: nats_resources, + volume_mounts: Some(vec![ + VolumeMount { + name: "nats-config".to_string(), + mount_path: "/nats/nats.conf".to_string(), + read_only: Some(true), + sub_path: Some("nats.conf".to_string()), + ..Default::default() + }, + VolumeMount { + name: "nats-creds".to_string(), + mount_path: "/nats/nats.creds".to_string(), + sub_path: Some("nats.creds".to_string()), + read_only: Some(true), + ..Default::default() + }, + ]), + ..Default::default() + }, + Container { + name: "wasmcloud-host".to_string(), + image: Some(format!("wasmcloud/wasmcloud:{}", config.spec.version)), + env: Some(wasmcloud_env), + resources: wasmcloud_resources, + ..Default::default() + }, + ]; + DeploymentSpec { + replicas: Some(config.spec.host_replicas as i32), + selector: ls, + template: PodTemplateSpec { + metadata: Some(ObjectMeta { + labels: Some(labels), + ..Default::default() + }), + spec: Some(PodSpec { + service_account: Some(config.name_any()), + containers, + volumes: Some(vec![ + Volume { + name: "nats-config".to_string(), + config_map: Some(ConfigMapVolumeSource { + name: Some(config.name_any()), + ..Default::default() + }), + ..Default::default() + }, + Volume { + name: "nats-creds".to_string(), + secret: Some(SecretVolumeSource { + secret_name: Some(config.spec.secret_name.clone()), + ..Default::default() + }), + ..Default::default() + }, + ]), + ..Default::default() + }), + }, + ..Default::default() + } +} + +async fn configure_deployment(config: &WasmCloudHostConfig, ctx: Arc) -> Result<()> { + let mut spec = deployment_spec(config, ctx.clone()); + + if let Some(registry_credentials) = &config.spec.registry_credentials_secret { + let secrets_client = + Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + let secret = secrets_client + .get(registry_credentials) + .await + .map_err(|e| { + warn!("Failed to get image pull secret: {}", e); + e + })?; + + let docker_config = DockerConfigJson::from_secret(secret.clone()).map_err(|e| { + warn!("Failed to convert image pull secret: {}", e); + Error::SecretError(format!( + "Failed to convert image pull secret {}: {}", + secret.metadata.name.clone().unwrap(), + e + )) + })?; + + if docker_config.auths.len() > 1 { + warn!("Only one registry is supported"); + return Err(Error::SecretError(format!( + "Only one registry is supported: the secret named {} contains {}", + secret.metadata.name.clone().unwrap(), + docker_config.auths.len() + ))); + } + + let mut env_vars = vec![ + EnvVar { + name: "OCI_REGISTRY_USER".to_string(), + value: Some( + docker_config + .auths + .values() + .next() + .unwrap() + .username + .clone(), + ), + ..Default::default() + }, + EnvVar { + name: "OCI_REGISTRY_PASSWORD".to_string(), + value: Some( + docker_config + .auths + .values() + .next() + .unwrap() + .password + .expose_secret() + .clone(), + ), + ..Default::default() + }, + EnvVar { + name: "OCI_REGISTRY".to_string(), + value: Some(docker_config.auths.keys().next().unwrap().clone()), + ..Default::default() + }, + ]; + + spec.template.spec.as_mut().unwrap().containers[1] + .env + .as_mut() + .unwrap() + .append(&mut env_vars); + } + + let deployment = Deployment { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + ..Default::default() + }, + spec: Some(spec), + ..Default::default() + }; + + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(deployment), + ) + .await?; + Ok(()) +} + +async fn configure_service(config: &WasmCloudHostConfig, ctx: Arc) -> Result<()> { + let mut label_selector = BTreeMap::new(); + label_selector.insert("app.kubernetes.io/name".to_string(), config.name_any()); + label_selector.insert("app.kubernetes.io/instance".to_string(), config.name_any()); + + let mut labels = label_selector.clone(); + labels.insert("wasmcloud.dev/route-to".to_string(), "true".to_string()); + + let svc = Service { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + labels: Some(labels), + ..Default::default() + }, + spec: Some(ServiceSpec { + selector: Some(label_selector), + ports: Some(vec![ServicePort { + port: 4222, + ..Default::default() + }]), + ..Default::default() + }), + ..Default::default() + }; + + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(svc), + ) + .await?; + Ok(()) +} + +async fn configure_auth(config: &WasmCloudHostConfig, ctx: Arc) -> Result<()> { + let svc_account = ServiceAccount { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + ..Default::default() + }, + ..Default::default() + }; + + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(svc_account), + ) + .await?; + + let rules = vec![PolicyRule { + api_groups: Some(vec!["".to_string()]), + resources: Some(vec!["services".to_string()]), + verbs: vec![ + "get".to_string(), + "list".to_string(), + "create".to_string(), + "update".to_string(), + "patch".to_string(), + "delete".to_string(), + ], + ..Default::default() + }]; + let role = Role { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + ..Default::default() + }, + rules: Some(rules), + }; + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(role), + ) + .await?; + + let role_binding = RoleBinding { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + ..Default::default() + }, + role_ref: RoleRef { + api_group: "rbac.authorization.k8s.io".to_string(), + kind: "Role".to_string(), + name: config.name_any(), + }, + subjects: Some(vec![Subject { + kind: "ServiceAccount".to_string(), + name: config.name_any(), + namespace: Some(config.namespace().unwrap()), + ..Default::default() + }]), + }; + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(role_binding), + ) + .await?; + + Ok(()) +} + +async fn configmap( + config: &WasmCloudHostConfig, + ctx: Arc, + use_nats_creds: bool, +) -> Result<()> { + let template = r#" +jetstream { + domain: {{jetstream_domain}} +} +leafnodes { + remotes: [ + { + url: "{{cluster_url}}:7422" + {{#if use_credentials}} + credentials: "/nats/nats.creds" + {{/if}} + } + ] +} +"#; + let tpl = Handlebars::new(); + let rendered = tpl.render_template(template, &json!({"jetstream_domain": config.spec.leaf_node_domain, "cluster_url": config.spec.nats_address, "use_credentials": use_nats_creds}))?; + let mut contents = BTreeMap::new(); + contents.insert("nats.conf".to_string(), rendered); + let cm = ConfigMap { + metadata: ObjectMeta { + name: Some(config.name_any()), + namespace: Some(config.namespace().unwrap()), + owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]), + ..Default::default() + }, + data: Some(contents), + ..Default::default() + }; + + let api = Api::::namespaced(ctx.client.clone(), &config.namespace().unwrap()); + api.patch( + &config.name_any(), + &PatchParams::apply(CLUSTER_CONFIG_FINALIZER), + &Patch::Apply(cm), + ) + .await?; + + Ok(()) +} + +async fn store_nats_creds( + config: &WasmCloudHostConfig, + ctx: Arc, + creds: String, +) -> Result<()> { + let nst = NameNamespace::new(config.name_any(), config.namespace().unwrap()); + ctx.nats_creds + .write() + .await + .insert(nst, SecretString::new(creds)); + Ok(()) +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Hash)] +pub struct NameNamespace { + pub name: String, + pub namespace: String, +} + +impl NameNamespace { + pub(crate) fn new(name: String, namespace: String) -> Self { + Self { name, namespace } + } +} + +#[derive(Clone, Default)] +pub struct State { + pub nats_creds: Arc>>, + pub config: WasmcloudConfig, +} + +impl State { + pub fn new(config: WasmcloudConfig) -> Self { + Self { + config, + ..Default::default() + } + } +} + +fn error_policy(_object: Arc, _error: &Error, _ctx: Arc) -> Action { + Action::requeue(Duration::from_secs(1)) +} + +pub async fn run(state: State) -> anyhow::Result<()> { + let client = Client::try_default().await?; + + let configs = Api::::all(client.clone()); + let cms = Api::::all(client.clone()); + let deployments = Api::::all(client.clone()); + let secrets = Api::::all(client.clone()); + + let config = Config::default(); + let ctx = Context { + client, + wasmcloud_config: state.config.clone(), + nats_creds: state.nats_creds.clone(), + }; + + Controller::new(configs, watcher::Config::default()) + .owns(cms, watcher::Config::default()) + .owns(deployments, watcher::Config::default()) + .owns(secrets, watcher::Config::default()) + .with_config(config) + .shutdown_on_signal() + .run(reconcile, error_policy, Arc::new(ctx)) + .for_each(|res| async move { + match res { + Ok(o) => info!("reconciled {:?}", o), + Err(e) => warn!("reconcile failed: {}", e), + } + }) + .await; + Ok(()) +} diff --git a/src/crdgen.rs b/src/crdgen.rs new file mode 100644 index 0000000..0861540 --- /dev/null +++ b/src/crdgen.rs @@ -0,0 +1,9 @@ +use kube::CustomResourceExt; +use wasmcloud_operator_types::v1alpha1::WasmCloudHostConfig; + +fn main() { + print!( + "{}", + serde_yaml::to_string(&WasmCloudHostConfig::crd()).unwrap() + ) +} diff --git a/src/discovery.rs b/src/discovery.rs new file mode 100644 index 0000000..f47b26e --- /dev/null +++ b/src/discovery.rs @@ -0,0 +1,142 @@ +use kube::core::{GroupVersionKind, ListMeta, ObjectMeta}; +use serde::Serialize; + +/// DiscoveryFreshness is an enum defining whether the Discovery document published by an apiservice is up to date (fresh). +#[derive(Clone, Debug, Serialize)] +pub enum DiscoveryFreshness { + Current, + Stale, +} + +/// ResourceScope is an enum defining the different scopes available to a resource. +#[derive(Clone, Debug, Serialize)] +pub enum ResourceScope { + Cluster, + Namespaced, +} + +/// APIGroupDiscoveryList is a resource containing a list of APIGroupDiscovery. +/// This is one of the types able to be returned from the /api and /apis endpoint and contains an aggregated +/// list of API resources (built-ins, Custom Resource Definitions, resources from aggregated servers) +/// that a cluster supports. +// Based on https://github.com/kubernetes/kubernetes/blob/ec5096fa869b801d6eb1bf019819287ca61edc4d/pkg/apis/apidiscovery/types.go#L25-L37 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct APIGroupDiscoveryList { + pub kind: String, + pub api_version: String, + // ResourceVersion will not be set, because this does not have a replayable ordering among multiple apiservers. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + pub metadata: ListMeta, + // items is the list of groups for discovery. The groups are listed in priority order. + pub items: Vec, +} + +impl Default for APIGroupDiscoveryList { + fn default() -> Self { + Self { + api_version: "apidiscovery.k8s.io/v2beta1".to_string(), + kind: "APIGroupDiscoveryList".to_string(), + metadata: ListMeta::default(), + items: vec![], + } + } +} + +/// APIGroupDiscovery holds information about which resources are being served for all version of the API Group. +/// It contains a list of APIVersionDiscovery that holds a list of APIResourceDiscovery types served for a version. +/// Versions are in descending order of preference, with the first version being the preferred entry. +// Based on https://github.com/kubernetes/kubernetes/blob/ec5096fa869b801d6eb1bf019819287ca61edc4d/pkg/apis/apidiscovery/types.go#L41-L58 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct APIGroupDiscovery { + pub kind: Option, + pub api_version: Option, + // Standard object's metadata. + // The only field completed will be name. For instance, resourceVersion will be empty. + // name is the name of the API group whose discovery information is presented here. + // name is allowed to be "" to represent the legacy, ungroupified resources. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + pub metadata: ObjectMeta, + // versions are the versions supported in this group. They are sorted in descending order of preference, + // with the preferred version being the first entry. + pub versions: Vec, // `json:"versions,omitempty" protobuf:"bytes,2,rep,name=versions"` +} + +/// APIVersionDiscovery holds a list of APIResourceDiscovery types that are served for a particular version within an API Group. +// Based on https://github.com/kubernetes/kubernetes/blob/ec5096fa869b801d6eb1bf019819287ca61edc4d/pkg/apis/apidiscovery/types.go#L60-L77 +#[derive(Clone, Debug, Serialize)] +pub struct APIVersionDiscovery { + // version is the name of the version within a group version. + pub version: String, + // resources is a list of APIResourceDiscovery objects for the corresponding group version. + pub resources: Vec, + // freshness marks whether a group version's discovery document is up to date. + // "Current" indicates the discovery document was recently + // refreshed. "Stale" indicates the discovery document could not + // be retrieved and the returned discovery document may be + // significantly out of date. Clients that require the latest + // version of the discovery information be retrieved before + // performing an operation should not use the aggregated document + // and instead retrieve the necessary version docs directly. + pub freshness: DiscoveryFreshness, +} + +/// APIResourceDiscovery provides information about an API resource for discovery. +// Based on https://github.com/kubernetes/kubernetes/blob/ec5096fa869b801d6eb1bf019819287ca61edc4d/pkg/apis/apidiscovery/types.go#L79-L113 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct APIResourceDiscovery { + // resource is the plural name of the resource. This is used in the URL path and is the unique identifier + // for this resource across all versions in the API group. + // Resources with non-empty groups are located at /apis/// + // Resources with empty groups are located at /api/v1/ + pub resource: String, + // responseKind describes the group, version, and kind of the serialization schema for the object type this endpoint typically returns. + // APIs may return other objects types at their discretion, such as error conditions, requests for alternate representations, or other operation specific behavior. + // This value will be null or empty if an APIService reports subresources but supports no operations on the parent resource + pub response_kind: GroupVersionKind, + // scope indicates the scope of a resource, either Cluster or Namespaced + pub scope: ResourceScope, + // singularResource is the singular name of the resource. This allows clients to handle plural and singular opaquely. + // For many clients the singular form of the resource will be more understandable to users reading messages and should be used when integrating the name of the resource into a sentence. + // The command line tool kubectl, for example, allows use of the singular resource name in place of plurals. + // The singular form of a resource should always be an optional element - when in doubt use the canonical resource name. + pub singular_resource: String, + // verbs is a list of supported API operation types (this includes + // but is not limited to get, list, watch, create, update, patch, + // delete, deletecollection, and proxy). + pub verbs: Vec, + // shortNames is a list of suggested short names of the resource. + pub short_names: Vec, + // categories is a list of the grouped resources this resource belongs to (e.g. 'all'). + // Clients may use this to simplify acting on multiple resource types at once. + pub categories: Vec, + // subresources is a list of subresources provided by this resource. Subresources are located at /apis////name-of-instance/ + pub subresources: Vec, +} + +/// APISubresourceDiscovery provides information about an API subresource for discovery. +// Based on https://github.com/kubernetes/kubernetes/blob/ec5096fa869b801d6eb1bf019819287ca61edc4d/pkg/apis/apidiscovery/types.go#L131C1-L156 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct APISubresourceDiscovery { + // subresource is the name of the subresource. This is used in the URL path and is the unique identifier + // for this resource across all versions. + pub subresource: String, + // responseKind describes the group, version, and kind of the serialization schema for the object type this endpoint typically returns. + // Some subresources do not return normal resources, these will have null or empty return types. + pub response_kind: GroupVersionKind, + // acceptedTypes describes the kinds that this endpoint accepts. + // Subresources may accept the standard content types or define + // custom negotiation schemes. The list may not be exhaustive for + // all operations. + pub accepted_types: Vec, + // verbs is a list of supported API operation types (this includes + // but is not limited to get, list, watch, create, update, patch, + // delete, deletecollection, and proxy). Subresources may define + // custom verbs outside the standard Kubernetes verb set. Clients + // should expect the behavior of standard verbs to align with + // Kubernetes interaction conventions. + pub verbs: Vec, +} diff --git a/src/docker_secret.rs b/src/docker_secret.rs new file mode 100644 index 0000000..7486b12 --- /dev/null +++ b/src/docker_secret.rs @@ -0,0 +1,35 @@ +use anyhow::anyhow; +use k8s_openapi::api::core::v1::Secret; +use secrecy::SecretString; +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Deserialize, Debug)] +pub struct DockerConfigJson { + pub auths: HashMap, +} + +#[derive(Deserialize, Debug)] +pub struct DockerConfigJsonAuth { + pub username: String, + pub password: SecretString, + pub auth: String, +} + +impl DockerConfigJson { + pub fn from_secret(secret: Secret) -> Result { + if let Some(data) = secret.data.clone() { + let bytes = data + .get(".dockerconfigjson") + .ok_or(anyhow!("No .dockerconfigjson in secret"))?; + let b = bytes.clone(); + + match std::str::from_utf8(&b.0) { + Ok(s) => Ok(serde_json::from_str(s)?), + Err(e) => Err(anyhow!("Error decoding secret: {}", e)), + } + } else { + Err(anyhow!("No data in secret")) + } + } +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..ecf188c --- /dev/null +++ b/src/header.rs @@ -0,0 +1,90 @@ +use axum::headers::{Error, Header, HeaderName, HeaderValue}; +use axum::http::header::ACCEPT; +use std::fmt; + +#[derive(Debug)] +pub enum As { + APIGroupDiscoveryList, + PartialObjectMetadataList, + Table, + NotSpecified, +} + +impl fmt::Display for As { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + As::APIGroupDiscoveryList => write!(f, "APIGroupDiscoveryList"), + As::PartialObjectMetadataList => write!(f, "PartialObjectMetadataList"), + As::Table => write!(f, "Table"), + As::NotSpecified => write!(f, "NotSpecified"), + } + } +} + +impl From for As { + fn from(accept: Accept) -> Self { + accept.0 + } +} + +#[derive(Debug)] +pub struct Accept(As); + +impl std::fmt::Display for Accept { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match self.0 { + As::APIGroupDiscoveryList => write!(f, "APIGroupDiscoveryList"), + As::PartialObjectMetadataList => write!(f, "PartialObjectMetadataList"), + As::Table => write!(f, "Table"), + As::NotSpecified => write!(f, "NotSpecified"), + } + } +} + +// Parses Accept headers from kube-apiserver/aggregator and turns them into Enum values. +// Some examples include: +// * application/json;g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList +// * application/json;as=Table;g=meta.k8s.io;v=v1 +impl Header for Accept { + fn name() -> &'static HeaderName { + &ACCEPT + } + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + let header_value = values.next(); + // We need to return a default rather than error here, because without it + // kube-aggregator will freak out about the responses, for some reason. + if header_value.is_none() { + return Ok(Accept(As::NotSpecified)); + } + + let value = header_value.unwrap().to_str().unwrap_or("n/a"); + + let parts: Vec<&str> = value + .split(';') + .filter(|p| p.starts_with("as=")) + .map(|p| p.strip_prefix("as=").unwrap_or("")) + .collect(); + + let header = match parts.into_iter().nth(0) { + Some("APIGroupDiscoveryList") => Accept(As::APIGroupDiscoveryList), + Some("PartialObjectMetadataList") => Accept(As::PartialObjectMetadataList), + Some("Table") => Accept(As::Table), + None => Accept(As::NotSpecified), + // This exists to satisfy rust-analyzer, we should probably try to + // figure out if a new type was added that we need to be responding to. + Some(&_) => Accept(As::NotSpecified), + }; + Ok(header) + } + fn encode(&self, values: &mut E) + where + E: Extend, + { + let s = self.0.to_string(); + let value = HeaderValue::from_static(s.leak()); + values.extend(std::iter::once(value)); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6362907 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,57 @@ +use axum::{ + body, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use handlebars::RenderError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("SerializationError: {0}")] + SerializationError(#[source] serde_json::Error), + + #[error("Kube Error: {0}")] + KubeError(#[from] kube::Error), + + #[error("Finalizer Error: {0}")] + // NB: awkward type because finalizer::Error embeds the reconciler error (which is this) + // so boxing this error to break cycles + FinalizerError(#[source] Box>), + + #[error("IllegalDocument")] + IllegalDocument, + + #[error("NATS error: {0}")] + NatsError(String), + + #[error("Request error: {0}")] + RequestError(String), + + #[error("Error retrieving secrets: {0}")] + SecretError(String), + + #[error("Error rendering template: {0}")] + RenderError(#[from] RenderError), +} +pub type Result = std::result::Result; + +impl IntoResponse for Error { + fn into_response(self) -> Response { + let mut resp = Response::new(body::boxed(self.to_string())); + *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + resp + } +} + +pub mod controller; +pub mod discovery; +pub mod docker_secret; +pub mod header; +pub(crate) mod openapi; +pub mod resources; +pub mod router; +pub(crate) mod table; + +pub use crate::controller::*; +pub use crate::resources::application::{delete_application, get_application, list_applications}; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..045c531 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,185 @@ +use anyhow::{anyhow, Result}; +use axum_server::{tls_rustls::RustlsConfig, Handle}; +use controller::{State, WasmcloudConfig}; + +use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use k8s_openapi::kube_aggregator::pkg::apis::apiregistration::v1::{ + APIService, APIServiceSpec, ServiceReference, +}; +use kube::{ + api::{Api, Patch, PatchParams, PostParams}, + client::Client, + CustomResourceExt, +}; +use opentelemetry::sdk::{ + trace::{self, RandomIdGenerator, Sampler}, + Resource as OTELResource, +}; +use std::net::SocketAddr; +use std::time::Duration; +use tracing::{error, info}; +use tracing_subscriber::layer::SubscriberExt; + +use wasmcloud_operator_types::v1alpha1::WasmCloudHostConfig; + +#[tokio::main] +async fn main() -> Result<()> { + let tracing_enabled = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok(); + configure_tracing(tracing_enabled).map_err(|e| { + error!("Failed to configure tracing: {}", e); + e + })?; + info!("Starting controller"); + + let config = WasmcloudConfig {}; + + let client = Client::try_default().await?; + install_crd(&client).await?; + + let state = State::new(config); + let ctl = controller::run(state.clone()); + let router = controller::router::setup(state.clone()); + + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?; + let tls_config = RustlsConfig::from_der( + vec![cert.serialize_der()?], + cert.serialize_private_key_der(), + ) + .await?; + + let handle = Handle::new(); + let addr = SocketAddr::from(([0, 0, 0, 0], 8443)); + let server = axum_server::bind_rustls(addr, tls_config) + .handle(handle.clone()) + .serve(router.into_make_service()); + tokio::spawn(async move { + info!("Starting apiserver"); + let res = server.await; + if let Err(e) = res { + error!("Error running apiserver: {}", e); + } + }); + + ctl.await?; + handle.graceful_shutdown(Some(Duration::from_secs(3))); + info!("Controller finished"); + Ok(()) +} + +fn configure_tracing(enabled: bool) -> anyhow::Result<()> { + let tracer = opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter(opentelemetry_otlp::new_exporter().tonic()) + .with_trace_config( + trace::config() + .with_sampler(Sampler::AlwaysOn) + .with_id_generator(RandomIdGenerator::default()) + .with_max_attributes_per_span(32) + .with_max_events_per_span(32) + .with_resource(OTELResource::new(vec![opentelemetry::KeyValue::new( + "service.name", + "wasmcloud-operator", + )])), + ) + .install_simple()?; + + let env_filter_layer = tracing_subscriber::EnvFilter::from_default_env(); + let log_layer = tracing_subscriber::fmt::layer() + .with_writer(std::io::stderr) + .with_ansi(atty::is(atty::Stream::Stderr)); + + let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer); + + if enabled { + let subscriber = tracing_subscriber::Registry::default() + .with(env_filter_layer) + .with(log_layer) + .with(otel_layer); + + let _ = tracing::subscriber::set_global_default(subscriber); + } else { + let subscriber = tracing_subscriber::Registry::default() + .with(env_filter_layer) + .with(log_layer); + + let _ = tracing::subscriber::set_global_default(subscriber); + } + Ok(()) +} + +// Initialize the CRD in the cluster +async fn install_crd(client: &Client) -> anyhow::Result<()> { + let crds = Api::::all(client.clone()); + let crd = &WasmCloudHostConfig::crd(); + let registrations = Api::::all(client.clone()); + + let crd_name = crd.metadata.name.as_ref().unwrap(); + // TODO(protochron) validate that the crd is upd to date and patch if needed + // This doesn't work for some reason with status subresources if they change, probably because + // they're not actually embedded in the struct + if let Ok(old_crd) = crds.get(crd_name.as_str()).await { + if old_crd != *crd { + info!("Updating CRD"); + crds.patch( + crd_name.as_str(), + // https://github.com/kubernetes/client-go/issues/1036 + // regarding +yaml: https://kubernetes.io/docs/reference/using-api/server-side-apply/#serialization + &PatchParams::apply("application/apply-patch+yaml").force(), + &Patch::Apply(crd), + ) + .await?; + } + } else { + crds.create(&PostParams::default(), crd) + .await + .map_err(|e| anyhow!("failed to create crd: {e}"))?; + } + + let namespace = std::env::var("POD_NAMESPACE").unwrap_or("default".to_string()); + let mut registration = APIService { + metadata: ObjectMeta { + name: Some("v1beta1.core.oam.dev".to_string()), + ..Default::default() + }, + spec: Some(APIServiceSpec { + group: Some("core.oam.dev".to_string()), + group_priority_minimum: 2100, + insecure_skip_tls_verify: Some(true), + version_priority: 100, + version: Some("v1beta1".to_string()), + service: Some(ServiceReference { + name: Some("wasmcloud-operator".to_string()), + namespace: Some(namespace), + port: Some(8443), + }), + ..Default::default() + }), + ..Default::default() + }; + + let old_reg = registrations.get("v1beta1.core.oam.dev").await; + if let Ok(old) = old_reg { + info!("Updating APIService"); + let resource_version = old.metadata.resource_version.unwrap(); + registration.metadata.resource_version = Some(resource_version); + + // Wholesale replace because we're terrible people and don't care at all about existing OAM + // managment controllers + registrations + .replace( + "v1beta1.core.oam.dev", + &PostParams::default(), + ®istration, + ) + .await?; + } else { + info!("Creating APIService"); + registrations + .create(&PostParams::default(), ®istration) + .await + .map_err(|e| anyhow!("failed to create registration: {e}"))?; + }; + + Ok(()) +} diff --git a/src/openapi.rs b/src/openapi.rs new file mode 100644 index 0000000..5f7abfd --- /dev/null +++ b/src/openapi.rs @@ -0,0 +1,779 @@ +use std::collections::{BTreeMap, HashMap}; + +use axum::{ + http::{HeaderMap, StatusCode, Uri}, + response::IntoResponse, + routing::get, + Json, Router, +}; +use serde::{Deserialize, Serialize}; +use tracing::debug; +use utoipa::{OpenApi, ToSchema}; + +/* TODO: + * - Add full support for Kubernetes' OpenAPI spec: https://github.com/kubernetes/kubernetes/tree/master/api/openapi-spec + * - Add namespace and labels support: + * - Without namespaces we get an "InvalidSpecError": https://github.com/argoproj/argo-cd/blob/a761a495f16d76c0a8e50359eda50f605e329aba/controller/state.go#L694-L696 + */ + +/// An OAM manifest +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Application)] +#[serde(rename = "Application")] +pub struct Application { + /// The OAM version of the manifest + #[serde(rename = "apiVersion")] + pub api_version: String, + /// The kind or type of manifest described by the spec + pub kind: String, + /// Metadata describing the manifest + pub metadata: Metadata, + /// The specification for this manifest + pub spec: Specification, +} + +/// The metadata describing the manifest +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Metadata)] +pub struct Metadata { + /// The name of the manifest. This should be unique + pub name: String, + // This is to satisfy ArgoCD's validation. + pub namespace: String, + /// Optional data for annotating this manifest + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub annotations: BTreeMap, + // This is to satisfy ArgoCD's validation. + pub labels: BTreeMap, +} + +/// A representation of an OAM specification +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Specification)] +pub struct Specification { + /// The list of components for describing an application + pub components: Vec, +} + +/// A component definition +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Component)] +pub struct Component { + /// The name of this component + pub name: String, + /// The type of component + /// The properties for this component + // NOTE(thomastaylor312): It would probably be better for us to implement a custom deserialze + // and serialize that combines this and the component type. This is good enough for first draft + #[serde(flatten)] + pub properties: Properties, + /// A list of various traits assigned to this component + #[serde(skip_serializing_if = "Option::is_none")] + pub traits: Option>, +} + +/// Properties that can be defined for a component +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Properties)] +#[serde(tag = "type")] +pub enum Properties { + #[serde(rename = "actor")] + Actor { properties: ActorProperties }, + #[serde(rename = "capability")] + Capability { properties: CapabilityProperties }, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::ActorProperties)] +pub struct ActorProperties { + /// The image reference to use + pub image: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::CapabilityProperties)] +pub struct CapabilityProperties { + /// The image reference to use + pub image: String, + /// The contract ID of this capability + pub contract: String, + /// An optional link name to use for this capability + #[serde(skip_serializing_if = "Option::is_none")] + pub link_name: Option, + /// Optional config to pass to the provider. This can be either a raw string encoded config, or + /// a JSON or YAML object + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, +} + +/// Right now providers can technically use any config format they want, although most use JSON. +/// This enum takes that into account and allows either type of data to be passed +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::CapabilityConfig)] +pub enum CapabilityConfig { + Json(serde_json::Value), + Opaque(String), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Trait)] +pub struct Trait { + /// The type of trait specified. This should be a unique string for the type of scaler. As we + /// plan on supporting custom scalers, these traits are not enumerated + #[serde(rename = "type")] + pub trait_type: String, + /// The properties of this trait + pub properties: TraitProperty, +} + +/// Properties for defining traits +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::TraitProperty)] +#[serde(untagged)] +pub enum TraitProperty { + Linkdef(LinkdefProperty), + SpreadScaler(SpreadScalerProperty), + // TODO(thomastaylor312): This is still broken right now with deserializing. If the incoming + // type specifies replicas, it matches with spreadscaler first. So we need to implement a custom + // parser here + Custom(serde_json::Value), +} + +/// Properties for linkdefs +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::LinkdefProperty)] +pub struct LinkdefProperty { + /// The target this linkdef applies to. This should be the name of an actor component + pub target: String, + /// Values to use for this linkdef + #[serde(skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Properties for spread scalers +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::SpreadScalerProperty)] +pub struct SpreadScalerProperty { + /// Number of replicas to scale + pub replicas: usize, + /// Requirements for spreading throse replicas + #[serde(default)] + pub spread: Vec, +} + +/// Configuration for various spreading requirements +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[schema(as = dev::oam::core::v1beta1::Spread)] +pub struct Spread { + /// The name of this spread requirement + pub name: String, + /// An arbitrary map of labels to match on for scaling requirements + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub requirements: BTreeMap, + /// An optional weight for this spread. Higher weights are given more precedence + #[serde(skip_serializing_if = "Option::is_none")] + pub weight: Option, +} + +#[derive(OpenApi)] +#[openapi( + components(schemas( + Application, + Metadata, + Specification, + Component, + Properties, + ActorProperties, + CapabilityProperties, + CapabilityConfig, + Trait, + TraitProperty, + LinkdefProperty, + SpreadScalerProperty, + Spread, + )), + info( + description = "The OAM Application API provides a way to manage applications in a Kubernetes cluster." + ), + paths( + crate::router::api_resources, + crate::resources::application::create_application, + crate::resources::application::list_applications, + crate::resources::application::get_application, + crate::resources::application::patch_application, + crate::resources::application::delete_application, + ) +)] +pub struct ApiDoc; + +#[derive(Serialize)] +struct OpenApiV3Discovery { + paths: HashMap, +} + +#[derive(Serialize)] +struct OpenApiV3DiscoveryGroupVersion { + #[serde(rename = "serverRelativeURL")] + server_relative_url: String, +} + +impl OpenApiV3Discovery { + pub fn new() -> Self { + let mut paths = HashMap::new(); + paths.insert( + "apis/core.oam.dev/v1beta1".to_string(), + OpenApiV3DiscoveryGroupVersion { + server_relative_url: "/openapi/v3/apis/core.oam.dev/v1beta1".to_string(), + }, + ); + Self { paths } + } +} + +pub fn router() -> Router { + Router::new() + .route("/v3", get(openapi_v3)) + .route("/v3/apis/core.oam.dev/v1beta1", get(openapi_v3_details)) + .route("/v2", get(openapi_v2)) + .fallback(fallback) +} + +async fn fallback(headers: HeaderMap, uri: Uri) -> (StatusCode, String) { + debug!("openapi fallback: uri={uri}"); + for (hk, hv) in headers.iter() { + debug!("hk={hk}, hv={}", hv.to_str().unwrap()) + } + (StatusCode::NOT_FOUND, format!("No route for {uri}")) +} + +async fn openapi_v3() -> Json { + //let doc = ApiDoc::openapi().to_json().unwrap(); + let root = OpenApiV3Discovery::new(); + Json(root) +} + +async fn openapi_v3_details() -> Json { + // TODO add actual OAM docs in this + // We may need to copy/paste the OAM spec from wadm here to add the right annotations or at + // least use type annotation. + let doc = ApiDoc::openapi(); + Json(serde_json::to_value(doc).unwrap()) +} + +async fn openapi_v2() -> impl IntoResponse { + OPENAPI_V2_SPEC_JSON.into_response() +} + +const OPENAPI_V2_SPEC_JSON: &str = r##" +{ + "info": { + "title": "wasmcloud-operator", + "description": "The OAM Application API provides a way to manage applications in a Kubernetes cluster.", + "license": { + "name": "" + }, + "version": "0.1.6" + }, + "paths": { + "/apis/core.oam.dev/v1beta1": { + "get": { + "tags": [ + "crate::router" + ], + "operationId": "api_resources", + "responses": {}, + "parameters": [] + } + }, + "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications": { + "get": { + "tags": [ + "crate::resources::application" + ], + "operationId": "list_applications", + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": {}, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + }, + "post": { + "tags": [ + "crate::resources::application" + ], + "operationId": "create_application", + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "description": "", + "required": true, + "name": "body", + "in": "body", + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": {}, + "consumes": [ + "application/octet-stream" + ], + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + } + }, + "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications/{name}": { + "get": { + "tags": [ + "crate::resources::application" + ], + "operationId": "get_application", + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": {}, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + }, + "delete": { + "tags": [ + "crate::resources::application" + ], + "operationId": "delete_application", + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": {}, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + }, + "patch": { + "tags": [ + "crate::resources::application" + ], + "operationId": "patch_application", + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "in": "path", + "required": true, + "type": "string" + }, + { + "description": "", + "required": true, + "name": "body", + "in": "body", + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": {}, + "consumes": [ + "application/octet-stream" + ], + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + } + } + }, + "swagger": "2.0", + "definitions": { + "dev.oam.core.v1beta1.ActorProperties": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "description": "The image reference to use" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "ActorProperties", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Application": { + "type": "object", + "description": "An OAM manifest", + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "The OAM version of the manifest" + }, + "kind": { + "type": "string", + "description": "The kind or type of manifest described by the spec" + }, + "metadata": { + "$ref": "#/definitions/dev.oam.core.v1beta1.Metadata" + }, + "spec": { + "$ref": "#/definitions/dev.oam.core.v1beta1.Specification" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Application", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.CapabilityConfig": { + "description": "Right now providers can technically use any config format they want, although most use JSON.\nThis enum takes that into account and allows either type of data to be passed", + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "CapabilityConfig", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.CapabilityProperties": { + "type": "object", + "required": [ + "image", + "contract" + ], + "properties": { + "config": { + "allOf": [ + { + "$ref": "#/definitions/dev.oam.core.v1beta1.CapabilityConfig" + } + ], + "x-nullable": true + }, + "contract": { + "type": "string", + "description": "The contract ID of this capability" + }, + "image": { + "type": "string", + "description": "The image reference to use" + }, + "link_name": { + "type": "string", + "description": "An optional link name to use for this capability", + "x-nullable": true + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "CapabilityProperties", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Component": { + "allOf": [ + { + "$ref": "#/definitions/dev.oam.core.v1beta1.Properties" + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of this component" + }, + "traits": { + "type": "array", + "items": { + "$ref": "#/definitions/dev.oam.core.v1beta1.Trait" + }, + "description": "A list of various traits assigned to this component", + "x-nullable": true + } + } + } + ], + "description": "A component definition", + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Component", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.LinkdefProperty": { + "type": "object", + "description": "Properties for linkdefs", + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "The target this linkdef applies to. This should be the name of an actor component" + }, + "values": { + "type": "object", + "description": "Values to use for this linkdef", + "additionalProperties": { + "type": "string" + }, + "x-nullable": true + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "LinkdefProperty", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Metadata": { + "type": "object", + "description": "The metadata describing the manifest", + "required": [ + "name" + ], + "properties": { + "annotations": { + "type": "object", + "description": "Optional data for annotating this manifest", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "type": "string", + "description": "The name of the manifest. This should be unique" + }, + "namespace": { + "type": "string", + "description": "The namespace for the application." + }, + "labels": { + "type": "object", + "description": "Optional data for labeling this manifest", + "additionalProperties": { + "type": "string" + } + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Metadata", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Properties": { + "description": "Properties that can be defined for a component", + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Properties", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Specification": { + "type": "object", + "description": "A representation of an OAM specification", + "required": [ + "components" + ], + "properties": { + "components": { + "type": "array", + "items": { + "$ref": "#/definitions/dev.oam.core.v1beta1.Component" + }, + "description": "The list of components for describing an application" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Specification", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Spread": { + "type": "object", + "description": "Configuration for various spreading requirements", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of this spread requirement" + }, + "requirements": { + "type": "object", + "description": "An arbitrary map of labels to match on for scaling requirements", + "additionalProperties": { + "type": "string" + } + }, + "weight": { + "type": "integer", + "description": "An optional weight for this spread. Higher weights are given more precedence", + "minimum": 0, + "x-nullable": true + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Spread", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.SpreadScalerProperty": { + "type": "object", + "description": "Properties for spread scalers", + "required": [ + "replicas" + ], + "properties": { + "replicas": { + "type": "integer", + "description": "Number of replicas to scale", + "minimum": 0 + }, + "spread": { + "type": "array", + "items": { + "$ref": "#/definitions/dev.oam.core.v1beta1.Spread" + }, + "description": "Requirements for spreading throse replicas" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "SpreadScalerProperty", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.Trait": { + "type": "object", + "required": [ + "type", + "properties" + ], + "properties": { + "properties": { + "$ref": "#/definitions/dev.oam.core.v1beta1.TraitProperty" + }, + "type": { + "type": "string", + "description": "The type of trait specified. This should be a unique string for the type of scaler. As we\nplan on supporting custom scalers, these traits are not enumerated" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "Trait", + "version": "v1beta1" + } + ] + }, + "dev.oam.core.v1beta1.TraitProperty": { + "description": "Properties for defining traits", + "x-kubernetes-group-version-kind": [ + { + "group": "core.oam.dev", + "kind": "TraitProperty", + "version": "v1beta1" + } + ] + } + }, + "x-components": {} +} +"##; diff --git a/src/resources/application.rs b/src/resources/application.rs new file mode 100644 index 0000000..ea2534b --- /dev/null +++ b/src/resources/application.rs @@ -0,0 +1,874 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::sync::Arc; + +use anyhow::{anyhow, Error}; +use async_nats::ConnectOptions; +use axum::{ + body::Bytes, + extract::{Path, State as AxumState}, + http::StatusCode, + response::{IntoResponse, Json, Response}, + TypedHeader, +}; +use kube::{ + api::{Api, ListParams}, + client::Client, + core::{ListMeta, ObjectMeta}, +}; +use secrecy::{ExposeSecret, SecretString}; +use serde::Serialize; +use serde_json::json; +use tokio::sync::RwLock; +use tracing::error; +use uuid::Uuid; +use wadm::{ + model::Manifest, + server::{ + DeleteResult, DeployResult, GetResult, ModelSummary, PutResult, StatusResult, StatusType, + }, +}; +use wasmcloud_operator_types::v1alpha1::WasmCloudHostConfig; + +use crate::{ + controller::State, + header::{Accept, As}, + router::{internal_error, not_found_error}, + table::{TableColumnDefinition, TableRow}, + NameNamespace, +}; + +/* TODO: + * - Add a way to store Kubernetes **Namespace** the App belongs to. + * - Possibly using annotations that are set automatically when app is deployed into the cluster. + * - Add a way to store app.kubernetes.io/name label and other Recommended labels: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ + * - Current hack results in a "SharedResourceWarning" warning in the UI: https://github.com/argoproj/argo-cd/blob/a761a495f16d76c0a8e50359eda50f605e329aba/controller/state.go#L529-L537 + * - Add a way to support all of the Argo resource tracking methods: https://argo-cd.readthedocs.io/en/stable/user-guide/resource_tracking/ + */ + +const GROUP_VERSION: &str = "core.oam.dev/v1beta1"; + +pub struct AppError(Error); + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error: {}", self.0), + ) + .into_response() + } +} + +impl From for AppError +where + E: Into, +{ + fn from(e: E) -> Self { + Self(e.into()) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Application { + pub api_version: String, + pub kind: String, + pub metadata: ObjectMeta, +} + +impl Application { + pub fn new(name: String) -> Self { + Self { + api_version: "v1beta1".to_string(), + kind: "Application".to_string(), + metadata: ObjectMeta { + name: Some(name), + ..Default::default() + }, + } + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationList { + api_version: String, + kind: String, + items: Vec, + metadata: ListMeta, +} + +impl ApplicationList { + pub fn new() -> Self { + Self { + api_version: GROUP_VERSION.to_string(), + kind: "ApplicationList".to_string(), + items: vec![], + metadata: ListMeta::default(), + } + } +} + +impl Default for ApplicationList { + fn default() -> Self { + Self::new() + } +} + +impl From> for ApplicationList { + fn from(partials: Vec) -> Self { + let mut al = ApplicationList::default(); + // TODO(joonas): Let's figure out a better way to do this shall we? + let v = serde_json::to_value(&partials).unwrap(); + let resource_version = Uuid::new_v5(&Uuid::NAMESPACE_OID, v.to_string().as_bytes()); + al.metadata.resource_version = Some(resource_version.to_string()); + al.items = partials; + al + } +} + +#[derive(Debug, Clone, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationPartial { + metadata: ObjectMeta, + spec: BTreeMap, + status: BTreeMap, +} + +impl From for ApplicationPartial { + fn from(summary: ModelSummary) -> Self { + let ns = format!("{}/{}", summary.name, summary.version); + let uid = Uuid::new_v5(&Uuid::NAMESPACE_OID, ns.as_bytes()); + Self { + metadata: ObjectMeta { + name: Some(summary.name), + // TODO(joonas): Infer this, or make it something that can be set later. + namespace: Some("default".to_string()), + resource_version: Some(uid.to_string()), + uid: Some(uid.to_string()), + ..Default::default() + }, + ..Default::default() + } + } +} + +// Definition: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Table +// Based on https://github.com/kubernetes-sigs/metrics-server/blob/master/pkg/api/table.go +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationTable { + api_version: String, + kind: String, + column_definitions: Vec, + rows: Vec, + metadata: ListMeta, +} + +impl Default for ApplicationTable { + fn default() -> Self { + Self::new() + } +} + +impl ApplicationTable { + pub fn new() -> Self { + Self { + api_version: "meta.k8s.io/v1".to_string(), + kind: "Table".to_string(), + column_definitions: vec![ + TableColumnDefinition { + name: "Application".to_string(), + kind: "string".to_string(), + description: "Name of the Application".to_string(), + priority: 0, + format: "name".to_string(), + }, + TableColumnDefinition { + name: "Deployed Version".to_string(), + kind: "string".to_string(), + description: "Currently deployed version of the Application".to_string(), + priority: 0, + ..Default::default() + }, + TableColumnDefinition { + name: "Latest Version".to_string(), + kind: "string".to_string(), + description: "Latest available version of the Application".to_string(), + priority: 0, + ..Default::default() + }, + TableColumnDefinition { + name: "Status".to_string(), + kind: "string".to_string(), + description: "Current status of the Application".to_string(), + priority: 0, + ..Default::default() + }, + ], + rows: vec![], + metadata: ListMeta::default(), + } + } +} + +impl From> for ApplicationTable { + fn from(summaries: Vec) -> Self { + let mut table = Self::default(); + let rows = summaries + .into_iter() + .map(|i| TableRow { + cells: vec![ + i.name, + i.deployed_version.unwrap_or("N/A".to_string()), + i.version, + match i.status { + StatusType::Undeployed => "Undeployed".to_string(), + StatusType::Reconciling => "Reconciling".to_string(), + StatusType::Deployed => "Deployed".to_string(), + StatusType::Failed => "Failed".to_string(), + }, + ], + }) + .collect(); + + table.rows = rows; + table + } +} + +impl From> for ApplicationTable { + fn from(manifests: Vec) -> Self { + let mut table = Self::default(); + let rows = manifests + .into_iter() + .map(|m| TableRow { + cells: vec![ + m.metadata.name, + "N/A".to_string(), + match m.metadata.annotations.get("version") { + Some(v) => v.to_owned(), + None => "N/A".to_string(), + }, + "N/A".to_string(), + ], + }) + .collect(); + + table.rows = rows; + table + } +} + +#[utoipa::path( + post, + path = "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications" +)] +pub async fn create_application( + Path(namespace): Path, + AxumState(state): AxumState, + body: Bytes, +) -> impl IntoResponse { + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + let configs: Api = Api::namespaced(client, &namespace); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("Unable to list cosmonic host configs: {}", e)), + }; + + let mut cfgs = cfgs.iter(); + // TODO(joonas): Remove this once we move to pulling NATS creds+secrets from lattice instead of hosts. + let mut lattice_id = String::new(); + let nats_client = loop { + let Some(cfg) = cfgs.next() else { + return internal_error(anyhow!( + "unable to find Host Config to use for initializing nats client " + )); + }; + let cluster_url = cfg.spec.nats_address.clone(); + lattice_id = cfg.spec.lattice.clone(); + let lattice_name = cfg.metadata.name.clone().unwrap(); + let nst: NameNamespace = NameNamespace::new(lattice_name.clone(), namespace.clone()); + match get_client(&cluster_url, state.nats_creds.clone(), nst).await { + Ok(c) => break Some(c), + Err(e) => { + error!("error connecting to nats: {}", e); + continue; + } + }; + }; + + let Some(nats_client) = nats_client else { + return internal_error(anyhow!("unable to initialize nats client")); + }; + + let model: serde_json::Value = match serde_json::from_slice(&body) { + Ok(v) => v, + Err(e) => return internal_error(anyhow!("unable to decode the patch: {}", e)), + }; + + let put = + match wash_lib::app::put_model(&nats_client, Some(lattice_id.clone()), &model.to_string()) + .await + { + Ok(res) => res, + Err(e) => return internal_error(anyhow!("could not deploy app: {}", e)), + }; + + let model_name = match put.result { + PutResult::Created | PutResult::NewVersion => put.name, + _ => { + // TODO(joonas): Add handling for the case where the model version + // might already exist (from prior deploy or otherwise). + return internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + put.result, + put.message + )); + } + }; + + let deploy = match wash_lib::app::deploy_model( + &nats_client, + Some(lattice_id.clone()), + &model_name, + Some(put.current_version.clone()), + ) + .await + { + Ok(res) => res, + Err(e) => return internal_error(anyhow!("could not deploy app: {}", e)), + }; + + if deploy.result != DeployResult::Acknowledged { + return internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + deploy.result, + deploy.message + )); + } + + // Get model from WADM for displaying in Kubernetes + let get = match wash_lib::app::get_model_details( + &nats_client, + Some(lattice_id), + &model_name, + Some(put.current_version), + ) + .await + { + Ok(res) => res, + Err(e) => return internal_error(anyhow!("error getting deployed app: {}", e)), + }; + + match get.result { + GetResult::Success => Json(Application::new(model_name)).into_response(), + // Either we received an error or could not find the deployed application, so return an error: + _ => internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + get.result, + get.message + )), + } +} + +#[utoipa::path(get, path = "/apis/core.oam.dev/v1beta1/applications")] +pub async fn list_all_applications( + TypedHeader(accept): TypedHeader, + AxumState(state): AxumState, +) -> impl IntoResponse { + // TODO(joonas): Use lattices (or perhaps Controller specific/special creds) for instanciating NATS client. + // TODO(joonas): Add watch support to stop Argo from spamming this endpoint every second. + + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + + let configs: Api = Api::all(client); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("Unable to list cosmonic host configs: {}", e)), + }; + + let mut apps = Vec::new(); + let mut lattices = HashSet::new(); + for cfg in cfgs { + let name = cfg.metadata.name.unwrap().clone(); + let lattice_id = cfg.spec.lattice.clone(); + let namespace = cfg.metadata.namespace.unwrap().clone(); + let nst = NameNamespace::new(name, namespace.clone()); + let map = state.nats_creds.read().await; + let secret = map.get(&nst); + // Prevent listing applications within a given lattice more than once + if !lattices.contains(&lattice_id) { + let result = match list_apps(&cfg.spec.nats_address, secret, lattice_id.clone()).await { + Ok(apps) => apps, + Err(e) => return internal_error(anyhow!("unable to list applications: {}", e)), + }; + apps.extend(result); + lattices.insert(lattice_id); + } + } + + // We're trying to match the appopriate response based on what Kubernetes/kubectl asked for. + match accept.into() { + As::Table => Json(ApplicationTable::from(apps)).into_response(), + As::NotSpecified => { + let partials: Vec = apps + .iter() + .map(|a| ApplicationPartial::from(a.clone())) + .collect(); + + Json(ApplicationList::from(partials)).into_response() + } + // TODO(joonas): Add better error handling here + _ => Json("").into_response(), + } +} + +#[utoipa::path( + get, + path = "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications" +)] +pub async fn list_applications( + TypedHeader(accept): TypedHeader, + Path(namespace): Path, + AxumState(state): AxumState, +) -> impl IntoResponse { + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + let configs: Api = Api::namespaced(client, &namespace); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("Unable to list cosmonic host configs: {}", e)), + }; + + let mut apps = Vec::new(); + let mut lattices = HashSet::new(); + for cfg in cfgs { + let name = cfg.metadata.name.unwrap().clone(); + let lattice_id = cfg.spec.lattice.clone(); + let nst = NameNamespace::new(name, namespace.clone()); + let map = state.nats_creds.read().await; + let secret = map.get(&nst); + // This is to check that we don't list a lattice more than once + if !lattices.contains(&lattice_id) { + let result = match list_apps(&cfg.spec.nats_address, secret, lattice_id.clone()).await { + Ok(apps) => apps, + Err(e) => return internal_error(anyhow!("unable to list applications: {}", e)), + }; + apps.extend(result); + lattices.insert(lattice_id); + } + } + + // We're trying to match the appopriate response based on what Kubernetes/kubectl asked for. + match accept.into() { + As::Table => Json(ApplicationTable::from(apps)).into_response(), + As::NotSpecified => { + let partials: Vec = apps + .iter() + .map(|a| ApplicationPartial::from(a.clone())) + .collect(); + Json(ApplicationList::from(partials)).into_response() + } + // TODO(joonas): Add better error handling here + _ => Json("").into_response(), + } +} + +pub async fn list_apps( + cluster_url: &str, + creds: Option<&SecretString>, + lattice_id: String, +) -> Result, Error> { + let client = match creds { + Some(creds) => { + ConnectOptions::with_credentials(creds.expose_secret())? + .connect(cluster_url) + .await? + } + None => ConnectOptions::new().connect(cluster_url).await?, + }; + let models = wash_lib::app::get_models(&client, Some(lattice_id)).await?; + + Ok(models) +} + +async fn get_client( + cluster_url: &str, + nats_creds: Arc>>, + namespace: NameNamespace, +) -> Result { + let creds = nats_creds.read().await; + match creds.get(&namespace) { + Some(creds) => { + let creds = creds.expose_secret(); + ConnectOptions::with_credentials(creds) + .expect("unable to create nats client") + .connect(cluster_url) + .await + } + None => ConnectOptions::new().connect(cluster_url).await, + } +} + +#[utoipa::path( + get, + path = "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications/{name}" +)] +pub async fn get_application( + TypedHeader(accept): TypedHeader, + Path((namespace, name)): Path<(String, String)>, + AxumState(state): AxumState, +) -> impl IntoResponse { + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + + let configs: Api = Api::namespaced(client, &namespace); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("unable to list cosmonic host configs: {}", e)), + }; + + let mut cfgs = cfgs.iter(); + // TODO(joonas): Remove this once we move to pulling NATS creds+secrets from lattice instead of hosts. + let mut lattice_id = String::new(); + let nats_client = loop { + let Some(cfg) = cfgs.next() else { + return internal_error(anyhow!( + "unable to find Host Config to use for initializing nats client " + )); + }; + let cluster_url = cfg.spec.nats_address.clone(); + lattice_id = cfg.spec.lattice.clone(); + let lattice_name = cfg.metadata.name.clone().unwrap(); + let nst: NameNamespace = NameNamespace::new(lattice_name.clone(), namespace.clone()); + match get_client(&cluster_url, state.nats_creds.clone(), nst).await { + Ok(c) => break Some(c), + Err(e) => { + error!("error connecting to nats: {}", e); + continue; + } + }; + }; + + let Some(nats_client) = nats_client else { + return internal_error(anyhow!("unable to initialize nats client")); + }; + + let get = + match wash_lib::app::get_model_details(&nats_client, Some(lattice_id.clone()), &name, None) + .await + { + Ok(res) => res, + Err(e) => { + return internal_error(anyhow!("unable to request app from wadm: {}", e)); + } + }; + + let status = match wash_lib::app::get_model_status( + &nats_client, + Some(lattice_id.clone()), + &name, + ) + .await + { + Ok(res) => res, + Err(e) => { + return internal_error(anyhow!("unable to request app status from wadm: {}", e)); + } + }; + + match status.result { + StatusResult::Ok => {} + StatusResult::NotFound => { + return not_found_error(anyhow!("applications \"{}\" not found", name)); + } + StatusResult::Error => { + return internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + status.result, + status.message + )); + } + }; + + if get.result == GetResult::Success { + if let Some(manifest) = get.manifest { + let response = match accept.into() { + As::Table => Json(ApplicationTable::from(vec![manifest])).into_response(), + As::NotSpecified => { + // TODO(joonas): This is a terrible hack, but for now it's what we need to do to satisfy Argo/Kubernetes since WADM doesn't support this metadata. + let mut manifest_value = serde_json::to_value(&manifest).unwrap(); + // TODO(joonas): We should add lattice id to this as well, but we need it in every place where the application is listed. + let ns = format!("{}/{}", &name, &manifest.version()); + let uid = Uuid::new_v5(&Uuid::NAMESPACE_OID, ns.as_bytes()); + manifest_value["metadata"]["uid"] = json!(uid.to_string()); + manifest_value["metadata"]["resourceVersion"] = json!(uid.to_string()); + manifest_value["metadata"]["namespace"] = json!(namespace); + manifest_value["metadata"]["labels"] = json!({ + "app.kubernetes.io/instance": &name + }); + // TODO(joonas): refactor status and the metadata inputs into a struct we could just serialize + // The custom health check we provide for Argo will handle the case where status is missing, so this is fine for now. + if let Some(status) = status.status { + let phase = match status.info.status_type { + StatusType::Undeployed => "Undeployed", + StatusType::Reconciling => "Reconciling", + StatusType::Deployed => "Deployed", + StatusType::Failed => "Failed", + }; + manifest_value["status"] = json!({ + "phase": phase, + }); + } + Json(manifest_value).into_response() + } + // TODO(joonas): Add better error handling here + t => return internal_error(anyhow!("unknown type: {}", t)), + }; + return response; + } + }; + + not_found_error(anyhow!("applications \"{}\" not found", name)) +} + +#[utoipa::path( + patch, + path = "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications/{name}" +)] +pub async fn patch_application( + Path((namespace, name)): Path<(String, String)>, + AxumState(state): AxumState, + body: Bytes, +) -> impl IntoResponse { + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + let configs: Api = Api::namespaced(client, &namespace); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("unable to list cosmonic host configs: {}", e)), + }; + + let mut cfgs = cfgs.iter(); + // TODO(joonas): Remove this once we move to pulling NATS creds+secrets from lattice instead of hosts. + let mut lattice_id = String::new(); + let nats_client = loop { + let Some(cfg) = cfgs.next() else { + return internal_error(anyhow!( + "unable to find Host Config to use for initializing nats client " + )); + }; + let cluster_url = cfg.spec.nats_address.clone(); + lattice_id = cfg.spec.lattice.clone(); + let lattice_name = cfg.metadata.name.clone().unwrap(); + let nst: NameNamespace = NameNamespace::new(lattice_name.clone(), namespace.clone()); + match get_client(&cluster_url, state.nats_creds.clone(), nst).await { + Ok(c) => break Some(c), + Err(e) => { + error!("error connecting to nats: {}", e); + continue; + } + }; + }; + + let Some(nats_client) = nats_client else { + return internal_error(anyhow!("unable to initialize nats client")); + }; + + // Fist, check if the model exists. + // TODO(joonas): we should likely fetch the version of the manifest that's running in Kubernetes + // TODO(joonas): Should this use model.status instead of model.get? + let get = + match wash_lib::app::get_model_details(&nats_client, Some(lattice_id.clone()), &name, None) + .await + { + Ok(res) => res, + Err(e) => { + return internal_error(anyhow!("unable to find app: {}", e)); + } + }; + + if get.result != GetResult::Success { + return internal_error(anyhow!( + "unexpected response from wadm: result={:?}, reason={}", + get.result, + get.message + )); + } + + // Prepare manifest for patching + let Some(manifest) = get.manifest else { + return internal_error(anyhow!("no manifest was found for app: {}", &name)); + }; + + let mut model = serde_json::to_value(manifest).unwrap(); + // Parse the Kubernetes-provided RFC 7386 patch + let patch: serde_json::Value = match serde_json::from_slice(&body) { + Ok(p) => p, + Err(e) => return internal_error(anyhow!("unable to decode the patch: {}", e)), + }; + + // Attempt to patch the currently running version + json_patch::merge(&mut model, &patch); + + let put = + match wash_lib::app::put_model(&nats_client, Some(lattice_id.clone()), &model.to_string()) + .await + { + Ok(res) => res, + Err(e) => { + return internal_error(anyhow!("could not update manifest for deploy: {}", e)) + } + }; + + match put.result { + PutResult::NewVersion => {} + _ => { + // For now we have to check the error message to see if we can continue, + // despite getting an error. + if !put.message.contains("already exists") { + return internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + put.result, + put.message + )); + } + } + }; + + let deploy = match wash_lib::app::deploy_model( + &nats_client, + Some(lattice_id), + &name, + Some(put.current_version), + ) + .await + { + Ok(res) => res, + Err(e) => return internal_error(anyhow!("could not deploy app: {}", e)), + }; + + match deploy.result { + DeployResult::Acknowledged => Json(Application::new(name)).into_response(), + DeployResult::Error => internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + deploy.result, + deploy.message + )), + DeployResult::NotFound => not_found_error(anyhow!("applications \"{}\" not found", &name)), + } +} + +#[utoipa::path( + delete, + path = "/apis/core.oam.dev/v1beta1/namespaces/{namespace}/applications/{name}" +)] +pub async fn delete_application( + Path((namespace, name)): Path<(String, String)>, + AxumState(state): AxumState, +) -> impl IntoResponse { + let client = match Client::try_default().await { + Ok(c) => c, + Err(e) => return internal_error(anyhow!("unable to initialize kubernetes client: {}", e)), + }; + + let configs: Api = Api::namespaced(client, &namespace); + let cfgs = match configs.list(&ListParams::default()).await { + Ok(objs) => objs, + Err(e) => return internal_error(anyhow!("unable to list cosmonic host configs: {}", e)), + }; + + let mut cfgs = cfgs.iter(); + // TODO(joonas): Remove this once we move to pulling NATS creds+secrets from lattice instead of hosts. + let mut lattice_id = String::new(); + let nats_client = loop { + let Some(cfg) = cfgs.next() else { + return internal_error(anyhow!( + "unable to find Host Config to use for initializing nats client " + )); + }; + let cluster_url = cfg.spec.nats_address.clone(); + lattice_id = cfg.spec.lattice.clone(); + let lattice_name = cfg.metadata.name.clone().unwrap(); + let nst: NameNamespace = NameNamespace::new(lattice_name.clone(), namespace.clone()); + match get_client(&cluster_url, state.nats_creds.clone(), nst).await { + Ok(c) => break Some(c), + Err(e) => { + error!("error connecting to nats: {}", e); + continue; + } + }; + }; + + let Some(nats_client) = nats_client else { + return internal_error(anyhow!("unable to initialize nats client")); + }; + + // Fist, check if the model exists. + // TODO(joonas): Replace this with wash_lib::app::get_model_status once + // https://github.com/wasmCloud/wasmCloud/pull/1151 ships. + let status = match wash_lib::app::get_model_status( + &nats_client, + Some(lattice_id.clone()), + &name, + ) + .await + { + Ok(res) => res, + Err(e) => { + return internal_error(anyhow!("unable to request app status from wadm: {}", e)); + } + }; + + match status.result { + StatusResult::Ok => { + // Proceed + } + StatusResult::NotFound => { + return not_found_error(anyhow!("apps \"{}\" not found", name)); + } + StatusResult::Error => { + return internal_error(anyhow!( + "unexpected response from status command: result={:?}, message={}", + status.result, + status.message + )); + } + }; + + let delete = match wash_lib::app::delete_model_version( + &nats_client, + Some(lattice_id), + &name, + None, + true, + ) + .await + { + Ok(res) => res, + Err(e) => return internal_error(anyhow!("error deleting app: {}", e)), + }; + + match delete.result { + // TODO(joonas): do we need to handle DeleteResult::Noop differently? + DeleteResult::Deleted | DeleteResult::Noop => Json(Application::new(name)).into_response(), + DeleteResult::Error => internal_error(anyhow!( + "unexpected response from wadm: result={:?}, message={}", + delete.result, + delete.message + )), + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..af7b1fe --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1 @@ +pub mod application; diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..4874014 --- /dev/null +++ b/src/router.rs @@ -0,0 +1,233 @@ +use std::fmt::Display; + +use anyhow::{anyhow, Error}; +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + routing::{delete, get, patch, post}, + Json, Router, TypedHeader, +}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ + APIGroup, APIGroupList, APIResource, APIResourceList, GroupVersionForDiscovery, ObjectMeta, + Status as StatusKind, +}; +use kube::core::{GroupVersionKind, ListMeta}; +use serde::Serialize; +use tracing::error; + +use crate::{ + discovery::{ + APIGroupDiscovery, APIGroupDiscoveryList, APIResourceDiscovery, APIVersionDiscovery, + DiscoveryFreshness, ResourceScope, + }, + header::{Accept, As}, + openapi, + resources::application::{ + create_application, delete_application, get_application, list_all_applications, + list_applications, patch_application, + }, + State, +}; + +pub fn setup(state: State) -> Router { + let openapi_router = openapi::router(); + Router::new() + .route("/apis/core.oam.dev/v1beta1", get(api_resources)) + .route( + "/apis/core.oam.dev/v1beta1/applications", + get(list_all_applications), + ) + .route( + "/apis/core.oam.dev/v1beta1/namespaces/:namespace/applications", + get(list_applications), + ) + .route( + "/apis/core.oam.dev/v1beta1/namespaces/:namespace/applications", + post(create_application), + ) + .route( + "/apis/core.oam.dev/v1beta1/namespaces/:namespace/applications/:name", + get(get_application), + ) + .route( + "/apis/core.oam.dev/v1beta1/namespaces/:namespace/applications/:name", + patch(patch_application), + ) + .route( + "/apis/core.oam.dev/v1beta1/namespaces/:namespace/applications/:name", + delete(delete_application), + ) + .with_state(state.clone()) + .route("/apis", get(api_groups)) + .route("/api", get(api_groups)) + .route("/health", get(health)) + .nest("/openapi", openapi_router) +} + +#[utoipa::path(get, path = "/apis/core.oam.dev/v1beta1")] +async fn api_resources() -> Json { + let resources = APIResourceList { + group_version: "core.oam.dev/v1beta1".to_string(), + resources: vec![APIResource { + group: Some("core.oam.dev".to_string()), + kind: "Application".to_string(), + name: "applications".to_string(), + namespaced: true, + short_names: Some(vec!["app".to_string()]), + singular_name: "application".to_string(), + categories: Some(vec!["oam".to_string()]), + verbs: vec![ + "create".to_string(), + "get".to_string(), + "list".to_string(), + "patch".to_string(), + "watch".to_string(), + ], + ..Default::default() + }], + }; + Json(resources) +} + +async fn api_groups(TypedHeader(accept): TypedHeader) -> impl IntoResponse { + match accept.into() { + // This is to support KEP-3352 + As::APIGroupDiscoveryList => Json(APIGroupDiscoveryList { + items: vec![APIGroupDiscovery { + metadata: ObjectMeta { + name: Some("core.oam.dev".to_string()), + ..Default::default() + }, + versions: vec![APIVersionDiscovery { + version: "v1beta1".to_string(), + resources: vec![APIResourceDiscovery { + resource: "applications".to_string(), + response_kind: GroupVersionKind { + group: "core.oam.dev".to_string(), + version: "v1beta1".to_string(), + kind: "Application".to_string(), + }, + scope: ResourceScope::Namespaced, + singular_resource: "application".to_string(), + verbs: vec![ + "create".to_string(), + "get".to_string(), + "list".to_string(), + "patch".to_string(), + "watch".to_string(), + ], + short_names: vec!["app".to_string()], + categories: vec!["oam".to_string()], + // TODO(joonas): Add status resource here once we have support for it. + subresources: vec![], + }], + freshness: DiscoveryFreshness::Current, + }], + kind: None, + api_version: None, + }], + ..Default::default() + }) + .into_response(), + // This is to support the "legacy" 'Accept: application/accept' requests. + As::NotSpecified => Json(APIGroupList { + groups: vec![APIGroup { + name: "core.oam.dev".to_string(), + preferred_version: Some(GroupVersionForDiscovery { + group_version: "core.oam.dev/v1beta1".to_string(), + version: "v1beta1".to_string(), + }), + versions: vec![GroupVersionForDiscovery { + group_version: "core.oam.dev/v1beta1".to_string(), + version: "v1beta1".to_string(), + }], + ..Default::default() + }], + }) + .into_response(), + t => internal_error(anyhow!("unknown type request: {}", t)), + } +} + +async fn health() -> &'static str { + "healthy" +} + +/// PartialObjectMetadataList contains a list of objects containing only their metadata +// Based on https://github.com/kubernetes/kubernetes/blob/022d50fe3a1bdf2386395da7c266fede0c110040/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L1469-L1480 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct PartialObjectMetadataList { + api_version: String, + kind: String, + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + metadata: ListMeta, + // items contains each of the included items. + items: Vec, +} + +impl Default for PartialObjectMetadataList { + fn default() -> Self { + Self { + api_version: "meta.k8s.io/v1".to_string(), + kind: "PartialObjectMetadataList".to_string(), + metadata: ListMeta::default(), + items: vec![], + } + } +} + +/// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients +/// to get access to a particular ObjectMeta schema without knowing the details of the version. +// https://github.com/kubernetes/kubernetes/blob/022d50fe3a1bdf2386395da7c266fede0c110040/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L1458-L1467 +#[derive(Clone, Debug, Serialize)] +struct PartialObjectMetadata { + api_version: String, + kind: String, + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metadata: ObjectMeta, +} + +/// Helper for mapping any error into a `500 Internal Server Error` response. +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn internal_error(err: E) -> Response +where + E: Into + Display, +{ + error!(%err); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(StatusKind { + code: Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16() as i32), + details: None, + message: Some(err.to_string()), + metadata: ListMeta::default(), + reason: Some("StatusInternalServerError".to_string()), + status: Some("Failure".to_string()), + }), + ) + .into_response() +} + +/// Helper for mapping any error into a `404 Not Found` response. +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn not_found_error(err: E) -> Response +where + E: Into + Display, +{ + error!(%err); + ( + StatusCode::NOT_FOUND, + Json(StatusKind { + code: Some(StatusCode::NOT_FOUND.as_u16() as i32), + details: None, + message: Some(err.to_string()), + metadata: ListMeta::default(), + reason: Some("NotFound".to_string()), + status: Some("Failure".to_string()), + }), + ) + .into_response() +} diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..6c5d6f3 --- /dev/null +++ b/src/table.rs @@ -0,0 +1,55 @@ +use serde::{self, Serialize}; + +// Based on https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Table +// TODO(joonas): should we have a Table struct that can be used by multiple resource types? +// pub struct Table { +// ... +//} + +// Based on https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#TableColumnDefinition +#[derive(Debug, Clone, Serialize)] +pub struct TableColumnDefinition { + // name is a human readable name for the column. + pub name: String, + // type is an OpenAPI type definition for this column, such as number, integer, string, or + // array. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + #[serde(rename = "type")] + pub kind: String, + // format is an optional OpenAPI type modifier for this column. A format modifies the type and + // imposes additional rules, like date or time formatting for a string. The 'name' format is applied + // to the primary identifier column which has type 'string' to assist in clients identifying column + // is the resource name. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + pub format: String, + // description is a human readable description of this column. + pub description: String, + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a higher priority. + /// NOTE: Set priority to 0 if you want things to show up in the non `-o wide` view, and anything above + /// if you don't mind the value being hidden behind `-o wide` flag in kubectl get . + pub priority: u8, +} + +impl Default for TableColumnDefinition { + fn default() -> Self { + Self { + name: "".to_string(), + kind: "".to_string(), + format: "".to_string(), + description: "".to_string(), + priority: 0, + } + } +} + +// Based on https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#TableRow +#[derive(Debug, Clone, Serialize)] +pub struct TableRow { + // TODO(joonas): Support more than strings here + // cells will be as wide as the column definitions array and may contain strings, numbers (float64 or + // int64), booleans, simple maps, lists, or null. See the type field of the column definition for a + // more detailed description. + pub cells: Vec, +}