diff --git a/.github/workflows/ci-generators.yml b/.github/workflows/ci-generators.yml index 8fb97f49b..4fd8ccf06 100644 --- a/.github/workflows/ci-generators.yml +++ b/.github/workflows/ci-generators.yml @@ -97,3 +97,11 @@ jobs: env: REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} DATABASE_URL: postgres://postgress:postgress@localhost:5432/postgress_test + + - name: docker deployment + run: cargo run -- generate deployment + working-directory: ./examples/demo + env: + LOCO_DEPLOYMENT_KIND: docker + REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} + DATABASE_URL: postgres://postgress:postgress@localhost:5432/postgress_test diff --git a/Cargo.lock b/Cargo.lock index 962d78232..98a563b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "cruet" version = "0.13.3" @@ -1718,6 +1743,7 @@ dependencies = [ "lettre", "rand", "regex", + "requestty", "rrgen", "rstest", "rusty-sidekiq", @@ -1817,6 +1843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -2455,6 +2482,32 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "requestty" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa883a1f3e288e65187f653e6ba2e84fdf810fe02f4c8074f9c723d1aa26e2ae" +dependencies = [ + "requestty-ui", + "shell-words", + "smallvec", + "tempfile", + "winsplit", +] + +[[package]] +name = "requestty-ui" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7549bab39cf982b629b68e7ec191a5574e85086e95c0ebe514c02d3b42ffe225" +dependencies = [ + "bitflags 1.3.2", + "crossterm", + "once_cell", + "textwrap", + "unicode-segmentation", +] + [[package]] name = "reserve-port" version = "2.0.0" @@ -3019,6 +3072,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3112,6 +3192,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.5" @@ -3517,6 +3603,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -3877,6 +3974,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -3892,6 +3995,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -4281,6 +4390,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsplit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index ae08f27b7..dfaeca9a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ cargo_metadata = "0.18.1" cfg-if = "1" uuid = { version = "1.6", features = ["v4"] } +requestty = "0.5.0" [dependencies.sea-orm-migration] diff --git a/docs-site/content/docs/getting-started/deployment.md b/docs-site/content/docs/getting-started/deployment.md index 5154e96a7..695bf8749 100644 --- a/docs-site/content/docs/getting-started/deployment.md +++ b/docs-site/content/docs/getting-started/deployment.md @@ -30,15 +30,14 @@ We took special care that **all of your work** is embbedded in a **single** bina There are a few configuration sections that are important to review and set accordingly when deploying to production: -* Logger: +- Logger: ```yaml logger: - level: + level: ``` - -* Server: +- Server: ```yaml server: @@ -48,7 +47,7 @@ server: host: http://localhost ``` -* Database: +- Database: ```yaml database: @@ -56,7 +55,7 @@ database: uri: postgres://loco:loco@localhost:5432/loco_app ``` -* Mailer: +- Mailer: ```yaml mailer: @@ -68,7 +67,7 @@ mailer: host: localhost ``` -* Redis: +- Redis: ``` redis: @@ -76,7 +75,7 @@ redis: uri: redis://127.0.0.1/ ``` -* JWT secret: +- JWT secret: ```yaml auth: @@ -85,3 +84,19 @@ auth: # Secret key for token generation and verification secret: ... ``` + +## Generate + +Loco offers a deployment template enabling the creation of a deployment infrastructure. + +Presently, we exclusively support Docker deployment, but stay tuned for future updates! 🙂 + +```sh +cargo loco generate deployment +? ❯ Choose your deployment › +❯ Docker +.. +✔ ❯ Choose your deployment · Docker +skipped (exists): "dockerfile" +added: ".dockerignore" +``` diff --git a/examples/demo/Cargo.lock b/examples/demo/Cargo.lock index 88afbaa01..1d7dcc936 100644 --- a/examples/demo/Cargo.lock +++ b/examples/demo/Cargo.lock @@ -989,6 +989,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "cruet" version = "0.13.3" @@ -2073,6 +2098,7 @@ dependencies = [ "lettre", "rand", "regex", + "requestty", "rrgen", "rusty-sidekiq", "sea-orm", @@ -2183,6 +2209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -2901,6 +2928,32 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "requestty" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa883a1f3e288e65187f653e6ba2e84fdf810fe02f4c8074f9c723d1aa26e2ae" +dependencies = [ + "requestty-ui", + "shell-words", + "smallvec", + "tempfile", + "winsplit", +] + +[[package]] +name = "requestty-ui" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7549bab39cf982b629b68e7ec191a5574e85086e95c0ebe514c02d3b42ffe225" +dependencies = [ + "bitflags 1.3.2", + "crossterm", + "once_cell", + "textwrap", + "unicode-segmentation", +] + [[package]] name = "reserve-port" version = "2.0.0" @@ -3513,12 +3566,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3612,6 +3692,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "snapbox" version = "0.4.14" @@ -4058,6 +4144,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -4439,6 +4536,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -4454,6 +4557,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -4952,6 +5061,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsplit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" + [[package]] name = "wyz" version = "0.5.1" diff --git a/examples/demo/tests/cmd/cli.trycmd b/examples/demo/tests/cmd/cli.trycmd index 354f7a53e..fa987f607 100644 --- a/examples/demo/tests/cmd/cli.trycmd +++ b/examples/demo/tests/cmd/cli.trycmd @@ -56,6 +56,7 @@ Commands: task Generate a Task based on the given name worker Generate worker mailer Generate mailer + deployment Generate a deployment infrastructure help Print this message or the help of the given subcommand(s) Options: diff --git a/src/cli.rs b/src/cli.rs index 2c6981ff7..83a6ab4a9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -132,6 +132,8 @@ enum ComponentArg { /// Name of the thing to generate name: String, }, + /// Generate a deployment infrastructure + Deployment {}, } impl From for Component { @@ -145,6 +147,7 @@ impl From for Component { ComponentArg::Task { name } => Self::Task { name }, ComponentArg::Worker { name } => Self::Worker { name }, ComponentArg::Mailer { name } => Self::Mailer { name }, + ComponentArg::Deployment {} => Self::Deployment {}, } } } diff --git a/src/gen/mod.rs b/src/gen/mod.rs index bf2fef989..c8d20d12b 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -5,8 +5,8 @@ use serde_json::json; mod model; #[cfg(feature = "with-db")] mod scaffold; - -use crate::{app::Hooks, Result}; +use crate::{app::Hooks, errors, Result}; +use std::str::FromStr; const CONTROLLER_T: &str = include_str!("templates/controller.t"); const CONTROLLER_TEST_T: &str = include_str!("templates/request_test.t"); @@ -22,6 +22,26 @@ const TASK_TEST_T: &str = include_str!("templates/task_test.t"); const WORKER_T: &str = include_str!("templates/worker.t"); const WORKER_TEST_T: &str = include_str!("templates/worker_test.t"); +const DEPLOYMENT_DOCKER_T: &str = include_str!("templates/deployment_docker.t"); +const DEPLOYMENT_DOCKER_IGNORE_T: &str = include_str!("templates/deployment_docker_ignore.t"); + +const DEPLOYMENT_OPTIONS: &[(&str, DeploymentKind)] = &[("Docker", DeploymentKind::Docker)]; + +#[derive(Debug, Clone)] +pub enum DeploymentKind { + Docker, +} +impl FromStr for DeploymentKind { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + match s.to_lowercase().as_str() { + "docker" => Ok(Self::Docker), + _ => Err(()), + } + } +} + pub enum Component { #[cfg(feature = "with-db")] Model { @@ -55,6 +75,7 @@ pub enum Component { /// Name of the thing to generate name: String, }, + Deployment {}, } pub fn generate(component: Component) -> Result<()> { @@ -93,6 +114,21 @@ pub fn generate(component: Component) -> Result<()> { rrgen.generate(MAILER_TEXT_T, &vars)?; rrgen.generate(MAILER_HTML_T, &vars)?; } + Component::Deployment {} => { + let deployment_kind = match std::env::var("LOCO_DEPLOYMENT_KIND") { + Ok(kind) => kind.parse::().map_err(|_e| { + errors::Error::Message(format!("deployment {kind} not supported")) + })?, + Err(_err) => prompt_deployment_selection().map_err(Box::from)?, + }; + let vars = json!({ "pkg_name": H::app_name()}); + match deployment_kind { + DeploymentKind::Docker => { + rrgen.generate(DEPLOYMENT_DOCKER_T, &vars)?; + rrgen.generate(DEPLOYMENT_DOCKER_IGNORE_T, &vars)?; + } + } + } } Ok(()) } @@ -109,3 +145,20 @@ fn collect_messages(results: Vec) -> String { } messages } + +fn prompt_deployment_selection() -> eyre::Result { + let options: Vec = DEPLOYMENT_OPTIONS.iter().map(|t| t.0.to_string()).collect(); + + let selection_options = requestty::Question::select("deployment") + .message("❯ Choose your deployment") + .choices(&options) + .build(); + + let answer = requestty::prompt_one(selection_options)?; + + let selection = answer + .as_list_item() + .ok_or_else(|| eyre::eyre!("deployment selection it empty"))?; + + Ok(DEPLOYMENT_OPTIONS[selection.index].1.clone()) +} diff --git a/src/gen/templates/deployment_docker.t b/src/gen/templates/deployment_docker.t new file mode 100644 index 000000000..ed7351aea --- /dev/null +++ b/src/gen/templates/deployment_docker.t @@ -0,0 +1,13 @@ +to: "dockerfile" +skip_exists: true +message: "Dockerfile generated successfully." +--- +FROM rust:1.74-slim + +WORKDIR /usr/src/ + +COPY . . + +RUN cargo build --release + +ENTRYPOINT ["./target/release/{{pkg_name}}"] \ No newline at end of file diff --git a/src/gen/templates/deployment_docker_ignore.t b/src/gen/templates/deployment_docker_ignore.t new file mode 100644 index 000000000..a8b5b1273 --- /dev/null +++ b/src/gen/templates/deployment_docker_ignore.t @@ -0,0 +1,9 @@ +to: ".dockerignore" +skip_exists: true +message: "Dockerignore generated successfully." +--- +target +dockerfile +.dockerignore +.git +.gitignore