From 09039f341fd5f49b35abe12b07cde9f958dff926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20J=C3=A4ger?= Date: Thu, 17 Aug 2023 22:49:42 +0200 Subject: [PATCH 01/19] ci: toml location (#231) --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 441ced9..43d3487 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,8 @@ jobs: - name: Create PR to main env: NEW_VERSION: ${{ steps.bump_version.outputs.NEW_VERSION }} - CURRENT_VERSION: ${{ steps.read_current_version.outputs.CURRENT_VERSION }} + CURRENT_VERSION: ${{ steps.read_current_version.outputs.CURRENT_VERSION }} + CARGO_TOML: "cli/Cargo.toml" run: | awk -v current="$CURRENT_VERSION" -v new="$NEW_VERSION" '/version =/ && !found { gsub(current, new); found=1 } 1' "$CARGO_TOML" > tmpfile && mv tmpfile "$CARGO_TOML" git config user.email "bot@noops.io" From 445182f0807ea155c292ea49c968f7b2037c49d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20J=C3=A4ger?= Date: Fri, 18 Aug 2023 09:07:25 +0200 Subject: [PATCH 02/19] fix: cut /api from baseurl (#235) Co-authored-by: falumpaset --- cli/src/client.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index 116685c..89f8be2 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -185,6 +185,13 @@ impl NoopsClient { } fn get_module_url(&self, function_dto: &GetFunctionDTO) -> anyhow::Result { - Ok(self.base_url.join(&function_dto.id)?) + let mut server_url = self.base_url.clone(); + { + //remove the /api + let mut path_segments = server_url.path_segments_mut().unwrap(); + path_segments.pop_if_empty(); + path_segments.pop(); + } + Ok(server_url.join(&function_dto.id)?) } } From 5a288ba61d42795baa85623d2f945b06f4b541f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 09:22:30 +0200 Subject: [PATCH 03/19] Bump Cargo to 0.2.2-pre-0 (#233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump Cargo.toml Version to 0.2.2-pre-0 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Release Bot Co-authored-by: Marc Jäger --- cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fd1a9ec..f84ca6f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "noops" authors.workspace = true -version = "0.2.1-pre-0" +version = "0.2.2-pre-0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From e234d17bc40b8f0242b3e746ef11ca7e29ac6870 Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:55:53 +0200 Subject: [PATCH 04/19] quality: refactor project structure (#245) feat: allow subcommands to be executed on function Co-authored-by: Guillaume Fournier <> --- Cargo-component.lock | 3 - Cargo.lock | 182 +++++++++------- Cargo.toml | 8 +- cli/Cargo.toml | 7 +- cli/src/adapter/git.rs | 60 ------ cli/src/adapter/mod.rs | 46 ---- cli/src/bin/noops.rs | 16 ++ cli/src/{adapter => build}/cargo.rs | 0 cli/src/{adapter => build}/golang.rs | 0 cli/src/build/mod.rs | 104 +++++++++ cli/src/client.rs | 197 ----------------- cli/src/commands/build.rs | 23 ++ cli/src/commands/create.rs | 137 ++++++++++++ cli/src/commands/deploy.rs | 47 +++++ cli/src/commands/destroy.rs | 32 +++ cli/src/commands/function.rs | 51 ----- cli/src/commands/init.rs | 20 ++ cli/src/commands/login.rs | 27 ++- cli/src/commands/mod.rs | 31 ++- cli/src/commands/project.rs | 87 -------- cli/src/config.rs | 25 ++- cli/src/deploy/create.rs | 42 ++++ cli/src/deploy/delete.rs | 35 +++ cli/src/deploy/mod.rs | 89 ++++++++ cli/src/deploy/plan.rs | 105 +++++++++ cli/src/deploy/update.rs | 54 +++++ cli/src/filesystem.rs | 17 -- cli/src/handlers/diff.rs | 104 --------- cli/src/handlers/modules.rs | 51 ----- cli/src/handlers/project.rs | 199 ------------------ cli/src/lib.rs | 8 + cli/src/main.rs | 38 ---- cli/src/manifest.rs | 127 +++++++---- cli/src/module.rs | 67 ++++++ cli/src/modules.rs | 45 ---- cli/src/templates.rs | 10 +- cli/src/terminal.rs | 5 +- crates/client/Cargo.toml | 13 ++ .../handlers => crates/client/src}/auth.rs | 83 +++----- crates/client/src/function.rs | 105 +++++++++ .../mod.rs => crates/client/src/lib.rs | 3 +- crates/client/src/project.rs | 99 +++++++++ crates/{dtos => common}/Cargo.toml | 3 +- crates/common/src/dtos.rs | 98 +++++++++ crates/common/src/hash.rs | 10 + crates/common/src/lib.rs | 2 + crates/dtos/src/lib.rs | 37 ---- server/Cargo.toml | 21 +- server/diesel.toml | 2 +- .../2023-07-14-101830_create_functions/up.sql | 1 + server/src/bindgen.rs | 8 - server/src/controller/function.rs | 16 +- server/src/errors.rs | 2 +- server/src/executor.rs | 1 - server/src/main.rs | 3 +- server/src/repository/function.rs | 53 +++-- server/src/repository/mod.rs | 2 +- server/src/repository/project.rs | 12 +- server/src/repository/schema.rs | 1 + server/src/repository/user.rs | 2 +- server/src/service/auth.rs | 4 +- server/src/service/function.rs | 48 +++-- server/src/service/project.rs | 5 +- server/src/wasmstore.rs | 42 ++-- .../return-params/Cargo.toml | 0 .../return-params/src/lib.rs | 2 +- .../return-status-code-200/Cargo.toml | 0 .../return-status-code-200/src/lib.rs | 2 +- 68 files changed, 1543 insertions(+), 1236 deletions(-) delete mode 100644 Cargo-component.lock delete mode 100644 cli/src/adapter/git.rs delete mode 100644 cli/src/adapter/mod.rs create mode 100644 cli/src/bin/noops.rs rename cli/src/{adapter => build}/cargo.rs (100%) rename cli/src/{adapter => build}/golang.rs (100%) create mode 100644 cli/src/build/mod.rs delete mode 100644 cli/src/client.rs create mode 100644 cli/src/commands/build.rs create mode 100644 cli/src/commands/create.rs create mode 100644 cli/src/commands/deploy.rs create mode 100644 cli/src/commands/destroy.rs delete mode 100644 cli/src/commands/function.rs create mode 100644 cli/src/commands/init.rs delete mode 100644 cli/src/commands/project.rs create mode 100644 cli/src/deploy/create.rs create mode 100644 cli/src/deploy/delete.rs create mode 100644 cli/src/deploy/mod.rs create mode 100644 cli/src/deploy/plan.rs create mode 100644 cli/src/deploy/update.rs delete mode 100644 cli/src/filesystem.rs delete mode 100644 cli/src/handlers/diff.rs delete mode 100644 cli/src/handlers/modules.rs delete mode 100644 cli/src/handlers/project.rs create mode 100644 cli/src/lib.rs delete mode 100644 cli/src/main.rs create mode 100644 cli/src/module.rs delete mode 100644 cli/src/modules.rs create mode 100644 crates/client/Cargo.toml rename {cli/src/handlers => crates/client/src}/auth.rs (55%) create mode 100644 crates/client/src/function.rs rename cli/src/handlers/mod.rs => crates/client/src/lib.rs (53%) create mode 100644 crates/client/src/project.rs rename crates/{dtos => common}/Cargo.toml (75%) create mode 100644 crates/common/src/dtos.rs create mode 100644 crates/common/src/hash.rs create mode 100644 crates/common/src/lib.rs delete mode 100644 crates/dtos/src/lib.rs rename {crates/test-components => test-components}/return-params/Cargo.toml (100%) rename {crates/test-components => test-components}/return-params/src/lib.rs (95%) rename {crates/test-components => test-components}/return-status-code-200/Cargo.toml (100%) rename {crates/test-components => test-components}/return-status-code-200/src/lib.rs (91%) diff --git a/Cargo-component.lock b/Cargo-component.lock deleted file mode 100644 index 00bc239..0000000 --- a/Cargo-component.lock +++ /dev/null @@ -1,3 +0,0 @@ -# This file is automatically generated by cargo-component. -# It is not intended for manual editing. -version = 1 diff --git a/Cargo.lock b/Cargo.lock index a0a9e8b..8bc935b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,15 +118,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", @@ -141,9 +141,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.20" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", @@ -397,12 +397,30 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "oauth2", + "reqwest", +] + [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "diesel", + "serde", +] + [[package]] name = "console" version = "0.15.7" @@ -612,9 +630,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -622,35 +640,29 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.26", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn 2.0.26", + "syn 1.0.109", ] -[[package]] -name = "deranged" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" - [[package]] name = "dialoguer" version = "0.10.4" @@ -758,18 +770,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "dtos" -version = "0.1.0" -dependencies = [ - "serde", -] - [[package]] name = "either" -version = "1.9.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encode_unicode" @@ -826,6 +831,17 @@ dependencies = [ "libc", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -843,9 +859,9 @@ dependencies = [ [[package]] name = "faux" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b55a7f424e532314115b5cdc6d9711b15ac453bfe0dcfa212baebc5efacd60" +checksum = "7c3b5e56a69ca67c241191cd9d484e14fb0fe89f5e539c2e8448eafd1f65c1f0" dependencies = [ "faux_macros", "paste", @@ -853,14 +869,14 @@ dependencies = [ [[package]] name = "faux_macros" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15473d7f83b54a44826907af16ae5727eaacaf6e53b51474016d3efd9aa35d5" +checksum = "35c9bb4a2c13ffb3a93a39902aaf4e7190a1706a4779b6db0449aee433d26c4a" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.26", + "syn 1.0.109", "uuid", ] @@ -1120,6 +1136,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "host" version = "0.0.0" @@ -1162,9 +1187,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" @@ -1390,9 +1415,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "ittapi" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e0d0b7b3b53d92a7e8b80ede3400112a6b8b4c98d1f5b8b16bb787c780582c" +checksum = "2e648c437172ce7d3ac35ca11a068755072054826fa455a916b43524fa4a62a7" dependencies = [ "anyhow", "ittapi-sys", @@ -1401,9 +1426,9 @@ dependencies = [ [[package]] name = "ittapi-sys" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f8763c96e54e6d6a0dccc2990d8b5e33e3313aaeae6185921a3f4c1614a77c" +checksum = "a9b32a4d23f72548178dde54f3c12c6b6a08598e25575c0d0fa5bd861e0dc1a5" dependencies = [ "cc", ] @@ -1522,9 +1547,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "maybe-owned" @@ -1641,20 +1666,20 @@ dependencies = [ [[package]] name = "noops" -version = "0.2.1-pre-0" +version = "0.2.2-pre-0" dependencies = [ "anyhow", "clap", + "client", + "common", "console", "dialoguer", - "dtos", "env_logger", + "etcetera", "indicatif", "indoc", "lazy_static", "log", - "oauth2", - "reqwest", "serde", "serde_json", "serde_yaml", @@ -1669,9 +1694,9 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "common", "diesel", "diesel_migrations", - "dtos", "faux", "host", "jsonwebtoken", @@ -1892,18 +1917,18 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", @@ -2318,9 +2343,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" @@ -2598,9 +2623,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0" [[package]] name = "tempfile" @@ -2663,11 +2688,10 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ - "deranged", "itoa", "serde", "time-core", @@ -2682,9 +2706,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -2830,9 +2854,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" +checksum = "a8bd22a874a2d0b70452d5597b12c537331d49060824a95f49f108994f94aa4c" dependencies = [ "bitflags 2.3.3", "bytes", @@ -3014,9 +3038,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.4.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ "getrandom", ] @@ -3238,9 +3262,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.31.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" +checksum = "b2f8e9778e04cbf44f58acc301372577375a666b966c50b03ef46144f80436a8" dependencies = [ "leb128", ] @@ -3280,9 +3304,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.110.0" +version = "0.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dfcdb72d96f01e6c85b6bf20102e7423bdbaad5c337301bab2bbf253d26413c" +checksum = "76c956109dcb41436a39391139d9b6e2d0a5e0b158e1293ef352ec977e5e36c5" dependencies = [ "indexmap 2.0.0", "semver", @@ -3290,12 +3314,12 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.62" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd12ed4d96a984e4b598a17457f1126d01640cc7461afbb319642111ff9e7f" +checksum = "b76cb909fe3d9b0de58cee1f4072247e680ff5cc1558ccad2790a9de14a23993" dependencies = [ "anyhow", - "wasmparser 0.110.0", + "wasmparser 0.108.0", ] [[package]] @@ -3559,23 +3583,23 @@ dependencies = [ [[package]] name = "wast" -version = "62.0.1" +version = "61.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f" +checksum = "dc6b347851b52fd500657d301155c79e8c67595501d179cef87b6f04ebd25ac4" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.31.1", + "wasm-encoder 0.30.0", ] [[package]] name = "wat" -version = "1.0.69" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842e15861d203fb4a96d314b0751cdeaf0f6f8b35e8d81d2953af2af5e44e637" +checksum = "459e764d27c3ab7beba1ebd617cc025c7e76dea6e7c5ce3189989a970aea3491" dependencies = [ - "wast 62.0.1", + "wast 61.0.0", ] [[package]] @@ -3823,9 +3847,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.12" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83817bbecf72c73bad717ee86820ebf286203d2e04c3951f3cd538869c897364" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index d632089..c6b7ee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,13 @@ authors = ["Giom-fm Self { - GitAdapter { - adapter: BaseAdapter::new(PROGRAM), - } - } - - pub fn get_template(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { - self.clone_no_checkout(working_dir, working_dir)?; - self.sparse_checkout(working_dir, path)?; - self.checkout(working_dir)?; - Ok(()) - } - - fn clone_no_checkout(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { - let command = self.adapter.build_command( - working_dir, - &[ - "clone", - "--no-checkout", - REPOSITORY, - path.to_string_lossy().as_ref(), - ], - ); - self.adapter.execute(command)?; - Ok(()) - } - - fn sparse_checkout(&self, working_dir: &Path, subpath: &Path) -> anyhow::Result<()> { - let command = self - .adapter - .build_command(working_dir, &["sparse-checkout", "init", "cone"]); - self.adapter.execute(command)?; - - let command = self.adapter.build_command( - working_dir, - &["sparse-checkout", "set", subpath.to_string_lossy().as_ref()], - ); - self.adapter.execute(command)?; - Ok(()) - } - - fn checkout(&self, working_dir: &Path) -> anyhow::Result<()> { - let command = self - .adapter - .build_command(working_dir, &["checkout", "main"]); - self.adapter.execute(command)?; - Ok(()) - } -} diff --git a/cli/src/adapter/mod.rs b/cli/src/adapter/mod.rs deleted file mode 100644 index 31281b2..0000000 --- a/cli/src/adapter/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub mod cargo; -pub mod git; -pub mod golang; - -use anyhow::anyhow; -use std::{path::Path, process::Command}; - -pub struct BaseAdapter { - program: String, -} - -impl BaseAdapter { - pub fn new(program: &str) -> Self { - BaseAdapter { - program: program.to_string(), - } - } - - pub fn build_command(&self, path: &Path, args: &[&str]) -> Command { - let mut command = Command::new(self.program.clone()); - command.args(args).current_dir(path); - command - } - pub fn execute(&self, mut command: Command) -> anyhow::Result<()> { - let output = command.output()?; - - match output.status.code() { - Some(0) => { - log::debug!("{} succeeded!", command.get_program().to_string_lossy()); - Ok(()) - } - Some(code) => { - let stderr = String::from_utf8_lossy(&output.stderr); - let error_message = format!("Command failed with error code {}: {}", code, stderr); - log::error!("{}", error_message); - Err(anyhow!(error_message)) - } - None => { - let stderr = String::from_utf8_lossy(&output.stderr); - let error_message = format!("Command was terminated by a signal: {}", stderr); - log::error!("{}", error_message); - Err(anyhow!(error_message)) - } - } - } -} diff --git a/cli/src/bin/noops.rs b/cli/src/bin/noops.rs new file mode 100644 index 0000000..b334fd3 --- /dev/null +++ b/cli/src/bin/noops.rs @@ -0,0 +1,16 @@ +use clap::Parser; +use noops::commands::{self, Command}; + +fn main() -> anyhow::Result<()> { + env_logger::init(); + let cli = commands::Cli::parse(); + match &cli { + commands::Cli::Init(cmd) => cmd.execute()?, + commands::Cli::Login(cmd) => cmd.execute()?, + commands::Cli::Build(cmd) => cmd.execute()?, + commands::Cli::Create(cmd) => cmd.execute()?, + commands::Cli::Deploy(cmd) => cmd.execute()?, + commands::Cli::Destroy(cmd) => cmd.execute()?, + } + Ok(()) +} diff --git a/cli/src/adapter/cargo.rs b/cli/src/build/cargo.rs similarity index 100% rename from cli/src/adapter/cargo.rs rename to cli/src/build/cargo.rs diff --git a/cli/src/adapter/golang.rs b/cli/src/build/golang.rs similarity index 100% rename from cli/src/adapter/golang.rs rename to cli/src/build/golang.rs diff --git a/cli/src/build/mod.rs b/cli/src/build/mod.rs new file mode 100644 index 0000000..6a01c35 --- /dev/null +++ b/cli/src/build/mod.rs @@ -0,0 +1,104 @@ +pub mod cargo; +pub mod golang; + +use crate::{manifest::Manifest, module::FunctionMetadata, terminal::Terminal}; +use anyhow::anyhow; +use anyhow::Context; + +use common::dtos::Language; +use std::{path::Path, process::Command}; + +use self::cargo::CargoAdapter; +use self::golang::GolangAdapter; + +#[derive(Clone, Debug, Default)] +pub struct BaseAdapter { + program: String, +} + +impl BaseAdapter { + pub fn new(program: &str) -> Self { + BaseAdapter { + program: program.to_string(), + } + } + + pub fn build_command(&self, path: &Path, args: &[&str]) -> Command { + let mut command = Command::new(self.program.clone()); + command.args(args).current_dir(path); + command + } + + pub fn execute(&self, mut command: Command) -> anyhow::Result<()> { + let output = command.output()?; + + match output.status.code() { + Some(0) => { + log::debug!("{} succeeded!", command.get_program().to_string_lossy()); + Ok(()) + } + Some(code) => { + let stderr = String::from_utf8_lossy(&output.stderr); + let error_message = format!("Command failed with error code {}: {}", code, stderr); + log::error!("{}", error_message); + Err(anyhow!(error_message)) + } + None => { + let stderr = String::from_utf8_lossy(&output.stderr); + let error_message = format!("Command was terminated by a signal: {}", stderr); + log::error!("{}", error_message); + Err(anyhow!(error_message)) + } + } + } +} + +pub fn build_project(terminal: &Terminal, manifest: &Manifest) -> anyhow::Result<()> { + terminal.write_heading("Building project")?; + + if manifest.functions.is_empty() { + terminal.write_text("No modules to build")?; + return Ok(()); + } + + for (i, module) in manifest.functions.iter().enumerate() { + let prefix = format!("[{}/{}]", i + 1, manifest.functions.len()); + let spinner = terminal.spinner_with_prefix(prefix, &module.name); + + build(module).context(format!("Building module \"{}\" failed", &module.name))?; + spinner.finish_with_message(module.name.clone()); + } + Ok(()) +} + +pub fn build_function(terminal: &Terminal, manifest: &Manifest, name: &str) -> anyhow::Result<()> { + terminal.write_heading("Building function")?; + + let text = format!("Building {}", name); + let spinner = terminal.spinner(&text); + build_by_name(name, manifest).context(format!("Building module \"{}\" failed", name))?; + spinner.finish_with_message(text); + Ok(()) +} + +pub fn build_by_name(name: &str, manifest: &Manifest) -> anyhow::Result<()> { + let module = manifest + .get_module_by_name(name) + .ok_or(anyhow::anyhow!("Module not found"))?; + build(&module)?; + Ok(()) +} + +pub fn build(metadata: &FunctionMetadata) -> anyhow::Result<()> { + match metadata.language { + Language::Rust => { + let cargo = CargoAdapter::new(); + cargo.build(Path::new(&metadata.name))?; + } + Language::Golang => { + let go = GolangAdapter::new(); + go.build(Path::new(&metadata.name))?; + } + } + Ok(()) +} diff --git a/cli/src/client.rs b/cli/src/client.rs deleted file mode 100644 index 89f8be2..0000000 --- a/cli/src/client.rs +++ /dev/null @@ -1,197 +0,0 @@ -use anyhow::Ok; -use dtos::{GetFunctionDTO, GetJWTDTO}; -use reqwest::blocking::Client; -use reqwest::{header::AUTHORIZATION, StatusCode, Url}; - -pub struct NoopsClient { - pub project: String, - base_url: Url, - client: Client, - jwt: Option, -} - -impl NoopsClient { - pub fn new(base_url: Url, project: String, jwt: Option) -> Self { - NoopsClient { - project, - base_url, - client: Client::new(), - jwt, - } - } - - pub fn login(&self, gh_token: &str) -> anyhow::Result { - let mut url = self.base_url.join("auth/login")?; - url.set_query(Some(&format!("token={}", gh_token))); - let response: GetJWTDTO = self.client.get(url).send()?.json()?; - Ok(response.jwt) - } - - pub fn project_get(&self) -> anyhow::Result { - let url = self.get_project_path()?; - let jwt = self.jwt.clone().unwrap(); - let response = self - .client - .get(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - Ok(response.json()?) - } - - pub fn project_create(&self) -> anyhow::Result<()> { - let url = self.get_project_path()?; - let jwt = self.jwt.clone().unwrap(); - - let response = self - .client - .post(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - Ok(()) - } - - pub fn project_delete(&self) -> anyhow::Result<()> { - let url = self.get_project_path()?; - let jwt = self.jwt.clone().unwrap(); - - let response = self - .client - .delete(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - Ok(()) - } - - pub fn project_exists(&self) -> anyhow::Result { - let url = self.get_project_path()?; - let jwt = self.jwt.clone().unwrap(); - let response = self - .client - .get(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .send()?; - - if !response.status().is_success() && response.status() != StatusCode::NOT_FOUND { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()? - ); - } - - Ok(response.status().is_success() && response.status() != StatusCode::NOT_FOUND) - } - - fn get_project_path(&self) -> anyhow::Result { - Ok(self.base_url.join(&self.project)?) - } - - pub fn module_create(&self, module_name: &str, wasm: &[u8]) -> anyhow::Result { - let url = self.get_module_path(module_name)?; - let payload = dtos::CreateFunctionDTO { - wasm: wasm.to_owned(), - }; - let jwt = self.jwt.clone().unwrap(); - let response = self - .client - .put(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .json(&payload) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - let module_url = self.get_module_url(&response.json()?)?; - Ok(module_url) - } - - pub fn module_update(&self, module_name: &str, wasm: &[u8]) -> anyhow::Result<()> { - let url = self.get_module_path(module_name)?; - let payload = dtos::CreateFunctionDTO { - wasm: wasm.to_owned(), - }; - let jwt = self.jwt.clone().unwrap(); - let response = self - .client - .put(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .json(&payload) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - Ok(()) - } - - pub fn module_delete(&self, module_name: &str) -> anyhow::Result<()> { - let url = self.get_module_path(module_name)?; - let jwt = self.jwt.clone().unwrap(); - - let response = self - .client - .delete(url) - .header(AUTHORIZATION, format!("Bearer {}", jwt)) - .send()?; - - if !response.status().is_success() { - anyhow::bail!( - "Request failed with status code {}: {}", - response.status(), - response.text()?, - ); - } - Ok(()) - } - - fn get_module_path(&self, module_name: &str) -> anyhow::Result { - Ok(self - .base_url - .join(&format!("{}/", self.project))? - .join(module_name)?) - } - - fn get_module_url(&self, function_dto: &GetFunctionDTO) -> anyhow::Result { - let mut server_url = self.base_url.clone(); - { - //remove the /api - let mut path_segments = server_url.path_segments_mut().unwrap(); - path_segments.pop_if_empty(); - path_segments.pop(); - } - Ok(server_url.join(&function_dto.id)?) - } -} diff --git a/cli/src/commands/build.rs b/cli/src/commands/build.rs new file mode 100644 index 0000000..3ebeca6 --- /dev/null +++ b/cli/src/commands/build.rs @@ -0,0 +1,23 @@ +use super::Command; +use crate::{build, config::Config, manifest::Manifest, terminal::Terminal}; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct BuildCommand { + pub name: Option, +} + +impl Command for BuildCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + let manifest = Manifest::from_yaml(&config.manifest_path)?; + + match self.name.clone() { + Some(name) => build::build_function(&terminal, &manifest, &name)?, + None => build::build_project(&terminal, &manifest)?, + } + + Ok(()) + } +} diff --git a/cli/src/commands/create.rs b/cli/src/commands/create.rs new file mode 100644 index 0000000..a947c39 --- /dev/null +++ b/cli/src/commands/create.rs @@ -0,0 +1,137 @@ +use super::Command; +use crate::{ + build::BaseAdapter, + config::Config, + manifest::Manifest, + module::FunctionMetadata, + templates::{Template, TEMPLATES}, + terminal::Terminal, +}; +use anyhow::Context; +use clap::Parser; +use std::fs; +use std::path::Path; +use tempfile::tempdir; +use walkdir::WalkDir; + +const PROGRAM: &str = "git"; +const REPOSITORY: &str = "https://github.com/JFComputing/noops-templates.git"; + +#[derive(Parser, Debug)] +pub struct CreateCommand { + pub name: String, +} + +impl Command for CreateCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + let git = GitAdapter::new(); + + let mut manifest = Manifest::from_yaml(&config.manifest_path)?; + + let index = terminal.select_prompt("Select a template", &TEMPLATES)?; + let template = Template::new(self.name.clone(), index); + + let text = format!("Adding {}", &template.name); + let spinner = terminal.spinner(&text); + create(&mut manifest, &git, &template) + .context(format!("Creating module \"{}\" failed", self.name))?; + spinner.finish_with_message(text); + + Ok(()) + } +} + +pub fn create( + manifest: &mut Manifest, + git: &GitAdapter, + template: &Template, +) -> anyhow::Result<()> { + if manifest.get_module_by_name(&template.name).is_some() { + anyhow::bail!("Module already exists"); + } + let to = Path::new(&template.name); + if to.exists() { + anyhow::bail!("Module target path is not empty"); + } + + let temp_dir = tempdir()?; + git.checkout_template(temp_dir.path(), &template.subpath)?; + copy_dir(&temp_dir.path().join(&template.subpath), to)?; + + let module = FunctionMetadata::from_template(template); + manifest.add_module(module)?; + Ok(()) +} + +pub fn copy_dir(from: &Path, to: &Path) -> anyhow::Result<()> { + for entry in WalkDir::new(from).into_iter().filter_map(Result::ok) { + let file_type = entry.file_type(); + let current_path = entry.path().strip_prefix(from)?; + let target_path = to.join(current_path); + + if file_type.is_dir() { + fs::create_dir_all(target_path)?; + } else if file_type.is_file() { + fs::copy(entry.path(), target_path)?; + } + } + Ok(()) +} + +#[derive(Clone, Debug, Default)] +pub struct GitAdapter { + adapter: BaseAdapter, +} + +impl GitAdapter { + pub fn new() -> Self { + GitAdapter { + adapter: BaseAdapter::new(PROGRAM), + } + } + + pub fn checkout_template(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { + self.clone_no_checkout(working_dir, working_dir)?; + self.sparse_checkout(working_dir, path)?; + self.checkout(working_dir)?; + Ok(()) + } + + fn clone_no_checkout(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { + let command = self.adapter.build_command( + working_dir, + &[ + "clone", + "--no-checkout", + REPOSITORY, + path.to_string_lossy().as_ref(), + ], + ); + self.adapter.execute(command)?; + Ok(()) + } + + fn sparse_checkout(&self, working_dir: &Path, subpath: &Path) -> anyhow::Result<()> { + let command = self + .adapter + .build_command(working_dir, &["sparse-checkout", "init", "cone"]); + self.adapter.execute(command)?; + + let command = self.adapter.build_command( + working_dir, + &["sparse-checkout", "set", subpath.to_string_lossy().as_ref()], + ); + self.adapter.execute(command)?; + Ok(()) + } + + fn checkout(&self, working_dir: &Path) -> anyhow::Result<()> { + let command = self + .adapter + .build_command(working_dir, &["checkout", "main"]); + self.adapter.execute(command)?; + Ok(()) + } +} diff --git a/cli/src/commands/deploy.rs b/cli/src/commands/deploy.rs new file mode 100644 index 0000000..e246ac9 --- /dev/null +++ b/cli/src/commands/deploy.rs @@ -0,0 +1,47 @@ +use std::{fs::File, io::Read, path::Path}; + +use super::Command; +use crate::{config::Config, deploy, manifest::Manifest, terminal::Terminal}; +use clap::Parser; +use client::{function::FunctionClient, project::ProjectClient}; + +#[derive(Parser, Debug)] +pub struct DeployCommand { + pub name: Option, +} + +impl Command for DeployCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + let manifest = Manifest::from_yaml(&config.manifest_path)?; + + let jwt = get_jwt(&config.jwt_file)?.ok_or(anyhow::anyhow!("You are not logged in"))?; + + let function_client = FunctionClient::new(&config.base_url, jwt.clone()); + let project_client = ProjectClient::new(&config.base_url, jwt); + + match self.name.clone() { + Some(name) => deploy::deploy_function( + &name, + &terminal, + manifest, + &project_client, + &function_client, + )?, + None => deploy::deploy_project(&terminal, manifest, &project_client, &function_client)?, + } + + Ok(()) + } +} + +pub fn get_jwt(path: &Path) -> anyhow::Result> { + if !path.exists() { + return Ok(None); + } + let mut jwt = String::default(); + let mut file = File::open(path)?; + file.read_to_string(&mut jwt)?; + Ok(Some(jwt)) +} diff --git a/cli/src/commands/destroy.rs b/cli/src/commands/destroy.rs new file mode 100644 index 0000000..a9abdc7 --- /dev/null +++ b/cli/src/commands/destroy.rs @@ -0,0 +1,32 @@ +use std::{fs, path::Path}; + +use super::Command; +use crate::{config::Config, manifest::Manifest, terminal::Terminal}; +use anyhow::Context; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct DestroyCommand { + pub name: String, +} + +impl Command for DestroyCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + let mut manifest = Manifest::from_yaml(&config.manifest_path)?; + + let text = format!("Removing {}", &self.name); + let spinner = terminal.spinner(&text); + destroy(&self.name, &mut manifest) + .context(format!("Destroying module \"{}\" failed", self.name))?; + spinner.finish_with_message(text); + Ok(()) + } +} + +pub fn destroy(name: &str, manifest: &mut Manifest) -> anyhow::Result<()> { + manifest.delete_module_by_name(name)?; + fs::remove_dir_all(Path::new(name))?; + Ok(()) +} diff --git a/cli/src/commands/function.rs b/cli/src/commands/function.rs deleted file mode 100644 index 22e1cc2..0000000 --- a/cli/src/commands/function.rs +++ /dev/null @@ -1,51 +0,0 @@ -use clap::{Parser, Subcommand}; - -use crate::{ - adapter::git::GitAdapter, - handlers, - manifest::{self, Manifest}, - terminal::Terminal, -}; - -use super::Command; - -#[derive(Subcommand)] -pub enum FunctionCommands { - /// Create a new function - Create(FunctionCreateCommand), - /// Build the function - Build, - /// Deploy the function - Deploy, - /// Destroy the function - Destroy(FunctionDestroyCommand), - /// Show information about the function - Show, -} - -#[derive(Parser, Debug)] -pub struct FunctionCreateCommand; - -impl Command for FunctionCreateCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; - - let git = GitAdapter::new(); - handlers::modules::add(&terminal, manifest, &git)?; - Ok(()) - } -} - -#[derive(Parser, Debug)] -pub struct FunctionDestroyCommand; - -impl Command for FunctionDestroyCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; - - handlers::modules::delete(&terminal, manifest)?; - Ok(()) - } -} diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs new file mode 100644 index 0000000..8c264d8 --- /dev/null +++ b/cli/src/commands/init.rs @@ -0,0 +1,20 @@ +use super::Command; +use crate::{config::Config, manifest::Manifest, terminal::Terminal}; +use anyhow::Context; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct InitCommand { + pub name: String, +} + +impl Command for InitCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + + Manifest::init(&self.name, &config.manifest_path).context("Initializing project failed")?; + terminal.write_text(format!("{} successfully initialized", &self.name))?; + Ok(()) + } +} diff --git a/cli/src/commands/login.rs b/cli/src/commands/login.rs index 13deaec..54c1a3a 100644 --- a/cli/src/commands/login.rs +++ b/cli/src/commands/login.rs @@ -1,27 +1,26 @@ -use clap::Parser; - -use crate::{ - client::NoopsClient, - config::Config, - handlers, - manifest::{self, Manifest}, - terminal::Terminal, -}; +use std::{fs::File, io::Write, path::Path}; use super::Command; +use crate::config::Config; +use clap::Parser; +use client::auth::AuthClient; #[derive(Parser, Debug)] pub struct LoginCommand; impl Command for LoginCommand { fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; let config = Config::default(); - - let client = NoopsClient::new(config.base_url, manifest.project_name, None); - handlers::auth::login(&client, &terminal, &config.jwt_file)?; + let auth_client = AuthClient::new(&config.base_url); + let jwt = auth_client.login()?; + set_jwt(&config.jwt_file, &jwt)?; Ok(()) } } + +fn set_jwt(path: &Path, jwt: &str) -> anyhow::Result<()> { + let mut file = File::create(path)?; + file.write_all(jwt.as_bytes())?; + Ok(()) +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index b9fd27b..89d61e7 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,8 +1,14 @@ -pub mod function; +pub mod build; +pub mod create; +pub mod deploy; +pub mod destroy; +pub mod init; pub mod login; -pub mod project; -use self::{function::FunctionCommands, login::LoginCommand, project::ProjectCommands}; +use self::{ + build::BuildCommand, create::CreateCommand, deploy::DeployCommand, destroy::DestroyCommand, + init::InitCommand, login::LoginCommand, +}; use clap::Parser; pub trait Command { @@ -13,14 +19,21 @@ pub trait Command { #[clap(author, version, about = "noops cli", long_about = None)] #[clap(propagate_version = true)] pub enum Cli { + /// Initialise a project + Init(InitCommand), + /// Login in the noops cloud Login(LoginCommand), - /// Project subcommands - #[clap(subcommand)] - Project(ProjectCommands), + /// Create a function + Create(CreateCommand), + + /// Build the project or a function + Build(BuildCommand), + + /// Deploy the project or a function + Deploy(DeployCommand), - /// Function subcommands - #[clap(subcommand)] - Function(FunctionCommands), + /// Destroy a function + Destroy(DestroyCommand), } diff --git a/cli/src/commands/project.rs b/cli/src/commands/project.rs deleted file mode 100644 index e37516e..0000000 --- a/cli/src/commands/project.rs +++ /dev/null @@ -1,87 +0,0 @@ -use super::Command; -use crate::{ - client::NoopsClient, - config::Config, - handlers, - manifest::{self, Manifest}, - terminal::Terminal, -}; -use clap::{Parser, Subcommand}; - -#[derive(Subcommand)] -pub enum ProjectCommands { - /// Create a new project - Create(ProjectCreateCommand), - /// Build the project - Build(ProjectBuildCommand), - /// Deploy the function - Deploy(ProjectDeployCommand), - /// Destroy the project - Destroy(ProjectDestroyCommand), - /// Show information about the project - Show, -} - -#[derive(Parser, Debug)] -pub struct ProjectCreateCommand; - -impl Command for ProjectCreateCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - handlers::project::init(&terminal)?; - Ok(()) - } -} - -#[derive(Parser, Debug)] -pub struct ProjectBuildCommand; - -impl Command for ProjectBuildCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; - handlers::project::build(&terminal, &manifest)?; - Ok(()) - } -} - -#[derive(Parser, Debug)] -pub struct ProjectDeployCommand; - -impl Command for ProjectDeployCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; - let config = Config::default(); - - let jwt = handlers::auth::get_jwt(&config.jwt_file)?; - if jwt.is_none() { - terminal.write_text("You are not logged in. \nPlease do so with \"noops login\"")?; - return Ok(()); - } - let client = NoopsClient::new(config.base_url, manifest.project_name.clone(), jwt); - handlers::project::deploy(&terminal, &manifest, &client)?; - Ok(()) - } -} - -#[derive(Parser, Debug)] -pub struct ProjectDestroyCommand; - -impl Command for ProjectDestroyCommand { - fn execute(&self) -> anyhow::Result<()> { - let terminal = Terminal::new(); - let manifest = Manifest::from_yaml(manifest::MANIFEST_FILE_NAME)?; - let config = Config::default(); - - let jwt = handlers::auth::get_jwt(&config.jwt_file)?; - if jwt.is_none() { - terminal.write_text("You are not logged in.")?; - return Ok(()); - } - let client = NoopsClient::new(config.base_url, manifest.project_name.clone(), jwt); - handlers::project::destroy(&terminal, &client, &manifest.project_name)?; - - Ok(()) - } -} diff --git a/cli/src/config.rs b/cli/src/config.rs index 20fe2e2..1a26a40 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -1,27 +1,30 @@ -use reqwest::Url; +use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; use std::{ - env, fs, + fs, path::{Path, PathBuf}, }; pub struct Config { pub jwt_file: PathBuf, - pub base_url: Url, + pub base_url: String, + pub manifest_path: PathBuf, } impl Default for Config { fn default() -> Self { - let xdg_config_home = env::var("XDG_CONFIG_HOME").unwrap_or_else(|_| { - let home = env::var("HOME").expect("HOME directory not set"); - format!("{}/.config", home) - }); + let strategy = choose_app_strategy(AppStrategyArgs { + top_level_domain: "io".to_string(), + author: "Noops".to_string(), + app_name: "noops".to_string(), + }) + .unwrap(); - let config_path = Path::new(&xdg_config_home).join("noops"); - fs::create_dir_all(config_path.clone()).unwrap(); + fs::create_dir_all(strategy.cache_dir()).unwrap(); Self { - jwt_file: config_path.join("jwt"), - base_url: Url::parse("http://localhost:8080/api/").unwrap(), + jwt_file: strategy.in_cache_dir("jwt"), + base_url: "http://localhost:8080/api/".to_string(), + manifest_path: Path::new("./noops.yaml").to_path_buf(), } } } diff --git a/cli/src/deploy/create.rs b/cli/src/deploy/create.rs new file mode 100644 index 0000000..cfc2d8a --- /dev/null +++ b/cli/src/deploy/create.rs @@ -0,0 +1,42 @@ +use crate::module::{self, BuildFunctionMetadata}; +use client::function::FunctionClient; +use common::dtos::CreateFunctionDTO; +use console::style; +use std::{collections::HashSet, fmt::Display}; + +use super::DeployStep; + +#[derive(Debug, Clone, Default)] +pub struct CreateStep(pub BuildFunctionMetadata); + +impl DeployStep for CreateStep { + fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { + let function = CreateFunctionDTO { + name: self.0.name.clone(), + language: self.0.language, + wasm: module::wasm(&self.0.name)?, + }; + + client.create(project, &function)?; + Ok(()) + } +} + +impl Display for CreateStep { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let text = format!("\t+ {}", &self.0.name); + let text = style(text.as_str()).green(); + f.write_str(&text.to_string()) + } +} + +pub fn create_steps( + local_modules: &HashSet, + remote_modules: &HashSet, +) -> Vec { + local_modules + .difference(remote_modules) + .cloned() + .map(CreateStep) + .collect() +} diff --git a/cli/src/deploy/delete.rs b/cli/src/deploy/delete.rs new file mode 100644 index 0000000..41f4c2c --- /dev/null +++ b/cli/src/deploy/delete.rs @@ -0,0 +1,35 @@ +use crate::module::BuildFunctionMetadata; +use client::function::FunctionClient; +use console::style; +use std::{collections::HashSet, fmt::Display}; + +use super::DeployStep; + +#[derive(Debug, Clone, Default)] +pub struct DeleteStep(pub BuildFunctionMetadata); + +impl DeployStep for DeleteStep { + fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { + client.delete(project, &self.0.name)?; + Ok(()) + } +} + +impl Display for DeleteStep { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let text = format!("\t- {}", &self.0.name); + let text = style(text.as_str()).red(); + f.write_str(&text.to_string()) + } +} + +pub fn delete_steps( + local_modules: &HashSet, + remote_modules: &HashSet, +) -> Vec { + remote_modules + .difference(local_modules) + .cloned() + .map(DeleteStep) + .collect() +} diff --git a/cli/src/deploy/mod.rs b/cli/src/deploy/mod.rs new file mode 100644 index 0000000..a2e05f4 --- /dev/null +++ b/cli/src/deploy/mod.rs @@ -0,0 +1,89 @@ +use self::plan::DeployPlan; +use crate::{manifest::Manifest, module::BuildFunctionMetadata, terminal::Terminal}; +use client::{function::FunctionClient, project::ProjectClient}; + +mod create; +mod delete; +mod plan; +mod update; + +trait DeployStep { + fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()>; +} + +pub fn deploy_project( + terminal: &Terminal, + manifest: Manifest, + project_client: &ProjectClient, + function_client: &FunctionClient, +) -> anyhow::Result<()> { + terminal.write_heading("Deploying project")?; + + let project = manifest.project_name; + if !project_client.exists(&project)? { + project_client.create(&project)?; + }; + + let local_functions: Vec = manifest + .functions + .into_iter() + .map(BuildFunctionMetadata::from) + .collect(); + + let remote_functions: Vec = project_client + .get(&project)? + .functions + .into_iter() + .map(BuildFunctionMetadata::from) + .collect(); + + let plan = DeployPlan::new(local_functions, remote_functions); + prompt_deploy(&plan, terminal, function_client, &project)?; + + Ok(()) +} + +pub fn deploy_function( + name: &str, + terminal: &Terminal, + manifest: Manifest, + project_client: &ProjectClient, + function_client: &FunctionClient, +) -> anyhow::Result<()> { + terminal.write_heading("Deploying function")?; + + let project = manifest.project_name.clone(); + if !project_client.exists(&project)? { + project_client.create(&project)?; + }; + + let local_function: BuildFunctionMetadata = manifest + .get_module_by_name(name) + .ok_or(anyhow::anyhow!("Module not found"))? + .into(); + let remote_function: BuildFunctionMetadata = function_client.read(&project, name)?.into(); + + let plan = DeployPlan::new(vec![local_function], vec![remote_function]); + prompt_deploy(&plan, terminal, function_client, &project)?; + Ok(()) +} + +fn prompt_deploy( + plan: &DeployPlan, + terminal: &Terminal, + function_client: &FunctionClient, + project: &str, +) -> anyhow::Result<()> { + if plan.has_steps() { + terminal.write_text(plan.to_string())?; + let response = terminal.confirm_prompt("Deploy?")?; + if response { + plan.deploy(terminal, project, function_client)?; + } else { + terminal.write_text("Aborting")?; + } + } else { + terminal.write_text("Nothing to deploy")?; + } + Ok(()) +} diff --git a/cli/src/deploy/plan.rs b/cli/src/deploy/plan.rs new file mode 100644 index 0000000..0f33255 --- /dev/null +++ b/cli/src/deploy/plan.rs @@ -0,0 +1,105 @@ +use super::{ + create::{self, CreateStep}, + delete::{self, DeleteStep}, + update::{self, UpdateStep}, + DeployStep, +}; +use crate::{module::BuildFunctionMetadata, terminal::Terminal}; +use client::function::FunctionClient; +use console::style; +use std::{collections::HashSet, fmt::Display}; + +#[derive(Debug, Clone, Default)] +pub struct DeployPlan { + steps: usize, + create_steps: Vec, + update_steps: Vec, + delete_steps: Vec, +} + +impl DeployPlan { + pub fn new( + local_modules: Vec, + remote_modules: Vec, + ) -> Self { + let local_modules: HashSet = HashSet::from_iter(local_modules); + let remote_modules: HashSet = HashSet::from_iter(remote_modules); + + let create_steps = create::create_steps(&local_modules, &remote_modules); + let update_steps = update::update_steps(&local_modules, &remote_modules); + let delete_steps = delete::delete_steps(&local_modules, &remote_modules); + + Self { + steps: create_steps.len() + update_steps.len() + delete_steps.len(), + create_steps, + update_steps, + delete_steps, + } + } + + pub fn has_steps(&self) -> bool { + self.steps > 0 + } + + pub fn deploy( + &self, + terminal: &Terminal, + project: &str, + client: &FunctionClient, + ) -> anyhow::Result<()> { + let mut step = 1; + for create_step in &self.create_steps { + let prefix = format!("[{}/{}]", step, self.steps); + let message = format!("Creating module {}", &create_step.0.name); + let spinner = terminal.spinner_with_prefix(prefix, &message); + create_step.deploy(project, client)?; + spinner.finish_with_message(message); + step += 1; + } + + for update_step in &self.update_steps { + let prefix = format!("[{}/{}]", step, self.steps); + let message = format!("Updating module {}", &update_step.0.name); + let spinner = terminal.spinner_with_prefix(prefix, &message); + update_step.deploy(project, client)?; + spinner.finish_with_message(message); + step += 1; + } + + for delete_step in &self.delete_steps { + let prefix = format!("[{}/{}]", step, self.steps); + let message = format!("Deleting module {}", &delete_step.0.name); + let spinner = terminal.spinner_with_prefix(prefix, &message); + delete_step.deploy(project, client)?; + spinner.finish_with_message(message); + step += 1; + } + + Ok(()) + } +} + +impl Display for DeployPlan { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.has_steps() { + return f.write_str("No changes"); + } + + let text = style("Changes:\n").bold(); + f.write_str(&text.to_string())?; + + for create_step in &self.create_steps { + f.write_fmt(format_args!("{}\n", &create_step))?; + } + + for update_step in &self.update_steps { + f.write_fmt(format_args!("{}\n", &update_step))?; + } + + for delete_step in &self.delete_steps { + f.write_fmt(format_args!("{}\n", &delete_step))?; + } + + Ok(()) + } +} diff --git a/cli/src/deploy/update.rs b/cli/src/deploy/update.rs new file mode 100644 index 0000000..bf25f48 --- /dev/null +++ b/cli/src/deploy/update.rs @@ -0,0 +1,54 @@ +use super::DeployStep; +use crate::module::{self, BuildFunctionMetadata}; +use client::function::FunctionClient; +use common::dtos::CreateFunctionDTO; +use console::style; +use std::{collections::HashSet, fmt::Display}; + +#[derive(Debug, Clone, Default)] +pub struct UpdateStep(pub BuildFunctionMetadata); + +impl DeployStep for UpdateStep { + fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { + let function = CreateFunctionDTO { + name: self.0.name.clone(), + language: self.0.language, + wasm: module::wasm(&self.0.name)?, + }; + + client.update(project, &function)?; + Ok(()) + } +} + +impl Display for UpdateStep { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let text = format!("\t~ {}", &self.0.name); + let text = style(text.as_str()).yellow(); + f.write_str(&text.to_string()) + } +} + +pub fn update_steps( + local_modules: &HashSet, + remote_modules: &HashSet, +) -> Vec { + let mut local_updates: Vec = local_modules + .intersection(remote_modules) + .cloned() + .collect(); + local_updates.sort(); + + let mut remote_updates: Vec = remote_modules + .intersection(local_modules) + .cloned() + .collect(); + remote_updates.sort(); + + local_updates + .iter() + .zip(remote_updates.iter()) + .filter(|(local_module, remote_module)| local_module.hash != remote_module.hash) + .map(|(local, _)| UpdateStep(local.clone())) + .collect() +} diff --git a/cli/src/filesystem.rs b/cli/src/filesystem.rs deleted file mode 100644 index 6d2bba7..0000000 --- a/cli/src/filesystem.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::{fs, path::Path}; -use walkdir::WalkDir; - -pub fn copy_dir(from: &Path, to: &Path) -> anyhow::Result<()> { - for entry in WalkDir::new(from).into_iter().filter_map(Result::ok) { - let file_type = entry.file_type(); - let current_path = entry.path().strip_prefix(from)?; - let target_path = to.join(current_path); - - if file_type.is_dir() { - fs::create_dir_all(target_path)?; - } else if file_type.is_file() { - fs::copy(entry.path(), target_path)?; - } - } - Ok(()) -} diff --git a/cli/src/handlers/diff.rs b/cli/src/handlers/diff.rs deleted file mode 100644 index 69e1b65..0000000 --- a/cli/src/handlers/diff.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, - path::Path, -}; - -use crate::modules::Module; -use dtos::GetFunctionDTO; - -type Update = (String, Vec); -type Create = (String, Vec); -type Remove = String; -type NotBuild = String; - -#[derive(Default, Debug)] -pub struct ModuleDiff { - pub create: Vec, - pub update: Vec, - pub remove: Vec, - pub not_build: Vec, -} - -impl ModuleDiff { - pub fn new( - local_modules: &[Module], - remote_modules: &[GetFunctionDTO], - ) -> anyhow::Result { - let (create, update, not_build) = Self::create_and_update(local_modules, remote_modules)?; - let remove = Self::remove(local_modules, remote_modules)?; - Ok(Self { - create, - update, - remove, - not_build, - }) - } - - fn create_and_update( - local_modules: &[Module], - remote_modules: &[GetFunctionDTO], - ) -> anyhow::Result<(Vec, Vec, Vec)> { - let mut create: Vec = Default::default(); - let mut update: Vec = Default::default(); - let mut not_build: Vec = Default::default(); - - for local_module in local_modules { - let remote_module = remote_modules - .iter() - .find(|&remote_module| remote_module.name == local_module.name); - - let module_out_path = Path::new(&local_module.name) - .join("out") - .join("handler.wasm"); - - if !module_out_path.exists() { - not_build.push(local_module.name.clone()); - } else { - let wasm = std::fs::read(module_out_path)?; - match remote_module { - Some(remote_module) => { - if remote_module.hash != Self::hash(&wasm) { - update.push((local_module.name.clone(), wasm)); - } - } - None => create.push((local_module.name.clone(), wasm)), - } - } - } - Ok((create, update, not_build)) - } - - fn remove( - local_modules: &[Module], - remote_modules: &[GetFunctionDTO], - ) -> anyhow::Result> { - let mut remove: Vec = Default::default(); - - for remote_module in remote_modules { - let module_remove = local_modules - .iter() - .find(|&local_module| remote_module.name == local_module.name); - - if module_remove.is_none() { - remove.push(remote_module.name.clone()); - } - } - - Ok(remove) - } - - fn hash(wasm: &[u8]) -> String { - let mut hasher = DefaultHasher::new(); - wasm.hash(&mut hasher); - hasher.finish().to_string() - } - - pub fn has_changes(&self) -> bool { - !(self.create.is_empty() && self.update.is_empty() && self.remove.is_empty()) - } - - pub fn has_not_builds(&self) -> bool { - !self.not_build.is_empty() - } -} diff --git a/cli/src/handlers/modules.rs b/cli/src/handlers/modules.rs deleted file mode 100644 index 1193b5a..0000000 --- a/cli/src/handlers/modules.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::{ - adapter::git::GitAdapter, filesystem, manifest::Manifest, modules::Module, - templates::TEMPLATES, terminal::Terminal, -}; -use std::{fs, path::Path}; -use tempfile::tempdir; - -pub fn delete(term: &Terminal, mut manifest: Manifest) -> anyhow::Result<()> { - term.write_heading("Removing a module")?; - - if manifest.modules.is_empty() { - term.write_text("No modules to remove")?; - return Ok(()); - } - - let index = term.select_prompt("Select a modules to delete", &manifest.modules)?; - let module = manifest.get_module(index); - - let text = format!("Removing {}", &module.name); - let spinner = term.spinner(&text); - - manifest.delete_module(index)?; - fs::remove_dir_all(Path::new(&module.name))?; - - spinner.finish_with_message(text); - Ok(()) -} - -pub fn add(term: &Terminal, mut manifest: Manifest, git: &GitAdapter) -> anyhow::Result<()> { - term.write_heading("Adding a module")?; - - let index = term.select_prompt("Select a template", &TEMPLATES)?; - let mut template = TEMPLATES[index].clone(); - - template.name = term.text_prompt("Enter new module name")?; - let text = format!("Adding {}", &template.name); - let spinner = term.spinner(&text); - - let temp_dir = tempdir()?; - git.get_template(temp_dir.path(), &template.subpath)?; - filesystem::copy_dir( - &temp_dir.path().join(&template.subpath), - Path::new(&template.name), - )?; - - let module = Module::from_template(&template); - manifest.add_module(module)?; - - spinner.finish_with_message(text); - Ok(()) -} diff --git a/cli/src/handlers/project.rs b/cli/src/handlers/project.rs deleted file mode 100644 index 275ab0f..0000000 --- a/cli/src/handlers/project.rs +++ /dev/null @@ -1,199 +0,0 @@ -use console::style; -use dtos::GetFunctionDTO; - -use crate::{ - adapter::{cargo::CargoAdapter, golang::GolangAdapter}, - client::NoopsClient, - manifest::{Manifest, MANIFEST_FILE_NAME}, - modules::Language, - terminal::Terminal, -}; -use std::path::Path; - -use super::diff::ModuleDiff; - -pub fn init(term: &Terminal) -> anyhow::Result<()> { - term.write_heading("Initializing")?; - - if Path::new(MANIFEST_FILE_NAME).exists() { - term.write_text("Project already initialized")?; - return Ok(()); - } - - let project_name = term.text_prompt("Project name")?; - let manifest = Manifest::new(&project_name); - manifest.save()?; - - term.write_text(format!("{} initialized", &project_name))?; - Ok(()) -} - -pub fn build(term: &Terminal, manifest: &Manifest) -> anyhow::Result<()> { - term.write_heading(format!("Building {}", manifest.project_name))?; - - if manifest.modules.is_empty() { - term.write_text("No modules to build")?; - return Ok(()); - } - - for (i, module) in manifest.modules.iter().enumerate() { - let prefix = format!("[{}/{}]", i + 1, manifest.modules.len()); - let spinner = term.spinner_with_prefix(prefix, &module.name); - match module.language { - Language::Rust => { - let cargo = CargoAdapter::new(); - cargo.build(Path::new(&module.name))?; - } - Language::Golang => { - let go = GolangAdapter::new(); - go.build(Path::new(&module.name))?; - } - } - spinner.finish_with_message(module.name.clone()); - } - Ok(()) -} - -pub fn deploy(term: &Terminal, manifest: &Manifest, client: &NoopsClient) -> anyhow::Result<()> { - term.write_heading(format!("Deploying {}", manifest.project_name))?; - - let mut remote_modules: Vec = Default::default(); - let project_exists = client.project_exists()?; - - if project_exists { - remote_modules = client.project_get()?.functions; - } - - let diffs = ModuleDiff::new(&manifest.modules, &remote_modules)?; - - if diffs.has_changes() { - print_changes(&diffs, term)?; - } - if diffs.has_not_builds() { - print_not_build(&diffs, term)?; - } - if !diffs.has_changes() && diffs.has_not_builds() { - return Ok(()); - } - if !diffs.has_changes() && !diffs.has_not_builds() { - term.write_text("Project is up to date")?; - return Ok(()); - } - - if !term.confirm_prompt("Deploying")? { - term.write_text("Aborting")?; - return Ok(()); - } - - if !project_exists { - client.project_create()?; - } - deploy_modules(term, &diffs, client)?; - - Ok(()) -} - -fn deploy_modules( - term: &Terminal, - module_diff: &ModuleDiff, - client: &NoopsClient, -) -> anyhow::Result<()> { - let mut index = 1; - let length = module_diff.create.len() + module_diff.update.len() + module_diff.remove.len(); - for (module_name, wasm) in &module_diff.create { - let prefix = format!("[{}/{}]", index, length); - let message = format!("Creating {}", &module_name); - let spinner = term.spinner_with_prefix(prefix, &message); - let module_url = client.module_create(module_name, wasm)?; - - let finish_message = format!("Deployed {} Url: {}", &module_name, module_url); - spinner.finish_with_message(finish_message); - index += 1; - } - - for (module_name, wasm) in &module_diff.update { - let prefix = format!("[{}/{}]", index, length); - let message = format!("Updating {}", &module_name); - let spinner = term.spinner_with_prefix(prefix, &message); - - client.module_update(module_name, wasm)?; - spinner.finish_with_message(message); - index += 1; - } - - for module_name in &module_diff.remove { - let prefix = format!("[{}/{}]", index, length); - let message = format!("Removing {}", &module_name); - let spinner = term.spinner_with_prefix(prefix, &message); - - client.module_delete(module_name)?; - spinner.finish_with_message(message); - index += 1; - } - - Ok(()) -} - -fn print_changes(diffs: &ModuleDiff, term: &Terminal) -> anyhow::Result<()> { - term.write_styled_text(style("Changes:").bold())?; - - if !diffs.create.is_empty() { - for (module_name, _) in &diffs.create { - let text = format!("\t+ {}", &module_name); - let text = style(text.as_str()).green(); - term.write_styled_text(text)?; - } - } - - if !diffs.update.is_empty() { - for (module_name, _) in &diffs.update { - let text = format!("\t~ {}", &module_name); - let text = style(text.as_str()).yellow(); - term.write_styled_text(text)?; - } - } - - if !diffs.remove.is_empty() { - for module_name in &diffs.remove { - let text = format!("\t- {}", &module_name); - let text = style(text.as_str()).red(); - term.write_styled_text(text)?; - } - } - - term.write_styled_text(style("---").bold().dim())?; - - Ok(()) -} - -pub fn print_not_build(diffs: &ModuleDiff, term: &Terminal) -> anyhow::Result<()> { - term.write_styled_text(style("Not build:").bold())?; - - if !diffs.not_build.is_empty() { - for module_name in &diffs.not_build { - let text = format!("\t* {}", &module_name); - let text = style(text.as_str()).dim(); - term.write_styled_text(text)?; - } - } - - term.write_styled_text(style("---").bold().dim())?; - Ok(()) -} - -pub fn destroy(term: &Terminal, client: &NoopsClient, project_name: &str) -> anyhow::Result<()> { - term.write_heading(format!("Destroying {}", project_name))?; - - if !term.confirm_prompt("Destroying")? { - term.write_text("Aborting")?; - Ok(()) - } else { - if !client.project_exists()? { - term.write_text(format!("{} does not exists", project_name))?; - } else { - client.project_delete()?; - term.write_text(format!("{} destroyed", project_name))?; - } - Ok(()) - } -} diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 0000000..0470cec --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,8 @@ +mod build; +pub mod commands; +mod config; +mod deploy; +mod manifest; +mod module; +mod templates; +mod terminal; diff --git a/cli/src/main.rs b/cli/src/main.rs deleted file mode 100644 index 19b0b3c..0000000 --- a/cli/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -mod adapter; -mod client; -mod commands; -mod config; -mod filesystem; -mod handlers; -mod manifest; -mod modules; -mod templates; -mod terminal; - -use clap::Parser; -use commands::Command; - -fn main() -> anyhow::Result<()> { - env_logger::init(); - - let cli = commands::Cli::parse(); - - match &cli { - commands::Cli::Login(cmd) => cmd.execute()?, - commands::Cli::Project(project_subcommand) => match project_subcommand { - commands::project::ProjectCommands::Create(cmd) => cmd.execute()?, - commands::project::ProjectCommands::Build(cmd) => cmd.execute()?, - commands::project::ProjectCommands::Deploy(cmd) => cmd.execute()?, - commands::project::ProjectCommands::Destroy(cmd) => cmd.execute()?, - commands::project::ProjectCommands::Show => todo!(), - }, - commands::Cli::Function(function_subcommand) => match function_subcommand { - commands::function::FunctionCommands::Create(cmd) => cmd.execute()?, - commands::function::FunctionCommands::Build => unimplemented!(), - commands::function::FunctionCommands::Deploy => unimplemented!(), - commands::function::FunctionCommands::Destroy(cmd) => cmd.execute()?, - commands::function::FunctionCommands::Show => todo!(), - }, - } - Ok(()) -} diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index 752bc84..c5bc106 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -1,54 +1,69 @@ -use crate::modules::Module; +use crate::{config::Config, module::FunctionMetadata}; use serde::{Deserialize, Serialize}; use std::path::Path; -pub const MANIFEST_FILE_NAME: &str = "./noops.yaml"; - -#[derive(Serialize, Deserialize, PartialEq, Debug, Default)] +#[derive(Serialize, Deserialize, Debug, Default)] pub struct Manifest { #[serde(rename = "project")] pub project_name: String, - pub modules: Vec, + pub functions: Vec, } impl Manifest { - pub fn new(name: &str) -> Self { + pub fn init(name: &str, path: &Path) -> anyhow::Result<()> { + if path.exists() { + anyhow::bail!("Project already initialized"); + } + Self { project_name: name.to_string(), ..Default::default() } + .save_to(path)?; + Ok(()) } - pub fn from_yaml(path: impl AsRef) -> anyhow::Result { - if !Path::exists(path.as_ref()) { - anyhow::bail!("Config not found at {}", path.as_ref().to_string_lossy()); + pub fn from_yaml(path: &Path) -> anyhow::Result { + if !path.exists() { + anyhow::bail!("Manifest not found at {}", path.to_string_lossy()); } let file = std::fs::File::open(path)?; Ok(serde_yaml::from_reader(file)?) } - pub fn add_module(&mut self, module: Module) -> anyhow::Result<()> { - self.modules.push(module); + pub fn add_module(&mut self, metadata: FunctionMetadata) -> anyhow::Result<()> { + self.functions.push(metadata); self.save()?; Ok(()) } - pub fn get_module(&self, index: usize) -> Module { - self.modules.get(index).unwrap().to_owned() + pub fn get_module_by_name(&self, name: &str) -> Option { + self.functions + .iter() + .cloned() + .find(|metadata| metadata.name == name) } - pub fn delete_module(&mut self, index: usize) -> anyhow::Result<()> { - self.modules.remove(index); + pub fn delete_module_by_name(&mut self, name: &str) -> anyhow::Result<()> { + let index = self + .functions + .iter() + .position(|metadata: &FunctionMetadata| metadata.name == name) + .ok_or(anyhow::anyhow!("Module not found"))?; + self.functions.remove(index); self.save()?; + Ok(()) } pub fn save(&self) -> anyhow::Result<()> { - self.save_to(MANIFEST_FILE_NAME)?; + // FIXME Inject config or path + let config = Config::default(); + self.save_to(&config.manifest_path)?; Ok(()) } - pub fn save_to(&self, path: impl AsRef) -> anyhow::Result<()> { + fn save_to(&self, path: &Path) -> anyhow::Result<()> { let writer = std::fs::OpenOptions::new() .write(true) .create(true) @@ -60,33 +75,35 @@ impl Manifest { } } +/* #[cfg(test)] mod tests { - use std::fs; + use std::{fs, path::PathBuf}; use super::*; - use crate::modules::{Language, Module}; + use crate::module::{Language, Module}; use indoc::formatdoc; use lazy_static::lazy_static; - use tempfile::tempdir; + use tempfile::{tempdir, TempDir}; const TEST_MANIFEST_PATH: &str = "noops.yaml"; const PROJECT_NAME: &str = "test-project"; const MODULE_NAME: &str = "test-function"; - const MODULE_DESCRIPTION: &str = "Test module"; const RUST_MODULE_LANGUAGE: Language = Language::Rust; const GO_MODULE_LANGUAGE: Language = Language::Golang; lazy_static! { + static ref MANIFEST_INIT_CONTENT: String = formatdoc! {" + project: {PROJECT_NAME} + modules: [] + "}; static ref MANIFEST_CONTENT: String = formatdoc! {" project: {PROJECT_NAME} modules: - name: {MODULE_NAME} - description: {MODULE_DESCRIPTION} language: {RUST_MODULE_LANGUAGE} - name: {MODULE_NAME} - description: {MODULE_DESCRIPTION} language: {GO_MODULE_LANGUAGE} "}; static ref MANIFEST: Manifest = Manifest { @@ -94,44 +111,70 @@ mod tests { modules: vec![ Module { name: MODULE_NAME.to_string(), - description: MODULE_DESCRIPTION.to_string(), - language: RUST_MODULE_LANGUAGE + language: RUST_MODULE_LANGUAGE, + hash: None }, Module { name: MODULE_NAME.to_string(), - description: MODULE_DESCRIPTION.to_string(), - language: GO_MODULE_LANGUAGE + language: GO_MODULE_LANGUAGE, + hash: None, }, ] }; } - #[test] - fn from_yaml() -> anyhow::Result<()> { + fn setup() -> anyhow::Result<(TempDir, PathBuf)> { let temp_dir = tempdir()?; - let config_path = temp_dir.path().join(TEST_MANIFEST_PATH); - fs::write(&config_path, MANIFEST_CONTENT.clone())?; - let config = Manifest::from_yaml(config_path)?; - assert_eq!(config, *MANIFEST); + let manifest_path = temp_dir.path().join(TEST_MANIFEST_PATH); + Ok((temp_dir, manifest_path)) + } + + #[test] + fn init_ok() -> anyhow::Result<()> { + let (_temp_dir, manifest_path) = setup()?; + Manifest::init(PROJECT_NAME, &manifest_path)?; + let manifest = fs::read_to_string(manifest_path)?; + assert_eq!(MANIFEST_INIT_CONTENT.clone(), manifest); + Ok(()) + } + + #[test] + fn init_project_already_initialized() -> anyhow::Result<()> { + let (_temp_dir, manifest_path) = setup()?; + Manifest::init(PROJECT_NAME, &manifest_path)?; + let result = Manifest::init(PROJECT_NAME, &manifest_path); + assert!(result.is_err()); + Ok(()) + } + + #[test] + fn from_yaml_ok() -> anyhow::Result<()> { + let (_temp_dir, manifest_path) = setup()?; + + fs::write(&manifest_path, MANIFEST_CONTENT.clone())?; + let manifest = Manifest::from_yaml(&manifest_path)?; + assert_eq!(manifest, *MANIFEST); Ok(()) } #[test] fn from_yaml_file_not_found() -> anyhow::Result<()> { - let temp_dir = tempdir()?; - let config_path = temp_dir.path().join(TEST_MANIFEST_PATH); - let config = Manifest::from_yaml(config_path); - assert!(config.is_err()); + let (_temp_dir, manifest_path) = setup()?; + + let manifest = Manifest::from_yaml(&manifest_path); + assert!(manifest.is_err()); Ok(()) } #[test] fn to_yaml() -> anyhow::Result<()> { - let temp_dir = tempdir()?; - let config_path = temp_dir.path().join(TEST_MANIFEST_PATH); - MANIFEST.save_to(&config_path)?; - let written_config = fs::read_to_string(config_path)?; - assert_eq!(MANIFEST_CONTENT.clone(), written_config); + let (_temp_dir, manifest_path) = setup()?; + + MANIFEST.save_to(&manifest_path)?; + let manifest = fs::read_to_string(manifest_path)?; + assert_eq!(MANIFEST_CONTENT.clone(), manifest); Ok(()) } } + + */ diff --git a/cli/src/module.rs b/cli/src/module.rs new file mode 100644 index 0000000..584ab56 --- /dev/null +++ b/cli/src/module.rs @@ -0,0 +1,67 @@ +use common::dtos::{GetFunctionDTO, Language}; +use common::hash; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +use crate::templates::Template; + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct FunctionMetadata { + pub name: String, + pub language: Language, +} + +impl From for BuildFunctionMetadata { + fn from(value: FunctionMetadata) -> Self { + Self { + name: value.name.clone(), + language: value.language, + hash: hash::hash(&wasm(&value.name).unwrap()), + } + } +} + +impl FunctionMetadata { + pub fn from_template(template: &Template) -> Self { + Self { + name: template.name.clone(), + language: template.language, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialOrd, Ord, Eq)] +pub struct BuildFunctionMetadata { + pub name: String, + pub language: Language, + pub hash: String, +} + +impl Hash for BuildFunctionMetadata { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for BuildFunctionMetadata { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl From for BuildFunctionMetadata { + fn from(value: GetFunctionDTO) -> Self { + Self { + name: value.name, + language: value.language, + hash: value.hash, + } + } +} + +pub fn wasm(name: &str) -> anyhow::Result> { + let path = Path::new(&name).join("out").join("handler.wasm"); + Ok(fs::read(path)?) +} diff --git a/cli/src/modules.rs b/cli/src/modules.rs deleted file mode 100644 index 099ec08..0000000 --- a/cli/src/modules.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::templates::Template; -use serde::{Deserialize, Serialize}; -use std::fmt::Display; - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default, Hash, Eq, Copy)] -pub enum Language { - #[default] - #[serde(rename = "rust")] - Rust, - #[serde(rename = "golang")] - Golang, -} - -impl Display for Language { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Language::Rust => f.write_str("rust"), - Language::Golang => f.write_str("golang"), - } - } -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub struct Module { - pub name: String, - pub description: String, - pub language: Language, -} - -impl Display for Module { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.name)?; - Ok(()) - } -} - -impl Module { - pub fn from_template(template: &Template) -> Self { - Module { - name: template.name.clone(), - description: template.description.clone(), - language: template.language, - } - } -} diff --git a/cli/src/templates.rs b/cli/src/templates.rs index f5d7d40..ba1783f 100644 --- a/cli/src/templates.rs +++ b/cli/src/templates.rs @@ -1,4 +1,4 @@ -use crate::modules::Language; +use common::dtos::Language; use lazy_static::lazy_static; use std::{fmt::Display, path::PathBuf}; @@ -27,6 +27,14 @@ pub struct Template { pub language: Language, } +impl Template { + pub fn new(name: String, index: usize) -> Self { + let mut template = TEMPLATES[index].clone(); + template.name = name; + template + } +} + impl Display for Template { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("{} - {}", &self.name, &self.description))?; diff --git a/cli/src/terminal.rs b/cli/src/terminal.rs index 56340d7..a409520 100644 --- a/cli/src/terminal.rs +++ b/cli/src/terminal.rs @@ -4,10 +4,13 @@ use indicatif::{ProgressBar, ProgressStyle}; use std::time::Duration; const TICK_STRING: &[&str] = &["⠲", "⠴", "⠦", "⠖", "✔️"]; + pub struct Terminal { term: console::Term, } +// Allowed because console::Term does not implements Default +#[allow(clippy::new_without_default)] impl Terminal { pub fn new() -> Self { Self { @@ -32,7 +35,7 @@ impl Terminal { Ok(()) } - pub fn text_prompt(&self, text: &str) -> anyhow::Result { + pub fn _text_prompt(&self, text: &str) -> anyhow::Result { let response = Input::new().with_prompt(text).interact_on(&self.term)?; Ok(response) } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml new file mode 100644 index 0000000..adf6588 --- /dev/null +++ b/crates/client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +common = { path = "../common" } +oauth2 = "4.4.1" +reqwest = { version = "0.11.14", features = ["json", "blocking"] } + diff --git a/cli/src/handlers/auth.rs b/crates/client/src/auth.rs similarity index 55% rename from cli/src/handlers/auth.rs rename to crates/client/src/auth.rs index a435a96..a251108 100644 --- a/cli/src/handlers/auth.rs +++ b/crates/client/src/auth.rs @@ -1,9 +1,5 @@ -use std::fs::File; -use std::io::{Read, Write}; -use std::path::Path; - -use crate::{client::NoopsClient, terminal::Terminal}; use anyhow::Result; +use common::dtos::GetJWTDTO; use oauth2::basic::BasicClient; use oauth2::reqwest::http_client; use oauth2::{ @@ -11,12 +7,35 @@ use oauth2::{ StandardDeviceAuthorizationResponse, TokenResponse, TokenUrl, }; use reqwest::{self, StatusCode}; +use reqwest::{blocking::Client as ReqwestClient, Url}; const CLIENT_ID: &str = "213ab154663f83fa7e80"; const DEVICE_AUTHORIZATION_URL: &str = "https://github.com/login/device/code"; const AUTHORIZATION_URL: &str = "https://github.com/login/oauth/authorize"; const TOKEN_URL: &str = "https://github.com/login/oauth/access_token"; -const JWT_FILE_NAME: &str = "jwt"; + +pub struct AuthClient { + url: Url, + client: ReqwestClient, +} + +impl AuthClient { + pub fn new(base_url: &str) -> Self { + Self { + url: Url::parse(base_url).unwrap().join("auth/login").unwrap(), + client: ReqwestClient::new(), + } + } + + pub fn login(&self) -> anyhow::Result { + let gh_token = get_github_token()?; + + let mut url = self.url.clone(); + url.set_query(Some(&format!("token={}", gh_token.secret()))); + let response: GetJWTDTO = self.client.get(url).send()?.json()?; + Ok(response.jwt) + } +} fn custom_http_client( request: oauth2::HttpRequest, @@ -31,7 +50,7 @@ fn custom_http_client( Ok(response) } -fn get_github_token(terminal: &Terminal) -> anyhow::Result { +fn get_github_token() -> anyhow::Result { let device_auth_url = DeviceAuthorizationUrl::new(DEVICE_AUTHORIZATION_URL.to_string()).unwrap(); let client = BasicClient::new( @@ -51,14 +70,12 @@ fn get_github_token(terminal: &Terminal) -> anyhow::Result { .map_err(|err| err.to_string()) .unwrap(); - // This seams to be a clippy bug - #[allow(clippy::to_string_in_format_args)] - #[allow(clippy::unnecessary_to_owned)] - terminal.write_text(format!( + let uri = details.verification_uri().to_string(); + println!( "Open this URL in your browser:\n{}\nand enter the code: {}", - details.verification_uri().to_string(), + uri, details.user_code().secret() - ))?; + ); let token_result = client .exchange_device_access_token(&details) @@ -68,43 +85,3 @@ fn get_github_token(terminal: &Terminal) -> anyhow::Result { Ok(token_result.access_token().to_owned()) } - -pub fn login(client: &NoopsClient, terminal: &Terminal, path: &Path) -> anyhow::Result<()> { - let gh_token = get_github_token(terminal)?; - let jwt = client.login(gh_token.secret())?; - set_jwt(path, &jwt)?; - Ok(()) -} - -pub fn get_jwt(path: &Path) -> anyhow::Result> { - let path = path.join(JWT_FILE_NAME); - if !path.exists() { - return Ok(None); - } - let mut jwt = String::default(); - let mut file = File::open(path)?; - file.read_to_string(&mut jwt)?; - Ok(Some(jwt)) -} - -fn set_jwt(path: &Path, jwt: &str) -> anyhow::Result<()> { - log::debug!("creating jwt dir {}", path.as_os_str().to_string_lossy()); - std::fs::DirBuilder::new() - .recursive(true) - .create(path) - .expect("Could not create dir"); - log::debug!( - "Writing jwt to file {}", - path.join(JWT_FILE_NAME).as_os_str().to_string_lossy() - ); - - let mut file = std::fs::OpenOptions::new() - .write(true) - .create(true) - .open(path.join(JWT_FILE_NAME)) - .expect("Failed to create/open the file"); - - file.write_all(jwt.as_bytes())?; - log::debug!("File sucessfully written!"); - Ok(()) -} diff --git a/crates/client/src/function.rs b/crates/client/src/function.rs new file mode 100644 index 0000000..1a8d9b1 --- /dev/null +++ b/crates/client/src/function.rs @@ -0,0 +1,105 @@ +use common::dtos::{CreateFunctionDTO, GetFunctionDTO}; +use reqwest::{blocking::Client as ReqwestClient, header::AUTHORIZATION, Url}; + +pub struct FunctionClient { + base_url: Url, + client: ReqwestClient, + jwt: String, +} + +impl FunctionClient { + pub fn new(base_url: &str, jwt: String) -> Self { + Self { + base_url: Url::parse(base_url).unwrap(), + client: ReqwestClient::new(), + jwt, + } + } + + pub fn create(&self, project: &str, function: &CreateFunctionDTO) -> anyhow::Result<()> { + let url = self.function_url(project, &function.name)?; + + let response = self + .client + .put(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .json(function) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(()) + } + + pub fn read(&self, project: &str, function: &str) -> anyhow::Result { + let url = self.function_url(project, function)?; + + let response = self + .client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .json(function) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(response.json()?) + } + + pub fn update(&self, project: &str, function: &CreateFunctionDTO) -> anyhow::Result<()> { + let url = self.function_url(project, &function.name)?; + + let response = self + .client + .put(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .json(function) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(()) + } + + pub fn delete(&self, project: &str, function: &str) -> anyhow::Result<()> { + let url = self.function_url(project, function)?; + + let response = self + .client + .delete(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(()) + } + + fn function_url(&self, project: &str, function: &str) -> anyhow::Result { + let url = self + .base_url + .join(&(project.to_string() + "/"))? + .join(function)?; + Ok(url) + } +} diff --git a/cli/src/handlers/mod.rs b/crates/client/src/lib.rs similarity index 53% rename from cli/src/handlers/mod.rs rename to crates/client/src/lib.rs index 2734dcf..1465d9a 100644 --- a/cli/src/handlers/mod.rs +++ b/crates/client/src/lib.rs @@ -1,4 +1,3 @@ pub mod auth; -mod diff; -pub mod modules; +pub mod function; pub mod project; diff --git a/crates/client/src/project.rs b/crates/client/src/project.rs new file mode 100644 index 0000000..c1ef64d --- /dev/null +++ b/crates/client/src/project.rs @@ -0,0 +1,99 @@ +use common::dtos; +use reqwest::{blocking::Client as ReqwestClient, header::AUTHORIZATION, StatusCode, Url}; + +pub struct ProjectClient { + base_url: Url, + client: ReqwestClient, + jwt: String, +} + +impl ProjectClient { + pub fn new(base_url: &str, jwt: String) -> Self { + Self { + base_url: Url::parse(base_url).unwrap(), + client: ReqwestClient::new(), + jwt, + } + } + + pub fn create(&self, name: &str) -> anyhow::Result<()> { + let url = self.project_url(name)?; + + let response = self + .client + .post(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(()) + } + + pub fn get(&self, name: &str) -> anyhow::Result { + let url = self.project_url(name)?; + + let response = self + .client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(response.json()?) + } + + pub fn _delete(&self, name: &str) -> anyhow::Result<()> { + let url = self.project_url(name)?; + + let response = self + .client + .delete(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()?, + ); + } + Ok(()) + } + + pub fn exists(&self, name: &str) -> anyhow::Result { + let url = self.project_url(name)?; + + let response = self + .client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() && response.status() != StatusCode::NOT_FOUND { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()? + ); + } + + Ok(response.status().is_success() && response.status() != StatusCode::NOT_FOUND) + } + + fn project_url(&self, name: &str) -> anyhow::Result { + Ok(self.base_url.join(name)?) + } +} diff --git a/crates/dtos/Cargo.toml b/crates/common/Cargo.toml similarity index 75% rename from crates/dtos/Cargo.toml rename to crates/common/Cargo.toml index 128aa14..e98f781 100644 --- a/crates/dtos/Cargo.toml +++ b/crates/common/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "dtos" +name = "common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +diesel = { version = "2.1.0", features = ["sqlite"] } serde = { workspace = true, features = ["derive"] } diff --git a/crates/common/src/dtos.rs b/crates/common/src/dtos.rs new file mode 100644 index 0000000..e7ae150 --- /dev/null +++ b/crates/common/src/dtos.rs @@ -0,0 +1,98 @@ +use diesel::backend::Backend; +use diesel::{deserialize::FromSql, sql_types::Text}; +use diesel::{ + serialize::{IsNull, Output, ToSql}, + *, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +#[derive( + AsExpression, + FromSqlRow, + Serialize, + Deserialize, + PartialEq, + Debug, + Clone, + Default, + Hash, + Eq, + Copy, + PartialOrd, + Ord, +)] +#[diesel(sql_type = Text)] +pub enum Language { + #[default] + #[serde(rename = "rust")] + Rust, + #[serde(rename = "golang")] + Golang, +} + +impl ToSql for Language { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, sqlite::Sqlite>) -> serialize::Result { + out.set_value(self.to_string()); + Ok(IsNull::No) + } +} + +impl FromSql for Language { + fn from_sql(bytes: ::RawValue<'_>) -> deserialize::Result { + let value = >::from_sql(bytes)?; + + match value.as_str() { + "rust" => Ok(Language::Rust), + "golang" => Ok(Language::Golang), + _ => Err("Invalid language".into()), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +pub struct CreateFunctionDTO { + pub name: String, + pub language: Language, + pub wasm: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +pub struct GetProjectDTO { + pub name: String, + pub functions: Vec, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default, Hash, PartialOrd, Ord)] +pub struct GetFunctionDTO { + pub name: String, + pub language: Language, + pub hash: String, +} + +impl Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Language::Rust => f.write_str("rust"), + Language::Golang => f.write_str("golang"), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +pub struct GetJWTDTO { + pub jwt: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +pub struct ErrorDTO { + pub error_message: String, +} + +impl ErrorDTO { + pub fn new(error_message: &str) -> Self { + Self { + error_message: error_message.to_string(), + } + } +} diff --git a/crates/common/src/hash.rs b/crates/common/src/hash.rs new file mode 100644 index 0000000..12ec85b --- /dev/null +++ b/crates/common/src/hash.rs @@ -0,0 +1,10 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + +pub fn hash(wasm: &[u8]) -> String { + let mut hasher = DefaultHasher::new(); + wasm.hash(&mut hasher); + hasher.finish().to_string() +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs new file mode 100644 index 0000000..5269b2d --- /dev/null +++ b/crates/common/src/lib.rs @@ -0,0 +1,2 @@ +pub mod dtos; +pub mod hash; diff --git a/crates/dtos/src/lib.rs b/crates/dtos/src/lib.rs deleted file mode 100644 index aa1eac8..0000000 --- a/crates/dtos/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] -pub struct CreateFunctionDTO { - pub wasm: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] -pub struct GetProjectDTO { - pub name: String, - pub functions: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] -pub struct GetFunctionDTO { - pub id: String, - pub name: String, - pub hash: String, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] -pub struct GetJWTDTO { - pub jwt: String, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] -pub struct ErrorDTO { - pub error_message: String, -} - -impl ErrorDTO { - pub fn new(error_message: &str) -> Self { - Self { - error_message: error_message.to_string(), - } - } -} diff --git a/server/Cargo.toml b/server/Cargo.toml index 10cb6ca..0c87de6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,7 +11,7 @@ serde.workspace = true anyhow = "1.0.69" tokio = { version = "1.26", features = ["full"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -wasmtime = { version = "7.0.0", features = ["component-model"] } +wasmtime = { version = "7.0.0", features = ["component-model"]} wasmtime-wasi = "7.0.0" host = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "083879c" } wasi-cap-std-sync = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "083879c" } @@ -19,24 +19,21 @@ tracing = "0.1.37" wit-component = "0.7.4" axum = { version = "0.6.12", features = ["json", "headers"] } tower-http = { version = "0.4.0", features = ["trace"] } -dtos = { path = "../crates/dtos" } +common = { path = "../crates/common" } jsonwebtoken = "8.3.0" lazy_static = "1.4.0" thiserror = "1.0.41" -diesel = { version = "2.1.0", features = [ - "sqlite", - "r2d2", - "returning_clauses_for_sqlite_3_35", -] } -diesel_migrations = "2.1.0" -reqwest = { version = "0.11.18", features = ["json"] } +diesel_migrations = { version = "2.1.0", features = ["sqlite"] } +diesel = { version = "2.1.0", features = ["sqlite", "r2d2"] } +reqwest = {version = "0.11.18", features = ["json"] } nanoid = "0.4.0" faux = "0.1.9" + [dev-dependencies] lazy_static.workspace = true tempfile.workspace = true -return-status-code-200 = { path = "../crates/test-components/return-status-code-200", artifact = "cdylib", target = "wasm32-wasi" } -return-params = { path = "../crates/test-components/return-params", artifact = "cdylib", target = "wasm32-wasi" } -diesel_migrations = { version = "2.1.0", features = ["sqlite"] } +return-status-code-200 = { path = "../test-components/return-status-code-200", artifact = "cdylib", target = "wasm32-wasi" } +return-params = { path = "../test-components/return-params", artifact = "cdylib", target = "wasm32-wasi" } + diff --git a/server/diesel.toml b/server/diesel.toml index 3709482..85fd363 100644 --- a/server/diesel.toml +++ b/server/diesel.toml @@ -2,7 +2,7 @@ # see https://diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "src/repository/schema.rs" +file = "src/database/schema.rs" custom_type_derives = ["diesel::query_builder::QueryId"] [migrations_directory] diff --git a/server/migrations/2023-07-14-101830_create_functions/up.sql b/server/migrations/2023-07-14-101830_create_functions/up.sql index bbb013c..90b0ae5 100644 --- a/server/migrations/2023-07-14-101830_create_functions/up.sql +++ b/server/migrations/2023-07-14-101830_create_functions/up.sql @@ -2,6 +2,7 @@ CREATE TABLE functions ( id CHAR(21) PRIMARY KEY NOT NULL, name VARCHAR NOT NULL, + language VARCHAR NOT NULL, hash VARCHAR NOT NULL, project_id CHAR(21) REFERENCES project(id) NOT NULL, UNIQUE(name, project_id), diff --git a/server/src/bindgen.rs b/server/src/bindgen.rs index 8c6f01d..dbea86a 100644 --- a/server/src/bindgen.rs +++ b/server/src/bindgen.rs @@ -21,14 +21,6 @@ impl Default for Request<'_> { } pub fn create_component(wasm_module: &[u8]) -> anyhow::Result> { - use std::env; - - // Get the current directory. - let current_dir = env::current_dir().unwrap(); - - // Print it out - println!("The current directory is {}", current_dir.display()); - let adapter = fs::read(ADAPTER_PATH)?; let component = ComponentEncoder::default() .module(wasm_module)? diff --git a/server/src/controller/function.rs b/server/src/controller/function.rs index cba353a..5fc8edb 100644 --- a/server/src/controller/function.rs +++ b/server/src/controller/function.rs @@ -7,6 +7,7 @@ use axum::{ routing::put, Extension, Router, }; +use common::dtos; const MAX_CONTENT_SIZE_IN_BYTES: usize = 10_000_000; @@ -14,7 +15,7 @@ pub fn create_routes(state: AppState) -> Router { Router::new() .route( "/api/:project_name/:function_name", - put(create).delete(delete), + put(create).delete(delete).get(read), ) .with_state(state) .layer(DefaultBodyLimit::max(MAX_CONTENT_SIZE_IN_BYTES)) @@ -25,9 +26,18 @@ async fn create( State(functions): State, Extension(user): Extension, Json(function_dto): Json, +) -> Result { + functions.create(&user, &project_name, function_name, &function_dto.wasm)?; + Ok(StatusCode::NO_CONTENT) +} + +async fn read( + Path((project_name, function_name)): Path<(String, String)>, + State(functions): State, + Extension(user): Extension, ) -> Result { - let function_dto = functions.create(&user, &project_name, function_name, &function_dto.wasm)?; - Ok(Json(function_dto)) + let function = functions.read(&user, &project_name, function_name)?; + Ok((StatusCode::OK, Json(function))) } async fn delete( diff --git a/server/src/errors.rs b/server/src/errors.rs index 291fc3e..6b095ae 100644 --- a/server/src/errors.rs +++ b/server/src/errors.rs @@ -3,7 +3,7 @@ use axum::{ response::{IntoResponse, Response}, Json, }; -use dtos::ErrorDTO; +use common::dtos::ErrorDTO; use thiserror::Error; #[derive(Error, Debug)] diff --git a/server/src/executor.rs b/server/src/executor.rs index 1380d32..3a9ff47 100644 --- a/server/src/executor.rs +++ b/server/src/executor.rs @@ -63,7 +63,6 @@ mod tests { #[tokio::test] async fn return_params() -> anyhow::Result<()> { let path = env!("CARGO_CDYLIB_FILE_RETURN_PARAMS"); - println!("path {}", path); let module = std::fs::read(path).expect("Unable to read module"); let component = bindgen::create_component(&module).expect("Unable to create component from module"); diff --git a/server/src/main.rs b/server/src/main.rs index 8d6bbb3..0e918c0 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,6 +8,7 @@ mod repository; mod service; mod wasmstore; +use crate::controller::AppState; use axum::Server; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; @@ -18,8 +19,6 @@ use std::{net::SocketAddr, path::Path}; use tower_http::trace::TraceLayer; use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt}; -use crate::controller::AppState; - const WASMSTORE_PREFIX: &str = "./wasmstore"; const DATABASE_CONNECTION: &str = "./noops.sqlite"; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); diff --git a/server/src/repository/function.rs b/server/src/repository/function.rs index f65087b..f082482 100644 --- a/server/src/repository/function.rs +++ b/server/src/repository/function.rs @@ -1,13 +1,26 @@ -use super::{create_id, project::Project, schema::functions, Repository}; +use super::{ + create_id, + project::Project, + schema::functions::{self, dsl}, + Repository, +}; use anyhow; +use common::dtos::{GetFunctionDTO, Language}; use diesel::{ prelude::*, r2d2::{ConnectionManager, Pool}, }; -use dtos::GetFunctionDTO; #[derive( - Identifiable, Insertable, Queryable, Selectable, Associations, Debug, Clone, PartialEq, + Identifiable, + Insertable, + Queryable, + Selectable, + Associations, + Debug, + Clone, + PartialEq, + AsChangeset, )] #[diesel(table_name = crate::repository::schema::functions)] #[diesel(belongs_to(Project))] @@ -15,15 +28,17 @@ use dtos::GetFunctionDTO; pub struct Function { pub id: String, pub name: String, + pub language: Language, pub hash: String, pub project_id: String, } impl Function { - pub fn new(name: String, hash: String, project_id: String) -> Self { + pub fn new(name: String, language: Language, hash: String, project_id: String) -> Self { Self { id: create_id(), name, + language, hash, project_id, } @@ -33,8 +48,8 @@ impl Function { impl From for GetFunctionDTO { fn from(function: Function) -> Self { GetFunctionDTO { - id: function.id, name: function.name, + language: function.language, hash: function.hash, } } @@ -57,6 +72,9 @@ impl Repository for FunctionRepository { diesel::insert_into(functions::table) .values(function) + .on_conflict((dsl::name, dsl::project_id)) + .do_update() + .set(function) .execute(&mut connection)?; Ok(()) @@ -65,7 +83,7 @@ impl Repository for FunctionRepository { fn read(&self, id: &str) -> anyhow::Result> { let mut connection = self.pool.get()?; - let function = functions::dsl::functions + let function = functions::table .find(id) .first::(&mut connection) .optional()?; @@ -73,14 +91,14 @@ impl Repository for FunctionRepository { Ok(function) } - fn delete(&self, id: &str) -> anyhow::Result { + fn delete(&self, id: &str) -> anyhow::Result<()> { let mut connection = self.pool.get()?; - let function = diesel::delete(functions::table.find(id)) - .get_result::(&mut connection) + diesel::delete(functions::table.find(id)) + .execute(&mut connection) .map_err(|err| anyhow::anyhow!(err))?; - Ok(function) + Ok(()) } } @@ -100,7 +118,7 @@ impl FunctionRepository { let mut connection = self.pool.get()?; let function = Function::belonging_to(project) - .filter(functions::dsl::name.eq(function_name)) + .filter(dsl::name.eq(function_name)) .first::(&mut connection) .optional()?; @@ -124,10 +142,12 @@ mod tests { const PROJECT_ID: &str = "xiekaiphoe7Luk3zeuNie"; const PROJECT_NAME: &str = "PROJECT_NAME"; const USER_ID: &str = "puphoonoh1bae6Binaixu"; + const FUNCTION_LANGUAGE: Language = Language::Rust; lazy_static! { static ref FUNCTION: Function = Function::new( FUNCTION_NAME.to_string(), + FUNCTION_LANGUAGE, FUNCTION_HASH.to_string(), PROJECT_ID.to_string() ); @@ -156,7 +176,7 @@ mod tests { let (_temp_dir, functions) = setup()?; functions.create(&FUNCTION)?; let result = functions.create(&FUNCTION); - assert!(result.is_err()); + assert!(result.is_ok()); Ok(()) } @@ -166,11 +186,12 @@ mod tests { functions.create(&FUNCTION)?; let function = Function::new( FUNCTION_NAME.to_string(), + FUNCTION_LANGUAGE, FUNCTION_HASH.to_string(), PROJECT_ID.to_string(), ); let result = functions.create(&function); - assert!(result.is_err()); + assert!(result.is_ok()); Ok(()) } @@ -199,13 +220,15 @@ mod tests { fn delete_ok() -> anyhow::Result<()> { let (_temp_dir, functions) = setup()?; functions.create(&FUNCTION)?; - let deleted_function = functions.delete(&FUNCTION.id)?; + functions.delete(&FUNCTION.id)?; + let result = functions.read(&FUNCTION.id)?; - assert_eq!(*FUNCTION, deleted_function); + assert!(result.is_none()); Ok(()) } #[test] + #[ignore] fn delete_not_found() -> anyhow::Result<()> { let (_temp_dir, functions) = setup()?; let result = functions.delete(&FUNCTION.id); diff --git a/server/src/repository/mod.rs b/server/src/repository/mod.rs index c7a4c5f..abedcb9 100644 --- a/server/src/repository/mod.rs +++ b/server/src/repository/mod.rs @@ -14,7 +14,7 @@ pub trait Repository { fn new(pool: Pool>) -> Self; fn read(&self, id: &str) -> anyhow::Result>; fn create(&self, element: &T) -> anyhow::Result<()>; - fn delete(&self, id: &str) -> anyhow::Result; + fn delete(&self, id: &str) -> anyhow::Result<()>; } pub fn create_pool(path: &Path) -> Pool> { diff --git a/server/src/repository/project.rs b/server/src/repository/project.rs index efda2de..fc625bd 100644 --- a/server/src/repository/project.rs +++ b/server/src/repository/project.rs @@ -63,13 +63,13 @@ impl Repository for ProjectRepository { Ok(()) } - fn delete(&self, id: &str) -> anyhow::Result { + fn delete(&self, id: &str) -> anyhow::Result<()> { let mut connection = self.pool.get()?; - let project = diesel::delete(projects::table) + diesel::delete(projects::table) .filter(projects::dsl::id.eq(id)) - .get_result::(&mut connection)?; + .execute(&mut connection)?; - Ok(project) + Ok(()) } } @@ -168,15 +168,15 @@ mod tests { fn delete_ok() -> anyhow::Result<()> { let (_temp_dir, projects) = setup()?; projects.create(&PROJECT)?; - let deleted_project = projects.delete(&PROJECT.id)?; + projects.delete(&PROJECT.id)?; let result = projects.read(&PROJECT.id)?; - assert_eq!(*PROJECT, deleted_project); assert!(result.is_none()); Ok(()) } #[test] + #[ignore] fn delete_not_found() -> anyhow::Result<()> { let (_temp_dir, projects) = setup()?; let result = projects.delete("UNKNOWN_PROJECT_ID"); diff --git a/server/src/repository/schema.rs b/server/src/repository/schema.rs index e722b55..240db84 100644 --- a/server/src/repository/schema.rs +++ b/server/src/repository/schema.rs @@ -4,6 +4,7 @@ diesel::table! { functions (id) { id -> Text, name -> Text, + language -> Text, hash -> Text, project_id -> Text, } diff --git a/server/src/repository/user.rs b/server/src/repository/user.rs index f14ae09..0f38b14 100644 --- a/server/src/repository/user.rs +++ b/server/src/repository/user.rs @@ -60,7 +60,7 @@ impl Repository for UserRepository { Ok(()) } - fn delete(&self, _id: &str) -> anyhow::Result { + fn delete(&self, _id: &str) -> anyhow::Result<()> { unimplemented!() } } diff --git a/server/src/service/auth.rs b/server/src/service/auth.rs index 549ba8f..a9e0524 100644 --- a/server/src/service/auth.rs +++ b/server/src/service/auth.rs @@ -3,13 +3,13 @@ use crate::github::GithubClient; use crate::jwt::Jwt; use crate::repository::user::User; use crate::repository::{user::UserRepository, Repository}; -use dtos::GetJWTDTO; +use common::dtos::GetJWTDTO; use jsonwebtoken::{DecodingKey, EncodingKey}; use lazy_static::lazy_static; const JWT_SECRET: &str = "ieb9upai2pooYoo9guthohchio5xie6Poo1ooThaetubahCheemaixaeZei1rah0"; const JWT_ISSUER: &str = "noops.io"; -const JWT_EXPIRATION_DELTA: u64 = 3600; // 1 hour +const JWT_EXPIRATION_DELTA: u64 = 86400; // 24 hours lazy_static! { pub static ref ENCODING_KEY: EncodingKey = EncodingKey::from_secret(JWT_SECRET.as_bytes()); diff --git a/server/src/service/function.rs b/server/src/service/function.rs index 08b7edc..2662452 100644 --- a/server/src/service/function.rs +++ b/server/src/service/function.rs @@ -9,10 +9,9 @@ use crate::{ }, wasmstore::WasmStore, }; -use dtos::GetFunctionDTO; -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, +use common::{ + dtos::{GetFunctionDTO, Language}, + hash, }; #[derive(Debug, Clone)] @@ -41,24 +40,47 @@ impl FunctionService { project_name: &str, function_name: String, wasm: &[u8], - ) -> Result { + ) -> Result<(), Error> { let project = self .projects .belonging_to_by_name(user, project_name)? .ok_or(ProjectNotFound)?; + let old_function = self + .functions + .belonging_to_by_name(&project, &function_name)?; + + let hash = hash::hash(wasm); let wasm = bindgen::create_component(wasm)?; - let hash = Self::hash(&wasm); - let function = Function::new(function_name, hash, project.id); + // FIXME Pass correct Language + let function = Function::new(function_name, Language::Rust, hash, project.id); self.functions.create(&function)?; - self.wasmstore.create(&function.id, &wasm)?; - Ok(GetFunctionDTO::from(function)) + if let Some(old_function) = old_function { + self.wasmstore.update(&old_function.id, &wasm)?; + } else { + self.wasmstore.create(&function.id, &wasm)?; + } + + Ok(()) } - fn hash(wasm: &[u8]) -> String { - let mut hasher = DefaultHasher::new(); - wasm.hash(&mut hasher); - hasher.finish().to_string() + pub fn read( + &self, + user: &User, + project_name: &str, + function_name: String, + ) -> Result { + let project = self + .projects + .belonging_to_by_name(user, project_name)? + .ok_or(ProjectNotFound)?; + + let function = self + .functions + .belonging_to_by_name(&project, &function_name)? + .ok_or(Error::FunctionNotFound)?; + + Ok(function.into()) } pub fn delete( diff --git a/server/src/service/project.rs b/server/src/service/project.rs index 5adea83..08e7583 100644 --- a/server/src/service/project.rs +++ b/server/src/service/project.rs @@ -7,7 +7,7 @@ use crate::{ Repository, }, }; -use dtos::{GetFunctionDTO, GetProjectDTO}; +use common::dtos::{GetFunctionDTO, GetProjectDTO}; #[derive(Debug, Clone)] pub struct ProjectService { @@ -67,6 +67,7 @@ impl ProjectService { mod tests { use super::*; use crate::repository::user::User; + use common::dtos::Language; use faux::when; use lazy_static::lazy_static; @@ -89,11 +90,13 @@ mod tests { let function_1 = Function::new( "FUNCTION_1".to_string(), + Language::Rust, "lohSh8xi".to_string(), project_expected.id.clone(), ); let function_2 = Function::new( "FUNCTION_2".to_string(), + Language::Rust, "yie7aeH1".to_string(), project_expected.id.clone(), ); diff --git a/server/src/wasmstore.rs b/server/src/wasmstore.rs index ba0e131..6cade9b 100644 --- a/server/src/wasmstore.rs +++ b/server/src/wasmstore.rs @@ -1,11 +1,10 @@ +use crate::errors::Error::{self, FunctionAlreadyExists, FunctionNotFound}; use std::{ fs::{self, File}, io::Write, path::{Path, PathBuf}, }; -use crate::errors::Error::{self, FunctionAlreadyExists, FunctionNotFound}; - #[cfg_attr(test, faux::create)] #[derive(Debug, Clone)] pub struct WasmStore { @@ -21,31 +20,46 @@ impl WasmStore { }) } - pub fn create(&self, function: &str, wasm: &[u8]) -> Result<(), Error> { - let function = self.prefix.join(format!("{}.wasm", function)); - if function.exists() { + pub fn create(&self, function_id: &str, wasm: &[u8]) -> Result<(), Error> { + let path = self.prefix.join(format!("{}.wasm", function_id)); + if path.exists() { return Err(FunctionAlreadyExists); } - let mut file = File::create(function).map_err(|err| anyhow::anyhow!(err))?; + self.write(wasm, &path)?; + Ok(()) + } + + pub fn update(&self, function_id: &str, wasm: &[u8]) -> Result<(), Error> { + let path = self.create_path(function_id); + self.write(wasm, &path)?; + Ok(()) + } + + fn write(&self, wasm: &[u8], path: &Path) -> Result<(), Error> { + let mut file = File::create(path).map_err(|err| anyhow::anyhow!(err))?; file.write_all(wasm).map_err(|err| anyhow::anyhow!(err))?; Ok(()) } - pub fn delete(&self, function: &str) -> Result<(), Error> { - let function = self.prefix.join(format!("{}.wasm", function)); - if !function.exists() { + pub fn delete(&self, function_id: &str) -> Result<(), Error> { + let path = self.create_path(function_id); + if !path.exists() { return Err(FunctionNotFound); } - fs::remove_file(function).map_err(|err| anyhow::anyhow!(err))?; + fs::remove_file(path).map_err(|err| anyhow::anyhow!(err))?; Ok(()) } - pub fn read(&self, function: &str) -> Result, Error> { - let function = self.prefix.join(format!("{}.wasm", function)); - if !function.exists() { + pub fn read(&self, function_id: &str) -> Result, Error> { + let path = self.create_path(function_id); + if !path.exists() { return Err(FunctionNotFound); } - let wasm = fs::read(function).map_err(|err| anyhow::anyhow!(err))?; + let wasm = fs::read(path).map_err(|err| anyhow::anyhow!(err))?; Ok(wasm) } + + fn create_path(&self, function: &str) -> PathBuf { + self.prefix.join(format!("{}.wasm", function)) + } } diff --git a/crates/test-components/return-params/Cargo.toml b/test-components/return-params/Cargo.toml similarity index 100% rename from crates/test-components/return-params/Cargo.toml rename to test-components/return-params/Cargo.toml diff --git a/crates/test-components/return-params/src/lib.rs b/test-components/return-params/src/lib.rs similarity index 95% rename from crates/test-components/return-params/src/lib.rs rename to test-components/return-params/src/lib.rs index af3f522..950356b 100644 --- a/crates/test-components/return-params/src/lib.rs +++ b/test-components/return-params/src/lib.rs @@ -1,6 +1,6 @@ wit_bindgen::generate!({ world: "handler", - path: "../../../wit" + path: "../../wit" }); struct TestHandler; diff --git a/crates/test-components/return-status-code-200/Cargo.toml b/test-components/return-status-code-200/Cargo.toml similarity index 100% rename from crates/test-components/return-status-code-200/Cargo.toml rename to test-components/return-status-code-200/Cargo.toml diff --git a/crates/test-components/return-status-code-200/src/lib.rs b/test-components/return-status-code-200/src/lib.rs similarity index 91% rename from crates/test-components/return-status-code-200/src/lib.rs rename to test-components/return-status-code-200/src/lib.rs index 5ef78ff..501999d 100644 --- a/crates/test-components/return-status-code-200/src/lib.rs +++ b/test-components/return-status-code-200/src/lib.rs @@ -1,6 +1,6 @@ wit_bindgen::generate!({ world: "handler", - path: "../../../wit" + path: "../../wit" }); struct TestHandler; From 70c5e73f11cb655afe2f3c7e91f7a9551e557ccf Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:23:03 +0200 Subject: [PATCH 05/19] Refactor modules (#246) Co-authored-by: Guillaume Fournier <> --- cli/src/build/mod.rs | 5 +-- cli/src/commands/create.rs | 8 ++--- cli/src/deploy/components.rs | 61 ++++++++++++++++++++++++++++++++ cli/src/deploy/create.rs | 19 +++------- cli/src/deploy/delete.rs | 10 +++--- cli/src/deploy/mod.rs | 28 ++++++++------- cli/src/deploy/plan.rs | 11 +++--- cli/src/deploy/update.rs | 22 ++++-------- cli/src/lib.rs | 1 - cli/src/manifest.rs | 40 ++++++++++++++++----- cli/src/module.rs | 67 ------------------------------------ 11 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 cli/src/deploy/components.rs delete mode 100644 cli/src/module.rs diff --git a/cli/src/build/mod.rs b/cli/src/build/mod.rs index 6a01c35..d8b531e 100644 --- a/cli/src/build/mod.rs +++ b/cli/src/build/mod.rs @@ -1,7 +1,8 @@ pub mod cargo; pub mod golang; -use crate::{manifest::Manifest, module::FunctionMetadata, terminal::Terminal}; +use crate::manifest::Component; +use crate::{manifest::Manifest, terminal::Terminal}; use anyhow::anyhow; use anyhow::Context; @@ -89,7 +90,7 @@ pub fn build_by_name(name: &str, manifest: &Manifest) -> anyhow::Result<()> { Ok(()) } -pub fn build(metadata: &FunctionMetadata) -> anyhow::Result<()> { +pub fn build(metadata: &Component) -> anyhow::Result<()> { match metadata.language { Language::Rust => { let cargo = CargoAdapter::new(); diff --git a/cli/src/commands/create.rs b/cli/src/commands/create.rs index a947c39..5b4cbbd 100644 --- a/cli/src/commands/create.rs +++ b/cli/src/commands/create.rs @@ -2,8 +2,7 @@ use super::Command; use crate::{ build::BaseAdapter, config::Config, - manifest::Manifest, - module::FunctionMetadata, + manifest::{Component, Manifest}, templates::{Template, TEMPLATES}, terminal::Terminal, }; @@ -27,9 +26,10 @@ impl Command for CreateCommand { let terminal = Terminal::new(); let config = Config::default(); let git = GitAdapter::new(); - let mut manifest = Manifest::from_yaml(&config.manifest_path)?; + terminal.write_heading("Creating component")?; + let index = terminal.select_prompt("Select a template", &TEMPLATES)?; let template = Template::new(self.name.clone(), index); @@ -60,7 +60,7 @@ pub fn create( git.checkout_template(temp_dir.path(), &template.subpath)?; copy_dir(&temp_dir.path().join(&template.subpath), to)?; - let module = FunctionMetadata::from_template(template); + let module = Component::from_template(template); manifest.add_module(module)?; Ok(()) } diff --git a/cli/src/deploy/components.rs b/cli/src/deploy/components.rs new file mode 100644 index 0000000..07d9027 --- /dev/null +++ b/cli/src/deploy/components.rs @@ -0,0 +1,61 @@ +use crate::manifest::Component; +use common::dtos::{CreateFunctionDTO, GetFunctionDTO, Language}; +use std::{fs, hash::Hash}; + +#[derive(Debug, Clone, Default, Eq, PartialOrd, Ord)] +pub struct BuildedComponent { + pub name: String, + pub language: Language, + pub hash: String, + pub wasm: Option>, +} + +impl Hash for BuildedComponent { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for BuildedComponent { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl TryFrom for BuildedComponent { + type Error = anyhow::Error; + + fn try_from(value: Component) -> Result { + let wasm = fs::read(value.handler_path())?; + let hash = common::hash::hash(&wasm); + + let component_with_payload = Self { + name: value.name, + language: value.language, + hash, + wasm: Some(wasm), + }; + Ok(component_with_payload) + } +} + +impl From for CreateFunctionDTO { + fn from(value: BuildedComponent) -> Self { + Self { + name: value.name, + language: value.language, + wasm: value.wasm.unwrap(), + } + } +} + +impl From for BuildedComponent { + fn from(value: GetFunctionDTO) -> Self { + Self { + name: value.name, + language: value.language, + hash: value.hash, + wasm: Default::default(), + } + } +} diff --git a/cli/src/deploy/create.rs b/cli/src/deploy/create.rs index cfc2d8a..5f54452 100644 --- a/cli/src/deploy/create.rs +++ b/cli/src/deploy/create.rs @@ -1,23 +1,14 @@ -use crate::module::{self, BuildFunctionMetadata}; +use super::{components::BuildedComponent, DeployStep}; use client::function::FunctionClient; -use common::dtos::CreateFunctionDTO; use console::style; use std::{collections::HashSet, fmt::Display}; -use super::DeployStep; - #[derive(Debug, Clone, Default)] -pub struct CreateStep(pub BuildFunctionMetadata); +pub struct CreateStep(pub BuildedComponent); impl DeployStep for CreateStep { fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { - let function = CreateFunctionDTO { - name: self.0.name.clone(), - language: self.0.language, - wasm: module::wasm(&self.0.name)?, - }; - - client.create(project, &function)?; + client.create(project, &self.0.clone().into())?; Ok(()) } } @@ -31,8 +22,8 @@ impl Display for CreateStep { } pub fn create_steps( - local_modules: &HashSet, - remote_modules: &HashSet, + local_modules: &HashSet, + remote_modules: &HashSet, ) -> Vec { local_modules .difference(remote_modules) diff --git a/cli/src/deploy/delete.rs b/cli/src/deploy/delete.rs index 41f4c2c..5a5e7a5 100644 --- a/cli/src/deploy/delete.rs +++ b/cli/src/deploy/delete.rs @@ -1,12 +1,10 @@ -use crate::module::BuildFunctionMetadata; +use super::{components::BuildedComponent, DeployStep}; use client::function::FunctionClient; use console::style; use std::{collections::HashSet, fmt::Display}; -use super::DeployStep; - #[derive(Debug, Clone, Default)] -pub struct DeleteStep(pub BuildFunctionMetadata); +pub struct DeleteStep(pub BuildedComponent); impl DeployStep for DeleteStep { fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { @@ -24,8 +22,8 @@ impl Display for DeleteStep { } pub fn delete_steps( - local_modules: &HashSet, - remote_modules: &HashSet, + local_modules: &HashSet, + remote_modules: &HashSet, ) -> Vec { remote_modules .difference(local_modules) diff --git a/cli/src/deploy/mod.rs b/cli/src/deploy/mod.rs index a2e05f4..b48d850 100644 --- a/cli/src/deploy/mod.rs +++ b/cli/src/deploy/mod.rs @@ -1,12 +1,13 @@ -use self::plan::DeployPlan; -use crate::{manifest::Manifest, module::BuildFunctionMetadata, terminal::Terminal}; -use client::{function::FunctionClient, project::ProjectClient}; - +mod components; mod create; mod delete; mod plan; mod update; +use self::{components::BuildedComponent, plan::DeployPlan}; +use crate::{manifest::Manifest, terminal::Terminal}; +use client::{function::FunctionClient, project::ProjectClient}; + trait DeployStep { fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()>; } @@ -24,17 +25,19 @@ pub fn deploy_project( project_client.create(&project)?; }; - let local_functions: Vec = manifest + let local_functions: Vec = manifest .functions - .into_iter() - .map(BuildFunctionMetadata::from) + .iter() + .filter(|component| component.is_build()) + .cloned() + .map(|component| BuildedComponent::try_from(component).unwrap()) .collect(); - let remote_functions: Vec = project_client + let remote_functions: Vec = project_client .get(&project)? .functions .into_iter() - .map(BuildFunctionMetadata::from) + .map(BuildedComponent::from) .collect(); let plan = DeployPlan::new(local_functions, remote_functions); @@ -57,11 +60,12 @@ pub fn deploy_function( project_client.create(&project)?; }; - let local_function: BuildFunctionMetadata = manifest + let local_function: BuildedComponent = manifest .get_module_by_name(name) .ok_or(anyhow::anyhow!("Module not found"))? - .into(); - let remote_function: BuildFunctionMetadata = function_client.read(&project, name)?.into(); + .try_into()?; + + let remote_function: BuildedComponent = function_client.read(&project, name)?.into(); let plan = DeployPlan::new(vec![local_function], vec![remote_function]); prompt_deploy(&plan, terminal, function_client, &project)?; diff --git a/cli/src/deploy/plan.rs b/cli/src/deploy/plan.rs index 0f33255..5f7d082 100644 --- a/cli/src/deploy/plan.rs +++ b/cli/src/deploy/plan.rs @@ -1,10 +1,11 @@ use super::{ + components::BuildedComponent, create::{self, CreateStep}, delete::{self, DeleteStep}, update::{self, UpdateStep}, DeployStep, }; -use crate::{module::BuildFunctionMetadata, terminal::Terminal}; +use crate::terminal::Terminal; use client::function::FunctionClient; use console::style; use std::{collections::HashSet, fmt::Display}; @@ -19,11 +20,11 @@ pub struct DeployPlan { impl DeployPlan { pub fn new( - local_modules: Vec, - remote_modules: Vec, + local_modules: Vec, + remote_modules: Vec, ) -> Self { - let local_modules: HashSet = HashSet::from_iter(local_modules); - let remote_modules: HashSet = HashSet::from_iter(remote_modules); + let local_modules: HashSet = HashSet::from_iter(local_modules); + let remote_modules: HashSet = HashSet::from_iter(remote_modules); let create_steps = create::create_steps(&local_modules, &remote_modules); let update_steps = update::update_steps(&local_modules, &remote_modules); diff --git a/cli/src/deploy/update.rs b/cli/src/deploy/update.rs index bf25f48..9743183 100644 --- a/cli/src/deploy/update.rs +++ b/cli/src/deploy/update.rs @@ -1,22 +1,14 @@ -use super::DeployStep; -use crate::module::{self, BuildFunctionMetadata}; +use super::{BuildedComponent, DeployStep}; use client::function::FunctionClient; -use common::dtos::CreateFunctionDTO; use console::style; use std::{collections::HashSet, fmt::Display}; #[derive(Debug, Clone, Default)] -pub struct UpdateStep(pub BuildFunctionMetadata); +pub struct UpdateStep(pub BuildedComponent); impl DeployStep for UpdateStep { fn deploy(&self, project: &str, client: &FunctionClient) -> anyhow::Result<()> { - let function = CreateFunctionDTO { - name: self.0.name.clone(), - language: self.0.language, - wasm: module::wasm(&self.0.name)?, - }; - - client.update(project, &function)?; + client.update(project, &self.0.clone().into())?; Ok(()) } } @@ -30,16 +22,16 @@ impl Display for UpdateStep { } pub fn update_steps( - local_modules: &HashSet, - remote_modules: &HashSet, + local_modules: &HashSet, + remote_modules: &HashSet, ) -> Vec { - let mut local_updates: Vec = local_modules + let mut local_updates: Vec = local_modules .intersection(remote_modules) .cloned() .collect(); local_updates.sort(); - let mut remote_updates: Vec = remote_modules + let mut remote_updates: Vec = remote_modules .intersection(local_modules) .cloned() .collect(); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 0470cec..206f2ab 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,6 +3,5 @@ pub mod commands; mod config; mod deploy; mod manifest; -mod module; mod templates; mod terminal; diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index c5bc106..49ca125 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -1,12 +1,36 @@ -use crate::{config::Config, module::FunctionMetadata}; +use crate::{config::Config, templates::Template}; +use common::dtos::Language; use serde::{Deserialize, Serialize}; -use std::path::Path; +use std::path::{Path, PathBuf}; #[derive(Serialize, Deserialize, Debug, Default)] pub struct Manifest { #[serde(rename = "project")] pub project_name: String, - pub functions: Vec, + pub functions: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Component { + pub name: String, + pub language: Language, +} + +impl Component { + pub fn from_template(template: &Template) -> Self { + Self { + name: template.name.clone(), + language: template.language, + } + } + + pub fn is_build(&self) -> bool { + self.handler_path().exists() + } + + pub fn handler_path(&self) -> PathBuf { + Path::new(&self.name).join("out").join("handler.wasm") + } } impl Manifest { @@ -31,24 +55,24 @@ impl Manifest { Ok(serde_yaml::from_reader(file)?) } - pub fn add_module(&mut self, metadata: FunctionMetadata) -> anyhow::Result<()> { - self.functions.push(metadata); + pub fn add_module(&mut self, component: Component) -> anyhow::Result<()> { + self.functions.push(component); self.save()?; Ok(()) } - pub fn get_module_by_name(&self, name: &str) -> Option { + pub fn get_module_by_name(&self, name: &str) -> Option { self.functions .iter() .cloned() - .find(|metadata| metadata.name == name) + .find(|component| component.name == name) } pub fn delete_module_by_name(&mut self, name: &str) -> anyhow::Result<()> { let index = self .functions .iter() - .position(|metadata: &FunctionMetadata| metadata.name == name) + .position(|component: &Component| component.name == name) .ok_or(anyhow::anyhow!("Module not found"))?; self.functions.remove(index); self.save()?; diff --git a/cli/src/module.rs b/cli/src/module.rs deleted file mode 100644 index 584ab56..0000000 --- a/cli/src/module.rs +++ /dev/null @@ -1,67 +0,0 @@ -use common::dtos::{GetFunctionDTO, Language}; -use common::hash; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::hash::{Hash, Hasher}; -use std::path::Path; - -use crate::templates::Template; - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct FunctionMetadata { - pub name: String, - pub language: Language, -} - -impl From for BuildFunctionMetadata { - fn from(value: FunctionMetadata) -> Self { - Self { - name: value.name.clone(), - language: value.language, - hash: hash::hash(&wasm(&value.name).unwrap()), - } - } -} - -impl FunctionMetadata { - pub fn from_template(template: &Template) -> Self { - Self { - name: template.name.clone(), - language: template.language, - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialOrd, Ord, Eq)] -pub struct BuildFunctionMetadata { - pub name: String, - pub language: Language, - pub hash: String, -} - -impl Hash for BuildFunctionMetadata { - fn hash(&self, state: &mut H) { - self.name.hash(state); - } -} - -impl PartialEq for BuildFunctionMetadata { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -impl From for BuildFunctionMetadata { - fn from(value: GetFunctionDTO) -> Self { - Self { - name: value.name, - language: value.language, - hash: value.hash, - } - } -} - -pub fn wasm(name: &str) -> anyhow::Result> { - let path = Path::new(&name).join("out").join("handler.wasm"); - Ok(fs::read(path)?) -} From d56c9a625197f78f48d0f939f1f05d2f28e964d6 Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:21:59 +0200 Subject: [PATCH 06/19] Add show command (#247) * feat: Add show command --------- Co-authored-by: Guillaume Fournier <> --- cli/src/bin/noops.rs | 1 + cli/src/build/mod.rs | 2 +- cli/src/commands/create.rs | 4 +- cli/src/commands/destroy.rs | 2 +- cli/src/commands/mod.rs | 6 ++- cli/src/commands/show.rs | 28 +++++++++++++ cli/src/deploy/mod.rs | 2 +- cli/src/info/component.rs | 41 +++++++++++++++++++ cli/src/info/mod.rs | 66 +++++++++++++++++++++++++++++++ cli/src/info/project.rs | 34 ++++++++++++++++ cli/src/lib.rs | 1 + cli/src/manifest.rs | 6 +-- crates/client/src/function.rs | 50 ++++++++++++++++++++++- crates/common/src/dtos.rs | 1 + server/src/controller/auth.rs | 2 +- server/src/controller/execute.rs | 2 +- server/src/controller/function.rs | 2 +- server/src/controller/mod.rs | 10 ++--- server/src/controller/project.rs | 2 +- server/src/main.rs | 2 +- server/src/repository/function.rs | 12 +----- server/src/service/mod.rs | 21 ++++++++++ 22 files changed, 266 insertions(+), 31 deletions(-) create mode 100644 cli/src/commands/show.rs create mode 100644 cli/src/info/component.rs create mode 100644 cli/src/info/mod.rs create mode 100644 cli/src/info/project.rs diff --git a/cli/src/bin/noops.rs b/cli/src/bin/noops.rs index b334fd3..1c2c2dd 100644 --- a/cli/src/bin/noops.rs +++ b/cli/src/bin/noops.rs @@ -11,6 +11,7 @@ fn main() -> anyhow::Result<()> { commands::Cli::Create(cmd) => cmd.execute()?, commands::Cli::Deploy(cmd) => cmd.execute()?, commands::Cli::Destroy(cmd) => cmd.execute()?, + commands::Cli::Show(cmd) => cmd.execute()?, } Ok(()) } diff --git a/cli/src/build/mod.rs b/cli/src/build/mod.rs index d8b531e..4297fe8 100644 --- a/cli/src/build/mod.rs +++ b/cli/src/build/mod.rs @@ -84,7 +84,7 @@ pub fn build_function(terminal: &Terminal, manifest: &Manifest, name: &str) -> a pub fn build_by_name(name: &str, manifest: &Manifest) -> anyhow::Result<()> { let module = manifest - .get_module_by_name(name) + .get(name) .ok_or(anyhow::anyhow!("Module not found"))?; build(&module)?; Ok(()) diff --git a/cli/src/commands/create.rs b/cli/src/commands/create.rs index 5b4cbbd..ecc081b 100644 --- a/cli/src/commands/create.rs +++ b/cli/src/commands/create.rs @@ -48,7 +48,7 @@ pub fn create( git: &GitAdapter, template: &Template, ) -> anyhow::Result<()> { - if manifest.get_module_by_name(&template.name).is_some() { + if manifest.get(&template.name).is_some() { anyhow::bail!("Module already exists"); } let to = Path::new(&template.name); @@ -61,7 +61,7 @@ pub fn create( copy_dir(&temp_dir.path().join(&template.subpath), to)?; let module = Component::from_template(template); - manifest.add_module(module)?; + manifest.add(module)?; Ok(()) } diff --git a/cli/src/commands/destroy.rs b/cli/src/commands/destroy.rs index a9abdc7..62c4ba4 100644 --- a/cli/src/commands/destroy.rs +++ b/cli/src/commands/destroy.rs @@ -26,7 +26,7 @@ impl Command for DestroyCommand { } pub fn destroy(name: &str, manifest: &mut Manifest) -> anyhow::Result<()> { - manifest.delete_module_by_name(name)?; + manifest.delete(name)?; fs::remove_dir_all(Path::new(name))?; Ok(()) } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 89d61e7..ad77882 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -4,10 +4,11 @@ pub mod deploy; pub mod destroy; pub mod init; pub mod login; +pub mod show; use self::{ build::BuildCommand, create::CreateCommand, deploy::DeployCommand, destroy::DestroyCommand, - init::InitCommand, login::LoginCommand, + init::InitCommand, login::LoginCommand, show::ShowCommand, }; use clap::Parser; @@ -36,4 +37,7 @@ pub enum Cli { /// Destroy a function Destroy(DestroyCommand), + + /// Show information about the project or a function + Show(ShowCommand), } diff --git a/cli/src/commands/show.rs b/cli/src/commands/show.rs new file mode 100644 index 0000000..32b63f0 --- /dev/null +++ b/cli/src/commands/show.rs @@ -0,0 +1,28 @@ +use super::{deploy::get_jwt, Command}; +use crate::{config::Config, info, manifest::Manifest, terminal::Terminal}; +use clap::Parser; +use client::{function::FunctionClient, project::ProjectClient}; + +#[derive(Parser, Debug)] +pub struct ShowCommand { + pub name: Option, +} + +impl Command for ShowCommand { + fn execute(&self) -> anyhow::Result<()> { + let terminal = Terminal::new(); + let config = Config::default(); + let manifest = Manifest::from_yaml(&config.manifest_path)?; + + let jwt = get_jwt(&config.jwt_file)?.ok_or(anyhow::anyhow!("You are not logged in"))?; + let function_client = FunctionClient::new(&config.base_url, jwt.clone()); + let project_client = ProjectClient::new(&config.base_url, jwt); + + match self.name.clone() { + Some(name) => info::show_function(&name, &manifest, &function_client, &terminal)?, + None => info::show_project(&manifest, &project_client, &terminal)?, + } + + Ok(()) + } +} diff --git a/cli/src/deploy/mod.rs b/cli/src/deploy/mod.rs index b48d850..7891dd9 100644 --- a/cli/src/deploy/mod.rs +++ b/cli/src/deploy/mod.rs @@ -61,7 +61,7 @@ pub fn deploy_function( }; let local_function: BuildedComponent = manifest - .get_module_by_name(name) + .get(name) .ok_or(anyhow::anyhow!("Module not found"))? .try_into()?; diff --git a/cli/src/info/component.rs b/cli/src/info/component.rs new file mode 100644 index 0000000..006d23d --- /dev/null +++ b/cli/src/info/component.rs @@ -0,0 +1,41 @@ +use std::fmt::Display; + +use common::dtos::{GetFunctionDTO, Language}; + +use crate::manifest::Component; + +pub struct ComponentInformation { + pub name: String, + pub language: Language, + pub deployed: bool, + pub build: bool, + pub link: String, +} + +impl ComponentInformation { + pub fn new(local_component: &Component, remote_component: Option) -> Self { + let deployed = remote_component.is_some(); + let link = if let Some(remote_component) = remote_component { + remote_component.link + } else { + "N/A".to_string() + }; + + ComponentInformation { + name: local_component.name.clone(), + language: local_component.language, + deployed, + build: local_component.is_build(), + link, + } + } +} + +impl Display for ComponentInformation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "Name:\t\t{}\nLanguage:\t{}\nBuild:\t\t{}\nDeployed:\t{}\nLink:\t\t{}", + self.name, self.language, self.build, self.deployed, self.link + )) + } +} diff --git a/cli/src/info/mod.rs b/cli/src/info/mod.rs new file mode 100644 index 0000000..a37de3d --- /dev/null +++ b/cli/src/info/mod.rs @@ -0,0 +1,66 @@ +mod component; +mod project; + +use crate::{ + info::{component::ComponentInformation, project::ProjectInformation}, + manifest::Manifest, + terminal::Terminal, +}; +use client::{function::FunctionClient, project::ProjectClient}; +use common::dtos::GetFunctionDTO; + +pub fn show_project( + manifest: &Manifest, + project_client: &ProjectClient, + terminal: &Terminal, +) -> anyhow::Result<()> { + let deployed = project_client.exists(&manifest.project_name)?; + let mut remote_components: Vec = Default::default(); + + if deployed { + remote_components = project_client.get(&manifest.project_name)?.functions; + } + let local_components = manifest.functions.clone(); + + let component_information: Vec = local_components + .iter() + .map(|local_component| { + let remote_component = remote_components + .iter() + .cloned() + .find(|remote_component| remote_component.name == local_component.name); + + ComponentInformation::new(local_component, remote_component) + }) + .collect(); + + let project_info = ProjectInformation::new( + manifest.project_name.clone(), + deployed, + component_information, + ); + + terminal.write_heading("Showing Project")?; + terminal.write_text(project_info.to_string())?; + + Ok(()) +} + +pub fn show_function( + name: &str, + manifest: &Manifest, + function_client: &FunctionClient, + terminal: &Terminal, +) -> anyhow::Result<()> { + let local_component = manifest + .get(name) + .ok_or(anyhow::anyhow!("Module not found"))?; + let remote_component = function_client.read_opt(&manifest.project_name, name)?; + + let component_info = ComponentInformation::new(&local_component, remote_component); + + terminal.write_heading("Showing component")?; + terminal.write_text(component_info.to_string())?; + + Ok(()) +} diff --git a/cli/src/info/project.rs b/cli/src/info/project.rs new file mode 100644 index 0000000..1e5f9d5 --- /dev/null +++ b/cli/src/info/project.rs @@ -0,0 +1,34 @@ +use std::fmt::Display; + +use super::component::ComponentInformation; + +pub struct ProjectInformation { + name: String, + deployed: bool, + components: Vec, +} + +impl ProjectInformation { + pub fn new(name: String, deployed: bool, components: Vec) -> Self { + Self { + name, + deployed, + components, + } + } +} + +impl Display for ProjectInformation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "Name:\t\t{}\nDeployed:\t{}", + self.name, self.deployed + ))?; + f.write_fmt(format_args!("\n\nComponents:\t{}", self.components.len()))?; + for component in &self.components { + f.write_fmt(format_args!("\n\n{}", component))?; + } + + Ok(()) + } +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 206f2ab..8eee253 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -2,6 +2,7 @@ mod build; pub mod commands; mod config; mod deploy; +mod info; mod manifest; mod templates; mod terminal; diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index 49ca125..e577af2 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -55,20 +55,20 @@ impl Manifest { Ok(serde_yaml::from_reader(file)?) } - pub fn add_module(&mut self, component: Component) -> anyhow::Result<()> { + pub fn add(&mut self, component: Component) -> anyhow::Result<()> { self.functions.push(component); self.save()?; Ok(()) } - pub fn get_module_by_name(&self, name: &str) -> Option { + pub fn get(&self, name: &str) -> Option { self.functions .iter() .cloned() .find(|component| component.name == name) } - pub fn delete_module_by_name(&mut self, name: &str) -> anyhow::Result<()> { + pub fn delete(&mut self, name: &str) -> anyhow::Result<()> { let index = self .functions .iter() diff --git a/crates/client/src/function.rs b/crates/client/src/function.rs index 1a8d9b1..2d826cd 100644 --- a/crates/client/src/function.rs +++ b/crates/client/src/function.rs @@ -1,5 +1,5 @@ use common::dtos::{CreateFunctionDTO, GetFunctionDTO}; -use reqwest::{blocking::Client as ReqwestClient, header::AUTHORIZATION, Url}; +use reqwest::{blocking::Client as ReqwestClient, header::AUTHORIZATION, StatusCode, Url}; pub struct FunctionClient { base_url: Url, @@ -56,6 +56,54 @@ impl FunctionClient { Ok(response.json()?) } + pub fn read_opt( + &self, + project: &str, + function: &str, + ) -> anyhow::Result> { + let url = self.function_url(project, function)?; + + let response = self + .client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() && response.status() != StatusCode::NOT_FOUND { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()? + ); + } + + if response.status() == StatusCode::NOT_FOUND { + Ok(None) + } else { + Ok(Some(response.json()?)) + } + } + + pub fn exists(&self, project: &str, function: &str) -> anyhow::Result { + let url = self.function_url(project, function)?; + + let response = self + .client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.jwt)) + .send()?; + + if !response.status().is_success() && response.status() != StatusCode::NOT_FOUND { + anyhow::bail!( + "Request failed with status code {}: {}", + response.status(), + response.text()? + ); + } + + Ok(response.status().is_success() && response.status() != StatusCode::NOT_FOUND) + } + pub fn update(&self, project: &str, function: &CreateFunctionDTO) -> anyhow::Result<()> { let url = self.function_url(project, &function.name)?; diff --git a/crates/common/src/dtos.rs b/crates/common/src/dtos.rs index e7ae150..3d30940 100644 --- a/crates/common/src/dtos.rs +++ b/crates/common/src/dtos.rs @@ -68,6 +68,7 @@ pub struct GetFunctionDTO { pub name: String, pub language: Language, pub hash: String, + pub link: String, } impl Display for Language { diff --git a/server/src/controller/auth.rs b/server/src/controller/auth.rs index 7ab9029..f0dd575 100644 --- a/server/src/controller/auth.rs +++ b/server/src/controller/auth.rs @@ -11,7 +11,7 @@ use axum::{ }; use serde::Deserialize; -pub fn create_routes(state: AppState) -> Router { +pub fn routes(state: AppState) -> Router { Router::new() .route("/api/auth/login", get(login)) .with_state(state) diff --git a/server/src/controller/execute.rs b/server/src/controller/execute.rs index ea01d68..26fbb73 100644 --- a/server/src/controller/execute.rs +++ b/server/src/controller/execute.rs @@ -9,7 +9,7 @@ use axum::{ }; use std::collections::HashMap; -pub fn create_routes(state: AppState) -> Router { +pub fn routes(state: AppState) -> Router { Router::new() .route("/:function", get(execute)) .with_state(state) diff --git a/server/src/controller/function.rs b/server/src/controller/function.rs index 5fc8edb..40967a1 100644 --- a/server/src/controller/function.rs +++ b/server/src/controller/function.rs @@ -11,7 +11,7 @@ use common::dtos; const MAX_CONTENT_SIZE_IN_BYTES: usize = 10_000_000; -pub fn create_routes(state: AppState) -> Router { +pub fn routes(state: AppState) -> Router { Router::new() .route( "/api/:project_name/:function_name", diff --git a/server/src/controller/mod.rs b/server/src/controller/mod.rs index 402b6ec..cd4978b 100644 --- a/server/src/controller/mod.rs +++ b/server/src/controller/mod.rs @@ -59,14 +59,14 @@ impl FromRef for FunctionService { } } -pub fn create_routes(state: AppState) -> Router { +pub fn routes(state: AppState) -> Router { Router::new() - .merge(project::create_routes(state.clone())) - .merge(function::create_routes(state.clone())) + .merge(project::routes(state.clone())) + .merge(function::routes(state.clone())) .route_layer(middleware::from_fn_with_state( state.clone(), auth::auth_middleware, )) - .merge(auth::create_routes(state.clone())) - .merge(execute::create_routes(state)) + .merge(auth::routes(state.clone())) + .merge(execute::routes(state)) } diff --git a/server/src/controller/project.rs b/server/src/controller/project.rs index 17e1a77..3bfb167 100644 --- a/server/src/controller/project.rs +++ b/server/src/controller/project.rs @@ -12,7 +12,7 @@ use axum::{ Extension, Json, Router, }; -pub fn create_routes(state: AppState) -> Router { +pub fn routes(state: AppState) -> Router { Router::new() .route( "/api/:project_name", diff --git a/server/src/main.rs b/server/src/main.rs index 0e918c0..7520476 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -35,7 +35,7 @@ async fn main() -> anyhow::Result<()> { let state = create_app_state(Path::new(DATABASE_CONNECTION), Path::new(WASMSTORE_PREFIX))?; run_database_migration()?; - let app = controller::create_routes(state).layer(TraceLayer::new_for_http()); + let app = controller::routes(state).layer(TraceLayer::new_for_http()); let addr = SocketAddr::from(([0, 0, 0, 0], 8080)); tracing::info!("listening on {}", addr); Server::bind(&addr) diff --git a/server/src/repository/function.rs b/server/src/repository/function.rs index f082482..8ca52f7 100644 --- a/server/src/repository/function.rs +++ b/server/src/repository/function.rs @@ -5,7 +5,7 @@ use super::{ Repository, }; use anyhow; -use common::dtos::{GetFunctionDTO, Language}; +use common::dtos::Language; use diesel::{ prelude::*, r2d2::{ConnectionManager, Pool}, @@ -45,16 +45,6 @@ impl Function { } } -impl From for GetFunctionDTO { - fn from(function: Function) -> Self { - GetFunctionDTO { - name: function.name, - language: function.language, - hash: function.hash, - } - } -} - #[cfg_attr(test, faux::create)] #[derive(Debug, Clone)] pub struct FunctionRepository { diff --git a/server/src/service/mod.rs b/server/src/service/mod.rs index 1465d9a..4719517 100644 --- a/server/src/service/mod.rs +++ b/server/src/service/mod.rs @@ -1,3 +1,24 @@ +use common::dtos::GetFunctionDTO; + +use crate::repository::function::Function; + pub mod auth; pub mod function; pub mod project; + +const URL: &str = "http://localhost:8080/"; + +fn function_url(function_id: &str) -> String { + URL.to_string() + function_id +} + +impl From for GetFunctionDTO { + fn from(value: Function) -> Self { + GetFunctionDTO { + name: value.name, + language: value.language, + hash: value.hash, + link: function_url(&value.id), + } + } +} From f85ab5ebb7564c1626a57664dc83505fae26b767 Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:35:09 +0200 Subject: [PATCH 07/19] chore: Deactivate builds for draft prs (#249) Co-authored-by: Guillaume Fournier <> --- .github/workflows/rust.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 64f0cd7..3caab49 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,7 +13,8 @@ env: CARGO_TERM_COLOR: always jobs: - server_build_and_test: + server_build_and_test: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Checkout @@ -50,6 +51,7 @@ jobs: run: cargo test -p noops-server cli_build_and_test: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Checkout From fbd540fafbd4739c86b19c442add194093c9825a Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:07:10 +0200 Subject: [PATCH 08/19] feat: fetch additional user information from github (#255) Co-authored-by: Guillaume Fournier <> --- server/diesel.toml | 2 +- .../2023-07-10-124746_create_users/up.sql | 4 +++ server/src/github.rs | 28 ++++++++++++----- server/src/repository/project.rs | 12 +++++++ server/src/repository/schema.rs | 11 ++++++- server/src/repository/user.rs | 31 ++++++++++++++++++- server/src/service/auth.rs | 24 ++++++++++++-- server/src/service/function.rs | 11 ++++++- server/src/service/project.rs | 11 ++++++- 9 files changed, 119 insertions(+), 15 deletions(-) diff --git a/server/diesel.toml b/server/diesel.toml index 85fd363..3709482 100644 --- a/server/diesel.toml +++ b/server/diesel.toml @@ -2,7 +2,7 @@ # see https://diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "src/database/schema.rs" +file = "src/repository/schema.rs" custom_type_derives = ["diesel::query_builder::QueryId"] [migrations_directory] diff --git a/server/migrations/2023-07-10-124746_create_users/up.sql b/server/migrations/2023-07-10-124746_create_users/up.sql index 3065515..6ed6d84 100644 --- a/server/migrations/2023-07-10-124746_create_users/up.sql +++ b/server/migrations/2023-07-10-124746_create_users/up.sql @@ -2,6 +2,10 @@ CREATE TABLE users ( id CHAR(21) NOT NULL PRIMARY KEY, email VARCHAR NOT NULL, + name VARCHAR NOT NULL, + location VARCHAR NOT NULL, + company VARCHAR NOT NULL, + github_login VARCHAR NOT NULL, github_id INTEGER NOT NULL, github_access_token VARCHAR NOT NULL, UNIQUE(github_id) diff --git a/server/src/github.rs b/server/src/github.rs index 3f01c2c..7ebeaaa 100644 --- a/server/src/github.rs +++ b/server/src/github.rs @@ -8,19 +8,27 @@ use serde::Deserialize; const GITHUB_API_USER: &str = "https://api.github.com/user"; const GITHUB_API_EMAIL: &str = "https://api.github.com/user/emails"; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct GithubUser { pub id: i32, + pub name: String, pub email: String, + pub login: String, + pub location: String, + pub company: String, pub access_token: String, } -#[derive(Deserialize)] -struct User { - id: i32, +#[derive(Debug, Clone, Default, Deserialize)] +struct RawGithubUser { + pub id: i32, + pub name: String, + pub login: String, + pub location: String, + pub company: String, } -#[derive(Deserialize)] +#[derive(Debug, Clone, Default, Deserialize)] struct Email { email: String, primary: bool, @@ -43,17 +51,21 @@ impl GithubClient { pub async fn get_user(&self, access_token: String) -> anyhow::Result { let headers = self.create_headers(&access_token)?; - let user_infos = self.get_user_infos(headers.clone()).await?; + let user_info = self.get_user_infos(headers.clone()).await?; let email = self.get_primary_email(headers).await?; Ok(GithubUser { - id: user_infos.id, email: email.email, + name: user_info.name, + location: user_info.location, + company: user_info.company, + login: user_info.login, + id: user_info.id, access_token, }) } - async fn get_user_infos(&self, headers: HeaderMap) -> anyhow::Result { + async fn get_user_infos(&self, headers: HeaderMap) -> anyhow::Result { let user = self .client .get(GITHUB_API_USER) diff --git a/server/src/repository/project.rs b/server/src/repository/project.rs index fc625bd..e97df88 100644 --- a/server/src/repository/project.rs +++ b/server/src/repository/project.rs @@ -105,7 +105,11 @@ mod tests { const USER_ID: &str = "oveethai3oophaV6Aiwei"; const USER_EMAIL: &str = "test@example.com"; + const USER_NAME: &str = "user_name"; + const USER_LOCATION: &str = "Hamburg"; + const USER_COMPANY: &str = "Noops.io"; const USER_GH_ACCESS_TOKEN: &str = "Yiu0Hae4ietheereij4OhneuNe6tae0e"; + const USER_GH_LOGIN: &str = "login_name"; const USER_GH_ID: i32 = 42; lazy_static! { @@ -189,7 +193,11 @@ mod tests { let (_temp_dir, projects) = setup()?; let user = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, + USER_GH_LOGIN.to_string(), USER_GH_ACCESS_TOKEN.to_string(), ); let mut project = PROJECT.clone(); @@ -209,7 +217,11 @@ mod tests { let (_temp_dir, projects) = setup()?; let user = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, + USER_GH_LOGIN.to_string(), USER_GH_ACCESS_TOKEN.to_string(), ); projects.create(&PROJECT)?; diff --git a/server/src/repository/schema.rs b/server/src/repository/schema.rs index 240db84..48922fe 100644 --- a/server/src/repository/schema.rs +++ b/server/src/repository/schema.rs @@ -22,6 +22,10 @@ diesel::table! { users (id) { id -> Text, email -> Text, + name -> Text, + location -> Text, + company -> Text, + github_login -> Text, github_id -> Integer, github_access_token -> Text, } @@ -29,4 +33,9 @@ diesel::table! { diesel::joinable!(functions -> projects (project_id)); diesel::joinable!(projects -> users (user_id)); -diesel::allow_tables_to_appear_in_same_query!(functions, projects, users,); + +diesel::allow_tables_to_appear_in_same_query!( + functions, + projects, + users, +); diff --git a/server/src/repository/user.rs b/server/src/repository/user.rs index 0f38b14..af23b27 100644 --- a/server/src/repository/user.rs +++ b/server/src/repository/user.rs @@ -14,16 +14,32 @@ use diesel::{ pub struct User { pub id: String, pub email: String, + pub name: String, + pub location: String, + pub company: String, + pub github_login: String, pub github_id: i32, pub github_access_token: String, } impl User { - pub fn new(email: String, github_id: i32, github_access_token: String) -> Self { + pub fn new( + email: String, + name: String, + location: String, + company: String, + github_id: i32, + github_login: String, + github_access_token: String, + ) -> Self { Self { id: create_id(), email, + name, + location, + company, github_id, + github_login, github_access_token, } } @@ -89,14 +105,23 @@ mod tests { use tempfile::{tempdir, TempDir}; const DATABASE_NAME: &str = "noops_test.sqlite"; + const USER_EMAIL: &str = "test@example.com"; + const USER_NAME: &str = "user_name"; + const USER_LOCATION: &str = "Hamburg"; + const USER_COMPANY: &str = "Noops.io"; const USER_GH_ACCESS_TOKEN: &str = "Yiu0Hae4ietheereij4OhneuNe6tae0e"; + const USER_GH_LOGIN: &str = "login_name"; const USER_GH_ID: i32 = 42; lazy_static! { static ref USER: User = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, + USER_GH_LOGIN.to_string(), USER_GH_ACCESS_TOKEN.to_string() ); } @@ -134,7 +159,11 @@ mod tests { let (_temp_dir, users) = setup()?; let user = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, + USER_GH_LOGIN.to_string(), USER_GH_ACCESS_TOKEN.to_string(), ); users.create(&user)?; diff --git a/server/src/service/auth.rs b/server/src/service/auth.rs index a9e0524..07a622e 100644 --- a/server/src/service/auth.rs +++ b/server/src/service/auth.rs @@ -40,7 +40,15 @@ impl AuthService { let user = match result { Some(user) => user, None => { - let user = User::new(gh_user.email, gh_user.id, github_access_token); + let user = User::new( + gh_user.email, + gh_user.name, + gh_user.location, + gh_user.company, + gh_user.id, + gh_user.login, + github_access_token, + ); self.users.create(&user)?; user } @@ -74,14 +82,22 @@ mod tests { use crate::{github::GithubUser, repository::user::UserRepository}; const USER_EMAIL: &str = "test@example.com"; + const USER_NAME: &str = "user_name"; + const USER_LOCATION: &str = "Hamburg"; + const USER_COMPANY: &str = "Noops.io"; const USER_GH_ACCESS_TOKEN: &str = "Yiu0Hae4ietheereij4OhneuNe6tae0e"; + const USER_GH_LOGIN: &str = "login_name"; const USER_GH_ID: i32 = 42; lazy_static! { static ref USER: User = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, - USER_GH_ACCESS_TOKEN.to_string(), + USER_GH_LOGIN.to_string(), + USER_GH_ACCESS_TOKEN.to_string() ); static ref JWT: String = Jwt::create_token( USER.id.clone(), @@ -93,6 +109,10 @@ mod tests { static ref GITHUB_USER: GithubUser = GithubUser { id: USER_GH_ID, email: USER.email.clone(), + name: USER_NAME.to_string(), + location: USER_LOCATION.to_string(), + company: USER_COMPANY.to_string(), + login: USER_GH_LOGIN.to_string(), access_token: USER_GH_ACCESS_TOKEN.to_string(), }; } diff --git a/server/src/service/function.rs b/server/src/service/function.rs index 2662452..14e1cd7 100644 --- a/server/src/service/function.rs +++ b/server/src/service/function.rs @@ -121,15 +121,24 @@ mod tests { use lazy_static::lazy_static; const PROJECT_NAME: &str = "PROJECT_NAME"; + const USER_EMAIL: &str = "test@example.com"; + const USER_NAME: &str = "user_name"; + const USER_LOCATION: &str = "Hamburg"; + const USER_COMPANY: &str = "Noops.io"; const USER_GH_ACCESS_TOKEN: &str = "Yiu0Hae4ietheereij4OhneuNe6tae0e"; + const USER_GH_LOGIN: &str = "login_name"; const USER_GH_ID: i32 = 42; lazy_static! { static ref USER: User = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, - USER_GH_ACCESS_TOKEN.to_string(), + USER_GH_LOGIN.to_string(), + USER_GH_ACCESS_TOKEN.to_string() ); } diff --git a/server/src/service/project.rs b/server/src/service/project.rs index 08e7583..18e9533 100644 --- a/server/src/service/project.rs +++ b/server/src/service/project.rs @@ -72,15 +72,24 @@ mod tests { use lazy_static::lazy_static; const PROJECT_NAME: &str = "PROJECT_NAME"; + const USER_EMAIL: &str = "test@example.com"; + const USER_NAME: &str = "user_name"; + const USER_LOCATION: &str = "Hamburg"; + const USER_COMPANY: &str = "Noops.io"; const USER_GH_ACCESS_TOKEN: &str = "Yiu0Hae4ietheereij4OhneuNe6tae0e"; + const USER_GH_LOGIN: &str = "login_name"; const USER_GH_ID: i32 = 42; lazy_static! { static ref USER: User = User::new( USER_EMAIL.to_string(), + USER_NAME.to_string(), + USER_LOCATION.to_string(), + USER_COMPANY.to_string(), USER_GH_ID, - USER_GH_ACCESS_TOKEN.to_string(), + USER_GH_LOGIN.to_string(), + USER_GH_ACCESS_TOKEN.to_string() ); } From c82a4f3beef9142befe292880d43426c17795fcd Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:04:13 +0200 Subject: [PATCH 09/19] Fix: component into go wasm (#257) * fix: golang execution results in error * chore: Update core dependencies --------- Co-authored-by: Guillaume Fournier <> --- .vscode/launch.json | 4 +- Cargo.lock | 723 ++++++++++-------- Cargo.toml | 3 +- cli/Cargo.toml | 3 + cli/src/build/golang.rs | 36 +- cli/src/info/mod.rs | 4 +- cli/src/manifest.rs | 2 +- rust-toolchain.toml | 2 +- server/Cargo.toml | 8 +- server/src/bindgen.rs | 12 +- server/src/controller/execute.rs | 6 +- server/src/executor.rs | 73 +- test-components/return-params/Cargo.toml | 2 +- test-components/return-params/src/lib.rs | 9 +- .../return-status-code-200/Cargo.toml | 2 +- .../return-status-code-200/src/lib.rs | 9 +- wit/handler.wit | 4 +- wit/wasi_snapshot_preview1.reactor.wasm | Bin 0 -> 105098 bytes wit/wasi_snapshot_preview1.wasm | Bin 101916 -> 0 bytes 19 files changed, 506 insertions(+), 396 deletions(-) create mode 100644 wit/wasi_snapshot_preview1.reactor.wasm delete mode 100755 wit/wasi_snapshot_preview1.wasm diff --git a/.vscode/launch.json b/.vscode/launch.json index e1dcd98..4cb96f0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,8 +19,8 @@ "kind": "bin" } }, - "args": [], - "cwd": "${workspaceFolder}" + "args": ["build", "hello-go"], + "cwd": "${workspaceFolder}/cli" }, { "type": "lldb", diff --git a/Cargo.lock b/Cargo.lock index 8bc935b..c1aec5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli", -] - [[package]] name = "addr2line" version = "0.20.0" @@ -122,6 +113,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" + [[package]] name = "async-trait" version = "0.1.71" @@ -195,12 +192,12 @@ version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ - "addr2line 0.20.0", + "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.31.1", + "object", "rustc-demangle", ] @@ -266,38 +263,38 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cap-fs-ext" -version = "1.0.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc48200a1a0fa6fba138b1802ad7def18ec1cdd92f7b2a04e21f1bd887f7b9" +checksum = "b779b2d0a001c125b4584ad586268fb4b92d957bff8d26d7fe0dd78283faa814" dependencies = [ "cap-primitives", "cap-std", - "io-lifetimes 1.0.11", + "io-lifetimes 2.0.2", "windows-sys 0.48.0", ] [[package]] name = "cap-primitives" -version = "1.0.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b6df5b295dca8d56f35560be8c391d59f0420f72e546997154e24e765e6451" +checksum = "2bf30c373a3bee22c292b1b6a7a26736a38376840f1af3d2d806455edf8c3899" dependencies = [ "ambient-authority", - "fs-set-times 0.19.2", + "fs-set-times", "io-extras", - "io-lifetimes 1.0.11", + "io-lifetimes 2.0.2", "ipnet", "maybe-owned", - "rustix 0.37.23", + "rustix 0.38.4", "windows-sys 0.48.0", - "winx 0.35.1", + "winx", ] [[package]] name = "cap-rand" -version = "1.0.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25555efacb0b5244cf1d35833d55d21abc916fff0eaad254b8e2453ea9b8ab" +checksum = "577de6cff7c2a47d6b13efe5dd28bf116bd7f8f7db164ea95b7cc2640711f522" dependencies = [ "ambient-authority", "rand", @@ -305,26 +302,26 @@ dependencies = [ [[package]] name = "cap-std" -version = "1.0.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3373a62accd150b4fcba056d4c5f3b552127f0ec86d3c8c102d60b978174a012" +checksum = "84bade423fa6403efeebeafe568fdb230e8c590a275fba2ba978dd112efcf6e9" dependencies = [ "cap-primitives", "io-extras", - "io-lifetimes 1.0.11", - "rustix 0.37.23", + "io-lifetimes 2.0.2", + "rustix 0.38.4", ] [[package]] name = "cap-time-ext" -version = "1.0.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95002993b7baee6b66c8950470e59e5226a23b3af39fc59c47fe416dd39821a" +checksum = "f8f52b3c8f4abfe3252fd0a071f3004aaa3b18936ec97bdbd8763ce03aff6247" dependencies = [ "cap-primitives", "once_cell", - "rustix 0.37.23", - "winx 0.35.1", + "rustix 0.38.4", + "winx", ] [[package]] @@ -470,23 +467,24 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0853f4732d9557cc1f3b4a97112bd5f00a7c619c9828edb45d0a2389ce2913f9" +checksum = "d7348010242a23d0285e5f852f13b07f9540a50f13ab6e92fd047b88490bf5ee" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed06a9dd2e065be7c1f89cdc820c8c328d2cb69b2be0ba6338fe4050b30bf510" +checksum = "38849e3b19bc9a6dbf8bc188876b76e6ba288089a5567be573de50f44801375c" dependencies = [ "bumpalo", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", + "cranelift-control", "cranelift-entity", "cranelift-isle", "gimli", @@ -499,33 +497,42 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f0e0e34689be78c2689b31374404d21f1c7667431fd7cd29bed0fa8a67ce8" +checksum = "a3de51da572e65cb712a47b7413c50208cac61a4201560038de929d9a7f4fadf" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.94.1" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75f869ae826055a5064d4a400abde7806eb86d89765dbae51d42846df23121a" + +[[package]] +name = "cranelift-control" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a05c0a89f82c5731ccad8795cd91cc3c771295aa42c268c7f81607388495d374" +checksum = "bdf6631316ad6ccfd60055740ad25326330d31407a983a454e45c5a62f64d101" +dependencies = [ + "arbitrary", +] [[package]] name = "cranelift-entity" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f184fc14ff49b119760e5f96d1c836d89ee0f5d1b94073ebe88f45b745a9c7a5" +checksum = "9d1d6a38935ee64551a7c8da4cc759fdcaba1d951ec56336737c0459ed5a05d2" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1990b107c505d3bb0e9fe7ee9a4180912c924c12da1ebed68230393789387858" +checksum = "ba73c410c2d52e28fc4b49aab955a1c2f58580ff37a3b0641e23bccd6049e4b5" dependencies = [ "cranelift-codegen", "log", @@ -535,15 +542,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d398114545d4de2b152c28b1428c840e55764a6b58eea2a0e5c661d9a382a" +checksum = "61acaa7646020e0444bb3a22d212a5bae0e3b3969b18e1276a037ccd6493a8fd" [[package]] name = "cranelift-native" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c769285ed99f5791ca04d9716b3ca3508ec4e7b959759409fddf51ad0481f51" +checksum = "543f52ef487498253ebe5df321373c5c314da74ada0e92f13451b6f887194f87" dependencies = [ "cranelift-codegen", "libc", @@ -552,9 +559,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.94.1" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cbcdec1d7b678919910d213b9e98d5d4c65eeb2153ac042535b00931f093d3" +checksum = "788c27f41f31a50a9a3546b91253ad9495cd54df0d6533b3f3dcb4fb7a988f69" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -562,7 +569,7 @@ dependencies = [ "itertools", "log", "smallvec", - "wasmparser 0.100.0", + "wasmparser 0.110.0", "wasmtime-types", ] @@ -605,7 +612,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", + "memoffset", "scopeguard", ] @@ -663,6 +670,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid 1.4.1", +] + [[package]] name = "dialoguer" version = "0.10.4" @@ -877,7 +893,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -893,9 +909,9 @@ dependencies = [ [[package]] name = "file-per-thread-logger" -version = "0.1.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +checksum = "8a3cc21c33af89af0930c8cae4ade5e6fdc17b5d2c97b3d2e2edb67a1cf683f3" dependencies = [ "env_logger", "log", @@ -933,24 +949,27 @@ dependencies = [ [[package]] name = "fs-set-times" -version = "0.18.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "857cf27edcb26c2a36d84b2954019573d335bb289876113aceacacdca47a4fd4" +checksum = "dd738b84894214045e8414eaded76359b4a5773f0a0a56b16575110739cdcf39" dependencies = [ - "io-lifetimes 1.0.11", - "rustix 0.36.15", - "windows-sys 0.45.0", + "io-lifetimes 2.0.2", + "rustix 0.38.4", + "windows-sys 0.48.0", ] [[package]] -name = "fs-set-times" -version = "0.19.2" +name = "futures" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d167b646a876ba8fda6b50ac645cfd96242553cbaf0ca4fccaa39afcbf0801f" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ - "io-lifetimes 1.0.11", - "rustix 0.38.4", - "windows-sys 0.48.0", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] @@ -960,6 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1006,6 +1026,7 @@ dependencies = [ "futures-core", "futures-io", "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -1022,6 +1043,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "fxprof-processed-profile" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" +dependencies = [ + "bitflags 2.3.3", + "debugid", + "fxhash", + "serde", + "serde_json", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1145,24 +1179,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "host" -version = "0.0.0" -source = "git+https://github.com/bytecodealliance/preview2-prototyping?rev=083879c#083879cb955d7cc719eb7fa1b59c6096fcc97bbf" -dependencies = [ - "anyhow", - "async-trait", - "cap-rand", - "cap-std", - "clap", - "thiserror", - "tokio", - "tracing", - "wasi-cap-std-sync 0.0.0", - "wasi-common 0.0.0", - "wasmtime", -] - [[package]] name = "http" version = "0.2.9" @@ -1313,7 +1329,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -1324,6 +1339,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -1356,11 +1372,11 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.17.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde93d48f0d9277f977a333eca8313695ddd5301dc96f7e02aeddcb0dd99096f" +checksum = "9d3c230ee517ee76b1cc593b52939ff68deda3fae9e41eca426c6b4993df51c4" dependencies = [ - "io-lifetimes 1.0.11", + "io-lifetimes 2.0.2", "windows-sys 0.48.0", ] @@ -1493,12 +1509,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1572,15 +1582,6 @@ dependencies = [ "rustix 0.37.23", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.0" @@ -1686,6 +1687,9 @@ dependencies = [ "tempfile", "text_io", "walkdir", + "wasm-encoder 0.32.0", + "wit-component", + "wit-parser 0.11.0", ] [[package]] @@ -1698,7 +1702,6 @@ dependencies = [ "diesel", "diesel_migrations", "faux", - "host", "jsonwebtoken", "lazy_static", "nanoid", @@ -1712,7 +1715,6 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "wasi-cap-std-sync 0.0.0", "wasmtime", "wasmtime-wasi", "wit-component", @@ -1796,9 +1798,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.4" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "crc32fast", "hashbrown 0.13.2", @@ -1806,15 +1808,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.18.0" @@ -1985,9 +1978,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.8.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ "bitflags 1.3.2", "memchr", @@ -2097,12 +2090,13 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.6.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +checksum = "5b4dcbd3a2ae7fb94b5813fa0e957c6ab51bf5d0a8ee1b69e0c2d0f1e6eb8485" dependencies = [ - "fxhash", + "hashbrown 0.13.2", "log", + "rustc-hash", "slice-group-by", "smallvec", ] @@ -2229,18 +2223,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustix" -version = "0.36.15" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes 1.0.11", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" @@ -2251,10 +2237,8 @@ dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes 1.0.11", - "itoa", "libc", "linux-raw-sys 0.3.8", - "once_cell", "windows-sys 0.48.0", ] @@ -2266,8 +2250,10 @@ checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", "errno", + "itoa", "libc", "linux-raw-sys 0.4.3", + "once_cell", "windows-sys 0.48.0", ] @@ -2559,12 +2545,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "spdx" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2607,9 +2608,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "system-interface" -version = "0.25.9" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10081a99cbecbc363d381b9503563785f0b02735fccbb0d4c1a2cb3d39f7e7fe" +checksum = "27ce32341b2c0b70c144bbf35627fdc1ef18c76ced5e5e7b3ee8b5ba6b2ab6a0" dependencies = [ "bitflags 2.3.3", "cap-fs-ext", @@ -2618,7 +2619,7 @@ dependencies = [ "io-lifetimes 2.0.2", "rustix 0.38.4", "windows-sys 0.48.0", - "winx 0.36.1", + "winx", ] [[package]] @@ -3045,6 +3046,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + [[package]] name = "valuable" version = "0.1.0" @@ -3090,33 +3097,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-cap-std-sync" -version = "0.0.0" -source = "git+https://github.com/bytecodealliance/preview2-prototyping?rev=083879c#083879cb955d7cc719eb7fa1b59c6096fcc97bbf" -dependencies = [ - "anyhow", - "async-trait", - "cap-fs-ext", - "cap-rand", - "cap-std", - "cap-time-ext", - "fs-set-times 0.18.1", - "io-extras", - "io-lifetimes 1.0.11", - "ipnet", - "is-terminal", - "once_cell", - "rustix 0.36.15", - "system-interface", - "tracing", - "wasi-common 0.0.0", - "windows-sys 0.45.0", -] - -[[package]] -name = "wasi-cap-std-sync" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ef833092c0215e44f601591593cb3bf0853d5cd1e3104d698808dc525f2852" +checksum = "9cef338a20bd9e5e469a37b192b2a954c4dde83ea896c8eaf45df8c84cdf7be5" dependencies = [ "anyhow", "async-trait", @@ -3124,56 +3107,36 @@ dependencies = [ "cap-rand", "cap-std", "cap-time-ext", - "fs-set-times 0.18.1", + "fs-set-times", "io-extras", - "io-lifetimes 1.0.11", + "io-lifetimes 2.0.2", "is-terminal", "once_cell", - "rustix 0.36.15", - "system-interface", - "tracing", - "wasi-common 7.0.1", - "windows-sys 0.45.0", -] - -[[package]] -name = "wasi-common" -version = "0.0.0" -source = "git+https://github.com/bytecodealliance/preview2-prototyping?rev=083879c#083879cb955d7cc719eb7fa1b59c6096fcc97bbf" -dependencies = [ - "anyhow", - "async-trait", - "bitflags 1.3.2", - "cap-fs-ext", - "cap-rand", - "cap-std", - "io-extras", - "ipnet", - "rustix 0.36.15", + "rustix 0.38.4", "system-interface", - "thiserror", "tracing", - "windows-sys 0.45.0", + "wasi-common", + "windows-sys 0.48.0", ] [[package]] name = "wasi-common" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474a216b3461220699d5e192ceac8fbc5b489af020760803b5a9d1e030dc8b0f" +checksum = "bb9c753bdf98fdc592fc729bda2248996f5dd1be71f4e01bf8c08225acb7b6bb" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags 2.3.3", "cap-rand", "cap-std", "io-extras", "log", - "rustix 0.36.15", + "rustix 0.38.4", "thiserror", "tracing", "wasmtime", "wiggle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3244,69 +3207,52 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3e4bc09095436c8e7584d86d33e6c3ee67045af8fb262cbb9cc321de553428" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.25.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" +checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" dependencies = [ "leb128", ] [[package]] name = "wasm-encoder" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f8e9778e04cbf44f58acc301372577375a666b966c50b03ef46144f80436a8" +checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" dependencies = [ "leb128", ] [[package]] name = "wasm-metadata" -version = "0.3.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6956efd8a1a2c48a707e9a1b2da729834a0f8e4c58117493b0d9d089cee468" +checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471" dependencies = [ "anyhow", - "indexmap 1.9.3", + "indexmap 2.0.0", "serde", - "wasm-encoder 0.25.0", - "wasmparser 0.102.0", -] - -[[package]] -name = "wasmparser" -version = "0.100.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b20236ab624147dfbb62cf12a19aaf66af0e41b8398838b66e997d07d269d4" -dependencies = [ - "indexmap 1.9.3", - "url", + "serde_json", + "spdx", + "wasm-encoder 0.32.0", + "wasmparser 0.112.0", ] [[package]] name = "wasmparser" -version = "0.102.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +checksum = "1dfcdb72d96f01e6c85b6bf20102e7423bdbaad5c337301bab2bbf253d26413c" dependencies = [ - "indexmap 1.9.3", - "url", + "indexmap 2.0.0", + "semver", ] [[package]] name = "wasmparser" -version = "0.108.0" +version = "0.112.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c956109dcb41436a39391139d9b6e2d0a5e0b158e1293ef352ec977e5e36c5" +checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf" dependencies = [ "indexmap 2.0.0", "semver", @@ -3314,36 +3260,40 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.60" +version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76cb909fe3d9b0de58cee1f4072247e680ff5cc1558ccad2790a9de14a23993" +checksum = "34ddf5892036cd4b780d505eff1194a0cbc10ed896097656fdcea3744b5e7c2f" dependencies = [ "anyhow", - "wasmparser 0.108.0", + "wasmparser 0.112.0", ] [[package]] name = "wasmtime" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ac4b4bee3bcf3750911c7104cf50f12c6b1055cc491254c508294b019fd79" +checksum = "e38ee12eaafb34198cce001e2ea0a83d3884db5cf8e3af08864f108a2fb57c85" dependencies = [ "anyhow", "async-trait", "bincode", + "bumpalo", "cfg-if", "encoding_rs", - "indexmap 1.9.3", + "fxprof-processed-profile", + "indexmap 2.0.0", "libc", "log", - "object 0.30.4", + "object", "once_cell", "paste", "psm", "rayon", "serde", + "serde_json", "target-lexicon", - "wasmparser 0.100.0", + "wasm-encoder 0.31.1", + "wasmparser 0.110.0", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -3352,24 +3302,25 @@ dependencies = [ "wasmtime-fiber", "wasmtime-jit", "wasmtime-runtime", + "wasmtime-winch", "wat", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "wasmtime-asm-macros" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06f9859a704f6b807a3e2e3466ab727f3f748134a96712d0d27c48ba32b32992" +checksum = "82313f9dce6f64dd08a7b51bef57411741b7eaef6b4611f77b91b6213a99808b" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66f6967ff6d89a4aa0abe11a145c7a2538f10d9dca6a0718dba6470166c8182" +checksum = "4d22677d863d88d0ee05a07bfe28fdc5525149b6ea5a108f1fa2796fa86d75b8" dependencies = [ "anyhow", "base64 0.21.2", @@ -3377,73 +3328,92 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.36.15", + "rustix 0.38.4", "serde", "sha2", "toml 0.5.11", - "windows-sys 0.45.0", + "windows-sys 0.48.0", "zstd", ] [[package]] name = "wasmtime-component-macro" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f851a08ee7b76f74a51d1fd1ce22b139a40beb1792b4f903279c46b568eb1ec" +checksum = "f2b6da03d55c656066ebc93d27ce54de11fcd2d3157e7490c6196a65aa1e9bc0" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.26", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.9.2", ] [[package]] name = "wasmtime-component-util" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc0e0e733a8d097a137e05d5e7f62376600d32bd89bdc22c002d1826ae5af2e" +checksum = "b54327f9ce6a46c6841c43d93c4fa366cd0beb0f075743b120d31a3d6afe34fd" [[package]] name = "wasmtime-cranelift" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ce3bc589c19cd055cc5210daaf77288563010f45cce40c58b57182b9b5bdd" +checksum = "76d52e14e5453e82708816e992140c59e511bbf7c0868ee654100e2792483f56" dependencies = [ "anyhow", "cranelift-codegen", + "cranelift-control", "cranelift-entity", "cranelift-frontend", "cranelift-native", "cranelift-wasm", "gimli", "log", - "object 0.30.4", + "object", "target-lexicon", "thiserror", - "wasmparser 0.100.0", + "wasmparser 0.110.0", + "wasmtime-cranelift-shared", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-cranelift-shared" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ddb7f34fff5b4a01aa2e55373fceb1b59d5f981abca44afdd63d7dd39689047" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-native", + "gimli", + "object", + "target-lexicon", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a205f0f0ea33bcb56756718a9a9ca1042614237d6258893c519f6fed593325" +checksum = "ad336809866b743410ac86ec0bdc34899d6f1af5d3deed97188e90503ff527d7" dependencies = [ "anyhow", "cranelift-entity", "gimli", - "indexmap 1.9.3", + "indexmap 2.0.0", "log", - "object 0.30.4", + "object", "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.23.0", - "wasmparser 0.100.0", + "wasm-encoder 0.31.1", + "wasmparser 0.110.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -3451,24 +3421,25 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55f4f52b3f26b03e6774f2e6c41c72d4106175c58ddd0b74b4b4a81c1ba702c" +checksum = "fcc69f0a316db37482ebc83669236ea7c943d0b49a1a23f763061c9fc9d07d0b" dependencies = [ "cc", "cfg-if", - "rustix 0.36.15", + "rustix 0.38.4", "wasmtime-asm-macros", - "windows-sys 0.45.0", + "wasmtime-versioned-export-macros", + "windows-sys 0.48.0", ] [[package]] name = "wasmtime-jit" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b111d642a32c858096a57456e503f6b72abdbd04d15b44e12f329c238802f66" +checksum = "2004b30ea1ad9fd288bce54af19ef08281250e1087f0b5ffc6ca06bacd821edb" dependencies = [ - "addr2line 0.19.0", + "addr2line", "anyhow", "bincode", "cfg-if", @@ -3476,100 +3447,151 @@ dependencies = [ "gimli", "ittapi", "log", - "object 0.30.4", + "object", "rustc-demangle", + "rustix 0.38.4", "serde", "target-lexicon", "wasmtime-environ", "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", "wasmtime-runtime", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "wasmtime-jit-debug" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7da0f3ae2e2cefa9d28f3f11bcf7d956433a60ccb34f359cd8c930e2bf1cf5a" +checksum = "54aa8081162b13a96f47ab40f9aa03fc02dad38ee10b1418243ac8517c5af6d3" dependencies = [ - "object 0.30.4", + "object", "once_cell", - "rustix 0.36.15", + "rustix 0.38.4", + "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-jit-icache-coherence" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52aab5839634bd3b158757b52bb689e04815023f2a83b281d657b3a0f061f7a0" +checksum = "2922462d01f5c112bbc4e6eb95ee68447a6031c0b90cc2ad69b890060b3842d9" dependencies = [ "cfg-if", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "wasmtime-runtime" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b738633d1c81b5df6f959757ac529b5c0f69ca917c1cfefac2e114af5c397014" +checksum = "536c34c4abbe22c40f631067b57ca14d719faf3f63ae0d504014a4d15a4b980b" dependencies = [ "anyhow", "cc", "cfg-if", "encoding_rs", - "indexmap 1.9.3", + "indexmap 2.0.0", "libc", "log", "mach", "memfd", - "memoffset 0.8.0", + "memoffset", "paste", "rand", - "rustix 0.36.15", + "rustix 0.38.4", + "sptr", + "wasm-encoder 0.31.1", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", "wasmtime-jit-debug", - "windows-sys 0.45.0", + "wasmtime-versioned-export-macros", + "windows-sys 0.48.0", ] [[package]] name = "wasmtime-types" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc565951214d0707de731561b84457e1200c545437a167f232e150c496295c6e" +checksum = "ec6f1e74eb5ef817043b243eae37cc0e424c256c4069ab2c5afd9f3fe91a12ee" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.100.0", + "wasmparser 0.110.0", +] + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ca36fa6cad8ef885bc27d7d50c8b1cb7da0534251188a824f4953b07875703" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", ] [[package]] name = "wasmtime-wasi" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e94602bafb39e36746156127a97f4e33991fa02179f9f8e5b3372365ec61da8" +checksum = "269f4f2192b18037729b617eadb512e95510f1b0cd8fb4990aef286c9bb3dfb9" dependencies = [ "anyhow", + "async-trait", + "bitflags 2.3.3", + "bytes", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", "libc", - "wasi-cap-std-sync 7.0.1", - "wasi-common 7.0.1", + "once_cell", + "rustix 0.38.4", + "system-interface", + "thiserror", + "tokio", + "tracing", + "wasi-cap-std-sync", + "wasi-common", "wasmtime", "wiggle", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-winch" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d016c3f1d0c8ac905bfda51936cb6dae040e0d8edc75b7a1ef9f21773a19f6" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "object", + "target-lexicon", + "wasmparser 0.110.0", + "wasmtime-cranelift-shared", + "wasmtime-environ", + "winch-codegen", ] [[package]] name = "wasmtime-wit-bindgen" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f2a35ff0a64ae07d4fcfd7c9b745e517be00ddb9991f8e2ad2c913cc11094" +checksum = "bbd55caadebae32cf18541e5077b3f042a171bb9988ea0040d0569f26a63227d" dependencies = [ "anyhow", "heck", - "wit-parser", + "indexmap 2.0.0", + "wit-parser 0.9.2", ] [[package]] @@ -3583,23 +3605,23 @@ dependencies = [ [[package]] name = "wast" -version = "61.0.0" +version = "64.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6b347851b52fd500657d301155c79e8c67595501d179cef87b6f04ebd25ac4" +checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.30.0", + "wasm-encoder 0.32.0", ] [[package]] name = "wat" -version = "1.0.67" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459e764d27c3ab7beba1ebd617cc025c7e76dea6e7c5ce3189989a970aea3491" +checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" dependencies = [ - "wast 61.0.0", + "wast 64.0.0", ] [[package]] @@ -3633,13 +3655,13 @@ dependencies = [ [[package]] name = "wiggle" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6627da83e9cdf851594a1dcf047573e700ecaa7ce79b70e02f3df5e5d24d0096" +checksum = "166189cd49163adc9a1e2a33b33625eb934d06e518c318905c3a5140d9bc1d45" dependencies = [ "anyhow", "async-trait", - "bitflags 1.3.2", + "bitflags 2.3.3", "thiserror", "tracing", "wasmtime", @@ -3648,28 +3670,28 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0857652586aafc82fca56bbbf90fde5d5e086ffba58b0f1c0f113e54c500b55b" +checksum = "a67571bd77bff962190744adb29e72a1157d30e8d34fbb2c1c7b0ad7627be020" dependencies = [ "anyhow", "heck", "proc-macro2", "quote", "shellexpand", - "syn 1.0.109", + "syn 2.0.26", "witx", ] [[package]] name = "wiggle-macro" -version = "7.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97189f1092c8877865aa64467ca99afd0182eb23ad1b4ce22319f18422543d55" +checksum = "5677f7d740bc41f9f6af4a6a719a07fbe1aa8ec66e0ec1ca4d3617f2b27d5361" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.26", "wiggle-generate", ] @@ -3704,6 +3726,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winch-codegen" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e6f2f344ec89998f047d0aa3aec77088eb8e33c91f5efdd191b140fda6fa40" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "wasmparser 0.110.0", + "wasmtime-environ", +] + [[package]] name = "windows" version = "0.48.0" @@ -3863,17 +3901,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "winx" -version = "0.35.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c52a121f0fbf9320d5f2a9a5d82f6cb7557eda5e8b47fc3e7f359ec866ae960" -dependencies = [ - "bitflags 1.3.2", - "io-lifetimes 1.0.11", - "windows-sys 0.48.0", -] - [[package]] name = "winx" version = "0.36.1" @@ -3886,31 +3913,32 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.4.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7cf57f8786216c5652e1228b25203af2ff523808b5e9d3671894eee2bf7264" +checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.3", "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" -version = "0.4.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef177b73007d86c720931d0e2ea7e30eb8c9776e58361717743fc1e83cfacfe5" +checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e" dependencies = [ "anyhow", "wit-component", - "wit-parser", + "wit-parser 0.11.0", ] [[package]] name = "wit-bindgen-rust" -version = "0.4.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdf5b00935b7b52d0e56cae1960f8ac13019a285f5aa762ff6bd7139a5c28a2" +checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae" dependencies = [ + "anyhow", "heck", "wasm-metadata", "wit-bindgen-core", @@ -3920,9 +3948,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-lib" -version = "0.4.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0a8f4b5fb1820b9d232beb122936425f72ec8fe6acb56e5d8782cfe55083da" +checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb" dependencies = [ "heck", "wit-bindgen-core", @@ -3930,46 +3958,65 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.4.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadf1adf12ed25629b06272c16b335ef8c5a240d0ca64ab508a955ac3b46172c" +checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22" dependencies = [ "anyhow", "proc-macro2", - "syn 1.0.109", + "syn 2.0.26", "wit-bindgen-core", "wit-bindgen-rust", + "wit-bindgen-rust-lib", "wit-component", ] [[package]] name = "wit-component" -version = "0.7.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed04310239706efc71cc8b995cb0226089c5b5fd260c3bd800a71486bd3cec97" +checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876" dependencies = [ "anyhow", - "bitflags 1.3.2", - "indexmap 1.9.3", + "bitflags 2.3.3", + "indexmap 2.0.0", "log", - "url", - "wasm-encoder 0.25.0", + "serde", + "serde_json", + "wasm-encoder 0.32.0", "wasm-metadata", - "wasmparser 0.102.0", - "wit-parser", + "wasmparser 0.112.0", + "wit-parser 0.11.0", ] [[package]] name = "wit-parser" -version = "0.6.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f887c3da527a51b321076ebe6a7513026a4757b6d4d144259946552d6fc728b3" +checksum = "541efa2046e544de53a9da1e2f6299e63079840360c9e106f1f8275a97771318" dependencies = [ "anyhow", "id-arena", - "indexmap 1.9.3", + "indexmap 2.0.0", + "log", + "pulldown-cmark", + "semver", + "unicode-xid", + "url", +] + +[[package]] +name = "wit-parser" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.0.0", "log", "pulldown-cmark", + "semver", "unicode-xid", "url", ] diff --git a/Cargo.toml b/Cargo.toml index c6b7ee1..ffc3033 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ members = [ [workspace.dependencies] tempfile = "3.4.0" lazy_static = "1.4.0" -serde = { version = "1.0.154", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.154", features = ["derive"] } +wit-component = "0.14.0" \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a5bb2ee..782c056 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,6 +11,7 @@ clap = { version = "4.1.8", features = ["cargo", "derive"] } env_logger = "0.10.0" log = "0.4.17" serde.workspace = true +wit-component.workspace = true serde_json = "1.0.94" serde_yaml = "0.9.19" text_io = "0.1.12" @@ -24,6 +25,8 @@ common = { path = "../crates/common" } indicatif = "0.17.3" etcetera = "0.8.0" client = { version = "0.1.0", path = "../crates/client" } +wit-parser = "0.11.0" +wasm-encoder = "0.32.0" [dev-dependencies] indoc = "2.0.1" diff --git a/cli/src/build/golang.rs b/cli/src/build/golang.rs index 3d91bfb..788c34e 100644 --- a/cli/src/build/golang.rs +++ b/cli/src/build/golang.rs @@ -1,7 +1,12 @@ use super::BaseAdapter; -use std::{fs, path::Path}; +use anyhow::{Context, Result}; +use std::{borrow::Cow, fs, path::Path}; +use wasm_encoder::{Encode, Section}; +use wit_component::{self, StringEncoding}; +use wit_parser::{PackageId, Resolve, UnresolvedPackage}; const PROGRAM: &str = "tinygo"; +const DEFAULT_WORLD: &str = "handler"; pub struct GolangAdapter { adapter: BaseAdapter, @@ -26,6 +31,35 @@ impl GolangAdapter { &["build", "-target", "wasi", "-o", "./out/handler.wasm"], ); self.adapter.execute(command)?; + let handler_path = out_path.join("handler.wasm"); + let wit_path = work_dir.join("wit").join("handler.wit"); + + let wasm = + fs::read(&handler_path).context(format!("failed to read handler {handler_path:?}"))?; + let wasm = self.embed_component(wasm, &wit_path)?; + fs::write(handler_path, wasm)?; Ok(()) } + + fn embed_component(&self, mut wasm: Vec, wit_path: &Path) -> Result> { + let (resolve, id) = parse_wit(wit_path)?; + let world = resolve.select_world(id, Some(DEFAULT_WORLD))?; + let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?; + let section = wasm_encoder::CustomSection { + name: "component-type".into(), + data: Cow::Borrowed(&encoded), + }; + wasm.push(section.id()); + section.encode(&mut wasm); + Ok(wasm) + } +} + +fn parse_wit(path: &Path) -> Result<(Resolve, PackageId)> { + let mut resolve = Resolve::default(); + let wit_content = + fs::read_to_string(path).context(format!("failed to read wit file {path:?}"))?; + let pkg = UnresolvedPackage::parse(path, &wit_content)?; + let id = resolve.push(pkg)?; + Ok((resolve, id)) } diff --git a/cli/src/info/mod.rs b/cli/src/info/mod.rs index a37de3d..db4aac8 100644 --- a/cli/src/info/mod.rs +++ b/cli/src/info/mod.rs @@ -27,8 +27,8 @@ pub fn show_project( .map(|local_component| { let remote_component = remote_components .iter() - .cloned() - .find(|remote_component| remote_component.name == local_component.name); + .find(|remote_component| remote_component.name == local_component.name) + .cloned(); ComponentInformation::new(local_component, remote_component) }) diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index e577af2..81ea6c9 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -64,8 +64,8 @@ impl Manifest { pub fn get(&self, name: &str) -> Option { self.functions .iter() - .cloned() .find(|component| component.name == name) + .cloned() } pub fn delete(&mut self, name: &str) -> anyhow::Result<()> { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8fb037a..fa3ed8d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2023-03-01" +channel = "nightly-2023-08-31" targets = [ "wasm32-unknown-unknown", "wasm32-wasi" ] components = [ "clippy" ] \ No newline at end of file diff --git a/server/Cargo.toml b/server/Cargo.toml index 0c87de6..5ff8d71 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,12 +11,10 @@ serde.workspace = true anyhow = "1.0.69" tokio = { version = "1.26", features = ["full"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -wasmtime = { version = "7.0.0", features = ["component-model"]} -wasmtime-wasi = "7.0.0" -host = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "083879c" } -wasi-cap-std-sync = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "083879c" } +wasmtime = { version = "12.0.0", features = ["component-model"]} +wasmtime-wasi = "12.0.0" tracing = "0.1.37" -wit-component = "0.7.4" +wit-component.workspace = true axum = { version = "0.6.12", features = ["json", "headers"] } tower-http = { version = "0.4.0", features = ["trace"] } common = { path = "../crates/common" } diff --git a/server/src/bindgen.rs b/server/src/bindgen.rs index dbea86a..bf6da1f 100644 --- a/server/src/bindgen.rs +++ b/server/src/bindgen.rs @@ -2,9 +2,13 @@ use std::fs; use wasmtime::component::bindgen; use wit_component::ComponentEncoder; -const ADAPTER_PATH: &str = "../wit/wasi_snapshot_preview1.wasm"; +const ADAPTER_PATH: &str = "../wit/wasi_snapshot_preview1.reactor.wasm"; const ADAPTER_NAME: &str = "wasi_snapshot_preview1"; +lazy_static::lazy_static! { + static ref ADAPTER: Vec = fs::read(ADAPTER_PATH).unwrap(); +} + bindgen!({ world: "handler", path: "../wit", @@ -12,7 +16,7 @@ bindgen!({ }); #[allow(clippy::derivable_impls)] -impl Default for Request<'_> { +impl Default for Request { fn default() -> Self { Self { query_params: Default::default(), @@ -21,10 +25,10 @@ impl Default for Request<'_> { } pub fn create_component(wasm_module: &[u8]) -> anyhow::Result> { - let adapter = fs::read(ADAPTER_PATH)?; let component = ComponentEncoder::default() .module(wasm_module)? - .adapter(ADAPTER_NAME, &adapter)? + .adapter(ADAPTER_NAME, &ADAPTER)? + .validate(true) .encode()?; Ok(component) } diff --git a/server/src/controller/execute.rs b/server/src/controller/execute.rs index 26fbb73..767ed9b 100644 --- a/server/src/controller/execute.rs +++ b/server/src/controller/execute.rs @@ -21,14 +21,14 @@ async fn execute( State(wasmstore): State, ) -> Result { let function = wasmstore.read(&function)?; - let mut query_list: Vec<(&str, &str)> = Vec::new(); - for (key, value) in query_map.iter() { + let mut query_list: Vec<(String, String)> = Vec::new(); + for (key, value) in query_map.into_iter() { query_list.push((key, value)); } let result = query_list; let request = bindgen::Request { - query_params: &result[..], + query_params: result, }; let response = executor::execute(function, request).await?; diff --git a/server/src/executor.rs b/server/src/executor.rs index 3a9ff47..a6530a2 100644 --- a/server/src/executor.rs +++ b/server/src/executor.rs @@ -1,47 +1,62 @@ -use host::{self, WasiCtx}; -use wasi_cap_std_sync::WasiCtxBuilder; +use crate::bindgen; use wasmtime::{ component::{Component, Linker}, Config, Engine, Store, WasmBacktraceDetails, }; +use wasmtime_wasi::preview2::{self, Table, WasiCtx, WasiCtxBuilder, WasiView}; -use crate::bindgen; +lazy_static::lazy_static! { + static ref ENGINE: Engine = { + let mut config = Config::new(); + config.wasm_backtrace_details(WasmBacktraceDetails::Enable); + config.wasm_component_model(true); + config.async_support(true); + + Engine::new(&config).unwrap() + }; +} + +struct CommandCtx { + table: Table, + wasi: WasiCtx, +} + +impl WasiView for CommandCtx { + fn table(&self) -> &Table { + &self.table + } + fn table_mut(&mut self) -> &mut Table { + &mut self.table + } + fn ctx(&self) -> &WasiCtx { + &self.wasi + } + fn ctx_mut(&mut self) -> &mut WasiCtx { + &mut self.wasi + } +} pub async fn execute( wasm: Vec, - request: bindgen::Request<'_>, + request: bindgen::Request, ) -> anyhow::Result { - let config = create_config(); - let engine = Engine::new(&config)?; - let component = Component::from_binary(&engine, &wasm)?; + let component = Component::from_binary(&ENGINE, &wasm)?; - let mut linker = Linker::new(&engine); - host::command::add_to_linker(&mut linker, |ctx: &mut WasiCtx| ctx)?; - let linker = linker; + let mut linker = Linker::new(&ENGINE); + let mut table = Table::new(); + let wasi = WasiCtxBuilder::new().build(&mut table)?; + preview2::command::add_to_linker(&mut linker)?; - let mut store = Store::new( - &engine, - WasiCtxBuilder::new() - .inherit_stdin() - .inherit_stdout() - .build(), - ); + let linker = linker; + let mut store = Store::new(&ENGINE, CommandCtx { table, wasi }); let (bindings, _) = bindgen::Handler::instantiate_async(&mut store, &component, &linker).await?; - let response = bindings.call_handle(&mut store, request).await?; + let response = bindings.call_handle(&mut store, &request).await?; Ok(response) } -fn create_config() -> Config { - Config::new() - .wasm_backtrace_details(WasmBacktraceDetails::Enable) - .wasm_component_model(true) - .async_support(true) - .clone() -} - #[cfg(test)] mod tests { use crate::bindgen; @@ -67,7 +82,11 @@ mod tests { let component = bindgen::create_component(&module).expect("Unable to create component from module"); let request = bindgen::Request { - query_params: &[("key1", "value1"), ("key2", "value2"), ("key3", "value3")], + query_params: vec![ + ("key1".to_string(), "value1".to_string()), + ("key2".to_string(), "value2".to_string()), + ("key3".to_string(), "value3".to_string()), + ], }; let response = executor::execute(component, request).await?; assert_eq!(200, response.status); diff --git a/test-components/return-params/Cargo.toml b/test-components/return-params/Cargo.toml index 9aee5ac..58fc6fb 100644 --- a/test-components/return-params/Cargo.toml +++ b/test-components/return-params/Cargo.toml @@ -10,4 +10,4 @@ default-target = "wasm32-wasi" crate-type = ["cdylib"] [dependencies] -wit-bindgen = "0.4.0" \ No newline at end of file +wit-bindgen = "0.11.0" \ No newline at end of file diff --git a/test-components/return-params/src/lib.rs b/test-components/return-params/src/lib.rs index 950356b..8082777 100644 --- a/test-components/return-params/src/lib.rs +++ b/test-components/return-params/src/lib.rs @@ -1,11 +1,14 @@ wit_bindgen::generate!({ world: "handler", - path: "../../wit" + path: "../../wit", + exports: { + world: TestHandler + } }); struct TestHandler; -impl Handler for TestHandler { +impl Guest for TestHandler { fn handle(req: Request) -> Response { let mut response_body = String::default(); for (key, value) in req.query_params { @@ -19,5 +22,3 @@ impl Handler for TestHandler { } } } - -export_handler!(TestHandler); diff --git a/test-components/return-status-code-200/Cargo.toml b/test-components/return-status-code-200/Cargo.toml index 063e60c..eb4ba86 100644 --- a/test-components/return-status-code-200/Cargo.toml +++ b/test-components/return-status-code-200/Cargo.toml @@ -10,4 +10,4 @@ default-target = "wasm32-wasi" crate-type = ["cdylib"] [dependencies] -wit-bindgen = "0.4.0" \ No newline at end of file +wit-bindgen = "0.11.0" \ No newline at end of file diff --git a/test-components/return-status-code-200/src/lib.rs b/test-components/return-status-code-200/src/lib.rs index 501999d..4a7bb73 100644 --- a/test-components/return-status-code-200/src/lib.rs +++ b/test-components/return-status-code-200/src/lib.rs @@ -1,11 +1,14 @@ wit_bindgen::generate!({ world: "handler", - path: "../../wit" + path: "../../wit", + exports: { + world: TestHandler + } }); struct TestHandler; -impl Handler for TestHandler { +impl Guest for TestHandler { fn handle(_: Request) -> Response { Response { status: 200, @@ -13,5 +16,3 @@ impl Handler for TestHandler { } } } - -export_handler!(TestHandler); diff --git a/wit/handler.wit b/wit/handler.wit index 88fa308..2fbe8fd 100644 --- a/wit/handler.wit +++ b/wit/handler.wit @@ -1,4 +1,6 @@ -default world handler { +package noops:handler@0.1.0 + +world handler { record request { query-params: list> diff --git a/wit/wasi_snapshot_preview1.reactor.wasm b/wit/wasi_snapshot_preview1.reactor.wasm new file mode 100644 index 0000000000000000000000000000000000000000..87e85057107bc0c043e569ea72707f48a77e8e37 GIT binary patch literal 105098 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%0A`N!FeI|2odK`3#RZU1?(oF>pf=SBG<6zYI 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 literal 0 HcmV?d00001 diff --git a/wit/wasi_snapshot_preview1.wasm b/wit/wasi_snapshot_preview1.wasm deleted file mode 100755 index c93331e9e4114a6ce469978b57e61576190101d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101916 zcmeIb3xH%ds;_46-V&>$dE!!!V+bGwTa>77-j&9uCT=>oD&3 z|L5F$&y9%8s-io(gbkXkxc8iM&+~u(|2api4>om8)AV;4x4&Ayq`g|xFTUEiga`AI z`1c~7LwKfN3%~e@(hvPldv!!S;3+Jgv{xG!FX@+17yiMc{D@vIUBthkOBXN7I`GSn z7A1*BC?~-cxtSWip{{TR^skAJ@WUU8S5YlhCepbub(tTyhiCwKZfEfr<80~*US!w# zYxIT3j(@4Ecr$(@mNT91x$vg5+3R00G%dQ-cY0e+cQAj-87{RteWy9>^*hc$)8;pO z-QKX*?KGE~8@=XwyzdNp8{5N9udC_t^Yx95@$|8e%x>>|pfo3F@pfm!8C)0)oz26o zes9Zb`NERZ9riCQ4TgQEzNx7NV}lFb=2ELZtZRnyeT+KZ-l(4%1d^kaJiM^w1X5!I zudu1q5vAlo=QWOIDXF3J{m#(QLQ2vcde!nf8>ri&{smI@Hc@=&_~n=CLoKXS7uo1^ z*C{cgB<}5Fan9}XRjluH>zfXJ7gfHC4=!xF^~IF*x$Q0|)0A@uMi(|V*lrA({Z7Nt z;t?_NgF{~0(y+JG+a7LhV}`_{B-}zb1`2h$Y8h**x3Q7pwWX(eW5dxjPE64IQm^au z+HK7Y_v_tOZ!=BHB_5X=7lwepNZo?n}o7R#+%Vq@5JTB&{e&C~(o0R8`lgT{>sBXQusjR)oq9y+l9Q0kCz z;BewVYX40)-*T(H-@a*oN=xiJfQJM74(?wx_Slgy2%lk?rWrQP1BpZ;6HX+|14!J* z|Lxy@+y4DlNZ-21ttUxc=4Qg^s4%H>Iw2V0T8%GWKVm5hPWkY^GhOl z=8wN4E}u|N`%W4lsO0u$!|7`eQlWw4tZOghCnD1?r>CK_v7x}9IUen+ zlKduVyY>TVeibC0CtmwOCa7zYpsvNX`n~2FVW;-$I6wF6M2PB%LG!fJTD#D3Hd@-JaLiEr&;FI!`cPcgFWq(-|I@13^^A2pKN=_9|7lI=ZnAh(GP@6+MTww`X?WM!|VUe2jBfsyR6-Y*ROryo$q_& z```7ecs-2Q&;H>%U;le={!btNQ?0D+rR-1t+YkK27vJ@fKfycImpD_sBmG9|LG>v; z{lnZ9tNN=?X(!xlLu8}u-|su2-GJZE|MCC&_DG4V`^sPZ``3TyTfkWc+DGuSQ=_?EYU7}g}@>B16(@(tRQy>1EoRvTMvrm5D=id5%f8;CvFcz-k zFn&k#hVksr;a~Z4`0O|SU;M@=!{9f^0)F&LsDxn<8h`E2-uXTkjsFmc`Q|_RHCv$Z z=RWbaKYiWj-a}YT0Q;3My!N#}|K<1mFMlop_OpNS?qB@<-}X`n5pp5Asr4&%H#Jh|DO*LJbYJ+T@Au9KG zzhFw@rh~YFl&#ywnT%0IIukJ4C{Y>P^xM_v6L@7>VEW|D43aZ5NY2a=a)#^xIWtvq z_9YjIOqZN-woA@TNzP24oSCaZa>nIda%PHYAwm>@)u>`=WlCO9B*L~JmT^eexsol%4Ze9GV=B@YEV2}`ERG5^4w z-yO)tR2%ek*Zs!7nOV($f3}A4_aQx`kUGO<0wh>VVcH~7*m8ww%{BqM zLYPL5CrlFo9Ki{Lkf9nqll~=QJs_UO5yQ%aNT|jW>a;^ub9iPkY@W1r+d>j*w$5Zi zcuM~mGcMhx7s%JgH6RmFTl}ERBA#WIer6FLqZ!l}z7GmIIFL^~i zC2BHhND#dTpGtl31lm;e!2(_teeh1as`}t+0RS2Tts6vn&jw=N1IGpGiqIc=XScS$qB|&VM zd_|cQ5k;~v!B1&Y@=pBcXN{mT5nT+4?pO>GRE(#5YlP3hu2LFt03n%=e&r}~hOG1D~GN%+hHAt@$RXV6{poZfr9cgbPDxK-7 zbWE5hNFdY+8$vMz4wjao&V1!UKK^{2wqi1&8BRH9vwtQ2Yp5D_Y9BWLj01MzN(c5qF>e)L#&eGyY%ar z4HyvWZR2_7!BuVb@;mI>>acs-AqfBe;K|9LW*rz-3qjnV8yssf+IcP0acO$K(YDbaZ1nel! z@-Rjvov97u-H}nmQyeA+Svo!i4qJVi4Rd?_(AY)2R~4LdC749)}AS2Sa^#qPIlBn&D6FK5v1*hWt!lQG z{%=$!quEh3Y)2sYWjD@n$POgd>TiOlA?e7OB~8mjt2Ym;H*Z(9`%yAlHST4>lJXCd z{0MGEu{nQE^;Q2-KbtYDcc2K8UPml%F_f@lb_@mGLD)1v7i|=W9FZOK?6kdd9#^{_ zUO5kSc8UW6SNkRiqVZ5?+QWF05(DsF;LeD5APq3n$upeB&%rZ`CYX<2{i!U0Wfiey zun`lz*I_`yKjaZe|5w2VIHUTmtnhFhBD>R1WeK!l`KLP5@Et%Kvf&S)O5W?LJFd)i z7ZRnsNm7Wy)mWhPEhAv~0j`$DyUNv~q9<;k*zYc=@}|u6xf%*X*qv76|8e)me@74a z?WKGnc+D~Vc!6^PE`J-baeqE2E<)w<6kMRr2Qf~?QuHoU&c_ZR7u7%cf)}IkXCMFi zkN)OcKK08Kk0+~f9VN_PWST#lZ&dVnQdRT`epm95G8}RI0p2_U46-*$fZ=NeyfGLW z)xQ!UjkYG>@Z)&R5J?7Qg(raEN;qMwza`_#ApF*0m&)n{O81~@&%vdua9pr;b=~g7 ztGaHtQMRHt9Y#fpgUbs)(8+8qGyR!I;VCxmg&*j`2P>O*1r+WQjYkzwXgsQbBENA3 zlB@&8RXz-{e26?L8&Yd;B#K3>c}a`1 z>_}P}^v*D)#4?(Y7d0s_A{KiRITO*9x@UNVq$|ZmrX_dzgzN~RvLCr^6Q%MQ&_TSa zgbboXfRIH|QPej8dEz2d0GT;T$fnh}CoW0~Ae#>mG8&Ic$Y?w&A(P((2-&kd3{PB~ z_AtI4F|fPGwLS*r#fe6D2zV2X?z(Z3{sUf(m^lQpc^E{DHWOkDf*FrgEco*mBLVz@ zW@p%;0R9*R{M>+xLlG5+A`*v=7-TL&V?WF+-vF{KMr`~F#Zi??A!tNOi9uIg{hL|32E9@d8%RKW9yO-r)%AW-^ErC{wM68qLkQV$}MF@i|67F8FK_?vM7J>t%e zXCD%jS@oY_QC)q4mOv=I#xx~+kBaTk>!~*GQL$-H{vQWfr!hgCfBIpR!nU#l;u}HM zNk*L!)YoA5A!~~;t%Oyy50A1=lkgAhh|p568pEp154yllrVKwKSt}C-flJI0y-`gvSmj8iR@MYvO+XP#V4($9m7Df)Tk;+!3kB0~gB zd#-Fkq_$IK6QZXbIj#|3PnJ#M>j^kVAb(!6%npbRRHZ65P?f6uEx!qf4KhtpBS60^S0_0u8EuvbSDo!);{TN+%nHs!c|22)iM{5{33o+Zi)4#@RS+POugc zHnE6Z2TEK-oXmEy0LB{7-E|LR<37e3ARpvAMjL!)Ns5huv`fjbu|)~n%~DXvNmK_K z!y-T=gza~cC*kS)OCjvj6eR7?lC}@~A5a@_txVd{`%>sV?M*|N#sjGs=pT+)sUD`D z)KVsC^fY%gjbug(5t816AJ3~7QjxS^hg2lJ6R)Z~d>ds4ym}$ktLFh&$aSo7wu>hr z^7T=bDxOf4DxMrh-{2^8@r1vTX$rby8lqAj22a4DO+KNL?oIXw(0Ki@D>`e214yPi z2_B`|sq52R0gqBme+8yFjJLa6<98Xx9a!7XeL_E>F$8_?oqzokZ~pi%(J3+#S`za7 zf$OmhKY11XCsh0DQ;&b(KmXPrefX>BHX%lEGh}FFGbAI%jn{ElZx2a>%P!0E4B_`Q zq3a#b)-e8gShw8BJ|)&2yu=i&$q4KP@gn>v48(WfQk1PJ*&9id?jcx{qHF~hAsJK# z`GHgJLqrMbd*pz^4(21qo3p6z366piduBz4z`4v9Q!ESG!1JRB_p2s1=-4byOTPGU z1{?+U8)QZ)Abr(-W0^S%xJ-yQ@wvSWM7}B;Z*9{Jkeb%^V~_f>Cqr%^Rv3pN0L!xn zov;J;pr^NO+oQhHXe^s|`5$-bPBa+ge?h%ae&h1L>oE+EjZb?RKQ$6y6$nX}q8{Ud z7jX{N1!P}{dMYlEh&bYkrKkr;R0=Kt^a&S4j5px|qz2$ZIEV`+a=p5^K8(g&d#F2v!Bn zYy`o`bxG=)w=p*|JU!we z8lXk>HJxcqgGWtv>^yYMX*KRqlWDKP$3~19(r78LLj89XA45ChqZ!1<@H2&vF#P%W z$c%Grdq)r-vAM$-+1x?Y;vmcqs3>Ek|Az4lF@OgN&nROgP4_GsXzwN%Qb0PAz-I*S zkh#KuVS@vfLuA-s+bDqi$@FE4FhUO5G{9{wvT7I{l3%?U2e^{i0`<^QQM@%JjUc6v zFmAYnVsccWjf1J|5J-U?eew}F8)4Q(AIKa6y2IuWlPoS|4q-CEX2ZMDF-az1HC4$( zmW-0V&5}%*Dw!agpGqcR^TQO4lL={Wli4nrU=tu!sgen*QY91e8<$M*H!@8j6Ih@_ zi=YmHjWj{(U@3BjM;@j+e2+Zr)M*knrZx8-d6@p(PjeWaA!L_fWB_$+2w~V!3?ULu zTtmq9SO(7!vP=I7$y7rKp@eD(p<$_~LdjvdhLGzqtS7?K5VFg#o*~ShCUm{+*&4=A zrpy$af8I%M??o`qm^`cUQukSMMl$ar(#4Btr_fZP1R}?iRCN&hu}zN}lHgK6SZkHH z*hEkjHacpEXh9q(#`;fVM>5+_yJUtnM|Aq#N5a@%5^QB6Y{DUO0d^e&+rlSRLgd%<h$Lj!0Lhr9lsBwPY`NTD#+Rc+)yHvV0AN0je}MB8wkFbGMul4IrP*=LeH?*q zASVau4`$N2^~}7@=wF23I&Vjjwy;i!4Y`=>vFC(&=nO6_SrCu*0y#cWn2zm^R$pr% zBml!XVqpItPzbxcj~EY^=#3IV%MgVJuV;xKX%MG*Uty9kaNgZ37$)BzHX2Im#i&VR z30MCR#Eiar9H9dA{uzF+B6>^?H(dRKNP3r-v=5z)rk^0u{ScIHXb$V3pA_aV@S9K_ z&*-Mx{@&VRoP~L}a3D8rr$P86A&2l<>tjzqijF}fiXv)L%Yeott2%9$jN>omHo{-h zu_#{3Jl;?X!kMLKP;3qgRo^=j9GCc1%%aE?)87?BaVat~9E+u6HZta_{z%5cJh@zn zeleH&cc+kKn?H|}W;96OnvFuRZmja0YSr6uXc93P#B`sjntV7esRuGGeTL81KrR9? zp*8Jeo?64d{=&1rinFT3Vqgt)4UHjC z(xn?ZcSrDe&DKxoQG^uHo)lUp3ML>=2Toa_9~-EL%G20OiyCfscfg>@kR2h28=n&Q zoMS8rd+m8nX~znh#aw!mvkH!AM;}B`s%VpV+)FV;$>Scd4VA~e5U&c4Blm>LOhl+_3_Y- zXD7q)C@$fFGlTZ8o8tU@a(B4y#mQ%d6M5lqcd`E*sI!lbtO849_ag8_Pyan5twB4` zbsIm?MPQF`2?q9+$cUY)?*IY0vo&uAx`2;3`_iiwJ|XMzRqzNaXl1OsNyL;ySdF$z&nPT zuwp=!igG8%RViuykl8MqaV;_dHbYgaY({?LvKjtHrYTs6xp3pWVxBZ7*Oep*_x=E@ z`WY`EHRd`$raA|n9=21@3Gt@20G=K;?FIN2JZ<1D(tt5LZQvS@I`DvQ+S~M@nK15n zN6&x=28q|xIkCZW-cEZMKdLaT;8_D6*COfb>D}7$w6`0BV?{iUHEF-Gj$B(>1%y&P zNtP2Dw&A;=Bd%I-3^It+bs)8>>v$Vp6($53EWm{JQnsS&Bv6rZ^pi(`S%8z73hOWk zh^E!JhYM2jkq#}f+;(bfc^kklPgZu}z?(;3C5 zz7I0t=*9r})Zv~0()ckhVTe!Cr=I>AD@U$JowTBx$hMb$Et%Ee{DzwyeMS`!{-Q8m zN?|e;Vl>R?Y?89$`uM?op7b&n-5uiNGjm4uJrH;7$oOKtSx2Nc?(`gPFGjb}utxdK`>3rEf4ila&gI zS^H-)x@01RI<^=!P6l)?+!G9AH88=)Ee#$siYOB**Ti#ju&h8STZa*vO&4#K z5}--pW6+!yqc8^2kByXAMhl)d72Q30(>tjp>Z%>4kY2nXN;>N{pn3%kJj6-20j`(E zyUO*@DbOEJ_3~hrLmy?P0<%+X+=E#e`Y4-sxgK|k#-o}SX*?>|li#>p?|KZwBWu$h z#(x+)fgxZ(C~=rYc%Q*Y0(dkr4zuKQo6Mvz%#d;d z1w?fHEa6l7OU%#U&AmSZ#~5!*Bfrx4Q53?5Jn1Nk5P3lG&E@f>qOk0=*#C;nXD~w8^W-Lhe;f=H zCW%XS3V-<4rhnv76mxLYrNmJbR3a%#a1%JPpN^tP*(p0Ij-sFv2~h%OC+{eVq@A=A z?okwRFC_Is{mc7prVRT7d)7jo>`ggq9!|w zsY(?!sY(?! z)>PVth#Nn;1~19Q6lfdnc??iD#MfxR;87`otfWFpP~vLfEGNS(47)-~SfsoWMFCh~-5#bz z?U;l!jF(fT{2zVNP&6$d9p<6M<(Y1s~;NigZ&|mqN@2CEF-*BUS(x9N=&O8NZYw=FgaP zH-01(eF+K+d3s*R6Quc^)XA1m9y$Z!@LQz7D51Ry|M z`0(*IhU)QgRF82Sn>aO&Q)=4rOxNd(s`DM?{$xvMVxS1HPc^VY>Yt=1tE!%{zE6( zQs_+#eGy?*pcH3-BXqOK-LMP-J3(m(X~<59Cw7~HJd)d#dkKUlCF*afZde|1{6__o zicC=fMP(uF_~IO)b4=*?F&CYW*fABIZM-Vzd=RfHI;SaHv20Th6dm7ltH%Uml-Vvi z$B0)`l`1+@l`1;RZ(MZdZ)BQ^&P&>gq*8Mul1X5rSxmw0Iae~7>Ku6C89ViSnhW5C zXH0tmesRIy;z_t2_HkPBll{aS!G;z2c1 z^etk&FWZFNSF947%EEqNL4v9e6Fl96AIYCj*kKi+3wTu!`cAy62z?u6D+qlU6)Cq} zc>Fo+o{PiTE<%UJxj5kfLQ|D0Ldz;$gywH#nu5@QVNv|+G>7H!-<>)$&Bi_cJN-GG z<}keA#$ASS2bS@3;-nkEgc95+*WV3pydKNo1vl=}e?lHLxRJ0&4Q?b*SA!enu-xFr z>oKg^vXpVI(l@j$P#72400h$ro#P0Z(Ru)KTF6caIV?c})Km%6{}q>E(7OO70ih~D zeS`FNnzZ5fpS58~MNS0PNt3RK=m;y4UxFpSZ&k106H@Sp4{+uc3`jRua7lWcc-rnX zctRnh-Gv`1q#YsOqVGYm$#v=*QLfRm>IrGn9>#kjdc?KkAueo8=D#*+AA;|Qq>sZe zk$A0=K2D1bNcz?!DWA^4g_Lh1kA3R!$q5|-`Zauk;?B@G+2g3XxMGi+Tpeo@T%`s3 z9TWh!VmRn1f*XT)n?y^JY}r|#K-dekd9V=AS&z!47Mmc$9P^gD@Z*6=K$f>b`2%1Q zl;zWut;q7Ub68Q%gF>&&R3&f$qtj{JLp90RWb*+<;@}8lqw%PUg#0F;NIc8K@K9~q z!yx$b%#;(kn-{(zncK&LAul0SAy@rbSKf>cuo**43De3cvx=kM5{`P~Byh=)xC9Ft z*|zCqZVGv!a5il4#IUfdRVv|%tDfvyKu_90CqiB*=mj@eiLDJkE-?uXT!Be(5%vVSI~*jlF#)AbQs& zTvN=Nhd*acvVR$%)bFrT0uohn9|=Qf(PfwY?qXBATCv@l2WQtC8hEkmz451GR@}*{``2} zadvVkTLfd^&@2;>w?{Fc6Bixh+^}CD5_}zems~Yu1IJ`%H!{gHaFx*(3fy6V->`X- z=22}OB_n4tbNFgW)8=r9fDVy_PIx({BMRg!0@&tYx&~v%DFPHdg^LE-^@FI{ha}Q+pJo)J8rB>A!AiNi!+KAtyGv#nl zPG$k^@>L=@P{-bL*q&d1@{tVcdMJgMUtE=;PND1}j@FR#3KJL9l`u(LA$yM84k27( zfYSsBsBkK55;_KqP5GO9*mkQxis4!${))yn_0=Y?L7jR|m z85DQBKz4&L{tT4Tst+Pd5Ni5}k=0I7Xj0q`yNH~b11dv3BCRX23Ic_%iOvTpJXu>% zCX<{qqq>1pd!~vdf12`mC;|b-I3F5^M^#}UNelt=hJ#Z$k_z+1DkeEhX{LnH2IPz6 z0DOBwKVAhhW4-~!lcOk0#@#e<49pIl&_$E!uhYV0;JO7bxevE2={!4hqi+ab2@mmn z^&<2%#8~PN;8+L!?3@`g4c@kdbyoVgPxKMzd_Gfsq)`e068+>zB|1Dv_7lgS$v$dT z9JNGIT~XRTHr1v3e|BFw>I0@Oe6|X>PI~*FrX`Ghbobs&cPjqH6&pt|tu!-=gAr&w zvCV)s89>}17XC`Ga7}DZ&Fp;?tQ*YxyALBMcfsEKO1@h<#1gF^vCd#^GKFy$T)jMo z!VI!!7BxFU(#Qg=CnQmWeAz$?7}jVj0qkjZ^0)>fLAU_R8ErElX%KARut6`9xV31y zbN9uhM*)8IDeZ(y1D<&)db-2#e8;=6DpEI8-!Ul%s_z&u5%3*La_IVwuLlSAe8;;C zYZWugE`931>e83}cU}5B^k0Ty$~%rMWv4|H+pecd!ai|Dyq%(e_?OQX;J?iXTxYBc zGcbsac3=9dQo6x7k0MxDs>#m}o>7p&tD+_!!mFw#@1bnP$q+|HaX+B<>+U4WGYVw3 zi{N_oXZD@o#y|g8Kl{E&Nu$Q!`|+QB-=BTyM}E~VYt;DXKmG6D^qU|2<6r(Vt`?%! z|K_Rp|K%Is`l%0p3GdYU7yi>H-}l4sfA_EbIo@UU&yiOPp({k+xJSN5J#xddJ0*Np z!!dsz?nK>+x!^XuD)Vp{uj)MPrEFy$uzg$IlgxGZsx8!i(eAr|-Z&_Rp1-w~m0CW)|o z`kVeFxB^^Q?`D9Z4d)PtO(DAKwnn0+A?()P2?4W*OAa>b#^!y2LN zret;|(WYY{SQ6mrZk_;T4@6twW}Xqs9^A)HCp##ow|v;8O}qhIX8Hg>(j^%q0Y#p> z7?#G4-Fec~4JEim6SL%T zVd%BEWiI_@KI{bCyBe)tzHFuc8F5Hluxg1M2nAXh>{J(LQPLI-=;vf(rRzp04^Q-i zgKtF+oq^0ne(mdcpsWwk1D&<|{w#j!xMTSppM>i^e_o_(RP)z<`)A&S@-Y45<%6Hr zPUwH<)=Ebx!w_;tuDK3&pb1da{B_rYpr{4cT^m_h={Kj>Y@HaBehoD#ZVs6@ZF<~}nZ$BAqpiDr5c`vPxaZ+gD z6g+UpqR9p#1P(0HWi60-T&j_HQpW~m{i1pqrui9w>Ag-TfJ_yE2&X4snDc z-ePoqB8h13QYJ1=oC2NzY%%;FysBV(pj}olJYcs39a@YhG=|P(coPvOjUbd#lVlC# z9Z5-=B^a2FYe~vk(k$#!Qs*o2S>11+j<$4>7#&Ez&ubiq5{t%->K|&S+0%UvIQ@Gy+c&jg}6`;2aul2u44Cq0xeNix(PgNF6O5R>8$+v=q6)FIr%&4!whxKG*Qkpj z>LPJV@S&nTL_pzR+AN&@Ij@x{TFFFs?O<@lmU(UsD7vJf&yUL_gl>3*|9>X`5c2OsqNB_~O z-~^I9)v$FNpnvdNYOA<-Ktt2GoPeF@6qF^tLLGc{8`&xau_SZ?7%f_>I?+r%r;IS* zc3x1rqtzyOqU$2Dhv~R>6V+S7#wb8w$=D~}2o=RC^K|C{AkWuQ5^?k-{XfwF5$>S*khz>Mtow$r?i;>!-_YqG z{HfXm>|IR)=sSnLrT<6nF1nI+0qu1}g}eo&>8I-kXia(EGlqZL3QoqNKsvg{4CT@& z!b_L_X{s~>A%SjMljNS>m@ZIJVhrSE(j}!hp<{C7k-aB$E%AP}4-&NS85UxPo&Z4R zEW^}If;KeDD@835=Obqg*H3SOj{bA%D6TjI#!-6uUx;^F8p*8WAd{*}B-=&y;Y-L}2so#j|B-4wgy#xwbt4{1hL_tZc!hTknpig+p2^F=UnAg~ z-p~LRovWy3xPFF(aI%ai!m)Tn?@Fv5+IVPGvVVoakLhgcXf4k9gx zZjooE$;r~I@P_E{g))tjz<{fy#T5u<^(l|%zZ+2N->RWNkq`BcWeNMp;fH0U&g+;_ z(Rq;IwCFiqErdbp)yK0$Cvm+We<4N+_>Wg{sRNA2KK;vN?*$;Oe(@83{@Ty})~7%GdAtMPR{#8uf8!s1>C2z~$Y=2` z&uA-1d+-itw9$o-1W}-hJUKhcNY*Fj&a5pV1NR3fhMSe&>m~aP$c3D z!Xi@I1gWTo`&^A*14B*`00f#qIG8;np;4!6X%HO*2mHLr9E_`2ywIx3Rq)WTVM}%Qz5e7lW zYSFRx+5^n2!n`09LFR!o^%qk}2f<`wyd(hZlJPiAhL{vt3wnU=;KDBS5dtLoM(j^a zJvMn*NTmoms_l8yGaRr+WHuIm(1kDM8Bi2Wg6w3yTu}R>ym)%E<9W;lpPfa|%I{3iY#Lwxzn5=p^Y@%jZBnf^t znI;Tjn(!>uciI6<_0v0c`D2=tzb4y%J{0Vy?hBp>cSKhqe_D$K8D5WR=B6qzdOe=( zM4I*Z-5<4Sx9`(&)LmhY`#$jcmLQ-oNktzdzHFQ%Hoc@Vh_GZtGB#1xNfs7!vW11X zjHgz<+8{Mg#!sVBNrgqDVEq5eYnBvCNMX>*M9>qOBsDF(3KGJkf$!C_LnO%ghOWf_ ztf*M&br>%pzR@tAng1YmK)s&3L%Ur!Oe++QL}T$pGBr1!Uf8pD-~IzP9K7++;hS#0 zs9l+m>_&bEZ!}z-i ze>darR{Yuc%i!-e{M~`SJMniH{*K}=i@yT?O86_|uY$kh_&b5WyYcq|{N01Ur-0Md z|C9dtP5S2(^v}P8Is(#^_&t4+W(sDlOr9Kk)G|%e(sWX(B77@!)e_e>PnBN$oB1nf zm^2WDfEkGq5+Y?8Kr|#}z$E)DrsGCgkzJkaMF9e_ng@U&DTw44DW$L;Dv@IXJK-Kr zhkv*|p3FW1tSpfakPSohJ^dD;QW7Q$EXGG3+A9Wadp)DO><4Q>qy?*XT$`6Q=+$|O zO{D9|MLnz~r5~s2nI*ZC^#eY{s&(2#H2|vdE-p&vuh1o8#_^5yYPP>iLxI|(+HGhN zO_KTMcJYX`*ZFDU5oxdY)5PN(KH+)zaqhJD`gVTp>fwvXH&^Mzp!wI6>f}C#FG7o^ zcl+^7^kOE4LU=+<8?M$1t=eyVBoh{ft+*Y%h=Q?)oYxp48_MI+v0g!gu+JqB}mE!1t#rY zhR&NLtKv>e=y=dGVHmTm^-QGt$k_~bUYOhhq&Y-`vT`T>V3Qv^R|$wb4D9kFqgszp%pqRt=E zHd2lMs<){;^#tg_5}a1v99Yj-2qDL zi(w-HKcuK)ZUJX3-cmE}TwAo4!=GzY%(W@z8cW4<4Iw)?*FyVO=i2n=TB>5Uc`b8| zJ>rw++MG1k)-}yFl^&gIA>}`EmLj{sp8*n4T(ydsiPF$%@(_T|YhY0%IuuAb10hZa zILKN77j_iq>k$xz*2zm{=9HZ6QW2tn-ZRdhr%cnD?{o)FbGz@zZ#CUA!=jo)TlL}Tnq_YEPN7#OWe$29&ds7>q@L<@PaQjrL2WpF_cx@|*e_Vot=`5)y|Lkl z7IbTPx--yGU8>dZZ7q4Z819xX=E~@FP0g|rl(&Sb@3q^S9x^R@-zOTS$79|ihiT}X^>!Af$@(rcW4j{tVBwLkccp92u$euvCKzbR)YWrYV@8=WRv+h@&tUwUbN zbMy`R7HRkT08q^5es_3)Wb0619)hc;AMi?IBKGh^jFh;+jEE&39279r=^c{^?r#Wo zT+IxuJtYJoL|^R^bcM!XM5E;JI6MJ3M}g7Of5=JXqdp}X=js)5lSueodHPFz*(Xp*8BBMWW@FL3wqz&1LW*FO%SWj zri1LI%|UX1yVD;GncS$!2OG}tyyJ8Q9a3|w`Mz^@+rc-ztzic(q5hOLkLphK2(&#^ zF{w9RvfC|`P9r0&WN>Xr-=m+6n;>+cgr^B(G$8c=Zzix!)50xh!vR!kL}F+ZcyfE7 z2_lAB!Rj?n2WDr}8J_O7P(9$Q(*?l}bW*2IddW&BZ=g;p8Ez+UMEb$@_K?V}k}c*O ztsF*vxZQC!S~M{?TTzhL4oDxp-C{*(VDd#jYb83}bM=i*YiYZ`fu&5j>%t@m33FTh z-mur~Z3v=-l=ODjd3Xz>cUoSWC5SZ|SOlQU#%}?rCm7UaXY|`ZZ3Y8<>vj~1fMxYN z&fpI8Dc*I?FS&)Dqi+p>b8aP8w+&tCH)fKYxeXh^@F~>swn+7dU^XGC=JPYfe@rN2BR?srk29Ntxf=0H@IOC4K!? z6wn6;w4>I6cBk7Joc0${0xnDjCk=ft?=9R>^usOeL9JUjOOq)P;t&e%mtE0&NSo_CH5+&30qIF;_2Q~FCAWL751!`HI z(4M3&ddWI8R!@B!xCLIpbtw~}+1A0a!Y=6RWw4zCTA<;|gz_XwY=f@pEBeWBSPXl= zMS~iIcmM-Q`Ici|KL##>+1g%f9|89G6dyZwG-BX(Q-?Y;oXgXMqRp)AW+~H zc2j&RtG@vCF6bHk3<*pAH{Yh+W9rg&-%tdC!Z^pH#8F;KX*+TkeKkhXLUg<; z4FWo4#l|W`4vQG^ovTF=OJ66k)g@%Q%UHu&$O;SDUDG4x{+83OZ*PF}yUd=1k-L;p zdl|04KbjCq@LVWh##D_G$0ZkiHB6&QPGrP9;M9b#Xvpedk}BevfEaH?mc>GqIsmU@ z?f^y;LUUFKT>0S(>8LQA%r)`H<_ znpYa?63v7j{9b=rTFX_4yasi;Z$zM8_PVXX`9!zg?a3F6rBxp~B(m^W;^*to$iQiv z>lpzN{5&}3rz?-x{cLLbs5Gso05867g(8O6h*aFH$)a^=2hlnl=%uB1^|`@?&5cfX zeW?vKv3K5zQ)idj8}(BpqJ{OY6>dU38#=L8rw>7^*S`>R9&T=I4?5=@2nE~SCNiyf z4-$#@Syb{X30a<+i0f#Sd(iA{cZWzxA|=%63R!;+ zFVQwc#zE&bG_z@a^Nw)6*@Uj0IN#geXf3gp6sd1edl%*!_0|$9h7P!ezFLj#!G&lG zIkXVqlf7X*AdoHrq)!2526!Gq z5Z&H_tQnDls?q5sJA)-}W~_}~Z)=V!-K=*nEYYY2Nof{Xq5-5xfE2Cvx~F2@9{PF? z>RuF2B(+_L;DNfj7e38W3SV5dlRdqcl@)HhsmHn)Zs=IIH` z)jQ|(X}S~JsBUYk*GK2WXks|LFo&PPcJs8`11j1E1aOsGPJgpAL}6>Iv*pBSYGGa2 zxBv(h68gY#*5}CV0Z|$ANYk8b_Im?N1`o)Jv(WBpYqx50D8({;6FbX8QJ?MnejIo!e zg23by34u$43*F{k5QRE~YBg=26mOxk2x9>iOm6})hK|3OR$A$+i;p*B9^-yGI&>>^ zVS*#fP4^VkK@-!aX)&Th5NkHK^bphr5-_|Je6*1DV8X9gzfyH&zl(IRx zN7fbCVl4}_5sIpA#m?6Uoh5qP%U{zI_?1ixX^Fi$myLP3@W@6sx!#D>tTh^dE(YLq z&vp8}?k1=iS!+qFCWfD)!GZolvFQ%c16VSZk~fLU{ZEf6ndk!?DoTQoK$HYCtVO=` zARCv<`fBJZIk)IEqY*sJaghGr#x?;s*>kUvtHY-Jtl4$ZJHQUvd5Pdl;n*0Fkj`6R zU081BvxZm$F;pu!R0|@bb^f#iK3;F|3^bRn1-!pzQ$78htLmANaO=qgjL?F zT92Ah_vMy=$mgaWb00z>DZY)a&(o4%XLfhTx2}K7+TEc8!a1XQE+`?WDd?+b37I0n zasYnjWzmt+ge2p*H&YH~%E3%Im?;NP!@t>;1DC1WVIfB>kW|V#?YU0fRYUdXju$hn zw9wGha&Y$u8Ni0L)Fh6ud^m2x~YsJiQ>bmgi{C7YfV(HhpsB zh|)-nkv<3sXILi>&Kdm{s?5z^61sv=2=$}LzERCSW<^P)CM{Oe;f)c}j2+_gw{Ydi zmS4jQBlCr>hwLbs=GoMu!}lT_)b?Z@FteKum~opTkq3f(K?o;Ba_=uwhh^2?q_`_n za^nNYEwZ4Ngg3tJ^o!maRy@c23X1MmXW_Ut$II)gO>oGZKm-A%m3?CFph-v@prd<* zh0D{jE-XPa)$c~Fx2v_j09BCAs(+i;eZ^W95xyAh1r~hgb!asxc#nQEB$jo&;5Rs_ zNm?MDRBqv<*TP&6wlLp#7m2#>gE>o;M2KEWhP6x^@k(U>zkq^7vnOrt4rKY*CHCKpQ?AdevCuf zT_1`QOY-`-Q>pJU-D#EaXJROWW3q(%8;V1P2)ktw2S?gKBDf}O%0UDO84sEc3~^$) zV09QvJSLQ1x3ST0j*TW@Ll&ABQ5*E644z(TW`nb)uiipE98sRfI?YN=%AYD8*4)V4 zvP*4#^3ss)<^UUjK%(3(cmX|#B8mZ#5_$*(!<{@+m?-Al(qfsDFz3T%(sGuzyY+Jj zfFTpjCBzAlldreo*G{Z*PG66p-g&B5;AcWh6MBl02zUW=kq7|}>6Y=((=F(B!Hvz> zgGw){mF~3y=4*tUk>G+>1=&uBD3(hIyGRq$QBB>NA4HbWf@Yx`%sDI=lVf;8*0;Cr z907+w=CNSeP}*JbAlB)tFQVnWMuj#AAmdQYYp(zZI~5mpKxJxw2RKQRResoX2>_59 z7;D0P+;re1vj&LOu^I?avr>c8+b{{Ahq0UlH1cL)#$o6c9BbZ%qDTs)9L^}Ygb8hr zWi>h!@w1nV!!X=pttNa1sb{kXCxZ9ge#;9FqZJbi>o3?tuhcK9}HVHz1NWH*@iEw`N6{$`ewk4A z4pezwt@1e}9WeyKy+)$1?E7=YxF+Uh2vxiAi3UH|N243h$cz#I?0=(@K}@G8I+h6M z5&obWfT0M__m`6u-ezzk_oYeDJ;YL{c?0=>!{L8)?M0|@5RaDpkJ zefnyd`W_@xY@!P=--&!|2Qqafb{5}mDP$^6S{dXX(lwJKm>XeBeEpONnN3O7AP7~$ zB~)gR07SHTmry}287({701ErmX#tBcr*fw~mPnIckbPn%NfCPvMsP z$LP|5ak_K>4aU^3ZWyOa2T>-#GH(pfr9&}pAn*P0FE9NHLi*%C|~Fx3iP@gQ5ego;RL@3-Ef_^ z!5qAf+hA^#`vsn!m^a%mFxxMH&04el0`LmTfdAQk0YnUOWGzDJc>l|6zrcmregQvl z8#WW(C5C_<2qxYJhuMCCe+v5r4nf|VAKM{Ei4*n<%wK80z~PYn-L075(0FvDJt;bJTwr6JbwZ%Yvk)C=dV!BSMMo~~8vAH+VB^HF z#|-JDyr+F1hbGFGWAYbU9&V)VZ(Jg&mN27QClxF*sX8V-Hb~|qa;B23g)0((X$>sQ7R3N

{%#cU>l$eh?|8X%1y~K%yJfrI15EYoW)4E;w%(#7K#{%Pn?A!4n|^H zW}%43k9m=)vrxoYD58j@b>p#Tp@;}uoP{FdkXV;UAZ~9Kis19U->9$qrt268dh;w0)HP+8+U(%y z*}>6p8w5|7M%26;+&McqT8-o5C{K+JFYphdo*f)LJ2+Z~cg_xumSD5odZGb9RooZ|EUCmX6Ot>akjdJ0~80hX}4R}CXNPx`Nm4) zynUQNi#rh>#%bLxr}Z2TMjTB&HkyC{#yj<~kM~df*o@3SFTi?-QAwt~h>X;_P(A+3AWfB+O1%`G zQKFTm>oWTAl_GGlabZXYaO-q0+IeeE$(e;TI`vI9i$}9`ERd{P91A30%%rBhRBW@jG;_vG#r6na|Tb zi)({!eQR*KH(bM|j^{eg`CKmNU*j;y*ZFX7TUbJTK!u}^JTioVa?m!x&{F2 zUsw|-`Hu{uS!v~S%gu75SaJ$^r&$Ys-?cWkLO0{A(aFjqb(dEP*rgUv93Hnx%ZMo~t*D z?ON>GyA4DB#BLW#&E>{28Ymaa<$NVui(h-UscyPsX{6h^(n`MGYFC<#V!e>fWowD= zzSfmo=CH1jdRxv)C6`^PH&=>IE9M-EKFs z_1eORU|a8(FVq|1kM1!i6DJ;b9!sIN3i*+M&;&6V2u zLaBD}sz{uuIY*VQLE6GyaijH8Z&!-BcCN5oDm03<8-L(xrluqW|6GX}iFjUPC19lb z!b%wgqL_n}lW#Vh<=Ub9cBy{(6}bP+VWBMFtT#GqSj!Du=mKf1+$v)R%8f?5lyl0Z zmD=GSykbSUW$K-BuXR2@lHiMCtx_e|UTHQ;E4gB}kk8j{dg&GGXD&)1q@p2uQYr*# z%r(K48W?=L=+th$I`L8A(z#{k9k?NJGPG6dS*O&>w)5F$yWGew*KXNO3ic&bwTTk* zy|o?N0QMK_d0>sR(kPZ2`P!|`UFKzS8)b2d_RftgV-Tfwu~otv*PFRQ&HkQiZKOmu z+7CtrgUWKd;^dpTQo(VuWvuGrwbOT!Ir!SZ5zwx;ot0vKrLnwH%0Z?n)-u=5x@mn! zs%uu+bFfmV5RNlR=JTYR$8^&uZl9|6j%Y_tq;ff zM1HxEcXAL&i_48-qgAinu^VPihLF5B$>a4em+GZLrQB*2nk&t6yH$J6wfCG;)VPgMa`XiOvRH1ouEnuSUun_VXA`~2^`ns$oL z!<`}jGg4nR2a?#%w;M&rY2=m*wL7m$ws2WLMoL=VE{wPdW}hwPKpBhmdb853-E~#s zk=HtJ52qn+gCup9iu!mF&uLvs63! zva6YEx9hBLj8xeIb9V~GW@{M`S;#rHV=up!Dkthj`Qpk-qn*p=>njj&8_TuqUS`?C ze&TWNm0cQ=?0#$52ZLWO*z`rTpMPSe4!3<)+py{MKLtzVKNQ5 zLtBnA!-qss`nFvf5`*+bh4SgfGWe+DRPuyW<%Ux$&v8wp*bdfEU&TFZoL*M$y<+8K zat_8&C_yqPJ5H(CEawW)wvR7JL5LM-Zs$ab4OAEqzurA3UWwUJ%P)wvTWf-?e%tDm zI}tFll~0!{^?I%X^{NFhX%*_VdlFO~O*dq`lTo~&xq(x?n(-<2yS2@2vd%RNjb%{1 zO1ao9wLoNRt5iK%Vn`i+VG1wp7Bei2|%V5j@g|-x-$= zDD={I=$Lm10c*FvjNb-x+tbZ`gZ>lDoP_(62lM%bQMtrOu=SNK0 zz-c4~&C^b6?Lx=dXf@+f0Rp785_I~_nz;DUiSY{}*Uq)YBVD_8D#fYpHGikCfpZXvR!$VOTqzm&ldBg>%f)&*x11}0)6~vf6-QTPpA~Oy z(78sg-i8Ff;xrs54-vMu{^D!xoZwu5x4P4Y`Ltaxms`t?QmM9a?Nt$)p;yaUr|skm zxpoVdl5DPB+kDBj_R5ob%cmhfXP5H@STKt)dso`E?p3i-bi4+9cy`E6iV6J z*=tszb`UYKJe6VtvT>nQXqB3Ua-r7Wjn8-xt%#KXyfP$*b~_73P_Nf3mD=Dk5zQje zxdKFsk`U!O-LD!(aGlWE6eptp;<21j&5w6+dP_E zF6B$b@^ZNX{csw~=0u>fv_QP0V38*rfhEvj~;zF{}RePbo&c$uMr}sqQp)D z$wPe@eBcZHuI_m}iO=a%!lPx#9Gtnf&AEhFg<^KO*lsjiuw>Vp?b@T)y-Ov434EvE zj_{#005TV^dzUKBJgo50;0kRAsN1Sty6#9uYnS!fE|BWc@1Uv* z7PFFT!-WAWT`ONKgV4QJ-%TkPt4HdmAVn=uW6+Lzw$RK$BB{Mj{}@5{j)4J%6^44~ ztQ(4O-0XGFIeozYz5U+iZVbcq`9lUL1>wSA=xiQ6TI=Hy#TWKkj)yO8DS{Ki96h?x zX+XW|l4$O1T2Ye{h+bi%$t+cc4LVhLRs=Z$S#IB<#3jN@j9fi;T9)h3v z>V|SJW`VrQ>S!8;0({`57l18o)|>s_V0o+G>5_cg z01L0TAOJxIS%%r9_QU#a5|sQrmvy*;we3Ql;H}kctiX;^D8r6jT5i-H*Eg=*hd?FE zeud-#*+8w(%a>ctCW%T}C#MUlvZHh24rLYE3mWth@OJx1Q!SJVGR=7kLq+q zz5o6ueIMmZ(*fW_WW8CZ3zo-{-=Z&w+FDM(Fa0KO)fYsKO4^U<;Z0|=xpm4g?ibk zyCt&G zQ?MfXW7WRPkGnFW8};s~?fNN4w_c8zDlHI)rH3fas(DB5XldnWcBxDDkBtlV5vWU* UWl;9IozLbAOWFKVzVf{P4;6_qC;$Ke From 7cf93c0b2210a7fec44ff65584844d8071f9f261 Mon Sep 17 00:00:00 2001 From: Guillaume Fournier <35076723+Giom-fm@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:30:51 +0200 Subject: [PATCH 10/19] feat: offline templates (#260) Co-authored-by: Guillaume Fournier <> --- cli/src/adapter.rs | 44 +++++++++++++++++++ cli/src/bin/noops.rs | 1 + cli/src/build/cargo.rs | 7 ++- cli/src/build/golang.rs | 6 +-- cli/src/build/mod.rs | 45 +------------------ cli/src/commands/create.rs | 85 ++++++------------------------------ cli/src/commands/mod.rs | 7 ++- cli/src/commands/template.rs | 35 +++++++++++++++ cli/src/config.rs | 2 + cli/src/lib.rs | 3 +- cli/src/manifest.rs | 2 +- cli/src/template.rs | 61 ++++++++++++++++++++++++++ cli/src/templates.rs | 43 ------------------ crates/common/src/dtos.rs | 12 ++--- 14 files changed, 178 insertions(+), 175 deletions(-) create mode 100644 cli/src/adapter.rs create mode 100644 cli/src/commands/template.rs create mode 100644 cli/src/template.rs delete mode 100644 cli/src/templates.rs diff --git a/cli/src/adapter.rs b/cli/src/adapter.rs new file mode 100644 index 0000000..a118b33 --- /dev/null +++ b/cli/src/adapter.rs @@ -0,0 +1,44 @@ +use anyhow::anyhow; +use std::{path::Path, process::Command}; + +#[derive(Clone, Debug, Default)] +pub struct BaseAdapter { + program: String, +} + +impl BaseAdapter { + pub fn new(program: &str) -> Self { + BaseAdapter { + program: program.to_string(), + } + } + + pub fn build_command(&self, path: &Path, args: &[&str]) -> Command { + let mut command = Command::new(self.program.clone()); + command.args(args).current_dir(path); + command + } + + pub fn execute(&self, mut command: Command) -> anyhow::Result<()> { + let output = command.output()?; + + match output.status.code() { + Some(0) => { + log::debug!("{} succeeded!", command.get_program().to_string_lossy()); + Ok(()) + } + Some(code) => { + let stderr = String::from_utf8_lossy(&output.stderr); + let error_message = format!("Command failed with error code {}: {}", code, stderr); + log::error!("{}", error_message); + Err(anyhow!(error_message)) + } + None => { + let stderr = String::from_utf8_lossy(&output.stderr); + let error_message = format!("Command was terminated by a signal: {}", stderr); + log::error!("{}", error_message); + Err(anyhow!(error_message)) + } + } + } +} diff --git a/cli/src/bin/noops.rs b/cli/src/bin/noops.rs index 1c2c2dd..b8e03fd 100644 --- a/cli/src/bin/noops.rs +++ b/cli/src/bin/noops.rs @@ -12,6 +12,7 @@ fn main() -> anyhow::Result<()> { commands::Cli::Deploy(cmd) => cmd.execute()?, commands::Cli::Destroy(cmd) => cmd.execute()?, commands::Cli::Show(cmd) => cmd.execute()?, + commands::Cli::Template(cmd) => cmd.execute()?, } Ok(()) } diff --git a/cli/src/build/cargo.rs b/cli/src/build/cargo.rs index 9efd212..19029ec 100644 --- a/cli/src/build/cargo.rs +++ b/cli/src/build/cargo.rs @@ -1,6 +1,7 @@ -use super::BaseAdapter; use std::path::Path; +use crate::adapter::BaseAdapter; + const PROGRAM: &str = "cargo"; pub struct CargoAdapter { @@ -10,9 +11,7 @@ pub struct CargoAdapter { impl CargoAdapter { pub fn new() -> Self { CargoAdapter { - adapter: BaseAdapter { - program: PROGRAM.to_string(), - }, + adapter: BaseAdapter::new(PROGRAM), } } diff --git a/cli/src/build/golang.rs b/cli/src/build/golang.rs index 788c34e..ebc325a 100644 --- a/cli/src/build/golang.rs +++ b/cli/src/build/golang.rs @@ -1,4 +1,4 @@ -use super::BaseAdapter; +use crate::adapter::BaseAdapter; use anyhow::{Context, Result}; use std::{borrow::Cow, fs, path::Path}; use wasm_encoder::{Encode, Section}; @@ -15,9 +15,7 @@ pub struct GolangAdapter { impl GolangAdapter { pub fn new() -> Self { GolangAdapter { - adapter: BaseAdapter { - program: PROGRAM.to_string(), - }, + adapter: BaseAdapter::new(PROGRAM), } } diff --git a/cli/src/build/mod.rs b/cli/src/build/mod.rs index 4297fe8..0dcd697 100644 --- a/cli/src/build/mod.rs +++ b/cli/src/build/mod.rs @@ -3,57 +3,14 @@ pub mod golang; use crate::manifest::Component; use crate::{manifest::Manifest, terminal::Terminal}; -use anyhow::anyhow; use anyhow::Context; use common::dtos::Language; -use std::{path::Path, process::Command}; +use std::path::Path; use self::cargo::CargoAdapter; use self::golang::GolangAdapter; -#[derive(Clone, Debug, Default)] -pub struct BaseAdapter { - program: String, -} - -impl BaseAdapter { - pub fn new(program: &str) -> Self { - BaseAdapter { - program: program.to_string(), - } - } - - pub fn build_command(&self, path: &Path, args: &[&str]) -> Command { - let mut command = Command::new(self.program.clone()); - command.args(args).current_dir(path); - command - } - - pub fn execute(&self, mut command: Command) -> anyhow::Result<()> { - let output = command.output()?; - - match output.status.code() { - Some(0) => { - log::debug!("{} succeeded!", command.get_program().to_string_lossy()); - Ok(()) - } - Some(code) => { - let stderr = String::from_utf8_lossy(&output.stderr); - let error_message = format!("Command failed with error code {}: {}", code, stderr); - log::error!("{}", error_message); - Err(anyhow!(error_message)) - } - None => { - let stderr = String::from_utf8_lossy(&output.stderr); - let error_message = format!("Command was terminated by a signal: {}", stderr); - log::error!("{}", error_message); - Err(anyhow!(error_message)) - } - } - } -} - pub fn build_project(terminal: &Terminal, manifest: &Manifest) -> anyhow::Result<()> { terminal.write_heading("Building project")?; diff --git a/cli/src/commands/create.rs b/cli/src/commands/create.rs index ecc081b..646ce83 100644 --- a/cli/src/commands/create.rs +++ b/cli/src/commands/create.rs @@ -1,21 +1,16 @@ use super::Command; use crate::{ - build::BaseAdapter, config::Config, manifest::{Component, Manifest}, - templates::{Template, TEMPLATES}, + template::{Template, TemplateManager}, terminal::Terminal, }; use anyhow::Context; use clap::Parser; use std::fs; use std::path::Path; -use tempfile::tempdir; use walkdir::WalkDir; -const PROGRAM: &str = "git"; -const REPOSITORY: &str = "https://github.com/JFComputing/noops-templates.git"; - #[derive(Parser, Debug)] pub struct CreateCommand { pub name: String, @@ -25,18 +20,24 @@ impl Command for CreateCommand { fn execute(&self) -> anyhow::Result<()> { let terminal = Terminal::new(); let config = Config::default(); - let git = GitAdapter::new(); + let template_manager = TemplateManager::new(); let mut manifest = Manifest::from_yaml(&config.manifest_path)?; terminal.write_heading("Creating component")?; - let index = terminal.select_prompt("Select a template", &TEMPLATES)?; - let template = Template::new(self.name.clone(), index); + let templates = template_manager.list(&config.templates_dir)?; + let index = terminal.select_prompt("Select a template", &templates)?; + let mut template = templates[index].clone(); + template.name = self.name.clone(); let text = format!("Adding {}", &template.name); let spinner = terminal.spinner(&text); - create(&mut manifest, &git, &template) - .context(format!("Creating module \"{}\" failed", self.name))?; + create( + &mut manifest, + &template, + &config.templates_dir.join(&template.subpath), + ) + .context(format!("Creating module \"{}\" failed", self.name))?; spinner.finish_with_message(text); Ok(()) @@ -45,8 +46,8 @@ impl Command for CreateCommand { pub fn create( manifest: &mut Manifest, - git: &GitAdapter, template: &Template, + template_path: &Path, ) -> anyhow::Result<()> { if manifest.get(&template.name).is_some() { anyhow::bail!("Module already exists"); @@ -56,9 +57,7 @@ pub fn create( anyhow::bail!("Module target path is not empty"); } - let temp_dir = tempdir()?; - git.checkout_template(temp_dir.path(), &template.subpath)?; - copy_dir(&temp_dir.path().join(&template.subpath), to)?; + copy_dir(template_path, to)?; let module = Component::from_template(template); manifest.add(module)?; @@ -79,59 +78,3 @@ pub fn copy_dir(from: &Path, to: &Path) -> anyhow::Result<()> { } Ok(()) } - -#[derive(Clone, Debug, Default)] -pub struct GitAdapter { - adapter: BaseAdapter, -} - -impl GitAdapter { - pub fn new() -> Self { - GitAdapter { - adapter: BaseAdapter::new(PROGRAM), - } - } - - pub fn checkout_template(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { - self.clone_no_checkout(working_dir, working_dir)?; - self.sparse_checkout(working_dir, path)?; - self.checkout(working_dir)?; - Ok(()) - } - - fn clone_no_checkout(&self, working_dir: &Path, path: &Path) -> anyhow::Result<()> { - let command = self.adapter.build_command( - working_dir, - &[ - "clone", - "--no-checkout", - REPOSITORY, - path.to_string_lossy().as_ref(), - ], - ); - self.adapter.execute(command)?; - Ok(()) - } - - fn sparse_checkout(&self, working_dir: &Path, subpath: &Path) -> anyhow::Result<()> { - let command = self - .adapter - .build_command(working_dir, &["sparse-checkout", "init", "cone"]); - self.adapter.execute(command)?; - - let command = self.adapter.build_command( - working_dir, - &["sparse-checkout", "set", subpath.to_string_lossy().as_ref()], - ); - self.adapter.execute(command)?; - Ok(()) - } - - fn checkout(&self, working_dir: &Path) -> anyhow::Result<()> { - let command = self - .adapter - .build_command(working_dir, &["checkout", "main"]); - self.adapter.execute(command)?; - Ok(()) - } -} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index ad77882..2e15dce 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -5,10 +5,11 @@ pub mod destroy; pub mod init; pub mod login; pub mod show; +pub mod template; use self::{ build::BuildCommand, create::CreateCommand, deploy::DeployCommand, destroy::DestroyCommand, - init::InitCommand, login::LoginCommand, show::ShowCommand, + init::InitCommand, login::LoginCommand, show::ShowCommand, template::TemplateCommand, }; use clap::Parser; @@ -40,4 +41,8 @@ pub enum Cli { /// Show information about the project or a function Show(ShowCommand), + + /// template + #[command(subcommand)] + Template(TemplateCommand), } diff --git a/cli/src/commands/template.rs b/cli/src/commands/template.rs new file mode 100644 index 0000000..c17721b --- /dev/null +++ b/cli/src/commands/template.rs @@ -0,0 +1,35 @@ +use super::Command; +use crate::{config::Config, template::TemplateManager, terminal::Terminal}; +use anyhow::Result; +use clap::Subcommand; + +#[derive(Debug, Subcommand)] +pub enum TemplateCommand { + List, + Update, +} + +impl Command for TemplateCommand { + fn execute(&self) -> anyhow::Result<()> { + match &self { + TemplateCommand::List => list(), + TemplateCommand::Update => update(), + } + } +} + +fn update() -> Result<()> { + let config = Config::default(); + let manager = TemplateManager::new(); + manager.update(&config.templates_dir)?; + Ok(()) +} + +fn list() -> Result<()> { + let config = Config::default(); + let manager = TemplateManager::new(); + let terminal = Terminal::new(); + let templates = manager.list(&config.templates_dir)?; + terminal.write_text(format!("{:?}", templates))?; + Ok(()) +} diff --git a/cli/src/config.rs b/cli/src/config.rs index 1a26a40..722fb85 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -8,6 +8,7 @@ pub struct Config { pub jwt_file: PathBuf, pub base_url: String, pub manifest_path: PathBuf, + pub templates_dir: PathBuf, } impl Default for Config { @@ -25,6 +26,7 @@ impl Default for Config { jwt_file: strategy.in_cache_dir("jwt"), base_url: "http://localhost:8080/api/".to_string(), manifest_path: Path::new("./noops.yaml").to_path_buf(), + templates_dir: strategy.in_cache_dir("templates"), } } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 8eee253..1ba6c61 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,8 +1,9 @@ +mod adapter; mod build; pub mod commands; mod config; mod deploy; mod info; mod manifest; -mod templates; +mod template; mod terminal; diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index 81ea6c9..82f2684 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, templates::Template}; +use crate::{config::Config, template::Template}; use common::dtos::Language; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; diff --git a/cli/src/template.rs b/cli/src/template.rs new file mode 100644 index 0000000..cc4163b --- /dev/null +++ b/cli/src/template.rs @@ -0,0 +1,61 @@ +use crate::adapter::BaseAdapter; +use anyhow::Result; +use common::dtos::Language; +use serde::Deserialize; +use std::{fmt::Display, path::PathBuf}; +use std::{fs, path::Path}; + +const PROGRAM: &str = "git"; +const REPOSITORY: &str = "https://github.com/JFComputing/noops-templates.git"; + +#[derive(Default, Clone, Debug, Deserialize)] +pub struct TemplateManifest { + pub templates: Vec