├── .cargo └── config ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── crates ├── wasmcloud-logging │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── wasmcloud-provider │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── states.rs │ └── states │ ├── container.rs │ ├── container │ ├── running.rs │ ├── terminated.rs │ └── waiting.rs │ ├── pod.rs │ └── pod │ ├── running.rs │ └── starting.rs ├── demos └── wasmcloud │ ├── fileserver │ ├── .cargo │ │ └── config │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── k8s.yaml │ └── src │ │ └── lib.rs │ ├── greet │ ├── .cargo │ │ └── config │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── greet-wasmcloud.yaml │ └── src │ │ └── lib.rs │ ├── hello-world-assemblyscript │ ├── .gitignore │ ├── README.md │ ├── assembly │ │ ├── .gitignore │ │ ├── index.ts │ │ └── tsconfig.json │ ├── k8s.yaml │ ├── package-lock.json │ └── package.json │ └── uppercase │ ├── .cargo │ └── config │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── src │ └── lib.rs │ └── uppercase-wasmcloud.yaml ├── justfile ├── justfile-windows ├── scripts ├── bootstrap.ps1 └── bootstrap.sh ├── src └── krustlet-wasmcloud.rs └── tests ├── integration_tests.rs ├── oneclick └── src │ └── main.rs └── podsmiter └── src └── main.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.armv7-unknown-linux-gnueabihf] 2 | linker = "arm-linux-gnueabihf-gcc" 3 | 4 | [target.aarch64-unknown-linux-gnu] 5 | linker = "aarch64-linux-gnu-gcc" 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.config.os }} 10 | env: ${{ matrix.config.env }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | config: 15 | - { 16 | os: "ubuntu-latest", 17 | arch: "amd64", 18 | args: "", 19 | url: "https://github.com/casey/just/releases/download/v0.5.11/just-v0.5.11-x86_64-unknown-linux-musl.tar.gz", 20 | name: "just", 21 | pathInArchive: "just", 22 | env: {}, 23 | } 24 | - { 25 | os: "ubuntu-latest", 26 | arch: "aarch64", 27 | args: "--target aarch64-unknown-linux-gnu", 28 | url: "https://github.com/casey/just/releases/download/v0.5.11/just-v0.5.11-x86_64-unknown-linux-musl.tar.gz", 29 | name: "just", 30 | pathInArchive: "just", 31 | env: { OPENSSL_DIR: "/usr/local/openssl-aarch64" }, 32 | } 33 | - { 34 | os: "macos-latest", 35 | arch: "amd64", 36 | args: "", 37 | url: "https://github.com/casey/just/releases/download/v0.5.11/just-v0.5.11-x86_64-apple-darwin.tar.gz", 38 | name: "just", 39 | pathInArchive: "just", 40 | env: {}, 41 | } 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: engineerd/configurator@v0.0.5 45 | with: 46 | name: ${{ matrix.config.name }} 47 | url: ${{ matrix.config.url }} 48 | pathInArchive: ${{ matrix.config.pathInArchive }} 49 | # hack(bacongobbler): install rustfmt to work around darwin toolchain issues 50 | - name: "(macOS) install dev tools" 51 | if: runner.os == 'macOS' 52 | run: | 53 | rustup component add rustfmt --toolchain stable-x86_64-apple-darwin 54 | rustup component add clippy --toolchain stable-x86_64-apple-darwin 55 | rustup update stable 56 | - name: setup for cross-compile builds 57 | if: matrix.config.arch == 'aarch64' 58 | run: | 59 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 60 | cd /tmp 61 | git clone https://github.com/openssl/openssl 62 | cd openssl 63 | git checkout OpenSSL_1_1_1h 64 | sudo mkdir -p $OPENSSL_DIR 65 | ./Configure linux-aarch64 --prefix=$OPENSSL_DIR --openssldir=$OPENSSL_DIR shared 66 | make CC=aarch64-linux-gnu-gcc 67 | sudo make install 68 | rustup target add aarch64-unknown-linux-gnu 69 | - name: Build 70 | run: | 71 | just build ${{ matrix.config.args }} 72 | just test 73 | windows-build: 74 | runs-on: windows-latest 75 | defaults: 76 | run: 77 | # For some reason, running with the default powershell doesn't work with the `Build` step, 78 | # but bash does! 79 | shell: bash 80 | steps: 81 | - uses: actions/checkout@v2 82 | - uses: engineerd/configurator@v0.0.5 83 | with: 84 | name: just 85 | url: "https://github.com/casey/just/releases/download/v0.5.11/just-v0.5.11-x86_64-pc-windows-msvc.zip" 86 | pathInArchive: just.exe 87 | - name: Build 88 | run: | 89 | just --justfile justfile-windows build 90 | just --justfile justfile-windows test 91 | # TODO: Figure out how to get kind or minikube running on a windows test host and see how we can 92 | # get things working with rustls 93 | # windows-e2e: 94 | # runs-on: windows-latest 95 | e2e: 96 | runs-on: ubuntu-latest 97 | steps: 98 | - uses: actions/checkout@v2 99 | - uses: engineerd/setup-kind@v0.5.0 100 | with: 101 | version: "v0.10.0" 102 | - uses: engineerd/configurator@v0.0.5 103 | with: 104 | name: just 105 | url: https://github.com/casey/just/releases/download/v0.5.11/just-v0.5.11-x86_64-unknown-linux-musl.tar.gz 106 | pathInArchive: just 107 | - name: Run e2e tests 108 | run: just test-e2e-standalone 109 | - name: Output krustlet logs (on error) 110 | if: ${{ failure() }} 111 | run: cat *.txt 112 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - "v*" 8 | jobs: 9 | build_and_publish: 10 | name: build release assets 11 | runs-on: ${{ matrix.config.os }} 12 | env: ${{ matrix.config.env }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: 17 | - { 18 | os: "ubuntu-latest", 19 | arch: "amd64", 20 | args: "--release", 21 | targetDir: "target/release", 22 | extension: "", 23 | env: {}, 24 | } 25 | - { 26 | os: "ubuntu-latest", 27 | arch: "aarch64", 28 | args: "--release --target aarch64-unknown-linux-gnu", 29 | targetDir: "target/aarch64-unknown-linux-gnu/release", 30 | extension: "", 31 | env: { OPENSSL_DIR: "/usr/local/openssl-aarch64" }, 32 | } 33 | - { 34 | os: "macos-latest", 35 | arch: "amd64", 36 | args: "--release", 37 | targetDir: "target/release", 38 | extension: "", 39 | env: {}, 40 | } 41 | - { 42 | os: "windows-latest", 43 | arch: "amd64", 44 | args: "--release --no-default-features --features rustls-tls", 45 | targetDir: "target/release", 46 | extension: ".exe", 47 | env: {}, 48 | } 49 | steps: 50 | - uses: actions/checkout@v2 51 | 52 | - name: set the release version (tag) 53 | if: startsWith(github.ref, 'refs/tags/v') 54 | shell: bash 55 | run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 56 | 57 | - name: set the release version (master) 58 | if: github.ref == 'refs/heads/master' 59 | shell: bash 60 | run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV 61 | 62 | - name: lowercase the runner OS name 63 | shell: bash 64 | run: | 65 | OS=$(echo "${{ runner.os }}" | tr '[:upper:]' '[:lower:]') 66 | echo "RUNNER_OS=${OS}" >> $GITHUB_ENV 67 | 68 | # hack(bacongobbler): install rustfmt to work around darwin toolchain issues 69 | - name: "(macOS) install dev tools" 70 | if: runner.os == 'macOS' 71 | run: | 72 | rustup component add rustfmt --toolchain stable-x86_64-apple-darwin 73 | rustup component add clippy --toolchain stable-x86_64-apple-darwin 74 | rustup update stable 75 | 76 | - name: setup for cross-compile builds 77 | if: matrix.config.arch == 'aarch64' 78 | run: | 79 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 80 | cd /tmp 81 | git clone https://github.com/openssl/openssl 82 | cd openssl 83 | git checkout OpenSSL_1_1_1h 84 | sudo mkdir -p $OPENSSL_DIR 85 | ./Configure linux-aarch64 --prefix=$OPENSSL_DIR --openssldir=$OPENSSL_DIR shared 86 | make CC=aarch64-linux-gnu-gcc 87 | sudo make install 88 | rustup target add aarch64-unknown-linux-gnu 89 | 90 | - name: build release 91 | uses: actions-rs/cargo@v1 92 | with: 93 | command: build 94 | args: ${{ matrix.config.args }} 95 | 96 | - name: package release assets 97 | shell: bash 98 | run: | 99 | mkdir _dist 100 | cp README.md LICENSE ${{ matrix.config.targetDir }}/krustlet-wasmcloud${{ matrix.config.extension }} _dist/ 101 | cd _dist 102 | tar czf krustlet-wasmcloud-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.tar.gz README.md LICENSE krustlet-wasmcloud${{ matrix.config.extension }} 103 | 104 | - name: calculate checksum (all except macOS) 105 | shell: bash 106 | if: runner.os != 'macOS' 107 | run: sha256sum _dist/krustlet-wasmcloud-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.tar.gz > _dist/checksum-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.txt 108 | 109 | - name: calculate checksum (macOS) 110 | shell: bash 111 | if: runner.os == 'macOS' 112 | run: shasum -a 256 _dist/krustlet-wasmcloud-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.tar.gz > _dist/checksum-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.txt 113 | 114 | - name: Upload tarball 115 | uses: actions/upload-artifact@v1 116 | with: 117 | name: krustlet-wasmcloud 118 | path: _dist/krustlet-wasmcloud-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.tar.gz 119 | - name: Upload checksum 120 | uses: actions/upload-artifact@v1 121 | with: 122 | name: krustlet-wasmcloud 123 | path: _dist/checksum-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.txt 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /.vscode 3 | .DS_Store 4 | 5 | # From demos 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "krustlet-wasmcloud-provider" 3 | version = "0.7.0" 4 | authors = [ 5 | "Matt Butcher ", 6 | "Matthew Fisher ", 7 | "Radu Matei ", 8 | "Taylor Thomas ", 9 | "Brian Ketelsen ", 10 | "Brian Hardock ", 11 | "Ryan Levick ", 12 | "Kevin Flansburg ", 13 | ] 14 | edition = "2018" 15 | default-run = "krustlet-wasmcloud" 16 | description = "A Krustlet provider implementation for WASMCloud" 17 | repository = "hhttps://github.com/wasmCloud/krustlet-wasmcloud-provider" 18 | 19 | [features] 20 | default = ["native-tls"] 21 | native-tls = [ 22 | "kube/native-tls", 23 | "kube-runtime/native-tls", 24 | "krator/kube-native-tls", 25 | "kubelet/kube-native-tls", 26 | "wasmcloud-provider/native-tls", 27 | "oci-distribution/native-tls" 28 | ] 29 | rustls-tls = [ 30 | "kube/rustls-tls", 31 | "kube-runtime/rustls-tls", 32 | "krator/rustls-tls", 33 | "kubelet/rustls-tls", 34 | "wasmcloud-provider/rustls-tls", 35 | "oci-distribution/rustls-tls" 36 | ] 37 | 38 | [dependencies] 39 | anyhow = "1.0" 40 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "time"] } 41 | actix-rt = "2.0" 42 | kube = { version= "0.48", default-features = false } 43 | k8s-openapi = { version = "0.11", default-features = false, features = ["v1_18"] } 44 | env_logger = "0.8" 45 | futures = "0.3" 46 | krator = { version = "0.2", default-features = false } 47 | kubelet = { version = "0.7", default-features = false, features = ["cli"] } 48 | wasmcloud-provider = { path = "./crates/wasmcloud-provider", version = "0.7", default-features = false } 49 | oci-distribution = { version = "0.6", default-features = false } 50 | dirs = "3.0" 51 | hostname = "0.3" 52 | regex = "1.3" 53 | 54 | [dev-dependencies] 55 | serde_derive = "1.0" 56 | serde_json = "1.0" 57 | serde = "1.0" 58 | reqwest = { version = "0.11", default-features = false } 59 | tempfile = "3.1" 60 | kube-runtime = { version = "0.48", default-features = false } 61 | 62 | [workspace] 63 | members = [ 64 | "crates/wasmcloud-logging", 65 | "crates/wasmcloud-provider", 66 | ] 67 | 68 | [[bin]] 69 | name = "krustlet-wasmcloud" 70 | path = "src/krustlet-wasmcloud.rs" 71 | 72 | [[bin]] 73 | name = "oneclick" 74 | path = "tests/oneclick/src/main.rs" 75 | 76 | [[bin]] 77 | name = "podsmiter" 78 | path = "tests/podsmiter/src/main.rs" 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) Microsoft Corporation. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Deprecation Notice**: After much discussion among maintainers, we have decided to deprecate this 2 | repo in favor of the new [wasmCloud Helm Chart](https://github.com/wasmCloud/wasmcloud-otp/tree/main/wasmcloud_host/chart). 3 | We arrived at this decision due to 2 main reasons. First, the amount of work to bring this up to date 4 | with both Krustlet 1.0 and the shiny new 0.50+ wasmCloud host is more than we are able to commit 5 | to at this time. More importantly, running your wasmCloud actors on wasmCloud-specific nodes severely 6 | limits the rich feature set wasmCloud provides. To that end we created the Helm chart, which allows 7 | you to run and easily scale wasmCloud hosts running as Kubernetes pods. This enables integration 8 | with existing services running in Kubernetes while not hampering the power of wasmCloud. 9 | 10 | # wasmCloud Krustlet provider 11 | 12 | This is a [Krustlet](https://github.com/deislabs/krustlet) 13 | [Provider](https://github.com/deislabs/krustlet/blob/master/docs/topics/architecture.md#providers) 14 | implementation for the [wasmCloud](https://github.com/wasmCloud/wasmCloud) runtime. 15 | 16 | ## Documentation 17 | 18 | If you're new to Krustlet, get started with [the 19 | introduction](https://github.com/deislabs/krustlet/blob/master/docs/intro/README.md) documentation. 20 | For more in-depth information about Krustlet, plunge right into the [topic 21 | guides](https://github.com/deislabs/krustlet/blob/master/docs/topics/README.md). 22 | 23 | ## Community, discussion, contribution, and support 24 | 25 | You can reach the Krustlet community and developers via the following channels: 26 | 27 | - [Kubernetes Slack](https://kubernetes.slack.com): 28 | - [#krustlet](https://kubernetes.slack.com/messages/krustlet) 29 | - Public Community Call on Mondays at 1:00 PM PT: 30 | - [Zoom](https://us04web.zoom.us/j/71695031152?pwd=T0g1d0JDZVdiMHpNNVF1blhxVC9qUT09) 31 | - Download the meeting calendar invite 32 | [here](https://raw.githubusercontent.com/deislabs/krustlet/master/docs/community/assets/community_meeting.ics) 33 | -------------------------------------------------------------------------------- /crates/wasmcloud-logging/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmcloud-logging" 3 | version = "0.3.0" 4 | authors = [ 5 | "Matt Butcher ", 6 | "Matthew Fisher ", 7 | "Radu Matei ", 8 | "Taylor Thomas ", 9 | "Brian Ketelsen ", 10 | "Brian Hardock ", 11 | "Ryan Levick ", 12 | "Kevin Flansburg ", 13 | ] 14 | edition = "2018" 15 | publish = false 16 | 17 | [lib] 18 | crate-type = ["cdylib", "rlib"] 19 | 20 | [features] 21 | static_plugin = [] # Enable to statically compile this into a host 22 | 23 | [dependencies] 24 | log = "0.4" 25 | simplelog = "0.9" 26 | tempfile = "3.1" 27 | wasmcloud-provider-core = "0.1" 28 | wasmcloud-actor-core = "0.2" 29 | wasmcloud-actor-logging = "0.1" 30 | -------------------------------------------------------------------------------- /crates/wasmcloud-logging/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This library was adapted from the original wascc logging 2 | // implementation contributed by Brian Ketelsen to wascc. 3 | // Original license below: 4 | 5 | // Copyright 2015-2019 Capital One Services, LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | use wasmcloud_actor_core::{CapabilityConfiguration, HealthCheckResponse}; 20 | use wasmcloud_actor_logging::{WriteLogArgs, OP_LOG}; 21 | use wasmcloud_provider_core::capabilities::{CapabilityProvider, Dispatcher, NullDispatcher}; 22 | use wasmcloud_provider_core::core::{OP_BIND_ACTOR, OP_HEALTH_REQUEST, OP_REMOVE_ACTOR}; 23 | use wasmcloud_provider_core::{deserialize, serialize}; 24 | 25 | use log::Log; 26 | 27 | use std::collections::HashMap; 28 | use std::error::Error; 29 | use std::fs::{File, OpenOptions}; 30 | use std::sync::{Arc, RwLock}; 31 | 32 | use simplelog::{Config, LevelFilter, WriteLogger}; 33 | 34 | #[cfg(not(feature = "static_plugin"))] 35 | capability_provider!(LoggingProvider, LoggingProvider::new); 36 | 37 | pub const LOG_PATH_KEY: &str = "LOG_PATH"; 38 | 39 | /// Origin of messages coming from wasmcloud host 40 | const SYSTEM_ACTOR: &str = "system"; 41 | 42 | #[allow(dead_code)] 43 | const CAPABILITY_ID: &str = "wasmcloud:logging"; 44 | 45 | const ERROR: &str = "error"; 46 | const WARN: &str = "warn"; 47 | const INFO: &str = "info"; 48 | const DEBUG: &str = "debug"; 49 | const TRACE: &str = "trace"; 50 | 51 | /// LoggingProvider provides an implementation of the wasmcloud:logging capability 52 | /// that keeps separate log output for each actor. 53 | #[derive(Clone)] 54 | pub struct LoggingProvider { 55 | dispatcher: Arc>>, 56 | output_map: Arc>>>>, 57 | } 58 | 59 | impl Default for LoggingProvider { 60 | fn default() -> Self { 61 | LoggingProvider { 62 | dispatcher: Arc::new(RwLock::new(Box::new(NullDispatcher::new()))), 63 | output_map: Arc::new(RwLock::new(HashMap::new())), 64 | } 65 | } 66 | } 67 | 68 | impl LoggingProvider { 69 | pub fn new() -> Self { 70 | Self::default() 71 | } 72 | 73 | fn configure( 74 | &self, 75 | config: CapabilityConfiguration, 76 | ) -> Result, Box> { 77 | let path = config 78 | .values 79 | .get(LOG_PATH_KEY) 80 | .ok_or("log file path was unspecified")?; 81 | 82 | let file = OpenOptions::new().write(true).open(path)?; 83 | let logger = WriteLogger::new(LevelFilter::Trace, Config::default(), file); 84 | let mut output_map = self.output_map.write().unwrap(); 85 | output_map.insert(config.module, logger); 86 | Ok(vec![]) 87 | } 88 | } 89 | 90 | impl CapabilityProvider for LoggingProvider { 91 | // Invoked by the runtime host to give this provider plugin the ability to communicate 92 | // with actors 93 | fn configure_dispatch( 94 | &self, 95 | dispatcher: Box, 96 | ) -> Result<(), Box> { 97 | let mut lock = self.dispatcher.write().unwrap(); 98 | *lock = dispatcher; 99 | 100 | Ok(()) 101 | } 102 | 103 | // Invoked by host runtime to allow an actor to make use of the capability 104 | // All providers MUST handle the "configure" message, even if no work will be done 105 | fn handle_call( 106 | &self, 107 | actor: &str, 108 | op: &str, 109 | msg: &[u8], 110 | ) -> Result, Box> { 111 | match (op, actor) { 112 | (OP_BIND_ACTOR, SYSTEM_ACTOR) => { 113 | let cfg_vals = deserialize::(msg)?; 114 | self.configure(cfg_vals) 115 | } 116 | (OP_REMOVE_ACTOR, SYSTEM_ACTOR) => Ok(vec![]), 117 | (OP_HEALTH_REQUEST, SYSTEM_ACTOR) => Ok(serialize(HealthCheckResponse { 118 | healthy: true, 119 | message: "".to_string(), 120 | })?), 121 | (OP_LOG, _) => { 122 | let log_msg = deserialize::(msg)?; 123 | 124 | let level = match &*log_msg.level { 125 | ERROR => log::Level::Error, 126 | WARN => log::Level::Warn, 127 | INFO => log::Level::Info, 128 | DEBUG => log::Level::Debug, 129 | TRACE => log::Level::Trace, 130 | _ => return Err(format!("Unknown log level {}", log_msg.level).into()), 131 | }; 132 | 133 | let output_map = self.output_map.read().unwrap(); 134 | let logger = output_map 135 | .get(actor) 136 | .ok_or(format!("Unable to find logger for actor {}", actor))?; 137 | logger.log( 138 | &log::Record::builder() 139 | .args(format_args!("[{}] {}", actor, log_msg.text)) 140 | .level(level) 141 | .target(&log_msg.target) 142 | .build(), 143 | ); 144 | Ok(vec![]) 145 | } 146 | _ => Err(format!("Unknown operation: {}", op).into()), 147 | } 148 | } 149 | 150 | // No cleanup needed on stop 151 | fn stop(&self) {} 152 | } 153 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmcloud-provider" 3 | version = "0.7.0" 4 | authors = [ 5 | "Matt Butcher ", 6 | "Matthew Fisher ", 7 | "Radu Matei ", 8 | "Taylor Thomas ", 9 | "Brian Ketelsen ", 10 | "Brian Hardock ", 11 | "Ryan Levick ", 12 | "Kevin Flansburg ", 13 | ] 14 | edition = "2018" 15 | publish = false 16 | 17 | [features] 18 | default = ["native-tls"] 19 | native-tls = ["kube/native-tls", "kubelet/kube-native-tls", "krator/kube-native-tls"] 20 | rustls-tls = ["kube/rustls-tls", "kubelet/rustls-tls", "krator/rustls-tls"] 21 | 22 | [dependencies] 23 | anyhow = "1.0" 24 | async-trait = "0.1" 25 | # Ideally, we should plumb the wasm3 vs wasmtime feature up into the top level build so people can 26 | # choose their own, but right now the wasm3 stuff doesn't work with windows without a clang 27 | # dependency 28 | wasmcloud-host = { version = "0.16", default-features = false, features = ["wasmtime"] } 29 | log = "0.4" 30 | serde = "1.0" 31 | serde_derive = "1.0" 32 | serde_json = "1.0" 33 | # This can be updated once the next version of krustlet comes out as there are some breaking API 34 | # changes in the kube crate 35 | kube = { version = "0.48", default-features = false } 36 | kubelet = { version = "0.7", default-features = false, features = ["derive"] } 37 | krator = { version = "0.2", default-features = false, features = ["derive"] } 38 | tokio = { version = "1.0", features = ["fs", "macros"] } 39 | chrono = { version = "0.4", features = ["serde"] } 40 | tempfile = "3.1" 41 | wasmcloud-provider-core = "0.1" 42 | wasmcloud-fs = { version = "0.4", features = ["static_plugin"] } 43 | wasmcloud-logging = { path = "../wasmcloud-logging", version = "0.3", features = ["static_plugin"] } 44 | wasmcloud-httpserver = { version = "0.12", features = ["static_plugin"] } 45 | wascap = "0.6" 46 | k8s-openapi = { version = "0.11", default-features = false, features = ["v1_18"] } 47 | rand = "0.8" 48 | 49 | [dev-dependencies] 50 | oci-distribution = "0.6" 51 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A custom kubelet backend that can run [wasmCloud](https://github.com/wasmCloud/wasmCloud) based workloads 2 | //! 3 | //! The crate provides the [`WasmCloudProvider`] type which can be used 4 | //! as a provider with [`kubelet`]. 5 | //! 6 | //! # Example 7 | //! ```rust,no_run 8 | //! use kubelet::{Kubelet, config::Config}; 9 | //! use kubelet::store::oci::FileStore; 10 | //! use std::sync::Arc; 11 | //! use wasmcloud_provider::WasmCloudProvider; 12 | //! 13 | //! async fn start() { 14 | //! // Get a configuration for the Kubelet 15 | //! let kubelet_config = Config::default(); 16 | //! let client = oci_distribution::Client::default(); 17 | //! let store = Arc::new(FileStore::new(client, &std::path::PathBuf::from(""))); 18 | //! 19 | //! // Load a kubernetes configuration 20 | //! let kubeconfig = kube::Config::infer().await.unwrap(); 21 | //! let plugin_registry = Arc::new(Default::default()); 22 | //! 23 | //! // Instantiate the provider type 24 | //! let provider = WasmCloudProvider::new(store, &kubelet_config, kubeconfig.clone(), plugin_registry).await.unwrap(); 25 | //! 26 | //! // Instantiate the Kubelet 27 | //! let kubelet = Kubelet::new(provider, kubeconfig, kubelet_config).await.unwrap(); 28 | //! // Start the Kubelet and block on it 29 | //! kubelet.start().await.unwrap(); 30 | //! } 31 | //! ``` 32 | 33 | #![deny(missing_docs)] 34 | 35 | use async_trait::async_trait; 36 | 37 | use kubelet::container::Handle as ContainerHandle; 38 | use kubelet::handle::StopHandler; 39 | use kubelet::node::Builder; 40 | use kubelet::plugin_watcher::PluginRegistry; 41 | use kubelet::pod::state::prelude::SharedState; 42 | use kubelet::pod::{Handle, Pod, PodKey}; 43 | use kubelet::provider::Provider; 44 | use kubelet::provider::ProviderError; 45 | use kubelet::state::common::registered::Registered; 46 | use kubelet::state::common::terminated::Terminated; 47 | use kubelet::state::common::{GenericProvider, GenericProviderState}; 48 | use kubelet::store::Store; 49 | use kubelet::volume::Ref; 50 | 51 | use log::{debug, info, trace}; 52 | use tempfile::NamedTempFile; 53 | use tokio::sync::{Mutex, RwLock}; 54 | use wascap::jwt::{CapabilityProvider, Claims}; 55 | use wasmcloud_fs::FileSystemProvider; 56 | use wasmcloud_host::{Actor, Host, HostBuilder, NativeCapability}; 57 | use wasmcloud_httpserver::HttpServerProvider; 58 | use wasmcloud_logging::{LoggingProvider, LOG_PATH_KEY}; 59 | 60 | use std::collections::{BTreeMap, HashMap}; 61 | use std::path::{Path, PathBuf}; 62 | use std::sync::Arc; 63 | 64 | mod states; 65 | 66 | use states::pod::PodState; 67 | 68 | /// The architecture that the pod targets. 69 | const TARGET_WASM32_WASMCLOUD: &str = "wasm32-wasmcloud"; 70 | 71 | /// The name of the Filesystem capability. 72 | const FS_CAPABILITY: &str = "wasmcloud:blobstore"; 73 | 74 | /// The name of the HTTP capability. 75 | const HTTP_CAPABILITY: &str = "wasmcloud:httpserver"; 76 | 77 | /// The name of the Logging capability. 78 | const LOG_CAPABILITY: &str = "wasmcloud:logging"; 79 | 80 | /// The root directory of wasmCloud logs. 81 | const LOG_DIR_NAME: &str = "wasmcloud-logs"; 82 | 83 | /// The key used to define the root directory of the Filesystem capability. 84 | const FS_CONFIG_ROOTDIR: &str = "ROOT"; 85 | 86 | /// The root directory of wasmCloud volumes. 87 | const VOLUME_DIR: &str = "volumes"; 88 | 89 | /// Kubernetes' view of environment variables is an unordered map of string to string. 90 | type EnvVars = std::collections::HashMap; 91 | 92 | /// A [kubelet::handle::StopHandler] implementation for a wasmCloud actor 93 | pub struct ActorHandle { 94 | /// The public key of the wasmCloud Actor that will be stopped 95 | pub key: String, 96 | host: Arc>, 97 | volumes: Vec, 98 | capabilities: Vec, 99 | } 100 | 101 | #[async_trait::async_trait] 102 | impl StopHandler for ActorHandle { 103 | async fn stop(&mut self) -> anyhow::Result<()> { 104 | debug!("stopping wasmcloud instance {}", self.key); 105 | let host = self.host.clone(); 106 | let key = self.key.clone(); 107 | let volumes: Vec = self.volumes.drain(0..).collect(); 108 | 109 | let lock = host.lock().await; 110 | 111 | // NOTE: Not running these in parallel because the host is behind a mutex. None of these 112 | // calls are `&mut self`, so I think we might be able to make it just a plain `Arc` instead 113 | // if it starts taking a while to stop actors 114 | debug!("Removing capability links"); 115 | for cap in self.capabilities.iter() { 116 | trace!("Attempting to remove link for {} capability", cap); 117 | match cap.as_str() { 118 | FS_CAPABILITY => { 119 | for volume in volumes.iter() { 120 | lock.stop_provider( 121 | FS_CAPABILITY_PUBKEY, 122 | FS_CAPABILITY, 123 | Some(volume.name.clone()), 124 | ) 125 | .await 126 | .map_err(|e| { 127 | anyhow::anyhow!( 128 | "unable to remove volume {:?} capability: {:?}", 129 | volume.name, 130 | e 131 | ) 132 | })?; 133 | 134 | lock.remove_link(&key, FS_CAPABILITY, Some(volume.name.clone())) 135 | .await 136 | .map_err(|e| { 137 | anyhow::anyhow!( 138 | "unable to unlink volume {:?} capability: {:?}", 139 | volume.name, 140 | e 141 | ) 142 | })?; 143 | } 144 | } 145 | HTTP_CAPABILITY => { 146 | lock.remove_link(&key, HTTP_CAPABILITY, None) 147 | .await 148 | .map_err(|e| { 149 | anyhow::anyhow!("unable to unlink http capability: {:?}", e) 150 | })?; 151 | } 152 | LOG_CAPABILITY => { 153 | lock.remove_link(&key, LOG_CAPABILITY, None) 154 | .await 155 | .map_err(|e| anyhow::anyhow!("unable to unlink log capability: {:?}", e))?; 156 | } 157 | _ => info!("Found unmanged capability {}. Skipping", cap), 158 | } 159 | } 160 | lock.stop_actor(&key) 161 | .await 162 | .map_err(|e| anyhow::anyhow!("unable to remove actor: {:?}", e))?; 163 | 164 | Ok(()) 165 | } 166 | 167 | async fn wait(&mut self) -> anyhow::Result<()> { 168 | // the `stop_actor` call should handle this ok, so we just return Ok 169 | Ok(()) 170 | } 171 | } 172 | 173 | /// WasmCloudProvider provides a Kubelet runtime implementation that executes WASM binaries. 174 | /// 175 | /// Currently, this runtime uses wasmCloud as a host, loading the primary container as an actor. 176 | /// TODO: In the future, we will look at loading capabilities using the "sidecar" metaphor 177 | /// from Kubernetes. 178 | #[derive(Clone)] 179 | pub struct WasmCloudProvider { 180 | shared: ProviderState, 181 | } 182 | 183 | /// Provider-level state shared between all pods 184 | #[derive(Clone)] 185 | pub struct ProviderState { 186 | client: kube::Client, 187 | handles: Arc>>>, 188 | store: Arc, 189 | volume_path: PathBuf, 190 | log_path: PathBuf, 191 | host: Arc>, 192 | port_map: Arc>>, 193 | plugin_registry: Arc, 194 | } 195 | 196 | #[async_trait::async_trait] 197 | impl GenericProviderState for ProviderState { 198 | fn client(&self) -> kube::client::Client { 199 | self.client.clone() 200 | } 201 | fn store(&self) -> std::sync::Arc<(dyn Store + Send + Sync + 'static)> { 202 | self.store.clone() 203 | } 204 | fn volume_path(&self) -> PathBuf { 205 | self.volume_path.clone() 206 | } 207 | fn plugin_registry(&self) -> Option> { 208 | Some(self.plugin_registry.clone()) 209 | } 210 | async fn stop(&self, pod: &Pod) -> anyhow::Result<()> { 211 | let key = PodKey::from(pod); 212 | let mut handle_writer = self.handles.write().await; 213 | if let Some(handle) = handle_writer.get_mut(&key) { 214 | handle.stop().await 215 | } else { 216 | Ok(()) 217 | } 218 | } 219 | } 220 | 221 | impl WasmCloudProvider { 222 | /// Returns a new wasmCloud provider configured to use the proper data directory 223 | /// (including creating it if necessary) 224 | pub async fn new( 225 | store: Arc, 226 | config: &kubelet::config::Config, 227 | kubeconfig: kube::Config, 228 | plugin_registry: Arc, 229 | ) -> anyhow::Result { 230 | let client = kube::Client::new(kubeconfig); 231 | let host = HostBuilder::new().build(); 232 | host.start() 233 | .await 234 | .map_err(|e| anyhow::anyhow!("Unable to start wasmCloud host: {}", e.to_string()))?; 235 | let log_path = config.data_dir.join(LOG_DIR_NAME); 236 | let volume_path = config.data_dir.join(VOLUME_DIR); 237 | let port_map = Arc::new(Mutex::new(BTreeMap::::new())); 238 | tokio::fs::create_dir_all(&log_path).await?; 239 | tokio::fs::create_dir_all(&volume_path).await?; 240 | 241 | // wasmCloud has native and portable capabilities. 242 | // 243 | // Native capabilities are either dynamic libraries (.so, .dylib, .dll) 244 | // or statically linked Rust libaries. If the native capabilty is a dynamic 245 | // library it must be loaded and configured through [`NativeCapability::from_file`]. 246 | // If it is a statically linked libary it can be configured through 247 | // [`NativeCapability::from_instance`]. 248 | // 249 | // Portable capabilities are WASM modules. Portable capabilities 250 | // don't fully work, and won't until the WASI spec has matured. 251 | // 252 | // Here we are using the native capabilties as statically linked libraries that will 253 | // be compiled into the wasmcloud-provider binary. 254 | info!("Loading HTTP capability"); 255 | let http_provider = HttpServerProvider::new(); 256 | let data = 257 | NativeCapability::from_instance(http_provider, None, get_claims(HTTP_CAPABILITY)) 258 | .map_err(|e| anyhow::anyhow!("Failed to instantiate HTTP capability: {}", e))?; 259 | 260 | host.start_native_capability(data) 261 | .await 262 | .map_err(|e| anyhow::anyhow!("Failed to add HTTP capability: {}", e))?; 263 | 264 | info!("Loading log capability"); 265 | let logging_provider = LoggingProvider::new(); 266 | let logging_capability = 267 | NativeCapability::from_instance(logging_provider, None, get_claims(LOG_CAPABILITY)) 268 | .map_err(|e| anyhow::anyhow!("Failed to instantiate log capability: {}", e))?; 269 | host.start_native_capability(logging_capability) 270 | .await 271 | .map_err(|e| anyhow::anyhow!("Failed to add log capability: {}", e))?; 272 | Ok(Self { 273 | shared: ProviderState { 274 | client, 275 | handles: Default::default(), 276 | store, 277 | volume_path, 278 | log_path, 279 | host: Arc::new(Mutex::new(host)), 280 | port_map, 281 | plugin_registry, 282 | }, 283 | }) 284 | } 285 | } 286 | 287 | struct ModuleRunContext { 288 | modules: HashMap>, 289 | volumes: HashMap, 290 | } 291 | 292 | #[async_trait] 293 | impl Provider for WasmCloudProvider { 294 | type ProviderState = ProviderState; 295 | type InitialState = Registered; 296 | type TerminatedState = Terminated; 297 | type PodState = PodState; 298 | 299 | const ARCH: &'static str = TARGET_WASM32_WASMCLOUD; 300 | 301 | fn provider_state(&self) -> SharedState { 302 | Arc::new(RwLock::new(self.shared.clone())) 303 | } 304 | 305 | async fn node(&self, builder: &mut Builder) -> anyhow::Result<()> { 306 | builder.set_architecture("wasm-wasi"); 307 | builder.add_taint("NoSchedule", "kubernetes.io/arch", Self::ARCH); 308 | builder.add_taint("NoExecute", "kubernetes.io/arch", Self::ARCH); 309 | Ok(()) 310 | } 311 | 312 | async fn initialize_pod_state(&self, pod: &Pod) -> anyhow::Result { 313 | Ok(PodState::new(pod)) 314 | } 315 | 316 | async fn logs( 317 | &self, 318 | namespace: String, 319 | pod_name: String, 320 | container_name: String, 321 | sender: kubelet::log::Sender, 322 | ) -> anyhow::Result<()> { 323 | let mut handles = self.shared.handles.write().await; 324 | let handle = handles 325 | .get_mut(&PodKey::new(&namespace, &pod_name)) 326 | .ok_or_else(|| ProviderError::PodNotFound { 327 | pod_name: pod_name.clone(), 328 | })?; 329 | handle.output(&container_name, sender).await 330 | } 331 | 332 | fn plugin_registry(&self) -> Option> { 333 | Some(self.shared.plugin_registry.clone()) 334 | } 335 | } 336 | 337 | impl GenericProvider for WasmCloudProvider { 338 | type ProviderState = ProviderState; 339 | type PodState = PodState; 340 | type RunState = crate::states::pod::starting::Starting; 341 | 342 | fn validate_pod_runnable(pod: &Pod) -> anyhow::Result<()> { 343 | if !pod.init_containers().is_empty() { 344 | return Err(anyhow::anyhow!( 345 | "Cannot run {}: spec specifies init containers which are not supported on wasmCloud", 346 | pod.name() 347 | )); 348 | } 349 | Ok(()) 350 | } 351 | 352 | fn validate_container_runnable( 353 | container: &kubelet::container::Container, 354 | ) -> anyhow::Result<()> { 355 | if has_args(container) { 356 | return Err(anyhow::anyhow!( 357 | "Cannot run {}: spec specifies container args which are not supported on wasmCloud", 358 | container.name() 359 | )); 360 | } 361 | if let Some(image) = container.image()? { 362 | if image.whole().starts_with("k8s.gcr.io/kube-proxy") { 363 | return Err(anyhow::anyhow!("Cannot run kube-proxy")); 364 | } 365 | } 366 | 367 | Ok(()) 368 | } 369 | } 370 | 371 | fn has_args(container: &kubelet::container::Container) -> bool { 372 | match &container.args() { 373 | None => false, 374 | Some(vec) => !vec.is_empty(), 375 | } 376 | } 377 | 378 | struct VolumeBinding { 379 | name: String, 380 | host_path: PathBuf, 381 | } 382 | 383 | /// Capability describes a wasmCloud capability. 384 | /// 385 | /// Capabilities are made available to actors through a two-part processthread: 386 | /// - They must be registered 387 | /// - For each actor, the capability must be configured 388 | struct Capability { 389 | name: &'static str, 390 | binding: Option, 391 | capability_provider_id: &'static str, 392 | env: EnvVars, 393 | } 394 | 395 | /// Holds our tempfile handle. 396 | struct LogHandleFactory { 397 | temp: NamedTempFile, 398 | } 399 | 400 | impl kubelet::log::HandleFactory for LogHandleFactory { 401 | /// Creates `tokio::fs::File` on demand for log reading. 402 | fn new_handle(&self) -> tokio::fs::File { 403 | tokio::fs::File::from_std(self.temp.reopen().unwrap()) 404 | } 405 | } 406 | 407 | /// Run the given WASM data as a wasmCloud actor with the given public key. 408 | /// 409 | /// The provided capabilities will be configured for this actor, but the capabilities 410 | /// must first be loaded into the host by some other process, such as register_native_capabilities(). 411 | async fn wasmcloud_run( 412 | host: Arc>, 413 | data: Vec, 414 | env: EnvVars, 415 | volumes: Vec, 416 | log_path: &Path, 417 | port_assigned: u16, 418 | ) -> anyhow::Result> { 419 | let mut capabilities: Vec = Vec::new(); 420 | info!("sending actor to wasmCloud host"); 421 | let log_output = NamedTempFile::new_in(&log_path)?; 422 | 423 | let load = 424 | Actor::from_slice(&data).map_err(|e| anyhow::anyhow!("Error loading WASM: {}", e))?; 425 | let pk = load.public_key(); 426 | 427 | let actor_caps = load.capabilities(); 428 | 429 | if actor_caps.contains(&LOG_CAPABILITY.to_owned()) { 430 | let mut logenv = env.clone(); 431 | logenv.insert( 432 | LOG_PATH_KEY.to_string(), 433 | log_output.path().to_str().unwrap().to_owned(), 434 | ); 435 | capabilities.push(Capability { 436 | name: LOG_CAPABILITY, 437 | binding: None, 438 | capability_provider_id: LOG_CAPABILITY_PUBKEY, 439 | env: logenv, 440 | }); 441 | } 442 | 443 | if actor_caps.contains(&HTTP_CAPABILITY.to_owned()) { 444 | let mut httpenv = env.clone(); 445 | httpenv.insert("PORT".to_string(), port_assigned.to_string()); 446 | capabilities.push(Capability { 447 | name: HTTP_CAPABILITY, 448 | binding: None, 449 | capability_provider_id: HTTP_CAPABILITY_PUBKEY, 450 | env: httpenv, 451 | }); 452 | } 453 | { 454 | let lock = host.lock().await; 455 | if actor_caps.contains(&FS_CAPABILITY.to_owned()) { 456 | for vol in &volumes { 457 | info!( 458 | "Loading File System capability for volume name: '{}' host_path: '{}'", 459 | vol.name, 460 | vol.host_path.display() 461 | ); 462 | let mut fsenv = env.clone(); 463 | fsenv.insert( 464 | FS_CONFIG_ROOTDIR.to_owned(), 465 | vol.host_path.as_path().to_str().unwrap().to_owned(), 466 | ); 467 | let fs_provider = FileSystemProvider::new(); 468 | let fs_capability = NativeCapability::from_instance( 469 | fs_provider, 470 | Some(vol.name.clone()), 471 | get_claims(FS_CAPABILITY), 472 | ) 473 | .map_err(|e| { 474 | anyhow::anyhow!("Failed to instantiate File System capability: {}", e) 475 | })?; 476 | lock.start_native_capability(fs_capability) 477 | .await 478 | .map_err(|e| anyhow::anyhow!("Failed to add File System capability: {}", e))?; 479 | capabilities.push(Capability { 480 | name: FS_CAPABILITY, 481 | binding: Some(vol.name.clone()), 482 | capability_provider_id: FS_CAPABILITY_PUBKEY, 483 | env: fsenv, 484 | }); 485 | } 486 | } 487 | 488 | lock.start_actor(load) 489 | .await 490 | .map_err(|e| anyhow::anyhow!("Error adding actor: {}", e))?; 491 | for cap in capabilities { 492 | info!("configuring capability {}", cap.name); 493 | lock.set_link( 494 | &pk, 495 | cap.name, 496 | cap.binding.clone(), 497 | cap.capability_provider_id.to_owned(), 498 | cap.env.clone(), 499 | ) 500 | .await 501 | .map_err(|e| anyhow::anyhow!("Error configuring capabilities for module: {}", e))?; 502 | } 503 | } 504 | 505 | let log_handle_factory = LogHandleFactory { temp: log_output }; 506 | 507 | info!("wasmCloud actor executing"); 508 | Ok(ContainerHandle::new( 509 | ActorHandle { 510 | host, 511 | key: pk, 512 | volumes, 513 | capabilities: actor_caps, 514 | }, 515 | log_handle_factory, 516 | )) 517 | } 518 | 519 | // This code contains the embedded claims needed to register the 3 providers. The public key comes 520 | // from the `sub` claim on each token. These tokens were generated with the following commands: 521 | // 522 | // `wash claims token provider --capid wasmcloud:blobstore --name "wasmCloud FS capability" --vendor wasmCloud` 523 | // `wash claims token provider --capid wasmcloud:httpserver --name "wasmCloud HTTP server capability" --vendor wasmCloud` 524 | // `wash claims token provider --capid wasmcloud:logging --name "wasmCloud krustlet logging capability" --vendor krustlet` 525 | 526 | const FS_CAPABILITY_JWT: &str = "eyJ0eXAiOiJqd3QiLCJhbGciOiJFZDI1NTE5In0.eyJqdGkiOiJtaHF4dnJ2djdRdHZNdWFSRVFlcTlyIiwiaWF0IjoxNjE3MTQ1ODA4LCJpc3MiOiJBQ1hZUE1BTlg1Uk5UTks0R1VVUEtFU1BQWU9DNEhPQ0RITlJFT0IySzVEVk82SUdIM0RENEtQVSIsInN1YiI6IlZBM1haSlhQUlRUN0o3WFhKRTI0TE1QSzdIUVI3M1cyVE9aU0o2NFpaTU80WVdNSU8yU0IzSUIyIiwid2FzY2FwIjp7Im5hbWUiOiJ3YXNtQ2xvdWQgRlMgY2FwYWJpbGl0eSIsImNhcGlkIjoid2FzbWNsb3VkOmJsb2JzdG9yZSIsInZlbmRvciI6Indhc21DbG91ZCIsInRhcmdldF9oYXNoZXMiOnt9fX0.rjxaEENSxMPiWIPA2R8VxiO-cNLoDRcXMKcbVC5fR966Tb7VhqK-DH9RJ7Oj6T5OgJpjqrempDqSqA4LdREjDg"; 527 | const FS_CAPABILITY_PUBKEY: &str = "VA3XZJXPRTT7J7XXJE24LMPK7HQR73W2TOZSJ64ZZMO4YWMIO2SB3IB2"; 528 | const HTTP_CAPABILITY_JWT: &str = "eyJ0eXAiOiJqd3QiLCJhbGciOiJFZDI1NTE5In0.eyJqdGkiOiJiTHl0ODdTbnVxY2RJdmUxWkVxRkExIiwiaWF0IjoxNjE3MTQ1ODM1LCJpc3MiOiJBQ1hZUE1BTlg1Uk5UTks0R1VVUEtFU1BQWU9DNEhPQ0RITlJFT0IySzVEVk82SUdIM0RENEtQVSIsInN1YiI6IlZCSDNNRkNFRFBRUFNJWUtVQzdJVVc3UlUyRzZYWEVKRjM0Uk8yNldWUlRHR0U0VVE1WFRBM1ZRIiwid2FzY2FwIjp7Im5hbWUiOiJ3YXNtQ2xvdWQgSFRUUCBzZXJ2ZXIgY2FwYWJpbGl0eSIsImNhcGlkIjoid2FzbWNsb3VkOmh0dHBzZXJ2ZXIiLCJ2ZW5kb3IiOiJ3YXNtQ2xvdWQiLCJ0YXJnZXRfaGFzaGVzIjp7fX19.gPhGiHq953a4w9cv1ZI_Hn9l7jQCcjiihL5ofmTjEQZk6mPIvK4lZSI-LIJQp7wZKVMFIe3bcs4Vdifhwq8ACg"; 529 | const HTTP_CAPABILITY_PUBKEY: &str = "VBH3MFCEDPQPSIYKUC7IUW7RU2G6XXEJF34RO26WVRTGGE4UQ5XTA3VQ"; 530 | const LOG_CAPABILITY_JWT: &str = "eyJ0eXAiOiJqd3QiLCJhbGciOiJFZDI1NTE5In0.eyJqdGkiOiJqSnJ5cDRFWnFTdU5RYlY0dVVXbmVRIiwiaWF0IjoxNjE3MTQ1ODY0LCJpc3MiOiJBQ1hZUE1BTlg1Uk5UTks0R1VVUEtFU1BQWU9DNEhPQ0RITlJFT0IySzVEVk82SUdIM0RENEtQVSIsInN1YiI6IlZESVlXNjMyMzdWSlFTSElTS1BCTzJDUTY3NE9QSTVaQ1ZXUTJQWFRBNEhJWU81TFhITEwzRFhRIiwid2FzY2FwIjp7Im5hbWUiOiJ3YXNtQ2xvdWQga3J1c3RsZXQgbG9nZ2luZyBjYXBhYmlsaXR5IiwiY2FwaWQiOiJ3YXNtY2xvdWQ6bG9nZ2luZyIsInZlbmRvciI6ImtydXN0bGV0IiwidGFyZ2V0X2hhc2hlcyI6e319fQ.SOqvIkPbFuPt5isr58CpLDV9Zbnmy5WzFR7cX5gBYc0fNbyY5qmtj1CLvzzQm1n0AamD-hFN_8UTNlx67y0tCg"; 531 | const LOG_CAPABILITY_PUBKEY: &str = "VDIYW63237VJQSHISKPBO2CQ674OPI5ZCVWQ2PXTA4HIYO5LXHLL3DXQ"; 532 | 533 | /// gets the proper claims for the given capability. Panics if the capability claim doesn't exist 534 | fn get_claims(capid: &str) -> Claims { 535 | let token = match capid { 536 | FS_CAPABILITY => FS_CAPABILITY_JWT, 537 | HTTP_CAPABILITY => HTTP_CAPABILITY_JWT, 538 | LOG_CAPABILITY => LOG_CAPABILITY_JWT, 539 | _ => panic!("Unknown capability {}", capid), 540 | }; 541 | 542 | Claims::::decode(token).unwrap() 543 | } 544 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod container; 2 | pub(crate) mod pod; 3 | 4 | /// When called in a state's `next` function, exits the current state 5 | /// and transitions to the Error state. 6 | #[macro_export] 7 | macro_rules! transition_to_error { 8 | ($slf:ident, $err:ident) => {{ 9 | let aerr = anyhow::Error::from($err); 10 | log::error!("{:?}", aerr); 11 | let error_state = 12 | kubelet::state::common::error::Error::::new(aerr.to_string()); 13 | return Transition::next($slf, error_state); 14 | }}; 15 | } 16 | 17 | /// When called in a state's `next` function, exits the state machine 18 | /// returns a fatal error to the kubelet. 19 | #[macro_export] 20 | macro_rules! fail_fatal { 21 | ($err:ident) => {{ 22 | let aerr = anyhow::Error::from($err); 23 | log::error!("{:?}", aerr); 24 | return Transition::Complete(Err(aerr)); 25 | }}; 26 | } 27 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/container.rs: -------------------------------------------------------------------------------- 1 | use crate::ModuleRunContext; 2 | use crate::ProviderState; 3 | use krator::{ObjectState, SharedState}; 4 | use kubelet::container::{Container, ContainerKey, Status}; 5 | use kubelet::pod::Pod; 6 | 7 | pub(crate) mod running; 8 | pub(crate) mod terminated; 9 | pub(crate) mod waiting; 10 | 11 | pub(crate) struct ContainerState { 12 | pod: Pod, 13 | container_key: ContainerKey, 14 | run_context: SharedState, 15 | } 16 | 17 | impl ContainerState { 18 | pub fn new( 19 | pod: Pod, 20 | container_key: ContainerKey, 21 | run_context: SharedState, 22 | ) -> Self { 23 | ContainerState { 24 | pod, 25 | container_key, 26 | run_context, 27 | } 28 | } 29 | } 30 | 31 | #[async_trait::async_trait] 32 | impl ObjectState for ContainerState { 33 | type Manifest = Container; 34 | type Status = Status; 35 | type SharedState = ProviderState; 36 | async fn async_drop(self, _shared_state: &mut Self::SharedState) {} 37 | } 38 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/container/running.rs: -------------------------------------------------------------------------------- 1 | use super::terminated::Terminated; 2 | use super::ContainerState; 3 | use crate::ProviderState; 4 | use kubelet::container::state::prelude::*; 5 | 6 | /// The container is starting. 7 | #[derive(Debug, TransitionTo)] 8 | #[transition_to(Terminated)] 9 | pub struct Running; 10 | 11 | #[async_trait::async_trait] 12 | impl State for Running { 13 | async fn next( 14 | mut self: Box, 15 | _shared_state: SharedState, 16 | _state: &mut ContainerState, 17 | _container: Manifest, 18 | ) -> Transition { 19 | loop { 20 | tokio::time::sleep(std::time::Duration::from_secs(10)).await; 21 | } 22 | } 23 | 24 | async fn status( 25 | &self, 26 | _state: &mut ContainerState, 27 | _container: &Container, 28 | ) -> anyhow::Result { 29 | Ok(Status::running()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/container/terminated.rs: -------------------------------------------------------------------------------- 1 | use log::error; 2 | 3 | use kubelet::container::state::prelude::*; 4 | 5 | use crate::ProviderState; 6 | 7 | use super::ContainerState; 8 | 9 | /// The container is starting. 10 | #[derive(Debug)] 11 | pub struct Terminated { 12 | message: String, 13 | failed: bool, 14 | } 15 | 16 | impl Terminated { 17 | pub fn new(message: String, failed: bool) -> Self { 18 | Terminated { message, failed } 19 | } 20 | } 21 | 22 | #[async_trait::async_trait] 23 | impl State for Terminated { 24 | async fn next( 25 | self: Box, 26 | _shared_state: SharedState, 27 | state: &mut ContainerState, 28 | container: Manifest, 29 | ) -> Transition { 30 | let container = container.latest(); 31 | 32 | if self.failed { 33 | error!( 34 | "Pod {} container {} exited with error: {}", 35 | state.pod.name(), 36 | container.name(), 37 | &self.message 38 | ); 39 | Transition::Complete(Err(anyhow::anyhow!(self.message.clone()))) 40 | } else { 41 | Transition::Complete(Ok(())) 42 | } 43 | } 44 | 45 | async fn status( 46 | &self, 47 | _state: &mut ContainerState, 48 | _container: &Container, 49 | ) -> anyhow::Result { 50 | Ok(Status::terminated(&self.message, self.failed)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/container/waiting.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, BTreeSet, HashMap}; 2 | use std::convert::TryFrom; 3 | use std::ops::Deref; 4 | use std::sync::Arc; 5 | 6 | use log::{debug, error, info}; 7 | use rand::Rng; 8 | use tokio::sync::Mutex; 9 | 10 | use kubelet::container::state::prelude::*; 11 | use kubelet::pod::{Handle as PodHandle, Pod, PodKey}; 12 | use kubelet::provider::Provider; 13 | 14 | use crate::wasmcloud_run; 15 | use crate::ProviderState; 16 | use crate::VolumeBinding; 17 | use crate::WasmCloudProvider; 18 | 19 | use super::running::Running; 20 | use super::terminated::Terminated; 21 | use super::ContainerState; 22 | 23 | #[derive(Debug)] 24 | struct PortAllocationError; 25 | 26 | impl std::fmt::Display for PortAllocationError { 27 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 28 | write!(f, "all ports are currently in use") 29 | } 30 | } 31 | 32 | impl std::error::Error for PortAllocationError { 33 | fn description(&self) -> &str { 34 | "all ports are currently in use" 35 | } 36 | } 37 | 38 | async fn find_available_port( 39 | port_map: &Arc>>, 40 | pod: &Pod, 41 | ) -> Result { 42 | let pod_key = PodKey::from(pod); 43 | let mut empty_port: BTreeSet = BTreeSet::new(); 44 | let mut lock = port_map.lock().await; 45 | while empty_port.len() < 2768 { 46 | let generated_port: u16 = rand::thread_rng().gen_range(30000..=32768); 47 | if !lock.contains_key(&generated_port) { 48 | lock.insert(generated_port, pod_key); 49 | return Ok(generated_port); 50 | } 51 | empty_port.insert(generated_port); 52 | } 53 | Err(PortAllocationError) 54 | } 55 | 56 | async fn assign_container_port( 57 | port_map: Arc>>, 58 | pod: &Pod, 59 | container: &Container, 60 | ) -> anyhow::Result { 61 | let mut port_assigned: u16 = 0; 62 | if let Some(container_vec) = container.ports().as_ref() { 63 | for c_port in container_vec.iter() { 64 | let container_port = c_port.container_port; 65 | if let Some(host_port) = c_port.host_port { 66 | let host_port: u16 = u16::try_from(host_port)?; 67 | let mut lock = port_map.lock().await; 68 | if !lock.contains_key(&host_port) { 69 | port_assigned = host_port; 70 | lock.insert(port_assigned, PodKey::from(pod)); 71 | } else { 72 | error!( 73 | "Failed to assign hostport {}, because it's taken", 74 | &host_port 75 | ); 76 | return Err(anyhow::anyhow!("Port {} is currently in use", &host_port)); 77 | } 78 | } else if (0..=65536).contains(&container_port) { 79 | port_assigned = find_available_port(&port_map, pod).await?; 80 | } 81 | } 82 | } 83 | Ok(port_assigned) 84 | } 85 | 86 | /// The container is starting. 87 | #[derive(Default, Debug, TransitionTo)] 88 | #[transition_to(Running, Terminated)] 89 | pub struct Waiting; 90 | 91 | #[async_trait::async_trait] 92 | impl State for Waiting { 93 | async fn next( 94 | self: Box, 95 | shared: SharedState, 96 | state: &mut ContainerState, 97 | container: Manifest, 98 | ) -> Transition { 99 | let container = container.latest(); 100 | 101 | info!( 102 | "Starting container {} for pod {}", 103 | container.name(), 104 | state.pod.name(), 105 | ); 106 | 107 | let port_assigned = { 108 | let port_map = shared.read().await.port_map.clone(); 109 | match assign_container_port(Arc::clone(&port_map), &state.pod, &container).await { 110 | Ok(port) => port, 111 | Err(e) => { 112 | return Transition::next( 113 | self, 114 | Terminated::new( 115 | format!( 116 | "Pod {} container {} failed to allocate port: {:?}", 117 | state.pod.name(), 118 | container.name(), 119 | e 120 | ), 121 | true, 122 | ), 123 | ) 124 | } 125 | } 126 | }; 127 | 128 | debug!( 129 | "New port assigned to {} is: {}", 130 | container.name(), 131 | port_assigned 132 | ); 133 | 134 | let (client, log_path, host) = { 135 | let state_reader = shared.read().await; 136 | ( 137 | state_reader.client.clone(), 138 | state_reader.log_path.clone(), 139 | state_reader.host.clone(), 140 | ) 141 | }; 142 | 143 | let env = ::env_vars(&container, &state.pod, &client).await; 144 | let volume_bindings: Vec = 145 | if let Some(volume_mounts) = container.volume_mounts().as_ref() { 146 | let run_context = state.run_context.read().await; 147 | match volume_mounts 148 | .iter() 149 | .map(|vm| -> anyhow::Result { 150 | // Check the volume exists first 151 | let vol = run_context.volumes.get(&vm.name).ok_or_else(|| { 152 | anyhow::anyhow!( 153 | "no volume with the name of {} found for container {}", 154 | vm.name, 155 | container.name() 156 | ) 157 | })?; 158 | // We can safely assume that this should be valid UTF-8 because it would have 159 | // been validated by the k8s API 160 | Ok(VolumeBinding { 161 | name: vm.name.clone(), 162 | host_path: vol.deref().clone(), 163 | }) 164 | }) 165 | .collect::>() 166 | { 167 | Ok(bindings) => bindings, 168 | Err(e) => { 169 | return Transition::next( 170 | self, 171 | Terminated::new( 172 | format!( 173 | "Pod {} container {} failed to allocate storage: {:?}", 174 | state.pod.name(), 175 | container.name(), 176 | e 177 | ), 178 | true, 179 | ), 180 | ) 181 | } 182 | } 183 | } else { 184 | vec![] 185 | }; 186 | 187 | debug!("Starting container {} on thread", container.name()); 188 | 189 | let module_data = { 190 | let mut run_context = state.run_context.write().await; 191 | match run_context.modules.remove(container.name()) { 192 | Some(module) => module, 193 | None => { 194 | return Transition::next( 195 | self, 196 | Terminated::new( 197 | format!( 198 | "FATAL ERROR: module map not properly populated ({}/{})", 199 | state.pod.name(), 200 | container.name(), 201 | ), 202 | true, 203 | ), 204 | ) 205 | } 206 | } 207 | }; 208 | 209 | match wasmcloud_run( 210 | host, 211 | module_data, 212 | env, 213 | volume_bindings, 214 | &log_path, 215 | port_assigned, 216 | ) 217 | .await 218 | { 219 | Ok(container_handle) => { 220 | let pod_key = PodKey::from(&state.pod); 221 | { 222 | let provider_state = shared.write().await; 223 | let mut handles_writer = provider_state.handles.write().await; 224 | let pod_handle = handles_writer 225 | .entry(pod_key) 226 | .or_insert_with(|| PodHandle::new(HashMap::new(), state.pod.clone(), None)); 227 | pod_handle 228 | .insert_container_handle(state.container_key.clone(), container_handle) 229 | .await; 230 | } 231 | } 232 | Err(e) => { 233 | return Transition::next( 234 | self, 235 | Terminated::new( 236 | format!( 237 | "Pod {} container {} failed to start wasmCloud actor: {:?}", 238 | state.pod.name(), 239 | container.name(), 240 | e 241 | ), 242 | true, 243 | ), 244 | ) 245 | } 246 | } 247 | 248 | Transition::next(self, Running) 249 | } 250 | 251 | async fn status( 252 | &self, 253 | _state: &mut ContainerState, 254 | _container: &Container, 255 | ) -> anyhow::Result { 256 | Ok(Status::waiting("Module is starting.")) 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/pod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use log::debug; 5 | use tokio::sync::RwLock; 6 | 7 | use krator::{ObjectState, SharedState}; 8 | use kubelet::backoff::BackoffStrategy; 9 | use kubelet::backoff::ExponentialBackoffStrategy; 10 | use kubelet::pod::{Pod, PodKey, Status}; 11 | use kubelet::state::common::{BackoffSequence, GenericPodState, ThresholdTrigger}; 12 | 13 | use crate::ModuleRunContext; 14 | use crate::ProviderState; 15 | 16 | pub(crate) mod running; 17 | pub(crate) mod starting; 18 | 19 | /// State that is shared between pod state handlers. 20 | pub struct PodState { 21 | key: PodKey, 22 | run_context: SharedState, 23 | errors: usize, 24 | image_pull_backoff_strategy: ExponentialBackoffStrategy, 25 | crash_loop_backoff_strategy: ExponentialBackoffStrategy, 26 | } 27 | 28 | impl PodState { 29 | pub fn new(pod: &Pod) -> Self { 30 | let run_context = ModuleRunContext { 31 | modules: Default::default(), 32 | volumes: Default::default(), 33 | }; 34 | let key = PodKey::from(pod); 35 | PodState { 36 | key, 37 | run_context: Arc::new(RwLock::new(run_context)), 38 | errors: 0, 39 | image_pull_backoff_strategy: ExponentialBackoffStrategy::default(), 40 | crash_loop_backoff_strategy: ExponentialBackoffStrategy::default(), 41 | } 42 | } 43 | } 44 | 45 | #[async_trait::async_trait] 46 | impl GenericPodState for PodState { 47 | async fn set_modules(&mut self, modules: HashMap>) { 48 | let mut run_context = self.run_context.write().await; 49 | run_context.modules = modules; 50 | } 51 | async fn set_volumes(&mut self, volumes: HashMap) { 52 | let mut run_context = self.run_context.write().await; 53 | run_context.volumes = volumes; 54 | } 55 | async fn backoff(&mut self, sequence: BackoffSequence) { 56 | let backoff_strategy = match sequence { 57 | BackoffSequence::ImagePull => &mut self.image_pull_backoff_strategy, 58 | BackoffSequence::CrashLoop => &mut self.crash_loop_backoff_strategy, 59 | }; 60 | backoff_strategy.wait().await; 61 | } 62 | async fn reset_backoff(&mut self, sequence: BackoffSequence) { 63 | let backoff_strategy = match sequence { 64 | BackoffSequence::ImagePull => &mut self.image_pull_backoff_strategy, 65 | BackoffSequence::CrashLoop => &mut self.crash_loop_backoff_strategy, 66 | }; 67 | backoff_strategy.reset(); 68 | } 69 | async fn record_error(&mut self) -> ThresholdTrigger { 70 | self.errors += 1; 71 | if self.errors > 3 { 72 | self.errors = 0; 73 | ThresholdTrigger::Triggered 74 | } else { 75 | ThresholdTrigger::Untriggered 76 | } 77 | } 78 | } 79 | 80 | #[async_trait::async_trait] 81 | impl ObjectState for PodState { 82 | type Manifest = Pod; 83 | type Status = Status; 84 | type SharedState = ProviderState; 85 | async fn async_drop(self, provider_state: &mut Self::SharedState) { 86 | { 87 | let mut lock = provider_state.port_map.lock().await; 88 | let ports_to_remove: Vec = lock 89 | .iter() 90 | .filter_map(|(k, v)| if v == &self.key { Some(*k) } else { None }) 91 | .collect(); 92 | debug!( 93 | "Pod {} in namespace {} releasing ports {:?}.", 94 | &self.key.name(), 95 | &self.key.namespace(), 96 | &ports_to_remove 97 | ); 98 | for port in ports_to_remove { 99 | lock.remove(&port); 100 | } 101 | } 102 | { 103 | let mut handles = provider_state.handles.write().await; 104 | handles.remove(&self.key); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/pod/running.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::mpsc::Receiver; 2 | 3 | use kubelet::pod::state::prelude::*; 4 | use kubelet::state::common::error::Error; 5 | use kubelet::state::common::GenericProviderState; 6 | 7 | use crate::{fail_fatal, PodState, ProviderState}; 8 | 9 | /// The Kubelet is running the Pod. 10 | #[derive(Debug, TransitionTo)] 11 | #[transition_to(Error)] 12 | pub struct Running { 13 | rx: Receiver>, 14 | } 15 | 16 | impl Running { 17 | pub fn new(rx: Receiver>) -> Self { 18 | Running { rx } 19 | } 20 | } 21 | 22 | #[async_trait::async_trait] 23 | impl State for Running { 24 | async fn next( 25 | mut self: Box, 26 | provider_state: SharedState, 27 | _pod_state: &mut PodState, 28 | pod: Manifest, 29 | ) -> Transition { 30 | let pod = pod.latest(); 31 | 32 | // This collects errors from registering the actor. 33 | if let Some(result) = self.rx.recv().await { 34 | match result { 35 | Ok(()) => { 36 | // This indicates some sort of premature exit. 37 | return Transition::next( 38 | self, 39 | Error::new(format!("Pod {} container exitted.", pod.name())), 40 | ); 41 | } 42 | Err(e) => { 43 | // Stop remaining containers; 44 | { 45 | let provider = provider_state.write().await; 46 | // This Result doesnt matter since we are about to exit with error. 47 | provider.stop(&pod).await.ok(); 48 | } 49 | fail_fatal!(e); 50 | } 51 | } 52 | } 53 | Transition::next( 54 | self, 55 | Error::new(format!( 56 | "Pod {} container result channel hung up.", 57 | pod.name() 58 | )), 59 | ) 60 | } 61 | 62 | async fn status(&self, _pod_state: &mut PodState, _pod: &Pod) -> anyhow::Result { 63 | Ok(make_status(Phase::Running, "Running")) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/wasmcloud-provider/src/states/pod/starting.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use log::info; 4 | 5 | use kubelet::container::{state::run_to_completion, ContainerKey}; 6 | use kubelet::pod::state::prelude::*; 7 | use kubelet::state::common::error::Error; 8 | use kubelet::state::common::GenericProviderState; 9 | 10 | use crate::states::container::waiting::Waiting; 11 | use crate::states::container::ContainerState; 12 | use crate::{PodState, ProviderState}; 13 | 14 | use super::running::Running; 15 | 16 | /// The Kubelet is starting the Pod. 17 | #[derive(Default, Debug, TransitionTo)] 18 | #[transition_to(Running, Error)] 19 | pub struct Starting; 20 | 21 | #[async_trait::async_trait] 22 | impl State for Starting { 23 | async fn next( 24 | self: Box, 25 | provider_state: SharedState, 26 | pod_state: &mut PodState, 27 | pod: Manifest, 28 | ) -> Transition { 29 | let pod_rx = pod.clone(); 30 | let pod = pod.latest(); 31 | 32 | info!("Starting containers for pod {:?}", pod.name()); 33 | 34 | let containers = pod.containers(); 35 | let (tx, rx) = tokio::sync::mpsc::channel(containers.len()); 36 | for container in containers { 37 | let initial_state = Waiting; 38 | let container_key = ContainerKey::App(container.name().to_string()); 39 | let container_state = ContainerState::new( 40 | pod.clone(), 41 | container_key.clone(), 42 | Arc::clone(&pod_state.run_context), 43 | ); 44 | let task_provider = Arc::clone(&provider_state); 45 | let task_pod = pod_rx.clone(); 46 | let task_tx = tx.clone(); 47 | tokio::task::spawn(async move { 48 | let client = { 49 | let provider_state = task_provider.read().await; 50 | provider_state.client() 51 | }; 52 | 53 | let result = run_to_completion( 54 | &client, 55 | initial_state, 56 | task_provider, 57 | container_state, 58 | task_pod, 59 | container_key, 60 | ) 61 | .await; 62 | task_tx.send(result).await 63 | }); 64 | } 65 | 66 | info!("All containers started for pod {:?}.", pod.name()); 67 | 68 | Transition::next(self, Running::new(rx)) 69 | } 70 | 71 | async fn status(&self, _pod_state: &mut PodState, _pod: &Pod) -> anyhow::Result { 72 | Ok(make_status(Phase::Pending, "Starting")) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/.gitignore: -------------------------------------------------------------------------------- 1 | .keys 2 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 8 | 9 | [[package]] 10 | name = "byteorder" 11 | version = "1.4.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 20 | 21 | [[package]] 22 | name = "fileserver" 23 | version = "0.3.0" 24 | dependencies = [ 25 | "wapc-guest", 26 | "wasmcloud-actor-blobstore", 27 | "wasmcloud-actor-core", 28 | "wasmcloud-actor-http-server", 29 | ] 30 | 31 | [[package]] 32 | name = "itoa" 33 | version = "0.4.5" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 36 | 37 | [[package]] 38 | name = "lazy_static" 39 | version = "1.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 42 | 43 | [[package]] 44 | name = "log" 45 | version = "0.4.14" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 48 | dependencies = [ 49 | "cfg-if", 50 | "serde", 51 | ] 52 | 53 | [[package]] 54 | name = "num-traits" 55 | version = "0.2.11" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 58 | dependencies = [ 59 | "autocfg", 60 | ] 61 | 62 | [[package]] 63 | name = "proc-macro2" 64 | version = "1.0.24" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 67 | dependencies = [ 68 | "unicode-xid", 69 | ] 70 | 71 | [[package]] 72 | name = "quote" 73 | version = "1.0.6" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 76 | dependencies = [ 77 | "proc-macro2", 78 | ] 79 | 80 | [[package]] 81 | name = "rmp" 82 | version = "0.8.9" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" 85 | dependencies = [ 86 | "byteorder", 87 | "num-traits", 88 | ] 89 | 90 | [[package]] 91 | name = "rmp-serde" 92 | version = "0.15.4" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "839395ef53057db96b84c9238ab29e1a13f2e5c8ec9f66bef853ab4197303924" 95 | dependencies = [ 96 | "byteorder", 97 | "rmp", 98 | "serde", 99 | ] 100 | 101 | [[package]] 102 | name = "ryu" 103 | version = "1.0.5" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 106 | 107 | [[package]] 108 | name = "serde" 109 | version = "1.0.125" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 112 | dependencies = [ 113 | "serde_derive", 114 | ] 115 | 116 | [[package]] 117 | name = "serde_bytes" 118 | version = "0.11.5" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 121 | dependencies = [ 122 | "serde", 123 | ] 124 | 125 | [[package]] 126 | name = "serde_derive" 127 | version = "1.0.125" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 130 | dependencies = [ 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "serde_json" 138 | version = "1.0.64" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 141 | dependencies = [ 142 | "itoa", 143 | "ryu", 144 | "serde", 145 | ] 146 | 147 | [[package]] 148 | name = "syn" 149 | version = "1.0.64" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" 152 | dependencies = [ 153 | "proc-macro2", 154 | "quote", 155 | "unicode-xid", 156 | ] 157 | 158 | [[package]] 159 | name = "unicode-xid" 160 | version = "0.2.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 163 | 164 | [[package]] 165 | name = "wapc-guest" 166 | version = "0.4.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "47cbd9d778b9718eda797278936f93f25ce81064fe26f0bb6a710cd51315f00b" 169 | dependencies = [ 170 | "lazy_static", 171 | ] 172 | 173 | [[package]] 174 | name = "wasmcloud-actor-blobstore" 175 | version = "0.2.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "36b1e8130245a50c37f7da28db0e1e3b63fc66faa9374b70beb69243a1b4333f" 178 | dependencies = [ 179 | "lazy_static", 180 | "log", 181 | "rmp-serde", 182 | "serde", 183 | "serde_bytes", 184 | "serde_derive", 185 | "serde_json", 186 | "wapc-guest", 187 | ] 188 | 189 | [[package]] 190 | name = "wasmcloud-actor-core" 191 | version = "0.2.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "3db490c15579b0c070db4cd548e4b0ebf344d5ced172d092971f07791b67b161" 194 | dependencies = [ 195 | "lazy_static", 196 | "log", 197 | "rmp-serde", 198 | "serde", 199 | "serde_bytes", 200 | "serde_json", 201 | "wapc-guest", 202 | "wasmcloud-actor-core-derive", 203 | ] 204 | 205 | [[package]] 206 | name = "wasmcloud-actor-core-derive" 207 | version = "0.1.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "48be47dd4eb5339812ef5cf60e174ad4ae6f5b4bc188dbed09eb668d7046b08f" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn", 214 | ] 215 | 216 | [[package]] 217 | name = "wasmcloud-actor-http-server" 218 | version = "0.1.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "227c7455dc3912ea692f6d66fca07147cae7a721dae15486caea68fbbdf40454" 221 | dependencies = [ 222 | "lazy_static", 223 | "log", 224 | "rmp-serde", 225 | "serde", 226 | "serde_bytes", 227 | "serde_derive", 228 | "serde_json", 229 | "wapc-guest", 230 | ] 231 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fileserver" 3 | version = "0.3.0" 4 | authors = ["Matt Fisher "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | wasmcloud-actor-core = { version = "0.2", features = ["guest"] } 12 | wapc-guest = "0.4" 13 | wasmcloud-actor-http-server = { version = "0.1", features = ["guest"] } 14 | wasmcloud-actor-blobstore = { version = "0.2", features = ["guest"] } 15 | 16 | [profile.release] 17 | opt-level = "s" 18 | lto = true 19 | 20 | [workspace] 21 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/README.md: -------------------------------------------------------------------------------- 1 | # Fileserver 2 | 3 | An example that will respond with the file metadata present in the volume, 4 | based on the URL path provided. 5 | 6 | If a POST request is made, the contents of the request body are written to the 7 | file based on the URL path provided. 8 | 9 | If a DELETE request is made, the file is removed based on the URL path provided. 10 | 11 | It is meant to demonstrate how volumes work with the wasmcloud-provider. 12 | 13 | ## Running the example 14 | 15 | This example has already been pre-built, so you only need to install it into 16 | your Kubernetes cluster. 17 | 18 | Create the pod and configmap with `kubectl`: 19 | 20 | ```shell 21 | $ kubectl create -f k8s.yaml 22 | ``` 23 | 24 | Once the pod is running, you can upload data with the following command: 25 | 26 | ```shell 27 | $ curl -X POST http://localhost:8080/foo -d 'foobar' 28 | ``` 29 | 30 | You can then get metadata by running: 31 | 32 | ```shell 33 | $ curl http://localhost:8080/foo 34 | OUTPUT TODO 35 | ``` 36 | 37 | And you can delete the file with: 38 | 39 | ```shell 40 | $ curl -X DELETE http://localhost:8080/foo 41 | OUTPUT TODO 42 | ``` 43 | 44 | ## Building the example 45 | 46 | To set up your development environment, you'll need the following tools: 47 | 48 | - cargo 49 | - wasm-to-oci 50 | - wash 51 | 52 | Instructions for [installing 53 | `cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html) and 54 | [`wasm-to-oci`](https://github.com/engineerd/wasm-to-oci) can be found in their 55 | respective project's documentation. Once those are installed, 56 | [`wash`](https://github.com/wasmCloud/wash#installing-wash) can be installed with 57 | 58 | ```shell 59 | $ cargo install wash-cli 60 | ``` 61 | 62 | Once complete, you'll need to build the binary: 63 | 64 | ```shell 65 | $ cargo build --release 66 | ``` 67 | 68 | After the binary is built, you'll need to sign the compiled Wasm binary so it can be trusted by 69 | wasmCloud: 70 | 71 | ```shell 72 | $ wash claims sign --http_server --logging --blob_store ./target/wasm32-unknown-unknown/release/fileserver.wasm --name fileserver 73 | No keypair found in "/Users/foobar/.wash/keys/fileserver_module.nk". 74 | We will generate one for you and place it there. 75 | If you'd like to use alternative keys, you can supply them as a flag. 76 | 77 | Successfully signed ./target/wasm32-unknown-unknown/release/fileserver_s.wasm with capabilities: wasmcloud:httpserver,wasmcloud:blobstore,wasmcloud:logging 78 | ``` 79 | 80 | Once signed, you can push it to an OCI registry. Please note that you'll need to be signed into that 81 | registry in order to push: 82 | 83 | ```shell 84 | $ wasm-to-oci push ./target/wasm32-unknown-unknown/release/fileserver_s.wasm webassembly.azurecr.io/fileserver-wasmcloud:v0.3.0 85 | ``` 86 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fileserver-wasmcloud 5 | labels: 6 | app: fileserver-wasmcloud 7 | spec: 8 | containers: 9 | - image: webassembly.azurecr.io/fileserver-wasmcloud:v0.3.0 10 | imagePullPolicy: Always 11 | name: fileserver-wascc 12 | ports: 13 | - containerPort: 8080 14 | hostPort: 8080 15 | volumeMounts: 16 | - name: storage 17 | mountPath: /tmp # this is ignored by wasmCloud, but is still necessary from the k8s API 18 | volumes: 19 | - name: storage 20 | hostPath: 21 | path: /tmp 22 | type: Directory 23 | nodeSelector: 24 | kubernetes.io/arch: wasm32-wasmcloud 25 | tolerations: 26 | - key: "node.kubernetes.io/network-unavailable" 27 | operator: "Exists" 28 | effect: "NoSchedule" 29 | - key: "kubernetes.io/arch" 30 | operator: "Equal" 31 | value: "wasm32-wasmcloud" 32 | effect: "NoExecute" 33 | - key: "kubernetes.io/arch" 34 | operator: "Equal" 35 | value: "wasm32-wasmcloud" 36 | effect: "NoSchedule" 37 | -------------------------------------------------------------------------------- /demos/wasmcloud/fileserver/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wapc_guest::prelude::*; 2 | use wasmcloud_actor_blobstore as blobstore; 3 | use wasmcloud_actor_http_server as http; 4 | 5 | #[wasmcloud_actor_core::init] 6 | pub fn init() { 7 | http::Handlers::register_handle_request(fetch); 8 | } 9 | 10 | fn fetch(r: http::Request) -> HandlerResult { 11 | // k8s volumes are mounted into the wasmCloud runtime using the same volume mount name 12 | let store = blobstore::host("storage"); 13 | let mut path = String::from(r.path); 14 | 15 | // strip the leading slash from the path 16 | path = path.trim_start_matches('/').to_string(); 17 | 18 | match r.method.as_str() { 19 | "GET" => { 20 | let blob = store.get_object_info(path.as_str().to_owned(), String::default())?; 21 | if blob.id == "none" { 22 | return Ok(http::Response::not_found()); 23 | } 24 | Ok(http::Response::json(blob, 200, "OK")) 25 | } 26 | "POST" => { 27 | let mut chunk = blobstore::FileChunk { 28 | id: path, 29 | container: blobstore::Container::new(""), 30 | sequence_no: 0, 31 | total_bytes: r.body.len() as u64, 32 | chunk_size: r.body.len() as u64, 33 | context: None, 34 | chunk_bytes: Vec::with_capacity(0), 35 | }; 36 | // TODO: check if this is the start of an upload or another chunk. Right now we accept the request as the only chunk. 37 | store.start_upload(chunk.clone())?; 38 | chunk.chunk_bytes = r.body; 39 | store.upload_chunk(chunk)?; 40 | Ok(http::Response::ok()) 41 | } 42 | "DELETE" => { 43 | store.remove_object(path.as_str().to_owned(), String::default())?; 44 | Ok(http::Response::ok()) 45 | } 46 | _ => Ok(http::Response::bad_request()), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /demos/wasmcloud/greet/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | -------------------------------------------------------------------------------- /demos/wasmcloud/greet/.gitignore: -------------------------------------------------------------------------------- 1 | .keys 2 | -------------------------------------------------------------------------------- /demos/wasmcloud/greet/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 8 | 9 | [[package]] 10 | name = "byteorder" 11 | version = "1.4.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 20 | 21 | [[package]] 22 | name = "greet" 23 | version = "0.6.0" 24 | dependencies = [ 25 | "log", 26 | "wapc-guest", 27 | "wasmcloud-actor-core", 28 | "wasmcloud-actor-http-server", 29 | "wasmcloud-actor-logging", 30 | ] 31 | 32 | [[package]] 33 | name = "itoa" 34 | version = "0.4.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 37 | 38 | [[package]] 39 | name = "lazy_static" 40 | version = "1.4.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 43 | 44 | [[package]] 45 | name = "log" 46 | version = "0.4.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 49 | dependencies = [ 50 | "cfg-if", 51 | "serde", 52 | ] 53 | 54 | [[package]] 55 | name = "num-traits" 56 | version = "0.2.11" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 59 | dependencies = [ 60 | "autocfg", 61 | ] 62 | 63 | [[package]] 64 | name = "proc-macro2" 65 | version = "1.0.24" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 68 | dependencies = [ 69 | "unicode-xid", 70 | ] 71 | 72 | [[package]] 73 | name = "quote" 74 | version = "1.0.7" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 77 | dependencies = [ 78 | "proc-macro2", 79 | ] 80 | 81 | [[package]] 82 | name = "rmp" 83 | version = "0.8.9" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" 86 | dependencies = [ 87 | "byteorder", 88 | "num-traits", 89 | ] 90 | 91 | [[package]] 92 | name = "rmp-serde" 93 | version = "0.15.4" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "839395ef53057db96b84c9238ab29e1a13f2e5c8ec9f66bef853ab4197303924" 96 | dependencies = [ 97 | "byteorder", 98 | "rmp", 99 | "serde", 100 | ] 101 | 102 | [[package]] 103 | name = "ryu" 104 | version = "1.0.5" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 107 | 108 | [[package]] 109 | name = "serde" 110 | version = "1.0.125" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 113 | dependencies = [ 114 | "serde_derive", 115 | ] 116 | 117 | [[package]] 118 | name = "serde_bytes" 119 | version = "0.11.5" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 122 | dependencies = [ 123 | "serde", 124 | ] 125 | 126 | [[package]] 127 | name = "serde_derive" 128 | version = "1.0.125" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 131 | dependencies = [ 132 | "proc-macro2", 133 | "quote", 134 | "syn", 135 | ] 136 | 137 | [[package]] 138 | name = "serde_json" 139 | version = "1.0.64" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 142 | dependencies = [ 143 | "itoa", 144 | "ryu", 145 | "serde", 146 | ] 147 | 148 | [[package]] 149 | name = "syn" 150 | version = "1.0.67" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" 153 | dependencies = [ 154 | "proc-macro2", 155 | "quote", 156 | "unicode-xid", 157 | ] 158 | 159 | [[package]] 160 | name = "unicode-xid" 161 | version = "0.2.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 164 | 165 | [[package]] 166 | name = "wapc-guest" 167 | version = "0.4.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "47cbd9d778b9718eda797278936f93f25ce81064fe26f0bb6a710cd51315f00b" 170 | dependencies = [ 171 | "lazy_static", 172 | ] 173 | 174 | [[package]] 175 | name = "wasmcloud-actor-core" 176 | version = "0.2.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "3db490c15579b0c070db4cd548e4b0ebf344d5ced172d092971f07791b67b161" 179 | dependencies = [ 180 | "lazy_static", 181 | "log", 182 | "rmp-serde", 183 | "serde", 184 | "serde_bytes", 185 | "serde_json", 186 | "wapc-guest", 187 | "wasmcloud-actor-core-derive", 188 | ] 189 | 190 | [[package]] 191 | name = "wasmcloud-actor-core-derive" 192 | version = "0.1.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "48be47dd4eb5339812ef5cf60e174ad4ae6f5b4bc188dbed09eb668d7046b08f" 195 | dependencies = [ 196 | "proc-macro2", 197 | "quote", 198 | "syn", 199 | ] 200 | 201 | [[package]] 202 | name = "wasmcloud-actor-http-server" 203 | version = "0.1.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "227c7455dc3912ea692f6d66fca07147cae7a721dae15486caea68fbbdf40454" 206 | dependencies = [ 207 | "lazy_static", 208 | "log", 209 | "rmp-serde", 210 | "serde", 211 | "serde_bytes", 212 | "serde_derive", 213 | "serde_json", 214 | "wapc-guest", 215 | ] 216 | 217 | [[package]] 218 | name = "wasmcloud-actor-logging" 219 | version = "0.1.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "e25b41da6aa9d05952ec35998163fb6e65232c99c5ed4827c778fdbf801993c5" 222 | dependencies = [ 223 | "lazy_static", 224 | "log", 225 | "rmp-serde", 226 | "serde", 227 | "serde_bytes", 228 | "wapc-guest", 229 | ] 230 | -------------------------------------------------------------------------------- /demos/wasmcloud/greet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "greet" 3 | version = "0.6.0" 4 | authors = ["Matthew Fisher HandlerResult { 14 | info!("info something"); 15 | warn!("warn something"); 16 | error!("error something"); 17 | trace!("trace something"); 18 | debug!("debug something"); 19 | Ok(http::Response { 20 | status_code: 200, 21 | status: "OK".to_owned(), 22 | header: HashMap::new(), 23 | body: b"Hello, world!\n".to_vec(), 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/README.md: -------------------------------------------------------------------------------- 1 | # Hello World AssemblyScript for wasmCloud 2 | 3 | A simple hello world example in AssemblyScript that will print "Hello World" as 4 | an HTTP response. 5 | 6 | It is meant to be a simple demo for the wasmcloud-provider with Krustlet. 7 | 8 | ## Running the example 9 | 10 | This example has already been pre-built, so you only need to install it into 11 | your Kubernetes cluster. 12 | 13 | Create the pod and configmap with `kubectl`: 14 | 15 | ```shell 16 | $ kubectl apply -f k8s.yaml 17 | ``` 18 | 19 | Once the pod is running, you can use `curl` to reach the pod: 20 | 21 | ```shell 22 | $ curl http://localhost:8080 23 | ``` 24 | 25 | ## Building from Source 26 | 27 | If you want to compile the demo and inspect it, you'll need to do the following. 28 | 29 | ### Prerequisites 30 | 31 | You'll need `npm` installed in order to install and build the dependencies. 32 | 33 | You will also need to install [`wash`](https://github.com/wasmCloud/wash#installing-wash). 34 | This tool is used for signing and managing your wasmCloud compatible modules. 35 | 36 | If you are interested in starting your own AssemblyScript project, visit the 37 | AssemblyScript 38 | [getting started guide](https://docs.assemblyscript.org/quick-start). 39 | 40 | ### Compiling 41 | 42 | Run: 43 | 44 | ```shell 45 | $ npm install 46 | $ npm run codegen 47 | $ npm run asbuild 48 | ``` 49 | 50 | ### Signing 51 | 52 | Before pushing the actor module, you will need to sign it and grant it a few capabilities. 53 | 54 | ```shell 55 | $ wash claims sign --http_server --logging --blob_store ./build/optimized.wasm --name hello-world-wasmcloud-assemblyscript 56 | No keypair found in "/Users/foobar/.wash/keys/optimized_module.nk". 57 | We will generate one for you and place it there. 58 | If you'd like to use alternative keys, you can supply them as a flag. 59 | 60 | Successfully signed ./build/optimized_s.wasm with capabilities: wasmcloud:httpserver,wasmcloud:blobstore,wasmcloud:logging 61 | ``` 62 | 63 | ### Pushing 64 | 65 | Once signed, you can push it to an OCI registry. Please note that you'll need to be signed into that 66 | registry in order to push: 67 | 68 | ```shell 69 | $ wasm-to-oci push ./build/optimized_s.wasm webassembly.azurecr.io/hello-world-wasmcloud-assemblyscript:v0.2.0 70 | ``` 71 | 72 | 73 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/assembly/.gitignore: -------------------------------------------------------------------------------- 1 | module.ts 2 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/assembly/index.ts: -------------------------------------------------------------------------------- 1 | import { handleCall, handleAbort } from "@wapc/as-guest"; 2 | import { Request, Response, ResponseBuilder, Handlers as HTTPHandlers } from "@wasmcloud/actor-http-server"; 3 | import { HealthCheckResponse, HealthCheckRequest, Handlers as CoreHandlers, HealthCheckResponseBuilder } from "@wasmcloud/actor-core"; 4 | 5 | export function wapc_init(): void { 6 | CoreHandlers.registerHealthRequest(HealthCheck); 7 | HTTPHandlers.registerHandleRequest(HandleRequest); 8 | } 9 | 10 | function HealthCheck(request: HealthCheckRequest): HealthCheckResponse { 11 | return new HealthCheckResponseBuilder().withHealthy(true).withMessage("AssemblyScript Hello World Healthy").build(); 12 | } 13 | 14 | function HandleRequest(request: Request): Response { 15 | const payload = String.UTF8.encode("Hello world!"); 16 | 17 | return new ResponseBuilder() 18 | .withStatusCode(200) 19 | .withStatus("OK") 20 | .withBody(payload) 21 | .build(); 22 | } 23 | 24 | export function __guest_call(operation_size: usize, payload_size: usize): bool { 25 | return handleCall(operation_size, payload_size); 26 | } 27 | 28 | // Abort function 29 | function abort( 30 | message: string | null, 31 | fileName: string | null, 32 | lineNumber: u32, 33 | columnNumber: u32 34 | ): void { 35 | handleAbort(message, fileName, lineNumber, columnNumber); 36 | } 37 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "declaration": true, 6 | }, 7 | "extends": "../node_modules/assemblyscript/std/assembly.json", 8 | "include": [ 9 | "./**/*.ts" 10 | ] 11 | } -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: hello-world-wasmcloud-assemblyscript 5 | spec: 6 | containers: 7 | - name: hello-world-wasmcloud-assemblyscript 8 | image: webassembly.azurecr.io/hello-world-wasmcloud-assemblyscript:v0.2.0 9 | ports: 10 | - containerPort: 8080 11 | hostPort: 8080 12 | nodeSelector: 13 | kubernetes.io/arch: wasm32-wasmcloud 14 | tolerations: 15 | - key: "node.kubernetes.io/network-unavailable" 16 | operator: "Exists" 17 | effect: "NoSchedule" 18 | - key: "kubernetes.io/arch" 19 | operator: "Equal" 20 | value: "wasm32-wasmcloud" 21 | effect: "NoExecute" 22 | - key: "kubernetes.io/arch" 23 | operator: "Equal" 24 | value: "wasm32-wasmcloud" 25 | effect: "NoSchedule" 26 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-assemblyscript", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "@wapc/as-guest": "^v0.2.1", 9 | "@wapc/as-msgpack": "^0.1.11", 10 | "@wasmcloud/actor-core": "^0.1.1", 11 | "@wasmcloud/actor-http-server": "^0.1.2" 12 | }, 13 | "devDependencies": { 14 | "assemblyscript": "^0.17.1", 15 | "graphql-schema-linter": "^0.2.0", 16 | "prettier": "^2.0.2" 17 | } 18 | }, 19 | "node_modules/@wapc/as-guest": { 20 | "version": "0.2.1", 21 | "resolved": "https://registry.npmjs.org/@wapc/as-guest/-/as-guest-0.2.1.tgz", 22 | "integrity": "sha512-67qrVC5MflfdTuXCG4P5mysifGgIt+AshBX5ZC1uRWYsBpsxQzP1WP9w/4vWJtTyTXUomvL9unmKJq+dLwqfNw==" 23 | }, 24 | "node_modules/@wapc/as-msgpack": { 25 | "version": "0.1.12", 26 | "resolved": "https://registry.npmjs.org/@wapc/as-msgpack/-/as-msgpack-0.1.12.tgz", 27 | "integrity": "sha512-W8n18/NSa+HhC/NWZNz6rULBNaG2i0XsjCWxZhiC1EWRYpWqv18rBtTPQD/qOACUL1coLSGGKR1z8Rule15a8Q==" 28 | }, 29 | "node_modules/@wasmcloud/actor-core": { 30 | "version": "0.1.1", 31 | "resolved": "https://registry.npmjs.org/@wasmcloud/actor-core/-/actor-core-0.1.1.tgz", 32 | "integrity": "sha512-B45w3PuVwdDDewKKXzk9Q4ls/P3OyRcwtbxJC91Okkwglj2AEdXUcizMYup/9pBJKx5tLvrZQTyM5Gq8UkS3cw==", 33 | "dependencies": { 34 | "@wapc/as-guest": "^v0.2.1", 35 | "@wapc/as-msgpack": "^0.1.11" 36 | } 37 | }, 38 | "node_modules/@wasmcloud/actor-http-server": { 39 | "version": "0.1.3", 40 | "resolved": "https://registry.npmjs.org/@wasmcloud/actor-http-server/-/actor-http-server-0.1.3.tgz", 41 | "integrity": "sha512-0zH2FeeMvSgfW8Xcv9QqJ4y2yk1amBk/4fnMBgiI8nOdRmcQCvJMZo1PQiFYRAke5PWStcc2PNbYMwT+ob14ow==", 42 | "dependencies": { 43 | "@wapc/as-guest": "^v0.2.1", 44 | "@wapc/as-msgpack": "^0.1.11" 45 | } 46 | }, 47 | "node_modules/ansi-regex": { 48 | "version": "2.1.1", 49 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 50 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 51 | "dev": true, 52 | "engines": { 53 | "node": ">=0.10.0" 54 | } 55 | }, 56 | "node_modules/ansi-styles": { 57 | "version": "3.2.1", 58 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 59 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 60 | "dev": true, 61 | "dependencies": { 62 | "color-convert": "^1.9.0" 63 | }, 64 | "engines": { 65 | "node": ">=4" 66 | } 67 | }, 68 | "node_modules/argparse": { 69 | "version": "1.0.10", 70 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 71 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 72 | "dev": true, 73 | "dependencies": { 74 | "sprintf-js": "~1.0.2" 75 | } 76 | }, 77 | "node_modules/assemblyscript": { 78 | "version": "0.17.14", 79 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.17.14.tgz", 80 | "integrity": "sha512-TLuwNvZAIH26wu2puKpAJokzLp10kJkVXxbgDjFFmbW9VF/qg7rkmi0hjsiu41bjoH1UaVgY4vYvbbUeOHtKyg==", 81 | "dev": true, 82 | "dependencies": { 83 | "binaryen": "98.0.0-nightly.20201109", 84 | "long": "^4.0.0" 85 | }, 86 | "bin": { 87 | "asc": "bin/asc", 88 | "asinit": "bin/asinit" 89 | }, 90 | "funding": { 91 | "type": "opencollective", 92 | "url": "https://opencollective.com/assemblyscript" 93 | } 94 | }, 95 | "node_modules/balanced-match": { 96 | "version": "1.0.0", 97 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 98 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 99 | "dev": true 100 | }, 101 | "node_modules/binaryen": { 102 | "version": "98.0.0-nightly.20201109", 103 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-98.0.0-nightly.20201109.tgz", 104 | "integrity": "sha512-iRarAqdH5lMWlMBzrDuJgLYJR2g4QXk93iYE2zpr6gEZkb/jCgDpPUXdhuN11Ge1zZ/6By4DwA1mmifcx7FWaw==", 105 | "dev": true, 106 | "bin": { 107 | "wasm-opt": "bin/wasm-opt" 108 | } 109 | }, 110 | "node_modules/brace-expansion": { 111 | "version": "1.1.11", 112 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 113 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 114 | "dev": true, 115 | "dependencies": { 116 | "balanced-match": "^1.0.0", 117 | "concat-map": "0.0.1" 118 | } 119 | }, 120 | "node_modules/caller-callsite": { 121 | "version": "2.0.0", 122 | "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", 123 | "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", 124 | "dev": true, 125 | "dependencies": { 126 | "callsites": "^2.0.0" 127 | }, 128 | "engines": { 129 | "node": ">=4" 130 | } 131 | }, 132 | "node_modules/caller-path": { 133 | "version": "2.0.0", 134 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", 135 | "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", 136 | "dev": true, 137 | "dependencies": { 138 | "caller-callsite": "^2.0.0" 139 | }, 140 | "engines": { 141 | "node": ">=4" 142 | } 143 | }, 144 | "node_modules/callsites": { 145 | "version": "2.0.0", 146 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", 147 | "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", 148 | "dev": true, 149 | "engines": { 150 | "node": ">=4" 151 | } 152 | }, 153 | "node_modules/chalk": { 154 | "version": "2.4.2", 155 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 156 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 157 | "dev": true, 158 | "dependencies": { 159 | "ansi-styles": "^3.2.1", 160 | "escape-string-regexp": "^1.0.5", 161 | "supports-color": "^5.3.0" 162 | }, 163 | "engines": { 164 | "node": ">=4" 165 | } 166 | }, 167 | "node_modules/clone": { 168 | "version": "1.0.4", 169 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 170 | "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", 171 | "dev": true, 172 | "engines": { 173 | "node": ">=0.8" 174 | } 175 | }, 176 | "node_modules/color-convert": { 177 | "version": "1.9.3", 178 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 179 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 180 | "dev": true, 181 | "dependencies": { 182 | "color-name": "1.1.3" 183 | } 184 | }, 185 | "node_modules/color-name": { 186 | "version": "1.1.3", 187 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 188 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 189 | "dev": true 190 | }, 191 | "node_modules/columnify": { 192 | "version": "1.5.4", 193 | "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", 194 | "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", 195 | "dev": true, 196 | "dependencies": { 197 | "strip-ansi": "^3.0.0", 198 | "wcwidth": "^1.0.0" 199 | } 200 | }, 201 | "node_modules/commander": { 202 | "version": "3.0.2", 203 | "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", 204 | "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", 205 | "dev": true 206 | }, 207 | "node_modules/concat-map": { 208 | "version": "0.0.1", 209 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 210 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 211 | "dev": true 212 | }, 213 | "node_modules/cosmiconfig": { 214 | "version": "5.2.1", 215 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", 216 | "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", 217 | "dev": true, 218 | "dependencies": { 219 | "import-fresh": "^2.0.0", 220 | "is-directory": "^0.3.1", 221 | "js-yaml": "^3.13.1", 222 | "parse-json": "^4.0.0" 223 | }, 224 | "engines": { 225 | "node": ">=4" 226 | } 227 | }, 228 | "node_modules/defaults": { 229 | "version": "1.0.3", 230 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", 231 | "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", 232 | "dev": true, 233 | "dependencies": { 234 | "clone": "^1.0.2" 235 | } 236 | }, 237 | "node_modules/error-ex": { 238 | "version": "1.3.2", 239 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 240 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 241 | "dev": true, 242 | "dependencies": { 243 | "is-arrayish": "^0.2.1" 244 | } 245 | }, 246 | "node_modules/escape-string-regexp": { 247 | "version": "1.0.5", 248 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 249 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 250 | "dev": true, 251 | "engines": { 252 | "node": ">=0.8.0" 253 | } 254 | }, 255 | "node_modules/esprima": { 256 | "version": "4.0.1", 257 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 258 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 259 | "dev": true, 260 | "bin": { 261 | "esparse": "bin/esparse.js", 262 | "esvalidate": "bin/esvalidate.js" 263 | }, 264 | "engines": { 265 | "node": ">=4" 266 | } 267 | }, 268 | "node_modules/fs.realpath": { 269 | "version": "1.0.0", 270 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 271 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 272 | "dev": true 273 | }, 274 | "node_modules/glob": { 275 | "version": "7.1.6", 276 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 277 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 278 | "dev": true, 279 | "dependencies": { 280 | "fs.realpath": "^1.0.0", 281 | "inflight": "^1.0.4", 282 | "inherits": "2", 283 | "minimatch": "^3.0.4", 284 | "once": "^1.3.0", 285 | "path-is-absolute": "^1.0.0" 286 | }, 287 | "engines": { 288 | "node": "*" 289 | } 290 | }, 291 | "node_modules/graphql": { 292 | "version": "14.6.0", 293 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.6.0.tgz", 294 | "integrity": "sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg==", 295 | "dev": true, 296 | "dependencies": { 297 | "iterall": "^1.2.2" 298 | }, 299 | "engines": { 300 | "node": ">= 6.x" 301 | } 302 | }, 303 | "node_modules/graphql-schema-linter": { 304 | "version": "0.2.4", 305 | "resolved": "https://registry.npmjs.org/graphql-schema-linter/-/graphql-schema-linter-0.2.4.tgz", 306 | "integrity": "sha512-163dyjcqBavDcJQkujwChuNws3qT4eiYz9YWhwPcWzLWXXHLLKHJe/+Mq5lfbUfHWc7+Fv+urEWda1ikgCdozQ==", 307 | "dev": true, 308 | "dependencies": { 309 | "chalk": "^2.0.1", 310 | "columnify": "^1.5.4", 311 | "commander": "^3.0.0", 312 | "cosmiconfig": "^5.2.1", 313 | "glob": "^7.1.2", 314 | "graphql": "^14.0.0" 315 | }, 316 | "bin": { 317 | "graphql-schema-linter": "lib/cli.js" 318 | } 319 | }, 320 | "node_modules/has-flag": { 321 | "version": "3.0.0", 322 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 323 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 324 | "dev": true, 325 | "engines": { 326 | "node": ">=4" 327 | } 328 | }, 329 | "node_modules/import-fresh": { 330 | "version": "2.0.0", 331 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", 332 | "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", 333 | "dev": true, 334 | "dependencies": { 335 | "caller-path": "^2.0.0", 336 | "resolve-from": "^3.0.0" 337 | }, 338 | "engines": { 339 | "node": ">=4" 340 | } 341 | }, 342 | "node_modules/inflight": { 343 | "version": "1.0.6", 344 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 345 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 346 | "dev": true, 347 | "dependencies": { 348 | "once": "^1.3.0", 349 | "wrappy": "1" 350 | } 351 | }, 352 | "node_modules/inherits": { 353 | "version": "2.0.4", 354 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 355 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 356 | "dev": true 357 | }, 358 | "node_modules/is-arrayish": { 359 | "version": "0.2.1", 360 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 361 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 362 | "dev": true 363 | }, 364 | "node_modules/is-directory": { 365 | "version": "0.3.1", 366 | "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", 367 | "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", 368 | "dev": true, 369 | "engines": { 370 | "node": ">=0.10.0" 371 | } 372 | }, 373 | "node_modules/iterall": { 374 | "version": "1.3.0", 375 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", 376 | "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", 377 | "dev": true 378 | }, 379 | "node_modules/js-yaml": { 380 | "version": "3.13.1", 381 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 382 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 383 | "dev": true, 384 | "dependencies": { 385 | "argparse": "^1.0.7", 386 | "esprima": "^4.0.0" 387 | }, 388 | "bin": { 389 | "js-yaml": "bin/js-yaml.js" 390 | } 391 | }, 392 | "node_modules/json-parse-better-errors": { 393 | "version": "1.0.2", 394 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 395 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 396 | "dev": true 397 | }, 398 | "node_modules/long": { 399 | "version": "4.0.0", 400 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 401 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 402 | "dev": true 403 | }, 404 | "node_modules/minimatch": { 405 | "version": "3.0.4", 406 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 407 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 408 | "dev": true, 409 | "dependencies": { 410 | "brace-expansion": "^1.1.7" 411 | }, 412 | "engines": { 413 | "node": "*" 414 | } 415 | }, 416 | "node_modules/once": { 417 | "version": "1.4.0", 418 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 419 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 420 | "dev": true, 421 | "dependencies": { 422 | "wrappy": "1" 423 | } 424 | }, 425 | "node_modules/parse-json": { 426 | "version": "4.0.0", 427 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 428 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 429 | "dev": true, 430 | "dependencies": { 431 | "error-ex": "^1.3.1", 432 | "json-parse-better-errors": "^1.0.1" 433 | }, 434 | "engines": { 435 | "node": ">=4" 436 | } 437 | }, 438 | "node_modules/path-is-absolute": { 439 | "version": "1.0.1", 440 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 441 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 442 | "dev": true, 443 | "engines": { 444 | "node": ">=0.10.0" 445 | } 446 | }, 447 | "node_modules/prettier": { 448 | "version": "2.0.2", 449 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", 450 | "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", 451 | "dev": true, 452 | "bin": { 453 | "prettier": "bin-prettier.js" 454 | }, 455 | "engines": { 456 | "node": ">=10.13.0" 457 | } 458 | }, 459 | "node_modules/resolve-from": { 460 | "version": "3.0.0", 461 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", 462 | "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", 463 | "dev": true, 464 | "engines": { 465 | "node": ">=4" 466 | } 467 | }, 468 | "node_modules/sprintf-js": { 469 | "version": "1.0.3", 470 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 471 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 472 | "dev": true 473 | }, 474 | "node_modules/strip-ansi": { 475 | "version": "3.0.1", 476 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 477 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 478 | "dev": true, 479 | "dependencies": { 480 | "ansi-regex": "^2.0.0" 481 | }, 482 | "engines": { 483 | "node": ">=0.10.0" 484 | } 485 | }, 486 | "node_modules/supports-color": { 487 | "version": "5.5.0", 488 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 489 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 490 | "dev": true, 491 | "dependencies": { 492 | "has-flag": "^3.0.0" 493 | }, 494 | "engines": { 495 | "node": ">=4" 496 | } 497 | }, 498 | "node_modules/wcwidth": { 499 | "version": "1.0.1", 500 | "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", 501 | "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", 502 | "dev": true, 503 | "dependencies": { 504 | "defaults": "^1.0.3" 505 | } 506 | }, 507 | "node_modules/wrappy": { 508 | "version": "1.0.2", 509 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 510 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 511 | "dev": true 512 | } 513 | }, 514 | "dependencies": { 515 | "@wapc/as-guest": { 516 | "version": "0.2.1", 517 | "resolved": "https://registry.npmjs.org/@wapc/as-guest/-/as-guest-0.2.1.tgz", 518 | "integrity": "sha512-67qrVC5MflfdTuXCG4P5mysifGgIt+AshBX5ZC1uRWYsBpsxQzP1WP9w/4vWJtTyTXUomvL9unmKJq+dLwqfNw==" 519 | }, 520 | "@wapc/as-msgpack": { 521 | "version": "0.1.12", 522 | "resolved": "https://registry.npmjs.org/@wapc/as-msgpack/-/as-msgpack-0.1.12.tgz", 523 | "integrity": "sha512-W8n18/NSa+HhC/NWZNz6rULBNaG2i0XsjCWxZhiC1EWRYpWqv18rBtTPQD/qOACUL1coLSGGKR1z8Rule15a8Q==" 524 | }, 525 | "@wasmcloud/actor-core": { 526 | "version": "0.1.1", 527 | "resolved": "https://registry.npmjs.org/@wasmcloud/actor-core/-/actor-core-0.1.1.tgz", 528 | "integrity": "sha512-B45w3PuVwdDDewKKXzk9Q4ls/P3OyRcwtbxJC91Okkwglj2AEdXUcizMYup/9pBJKx5tLvrZQTyM5Gq8UkS3cw==", 529 | "requires": { 530 | "@wapc/as-guest": "^v0.2.1", 531 | "@wapc/as-msgpack": "^0.1.11" 532 | } 533 | }, 534 | "@wasmcloud/actor-http-server": { 535 | "version": "0.1.3", 536 | "resolved": "https://registry.npmjs.org/@wasmcloud/actor-http-server/-/actor-http-server-0.1.3.tgz", 537 | "integrity": "sha512-0zH2FeeMvSgfW8Xcv9QqJ4y2yk1amBk/4fnMBgiI8nOdRmcQCvJMZo1PQiFYRAke5PWStcc2PNbYMwT+ob14ow==", 538 | "requires": { 539 | "@wapc/as-guest": "^v0.2.1", 540 | "@wapc/as-msgpack": "^0.1.11" 541 | } 542 | }, 543 | "ansi-regex": { 544 | "version": "2.1.1", 545 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 546 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 547 | "dev": true 548 | }, 549 | "ansi-styles": { 550 | "version": "3.2.1", 551 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 552 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 553 | "dev": true, 554 | "requires": { 555 | "color-convert": "^1.9.0" 556 | } 557 | }, 558 | "argparse": { 559 | "version": "1.0.10", 560 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 561 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 562 | "dev": true, 563 | "requires": { 564 | "sprintf-js": "~1.0.2" 565 | } 566 | }, 567 | "assemblyscript": { 568 | "version": "0.17.14", 569 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.17.14.tgz", 570 | "integrity": "sha512-TLuwNvZAIH26wu2puKpAJokzLp10kJkVXxbgDjFFmbW9VF/qg7rkmi0hjsiu41bjoH1UaVgY4vYvbbUeOHtKyg==", 571 | "dev": true, 572 | "requires": { 573 | "binaryen": "98.0.0-nightly.20201109", 574 | "long": "^4.0.0" 575 | } 576 | }, 577 | "balanced-match": { 578 | "version": "1.0.0", 579 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 580 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 581 | "dev": true 582 | }, 583 | "binaryen": { 584 | "version": "98.0.0-nightly.20201109", 585 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-98.0.0-nightly.20201109.tgz", 586 | "integrity": "sha512-iRarAqdH5lMWlMBzrDuJgLYJR2g4QXk93iYE2zpr6gEZkb/jCgDpPUXdhuN11Ge1zZ/6By4DwA1mmifcx7FWaw==", 587 | "dev": true 588 | }, 589 | "brace-expansion": { 590 | "version": "1.1.11", 591 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 592 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 593 | "dev": true, 594 | "requires": { 595 | "balanced-match": "^1.0.0", 596 | "concat-map": "0.0.1" 597 | } 598 | }, 599 | "caller-callsite": { 600 | "version": "2.0.0", 601 | "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", 602 | "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", 603 | "dev": true, 604 | "requires": { 605 | "callsites": "^2.0.0" 606 | } 607 | }, 608 | "caller-path": { 609 | "version": "2.0.0", 610 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", 611 | "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", 612 | "dev": true, 613 | "requires": { 614 | "caller-callsite": "^2.0.0" 615 | } 616 | }, 617 | "callsites": { 618 | "version": "2.0.0", 619 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", 620 | "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", 621 | "dev": true 622 | }, 623 | "chalk": { 624 | "version": "2.4.2", 625 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 626 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 627 | "dev": true, 628 | "requires": { 629 | "ansi-styles": "^3.2.1", 630 | "escape-string-regexp": "^1.0.5", 631 | "supports-color": "^5.3.0" 632 | } 633 | }, 634 | "clone": { 635 | "version": "1.0.4", 636 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 637 | "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", 638 | "dev": true 639 | }, 640 | "color-convert": { 641 | "version": "1.9.3", 642 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 643 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 644 | "dev": true, 645 | "requires": { 646 | "color-name": "1.1.3" 647 | } 648 | }, 649 | "color-name": { 650 | "version": "1.1.3", 651 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 652 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 653 | "dev": true 654 | }, 655 | "columnify": { 656 | "version": "1.5.4", 657 | "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", 658 | "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", 659 | "dev": true, 660 | "requires": { 661 | "strip-ansi": "^3.0.0", 662 | "wcwidth": "^1.0.0" 663 | } 664 | }, 665 | "commander": { 666 | "version": "3.0.2", 667 | "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", 668 | "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", 669 | "dev": true 670 | }, 671 | "concat-map": { 672 | "version": "0.0.1", 673 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 674 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 675 | "dev": true 676 | }, 677 | "cosmiconfig": { 678 | "version": "5.2.1", 679 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", 680 | "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", 681 | "dev": true, 682 | "requires": { 683 | "import-fresh": "^2.0.0", 684 | "is-directory": "^0.3.1", 685 | "js-yaml": "^3.13.1", 686 | "parse-json": "^4.0.0" 687 | } 688 | }, 689 | "defaults": { 690 | "version": "1.0.3", 691 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", 692 | "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", 693 | "dev": true, 694 | "requires": { 695 | "clone": "^1.0.2" 696 | } 697 | }, 698 | "error-ex": { 699 | "version": "1.3.2", 700 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 701 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 702 | "dev": true, 703 | "requires": { 704 | "is-arrayish": "^0.2.1" 705 | } 706 | }, 707 | "escape-string-regexp": { 708 | "version": "1.0.5", 709 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 710 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 711 | "dev": true 712 | }, 713 | "esprima": { 714 | "version": "4.0.1", 715 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 716 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 717 | "dev": true 718 | }, 719 | "fs.realpath": { 720 | "version": "1.0.0", 721 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 722 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 723 | "dev": true 724 | }, 725 | "glob": { 726 | "version": "7.1.6", 727 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 728 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 729 | "dev": true, 730 | "requires": { 731 | "fs.realpath": "^1.0.0", 732 | "inflight": "^1.0.4", 733 | "inherits": "2", 734 | "minimatch": "^3.0.4", 735 | "once": "^1.3.0", 736 | "path-is-absolute": "^1.0.0" 737 | } 738 | }, 739 | "graphql": { 740 | "version": "14.6.0", 741 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.6.0.tgz", 742 | "integrity": "sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg==", 743 | "dev": true, 744 | "requires": { 745 | "iterall": "^1.2.2" 746 | } 747 | }, 748 | "graphql-schema-linter": { 749 | "version": "0.2.4", 750 | "resolved": "https://registry.npmjs.org/graphql-schema-linter/-/graphql-schema-linter-0.2.4.tgz", 751 | "integrity": "sha512-163dyjcqBavDcJQkujwChuNws3qT4eiYz9YWhwPcWzLWXXHLLKHJe/+Mq5lfbUfHWc7+Fv+urEWda1ikgCdozQ==", 752 | "dev": true, 753 | "requires": { 754 | "chalk": "^2.0.1", 755 | "columnify": "^1.5.4", 756 | "commander": "^3.0.0", 757 | "cosmiconfig": "^5.2.1", 758 | "glob": "^7.1.2", 759 | "graphql": "^14.0.0" 760 | } 761 | }, 762 | "has-flag": { 763 | "version": "3.0.0", 764 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 765 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 766 | "dev": true 767 | }, 768 | "import-fresh": { 769 | "version": "2.0.0", 770 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", 771 | "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", 772 | "dev": true, 773 | "requires": { 774 | "caller-path": "^2.0.0", 775 | "resolve-from": "^3.0.0" 776 | } 777 | }, 778 | "inflight": { 779 | "version": "1.0.6", 780 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 781 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 782 | "dev": true, 783 | "requires": { 784 | "once": "^1.3.0", 785 | "wrappy": "1" 786 | } 787 | }, 788 | "inherits": { 789 | "version": "2.0.4", 790 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 791 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 792 | "dev": true 793 | }, 794 | "is-arrayish": { 795 | "version": "0.2.1", 796 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 797 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 798 | "dev": true 799 | }, 800 | "is-directory": { 801 | "version": "0.3.1", 802 | "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", 803 | "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", 804 | "dev": true 805 | }, 806 | "iterall": { 807 | "version": "1.3.0", 808 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", 809 | "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", 810 | "dev": true 811 | }, 812 | "js-yaml": { 813 | "version": "3.13.1", 814 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 815 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 816 | "dev": true, 817 | "requires": { 818 | "argparse": "^1.0.7", 819 | "esprima": "^4.0.0" 820 | } 821 | }, 822 | "json-parse-better-errors": { 823 | "version": "1.0.2", 824 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 825 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 826 | "dev": true 827 | }, 828 | "long": { 829 | "version": "4.0.0", 830 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 831 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 832 | "dev": true 833 | }, 834 | "minimatch": { 835 | "version": "3.0.4", 836 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 837 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 838 | "dev": true, 839 | "requires": { 840 | "brace-expansion": "^1.1.7" 841 | } 842 | }, 843 | "once": { 844 | "version": "1.4.0", 845 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 846 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 847 | "dev": true, 848 | "requires": { 849 | "wrappy": "1" 850 | } 851 | }, 852 | "parse-json": { 853 | "version": "4.0.0", 854 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 855 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 856 | "dev": true, 857 | "requires": { 858 | "error-ex": "^1.3.1", 859 | "json-parse-better-errors": "^1.0.1" 860 | } 861 | }, 862 | "path-is-absolute": { 863 | "version": "1.0.1", 864 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 865 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 866 | "dev": true 867 | }, 868 | "prettier": { 869 | "version": "2.0.2", 870 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", 871 | "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", 872 | "dev": true 873 | }, 874 | "resolve-from": { 875 | "version": "3.0.0", 876 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", 877 | "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", 878 | "dev": true 879 | }, 880 | "sprintf-js": { 881 | "version": "1.0.3", 882 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 883 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 884 | "dev": true 885 | }, 886 | "strip-ansi": { 887 | "version": "3.0.1", 888 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 889 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 890 | "dev": true, 891 | "requires": { 892 | "ansi-regex": "^2.0.0" 893 | } 894 | }, 895 | "supports-color": { 896 | "version": "5.5.0", 897 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 898 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 899 | "dev": true, 900 | "requires": { 901 | "has-flag": "^3.0.0" 902 | } 903 | }, 904 | "wcwidth": { 905 | "version": "1.0.1", 906 | "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", 907 | "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", 908 | "dev": true, 909 | "requires": { 910 | "defaults": "^1.0.3" 911 | } 912 | }, 913 | "wrappy": { 914 | "version": "1.0.2", 915 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 916 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 917 | "dev": true 918 | } 919 | } 920 | } 921 | -------------------------------------------------------------------------------- /demos/wasmcloud/hello-world-assemblyscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "echo \"Error: no test specified\" && exit 1", 4 | "build": "asc assembly/index.ts -b build/helloworld-as.wasm --use abort=assembly/index/abort --optimize", 5 | "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --use abort=assembly/index/abort --debug", 6 | "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --use abort=assembly/index/abort --optimize", 7 | "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized" 8 | }, 9 | "dependencies": { 10 | "@wapc/as-guest": "^v0.2.1", 11 | "@wapc/as-msgpack": "^0.1.11", 12 | "@wasmcloud/actor-core": "^0.1.1", 13 | "@wasmcloud/actor-http-server": "^0.1.2" 14 | }, 15 | "devDependencies": { 16 | "assemblyscript": "^0.17.1", 17 | "graphql-schema-linter": "^0.2.0", 18 | "prettier": "^2.0.2" 19 | } 20 | } -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/.gitignore: -------------------------------------------------------------------------------- 1 | .keys 2 | -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 8 | 9 | [[package]] 10 | name = "byteorder" 11 | version = "1.4.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 20 | 21 | [[package]] 22 | name = "itoa" 23 | version = "0.4.5" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 26 | 27 | [[package]] 28 | name = "lazy_static" 29 | version = "1.4.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 32 | 33 | [[package]] 34 | name = "log" 35 | version = "0.4.14" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 38 | dependencies = [ 39 | "cfg-if", 40 | "serde", 41 | ] 42 | 43 | [[package]] 44 | name = "num-traits" 45 | version = "0.2.11" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 48 | dependencies = [ 49 | "autocfg", 50 | ] 51 | 52 | [[package]] 53 | name = "proc-macro2" 54 | version = "1.0.24" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 57 | dependencies = [ 58 | "unicode-xid", 59 | ] 60 | 61 | [[package]] 62 | name = "quote" 63 | version = "1.0.3" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 66 | dependencies = [ 67 | "proc-macro2", 68 | ] 69 | 70 | [[package]] 71 | name = "rmp" 72 | version = "0.8.9" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" 75 | dependencies = [ 76 | "byteorder", 77 | "num-traits", 78 | ] 79 | 80 | [[package]] 81 | name = "rmp-serde" 82 | version = "0.15.4" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "839395ef53057db96b84c9238ab29e1a13f2e5c8ec9f66bef853ab4197303924" 85 | dependencies = [ 86 | "byteorder", 87 | "rmp", 88 | "serde", 89 | ] 90 | 91 | [[package]] 92 | name = "ryu" 93 | version = "1.0.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" 96 | 97 | [[package]] 98 | name = "serde" 99 | version = "1.0.125" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 102 | dependencies = [ 103 | "serde_derive", 104 | ] 105 | 106 | [[package]] 107 | name = "serde_bytes" 108 | version = "0.11.5" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 111 | dependencies = [ 112 | "serde", 113 | ] 114 | 115 | [[package]] 116 | name = "serde_derive" 117 | version = "1.0.125" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 120 | dependencies = [ 121 | "proc-macro2", 122 | "quote", 123 | "syn", 124 | ] 125 | 126 | [[package]] 127 | name = "serde_json" 128 | version = "1.0.64" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 131 | dependencies = [ 132 | "itoa", 133 | "ryu", 134 | "serde", 135 | ] 136 | 137 | [[package]] 138 | name = "syn" 139 | version = "1.0.67" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" 142 | dependencies = [ 143 | "proc-macro2", 144 | "quote", 145 | "unicode-xid", 146 | ] 147 | 148 | [[package]] 149 | name = "unicode-xid" 150 | version = "0.2.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 153 | 154 | [[package]] 155 | name = "uppercase" 156 | version = "0.4.0" 157 | dependencies = [ 158 | "log", 159 | "serde", 160 | "wapc-guest", 161 | "wasmcloud-actor-core", 162 | "wasmcloud-actor-http-server", 163 | ] 164 | 165 | [[package]] 166 | name = "wapc-guest" 167 | version = "0.4.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "47cbd9d778b9718eda797278936f93f25ce81064fe26f0bb6a710cd51315f00b" 170 | dependencies = [ 171 | "lazy_static", 172 | ] 173 | 174 | [[package]] 175 | name = "wasmcloud-actor-core" 176 | version = "0.2.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "3db490c15579b0c070db4cd548e4b0ebf344d5ced172d092971f07791b67b161" 179 | dependencies = [ 180 | "lazy_static", 181 | "log", 182 | "rmp-serde", 183 | "serde", 184 | "serde_bytes", 185 | "serde_json", 186 | "wapc-guest", 187 | "wasmcloud-actor-core-derive", 188 | ] 189 | 190 | [[package]] 191 | name = "wasmcloud-actor-core-derive" 192 | version = "0.1.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "48be47dd4eb5339812ef5cf60e174ad4ae6f5b4bc188dbed09eb668d7046b08f" 195 | dependencies = [ 196 | "proc-macro2", 197 | "quote", 198 | "syn", 199 | ] 200 | 201 | [[package]] 202 | name = "wasmcloud-actor-http-server" 203 | version = "0.1.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "227c7455dc3912ea692f6d66fca07147cae7a721dae15486caea68fbbdf40454" 206 | dependencies = [ 207 | "lazy_static", 208 | "log", 209 | "rmp-serde", 210 | "serde", 211 | "serde_bytes", 212 | "serde_derive", 213 | "serde_json", 214 | "wapc-guest", 215 | ] 216 | -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uppercase" 3 | version = "0.4.0" 4 | authors = ["Brian Ketelsen "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | log = '0.4.8' 12 | serde = { version = "1.0", features = ["derive"]} 13 | wasmcloud-actor-core = { version = "0.2", features = ["guest"] } 14 | wapc-guest = "0.4" 15 | wasmcloud-actor-http-server = { version = "0.1", features = ["guest"] } 16 | 17 | [profile.release] 18 | opt-level = "s" 19 | 20 | [workspace] 21 | -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/README.md: -------------------------------------------------------------------------------- 1 | # Uppercase 2 | 3 | An example that will respond with the uppercased version of the querystring 4 | sent in. 5 | 6 | It is meant to be a simple demo for the wasmcloud-provider with Krustlet. 7 | 8 | ## Video 9 | 10 | You can watch a video of the creation of this actor on 11 | [Youtube](https://www.youtube.com/watch?v=uy91W7OxHcQ). 12 | 13 | ## Running the example 14 | 15 | This example has already been pre-built, so you only need to install it into 16 | your Kubernetes cluster. 17 | 18 | Create the pod and configmap with `kubectl`: 19 | 20 | ```shell 21 | $ kubectl apply -f uppercase-wasmcloud.yaml 22 | ``` 23 | 24 | Once the pod is running, you can run a `curl` command and the service will 25 | return your query string uppercased: 26 | 27 | ```shell 28 | $ curl http://localhost:8080?hello=world 29 | {"original":"hello=world","uppercased":"HELLO=WORLD"} 30 | ``` 31 | 32 | ## Building the example 33 | 34 | To set up your development environment, you'll need the following tools: 35 | 36 | - cargo 37 | - wasm-to-oci 38 | - wash 39 | 40 | Instructions for [installing 41 | `cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html) and 42 | [`wasm-to-oci`](https://github.com/engineerd/wasm-to-oci) can be found in their 43 | respective project's documentation. Once those are installed, 44 | [`wash`](https://github.com/wasmCloud/wash#installing-wash) can be installed with 45 | 46 | ```shell 47 | $ cargo install wash-cli 48 | ``` 49 | 50 | Once complete, you'll need to build the binary: 51 | 52 | ```shell 53 | $ cargo build --release 54 | ``` 55 | 56 | After the binary is built, you'll need to sign the compiled Wasm binary so it can be trusted by 57 | wasmCloud: 58 | 59 | ```shell 60 | $ wash claims sign --http_server --logging --blob_store ./target/wasm32-unknown-unknown/release/uppercase.wasm --name uppercase 61 | No keypair found in "/Users/foobar/.wash/keys/uppercase_module.nk". 62 | We will generate one for you and place it there. 63 | If you'd like to use alternative keys, you can supply them as a flag. 64 | 65 | Successfully signed ./target/wasm32-unknown-unknown/release/uppercase_s.wasm with capabilities: wasmcloud:httpserver,wasmcloud:blobstore,wasmcloud:logging 66 | ``` 67 | 68 | Once signed, you can push it to an OCI registry. Please note that you'll need to be signed into that 69 | registry in order to push: 70 | 71 | ```shell 72 | $ wasm-to-oci push ./target/wasm32-unknown-unknown/release/uppercase_s.wasm webassembly.azurecr.io/uppercase-wasmcloud:v0.4.0 73 | ``` 74 | -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/src/lib.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | use serde::Serialize; 3 | use wapc_guest::prelude::*; 4 | use wasmcloud_actor_http_server as http; 5 | 6 | #[wasmcloud_actor_core::init] 7 | pub fn init() { 8 | http::Handlers::register_handle_request(uppercase); 9 | } 10 | 11 | fn uppercase(r: http::Request) -> HandlerResult { 12 | info!("Query String: {}", r.query_string); 13 | let upper = UppercaseResponse { 14 | original: r.query_string.to_string(), 15 | uppercased: r.query_string.to_ascii_uppercase(), 16 | }; 17 | 18 | Ok(http::Response::json(upper, 200, "OK")) 19 | } 20 | 21 | #[derive(Serialize)] 22 | struct UppercaseResponse { 23 | original: String, 24 | uppercased: String, 25 | } 26 | -------------------------------------------------------------------------------- /demos/wasmcloud/uppercase/uppercase-wasmcloud.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: uppercase 5 | labels: 6 | app: uppercase 7 | spec: 8 | containers: 9 | - image: webassembly.azurecr.io/uppercase-wasmcloud:v0.4.0 10 | imagePullPolicy: Always 11 | name: uppercase 12 | ports: 13 | - containerPort: 8080 14 | hostPort: 8080 15 | nodeSelector: 16 | kubernetes.io/arch: wasm32-wasmcloud 17 | tolerations: 18 | - key: "node.kubernetes.io/network-unavailable" 19 | operator: "Exists" 20 | effect: "NoSchedule" 21 | - key: "kubernetes.io/arch" 22 | operator: "Equal" 23 | value: "wasm32-wasmcloud" 24 | effect: "NoExecute" 25 | - key: "kubernetes.io/arch" 26 | operator: "Equal" 27 | value: "wasm32-wasmcloud" 28 | effect: "NoSchedule" 29 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | export RUST_LOG := "wasmcloud_host=debug,wasmcloud_provider=debug,kubelet=debug,main=debug" 2 | export PFX_PASSWORD := "testing" 3 | export CONFIG_DIR := env_var_or_default('CONFIG_DIR', '$HOME/.krustlet/config') 4 | 5 | build +FLAGS='': 6 | cargo build {{FLAGS}} 7 | 8 | test: 9 | cargo fmt --all -- --check 10 | cargo clippy --workspace 11 | cargo test --workspace --lib 12 | cargo test --doc --all 13 | 14 | test-e2e: 15 | cargo test --test integration_tests 16 | 17 | test-e2e-standalone: 18 | cargo run --bin oneclick 19 | 20 | run +FLAGS='': bootstrap 21 | KUBECONFIG=$(eval echo $CONFIG_DIR)/kubeconfig-wasmcloud cargo run --bin krustlet-wasmcloud {{FLAGS}} -- --node-name krustlet-wasmcloud --port 3000 --bootstrap-file $(eval echo $CONFIG_DIR)/bootstrap.conf --cert-file $(eval echo $CONFIG_DIR)/krustlet-wasmcloud.crt --private-key-file $(eval echo $CONFIG_DIR)/krustlet-wasmcloud.key 22 | 23 | bootstrap: 24 | @# This is to get around an issue with the default function returning a string that gets escaped 25 | @mkdir -p $(eval echo $CONFIG_DIR) 26 | @test -f $(eval echo $CONFIG_DIR)/bootstrap.conf || CONFIG_DIR=$(eval echo $CONFIG_DIR) ./scripts/bootstrap.sh 27 | @chmod 600 $(eval echo $CONFIG_DIR)/* 28 | -------------------------------------------------------------------------------- /justfile-windows: -------------------------------------------------------------------------------- 1 | set shell := ["powershell.exe", "-c"] 2 | 3 | export RUST_LOG := "wasmcloud_host=debug,wasmcloud_provider=debug,main=debug" 4 | export PFX_PASSWORD := "testing" 5 | export CONFIG_DIR := env_var_or_default('CONFIG_DIR', '$HOME\.krustlet\config') 6 | 7 | build +FLAGS='--no-default-features --features rustls-tls': 8 | cargo build {{FLAGS}} 9 | 10 | test +FLAGS='--no-default-features --features rustls-tls': 11 | cargo fmt --all -- --check 12 | cargo clippy {{FLAGS}} 13 | @# You can't pass in features with the --workspace flag, so we need to 14 | @# change directory into each crate for testing. This will result in some 15 | @# "not_used" errors as it isn't checking the whole workspace, but it should be 16 | @# sufficient for now. We may want to consider improving things using `cfg` 17 | @# directives to always pull in rustls-tls on windows machines 18 | Get-ChildItem crates -Name | ForEach-Object -Process { Push-Location .\crates\$_; cargo test {{FLAGS}}; Pop-Location } 19 | 20 | test-e2e +FLAGS='--no-default-features --features rustls-tls': 21 | cargo test --test integration_tests {{FLAGS}} 22 | 23 | run +FLAGS='--no-default-features --features rustls-tls': bootstrap 24 | $env:KUBECONFIG = "$(Invoke-Expression "echo $env:CONFIG_DIR")\kubeconfig-wasmcloud"; cargo run --bin krustlet-wasmcloud {{FLAGS}} -- --node-name krustlet-wasmcloud --port 3000 --bootstrap-file "$(Invoke-Expression "echo $env:CONFIG_DIR")\bootstrap.conf" --cert-file "$(Invoke-Expression "echo $env:CONFIG_DIR")\krustlet-wasmcloud.crt" --private-key-file "$(Invoke-Expression "echo $env:CONFIG_DIR")\krustlet-wasmcloud.key" 25 | 26 | bootstrap: 27 | @# This is to get around an issue with the default function returning a string that gets escaped 28 | @if ( -not (Test-Path -LiteralPath "$(Invoke-Expression "echo $env:CONFIG_DIR")\bootstrap.conf") ) { $env:CONFIG_DIR = Invoke-Expression "echo $env:CONFIG_DIR"; .\scripts\bootstrap.ps1 } 29 | -------------------------------------------------------------------------------- /scripts/bootstrap.ps1: -------------------------------------------------------------------------------- 1 | $token_id = -join ((48..57) + (97..122) | Get-Random -Count 6 | ForEach-Object { [char]$_ }) 2 | $token_secret = -join ((48..57) + (97..122) | Get-Random -Count 16 | ForEach-Object { [char]$_ }) 3 | 4 | $expiration = (Get-Date).ToUniversalTime().AddHours(1).ToString("yyyy-MM-ddTHH:mm:ssZ") 5 | 6 | @" 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: bootstrap-token-${token_id} 11 | namespace: kube-system 12 | type: bootstrap.kubernetes.io/token 13 | stringData: 14 | auth-extra-groups: system:bootstrappers:kubeadm:default-node-token 15 | expiration: ${expiration} 16 | token-id: ${token_id} 17 | token-secret: ${token_secret} 18 | usage-bootstrap-authentication: "true" 19 | usage-bootstrap-signing: "true" 20 | "@ | kubectl.exe apply -f - 21 | 22 | if (!$env:CONFIG_DIR -or -not (Test-Path $env:CONFIG_DIR)) { 23 | $config_dir = "$HOME\.krustlet\config" 24 | } 25 | else { 26 | $config_dir = $env:CONFIG_DIR 27 | } 28 | 29 | mkdir $config_dir -ErrorAction SilentlyContinue > $null 30 | 31 | if (!$env:FILE_NAME -or -not (Test-Path $env:FILE_NAME)) { 32 | $file_name = "bootstrap.conf" 33 | } 34 | else { 35 | $file_name = env:FILE_NAME 36 | } 37 | 38 | # Helpful script taken from the armory docs: https://docs.armory.io/spinnaker-install-admin-guides/manual-service-account/ 39 | # and modified to suit our needs 40 | 41 | $context = kubectl config current-context 42 | $new_context = "tls-bootstrap-token-user@kubernetes" 43 | $kubeconfig_file = "$config_dir\$file_name" 44 | $token_user = "tls-bootstrap-token-user" 45 | $token = "$token_id.$token_secret" 46 | 47 | 48 | try { 49 | # Create a full copy 50 | kubectl config view --raw > "$kubeconfig_file.full.tmp" 51 | 52 | # Switch working context to correct context 53 | kubectl --kubeconfig "$kubeconfig_file.full.tmp" config use-context "$context" 54 | 55 | # Minify 56 | kubectl --kubeconfig "$kubeconfig_file.full.tmp" config view --flatten --minify >"$kubeconfig_file.tmp" 57 | 58 | # Rename context 59 | kubectl config --kubeconfig "$kubeconfig_file.tmp" rename-context "$context" "$new_context" 60 | 61 | # Create token user 62 | kubectl config --kubeconfig "$kubeconfig_file.tmp" set-credentials "$token_user" --token "$token" 63 | 64 | # Set context to use token user 65 | kubectl config --kubeconfig "$kubeconfig_file.tmp" set-context "$new_context" --user "$token_user" 66 | 67 | # Flatten/minify kubeconfig 68 | $content = kubectl config --kubeconfig "$kubeconfig_file.tmp" view --flatten --minify 69 | 70 | [IO.File]::WriteAllLines($kubeconfig_file, $content) 71 | } 72 | 73 | finally { 74 | Remove-Item -Force "$kubeconfig_file.full.tmp" 75 | Remove-Item -Force "$kubeconfig_file.tmp" 76 | } 77 | 78 | -------------------------------------------------------------------------------- /scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | export LC_CTYPE=C 5 | 6 | token_id="$(/dev/null || 11 | date -v+1H -u "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null) 12 | 13 | cat <"${KUBECONFIG_FILE}.full.tmp" 50 | 51 | # Switch working context to correct context 52 | kubectl --kubeconfig "${KUBECONFIG_FILE}.full.tmp" config use-context "${CONTEXT}" 53 | 54 | # Minify 55 | kubectl --kubeconfig "${KUBECONFIG_FILE}.full.tmp" \ 56 | config view --flatten --minify >"${KUBECONFIG_FILE}.tmp" 57 | 58 | # Rename context 59 | kubectl config --kubeconfig "${KUBECONFIG_FILE}.tmp" \ 60 | rename-context "${CONTEXT}" "${NEW_CONTEXT}" 61 | 62 | # Create token user 63 | kubectl config --kubeconfig "${KUBECONFIG_FILE}.tmp" \ 64 | set-credentials "${TOKEN_USER}" --token "${TOKEN}" 65 | 66 | # Set context to use token user 67 | kubectl config --kubeconfig "${KUBECONFIG_FILE}.tmp" \ 68 | set-context "${NEW_CONTEXT}" --user "${TOKEN_USER}" 69 | 70 | # Set context to correct namespace 71 | kubectl config --kubeconfig "${KUBECONFIG_FILE}.tmp" \ 72 | set-context "${NEW_CONTEXT}" --namespace "${NAMESPACE}" 73 | 74 | # Flatten/minify kubeconfig 75 | kubectl config --kubeconfig "${KUBECONFIG_FILE}.tmp" \ 76 | view --flatten --minify >"${KUBECONFIG_FILE}" 77 | -------------------------------------------------------------------------------- /src/krustlet-wasmcloud.rs: -------------------------------------------------------------------------------- 1 | use kubelet::config::Config; 2 | use kubelet::plugin_watcher::PluginRegistry; 3 | use kubelet::store::composite::ComposableStore; 4 | use kubelet::store::oci::FileStore; 5 | use kubelet::Kubelet; 6 | use std::sync::Arc; 7 | use wasmcloud_provider::WasmCloudProvider; 8 | 9 | #[actix_rt::main] 10 | async fn main() -> anyhow::Result<()> { 11 | // The provider is responsible for all the "back end" logic. If you are creating 12 | // a new Kubelet, all you need to implement is a provider. 13 | let config = Config::new_from_file_and_flags(env!("CARGO_PKG_VERSION"), None); 14 | 15 | // Initialize the logger 16 | env_logger::init(); 17 | 18 | let kubeconfig = kubelet::bootstrap(&config, &config.bootstrap_file, notify_bootstrap).await?; 19 | 20 | let store = make_store(&config); 21 | let plugin_registry = Arc::new(PluginRegistry::new(&config.plugins_dir)); 22 | 23 | let provider = 24 | WasmCloudProvider::new(store, &config, kubeconfig.clone(), plugin_registry).await?; 25 | let kubelet = Kubelet::new(provider, kubeconfig, config).await?; 26 | kubelet.start().await 27 | } 28 | 29 | fn make_store(config: &Config) -> Arc { 30 | let client = oci_distribution::Client::from_source(config); 31 | let mut store_path = config.data_dir.join(".oci"); 32 | store_path.push("modules"); 33 | let file_store = Arc::new(FileStore::new(client, &store_path)); 34 | 35 | if config.allow_local_modules { 36 | file_store.with_override(Arc::new(kubelet::store::fs::FileSystemStore {})) 37 | } else { 38 | file_store 39 | } 40 | } 41 | 42 | fn notify_bootstrap(message: String) { 43 | println!("BOOTSTRAP: {}", message); 44 | } 45 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use futures::{StreamExt, TryStreamExt}; 2 | use k8s_openapi::api::core::v1::{Node, Pod, Taint}; 3 | use kube::api::{Api, DeleteParams, ListParams, LogParams, PostParams}; 4 | use kube_runtime::watcher::{watcher, Event}; 5 | use serde_json::json; 6 | 7 | #[tokio::test] 8 | async fn test_wasmcloud_provider() -> Result<(), Box> { 9 | let client = kube::Client::try_default().await?; 10 | 11 | let nodes: Api = Api::all(client); 12 | 13 | let node = nodes.get("krustlet-wasmcloud").await?; 14 | 15 | verify_wasmcloud_node(node).await; 16 | 17 | let client: kube::Client = nodes.into(); 18 | 19 | let _cleaner = WasmCloudTestResourceCleaner {}; 20 | 21 | let pods: Api = Api::namespaced(client.clone(), "default"); 22 | 23 | create_wasmcloud_pod(client.clone(), &pods).await?; 24 | 25 | let mut tries: u8 = 0; 26 | loop { 27 | // Send a request to the pod to trigger some logging 28 | if reqwest::get("http://127.0.0.1:30000").await.is_ok() { 29 | break; 30 | } 31 | tries += 1; 32 | if tries == 10 { 33 | panic!("wasmCloud pod failed 10 readiness checks."); 34 | } 35 | tokio::time::sleep(std::time::Duration::from_millis(100)).await; 36 | } 37 | 38 | let logs = pods 39 | .logs("greet-wasmcloud", &LogParams::default()) 40 | .await 41 | .expect("unable to get logs"); 42 | assert!(logs.contains("warn something")); 43 | assert!(logs.contains("info something")); 44 | assert!(logs.contains("error something")); 45 | 46 | Ok(()) 47 | } 48 | 49 | async fn verify_wasmcloud_node(node: Node) { 50 | let node_status = node.status.expect("node reported no status"); 51 | assert_eq!( 52 | node_status 53 | .node_info 54 | .expect("node status reported no info") 55 | .architecture, 56 | "wasm-wasi", 57 | "expected node to support the wasm-wasi architecture" 58 | ); 59 | 60 | let node_meta = node.metadata; 61 | assert_eq!( 62 | node_meta 63 | .labels 64 | .expect("node had no labels") 65 | .get("kubernetes.io/arch") 66 | .expect("node did not have kubernetes.io/arch label"), 67 | "wasm32-wasmcloud" 68 | ); 69 | 70 | let taints = node 71 | .spec 72 | .expect("node had no spec") 73 | .taints 74 | .expect("node had no taints"); 75 | let taint = taints 76 | .iter() 77 | .find(|t| (t.key == "kubernetes.io/arch") & (t.effect == "NoExecute")) 78 | .expect("did not find kubernetes.io/arch taint"); 79 | // There is no "operator" field in the type for the crate for some reason, 80 | // so we can't compare it here 81 | assert_eq!( 82 | taint, 83 | &Taint { 84 | effect: "NoExecute".to_owned(), 85 | key: "kubernetes.io/arch".to_owned(), 86 | value: Some("wasm32-wasmcloud".to_owned()), 87 | ..Default::default() 88 | } 89 | ); 90 | 91 | let taint = taints 92 | .iter() 93 | .find(|t| (t.key == "kubernetes.io/arch") & (t.effect == "NoSchedule")) 94 | .expect("did not find kubernetes.io/arch taint"); 95 | // There is no "operator" field in the type for the crate for some reason, 96 | // so we can't compare it here 97 | assert_eq!( 98 | taint, 99 | &Taint { 100 | effect: "NoSchedule".to_owned(), 101 | key: "kubernetes.io/arch".to_owned(), 102 | value: Some("wasm32-wasmcloud".to_owned()), 103 | ..Default::default() 104 | } 105 | ); 106 | } 107 | 108 | async fn create_wasmcloud_pod(client: kube::Client, pods: &Api) -> anyhow::Result<()> { 109 | let p = serde_json::from_value(json!({ 110 | "apiVersion": "v1", 111 | "kind": "Pod", 112 | "metadata": { 113 | "name": "greet-wasmcloud" 114 | }, 115 | "spec": { 116 | "containers": [ 117 | { 118 | "name": "greet-wasmcloud", 119 | "image": "webassembly.azurecr.io/greet-wasmcloud:v0.6.0", 120 | "ports": [ 121 | { 122 | "containerPort": 8080, 123 | "hostPort": 30000 124 | } 125 | ], 126 | }, 127 | ], 128 | "tolerations": [ 129 | { 130 | "effect": "NoExecute", 131 | "key": "kubernetes.io/arch", 132 | "operator": "Equal", 133 | "value": "wasm32-wasmcloud" 134 | }, 135 | { 136 | "effect": "NoSchedule", 137 | "key": "kubernetes.io/arch", 138 | "operator": "Equal", 139 | "value": "wasm32-wasmcloud" 140 | }, 141 | ] 142 | } 143 | }))?; 144 | 145 | let pod = pods.create(&PostParams::default(), &p).await?; 146 | 147 | assert_eq!(pod.status.unwrap().phase.unwrap(), "Pending"); 148 | 149 | wait_for_pod_ready(client, "greet-wasmcloud", "default").await?; 150 | 151 | Ok(()) 152 | } 153 | 154 | struct WasmCloudTestResourceCleaner {} 155 | 156 | impl Drop for WasmCloudTestResourceCleaner { 157 | fn drop(&mut self) { 158 | let t = std::thread::spawn(move || { 159 | let rt = 160 | tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime for cleanup"); 161 | rt.block_on(clean_up_wasmcloud_test_resources()); 162 | }); 163 | 164 | t.join() 165 | .expect("Failed to clean up wasmcloud test resources"); 166 | } 167 | } 168 | 169 | async fn clean_up_wasmcloud_test_resources() { 170 | let client = kube::Client::try_default() 171 | .await 172 | .expect("Failed to create client"); 173 | 174 | let pods: Api = Api::namespaced(client.clone(), "default"); 175 | pods.delete("greet-wasmcloud", &DeleteParams::default()) 176 | .await 177 | .expect("Failed to delete pod"); 178 | } 179 | 180 | pub async fn wait_for_pod_ready( 181 | client: kube::Client, 182 | pod_name: &str, 183 | namespace: &str, 184 | ) -> anyhow::Result<()> { 185 | let api: Api = Api::namespaced(client, namespace); 186 | let inf = watcher( 187 | api, 188 | ListParams::default() 189 | .fields(&format!("metadata.name={}", pod_name)) 190 | .timeout(30), 191 | ); 192 | 193 | let mut watcher = inf.boxed(); 194 | let mut went_ready = false; 195 | while let Some(event) = watcher.try_next().await? { 196 | if let Event::Applied(o) = event { 197 | let containers = o 198 | .clone() 199 | .status 200 | .unwrap() 201 | .container_statuses 202 | .unwrap_or_else(Vec::new); 203 | let phase = o.status.unwrap().phase.unwrap(); 204 | if (phase == "Running") 205 | & (!containers.is_empty()) 206 | & containers.iter().all(|status| status.ready) 207 | { 208 | went_ready = true; 209 | break; 210 | } 211 | } 212 | } 213 | 214 | assert!(went_ready, "pod never went ready"); 215 | 216 | Ok(()) 217 | } 218 | -------------------------------------------------------------------------------- /tests/oneclick/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | enum BootstrapReadiness { 4 | AlreadyBootstrapped, 5 | NeedBootstrapAndApprove, 6 | NeedManualCleanup, 7 | } 8 | 9 | const EXIT_CODE_TESTS_PASSED: i32 = 0; 10 | const EXIT_CODE_TESTS_FAILED: i32 = 1; 11 | const EXIT_CODE_NEED_MANUAL_CLEANUP: i32 = 2; 12 | const EXIT_CODE_BUILD_FAILED: i32 = 3; 13 | 14 | fn main() { 15 | println!("Ensuring all binaries are built..."); 16 | 17 | let build_result = build_workspace(); 18 | 19 | match build_result { 20 | Ok(()) => { 21 | println!("Build succeeded"); 22 | } 23 | Err(e) => { 24 | eprintln!("{}", e); 25 | eprintln!("Build FAILED"); 26 | std::process::exit(EXIT_CODE_BUILD_FAILED); 27 | } 28 | } 29 | 30 | println!("Preparing for bootstrap..."); 31 | 32 | let readiness = prepare_for_bootstrap(); 33 | 34 | match readiness { 35 | BootstrapReadiness::AlreadyBootstrapped => { 36 | println!("Already bootstrapped"); 37 | } 38 | BootstrapReadiness::NeedBootstrapAndApprove => { 39 | println!("Bootstrap required"); 40 | } 41 | BootstrapReadiness::NeedManualCleanup => { 42 | eprintln!("Bootstrap directory and CSRs need manual clean up"); 43 | std::process::exit(EXIT_CODE_NEED_MANUAL_CLEANUP); 44 | } 45 | } 46 | 47 | if matches!(readiness, BootstrapReadiness::NeedBootstrapAndApprove) { 48 | println!("Running bootstrap script..."); 49 | let bootstrap_result = run_bootstrap(); 50 | match bootstrap_result { 51 | Ok(()) => { 52 | println!("Bootstrap script succeeded"); 53 | } 54 | Err(e) => { 55 | eprintln!("Running bootstrap script failed: {}", e); 56 | std::process::exit(EXIT_CODE_NEED_MANUAL_CLEANUP); 57 | } 58 | } 59 | } 60 | 61 | let test_result = run_tests(readiness); 62 | 63 | println!("All complete"); 64 | 65 | let exit_code = match test_result { 66 | Ok(()) => EXIT_CODE_TESTS_PASSED, 67 | Err(_) => EXIT_CODE_TESTS_FAILED, 68 | }; 69 | 70 | std::process::exit(exit_code); 71 | } 72 | 73 | fn config_dir() -> std::path::PathBuf { 74 | let home_dir = dirs::home_dir().expect("Can't get home dir"); // TODO: allow override of config dir 75 | home_dir.join(".krustlet/config") 76 | } 77 | 78 | fn config_file_path_str(file_name: impl AsRef) -> String { 79 | config_dir().join(file_name).to_str().unwrap().to_owned() 80 | } 81 | 82 | fn build_workspace() -> anyhow::Result<()> { 83 | let build_result = std::process::Command::new("cargo") 84 | .args(&["build"]) 85 | .output()?; 86 | 87 | if build_result.status.success() { 88 | Ok(()) 89 | } else { 90 | Err(anyhow::anyhow!( 91 | "{}", 92 | String::from_utf8(build_result.stderr).unwrap() 93 | )) 94 | } 95 | } 96 | 97 | fn prepare_for_bootstrap() -> BootstrapReadiness { 98 | let host_name = hostname::get() 99 | .expect("Can't get host name") 100 | .into_string() 101 | .expect("Can't get host name"); 102 | 103 | let cert_paths: Vec<_> = vec!["krustlet-wasmcloud.crt", "krustlet-wasmcloud.key"] 104 | .iter() 105 | .map(|f| config_dir().join(f)) 106 | .collect(); 107 | 108 | let status = all_or_none(cert_paths); 109 | 110 | match status { 111 | AllOrNone::AllExist => { 112 | return BootstrapReadiness::AlreadyBootstrapped; 113 | } 114 | AllOrNone::NoneExist => (), 115 | AllOrNone::Error => { 116 | return BootstrapReadiness::NeedManualCleanup; 117 | } 118 | }; 119 | 120 | // We are not bootstrapped, but there may be existing CSRs around 121 | 122 | // TODO: allow override of host names 123 | let wasmcloud_host_name = &host_name; 124 | 125 | let wasmcloud_cert_name = format!("{}-tls", wasmcloud_host_name); 126 | 127 | let csr_spawn_deletes: Vec<_> = vec!["krustlet-wasmcloud", &wasmcloud_cert_name] 128 | .iter() 129 | .map(delete_csr) 130 | .collect(); 131 | 132 | let (csr_deletions, csr_spawn_delete_errors) = csr_spawn_deletes.partition_success(); 133 | 134 | if !csr_spawn_delete_errors.is_empty() { 135 | return BootstrapReadiness::NeedManualCleanup; 136 | } 137 | 138 | let csr_deletion_results: Vec<_> = csr_deletions 139 | .into_iter() 140 | .map(|c| c.wait_with_output()) 141 | .collect(); 142 | 143 | let (csr_deletion_outputs, csr_run_deletion_failures) = 144 | csr_deletion_results.partition_success(); 145 | 146 | if !csr_run_deletion_failures.is_empty() { 147 | return BootstrapReadiness::NeedManualCleanup; 148 | } 149 | 150 | if csr_deletion_outputs.iter().any(|o| !is_resource_gone(o)) { 151 | return BootstrapReadiness::NeedManualCleanup; 152 | } 153 | 154 | // We have now deleted all the local certificate files, and all the CSRs that 155 | // might get in the way of our re-bootstrapping. Let the caller know they 156 | // will need to re-approve once the new CSRs come up. 157 | BootstrapReadiness::NeedBootstrapAndApprove 158 | } 159 | 160 | enum AllOrNone { 161 | AllExist, 162 | NoneExist, 163 | Error, 164 | } 165 | 166 | fn all_or_none(files: Vec) -> AllOrNone { 167 | let (exist, missing): (Vec<_>, Vec<_>) = files.iter().partition(|f| f.exists()); 168 | 169 | if missing.is_empty() { 170 | return AllOrNone::AllExist; 171 | } 172 | 173 | for f in exist { 174 | if matches!(std::fs::remove_file(f), Err(_)) { 175 | return AllOrNone::Error; 176 | } 177 | } 178 | 179 | AllOrNone::NoneExist 180 | } 181 | 182 | fn delete_csr(csr_name: impl AsRef) -> std::io::Result { 183 | std::process::Command::new("kubectl") 184 | .args(&["delete", "csr", csr_name.as_ref()]) 185 | .stderr(std::process::Stdio::piped()) 186 | .stdout(std::process::Stdio::piped()) 187 | .spawn() 188 | } 189 | 190 | trait ResultSequence { 191 | type SuccessItem; 192 | type FailureItem; 193 | fn partition_success(self) -> (Vec, Vec); 194 | } 195 | 196 | impl ResultSequence for Vec> { 197 | type SuccessItem = T; 198 | type FailureItem = E; 199 | fn partition_success(self) -> (Vec, Vec) { 200 | let (success_results, error_results): (Vec<_>, Vec<_>) = 201 | self.into_iter().partition(|r| r.is_ok()); 202 | let success_values = success_results.into_iter().map(|r| r.unwrap()).collect(); 203 | let error_values = error_results 204 | .into_iter() 205 | .map(|r| r.err().unwrap()) 206 | .collect(); 207 | (success_values, error_values) 208 | } 209 | } 210 | 211 | fn is_resource_gone(kubectl_output: &std::process::Output) -> bool { 212 | kubectl_output.status.success() 213 | || match String::from_utf8(kubectl_output.stderr.clone()) { 214 | Ok(s) => s.contains("NotFound"), 215 | _ => false, 216 | } 217 | } 218 | 219 | fn run_bootstrap() -> anyhow::Result<()> { 220 | let (shell, ext) = match std::env::consts::OS { 221 | "windows" => Ok(("powershell.exe", "ps1")), 222 | "linux" | "macos" => Ok(("bash", "sh")), 223 | os => Err(anyhow::anyhow!("Unsupported OS {}", os)), 224 | }?; 225 | 226 | let repo_root = std::env!("CARGO_MANIFEST_DIR"); 227 | 228 | let bootstrap_script = format!("{}/scripts/bootstrap.{}", repo_root, ext); 229 | let bootstrap_output = std::process::Command::new(shell) 230 | .arg(bootstrap_script) 231 | .env("CONFIG_DIR", config_dir()) 232 | .stdout(std::process::Stdio::piped()) 233 | .stderr(std::process::Stdio::piped()) 234 | .output()?; 235 | 236 | match bootstrap_output.status.code() { 237 | Some(0) => Ok(()), 238 | Some(e) => Err(anyhow::anyhow!( 239 | "Bootstrap error {}: {}", 240 | e, 241 | String::from_utf8_lossy(&bootstrap_output.stderr) 242 | )), 243 | None => Err(anyhow::anyhow!( 244 | "Bootstrap error (no exit code): {}", 245 | String::from_utf8_lossy(&bootstrap_output.stderr) 246 | )), 247 | } 248 | } 249 | 250 | fn launch_kubelet( 251 | name: &str, 252 | kubeconfig_suffix: &str, 253 | kubelet_port: i32, 254 | need_csr: bool, 255 | ) -> anyhow::Result { 256 | // run the kubelet as a background process using the 257 | // same cmd line as in the justfile: 258 | // KUBECONFIG=$(eval echo $CONFIG_DIR)/kubeconfig-wasmcloud cargo run --bin krustlet-wasmcloud {{FLAGS}} -- --node-name krustlet-wasmcloud --port 3001 --bootstrap-file $(eval echo $CONFIG_DIR)/bootstrap.conf --cert-file $(eval echo $CONFIG_DIR)/krustlet-wasmcloud.crt --private-key-file $(eval echo $CONFIG_DIR)/krustlet-wasmcloud.key 259 | let bootstrap_conf = config_file_path_str("bootstrap.conf"); 260 | let cert = config_file_path_str(format!("{}.crt", name)); 261 | let private_key = config_file_path_str(format!("{}.key", name)); 262 | let kubeconfig = config_file_path_str(format!("kubeconfig-{}", kubeconfig_suffix)); 263 | let port_arg = format!("{}", kubelet_port); 264 | 265 | let repo_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 266 | let bin_path = repo_root.join("target/debug").join(name); 267 | 268 | let mut launch_kubelet_process = std::process::Command::new(bin_path) 269 | .args(&[ 270 | "--node-name", 271 | name, 272 | "--port", 273 | &port_arg, 274 | "--bootstrap-file", 275 | &bootstrap_conf, 276 | "--cert-file", 277 | &cert, 278 | "--private-key-file", 279 | &private_key, 280 | "--x-allow-local-modules", 281 | "true", 282 | ]) 283 | .env("KUBECONFIG", kubeconfig) 284 | .env( 285 | "RUST_LOG", 286 | "wasmcloud_host=debug,wasmcloud_provider=debug,main=debug", 287 | ) 288 | .stdout(std::process::Stdio::piped()) 289 | .stderr(std::process::Stdio::piped()) 290 | .spawn()?; 291 | 292 | println!("Kubelet process {} launched", name); 293 | 294 | if need_csr { 295 | println!("Waiting for kubelet {} to generate CSR", name); 296 | let stdout = launch_kubelet_process.stdout.as_mut().unwrap(); 297 | wait_for_tls_certificate_approval(stdout)?; 298 | println!("Finished bootstrapping for kubelet {}", name); 299 | } 300 | 301 | let terminator = OwnedChildProcess { 302 | terminated: false, 303 | child: launch_kubelet_process, 304 | }; 305 | Ok(terminator) 306 | } 307 | 308 | fn wait_for_tls_certificate_approval(stdout: impl std::io::Read) -> anyhow::Result<()> { 309 | let reader = std::io::BufReader::new(stdout); 310 | for (_, line) in reader.lines().enumerate() { 311 | match line { 312 | Ok(line_text) => { 313 | println!("Kubelet printed: {}", line_text); 314 | if line_text == "BOOTSTRAP: received TLS certificate approval: continuing" { 315 | return Ok(()); 316 | } 317 | let re = regex::Regex::new(r"^BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve (\S+)$").unwrap(); 318 | match re.captures(&line_text) { 319 | None => (), 320 | Some(captures) => { 321 | let csr_name = &captures[1]; 322 | approve_csr(csr_name)? 323 | } 324 | } 325 | } 326 | Err(e) => eprintln!("Error reading kubelet stdout: {}", e), 327 | } 328 | } 329 | println!("End of kubelet output with no approval"); 330 | Err(anyhow::anyhow!("End of kubelet output with no approval")) 331 | } 332 | 333 | fn approve_csr(csr_name: &str) -> anyhow::Result<()> { 334 | println!("Approving CSR {}", csr_name); 335 | let approve_process = std::process::Command::new("kubectl") 336 | .args(&["certificate", "approve", csr_name]) 337 | .stderr(std::process::Stdio::piped()) 338 | .stdout(std::process::Stdio::piped()) 339 | .output()?; 340 | if !approve_process.status.success() { 341 | Err(anyhow::anyhow!( 342 | "Error approving CSR {}: {}", 343 | csr_name, 344 | String::from_utf8(approve_process.stderr).unwrap() 345 | )) 346 | } else { 347 | println!("Approved CSR {}", csr_name); 348 | clean_up_csr(csr_name) 349 | } 350 | } 351 | 352 | fn clean_up_csr(csr_name: &str) -> anyhow::Result<()> { 353 | println!("Cleaning up approved CSR {}", csr_name); 354 | let clean_up_process = std::process::Command::new("kubectl") 355 | .args(&["delete", "csr", csr_name]) 356 | .stderr(std::process::Stdio::piped()) 357 | .stdout(std::process::Stdio::piped()) 358 | .output()?; 359 | if !clean_up_process.status.success() { 360 | Err(anyhow::anyhow!( 361 | "Error cleaning up CSR {}: {}", 362 | csr_name, 363 | String::from_utf8(clean_up_process.stderr).unwrap() 364 | )) 365 | } else { 366 | println!("Cleaned up approved CSR {}", csr_name); 367 | Ok(()) 368 | } 369 | } 370 | 371 | struct OwnedChildProcess { 372 | terminated: bool, 373 | child: std::process::Child, 374 | } 375 | 376 | impl OwnedChildProcess { 377 | fn terminate(&mut self) -> anyhow::Result<()> { 378 | match self.child.kill().and_then(|_| self.child.wait()) { 379 | Ok(_) => { 380 | self.terminated = true; 381 | Ok(()) 382 | } 383 | Err(e) => Err(anyhow::anyhow!( 384 | "Failed to terminate spawned kubelet process: {}", 385 | e 386 | )), 387 | } 388 | } 389 | 390 | fn exited(&mut self) -> anyhow::Result { 391 | let exit_status = self.child.try_wait()?; 392 | Ok(exit_status.is_some()) 393 | } 394 | } 395 | 396 | impl Drop for OwnedChildProcess { 397 | fn drop(&mut self) { 398 | if !self.terminated { 399 | match self.terminate() { 400 | Ok(()) => (), 401 | Err(e) => eprintln!("{}", e), 402 | } 403 | } 404 | } 405 | } 406 | 407 | fn run_tests(readiness: BootstrapReadiness) -> anyhow::Result<()> { 408 | let wasmcloud_process_result = launch_kubelet( 409 | "krustlet-wasmcloud", 410 | "wasmcloud", 411 | 3000, 412 | matches!(readiness, BootstrapReadiness::NeedBootstrapAndApprove), 413 | ); 414 | 415 | match wasmcloud_process_result { 416 | Err(e) => { 417 | eprintln!("Error running kubelet process: {}", e); 418 | return Err(anyhow::anyhow!("Error running kubelet process: {}", e)); 419 | } 420 | Ok(_) => println!("Running kubelet process"), 421 | } 422 | 423 | let test_result = run_test_suite(); 424 | 425 | let mut wasmcloud_process = wasmcloud_process_result.unwrap(); 426 | 427 | if matches!(test_result, Err(_)) { 428 | warn_if_premature_exit(&mut wasmcloud_process, "krustlet-wasmcloud"); 429 | // TODO: ideally we shouldn't have to wait for termination before getting logs 430 | let terminate_result = wasmcloud_process.terminate(); 431 | match terminate_result { 432 | Ok(_) => { 433 | let wasmcloud_log_destination = 434 | std::path::PathBuf::from("./krustlet-wasmcloud-e2e"); 435 | capture_kubelet_logs( 436 | "krustlet-wasmcloud", 437 | &mut wasmcloud_process.child, 438 | wasmcloud_log_destination, 439 | ); 440 | } 441 | Err(e) => { 442 | eprintln!("{}", e); 443 | eprintln!("Can't capture kubelet logs as they didn't terminate"); 444 | } 445 | } 446 | } 447 | 448 | test_result 449 | } 450 | 451 | fn warn_if_premature_exit(process: &mut OwnedChildProcess, name: &str) { 452 | match process.exited() { 453 | Err(e) => eprintln!( 454 | "FAILED checking kubelet process {} exit state ({})", 455 | name, e 456 | ), 457 | Ok(false) => eprintln!("WARNING: Kubelet process {} exited prematurely", name), 458 | _ => (), 459 | }; 460 | } 461 | 462 | fn run_test_suite() -> anyhow::Result<()> { 463 | println!("Launching integration tests"); 464 | let test_process = std::process::Command::new("cargo") 465 | .args(&["test", "--test", "integration_tests"]) 466 | .stderr(std::process::Stdio::piped()) 467 | .stdout(std::process::Stdio::piped()) 468 | .spawn()?; 469 | println!("Integration tests running"); 470 | // TODO: consider streaming progress 471 | // TODO: capture pod logs: probably requires cooperation from the test 472 | // process 473 | let test_process_result = test_process.wait_with_output()?; 474 | if test_process_result.status.success() { 475 | println!("Integration tests PASSED"); 476 | Ok(()) 477 | } else { 478 | let stdout = String::from_utf8(test_process_result.stdout)?; 479 | eprintln!("{}", stdout); 480 | let stderr = String::from_utf8(test_process_result.stderr)?; 481 | eprintln!("{}", stderr); 482 | eprintln!("Integration tests FAILED"); 483 | Err(anyhow::anyhow!(stderr)) 484 | } 485 | } 486 | 487 | fn capture_kubelet_logs( 488 | kubelet_name: &str, 489 | kubelet_process: &mut std::process::Child, 490 | destination: std::path::PathBuf, 491 | ) { 492 | let stdout = kubelet_process.stdout.as_mut().unwrap(); 493 | let stdout_path = destination.with_extension("stdout.txt"); 494 | write_kubelet_log_to_file(kubelet_name, stdout, stdout_path); 495 | 496 | let stderr = kubelet_process.stderr.as_mut().unwrap(); 497 | let stderr_path = destination.with_extension("stderr.txt"); 498 | write_kubelet_log_to_file(kubelet_name, stderr, stderr_path); 499 | } 500 | 501 | fn write_kubelet_log_to_file( 502 | kubelet_name: &str, 503 | log: &mut impl std::io::Read, 504 | file_path: std::path::PathBuf, 505 | ) { 506 | let mut file_result = std::fs::File::create(file_path); 507 | match file_result { 508 | Ok(ref mut file) => { 509 | let write_result = std::io::copy(log, file); 510 | match write_result { 511 | Ok(_) => (), 512 | Err(e) => eprintln!("Can't capture {} output: {}", kubelet_name, e), 513 | } 514 | } 515 | Err(e) => { 516 | eprintln!("Can't capture {} output: {}", kubelet_name, e); 517 | } 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /tests/podsmiter/src/main.rs: -------------------------------------------------------------------------------- 1 | use k8s_openapi::api::core::v1::{Namespace, Pod}; 2 | use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; 3 | use k8s_openapi::Metadata; 4 | use kube::api::{Api, DeleteParams, ListParams}; 5 | 6 | const E2E_NS_PREFIXES: &[&str] = &["wasmcloud-e2e"]; 7 | 8 | #[tokio::main(flavor = "multi_thread")] 9 | async fn main() -> anyhow::Result<()> { 10 | let result = smite_all_integration_test_pods().await; 11 | 12 | match &result { 13 | Ok(message) => println!("{}", message), 14 | Err(e) => println!("{}", e), 15 | }; 16 | 17 | result.map(|_| ()) 18 | } 19 | 20 | async fn smite_all_integration_test_pods() -> anyhow::Result<&'static str> { 21 | let client = match kube::Client::try_default().await { 22 | Ok(c) => c, 23 | Err(e) => { 24 | return Err(anyhow::anyhow!( 25 | "Failed to acquire Kubernetes client: {}", 26 | e 27 | )) 28 | } 29 | }; 30 | 31 | let namespaces = list_e2e_namespaces(client.clone()).await?; 32 | 33 | if namespaces.is_empty() { 34 | return Ok("No e2e namespaces found"); 35 | } 36 | if !confirm_smite(&namespaces) { 37 | return Ok("Operation cancelled"); 38 | } 39 | 40 | let pod_smite_operations = namespaces 41 | .iter() 42 | .map(|ns| smite_namespace_pods(client.clone(), ns)); 43 | let pod_smite_results = futures::future::join_all(pod_smite_operations).await; 44 | let (_, pod_smite_errors) = pod_smite_results.partition_success(); 45 | 46 | if !pod_smite_errors.is_empty() { 47 | return Err(smite_failure_error(&pod_smite_errors)); 48 | } 49 | 50 | println!("Requested force-delete of all pods; requesting delete of namespaces..."); 51 | 52 | let ns_smite_operations = namespaces 53 | .iter() 54 | .map(|ns| smite_namespace(client.clone(), ns)); 55 | let ns_smite_results = futures::future::join_all(ns_smite_operations).await; 56 | let (_, ns_smite_errors) = ns_smite_results.partition_success(); 57 | 58 | if !ns_smite_errors.is_empty() { 59 | return Err(smite_failure_error(&ns_smite_errors)); 60 | } 61 | 62 | Ok("All e2e pods force-deleted; namespace cleanup may take a couple of minutes") 63 | } 64 | 65 | async fn list_e2e_namespaces(client: kube::Client) -> anyhow::Result> { 66 | println!("Finding e2e namespaces..."); 67 | 68 | let nsapi: Api = Api::all(client.clone()); 69 | let nslist = nsapi.list(&ListParams::default()).await?; 70 | 71 | Ok(nslist 72 | .iter() 73 | .map(name_of) 74 | .filter(|n| is_e2e_namespace(n)) 75 | .collect()) 76 | } 77 | 78 | fn name_of(ns: &impl Metadata) -> String { 79 | ns.metadata().name.as_ref().unwrap().to_owned() 80 | } 81 | 82 | fn is_e2e_namespace(namespace: &str) -> bool { 83 | E2E_NS_PREFIXES 84 | .iter() 85 | .any(|prefix| namespace.starts_with(prefix)) 86 | } 87 | 88 | async fn smite_namespace_pods(client: kube::Client, namespace: &str) -> anyhow::Result<()> { 89 | println!("Finding pods in namespace {}...", namespace); 90 | 91 | let podapi: Api = Api::namespaced(client.clone(), namespace); 92 | let pods = podapi.list(&ListParams::default()).await?; 93 | 94 | println!("Deleting pods in namespace {}...", namespace); 95 | 96 | let delete_operations = pods.iter().map(|p| smite_pod(&podapi, p)); 97 | let delete_results = futures::future::join_all(delete_operations).await; 98 | let (_, errors) = delete_results.partition_success(); 99 | 100 | if !errors.is_empty() { 101 | return Err(smite_pods_failure_error(namespace, &errors)); 102 | } 103 | 104 | Ok(()) 105 | } 106 | 107 | async fn smite_pod(podapi: &Api, pod: &Pod) -> anyhow::Result<()> { 108 | let pod_name = name_of(pod); 109 | let _ = podapi 110 | .delete( 111 | &pod_name, 112 | &DeleteParams { 113 | grace_period_seconds: Some(0), 114 | ..DeleteParams::default() 115 | }, 116 | ) 117 | .await?; 118 | Ok(()) 119 | } 120 | 121 | async fn smite_namespace(client: kube::Client, namespace: &str) -> anyhow::Result<()> { 122 | let nsapi: Api = Api::all(client.clone()); 123 | nsapi.delete(namespace, &DeleteParams::default()).await?; 124 | Ok(()) 125 | } 126 | 127 | fn smite_failure_error(errors: &[anyhow::Error]) -> anyhow::Error { 128 | let message_list = errors 129 | .iter() 130 | .map(|e| format!("{}", e)) 131 | .collect::>() 132 | .join("\n"); 133 | anyhow::anyhow!( 134 | "Some integration test resources were not cleaned up:\n{}", 135 | message_list 136 | ) 137 | } 138 | 139 | fn smite_pods_failure_error(namespace: &str, errors: &[anyhow::Error]) -> anyhow::Error { 140 | let message_list = errors 141 | .iter() 142 | .map(|e| format!(" - {}", e)) 143 | .collect::>() 144 | .join("\n"); 145 | anyhow::anyhow!( 146 | "- Namespace {}: pod delete(s) failed:\n{}", 147 | namespace, 148 | message_list 149 | ) 150 | } 151 | 152 | fn confirm_smite(namespaces: &[String]) -> bool { 153 | println!( 154 | "Smite these namespaces and all resources within them: {}? (y/n) ", 155 | namespaces.join(", ") 156 | ); 157 | let mut response = String::new(); 158 | match std::io::stdin().read_line(&mut response) { 159 | Err(e) => { 160 | eprintln!("Error reading response: {}", e); 161 | confirm_smite(namespaces) 162 | } 163 | Ok(_) => response.starts_with('y') || response.starts_with('Y'), 164 | } 165 | } 166 | 167 | // TODO: deduplicate with oneclick 168 | 169 | trait ResultSequence { 170 | type SuccessItem; 171 | type FailureItem; 172 | fn partition_success(self) -> (Vec, Vec); 173 | } 174 | 175 | impl ResultSequence for Vec> { 176 | type SuccessItem = T; 177 | type FailureItem = E; 178 | fn partition_success(self) -> (Vec, Vec) { 179 | let (success_results, error_results): (Vec<_>, Vec<_>) = 180 | self.into_iter().partition(|r| r.is_ok()); 181 | let success_values = success_results.into_iter().map(|r| r.unwrap()).collect(); 182 | let error_values = error_results 183 | .into_iter() 184 | .map(|r| r.err().unwrap()) 185 | .collect(); 186 | (success_values, error_values) 187 | } 188 | } 189 | --------------------------------------------------------------------------------