From b430d2180790ff58ae5614746b0544aa4bf023a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20Gr=C3=B8dem?= <29732646+oddgrd@users.noreply.github.com> Date: Fri, 28 Oct 2022 08:36:34 +0200 Subject: [PATCH] Feat/update contributing (#426) * feat(docs): update contributing.md * feat(e2e): make e2e admin user unique * feat(e2e): clean up e2e test projects on test complete * feat(e2e): don't fail on superuser conflict * feat: add section about contributing to docs * feat: remove unwrap, add comment to drop impl * tests: update e2e test key Co-authored-by: chesedo --- CONTRIBUTING.md | 75 ++++++++++++++++++++++++---- e2e/README.md | 10 ++-- e2e/tests/integration/axum.rs | 5 +- e2e/tests/integration/helpers/mod.rs | 60 ++++++++++++++-------- e2e/tests/integration/poem.rs | 13 ++--- e2e/tests/integration/rocket.rs | 19 ++++--- e2e/tests/integration/salvo.rs | 5 +- e2e/tests/integration/thruster.rs | 8 ++- e2e/tests/integration/tide.rs | 5 +- e2e/tests/integration/tower.rs | 8 ++- e2e/tests/integration/warp.rs | 5 +- 11 files changed, 154 insertions(+), 59 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 276d489e2..50574ba80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,11 @@ Raising [issues](https://github.com/shuttle-hq/shuttle/issues) is encouraged. We have some templates to help you get started. +## Docs + +If you found an error in our docs, or you simply want to make them better, contributions to our [docs](https://github.com/shuttle-hq/shuttle-docs) +are always appreciated! + ## Running Locally You can use Docker and docker-compose to test shuttle locally during development. See the [Docker install](https://docs.docker.com/get-docker/) and [docker-compose install](https://docs.docker.com/compose/install/) instructions if you do not have them installed already. @@ -13,20 +18,22 @@ You should now be ready to setup a local environment to test code changes to cor Build the required images with: ```bash -$ make images +make images ``` +> Note: The current [Makefile](https://github.com/shuttle-hq/shuttle/blob/main/Makefile) does not work on Windows systems, if you want to build the local environment on Windows you could use [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install). + The images get built with [cargo-chef](https://github.com/LukeMathWalker/cargo-chef) and therefore support incremental builds (most of the time). So they will be much faster to re-build after an incremental change in your code - should you wish to deploy it locally straight away. You can now start a local deployment of shuttle and the required containers with: ```bash -$ make up +make up ``` -*Note*: Other useful commands can be found within the [Makefile](https://github.com/shuttle-hq/shuttle/blob/main/Makefile). +> Note: Other useful commands can be found within the [Makefile](https://github.com/shuttle-hq/shuttle/blob/main/Makefile). -The API is now accessible on `localhost:8000` (for app proxies) and `localhost:8001` (for the control plane). When running `cargo run --bin cargo-shuttle` (in a debug build), the CLI will point itself to `localhost` for its API calls. The deployment parameters can be tweaked by changing values in the [.env](./.env) file. +The API is now accessible on `localhost:8000` (for app proxies) and `localhost:8001` (for the control plane). When running `cargo run --bin cargo-shuttle` (in a debug build), the CLI will point itself to `localhost` for its API calls. In order to test local changes to the `shuttle-service` crate, you may want to add the below to a `.cargo/config.toml` file. (See [Overriding Dependencies](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html) for more) @@ -39,6 +46,12 @@ shuttle-shared-db = { path = "[base]/shuttle/resources/shared-db" } shuttle-secrets = { path = "[base]/shuttle/resources/secrets" } ``` +Prime gateway database with an admin user: + +```bash +docker compose --file docker-compose.rendered.yml --project-name shuttle-dev exec gateway /usr/local/bin/service --state=/var/lib/shuttle/gateway.sqlite init --name admin --key test-key +``` + Login to shuttle service in a new terminal window from the main shuttle directory: ```bash @@ -51,18 +64,37 @@ cd into one of the examples: cd examples/rocket/hello-world/ ``` -Deploy the example: +Create a new project, this will start a deployer container: ```bash # the --manifest-path is used to locate the root of the shuttle workspace +cargo run --manifest-path ../../../Cargo.toml --bin cargo-shuttle -- project new +``` + +Verify that the deployer is healthy and in the ready state: + +```bash +cargo run --manifest-path ../../../Cargo.toml --bin cargo-shuttle -- project status +``` + +Deploy the example: + +```bash cargo run --manifest-path ../../../Cargo.toml --bin cargo-shuttle -- deploy ``` Test if the deploy is working: ```bash -# (the Host header should match the Host from the deploy output) -curl --header "Host: {app}.localhost.local" localhost:8000/hello +# the Host header should match the Host from the deploy output +curl --header "Host: {app}.unstable.shuttleapp.rs" localhost:8000/hello +``` + +View logs from the current deployment: + +```bash +# append `--follow` to this command for a live feed of logs +cargo run --manifest-path ../../../Cargo.toml --bin cargo-shuttle -- logs ``` ### Testing deployer only @@ -96,7 +128,7 @@ export DOCKER_HOST=unix:///tmp/podman.sock shuttle can now be run locally using the steps shown earlier. -*NOTE*: Testing the `gateway` with a rootless Podman does not work since Podman does not allow access to the `deployer` containers via IP address! +> Note: Testing the `gateway` with a rootless Podman does not work since Podman does not allow access to the `deployer` containers via IP address! ## Running Tests @@ -104,15 +136,38 @@ shuttle has reasonable test coverage - and we are working on improving this every day. We encourage PRs to come with tests. If you're not sure about what a test should look like, feel free to [get in touch](https://discord.gg/H33rRDTm3p). -To run the test suite - just run `make test` at the root of the repository. +To run the unit tests for a spesific crate, from the root of the repository run: + +```bash +# replace with the name of the crate to test, e.g. `shuttle-common` +cargo test --package --all-features --lib -- --nocapture +``` + +To run the integration tests for a spesific crate (if it has any), from the root of the repository run: + +```bash +# replace with the name of the crate to test, e.g. `cargo-shuttle` +cargo test --package --all-features --test '*' -- --nocapture +``` + +To run the end-to-end tests, from the root of the repository run: + +```bash +make test +``` +> Note: Running all the end-to-end tests may take a long time, so it is recommended to run individual tests shipped as part of each crate in the workspace first. ## Committing We use the [Angular Commit Guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). We expect all commits to conform to these guidelines. Furthermore, commits should be squashed before being merged to master. -Also, make sure your commits don't trigger any warnings from Clippy by running: `cargo clippy --tests --all-targets`. If you have a good reason to contradict Clippy, insert an #allow[] macro, so that it won't complain. +Before committing: +- Make sure your commits don't trigger any warnings from Clippy by running: `cargo clippy --tests --all-targets`. If you have a good reason to contradict Clippy, insert an `#[allow(clippy::)]` macro, so that it won't complain. +- Make sure your code is correctly formatted: `cargo fmt --all --check`. +- Make sure your `Cargo.toml`'s are sorted: `cargo sort --workspace`. This command uses the [cargo-sort crate](https://crates.io/crates/cargo-sort) to sort the `Cargo.toml` dependencies alphabetically. +- If you've made changes to examples, make sure the above commands are ran there as well. ## Project Layout The folders in this repository relate to each other as follow: diff --git a/e2e/README.md b/e2e/README.md index 297ff3864..7d6caaf99 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -4,12 +4,16 @@ This crate runs all the end-to-end tests for shuttle. These tests must run again Running all the end-to-end tests may take a long time, so it is recommended to run individual tests shipped as part of each crate in the workspace first. ## Running the tests -Simply do +In the root of the repository, run: ```bash -$ SHUTTLE_API_KEY=test-key cargo test -- --nocapture +make test ``` -the `--nocapture` flag helps with logging errors as they arise instead of in one block at the end. +To run individual tests, in the root of the e2e directory run: + +```bash +POSTGRES_PASSWORD=postgres APPS_FQDN=unstable.shuttleapp.rs cargo test -- --nocapture +``` The server-side logs can be accessed with `docker compose logs`. diff --git a/e2e/tests/integration/axum.rs b/e2e/tests/integration/axum.rs index fc20183ce..698cd7e35 100644 --- a/e2e/tests/integration/axum.rs +++ b/e2e/tests/integration/axum.rs @@ -4,8 +4,9 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_axum() { - let client = helpers::Services::new_docker("hello-world (axum)", Color::Green); - client.deploy("axum/hello-world"); + let client = + helpers::Services::new_docker("hello-world (axum)", "axum/hello-world", Color::Green); + client.deploy(); let request_text = client .get("hello") diff --git a/e2e/tests/integration/helpers/mod.rs b/e2e/tests/integration/helpers/mod.rs index 8704b296d..f2c1269fe 100644 --- a/e2e/tests/integration/helpers/mod.rs +++ b/e2e/tests/integration/helpers/mod.rs @@ -112,10 +112,10 @@ CARGO_HOME: {} let admin_key = if let Ok(key) = env::var("SHUTTLE_API_KEY") { key } else { - "test-key".to_string() + "e2e-test-key".to_string() }; - Command::new(DOCKER.as_os_str()) + _ = Command::new(DOCKER.as_os_str()) .args([ "compose", "--file", @@ -128,12 +128,11 @@ CARGO_HOME: {} "--state=/var/lib/shuttle/gateway.sqlite", "init", "--name", - "admin", + "test", "--key", &admin_key, ]) - .output() - .ensure_success("failed to create admin user on gateway"); + .output(); }; } @@ -214,12 +213,14 @@ pub fn spawn_and_log>( pub struct Services { api_addr: SocketAddr, proxy_addr: SocketAddr, + /// Path within the examples dir to a specific example + example_path: String, target: String, color: Color, } impl Services { - fn new_free(target: D, color: C) -> Self + fn new_free(target: D, example_path: D, color: C) -> Self where D: std::fmt::Display, C: Into, @@ -229,16 +230,25 @@ impl Services { proxy_addr: "127.0.0.1:8000".parse().unwrap(), target: target.to_string(), color: color.into(), + example_path: example_path.to_string(), } } - pub fn new_docker(target: D, color: C) -> Self + /// Initializes a a test client + /// + /// # Arguments + /// + /// * `target` - A string that describes the test target + /// * `example_path` - Path to a specific example within the examples dir, this is where + /// `project new` and `deploy` will run + /// * `color` - a preferably unique `crossterm::style::Color` to distinguish test logs + pub fn new_docker(target: D, example_path: D, color: C) -> Self where D: std::fmt::Display, C: Into, { let _ = *LOCAL_UP; - let service = Self::new_free(target, color); + let service = Self::new_free(target, example_path, color); service.wait_ready(Duration::from_secs(15)); // Make sure provisioner is ready, else deployers will fail to start up @@ -330,18 +340,18 @@ impl Services { panic!("timed out while waiting for mongodb to be ready"); } - pub fn wait_deployer_ready(&self, project_path: &str, mut timeout: Duration) { + pub fn wait_deployer_ready(&self, mut timeout: Duration) { let mut now = SystemTime::now(); while !timeout.is_zero() { let mut run = Command::new(WORKSPACE_ROOT.join("target/debug/cargo-shuttle")); if env::var("SHUTTLE_API_KEY").is_err() { - run.env("SHUTTLE_API_KEY", "test-key"); + run.env("SHUTTLE_API_KEY", "e2e-test-key"); } run.env("CARGO_HOME", CARGO_HOME.path()); run.args(["project", "status"]) - .current_dir(Self::get_project_path(project_path)); + .current_dir(self.get_full_project_path()); let stdout = run.output().unwrap().stdout; let stdout = String::from_utf8(stdout).unwrap(); @@ -358,31 +368,31 @@ impl Services { panic!("timed out while waiting for deployer to be ready"); } - pub fn run_client<'s, I>(&self, args: I, project_path: &str) -> Child + pub fn run_client<'s, I>(&self, args: I) -> Child where I: IntoIterator, { let mut run = Command::new(WORKSPACE_ROOT.join("target/debug/cargo-shuttle")); if env::var("SHUTTLE_API_KEY").is_err() { - run.env("SHUTTLE_API_KEY", "test-key"); + run.env("SHUTTLE_API_KEY", "e2e-test-key"); } run.env("CARGO_HOME", CARGO_HOME.path()); - run.args(args) - .current_dir(Self::get_project_path(project_path)); + run.args(args).current_dir(self.get_full_project_path()); spawn_and_log(&mut run, &self.target, self.color) } - pub fn deploy(&self, project_path: &str) { - self.run_client(["project", "new"], project_path) + /// Starts a project and deploys a service for the example in `self.example_path` + pub fn deploy(&self) { + self.run_client(["project", "new"]) .wait() .ensure_success("failed to run deploy"); - self.wait_deployer_ready(project_path, Duration::from_secs(120)); + self.wait_deployer_ready(Duration::from_secs(120)); - self.run_client(["deploy", "--allow-dirty"], project_path) + self.run_client(["deploy", "--allow-dirty"]) .wait() .ensure_success("failed to run deploy"); } @@ -396,7 +406,15 @@ impl Services { reqwest::blocking::Client::new().post(format!("http://{}/{}", self.proxy_addr, sub_path)) } - pub fn get_project_path(project_path: &str) -> PathBuf { - WORKSPACE_ROOT.join("examples").join(project_path) + /// Gets the full path: the path within examples to a specific example appended to the workspace root + pub fn get_full_project_path(&self) -> PathBuf { + WORKSPACE_ROOT.join("examples").join(&self.example_path) + } +} + +impl Drop for Services { + fn drop(&mut self) { + // Initiate project destruction on test completion + _ = self.run_client(["project", "rm"]).wait(); } } diff --git a/e2e/tests/integration/poem.rs b/e2e/tests/integration/poem.rs index 9c521b387..7f5a566bb 100644 --- a/e2e/tests/integration/poem.rs +++ b/e2e/tests/integration/poem.rs @@ -4,8 +4,9 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_poem() { - let client = helpers::Services::new_docker("hello-world (poem)", Color::Cyan); - client.deploy("poem/hello-world"); + let client = + helpers::Services::new_docker("hello-world (poem)", "poem/hello-world", Color::Cyan); + client.deploy(); let request_text = client .get("hello") @@ -20,8 +21,8 @@ fn hello_world_poem() { #[test] fn postgres_poem() { - let client = helpers::Services::new_docker("postgres (poem)", Color::Blue); - client.deploy("poem/postgres"); + let client = helpers::Services::new_docker("postgres (poem)", "poem/postgres", Color::Blue); + client.deploy(); let add_response = client .post("todo") @@ -48,8 +49,8 @@ fn postgres_poem() { #[test] fn mongodb_poem() { - let client = helpers::Services::new_docker("mongo (poem)", Color::Green); - client.deploy("poem/mongodb"); + let client = helpers::Services::new_docker("mongo (poem)", "poem/mongodb", Color::Green); + client.deploy(); // post todo and get its generated objectId let add_response = client diff --git a/e2e/tests/integration/rocket.rs b/e2e/tests/integration/rocket.rs index e508af62c..bbf4d0017 100644 --- a/e2e/tests/integration/rocket.rs +++ b/e2e/tests/integration/rocket.rs @@ -4,8 +4,12 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_rocket() { - let client = helpers::Services::new_docker("hello-world (rocket)", Color::DarkMagenta); - client.deploy("rocket/hello-world"); + let client = helpers::Services::new_docker( + "hello-world (rocket)", + "rocket/hello-world", + Color::DarkMagenta, + ); + client.deploy(); let request_text = client .get("hello") @@ -20,8 +24,9 @@ fn hello_world_rocket() { #[test] fn postgres_rocket() { - let client = helpers::Services::new_docker("postgres (rocket)", Color::Magenta); - client.deploy("rocket/postgres"); + let client = + helpers::Services::new_docker("postgres (rocket)", "rocket/postgres", Color::Magenta); + client.deploy(); let add_response = client .post("todo") @@ -47,15 +52,15 @@ fn postgres_rocket() { #[test] fn secrets_rocket() { - let client = helpers::Services::new_docker("secrets (rocket)", Color::Red); - let project_path = helpers::Services::get_project_path("rocket/secrets"); + let client = helpers::Services::new_docker("secrets (rocket)", "rocket/secrets", Color::Red); + let project_path = client.get_full_project_path(); std::fs::copy( project_path.join("Secrets.toml.example"), project_path.join("Secrets.toml"), ) .unwrap(); - client.deploy("rocket/secrets"); + client.deploy(); let secret_response: String = client .get("secret") .header("Host", format!("secrets-rocket-app.{}", *APPS_FQDN)) diff --git a/e2e/tests/integration/salvo.rs b/e2e/tests/integration/salvo.rs index c0749a0e4..01daf0b41 100644 --- a/e2e/tests/integration/salvo.rs +++ b/e2e/tests/integration/salvo.rs @@ -4,8 +4,9 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_salvo() { - let client = helpers::Services::new_docker("hello-world (salvo)", Color::DarkRed); - client.deploy("salvo/hello-world"); + let client = + helpers::Services::new_docker("hello-world (salvo)", "salvo/hello-world", Color::DarkRed); + client.deploy(); let request_text = client .get("hello") diff --git a/e2e/tests/integration/thruster.rs b/e2e/tests/integration/thruster.rs index eabc89ab4..ad6a20919 100644 --- a/e2e/tests/integration/thruster.rs +++ b/e2e/tests/integration/thruster.rs @@ -4,8 +4,12 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_thruster() { - let client = helpers::Services::new_docker("hello-world (thruster)", Color::DarkYellow); - client.deploy("thruster/hello-world"); + let client = helpers::Services::new_docker( + "hello-world (thruster)", + "thruster/hello-world", + Color::DarkYellow, + ); + client.deploy(); let request_text = client .get("hello") diff --git a/e2e/tests/integration/tide.rs b/e2e/tests/integration/tide.rs index 270d5c465..af4d487fe 100644 --- a/e2e/tests/integration/tide.rs +++ b/e2e/tests/integration/tide.rs @@ -4,8 +4,9 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_tide() { - let client = helpers::Services::new_docker("hello-world (tide)", Color::DarkYellow); - client.deploy("tide/hello-world"); + let client = + helpers::Services::new_docker("hello-world (tide)", "tide/hello-world", Color::DarkYellow); + client.deploy(); let request_text = client .get("hello") diff --git a/e2e/tests/integration/tower.rs b/e2e/tests/integration/tower.rs index 57945a99a..5592332aa 100644 --- a/e2e/tests/integration/tower.rs +++ b/e2e/tests/integration/tower.rs @@ -4,8 +4,12 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_tower() { - let client = helpers::Services::new_docker("hello-world (tower)", Color::DarkYellow); - client.deploy("tower/hello-world"); + let client = helpers::Services::new_docker( + "hello-world (tower)", + "tower/hello-world", + Color::DarkYellow, + ); + client.deploy(); let request_text = client .get("hello") diff --git a/e2e/tests/integration/warp.rs b/e2e/tests/integration/warp.rs index e47a4b58d..802fa2733 100644 --- a/e2e/tests/integration/warp.rs +++ b/e2e/tests/integration/warp.rs @@ -4,8 +4,9 @@ use crate::helpers::{self, APPS_FQDN}; #[test] fn hello_world_warp() { - let client = helpers::Services::new_docker("hello-world (warp)", Color::Cyan); - client.deploy("warp/hello-world"); + let client = + helpers::Services::new_docker("hello-world (warp)", "warp/hello-world", Color::Cyan); + client.deploy(); let request_text = client .get("hello")