diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 833c9e6824..02781cdb06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,7 +106,7 @@ jobs: echo "$((Get-Item .\hippo-server-output).FullName)\win-x64"; echo "$((Get-Item .\hippo-server-output).FullName)\win-x64" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append; - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: Cargo Format if: ${{ matrix.config.platformAgnosticChecks }} @@ -158,18 +158,3 @@ jobs: with: name: spin-${{ matrix.config.os }} path: target/debug/spin${{ matrix.config.extension }} - - check-docs: - name: Check Docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install bart - run: | - curl -LOs https://github.com/fermyon/bartholomew/releases/download/v0.3.0/bart - chmod +x bart - mv bart /usr/local/bin - - - name: Check docs site content - run: make check-content diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b3132ddd6..8b2112851d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,7 +221,7 @@ jobs: steps: - uses: actions/checkout@v2 - - name: set the tag sdk/go/v* + - name: Set the tag to sdk/go/v* shell: bash run: echo "GO_SDK_TAG=sdk/go/${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV @@ -233,24 +233,18 @@ jobs: git tag ${{ env.GO_SDK_TAG }} git push origin ${{ env.GO_SDK_TAG }} - commit-and-create-template-tag: - name: Change sdk version in templates and create a new tag spin/templates/v* + create-template-sdk-update-pr: + name: Create PR with template SDK updates runs-on: ubuntu-latest needs: create-go-sdk-tag if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v2 - - name: set the spin tag + - name: Set the spin tag shell: bash run: echo "SPIN_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - - name: set the tag spin/templates/v* - shell: bash - run: | - IFS=. read -r major minor patch <<< "${{ env.SPIN_TAG }}" - echo "TEMPLATE_TAG=spin/templates/$major.$minor" >> $GITHUB_ENV - - name: Change sdk version shell: bash run: | @@ -265,19 +259,33 @@ jobs: git_user_signingkey: true git_commit_gpgsign: true - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 with: - commit_message: "feat(templates): update sdk to ${{ env.SPIN_TAG }}" - commit_options: '-s -S' - commit_user_name: fermybot - commit_user_email: 103076628+fermybot@users.noreply.github.com - commit_author: fermybot <103076628+fermybot@users.noreply.github.com> - file_pattern: templates/* - branch: main - skip_dirty_check: true - skip_fetch: true - skip_checkout: true + commit-message: "feat(templates): update sdk to ${{ env.SPIN_TAG }}" + title: "feat(templates): update sdk to ${{ env.SPIN_TAG }}" + body: Update the SDK version used by the templates + branch: update-sdk-${{ env.SPIN_TAG }} + base: main + delete-branch: true + committer: fermybot <103076628+fermybot@users.noreply.github.com> + author: fermybot <103076628+fermybot@users.noreply.github.com> + signoff: true + + # This will run when the PR above is approved and merged into main via a merge commit + push-templates-tag: + runs-on: ubuntu-latest + needs: build + if: github.event.commits[0].author.name == 'fermybot' && contains(github.event.commits[0].message, 'update sdk') + steps: + - uses: actions/checkout@v2 + + - name: Set the tag to spin/templates/v* + shell: bash + run: | + spin_tag=$(echo "${{ github.event.commits[0].message }}" | grep -Eo v[0-9.]+) + IFS=. read -r major minor patch <<< "${spin_tag}" + echo "TEMPLATE_TAG=spin/templates/$major.$minor" >> $GITHUB_ENV - name: Tag spin/templates/v* and push it shell: bash diff --git a/Cargo.lock b/Cargo.lock index 3e7f0028dc..90725f60ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + [[package]] name = "addr2line" version = "0.17.0" @@ -17,6 +23,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -30,13 +48,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "ambient-authority" version = "0.0.1" @@ -52,20 +76,11 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" -version = "1.0.63" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "anymap2" @@ -86,6 +101,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-io" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +dependencies = [ + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + [[package]] name = "async-trait" version = "0.1.57" @@ -158,7 +193,7 @@ dependencies = [ [[package]] name = "bindle" version = "0.8.0" -source = "git+https://github.com/fermyon/bindle?tag=v0.8.1#0dd1e7a7747d9f0ea8fda61d5f0f8cc3550f65dd" +source = "git+https://github.com/fermyon/bindle?tag=v0.8.2#84ea1dd86fab6ffe27235e268f47f632421db8f9" dependencies = [ "anyhow", "async-compression", @@ -178,15 +213,15 @@ dependencies = [ "oauth2", "rand 0.7.3", "reqwest", - "semver 1.0.13", + "semver 1.0.14", "serde", "serde_cbor", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "sled", "tempfile", "thiserror", - "time 0.3.14", + "time 0.3.15", "tokio", "tokio-stream", "tokio-tar", @@ -203,6 +238,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -214,13 +255,29 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blowfish" version = "0.8.0" @@ -263,60 +320,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] -name = "cap-fs-ext" -version = "0.25.2" +name = "cache-padded" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e142bbbe9d5d6a2dd0387f887a000b41f4c82fb1226316dfb4cc8dbc3b1a29" -dependencies = [ - "cap-primitives 0.25.2", - "cap-std 0.25.2", - "io-lifetimes 0.7.3", - "windows-sys", -] +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] -name = "cap-primitives" -version = "0.24.4" +name = "cap-fs-ext" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" +checksum = "438ca7f5bb15c799ea146429e4f8b7bfd25ff1eb05319024549a7728de45800c" dependencies = [ - "ambient-authority", - "errno", - "fs-set-times 0.15.0", - "io-extras 0.13.2", - "io-lifetimes 0.5.3", - "ipnet", - "maybe-owned", - "rustix 0.33.7", - "winapi", - "winapi-util", - "winx 0.31.0", + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys", ] [[package]] name = "cap-primitives" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f22f4975282dd4f2330ee004f001c4e22f420da9fb474ea600e9af330f1e548" +checksum = "ba063daa90ed40882bb288ac4ecaa942d655d15cf74393d41d2267b5d7daf120" dependencies = [ "ambient-authority", - "errno", - "fs-set-times 0.17.1", - "io-extras 0.15.0", - "io-lifetimes 0.7.3", + "fs-set-times", + "io-extras", + "io-lifetimes", "ipnet", "maybe-owned", - "rustix 0.35.9", + "rustix", "winapi-util", "windows-sys", - "winx 0.33.0", + "winx", ] [[package]] name = "cap-rand" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef643f8defef7061c395bb3721b6a80d39c1baaa8ee2e42edf2917fa05584e7f" +checksum = "c720808e249f0ae846ec647fe48cef3cea67e4e5026cf869c041c278b7dcae45" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -324,40 +367,27 @@ dependencies = [ [[package]] name = "cap-std" -version = "0.24.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" -dependencies = [ - "cap-primitives 0.24.4", - "io-extras 0.13.2", - "io-lifetimes 0.5.3", - "ipnet", - "rustix 0.33.7", -] - -[[package]] -name = "cap-std" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95624bb0abba6b6ff6fad2e02a7d3945d093d064ac5a3477a308c29fbe3bfd49" +checksum = "0e3a603c9f3bd2181ed128ab3cd32fbde7cff76afc64a3576662701c4aee7e2b" dependencies = [ - "cap-primitives 0.25.2", - "io-extras 0.15.0", - "io-lifetimes 0.7.3", + "cap-primitives", + "io-extras", + "io-lifetimes", "ipnet", - "rustix 0.35.9", + "rustix", ] [[package]] name = "cap-time-ext" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a2d284862edf6e431e9ad4e109c02855157904cebaceae6f042b124a1a21e2" +checksum = "da76e64f3e46f8c8479e392a7fe3faa2e76b8c1cea4618bae445276fdec12082" dependencies = [ - "cap-primitives 0.25.2", + "cap-primitives", "once_cell", - "rustix 0.35.9", - "winx 0.33.0", + "rustix", + "winx", ] [[package]] @@ -383,6 +413,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -427,9 +463,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.19" +version = "3.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d43934757334b5c0519ff882e1ab9647ac0258b47c24c4f490d78e42697fd5" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", @@ -439,7 +475,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap 0.15.0", + "textwrap 0.15.1", ] [[package]] @@ -464,6 +500,65 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "cloud" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bindle", + "clap 3.2.22", + "cloud-openapi", + "colored", + "dialoguer 0.9.0", + "dirs 4.0.0", + "dunce", + "env_logger", + "futures", + "glob", + "itertools", + "log", + "mime_guess", + "path-absolutize", + "regex", + "reqwest", + "semver 1.0.14", + "serde", + "serde_json", + "sha2 0.9.9", + "spin-loader", + "spin-publish", + "tempfile", + "tokio", + "tokio-util 0.7.4", + "toml", + "tracing", + "uuid", +] + +[[package]] +name = "cloud-openapi" +version = "0.1.0" +source = "git+https://github.com/fermyon/cloud-openapi#350f67d7abf3cab9da83a44ba93ff5bfc2ad8b29" +dependencies = [ + "reqwest", + "serde", + "serde_derive", + "serde_json", + "url", + "uuid", +] + [[package]] name = "colored" version = "2.0.0" @@ -486,7 +581,7 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -501,20 +596,43 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + [[package]] name = "console" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", "terminal_size", "unicode-width", "winapi", ] +[[package]] +name = "copypasta" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7216b5c1e9ad3867252505995b02d01c6fa7e6db0d8abd42634352ef377777e" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -542,9 +660,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -715,26 +833,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -772,6 +888,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "csv" version = "1.1.6" @@ -817,6 +943,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dialoguer" version = "0.9.0" @@ -851,11 +1023,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -910,6 +1082,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -918,18 +1099,24 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dotenvy" -version = "0.15.3" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3db6fcad7c1fc4abdd99bf5276a4db30d6a819127903a709ed41e5ff016e84" +checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314" dependencies = [ "dirs 4.0.0", ] +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "dunce" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "ed25519" @@ -995,11 +1182,32 @@ dependencies = [ "syn", ] +[[package]] +name = "enumflags2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" dependencies = [ "atty", "humantime", @@ -1099,33 +1307,21 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] -[[package]] -name = "fs-set-times" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" -dependencies = [ - "io-lifetimes 0.5.3", - "rustix 0.33.7", - "winapi", -] - [[package]] name = "fs-set-times" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a267b6a9304912e018610d53fe07115d8b530b160e85db4d2d3a59f3ddde1aec" dependencies = [ - "io-lifetimes 0.7.3", - "rustix 0.35.9", + "io-lifetimes", + "rustix", "windows-sys", ] @@ -1193,6 +1389,21 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.24" @@ -1334,7 +1545,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tracing", ] @@ -1388,9 +1599,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897cd85af6387be149f55acf168e41be176a02de7872403aaab184afc2f327e6" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -1402,7 +1613,7 @@ source = "git+https://github.com/deislabs/hippo-cli?tag=v0.16.1#73315f55fadd2bb0 dependencies = [ "anyhow", "async-trait", - "clap 3.2.19", + "clap 3.2.22", "colored", "dialoguer 0.9.0", "dirs 4.0.0", @@ -1439,13 +1650,33 @@ dependencies = [ "uuid", ] +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -1456,7 +1687,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa 1.0.4", ] [[package]] @@ -1503,7 +1734,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", + "itoa 1.0.4", "pin-project-lite", "socket2", "tokio", @@ -1520,9 +1751,7 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "log", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", ] @@ -1542,14 +1771,13 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] @@ -1561,13 +1789,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] -name = "idna" -version = "0.2.3" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", "unicode-normalization", ] @@ -1591,32 +1824,16 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-extras" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" -dependencies = [ - "io-lifetimes 0.5.3", - "winapi", -] - [[package]] name = "io-extras" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5d8c2ab5becd8720e30fd25f8fa5500d8dc3fceadd8378f05859bd7b46fc49" dependencies = [ - "io-lifetimes 0.7.3", + "io-lifetimes", "windows-sys", ] -[[package]] -name = "io-lifetimes" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" - [[package]] name = "io-lifetimes" version = "0.7.3" @@ -1639,17 +1856,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d508111813f9af3afd2f92758f77e4ed2cc9371b642112c6a48d22eb73105c5" dependencies = [ - "hermit-abi 0.2.5", - "io-lifetimes 0.7.3", - "rustix 0.35.9", + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", "windows-sys", ] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1662,9 +1879,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "ittapi-rs" @@ -1675,20 +1892,40 @@ dependencies = [ "cc", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -1707,6 +1944,18 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "keyring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38fb8399ddcabfccb274577a8d90f0653e0b5b5977797c1c8834ad09839a10e5" +dependencies = [ + "byteorder", + "secret-service", + "security-framework", + "winapi", +] + [[package]] name = "kstring" version = "1.0.6" @@ -1716,6 +1965,12 @@ dependencies = [ "serde", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1730,9 +1985,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -1746,6 +2001,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libz-sys" version = "1.1.8" @@ -1758,12 +2023,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" - [[package]] name = "linux-raw-sys" version = "0.0.46" @@ -1830,9 +2089,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1865,6 +2124,15 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1874,12 +2142,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "maybe-owned" version = "0.3.4" @@ -1888,11 +2150,11 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "md-5" -version = "0.10.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274fd6bd98a3c75c9515d9393b063099f60f9b47f09ee20a34fd76287fd017f4" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -1907,7 +2169,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "480b5a5de855d11ff13195950bdc8b98b5e942ef47afc447f6615cdcc4e15d80" dependencies = [ - "rustix 0.35.9", + "rustix", +] + +[[package]] +name = "memmap2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +dependencies = [ + "libc", ] [[package]] @@ -1936,32 +2207,16 @@ dependencies = [ ] [[package]] -name = "mini-internal" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a63337614a1d280fdb2880599af563c99e9f388757f8d6515d785d85d14fb01" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "miniserde" -version = "0.1.27" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4313e4a66a442473e181963daf8c1e9def85c2d9fb0bb2ae59444260b28285" -dependencies = [ - "itoa 1.0.3", - "mini-internal", - "ryu", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -2002,6 +2257,85 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nb-connect" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bb540dc6ef51cfe1916ec038ce7a620daf3a111e2502d745197cd53d6bca15" +dependencies = [ + "libc", + "socket2", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.24.2" @@ -2026,6 +2360,40 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2037,6 +2405,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2047,6 +2424,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -2066,6 +2466,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num_threads" version = "0.1.6" @@ -2090,11 +2511,40 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "sha2 0.10.3", + "sha2 0.10.6", "thiserror", "url", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.28.4" @@ -2118,9 +2568,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" @@ -2136,9 +2586,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if", @@ -2168,9 +2618,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg", "cc", @@ -2185,14 +2635,51 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "ouroboros" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "outbound-http" +version = "0.2.0" +dependencies = [ + "anyhow", + "http", + "reqwest", + "spin-app", + "spin-core", + "tracing", + "url", + "wit-bindgen-wasmtime", +] + [[package]] name = "outbound-pg" version = "0.2.0" dependencies = [ "anyhow", - "postgres", - "spin-engine", - "spin-manifest", + "spin-core", + "tokio", + "tokio-postgres", "tracing", "wit-bindgen-wasmtime", ] @@ -2202,23 +2689,24 @@ name = "outbound-redis" version = "0.2.0" dependencies = [ "anyhow", - "owning_ref", "redis", - "spin-engine", - "spin-manifest", + "spin-core", + "tokio", "tracing", - "tracing-futures", "wit-bindgen-wasmtime", ] [[package]] -name = "owning_ref" -version = "0.4.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" -dependencies = [ - "stable_deref_trait", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" @@ -2309,15 +2797,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" +checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" dependencies = [ "thiserror", "ucd-trie", @@ -2325,9 +2813,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91" +checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" dependencies = [ "pest", "pest_generator", @@ -2335,9 +2823,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad" +checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" dependencies = [ "pest", "pest_meta", @@ -2348,13 +2836,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04" +checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" dependencies = [ "once_cell", "pest", - "sha-1", + "sha1 0.10.5", ] [[package]] @@ -2415,9 +2903,9 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "plotters" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", @@ -2442,17 +2930,17 @@ dependencies = [ ] [[package]] -name = "postgres" -version = "0.19.4" +name = "polling" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960c214283ef8f0027974c03e9014517ced5db12f021a9abb66185a5751fab0a" +checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", + "autocfg", + "cfg-if", + "libc", "log", - "tokio", - "tokio-postgres", + "wepoll-ffi", + "winapi", ] [[package]] @@ -2465,11 +2953,11 @@ dependencies = [ "byteorder", "bytes", "fallible-iterator", - "hmac", + "hmac 0.12.1", "md-5", "memchr", "rand 0.8.5", - "sha2 0.10.3", + "sha2 0.10.6", "stringprep", ] @@ -2490,6 +2978,26 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2522,9 +3030,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -2555,9 +3063,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f446d0a6efba22928558c4fb4ce0b3fd6c89b0061343e390bf01a703742b8125" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" dependencies = [ "cc", ] @@ -2573,6 +3081,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.21" @@ -2603,7 +3120,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2623,7 +3140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2637,9 +3154,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -2687,13 +3204,13 @@ dependencies = [ "bytes", "combine", "futures-util", - "itoa 1.0.3", + "itoa 1.0.4", "percent-encoding", "pin-project-lite", "ryu", - "sha1", + "sha1 0.6.1", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "url", ] @@ -2778,9 +3295,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64", "bytes", @@ -2795,11 +3312,11 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "mime_guess", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "rustls", @@ -2810,7 +3327,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tower-service", "url", "wasm-bindgen", @@ -2836,39 +3353,33 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "rustix" -version = "0.33.7" +name = "rpassword" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" +checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" dependencies = [ - "bitflags", - "errno", - "io-lifetimes 0.5.3", - "itoa 1.0.3", "libc", - "linux-raw-sys 0.0.42", - "once_cell", "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustix" -version = "0.35.9" +version = "0.35.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef" dependencies = [ "bitflags", "errno", - "io-lifetimes 0.7.3", - "itoa 1.0.3", + "io-lifetimes", + "itoa 1.0.4", "libc", - "linux-raw-sys 0.0.46", + "linux-raw-sys", "once_cell", "windows-sys", ] @@ -2885,18 +3396,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.1", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "0.3.0" @@ -2938,9 +3437,9 @@ dependencies = [ [[package]] name = "sanitize-filename" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf18934a12018228c5b55a6dae9df5d0641e3566b3630cb46cc55564068e7c2f" +checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c" dependencies = [ "lazy_static", "regex", @@ -2956,6 +3455,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2972,6 +3477,26 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secret-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1da5c423b8783185fd3fecd1c8796c267d2c089d894ce5a93c280a5d3f780a2" +dependencies = [ + "aes", + "block-modes", + "hkdf", + "lazy_static", + "num", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "zbus", + "zbus_macros", + "zvariant", + "zvariant_derive", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -3007,9 +3532,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] @@ -3025,9 +3550,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -3044,9 +3569,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -3059,7 +3584,7 @@ version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.4", "ryu", "serde", ] @@ -3073,6 +3598,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3080,29 +3616,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.3", + "itoa 1.0.4", "ryu", "serde", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.3", + "sha1_smol", ] [[package]] name = "sha1" -version = "0.6.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ - "sha1_smol", + "cfg-if", + "cpufeatures", + "digest 0.10.5", ] [[package]] @@ -3126,13 +3662,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -3185,9 +3721,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simple_asn1" @@ -3198,7 +3734,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.14", + "time 0.3.15", ] [[package]] @@ -3240,9 +3776,37 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.2", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] [[package]] name = "socket2" @@ -3260,6 +3824,39 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin-abi-conformance" +version = "0.5.0" +dependencies = [ + "anyhow", + "cap-std", + "clap 3.2.22", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_json", + "tempfile", + "toml", + "wasi-common", + "wasmtime", + "wasmtime-wasi", + "wit-bindgen-wasmtime", +] + +[[package]] +name = "spin-app" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "ouroboros", + "serde", + "serde_json", + "spin-core", + "thiserror", +] + [[package]] name = "spin-build" version = "0.2.0" @@ -3269,13 +3866,12 @@ dependencies = [ "path-absolutize", "spin-loader", "subprocess", - "tokio", "tracing", ] [[package]] name = "spin-cli" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "async-trait", @@ -3283,9 +3879,14 @@ dependencies = [ "bindle", "bytes", "cargo-target-dep", - "clap 3.2.19", + "chrono", + "clap 3.2.22", + "cloud", + "cloud-openapi", "comfy-table", + "copypasta", "ctrlc", + "dialoguer 0.10.2", "dirs 4.0.0", "dunce", "env_logger", @@ -3293,23 +3894,27 @@ dependencies = [ "hippo", "hippo-openapi", "hyper", + "keyring", "lazy_static", "nix 0.24.2", "openssl", + "outbound-http", "outbound-redis", "path-absolutize", + "rand 0.8.5", "regex", "reqwest", - "semver 1.0.13", + "rpassword", + "semver 1.0.14", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "spin-build", "spin-config", - "spin-engine", - "spin-http-engine", + "spin-http", "spin-loader", "spin-manifest", + "spin-plugins", "spin-publish", "spin-redis-engine", "spin-templates", @@ -3318,13 +3923,12 @@ dependencies = [ "tokio", "toml", "tracing", - "tracing-futures", "tracing-subscriber", "url", "uuid", "vergen", - "wasi-outbound-http", "wasmtime", + "webbrowser", "which", ] @@ -3333,71 +3937,58 @@ name = "spin-config" version = "0.2.0" dependencies = [ "anyhow", - "serde", + "async-trait", + "dotenvy", + "once_cell", + "spin-app", + "spin-core", "thiserror", + "tokio", "toml", "wit-bindgen-wasmtime", ] [[package]] -name = "spin-engine" -version = "0.2.0" +name = "spin-core" +version = "0.1.0" dependencies = [ "anyhow", - "bytes", - "cap-std 0.24.4", - "dirs 4.0.0", - "sanitize-filename", - "spin-config", - "spin-manifest", + "async-trait", "tempfile", "tokio", "tracing", - "tracing-futures", "wasi-cap-std-sync", "wasi-common", "wasmtime", "wasmtime-wasi", - "wit-bindgen-wasmtime", ] [[package]] -name = "spin-http-engine" +name = "spin-http" version = "0.2.0" dependencies = [ "anyhow", - "async-trait", - "atty", - "bytes", - "cap-std 0.24.4", - "clap 3.2.19", + "async-trait", + "clap 3.2.22", "criterion", "futures", "futures-util", "http", "hyper", - "hyper-rustls", "indexmap", - "miniserde", "num_cpus", "percent-encoding", "rustls-pemfile 0.3.0", "serde", - "spin-engine", - "spin-manifest", + "serde_json", + "spin-app", + "spin-core", "spin-testing", "spin-trigger", "tls-listener", "tokio", "tokio-rustls", "tracing", - "tracing-futures", - "tracing-subscriber", - "url", - "wasi-cap-std-sync", - "wasi-common", - "wasmtime", - "wasmtime-wasi", "wit-bindgen-wasmtime", ] @@ -3409,17 +4000,16 @@ dependencies = [ "async-trait", "bindle", "bytes", - "dirs 4.0.0", - "fs_extra", "futures", "glob", "itertools", "lazy_static", + "outbound-http", "path-absolutize", "regex", "reqwest", "serde", - "sha2 0.10.3", + "sha2 0.10.6", "spin-config", "spin-manifest", "tempfile", @@ -3427,10 +4017,7 @@ dependencies = [ "tokio-util 0.6.10", "toml", "tracing", - "tracing-futures", - "tracing-subscriber", "walkdir", - "wasi-outbound-http", ] [[package]] @@ -3455,11 +4042,31 @@ dependencies = [ "anyhow", "indexmap", "serde", - "spin-config", "thiserror", "url", ] +[[package]] +name = "spin-plugins" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "dirs 4.0.0", + "flate2", + "log", + "reqwest", + "semver 1.0.14", + "serde", + "serde_json", + "sha2 0.10.6", + "tar", + "tempfile", + "thiserror", + "tokio", + "url", +] + [[package]] name = "spin-publish" version = "0.2.0" @@ -3473,9 +4080,9 @@ dependencies = [ "mime_guess", "path-absolutize", "reqwest", - "semver 1.0.13", + "semver 1.0.14", "serde", - "sha2 0.10.3", + "sha2 0.10.6", "spin-loader", "tempfile", "tokio", @@ -3488,28 +4095,20 @@ version = "0.2.0" dependencies = [ "anyhow", "async-trait", - "env_logger", "futures", - "log", "redis", "serde", - "spin-engine", - "spin-manifest", + "spin-app", + "spin-core", "spin-testing", "spin-trigger", - "tokio", "tracing", - "tracing-futures", - "tracing-subscriber", - "wasi-common", - "wasmtime", - "wasmtime-wasi", "wit-bindgen-wasmtime", ] [[package]] name = "spin-sdk" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "bytes", @@ -3544,15 +4143,18 @@ dependencies = [ "path-absolutize", "pathdiff", "regex", - "semver 1.0.13", + "semver 1.0.14", "serde", - "sha2 0.10.3", + "sha2 0.10.6", "symlink", "tempfile", "tokio", "toml", "url", "walkdir", + "wasmtime", + "wasmtime-wasi", + "wit-bindgen-wasmtime", ] [[package]] @@ -3562,10 +4164,14 @@ dependencies = [ "anyhow", "http", "hyper", - "spin-engine", - "spin-http-engine", - "spin-manifest", + "serde", + "serde_json", + "spin-app", + "spin-core", + "spin-http", "spin-trigger", + "tokio", + "tracing-subscriber", ] [[package]] @@ -3573,21 +4179,11 @@ name = "spin-timer" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", "chrono", - "env_logger", - "futures", - "log", - "spin-engine", - "spin-manifest", - "spin-trigger", + "spin-core", "tokio", "tracing", - "tracing-futures", "tracing-subscriber", - "wasi-common", - "wasmtime", - "wasmtime-wasi", "wit-bindgen-wasmtime", ] @@ -3597,20 +4193,26 @@ version = "0.2.0" dependencies = [ "anyhow", "async-trait", - "clap 3.2.19", + "clap 3.2.22", "ctrlc", - "dotenvy", + "dirs 4.0.0", "futures", - "http", + "outbound-http", "outbound-pg", "outbound-redis", + "sanitize-filename", "serde", + "serde_json", + "spin-app", "spin-config", - "spin-engine", + "spin-core", "spin-loader", "spin-manifest", + "tempfile", + "tokio", + "toml", "tracing", - "wasi-outbound-http", + "url", "wasmtime", ] @@ -3620,6 +4222,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.2" @@ -3679,9 +4287,9 @@ checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -3709,11 +4317,22 @@ dependencies = [ "atty", "bitflags", "cap-fs-ext", - "cap-std 0.25.2", - "io-lifetimes 0.7.3", - "rustix 0.35.9", + "cap-std", + "io-lifetimes", + "rustix", "windows-sys", - "winx 0.33.0", + "winx", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", ] [[package]] @@ -3766,24 +4385,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -3812,11 +4431,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.4", "libc", "num_threads", "serde", @@ -3870,9 +4489,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.20.1" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -3880,7 +4499,6 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", @@ -3931,7 +4549,7 @@ dependencies = [ "postgres-types", "socket2", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -3947,9 +4565,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite", @@ -3987,9 +4605,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -4016,9 +4634,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -4029,9 +4647,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -4040,9 +4658,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -4071,12 +4689,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -4101,9 +4719,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "unicase" @@ -4122,36 +4740,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" @@ -4161,13 +4779,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", "serde", ] @@ -4206,7 +4823,7 @@ dependencies = [ "git2", "rustversion", "thiserror", - "time 0.3.14", + "time 0.3.15", ] [[package]] @@ -4215,6 +4832,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -4264,14 +4887,14 @@ dependencies = [ "async-trait", "cap-fs-ext", "cap-rand", - "cap-std 0.25.2", + "cap-std", "cap-time-ext", - "fs-set-times 0.17.1", - "io-extras 0.15.0", - "io-lifetimes 0.7.3", + "fs-set-times", + "io-extras", + "io-lifetimes", "is-terminal", "lazy_static", - "rustix 0.35.9", + "rustix", "system-interface", "tracing", "wasi-common", @@ -4287,9 +4910,9 @@ dependencies = [ "anyhow", "bitflags", "cap-rand", - "cap-std 0.25.2", - "io-extras 0.15.0", - "rustix 0.35.9", + "cap-std", + "io-extras", + "rustix", "thiserror", "tracing", "wiggle", @@ -4297,28 +4920,28 @@ dependencies = [ ] [[package]] -name = "wasi-outbound-http" -version = "0.2.0" +name = "wasi-tokio" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab325bba31ae9286b8ebdc18d32a43d6471312c9bc4e477240be444e00ec4f4" dependencies = [ "anyhow", - "bytes", - "futures", - "http", - "reqwest", - "spin-engine", - "spin-manifest", + "cap-std", + "io-extras", + "io-lifetimes", + "lazy_static", + "rustix", "tokio", - "tracing", - "tracing-futures", - "url", - "wit-bindgen-wasmtime", + "wasi-cap-std-sync", + "wasi-common", + "wiggle", ] [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4326,9 +4949,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", @@ -4341,9 +4964,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -4353,9 +4976,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4363,9 +4986,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -4376,15 +4999,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" +checksum = "c64ac98d5d61192cc45c701b7e4bd0b9aff91e2edfc7a088406cfe2288581e2c" dependencies = [ "leb128", ] @@ -4444,7 +5067,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.35.9", + "rustix", "serde", "sha2 0.9.9", "toml", @@ -4502,7 +5125,7 @@ checksum = "2f6aba0b317746e8213d1f36a4c51974e66e69c1f05bfc09ed29b4d4bda290eb" dependencies = [ "cc", "cfg-if", - "rustix 0.35.9", + "rustix", "windows-sys", ] @@ -4523,7 +5146,7 @@ dependencies = [ "object 0.28.4", "region", "rustc-demangle", - "rustix 0.35.9", + "rustix", "serde", "target-lexicon", "thiserror", @@ -4541,7 +5164,7 @@ checksum = "55e23273fddce8cab149a0743c46932bf4910268641397ed86b46854b089f38f" dependencies = [ "lazy_static", "object 0.28.4", - "rustix 0.35.9", + "rustix", ] [[package]] @@ -4563,7 +5186,7 @@ dependencies = [ "more-asserts", "rand 0.8.5", "region", - "rustix 0.35.9", + "rustix", "thiserror", "wasmtime-environ", "wasmtime-fiber", @@ -4592,6 +5215,7 @@ dependencies = [ "anyhow", "wasi-cap-std-sync", "wasi-common", + "wasi-tokio", "wasmtime", "wiggle", ] @@ -4607,9 +5231,9 @@ dependencies = [ [[package]] name = "wast" -version = "46.0.0" +version = "47.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" +checksum = "02b98502f3978adea49551e801a6687678e6015317d7d9470a67fe813393f2a8" dependencies = [ "leb128", "memchr", @@ -4619,23 +5243,110 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.48" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" +dependencies = [ + "wast 47.0.1", +] + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.24.2", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.2", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" dependencies = [ - "wast 46.0.0", + "dlib", + "lazy_static", + "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b" +dependencies = [ + "jni", + "ndk-glue", + "url", + "web-sys", + "widestring", + "winapi", +] + [[package]] name = "webpki" version = "0.22.0" @@ -4648,13 +5359,22 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" dependencies = [ "webpki", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "which" version = "4.3.0" @@ -4666,6 +5386,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + [[package]] name = "wiggle" version = "0.39.1" @@ -4679,6 +5405,7 @@ dependencies = [ "tracing", "wasmtime", "wiggle-macro", + "witx", ] [[package]] @@ -4791,17 +5518,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "winx" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" -dependencies = [ - "bitflags", - "io-lifetimes 0.5.3", - "winapi", -] - [[package]] name = "winx" version = "0.33.0" @@ -4809,7 +5525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7b01e010390eb263a4518c8cebf86cb67469d1511c00b749a47b64c39e8054d" dependencies = [ "bitflags", - "io-lifetimes 0.7.3", + "io-lifetimes", "windows-sys", ] @@ -4878,6 +5594,7 @@ version = "0.2.0" source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" dependencies = [ "anyhow", + "async-trait", "bitflags", "thiserror", "wasmtime", @@ -4919,6 +5636,15 @@ dependencies = [ "wast 35.0.2", ] +[[package]] +name = "x11-clipboard" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7468a5768fea473e6c8c0d4b60d6d7001a64acceaac267207ca0281e1337e8" +dependencies = [ + "xcb", +] + [[package]] name = "xattr" version = "0.2.3" @@ -4928,6 +5654,67 @@ dependencies = [ "libc", ] +[[package]] +name = "xcb" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9" +dependencies = [ + "bitflags", + "libc", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zbus" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbeb2291cd7267a94489b71376eda33496c1b9881adf6b36f26cc2779f3fc49" +dependencies = [ + "async-io", + "byteorder", + "derivative", + "enumflags2", + "fastrand", + "futures", + "nb-connect", + "nix 0.22.3", + "once_cell", + "polling", + "scoped-tls", + "serde", + "serde_repr", + "zbus_macros", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa3959a7847cf95e3d51e312856617c5b1b77191176c65a79a5f14d778bbe0a6" +dependencies = [ + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.3.0" @@ -4977,3 +5764,29 @@ dependencies = [ "cc", "libc", ] + +[[package]] +name = "zvariant" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c7b55f2074489b7e8e07d2d0a6ee6b4f233867a653c664d8020ba53692525" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca5e22593eb4212382d60d26350065bf2a02c34b85bc850474a74b589a3de9" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index ae1d9c0f3d..ef39a143da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spin-cli" -version = "0.5.0" +version = "0.6.0" edition = "2021" authors = [ "Fermyon Engineering " ] @@ -8,33 +8,42 @@ authors = [ "Fermyon Engineering " ] anyhow = "1.0" async-trait = "0.1" atty = "0.2" -bindle = { git = "https://github.com/fermyon/bindle", tag = "v0.8.1", default-features = false, features = ["client"] } +bindle = { git = "https://github.com/fermyon/bindle", tag = "v0.8.2", default-features = false, features = ["client"] } bytes = "1.1" +chrono = "0.4" clap = { version = "3.1.15", features = ["derive", "env"] } +cloud = { path = "crates/cloud" } +cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi" } comfy-table = "5.0" +copypasta = "0.8.1" ctrlc = { version = "3.2", features = ["termination"] } +dialoguer = "0.10" dirs = "4.0" dunce = "1.0" env_logger = "0.9" futures = "0.3" hippo-openapi = "0.10" hippo = { git = "https://github.com/deislabs/hippo-cli", tag = "v0.16.1" } +keyring = "1" lazy_static = "1.4.0" nix = { version = "0.24", features = ["signal"] } +outbound-http = { path = "crates/outbound-http" } outbound-redis = { path = "crates/outbound-redis" } path-absolutize = "3.0.11" +rand = "0.8" regex = "1.5.5" reqwest = { version = "0.11", features = ["stream"] } +rpassword = "7.0" semver = "1.0" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0.82" sha2 = "0.10.2" spin-build = { path = "crates/build" } spin-config = { path = "crates/config" } -spin-engine = { path = "crates/engine" } -spin-http-engine = { path = "crates/http" } +spin-http = { path = "crates/http" } spin-loader = { path = "crates/loader" } spin-manifest = { path = "crates/manifest" } +spin-plugins = { path = "crates/plugins" } spin-publish = { path = "crates/publish" } spin-redis-engine = { path = "crates/redis" } spin-templates = { path = "crates/templates" } @@ -43,12 +52,11 @@ tempfile = "3.3.0" tokio = { version = "1.11", features = [ "full" ] } toml = "0.5" tracing = { version = "0.1", features = [ "log" ] } -tracing-futures = "0.2" tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] } url = "2.2.2" uuid = "^1.0" -wasi-outbound-http = { path = "crates/outbound-http" } wasmtime = "0.39.1" +webbrowser = "0.7.1" [target.'cfg(target_os = "linux")'.dependencies] # This needs to be an explicit dependency to enable @@ -70,14 +78,17 @@ e2e-tests = [] [workspace] members = [ + "crates/abi-conformance", + "crates/app", "crates/build", "crates/config", - "crates/engine", + "crates/core", "crates/http", "crates/loader", "crates/manifest", "crates/outbound-http", "crates/outbound-redis", + "crates/plugins", "crates/redis", "crates/templates", "crates/testing", diff --git a/Makefile b/Makefile index a8d02d84e5..e6d2a2460b 100644 --- a/Makefile +++ b/Makefile @@ -22,16 +22,16 @@ check-rust-examples: .PHONY: test-unit test-unit: - RUST_LOG=$(LOG_LEVEL) cargo test --all --no-fail-fast -- --skip integration_tests --nocapture --include-ignored + RUST_LOG=$(LOG_LEVEL) cargo test --all --no-fail-fast -- --skip integration_tests --nocapture .PHONY: test-integration test-integration: - RUST_LOG=$(LOG_LEVEL) cargo test --test integration --no-fail-fast -- --nocapture --include-ignored + RUST_LOG=$(LOG_LEVEL) cargo test --test integration --no-fail-fast -- --nocapture .PHONY: test-e2e test-e2e: RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features e2e-tests --no-fail-fast -- integration_tests::test_dependencies --nocapture - RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features e2e-tests --no-fail-fast -- --skip integration_tests::test_dependencies --nocapture --include-ignored + RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features e2e-tests --no-fail-fast -- --skip integration_tests::test_dependencies --nocapture .PHONY: test-sdk-go test-sdk-go: @@ -42,14 +42,4 @@ test-sdk-go: tls: ${CERT_NAME}.crt.pem $(CERT_NAME).crt.pem: - openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem - -.PHONY: doc -doc: - DATE=$(shell date --utc +%Y-%m-%dT%TZ) - echo "title = \"\"\ntemplate = \"main\"\ndate = \"`date --utc +%Y-%m-%dT%TZ`\"\n" > docs/content/$(SPIN_DOC_NAME) - echo "[extra]\nurl = \"https://github.com/fermyon/spin/blob/main/docs/content/$(SPIN_DOC_NAME)\"\n\n---\n" >> docs/content/$(SPIN_DOC_NAME) - -.PHONY: check-content -check-content: - cd docs && bart check content/* && bart check content/**/* + openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem \ No newline at end of file diff --git a/README.md b/README.md index 4180bc7dc7..d864e157bb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-

Spin

- +

Fermyon Spin

+

Spin is a framework for building, deploying, and running fast, secure, and composable cloud microservices with WebAssembly.

build status Discord @@ -24,7 +24,7 @@ applications and how to get started. ## Getting started -See the [quickstart document](https://spin.fermyon.dev/quickstart/) for a detailed +See the [quickstart document](https://developer.fermyon.com/spin/quickstart/) for a detailed guide on configuring Spin and writing your first Spin application, but in short: ``` @@ -33,12 +33,12 @@ $ tar xfv spin--.tar.gz $ ./spin --help ``` -> Alternatively, you could [build Spin from source](https://spin.fermyon.dev/contributing/). +> Alternatively, you could [build Spin from source](https://developer.fermyon.com/spin/contributing/). -After you follow the [quickstart document](https://spin.fermyon.dev/quickstart/), +After you follow the [quickstart document](https://developer.fermyon.com/spin/quickstart/), you can follow the -[Rust](https://spin.fermyon.dev/rust-components/) or [Go](https://spin.fermyon.dev/go-components/) -language guides, and the [guide on configuring Spin applications](https://spin.fermyon.dev/configuration/). +[Rust](https://developer.fermyon.com/spin/rust-components/) or [Go](https://developer.fermyon.com/spin/go-components/) +language guides, and the [guide on configuring Spin applications](https://developer.fermyon.com/spin/configuration/). After you build your application, run it using Spin: diff --git a/build.rs b/build.rs index b65cdc7a76..58f52f5f35 100644 --- a/build.rs +++ b/build.rs @@ -39,6 +39,7 @@ error: the `wasm32-wasi` target is not installed std::fs::create_dir_all("target/test-programs").unwrap(); + build_wasm_test_program("core-wasi-test.wasm", "crates/core/tests/core-wasi-test"); build_wasm_test_program("rust-http-test.wasm", "crates/http/tests/rust-http-test"); build_wasm_test_program("redis-rust.wasm", "crates/redis/tests/rust"); build_wasm_test_program("wagi-test.wasm", "crates/http/tests/wagi-test"); @@ -129,6 +130,6 @@ fn get_os_process() -> String { if cfg!(target_os = "windows") { String::from("powershell.exe") } else { - String::from("/bin/bash") + String::from("bash") } } diff --git a/crates/abi-conformance/Cargo.toml b/crates/abi-conformance/Cargo.toml new file mode 100644 index 0000000000..9d97b39fdc --- /dev/null +++ b/crates/abi-conformance/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spin-abi-conformance" +version = "0.5.0" +edition = "2021" +authors = [ "Fermyon Engineering " ] + +[dependencies] +anyhow = "1.0.44" +clap = { version = "3.1.15", features = ["derive", "env"] } +serde = { version = "1.0", features = [ "derive" ] } +serde_json = "1.0.82" +wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" } +wasmtime = "0.39.1" +wasmtime-wasi = "0.39.1" +wasi-common = "0.39.1" +rand = "0.8.5" +rand_chacha = "0.3.1" +rand_core = "0.6.3" +tempfile = "3.3.0" +cap-std = "0.25.2" +toml = "0.5.9" diff --git a/crates/abi-conformance/src/bin/spin-abi-conformance.rs b/crates/abi-conformance/src/bin/spin-abi-conformance.rs new file mode 100644 index 0000000000..1bf1eccd88 --- /dev/null +++ b/crates/abi-conformance/src/bin/spin-abi-conformance.rs @@ -0,0 +1,58 @@ +use anyhow::Result; +use clap::Parser; +use std::{ + fs::{self, File}, + io::{self, Read, Write}, + path::PathBuf, +}; +use wasmtime::{Config, Engine, Module}; + +#[derive(Parser)] +#[clap(author, version, about)] +pub struct Options { + /// Name of Wasm file to test (or stdin if not specified) + #[clap(short, long)] + pub input: Option, + + /// Name of JSON file to write report to (or stdout if not specified) + #[clap(short, long)] + pub output: Option, + + /// Name of TOML configuration file to use + #[clap(short, long)] + pub config: Option, +} + +fn main() -> Result<()> { + let options = &Options::parse(); + + let engine = &Engine::new(&Config::new())?; + + let module = &if let Some(input) = &options.input { + Module::from_file(engine, input) + } else { + Module::new(engine, &{ + let mut buffer = Vec::new(); + io::stdin().read_to_end(&mut buffer)?; + buffer + }) + }?; + + let config = if let Some(config) = &options.config { + toml::from_str(&fs::read_to_string(config)?)? + } else { + spin_abi_conformance::Config::default() + }; + + let report = &spin_abi_conformance::test(module, config)?; + + let writer = if let Some(output) = &options.output { + Box::new(File::create(output)?) as Box + } else { + Box::new(io::stdout().lock()) + }; + + serde_json::to_writer_pretty(writer, report)?; + + Ok(()) +} diff --git a/crates/abi-conformance/src/lib.rs b/crates/abi-conformance/src/lib.rs new file mode 100644 index 0000000000..1cbb5fc61e --- /dev/null +++ b/crates/abi-conformance/src/lib.rs @@ -0,0 +1,239 @@ +//! Spin ABI Conformance Test Suite +//! +//! This crate provides a suite of tests to check a given SDK or language integration's implementation of Spin +//! functions. It is intended for use by language integrators and SDK authors to verify that their integrations +//! and SDKs work correctly with the Spin ABIs. It is not intended for Spin _application_ development, since it +//! requires a module written specifically to behave as expected by this suite, whereas a given application will +//! have its own expected behaviors which can only be verified by tests specific to that application. +//! +//! The suite may be run via the [`test()`] function, which accepts a [`wasmtime::Module`] and a [`Config`] and +//! returns a [`Report`] which details which tests succeeded and which failed. The definition of success in this +//! context depends on whether the test is for a function implemented by the guest (e.g. triggers) or by the host +//! (e.g. outbound requests). +//! +//! - For a guest-implemented function, the host will call the function and assert the result matches what is +//! expected (see [`Report::http_trigger`] for an example). +//! +//! - For a host-implemented function, the host will call a guest-implemented function according to the specified +//! [`InvocationStyle`] with a set of arguments indicating which host function to call and with what arguments. +//! The host then asserts that host function was indeed called with the expected arguments (see +//! [`Report::outbound_http`] for an example). + +use anyhow::{Context as _, Result}; +use outbound_http::OutboundHttp; +use outbound_pg::OutboundPg; +use outbound_redis::OutboundRedis; +use serde::{Deserialize, Serialize}; +use spin_config::SpinConfig; +use spin_http::{Method, Request, SpinHttp, SpinHttpData}; +use spin_redis::SpinRedisData; +use std::str; +use wasi_common::{pipe::WritePipe, WasiCtx}; +use wasmtime::{InstancePre, Linker, Module, Store}; +use wasmtime_wasi::WasiCtxBuilder; + +pub use outbound_pg::PgReport; +pub use outbound_redis::RedisReport; +pub use wasi::WasiReport; + +mod outbound_http; +mod outbound_pg; +mod outbound_redis; +mod spin_config; +mod spin_http; +mod spin_redis; +mod wasi; + +/// The invocation style to use when the host asks the guest to call a host-implemented function +#[derive(Copy, Clone, Default, Deserialize)] +pub enum InvocationStyle { + /// The host should call into the guest using WASI's `_start` function, passing arguments as CLI parameters. + /// + /// This is the default if no value is specified. + #[default] + Command, + + /// The host should call into the guest using spin-http.wit's `handle-http-request` function, passing arguments + /// via the request body as a JSON array of strings. + HttpTrigger, +} + +/// Configuration options for the [`test()`] function +#[derive(Default, Deserialize)] +pub struct Config { + /// The invocation style to use when the host asks the guest to call a host-implemented function + #[serde(default)] + pub invocation_style: InvocationStyle, +} + +/// Report of which tests succeeded or failed +/// +/// These results fall into either of two categories: +/// +/// - Guest-implemented exports which behave as prescribed by the test (e.g. `http_trigger` and `redis_trigger`) +/// +/// - Host-implemented imports which are called by the guest with the arguments specified by the host +/// (e.g. `outbound_http`) +#[derive(Serialize)] +pub struct Report { + /// Result of the Spin HTTP trigger test + /// + /// The guest module should expect a call to `handle-http-request` with a POST request to "/foo" containing a + /// single header "foo: bar" and a UTF-8 string body "Hello, SpinHttp!" and return a 200 OK response that + /// includes a single header "lorem: ipsum" and a UTF-8 string body "dolor sit amet". + pub http_trigger: Result<(), String>, + + /// Result of the Spin Redis trigger test + /// + /// The guest module should expect a call to `handle-redis-message` with the text "Hello, SpinRedis!" and + /// return `ok(unit)` as the result. + pub redis_trigger: Result<(), String>, + + /// Result of the Spin config test + /// + /// The guest module should expect a call according to [`InvocationStyle`] with \["config", "foo"\] as + /// arguments. The module should call the host-implemented `spin-config::get-config` function with "foo" as + /// the argument and expect `ok("bar")` as the result. The host will assert that said function is called + /// exactly once with the expected argument. + pub config: Result<(), String>, + + /// Result of the Spin outbound HTTP test + /// + /// The guest module should expect a call according to [`InvocationStyle`] with \["outbound-http", + /// "http://127.0.0.1/test"\] as arguments. The module should call the host-implemented + /// `wasi-outbound-http::request` function with a GET request for the URL "http://127.0.0.1/test" with no + /// headers, params, or body, and expect `ok({ status: 200, headers: none, body: some("Jabberwocky"))` as the + /// result. The host will assert that said function is called exactly once with the specified argument. + pub outbound_http: Result<(), String>, + + /// Results of the Spin outbound Redis tests + /// + /// See [`RedisReport`] for details. + pub outbound_redis: RedisReport, + + /// Results of the Spin outbound PostgreSQL tests + /// + /// See [`PgReport`] for details. + pub outbound_pg: PgReport, + + /// Results of the WASI tests + /// + /// See [`WasiReport`] for details. + pub wasi: WasiReport, +} + +/// Run a test for each Spin-related function the specified `module` imports or exports, returning the results as a +/// [`Report`]. +/// +/// See the fields of [`Report`] and the structs from which it is composed for descriptions of each test. +pub fn test(module: &Module, config: Config) -> Result { + let mut store = Store::new( + module.engine(), + Context { + config, + wasi: WasiCtxBuilder::new().arg("")?.build(), + outbound_http: OutboundHttp::default(), + outbound_redis: OutboundRedis::default(), + outbound_pg: OutboundPg::default(), + spin_http: SpinHttpData {}, + spin_redis: SpinRedisData {}, + spin_config: SpinConfig::default(), + }, + ); + + let mut linker = Linker::::new(module.engine()); + wasmtime_wasi::add_to_linker(&mut linker, |context| &mut context.wasi)?; + outbound_http::add_to_linker(&mut linker, |context| &mut context.outbound_http)?; + outbound_redis::add_to_linker(&mut linker, |context| &mut context.outbound_redis)?; + outbound_pg::add_to_linker(&mut linker, |context| &mut context.outbound_pg)?; + spin_config::add_to_linker(&mut linker, |context| &mut context.spin_config)?; + + let pre = linker.instantiate_pre(&mut store, module)?; + + Ok(Report { + http_trigger: spin_http::test(&mut store, &pre), + + redis_trigger: spin_redis::test(&mut store, &pre), + + config: spin_config::test(&mut store, &pre), + + outbound_http: outbound_http::test(&mut store, &pre), + + outbound_redis: outbound_redis::test(&mut store, &pre)?, + + outbound_pg: outbound_pg::test(&mut store, &pre)?, + + wasi: wasi::test(&mut store, &pre)?, + }) +} + +struct Context { + config: Config, + wasi: WasiCtx, + outbound_http: OutboundHttp, + outbound_redis: OutboundRedis, + outbound_pg: OutboundPg, + spin_http: SpinHttpData, + spin_redis: SpinRedisData, + spin_config: SpinConfig, +} + +fn run(fun: impl FnOnce() -> Result<()>) -> Result<(), String> { + fun().map_err(|e| format!("{e:?}")) +} + +fn run_command( + store: &mut Store, + pre: &InstancePre, + arguments: &[&str], + fun: impl FnOnce(&mut Store) -> Result<()>, +) -> Result<(), String> { + run(|| { + let stderr = WritePipe::new_in_memory(); + store.data_mut().wasi.set_stderr(Box::new(stderr.clone())); + + let instance = &pre.instantiate(&mut *store)?; + + let result = match store.data().config.invocation_style { + InvocationStyle::HttpTrigger => { + let handle = + SpinHttp::new(&mut *store, instance, |context| &mut context.spin_http)?; + + handle + .handle_http_request( + &mut *store, + Request { + method: Method::Post, + uri: "/", + headers: &[], + params: &[], + body: Some(&serde_json::to_vec(arguments)?), + }, + ) + .map(drop) // Ignore the response and make this a `Result<(), Trap>` to match the `_start` case + // below + } + + InvocationStyle::Command => { + for argument in arguments { + store.data_mut().wasi.push_arg(argument)?; + } + + instance + .get_typed_func::<(), (), _>(&mut *store, "_start")? + .call(&mut *store, ()) + } + }; + + // Reset `Context::wasi` so the next test has a clean slate and also to ensure there are no more references + // to the `stderr` pipe, ensuring `try_into_inner` succeeds below. This is also needed in case the caller + // attached its own pipes for e.g. stdin and/or stdout and expects exclusive ownership once we return. + store.data_mut().wasi = WasiCtxBuilder::new().arg("")?.build(); + + result.with_context(|| { + String::from_utf8_lossy(&stderr.try_into_inner().unwrap().into_inner()).into_owned() + })?; + + fun(store) + }) +} diff --git a/crates/abi-conformance/src/outbound_http.rs b/crates/abi-conformance/src/outbound_http.rs new file mode 100644 index 0000000000..54b3e64bcb --- /dev/null +++ b/crates/abi-conformance/src/outbound_http.rs @@ -0,0 +1,49 @@ +use super::Context; +use anyhow::ensure; +use std::collections::HashMap; +use wasi_outbound_http::{HttpError, Request, Response, WasiOutboundHttp}; +use wasmtime::{InstancePre, Store}; + +pub use wasi_outbound_http::add_to_linker; + +wit_bindgen_wasmtime::export!("../../wit/ephemeral/wasi-outbound-http.wit"); + +#[derive(Default)] +pub(super) struct OutboundHttp { + map: HashMap, +} + +impl WasiOutboundHttp for OutboundHttp { + fn request(&mut self, req: Request) -> Result { + self.map + .remove(req.uri) + .map(|body| Response { + status: 200, + headers: None, + body: Some(body.into_bytes()), + }) + .ok_or(HttpError::InvalidUrl) + } +} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + store + .data_mut() + .outbound_http + .map + .insert("http://127.0.0.1/test".into(), "Jabberwocky".into()); + + super::run_command( + store, + pre, + &["outbound-http", "http://127.0.0.1/test"], + |store| { + ensure!( + store.data().outbound_http.map.is_empty(), + "expected module to call `wasi-outbound-http::request` exactly once" + ); + + Ok(()) + }, + ) +} diff --git a/crates/abi-conformance/src/outbound_pg.rs b/crates/abi-conformance/src/outbound_pg.rs new file mode 100644 index 0000000000..ad2a0f947a --- /dev/null +++ b/crates/abi-conformance/src/outbound_pg.rs @@ -0,0 +1,160 @@ +use super::Context; +use anyhow::{ensure, Result}; +use outbound_pg::{Column, DbDataType, DbValue, ParameterValue, PgError, RowSet}; +use serde::Serialize; +use std::{collections::HashMap, iter}; +use wasmtime::{InstancePre, Store}; + +pub(super) use outbound_pg::add_to_linker; + +/// Report of which outbound PostgreSQL functions a module successfully used, if any +#[derive(Serialize)] +pub struct PgReport { + /// Result of the PostgreSQL statement execution test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["outbound-pg-execute", + /// "127.0.0.1", "INSERT INTO foo (x) VALUES ($1)", "int8:42"\] as arguments. The module should call the + /// host-implemented `outbound-pg::execute` function with the arguments \["127.0.0.1", "INSERT INTO foo (x) + /// VALUES ($1)", `\[int8(42)\]`\] and expect `ok(1)` as the result. The host will assert that said function + /// is called exactly once with the specified arguments. + pub execute: Result<(), String>, + + /// Result of the PostgreSQL query execution test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["outbound-pg-query", + /// "127.0.0.1", "SELECT x FROM foo"\] as arguments. The module should call the host-implemented + /// `outbound-pg::execute` function with the arguments \["127.0.0.1", "SELECT x FROM foo"\] and expect `ok({ + /// columns: \[ { name: "x", data_type: int8 } \], rows: \[ \[ int8(42) \] \]})` as the result. The host will + /// assert that said function is called exactly once with the specified arguments. + pub query: Result<(), String>, +} + +wit_bindgen_wasmtime::export!("../../wit/ephemeral/outbound-pg.wit"); + +#[derive(Default)] +pub(super) struct OutboundPg { + execute_map: HashMap<(String, String, String), u64>, + query_map: HashMap<(String, String, String), RowSet>, +} + +impl outbound_pg::OutboundPg for OutboundPg { + fn execute( + &mut self, + address: &str, + statement: &str, + params: Vec>, + ) -> Result { + self.execute_map + .remove(&( + address.to_owned(), + statement.to_owned(), + format!("{params:?}"), + )) + .ok_or_else(|| { + PgError::OtherError(format!( + "expected {:?}, got {:?}", + self.execute_map.keys(), + iter::once(&( + address.to_owned(), + statement.to_owned(), + format!("{params:?}") + )) + )) + }) + } + + fn query( + &mut self, + address: &str, + statement: &str, + params: Vec>, + ) -> Result { + self.query_map + .remove(&( + address.to_owned(), + statement.to_owned(), + format!("{params:?}"), + )) + .ok_or_else(|| { + PgError::OtherError(format!( + "expected {:?}, got {:?}", + self.query_map.keys(), + iter::once(&( + address.to_owned(), + statement.to_owned(), + format!("{params:?}") + )) + )) + }) + } +} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result { + Ok(PgReport { + execute: test_execute(store, pre), + query: test_query(store, pre), + }) +} + +fn test_execute(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + store.data_mut().outbound_pg.execute_map.insert( + ( + "127.0.0.1".into(), + "INSERT INTO foo (x) VALUES ($1)".into(), + format!("{:?}", vec![ParameterValue::Int8(42)]), + ), + 1, + ); + + super::run_command( + store, + pre, + &[ + "outbound-pg-execute", + "127.0.0.1", + "INSERT INTO foo (x) VALUES ($1)", + "int8:42", + ], + |store| { + ensure!( + store.data().outbound_pg.execute_map.is_empty(), + "expected module to call `outbound-pg::execute` exactly once" + ); + + Ok(()) + }, + ) +} + +fn test_query(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + let row_set = RowSet { + columns: vec![Column { + name: "x".into(), + data_type: DbDataType::Int8, + }], + rows: vec![vec![DbValue::Int8(42)]], + }; + + store.data_mut().outbound_pg.query_map.insert( + ( + "127.0.0.1".into(), + "SELECT x FROM foo".into(), + format!("{:?}", Vec::<()>::new()), + ), + row_set, + ); + + super::run_command( + store, + pre, + &["outbound-pg-query", "127.0.0.1", "SELECT x FROM foo"], + |store| { + ensure!( + store.data().outbound_pg.query_map.is_empty(), + "expected module to call `outbound-pg::query` exactly once" + ); + + Ok(()) + }, + ) +} diff --git a/crates/abi-conformance/src/outbound_redis.rs b/crates/abi-conformance/src/outbound_redis.rs new file mode 100644 index 0000000000..4aa117f268 --- /dev/null +++ b/crates/abi-conformance/src/outbound_redis.rs @@ -0,0 +1,187 @@ +use super::Context; +use anyhow::{ensure, Result}; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; +use wasmtime::{InstancePre, Store}; + +pub(super) use outbound_redis::add_to_linker; + +/// Report of which outbound Redis tests succeeded or failed +#[derive(Serialize)] +pub struct RedisReport { + /// Result of the Redis `PUBLISH` test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with + /// \["outbound-redis-publish", "127.0.0.1", "foo", "bar"\] as arguments. The module should call the + /// host-implemented `outbound-redis::publish` function with the arguments \["127.0.0.1", "foo", "bar"\] and + /// expect `ok(unit)` as the result. The host will assert that said function is called exactly once with the + /// specified arguments. + pub publish: Result<(), String>, + + /// Result of the Redis `SET` test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["outbound-redis-set", + /// "127.0.0.1", "foo", "bar"\] as arguments. The module should call the host-implemented + /// `outbound-redis::set` function with the arguments \["127.0.0.1", "foo", "bar"\] and expect `ok(unit)` as + /// the result. The host will assert that said function is called exactly once with the specified arguments. + pub set: Result<(), String>, + + /// Result of the Redis `GET` test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["outbound-redis-get", + /// "127.0.0.1", "foo"\] as arguments. The module should call the host-implemented `outbound-redis::get` + /// function with the arguments \["127.0.0.1", "foo"\] and expect `ok("bar")` (UTF-8-encoded) as the result. + /// The host will assert that said function is called exactly once with the specified arguments. + pub get: Result<(), String>, + + /// Result of the Redis `INCR` test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["outbound-redis-incr", + /// "127.0.0.1", "foo"\] as arguments. The module should call the host-implemented `outbound-redis::incr` + /// function with the arguments \["127.0.0.1", "foo"\] and expect `ok(42)` as the result. The host will assert + /// that said function is called exactly once with the specified arguments. + pub incr: Result<(), String>, +} + +wit_bindgen_wasmtime::export!("../../wit/ephemeral/outbound-redis.wit"); + +#[derive(Default)] +pub(super) struct OutboundRedis { + publish_set: HashSet<(String, String, Vec)>, + set_set: HashSet<(String, String, Vec)>, + get_map: HashMap<(String, String), Vec>, + incr_map: HashMap<(String, String), i64>, +} + +impl outbound_redis::OutboundRedis for OutboundRedis { + fn publish( + &mut self, + address: &str, + channel: &str, + payload: &[u8], + ) -> Result<(), outbound_redis::Error> { + if self + .publish_set + .remove(&(address.to_owned(), channel.to_owned(), payload.to_vec())) + { + Ok(()) + } else { + Err(outbound_redis::Error::Error) + } + } + + fn get(&mut self, address: &str, key: &str) -> Result, outbound_redis::Error> { + self.get_map + .remove(&(address.to_owned(), key.to_owned())) + .ok_or(outbound_redis::Error::Error) + } + + fn set(&mut self, address: &str, key: &str, value: &[u8]) -> Result<(), outbound_redis::Error> { + if self + .set_set + .remove(&(address.to_owned(), key.to_owned(), value.to_vec())) + { + Ok(()) + } else { + Err(outbound_redis::Error::Error) + } + } + + fn incr(&mut self, address: &str, key: &str) -> Result { + self.incr_map + .remove(&(address.to_owned(), key.to_owned())) + .map(|value| value + 1) + .ok_or(outbound_redis::Error::Error) + } +} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result { + Ok(RedisReport { + publish: { + store.data_mut().outbound_redis.publish_set.insert(( + "127.0.0.1".into(), + "foo".into(), + "bar".as_bytes().to_vec(), + )); + + super::run_command( + store, + pre, + &["outbound-redis-publish", "127.0.0.1", "foo", "bar"], + |store| { + ensure!( + store.data().outbound_redis.publish_set.is_empty(), + "expected module to call `outbound-redis::publish` exactly once" + ); + + Ok(()) + }, + ) + }, + + set: { + store.data_mut().outbound_redis.set_set.insert(( + "127.0.0.1".into(), + "foo".into(), + "bar".as_bytes().to_vec(), + )); + + super::run_command( + store, + pre, + &["outbound-redis-set", "127.0.0.1", "foo", "bar"], + |store| { + ensure!( + store.data().outbound_redis.set_set.is_empty(), + "expected module to call `outbound-redis::set` exactly once" + ); + + Ok(()) + }, + ) + }, + + get: { + store.data_mut().outbound_redis.get_map.insert( + ("127.0.0.1".into(), "foo".into()), + "bar".as_bytes().to_vec(), + ); + + super::run_command( + store, + pre, + &["outbound-redis-get", "127.0.0.1", "foo"], + |store| { + ensure!( + store.data().outbound_redis.get_map.is_empty(), + "expected module to call `outbound-redis::get` exactly once" + ); + + Ok(()) + }, + ) + }, + + incr: { + store + .data_mut() + .outbound_redis + .incr_map + .insert(("127.0.0.1".into(), "foo".into()), 41); + + super::run_command( + store, + pre, + &["outbound-redis-incr", "127.0.0.1", "foo"], + |store| { + ensure!( + store.data().outbound_redis.incr_map.is_empty(), + "expected module to call `outbound-redis::incr` exactly once" + ); + + Ok(()) + }, + ) + }, + }) +} diff --git a/crates/abi-conformance/src/spin_config.rs b/crates/abi-conformance/src/spin_config.rs new file mode 100644 index 0000000000..c3dcbefdfd --- /dev/null +++ b/crates/abi-conformance/src/spin_config.rs @@ -0,0 +1,53 @@ +use super::Context; +use anyhow::ensure; +use std::{collections::HashMap, error, fmt}; +use wasmtime::{InstancePre, Store}; + +pub use spin_config::add_to_linker; + +wit_bindgen_wasmtime::export!("../../wit/ephemeral/spin-config.wit"); + +impl fmt::Display for spin_config::Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Provider(provider_err) => write!(f, "provider error: {}", provider_err), + Self::InvalidKey(invalid_key) => write!(f, "invalid key: {}", invalid_key), + Self::InvalidSchema(invalid_schema) => { + write!(f, "invalid schema: {}", invalid_schema) + } + Self::Other(other) => write!(f, "other: {}", other), + } + } +} + +impl error::Error for spin_config::Error {} + +#[derive(Default)] +pub(super) struct SpinConfig { + map: HashMap, +} + +impl spin_config::SpinConfig for SpinConfig { + fn get_config(&mut self, key: &str) -> Result { + self.map + .remove(key) + .ok_or_else(|| spin_config::Error::InvalidKey(key.to_owned())) + } +} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + store + .data_mut() + .spin_config + .map + .insert("foo".into(), "bar".into()); + + super::run_command(store, pre, &["config", "foo"], |store| { + ensure!( + store.data().spin_config.map.is_empty(), + "expected module to call `spin-config::get-config` exactly once" + ); + + Ok(()) + }) +} diff --git a/crates/abi-conformance/src/spin_http.rs b/crates/abi-conformance/src/spin_http.rs new file mode 100644 index 0000000000..f6e0cef2d9 --- /dev/null +++ b/crates/abi-conformance/src/spin_http.rs @@ -0,0 +1,49 @@ +use super::Context; +use anyhow::ensure; +use wasmtime::{InstancePre, Store}; + +pub use spin_http::{Method, Request, SpinHttp, SpinHttpData}; + +wit_bindgen_wasmtime::import!("../../wit/ephemeral/spin-http.wit"); + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + super::run(|| { + let instance = &pre.instantiate(&mut *store)?; + let handle = SpinHttp::new(&mut *store, instance, |context| &mut context.spin_http)?; + let response = handle.handle_http_request( + store, + Request { + method: Method::Post, + uri: "/foo", + headers: &[("foo", "bar")], + params: &[], + body: Some(b"Hello, SpinHttp!"), + }, + )?; + + ensure!( + response.status == 200, + "expected response status 200, got {}", + response.status + ); + + ensure!( + response.headers == Some(vec![("lorem".to_owned(), "ipsum".to_owned())]), + "expected a single response header, \"lorem: ipsum\", got {:?}", + response.headers + ); + + let expected_body = "dolor sit amet"; + + ensure!( + response.body == Some(expected_body.as_bytes().to_vec()), + "expected a response body containing the string {expected_body:?}, got {:?}", + response + .body + .as_ref() + .map(|body| String::from_utf8_lossy(body)) + ); + + Ok(()) + }) +} diff --git a/crates/abi-conformance/src/spin_redis.rs b/crates/abi-conformance/src/spin_redis.rs new file mode 100644 index 0000000000..9b07882b33 --- /dev/null +++ b/crates/abi-conformance/src/spin_redis.rs @@ -0,0 +1,29 @@ +use super::Context; +use spin_redis::SpinRedis; +use std::{error, fmt}; +use wasmtime::{InstancePre, Store}; + +pub use spin_redis::SpinRedisData; + +wit_bindgen_wasmtime::import!("../../wit/ephemeral/spin-redis.wit"); + +impl fmt::Display for spin_redis::Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Success => f.write_str("redis success"), + Self::Error => f.write_str("redis error"), + } + } +} + +impl error::Error for spin_redis::Error {} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result<(), String> { + super::run(|| { + let instance = &pre.instantiate(&mut *store)?; + let handle = SpinRedis::new(&mut *store, instance, |context| &mut context.spin_redis)?; + handle.handle_redis_message(store, b"Hello, SpinRedis!")??; + + Ok(()) + }) +} diff --git a/crates/abi-conformance/src/wasi.rs b/crates/abi-conformance/src/wasi.rs new file mode 100644 index 0000000000..58138374a2 --- /dev/null +++ b/crates/abi-conformance/src/wasi.rs @@ -0,0 +1,290 @@ +use super::Context; +use anyhow::{ensure, Result}; +use cap_std::time::SystemTime as CapStdSystemTime; +use rand::SeedableRng; +use rand_chacha::ChaCha12Core; +use rand_core::block::{BlockRng, BlockRngCore}; +use serde::Serialize; +use std::{ + collections::HashSet, + fs::File, + io::Write, + ops::Deref, + path::Path, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::{Duration, SystemTime}, +}; +use wasi_common::{ + pipe::{ReadPipe, WritePipe}, + WasiSystemClock, +}; +use wasmtime::{InstancePre, Store}; +use wasmtime_wasi::{sync::dir::Dir, Dir as CapStdDir}; + +/// Report of which WASI functions a module successfully used, if any +/// +/// This represents the subset of WASI which is relevant to Spin and does not include e.g. network sockets, +/// filesystem odification, etc. +#[derive(Serialize)] +pub struct WasiReport { + /// Result of the WASI environment variable test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-env", "foo"\] as + /// arguments. The module should call the host-implemented `wasi_snapshot_preview1::environ_get` function + /// w`ok("foo=bar")` as the result. The module should extract the value of the "foo" variable and write the + /// result to `stdout` as a UTF-8 string. The host will assert the output matches the expected value. + pub env: Result<(), String>, + + /// Result of the WASI system clock test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-epoch"\] as the + /// argument. The module should call the host-implemented `wasi_snapshot_preview1::clock_time_get` function + /// with `realtime` as the clock ID and expect `ok(1663014331719000000)` as the result. The module should then + /// divide that value by 1000000 to convert to milliseconds and write the result to `stdout` as a UTF-8 string. + /// The host will assert the output matches the expected value. + pub epoch: Result<(), String>, + + /// Result of the WASI system random number generator test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-random"\] as the + /// argument. The module should call the host-implemented `wasi_snapshot_preview1::random_get` function at + /// least once. The host will assert that said function was called at least once. + pub random: Result<(), String>, + + /// Result of the WASI stdio test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-stdio"\] as the + /// argument. The module should call the host-implemented `wasi_snapshot_preview1::fd_read` and + /// `wasi_snapshot_preview1::fd_write` functions as necessary to read the UTF-8 string "All mimsy were the + /// borogroves" from `stdin` and write the same string back to `stdout`. The host will assert that the output + /// matches the input. + pub stdio: Result<(), String>, + + /// Result of the WASI filesystem read test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-read", + /// "foo.txt"\] as arguments. The module should call the relevant `wasi_snapshot_preview1` functions to open + /// the file "foo.txt" in the preopened directory descriptor 3 and read its content, which will be the UTF-8 + /// string "And the mome raths outgrabe". The module should then write that string to `stdout`. The host will + /// assert that the output matches the contents of the file. + pub read: Result<(), String>, + + /// Result of the WASI filesystem readdir test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-readdir", "/"\] + /// as arguments. The module should call the relevant `wasi_snapshot_preview1` functions to read the contents + /// of the preopened directory named "/" and write them to `stdout` as comma-delimited, UTF-8-encoded strings + /// (in arbitrary order), skipping the "." and ".." entries. The host will assert that the output matches the + /// contents of the directory: "bar.txt", "baz.txt", and "foo.txt". + pub readdir: Result<(), String>, + + /// Result of the WASI filesystem stat test + /// + /// The guest module should expect a call according to [`super::InvocationStyle`] with \["wasi-stat", + /// "foo.txt"\] as arguments. The module should call the relevant `wasi_snapshot_preview1` functions to + /// retrieve metadata from the file "foo.txt" in the preopened directory descriptor 3. The module should then + /// write a UTF-8-encoded string of the form "length:,modified:" to `stdout`, where + /// "" is the length of the file and "" is the last-modified time in milliseconds since 1970 + /// UTC. The host will assert that the output matches the metdata of the file. + pub stat: Result<(), String>, +} + +pub(super) fn test(store: &mut Store, pre: &InstancePre) -> Result { + Ok(WasiReport { + env: { + let stdout = WritePipe::new_in_memory(); + store.data_mut().wasi.set_stdout(Box::new(stdout.clone())); + store.data_mut().wasi.push_env("foo", "bar")?; + + super::run_command(store, pre, &["wasi-env", "foo"], move |_| { + let stdout = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + ensure!( + "bar" == stdout.deref(), + "expected module to write \"bar\" to stdout, got {stdout:?}" + ); + + Ok(()) + }) + }, + + epoch: { + const TIME: u64 = 1663014331719; + + struct MyClock; + + impl WasiSystemClock for MyClock { + fn resolution(&self) -> Duration { + Duration::from_millis(1) + } + + fn now(&self, _precision: Duration) -> CapStdSystemTime { + CapStdSystemTime::from_std( + SystemTime::UNIX_EPOCH + .checked_add(Duration::from_millis(TIME)) + .unwrap(), + ) + } + } + + let stdout = WritePipe::new_in_memory(); + { + let context = store.data_mut(); + context.wasi.set_stdout(Box::new(stdout.clone())); + context.wasi.clocks.system = Box::new(MyClock); + } + + super::run_command(store, pre, &["wasi-epoch"], move |_| { + let stdout = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + ensure!( + TIME.to_string() == stdout, + "expected module to write {TIME:?} to stdout, got {stdout:?}" + ); + + Ok(()) + }) + }, + + random: { + #[derive(Clone)] + struct MyRngCore { + cha_cha_12: ChaCha12Core, + called: Arc, + } + + impl BlockRngCore for MyRngCore { + type Item = ::Item; + type Results = ::Results; + + fn generate(&mut self, results: &mut Self::Results) { + self.called.store(true, Ordering::Relaxed); + self.cha_cha_12.generate(results) + } + } + + let called = Arc::new(AtomicBool::default()); + store.data_mut().wasi.random = Box::new(BlockRng::new(MyRngCore { + cha_cha_12: ChaCha12Core::seed_from_u64(42), + called: called.clone(), + })); + + super::run_command(store, pre, &["wasi-random"], move |_| { + ensure!( + called.load(Ordering::Relaxed), + "expected module to call `wasi_snapshot_preview1::random_get` at least once" + ); + + Ok(()) + }) + }, + + stdio: { + let stdin = ReadPipe::from("All mimsy were the borogroves"); + let stdout = WritePipe::new_in_memory(); + + store.data_mut().wasi.set_stdin(Box::new(stdin.clone())); + store.data_mut().wasi.set_stdout(Box::new(stdout.clone())); + + super::run_command(store, pre, &["wasi-stdio"], move |_| { + let stdin = stdin.try_into_inner().unwrap().into_inner(); + let stdout = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + ensure!( + stdin == stdout.deref(), + "expected module to write {stdin:?} to stdout, got {stdout:?}" + ); + + Ok(()) + }) + }, + + read: { + let stdout = WritePipe::new_in_memory(); + let message = "And the mome raths outgrabe"; + let dir = tempfile::tempdir()?; + let mut file = File::create(dir.path().join("foo.txt"))?; + file.write_all(message.as_bytes())?; + + store.data_mut().wasi.set_stdout(Box::new(stdout.clone())); + add_dir(store, dir.path())?; + + super::run_command(store, pre, &["wasi-read", "foo.txt"], move |_| { + let stdout = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + ensure!( + message == stdout.deref(), + "expected module to write {message:?} to stdout, got {stdout:?}" + ); + + Ok(()) + }) + }, + + readdir: { + let stdout = WritePipe::new_in_memory(); + let dir = tempfile::tempdir()?; + + let names = ["foo.txt", "bar.txt", "baz.txt"]; + for &name in &names { + File::create(dir.path().join(name))?; + } + + store.data_mut().wasi.set_stdout(Box::new(stdout.clone())); + add_dir(store, dir.path())?; + + super::run_command(store, pre, &["wasi-readdir", "/"], move |_| { + let expected = names.iter().copied().collect::>(); + let stdout = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + let got = stdout.split(',').collect(); + ensure!( + expected == got, + "expected module to write {expected:?} to stdout (in any order), got {got:?}" + ); + + Ok(()) + }) + }, + + stat: { + let stdout = WritePipe::new_in_memory(); + let message = "O frabjous day! Callooh! Callay!"; + let dir = tempfile::tempdir()?; + let mut file = File::create(dir.path().join("foo.txt"))?; + file.write_all(message.as_bytes())?; + let metadata = file.metadata()?; + + store.data_mut().wasi.set_stdout(Box::new(stdout.clone())); + add_dir(store, dir.path())?; + + super::run_command(store, pre, &["wasi-stat", "foo.txt"], move |_| { + let expected = format!( + "length:{},modified:{}", + metadata.len(), + metadata + .modified()? + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis() + ); + let got = String::from_utf8(stdout.try_into_inner().unwrap().into_inner())?; + + ensure!( + expected == got, + "expected module to write {expected:?} to stdout, got {got:?}" + ); + + Ok(()) + }) + }, + }) +} + +fn add_dir(store: &mut Store, path: &Path) -> Result<()> { + store.data_mut().wasi.push_preopened_dir( + Box::new(Dir::from_cap_std(CapStdDir::from_std_file(File::open( + path, + )?))), + "/", + )?; + + Ok(()) +} diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml new file mode 100644 index 0000000000..1d8dedf4b3 --- /dev/null +++ b/crates/app/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "spin-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +ouroboros = "0.15" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +spin-core = { path = "../core" } +thiserror = "1.0" diff --git a/crates/app/src/host_component.rs b/crates/app/src/host_component.rs new file mode 100644 index 0000000000..cc81e66988 --- /dev/null +++ b/crates/app/src/host_component.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use spin_core::{EngineBuilder, HostComponent, HostComponentsData}; + +use crate::AppComponent; + +/// A trait for "dynamic" Spin host components. +/// +/// This extends [`HostComponent`] to support per-[`AppComponent`] dynamic +/// runtime configuration. +pub trait DynamicHostComponent: HostComponent { + /// Called on [`AppComponent`] instance initialization. + /// + /// The `data` returned by [`HostComponent::build_data`] is passed, along + /// with a reference to the `component` being instantiated. + fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()>; +} + +impl DynamicHostComponent for Arc { + fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()> { + (**self).update_data(data, component) + } +} + +type DataUpdater = + Box anyhow::Result<()> + Send + Sync>; + +#[derive(Default)] +pub struct DynamicHostComponents { + data_updaters: Vec, +} + +impl DynamicHostComponents { + pub fn add_dynamic_host_component( + &mut self, + engine_builder: &mut EngineBuilder, + host_component: DHC, + ) -> anyhow::Result<()> { + let host_component = Arc::new(host_component); + let handle = engine_builder.add_host_component(host_component.clone())?; + self.data_updaters + .push(Box::new(move |host_components_data, component| { + let data = host_components_data.get_or_insert(handle); + host_component.update_data(data, component) + })); + Ok(()) + } + + pub fn update_data( + &self, + host_components_data: &mut HostComponentsData, + component: &AppComponent, + ) -> anyhow::Result<()> { + for data_updater in &self.data_updaters { + data_updater(host_components_data, component)?; + } + Ok(()) + } +} diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs new file mode 100644 index 0000000000..eac9fdc3c1 --- /dev/null +++ b/crates/app/src/lib.rs @@ -0,0 +1,345 @@ +//! Spin internal application interfaces +//! +//! This crate contains interfaces to Spin application configuration to be used +//! by crates that implement Spin execution environments: trigger executors and +//! host components, in particular. + +#![deny(missing_docs)] + +mod host_component; +pub mod locked; +pub mod values; + +use ouroboros::self_referencing; +use serde::Deserialize; +use spin_core::{wasmtime, Engine, EngineBuilder, StoreBuilder}; + +use host_component::DynamicHostComponents; +use locked::{ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger}; +use values::MetadataExt; + +pub use async_trait::async_trait; +pub use host_component::DynamicHostComponent; +pub use locked::Variable; + +/// A trait for implementing the low-level operations needed to load an [`App`]. +// TODO(lann): Should this migrate to spin-loader? +#[async_trait] +pub trait Loader { + /// Called with an implementation-defined `uri` pointing to some + /// representation of a [`LockedApp`], which will be loaded. + async fn load_app(&self, uri: &str) -> anyhow::Result; + + /// Called with a [`LockedComponentSource`] pointing to a Wasm module + /// binary, which will be loaded. + async fn load_module( + &self, + engine: &wasmtime::Engine, + source: &LockedComponentSource, + ) -> anyhow::Result; + + /// Called with an [`AppComponent`]; any `files` configured with the + /// component should be "mounted" into the `store_builder`, via e.g. + /// [`StoreBuilder::read_only_preopened_dir`]. + async fn mount_files( + &self, + store_builder: &mut StoreBuilder, + component: &AppComponent, + ) -> anyhow::Result<()>; +} + +/// An `AppLoader` holds an implementation of [`Loader`] along with +/// [`DynamicHostComponents`] configuration. +pub struct AppLoader { + inner: Box, + dynamic_host_components: DynamicHostComponents, +} + +impl AppLoader { + /// Creates a new [`AppLoader`]. + pub fn new(loader: impl Loader + Send + Sync + 'static) -> Self { + Self { + inner: Box::new(loader), + dynamic_host_components: Default::default(), + } + } + + /// Adds a [`DynamicHostComponent`] to the given [`EngineBuilder`] and + /// configures this [`AppLoader`] to update it on component instantiation. + /// + /// This calls [`EngineBuilder::add_host_component`] for you; it should not + /// be called separately. + pub fn add_dynamic_host_component( + &mut self, + engine_builder: &mut EngineBuilder, + host_component: DHC, + ) -> anyhow::Result<()> { + self.dynamic_host_components + .add_dynamic_host_component(engine_builder, host_component) + } + + /// Loads an [`App`] from the given `Loader`-implementation-specific `uri`. + pub async fn load_app(&self, uri: String) -> Result { + let locked = self + .inner + .load_app(&uri) + .await + .map_err(Error::LoaderError)?; + Ok(App { + loader: self, + uri, + locked, + }) + } + + /// Loads an [`OwnedApp`] from the given `Loader`-implementation-specific + /// `uri`; the [`OwnedApp`] takes ownership of this [`AppLoader`]. + pub async fn load_owned_app(self, uri: String) -> Result { + OwnedApp::try_new_async(self, |loader| Box::pin(loader.load_app(uri))).await + } +} + +impl std::fmt::Debug for AppLoader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AppLoader").finish() + } +} + +#[self_referencing] +#[derive(Debug)] +pub struct OwnedApp { + loader: AppLoader, + + #[borrows(loader)] + #[covariant] + app: App<'this>, +} + +impl OwnedApp { + /// Returns a reference to the owned [`App`]. + pub fn borrowed(&self) -> &App { + self.borrow_app() + } +} + +/// An `App` holds loaded configuration for a Spin application. +#[derive(Debug)] +pub struct App<'a> { + loader: &'a AppLoader, + uri: String, + locked: LockedApp, +} + +impl<'a> App<'a> { + /// Returns a [`Loader`]-implementation-specific URI for this app. + pub fn uri(&self) -> &str { + &self.uri + } + + /// Deserializes typed metadata for this app. + /// + /// Returns `Ok(None)` if there is no metadata for the given `key` and an + /// `Err` only if there _is_ a value for the `key` but the typed + /// deserialization failed. + pub fn get_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result> { + self.locked.metadata.get_typed(key) + } + + /// Deserializes typed metadata for this app. + /// + /// Like [`App::get_metadata`], but returns an error if there is + /// no metadata for the given `key`. + pub fn require_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result { + self.locked.metadata.require_typed(key) + } + + /// Returns an iterator of custom config [`Variable`]s defined for this app. + pub fn variables(&self) -> impl Iterator { + self.locked.variables.iter() + } + + /// Returns an iterator of [`AppComponent`]s defined for this app. + pub fn components(&self) -> impl Iterator { + self.locked + .components + .iter() + .map(|locked| AppComponent { app: self, locked }) + } + + /// Returns the [`AppComponent`] with the given `component_id`, or `None` + /// if it doesn't exist. + pub fn get_component(&self, component_id: &str) -> Option { + self.components() + .find(|component| component.locked.id == component_id) + } + + /// Returns an iterator of [`AppTrigger`]s defined for this app. + pub fn triggers(&self) -> impl Iterator { + self.locked + .triggers + .iter() + .map(|locked| AppTrigger { app: self, locked }) + } + + /// Returns an iterator of [`AppTrigger`]s defined for this app with + /// the given `trigger_type`. + pub fn triggers_with_type(&'a self, trigger_type: &'a str) -> impl Iterator { + self.triggers() + .filter(move |trigger| trigger.locked.trigger_type == trigger_type) + } +} + +/// An `AppComponent` holds configuration for a Spin application component. +pub struct AppComponent<'a> { + /// The app this component belongs to. + pub app: &'a App<'a>, + locked: &'a LockedComponent, +} + +impl<'a> AppComponent<'a> { + /// Returns this component's app-unique ID. + pub fn id(&self) -> &str { + &self.locked.id + } + + /// Returns this component's Wasm module source. + pub fn source(&self) -> &LockedComponentSource { + &self.locked.source + } + + /// Returns an iterator of [`ContentPath`]s for this component's configured + /// "directory mounts". + pub fn files(&self) -> std::slice::Iter { + self.locked.files.iter() + } + + /// Deserializes typed metadata for this component. + /// + /// Returns `Ok(None)` if there is no metadata for the given `key` and an + /// `Err` only if there _is_ a value for the `key` but the typed + /// deserialization failed. + pub fn get_metadata>(&self, key: &str) -> Result> { + self.locked.metadata.get_typed(key) + } + + /// Deserializes typed metadata for this component. + /// + /// Like [`AppComponent::get_metadata`], but returns an error if there is + /// no metadata for the given `key`. + pub fn require_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result { + self.locked.metadata.require_typed(key) + } + + /// Returns an iterator of custom config values for this component. + pub fn config(&self) -> impl Iterator { + self.locked.config.iter() + } + + /// Loads and returns the [`spin_core::Module`] for this component. + pub async fn load_module( + &self, + engine: &Engine, + ) -> Result { + self.app + .loader + .inner + .load_module(engine.as_ref(), &self.locked.source) + .await + .map_err(Error::LoaderError) + } + + /// Updates the given [`StoreBuilder`] with configuration for this component. + /// + /// In particular, the WASI 'env' and "preloaded dirs" are set up, and any + /// [`DynamicHostComponent`]s associated with the source [`AppLoader`] are + /// configured. + pub async fn apply_store_config(&self, builder: &mut StoreBuilder) -> Result<()> { + builder.env(&self.locked.env).map_err(Error::CoreError)?; + + let loader = self.app.loader; + loader + .inner + .mount_files(builder, self) + .await + .map_err(Error::LoaderError)?; + + loader + .dynamic_host_components + .update_data(builder.host_components_data(), self) + .map_err(Error::HostComponentError)?; + + Ok(()) + } +} + +/// An `AppTrigger` holds configuration for a Spin application trigger. +pub struct AppTrigger<'a> { + /// The app this trigger belongs to. + pub app: &'a App<'a>, + locked: &'a LockedTrigger, +} + +impl<'a> AppTrigger<'a> { + /// Returns this trigger's app-unique ID. + pub fn id(&self) -> &str { + &self.locked.id + } + + /// Returns the Trigger's type. + pub fn trigger_type(&self) -> &str { + &self.locked.trigger_type + } + + /// Returns a reference to the [`AppComponent`] configured for this trigger. + /// + /// This is a convenience wrapper that looks up the component based on the + /// 'component' metadata value which is conventionally a component ID. + pub fn component(&self) -> Result> { + let component_id = self.locked.trigger_config.get("component").ok_or_else(|| { + Error::MetadataError(format!( + "trigger {:?} missing 'component' config field", + self.locked.id + )) + })?; + let component_id = component_id.as_str().ok_or_else(|| { + Error::MetadataError(format!( + "trigger {:?} 'component' field has unexpected value {:?}", + self.locked.id, component_id + )) + })?; + self.app.get_component(component_id).ok_or_else(|| { + Error::MetadataError(format!( + "missing component {:?} configured for trigger {:?}", + component_id, self.locked.id + )) + }) + } + + /// Deserializes this trigger's configuration into a typed value. + pub fn typed_config>(&self) -> Result { + Ok(Config::deserialize(&self.locked.trigger_config)?) + } +} + +/// Type alias for a [`Result`]s with [`Error`]. +pub type Result = std::result::Result; + +/// Errors returned by methods in this crate. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// An error propagated from the [`spin_core`] crate. + #[error("spin core error: {0:#}")] + CoreError(#[source] anyhow::Error), + /// An error from a [`DynamicHostComponent`]. + #[error("host component error: {0:#}")] + HostComponentError(#[source] anyhow::Error), + /// An error from a [`Loader`] implementation. + #[error("loader error: {0:#}")] + LoaderError(#[source] anyhow::Error), + /// An error indicating missing or unexpected metadata. + #[error("metadata error: {0}")] + MetadataError(String), + /// An error indicating failed JSON (de)serialization. + #[error("json error: {0}")] + JsonError(#[from] serde_json::Error), +} diff --git a/crates/app/src/locked.rs b/crates/app/src/locked.rs new file mode 100644 index 0000000000..fd5a8e3ded --- /dev/null +++ b/crates/app/src/locked.rs @@ -0,0 +1,158 @@ +//! Spin lock file (spin.lock) serialization models. + +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::values::ValuesMap; + +/// A String-keyed map with deterministic serialization order. +pub type LockedMap = std::collections::BTreeMap; + +/// A LockedApp represents a "fully resolved" Spin application. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LockedApp { + /// Locked schema version + pub spin_lock_version: FixedVersion<0>, + /// Application metadata + #[serde(default, skip_serializing_if = "ValuesMap::is_empty")] + pub metadata: ValuesMap, + /// Custom config variables + #[serde(default, skip_serializing_if = "LockedMap::is_empty")] + pub variables: LockedMap, + /// Application triggers + pub triggers: Vec, + /// Application components + pub components: Vec, +} + +impl LockedApp { + /// Deserializes a [`LockedApp`] from the given JSON data. + pub fn from_json(contents: &[u8]) -> serde_json::Result { + serde_json::from_slice(contents) + } + + /// Serializes the [`LockedApp`] into JSON data. + pub fn to_json(&self) -> serde_json::Result> { + serde_json::to_vec_pretty(&self) + } +} + +/// A LockedComponent represents a "fully resolved" Spin component. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LockedComponent { + /// Application-unique component identifier + pub id: String, + /// Component metadata + #[serde(default, skip_serializing_if = "ValuesMap::is_empty")] + pub metadata: ValuesMap, + /// Wasm source + pub source: LockedComponentSource, + /// WASI environment variables + #[serde(default, skip_serializing_if = "LockedMap::is_empty")] + pub env: LockedMap, + /// WASI filesystem contents + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub files: Vec, + /// Custom config values + #[serde(default, skip_serializing_if = "LockedMap::is_empty")] + pub config: LockedMap, +} + +/// A LockedComponentSource specifies a Wasm source. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LockedComponentSource { + /// Wasm source content type (e.g. "application/wasm") + pub content_type: String, + /// Wasm source content specification + #[serde(flatten)] + pub content: ContentRef, +} + +/// A ContentPath specifies content mapped to a WASI path. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ContentPath { + /// Content specification + #[serde(flatten)] + pub content: ContentRef, + /// WASI mount path + pub path: PathBuf, +} + +/// A ContentRef represents content used by an application. +/// +/// At least one of `source` or `digest` must be specified. Implementations may +/// require one or the other (or both). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ContentRef { + /// A URI where the content can be accessed. Implementations may support + /// different URI schemes. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source: Option, + /// If set, the content must have the given SHA-256 digest. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub digest: Option, +} + +/// A LockedTrigger specifies configuration for an application trigger. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LockedTrigger { + /// Application-unique trigger identifier + pub id: String, + /// Trigger type (e.g. "http") + pub trigger_type: String, + /// Trigger-type-specific configuration + pub trigger_config: Value, +} + +/// A Variable specifies a custom configuration variable. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Variable { + /// The variable's default value. If unset, the variable is required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default: Option, + /// If set, the variable's value may be sensitive and e.g. shouldn't be logged. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub secret: bool, +} + +/// FixedVersion represents a schema version field with a const value. +#[allow(unused)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(into = "usize", try_from = "usize")] +pub struct FixedVersion; + +impl From> for usize { + fn from(_: FixedVersion) -> usize { + V + } +} + +impl From> for String { + fn from(_: FixedVersion) -> String { + V.to_string() + } +} + +impl TryFrom for FixedVersion { + type Error = String; + + fn try_from(value: usize) -> Result { + if value != V { + return Err(format!("invalid version {} != {}", value, V)); + } + Ok(Self) + } +} + +impl TryFrom for FixedVersion { + type Error = String; + + fn try_from(value: String) -> Result { + let value: usize = value + .parse() + .map_err(|err| format!("invalid version: {}", err))?; + value.try_into() + } +} diff --git a/crates/app/src/values.rs b/crates/app/src/values.rs new file mode 100644 index 0000000000..a1434a5aef --- /dev/null +++ b/crates/app/src/values.rs @@ -0,0 +1,100 @@ +//! Dynamically-typed value helpers. + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::Error; + +/// A String-keyed map with dynamically-typed values. +pub type ValuesMap = serde_json::Map; + +/// ValuesMapBuilder assists in building a ValuesMap. +#[derive(Default)] +pub struct ValuesMapBuilder(ValuesMap); + +impl ValuesMapBuilder { + /// Returns a new empty ValuesMapBuilder. + pub fn new() -> Self { + Self::default() + } + + /// Inserts a string value into the map. + pub fn string(&mut self, key: impl Into, value: impl Into) -> &mut Self { + self.entry(key, value.into()) + } + + /// Inserts a string value into the map only if the given Option is Some. + pub fn string_option( + &mut self, + key: impl Into, + value: Option>, + ) -> &mut Self { + if let Some(value) = value { + self.0.insert(key.into(), value.into().into()); + } + self + } + + /// Inserts a string array into the map. + pub fn string_array>( + &mut self, + key: impl Into, + iter: impl IntoIterator, + ) -> &mut Self { + self.entry(key, iter.into_iter().map(|s| s.into()).collect::>()) + } + + /// Inserts an entry into the map using the value's `impl Into`. + pub fn entry(&mut self, key: impl Into, value: impl Into) -> &mut Self { + self.0.insert(key.into(), value.into()); + self + } + + /// Inserts an entry into the map using the value's `impl Serialize`. + pub fn serializable( + &mut self, + key: impl Into, + value: impl Serialize, + ) -> serde_json::Result<&mut Self> { + let value = serde_json::to_value(value)?; + self.0.insert(key.into(), value); + Ok(self) + } + + /// Returns the built ValuesMap. + pub fn build(self) -> ValuesMap { + self.0 + } + + /// Returns the build ValuesMap and resets the builder to empty. + pub fn take(&mut self) -> ValuesMap { + std::mem::take(&mut self.0) + } +} + +pub(crate) trait MetadataExt { + fn get_value(&self, key: impl AsRef) -> Option<&Value>; + + fn get_typed<'a, T: Deserialize<'a>>( + &'a self, + key: impl AsRef, + ) -> Result, Error> { + let key = key.as_ref(); + self.get_value(key) + .map(|value| T::deserialize(value)) + .transpose() + .map_err(|err| Error::MetadataError(format!("invalid value for {key:?}: {err:?}"))) + } + + fn require_typed<'a, T: Deserialize<'a>>(&'a self, key: impl AsRef) -> Result { + let key = key.as_ref(); + self.get_typed(key)? + .ok_or_else(|| Error::MetadataError(format!("missing required {key:?}"))) + } +} + +impl MetadataExt for ValuesMap { + fn get_value(&self, key: impl AsRef) -> Option<&Value> { + self.get(key.as_ref()) + } +} diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 6604fc7a59..d9bbb1cafa 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -8,7 +8,6 @@ authors = [ "Fermyon Engineering " ] anyhow = "1.0.57" futures = "0.3.21" path-absolutize = "3.0.11" -tokio = { version = "1.11", features = [ "full" ] } spin-loader = { path = "../loader" } subprocess = "0.2.8" tracing = { version = "0.1", features = [ "log" ] } diff --git a/crates/cloud/Cargo.toml b/crates/cloud/Cargo.toml new file mode 100644 index 0000000000..cbb57dce47 --- /dev/null +++ b/crates/cloud/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "cloud" +version = "0.1.0" +edition = "2021" +authors = [ "Fermyon Engineering " ] + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +clap = { version = "3.0", features = ["derive", "env"] } +colored = "2.0.0" +dialoguer = "0.9" +dirs = "4.0" +dunce = "1.0" +env_logger = "0.9" +futures = "0.3.14" +glob = "0.3.0" +cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi" } +itertools = "0.10.0" +log = "0.4" +mime_guess = { version = "2.0" } +path-absolutize = "3.0.11" +regex = "1.5" +reqwest = { version = "0.11", features = ["stream"] } +semver = "1.0" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +sha2 = "0.9" +spin-loader = { path = "../loader" } +spin-publish = { path = "../publish" } +tempfile = "3.3.0" +tokio = { version = "1.17", features = ["full"] } +tokio-util = { version = "0.7.3", features = ["codec"] } +tracing = { version = "0.1", features = [ "log" ] } +toml = "0.5" +uuid = "1" + +[dependencies.bindle] +git = "https://github.com/fermyon/bindle" +tag = "v0.8.2" +default-features = false +features = ["client"] diff --git a/crates/cloud/src/client.rs b/crates/cloud/src/client.rs new file mode 100644 index 0000000000..914e1c8da6 --- /dev/null +++ b/crates/cloud/src/client.rs @@ -0,0 +1,332 @@ +use anyhow::{Context, Result}; +use cloud_openapi::{ + apis::{ + self, + apps_api::{api_apps_get, api_apps_id_delete, api_apps_post}, + auth_tokens_api::api_auth_tokens_post, + channels_api::{ + api_channels_get, api_channels_id_delete, api_channels_id_get, + api_channels_id_logs_get, api_channels_id_patch, api_channels_post, + ApiChannelsIdPatchError, + }, + configuration::{ApiKey, Configuration}, + device_codes_api::api_device_codes_post, + revisions_api::{api_revisions_get, api_revisions_post}, + Error, ResponseContent, + }, + models::{ + AppItemPage, ChannelItem, ChannelItemPage, ChannelRevisionSelectionStrategy, + ChannelRevisionSelectionStrategyField, CreateAppCommand, CreateChannelCommand, + CreateDeviceCodeCommand, CreateTokenCommand, DeviceCodeItem, GetChannelLogsVm, + GuidNullableField, RegisterRevisionCommand, RevisionItemPage, StringField, TokenInfo, + UpdateEnvironmentVariableDto, UpdateEnvironmentVariableDtoListField, + }, +}; +use reqwest::header; +use semver::BuildMetadata; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, path::Path}; +use tracing::log; +use uuid::Uuid; + +const JSON_MIME_TYPE: &str = "application/json"; + +pub struct Client { + configuration: Configuration, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct ConnectionConfig { + pub insecure: bool, + pub token: TokenInfo, + pub url: String, +} + +impl Client { + pub fn new(conn_info: ConnectionConfig) -> Self { + let mut headers = header::HeaderMap::new(); + headers.insert(header::ACCEPT, JSON_MIME_TYPE.parse().unwrap()); + headers.insert(header::CONTENT_TYPE, JSON_MIME_TYPE.parse().unwrap()); + + let base_path = match conn_info.url.strip_suffix('/') { + Some(s) => s.to_owned(), + None => conn_info.url, + }; + + let configuration = Configuration { + base_path, + user_agent: Some(format!( + "{}/{}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + )), + client: reqwest::Client::builder() + .danger_accept_invalid_certs(conn_info.insecure) + .default_headers(headers) + .build() + .unwrap(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: conn_info.token.token.map(|t| ApiKey { + prefix: Some("Bearer".to_owned()), + key: t, + }), + }; + + Self { configuration } + } + + pub async fn create_device_code(&self, client_id: Uuid) -> Result { + api_device_codes_post( + &self.configuration, + Some(CreateDeviceCodeCommand { client_id }), + ) + .await + .map_err(format_response_error) + } + + pub async fn login(&self, token: String) -> Result { + // When the new OpenAPI specification is released, manually crafting + // the request should no longer be necessary. + let response = self + .configuration + .client + .post(format!("{}/api/auth-tokens", self.configuration.base_path)) + .body( + serde_json::json!( + { + "provider": "DeviceFlow", + "clientId": "583e63e9-461f-4fbe-a246-23e0fb1cad10", + "providerCode": token, + } + ) + .to_string(), + ) + .send() + .await?; + + serde_json::from_reader(response.bytes().await?.as_ref()) + .context("Failed to parse response") + } + + pub async fn add_app(&self, name: &str, storage_id: &str) -> Result { + api_apps_post( + &self.configuration, + Some(CreateAppCommand { + name: name.to_string(), + storage_id: storage_id.to_string(), + }), + ) + .await + .map_err(format_response_error) + } + + pub async fn remove_app(&self, id: String) -> Result<()> { + api_apps_id_delete(&self.configuration, &id) + .await + .map_err(format_response_error) + } + + pub async fn list_apps(&self) -> Result { + api_apps_get(&self.configuration, None, None, None, None, None) + .await + .map_err(format_response_error) + } + + pub async fn get_channel_by_id(&self, id: &str) -> Result { + api_channels_id_get(&self.configuration, id) + .await + .map_err(format_response_error) + } + + pub async fn list_channels(&self) -> Result { + api_channels_get( + &self.configuration, + Some(""), + None, + None, + Some("Name"), + None, + ) + .await + .map_err(format_response_error) + } + + #[allow(clippy::too_many_arguments)] + pub async fn add_channel( + &self, + app_id: Uuid, + name: String, + revision_selection_strategy: ChannelRevisionSelectionStrategy, + range_rule: Option, + active_revision_id: Option, + ) -> anyhow::Result { + let command = CreateChannelCommand { + app_id, + name, + revision_selection_strategy, + range_rule, + active_revision_id, + }; + api_channels_post(&self.configuration, Some(command)) + .await + .map_err(format_response_error) + } + + #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] + pub async fn patch_channel( + &self, + id: Uuid, + name: Option, + revision_selection_strategy: Option, + range_rule: Option, + active_revision_id: Option, + environment_variables: Option>, + ) -> anyhow::Result<()> { + let patch_channel_command = PatchChannelCommand { + channel_id: Some(id), + name, + revision_selection_strategy, + range_rule, + active_revision_id, + environment_variables, + }; + + let local_var_configuration = &self.configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/api/channels/{id}", + local_var_configuration.base_path, + id = apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::PATCH, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder + .header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_apikey) = local_var_configuration.api_key { + let local_var_key = local_var_apikey.key.clone(); + let local_var_value = match local_var_apikey.prefix { + Some(ref local_var_prefix) => format!("{} {}", local_var_prefix, local_var_key), + None => local_var_key, + }; + local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); + }; + local_var_req_builder = local_var_req_builder.json(&patch_channel_command); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(()) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(format_response_error(Error::ResponseError(local_var_error))) + } + } + + pub async fn remove_channel(&self, id: String) -> Result<()> { + api_channels_id_delete(&self.configuration, &id) + .await + .map_err(format_response_error) + } + + pub async fn channel_logs(&self, id: String) -> Result { + api_channels_id_logs_get(&self.configuration, &id) + .await + .map_err(format_response_error) + } + + pub async fn add_revision( + &self, + app_storage_id: String, + revision_number: String, + ) -> anyhow::Result<()> { + api_revisions_post( + &self.configuration, + Some(RegisterRevisionCommand { + app_storage_id, + revision_number, + }), + ) + .await + .map_err(format_response_error) + } + + pub async fn list_revisions(&self) -> anyhow::Result { + api_revisions_get(&self.configuration, None, None) + .await + .map_err(format_response_error) + } +} + +#[derive(Deserialize, Debug)] +struct ValidationExceptionMessage { + title: String, + errors: HashMap>, +} + +fn format_response_error(e: Error) -> anyhow::Error { + match e { + Error::ResponseError(r) => { + match serde_json::from_str::(&r.content) { + Ok(m) => anyhow::anyhow!("{} {:?}", m.title, m.errors), + _ => anyhow::anyhow!(r.content), + } + } + Error::Serde(err) => { + anyhow::anyhow!(format!("could not parse JSON object: {}", err)) + } + _ => anyhow::anyhow!(e.to_string()), + } +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct PatchChannelCommand { + #[serde(rename = "channelId", skip_serializing_if = "Option::is_none")] + pub channel_id: Option, + #[serde( + rename = "environmentVariables", + skip_serializing_if = "Option::is_none" + )] + pub environment_variables: Option>, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde( + rename = "revisionSelectionStrategy", + skip_serializing_if = "Option::is_none" + )] + pub revision_selection_strategy: Option, + #[serde(rename = "rangeRule", skip_serializing_if = "Option::is_none")] + pub range_rule: Option, + #[serde(rename = "activeRevisionId", skip_serializing_if = "Option::is_none")] + pub active_revision_id: Option, +} + +impl PatchChannelCommand { + pub fn new() -> PatchChannelCommand { + PatchChannelCommand { + channel_id: None, + environment_variables: None, + name: None, + revision_selection_strategy: None, + range_rule: None, + active_revision_id: None, + } + } +} diff --git a/crates/cloud/src/lib.rs b/crates/cloud/src/lib.rs new file mode 100644 index 0000000000..d7551f3646 --- /dev/null +++ b/crates/cloud/src/lib.rs @@ -0,0 +1,2 @@ +#[allow(unused)] +pub mod client; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index d7edb3c71f..960dbe38a3 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -6,9 +6,18 @@ authors = [ "Fermyon Engineering " ] [dependencies] anyhow = "1.0" -serde = { version = "1.0", features = [ "derive" ] } +async-trait = "0.1" +dotenvy = "0.15" +once_cell = "1" +spin-app = { path = "../app" } +spin-core = { path = "../core" } thiserror = "1" -wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" } +tokio = { version = "1", features = ["rt-multi-thread"] } + +[dependencies.wit-bindgen-wasmtime] +git = "https://github.com/bytecodealliance/wit-bindgen" +rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +features = ["async"] [dev-dependencies] toml = "0.5" diff --git a/crates/config/src/host_component.rs b/crates/config/src/host_component.rs index 20e7528bc6..bfa1a88c3d 100644 --- a/crates/config/src/host_component.rs +++ b/crates/config/src/host_component.rs @@ -1,40 +1,93 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; -use crate::{Error, Key, Resolver, TreePath}; +use async_trait::async_trait; +use once_cell::sync::OnceCell; +use spin_app::{AppComponent, DynamicHostComponent}; +use spin_core::HostComponent; -mod wit { - wit_bindgen_wasmtime::export!("../../wit/ephemeral/spin-config.wit"); +use crate::{Error, Key, Provider, Resolver}; + +wit_bindgen_wasmtime::export!({paths: ["../../wit/ephemeral/spin-config.wit"], async: *}); + +pub struct ConfigHostComponent { + providers: Mutex>>, + resolver: Arc>, } -pub use wit::spin_config::add_to_linker; -/// A component configuration interface implementation. -pub struct ComponentConfig { - component_root: TreePath, - resolver: Arc, +impl ConfigHostComponent { + pub fn new(providers: Vec>) -> Self { + Self { + providers: Mutex::new(providers), + resolver: Default::default(), + } + } } -impl ComponentConfig { - pub fn new(component_id: impl Into, resolver: Arc) -> crate::Result { - let component_root = TreePath::new(component_id).or_else(|_| { - // Temporary mitigation for https://github.com/fermyon/spin/issues/337 - TreePath::new("invalid.path.issue_337") +impl HostComponent for ConfigHostComponent { + type Data = ComponentConfig; + + fn add_to_linker( + linker: &mut spin_core::Linker, + get: impl Fn(&mut spin_core::Data) -> &mut Self::Data + Send + Sync + Copy + 'static, + ) -> anyhow::Result<()> { + spin_config::add_to_linker(linker, get) + } + + fn build_data(&self) -> Self::Data { + ComponentConfig { + resolver: self.resolver.clone(), + component_id: None, + } + } +} + +impl DynamicHostComponent for ConfigHostComponent { + fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()> { + self.resolver.get_or_try_init(|| { + let mut resolver = Resolver::new( + component + .app + .variables() + .map(|(key, var)| (key.clone(), var.clone())), + )?; + for component in component.app.components() { + resolver.add_component_config( + component.id(), + component.config().map(|(k, v)| (k.into(), v.into())), + )?; + } + for provider in self.providers.lock().unwrap().drain(..) { + resolver.add_provider(provider); + } + Ok::<_, anyhow::Error>(resolver) })?; - Ok(Self { - component_root, - resolver, - }) + data.component_id = Some(component.id().to_string()); + Ok(()) } } -impl wit::spin_config::SpinConfig for ComponentConfig { - fn get_config(&mut self, key: &str) -> Result { +/// A component configuration interface implementation. +pub struct ComponentConfig { + resolver: Arc>, + component_id: Option, +} + +#[async_trait] +impl spin_config::SpinConfig for ComponentConfig { + async fn get_config(&mut self, key: &str) -> Result { + // Set by DynamicHostComponent::update_data + let component_id = self.component_id.as_deref().unwrap(); let key = Key::new(key)?; - let path = &self.component_root + key; - Ok(self.resolver.resolve(&path)?) + Ok(self + .resolver + .get() + .unwrap() + .resolve(component_id, key) + .await?) } } -impl From for wit::spin_config::Error { +impl From for spin_config::Error { fn from(err: Error) -> Self { match err { Error::InvalidKey(msg) => Self::InvalidKey(msg), diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 3df750684a..d9db96ea2f 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,128 +1,123 @@ -pub mod host_component; +mod host_component; pub mod provider; - mod template; -mod tree; -use std::fmt::Debug; +use std::{borrow::Cow, collections::HashMap, fmt::Debug}; -pub use provider::Provider; -pub use tree::{Tree, TreePath}; +use spin_app::Variable; +pub use crate::{host_component::ConfigHostComponent, provider::Provider}; use template::{Part, Template}; -/// A config resolution error. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Invalid config key. - #[error("invalid config key: {0}")] - InvalidKey(String), - - /// Invalid config path. - #[error("invalid config path: {0}")] - InvalidPath(String), - - /// Invalid config schema. - #[error("invalid config schema: {0}")] - InvalidSchema(String), - - /// Invalid config template. - #[error("invalid config template: {0}")] - InvalidTemplate(String), - - /// Config provider error. - #[error("provider error: {0:?}")] - Provider(anyhow::Error), - - /// Unknown config path. - #[error("unknown config path: {0}")] - UnknownPath(String), -} - -type Result = std::result::Result; - /// A configuration resolver. #[derive(Debug, Default)] pub struct Resolver { - tree: Tree, + // variable key -> variable + variables: HashMap, + // component ID -> config key -> config value template + component_configs: HashMap>, providers: Vec>, } impl Resolver { /// Creates a Resolver for the given Tree. - pub fn new(tree: Tree) -> Result { + pub fn new(variables: impl IntoIterator) -> Result { + let variables: HashMap<_, _> = variables.into_iter().collect(); + // Validate keys so that we can rely on them during resolution + variables.keys().try_for_each(|key| Key::validate(key))?; Ok(Self { - tree, - providers: vec![], + variables, + component_configs: Default::default(), + providers: Default::default(), }) } + /// Adds component configuration values to the Resolver. + pub fn add_component_config( + &mut self, + component_id: impl Into, + config: impl IntoIterator, + ) -> Result<()> { + let component_id = component_id.into(); + let templates = config + .into_iter() + .map(|(key, val)| { + // Validate config keys so that we can rely on them during resolution + Key::validate(&key)?; + let template = self.validate_template(val)?; + Ok((key, template)) + }) + .collect::>()?; + + self.component_configs.insert(component_id, templates); + + Ok(()) + } + /// Adds a config Provider to the Resolver. - pub fn add_provider(&mut self, provider: impl Provider + 'static) { - self.providers.push(Box::new(provider)); + pub fn add_provider(&mut self, provider: Box) { + self.providers.push(provider); } /// Resolves a config value for the given path. - pub fn resolve(&self, path: &TreePath) -> Result { - self.resolve_path(path, 0) + pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result { + let configs = self.component_configs.get(component_id).ok_or_else(|| { + Error::UnknownPath(format!("no config for component {component_id:?}")) + })?; + + let key = key.as_ref(); + let template = configs + .get(key) + .ok_or_else(|| Error::UnknownPath(format!("no config for {component_id:?}.{key:?}")))?; + + self.resolve_template(template).await } - // Simple protection against infinite recursion - const RECURSION_LIMIT: usize = 100; - - // TODO(lann): make this non-recursive and/or "flatten" templates - fn resolve_path(&self, path: &TreePath, depth: usize) -> Result { - let depth = depth + 1; - if depth > Self::RECURSION_LIMIT { - return Err(Error::InvalidTemplate(format!( - "hit recursion limit at path: {}", - path - ))); + async fn resolve_template(&self, template: &Template) -> Result { + let mut resolved_parts: Vec> = Vec::with_capacity(template.parts().len()); + for part in template.parts() { + resolved_parts.push(match part { + Part::Lit(lit) => lit.as_ref().into(), + Part::Expr(var) => self.resolve_variable(var).await?.into(), + }); } - let slot = self.tree.get(path)?; - // If we're resolving top-level config we are ready to query provider(s). - if path.size() == 1 { - let key = path.keys().next().unwrap(); - for provider in &self.providers { - if let Some(value) = provider.get(&key).map_err(Error::Provider)? { - return Ok(value); - } + Ok(resolved_parts.concat()) + } + + async fn resolve_variable(&self, key: &str) -> Result { + let var = self + .variables + .get(key) + // This should have been caught by validate_template + .ok_or_else(|| Error::InvalidKey(key.to_string()))?; + + for provider in &self.providers { + if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? { + return Ok(value); } } - // Resolve default template - if let Some(template) = &slot.default { - self.resolve_template(path, template, depth) - } else { - Err(Error::InvalidPath(format!( - "missing value at required path: {}", - path - ))) - } - } - fn resolve_template( - &self, - path: &TreePath, - template: &Template, - depth: usize, - ) -> Result { - template.parts().try_fold(String::new(), |value, part| { - Ok(match part { - Part::Lit(lit) => value + lit, - Part::Expr(expr) => { - let expr_path = if expr.starts_with('.') { - path.resolve_relative(expr)? - } else { - TreePath::new(expr.to_string())? - }; - value + &self.resolve_path(&expr_path, depth)? - } - }) + var.default.clone().ok_or_else(|| { + Error::Provider(anyhow::anyhow!( + "no provider resolved required variable {key:?}" + )) }) } + + fn validate_template(&self, template: String) -> Result