From 7a8555c6ba87e98716912f9f54103912e94728a7 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sun, 7 May 2023 00:38:35 +0300 Subject: [PATCH] feat(hcl): simplify service spec in HCL config --- Cargo.lock | 40 +++++++++ Cargo.toml | 1 + README.md | 33 ++++--- examples/angular/Superfile.hcl | 42 +++++---- examples/deno-fresh/Superfile.hcl | 36 ++++---- examples/deno-fresh/deno.lock | 38 ++++---- examples/nginx-golang-mysql/Superfile.hcl | 80 ++++++++--------- examples/nginx-nodejs-redis/Superfile.hcl | 88 +++++++++---------- examples/react-nginx/Superfile.hcl | 34 ++++--- src/cmd/new.rs | 17 ++-- src/cmd/preview.rs | 4 + src/graphql/schema/control.rs | 61 ++++++++----- src/graphql/schema/logging.rs | 16 ++-- .../schema/objects/project_configuration.rs | 12 +-- src/server/control.rs | 65 +++++++++----- src/server/logging.rs | 8 +- src/server/project.rs | 2 +- src/superviseur/core.rs | 15 ++-- src/superviseur/drivers/exec/driver.rs | 1 - src/superviseur/drivers/flox/driver.rs | 15 +++- src/types/configuration.rs | 6 +- src/types/process.rs | 1 + src/types/service.rs | 1 + 23 files changed, 351 insertions(+), 265 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db05246..b8470f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1926,6 +1926,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchit" version = "0.7.0" @@ -2839,6 +2845,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" +[[package]] +name = "spinners" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab" +dependencies = [ + "lazy_static", + "maplit", + "strum", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -2857,6 +2874,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "superviseur" version = "0.1.0-alpha.5" @@ -2892,6 +2931,7 @@ dependencies = [ "serde_json", "sha256", "slab", + "spinners", "sysinfo", "tabled", "tokio", diff --git a/Cargo.toml b/Cargo.toml index e4995c4..6d05c2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ async-trait = "0.1.68" dyn-clone = "1.0.11" serde_json = "1.0.96" colored_json = "3.1.0" +spinners = "4.1.0" [build-dependencies] tonic-build = "0.8" diff --git a/README.md b/README.md index 32e5fee..23a2322 100644 --- a/README.md +++ b/README.md @@ -105,25 +105,22 @@ Start by initializing a new project. This will create a `Superfile.hcl` file in ```hcl project = "demo" -services = [ - { - "name" = "demo" - "type" = "exec" - "command" = "ping $GITHUB_DOMAIN" - "working_dir" = "/tmp" - "description" = "Ping Service Example" - "depends_on" = [] - "env" = { - "GITHUB_DOMAIN" = "github.com" - } - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "port" = 5060 - "stdout" = "/tmp/demo-stdout.log" - "stderr" = "/tmp/demo-stderr.log" + +service "demo" { + type = "exec" + command = "ping $GITHUB_DOMAIN" + working_dir = "/tmp" + description = "Ping Service Example" + depends_on = [] + env = { + "GITHUB_DOMAIN" = "github.com" } -] + autostart = true + autorestart = false + namespace = "demo_namespace" + stdout = "/tmp/demo-stdout.log" + stderr = "/tmp/demo-stderr.log" +} ``` ### Start the service diff --git a/examples/angular/Superfile.hcl b/examples/angular/Superfile.hcl index 9c6635b..4efc23f 100644 --- a/examples/angular/Superfile.hcl +++ b/examples/angular/Superfile.hcl @@ -1,24 +1,22 @@ project = "angular" -services = [ - { - "name" = "angular" - "type" = "exec" - "command" = "npm start" - "working_dir" = "./angular" - "description" = "Angular example" - "depends_on" = [] - "env" = {} - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/angular-stdout.log" - "stderr" = "/tmp/angular-stderr.log" - "port" = 4200 - flox = { - "environment" = ".#angular" - } - build = { - "command" = "npm install" - } + +service "angular" { + "type" = "exec" + "command" = "npm start" + "working_dir" = "./angular" + "description" = "Angular example" + "depends_on" = [] + "env" = {} + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/angular-stdout.log" + "stderr" = "/tmp/angular-stderr.log" + "port" = 4200 + flox = { + "environment" = ".#angular" } -] + build = { + "command" = "npm install" + } +} diff --git a/examples/deno-fresh/Superfile.hcl b/examples/deno-fresh/Superfile.hcl index 0a84858..6ab528e 100644 --- a/examples/deno-fresh/Superfile.hcl +++ b/examples/deno-fresh/Superfile.hcl @@ -1,21 +1,19 @@ project = "deno-fresh" -services = [ - { - "name" = "deno" - "type" = "exec" - "command" = "./dev.ts" - "working_dir" = "." - "description" = "Deno example app" - "depends_on" = [] - "env" = {} - "autostart" = true - "autorestart" = false - "namespace" = "deno_namespace" - "stdout" = "/tmp/deno-stdout.log" - "stderr" = "/tmp/deno-stderr.log" - "port" = 8000 - flox = { - "environment" = ".#deno-fresh" - } + +service "deno" { + type = "exec" + command = "./dev.ts" + working_dir = "." + description = "Deno example app" + depends_on = [] + env = {} + autostart = true + autorestart = false + namespace = "deno_namespace" + stdout = "/tmp/deno-stdout.log" + stderr = "/tmp/deno-stderr.log" + port = 8000 + flox = { + environment = ".#deno-fresh" } -] +} diff --git a/examples/deno-fresh/deno.lock b/examples/deno-fresh/deno.lock index fc878fc..e2d2411 100644 --- a/examples/deno-fresh/deno.lock +++ b/examples/deno-fresh/deno.lock @@ -102,26 +102,26 @@ "https://deno.land/x/ts_morph@16.0.0/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93", "https://deno.land/x/ts_morph@16.0.0/ts_morph.d.ts": "38668b0e3780282a56a805425494490b0045d1928bd040c47a94095749dab8c3", "https://deno.land/x/ts_morph@16.0.0/ts_morph.js": "9fc0f3d6a3997c2df023fabc4e529d2117d214ffd4fd04247ca2f56c4e9cd470", - "https://esm.sh/*preact-render-to-string@5.2.4": "ea3c2017616ad84afc9e1aa55e1d40d692d191dd0e232c4063c200a458740c71", - "https://esm.sh/preact@10.11.0": "7a2563b2c3ec030072836f2d2746ffda575fbddd9bb573ec8f9ebdcdefabf4fa", + "https://esm.sh/*preact-render-to-string@5.2.4": "5c965103e5039039a89c5de1f551a690aecd3918afe53b5d967ce5d96d939584", + "https://esm.sh/preact@10.11.0": "e888b244446037c56f1881173fb51d1f5fa7aae5599e6c5154619346a6a5094e", "https://esm.sh/preact@10.11.0/hooks": "2b8ec155eb8b87501663f074acff1d55a9114fa7d88f0b39da06c940af1ff736", "https://esm.sh/preact@10.11.0/jsx-runtime": "5c123264f19799ab243211132dded45f6d42d594b5c78dd585f947d07bf20eae", - "https://esm.sh/stable/preact@10.11.0/deno/hooks.js": "da007f66891314678a699e81043726a60ccc7981a8c65a6c5b2a83099dd4eb8c", - "https://esm.sh/stable/preact@10.11.0/deno/jsx-runtime.js": "dd51d3a9e2006fc844e355973253755ef3d869729d44746dc69a2a4c14771f78", - "https://esm.sh/stable/preact@10.11.0/deno/preact.mjs": "ad6f5591bcf99d1a3f92079b2215ee7d0b00aed264afde30b4290159142328dd", - "https://esm.sh/twind@0.16.17": "7cc696d414627c6b6c684a2a6d8ce37e0665aad6ed7363cea67464e8469675e3", - "https://esm.sh/twind@0.16.17/sheets": "7b0edb211c27ed5b0f28ea8c56dd8de5eee777c26ef062ba010d591b0df2cee1", - "https://esm.sh/v117/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", - "https://esm.sh/v117/preact@10.11.0/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd", - "https://esm.sh/v117/preact@10.11.0/src/index.d.ts": "1a5c331227be54be6515b0c92a469d352834fa413963ae84a39a05a3177111f6", - "https://esm.sh/v117/preact@10.11.0/src/jsx.d.ts": "c423715fd7992b2e1446fea11d2d04e8adbd66c1edca1ce5e85f90e0d26a2eb2", - "https://esm.sh/v119/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288", - "https://esm.sh/v119/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.mjs": "5ddbdb771669d535c808638acaa03e0866128c4e58462aa34de05a8997d6f635", - "https://esm.sh/v119/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", - "https://esm.sh/v119/style-vendorizer@2.2.3/deno/style-vendorizer.mjs": "fb725497dd9621a84f552f9b6a4f3df82af5989ff18c40e972de1bdf475c9765", - "https://esm.sh/v119/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", - "https://esm.sh/v119/twind@0.16.17/deno/twind.mjs": "ad9863faad5a13136a5e3449188648b7600860d5bd206d6778f3515e796e3455", - "https://esm.sh/v119/twind@0.16.17/sheets/sheets.d.ts": "9599f49d7214ac8c2c9b4286485ee033d01b742716f63fee110a8fc5301384f4", - "https://esm.sh/v119/twind@0.16.17/twind.d.ts": "96d3dae0fd2fdc7c826236533c2fc8c0eac3dd4982ca0a045cfe6ec61c1f557d" + "https://esm.sh/stable/preact@10.11.0/deno/hooks.js": "32a891b0e7abdfbf30dc7c274dbcbd46f2ea04d6637174451c50c41c12429805", + "https://esm.sh/stable/preact@10.11.0/deno/jsx-runtime.js": "0310ceb4c83643ee994baa3104675f08834300731a838ddecf5078853492e8d3", + "https://esm.sh/stable/preact@10.11.0/deno/preact.js": "08219fcb5b92ccdc709233a070ab22478c91521866c819aea679144847e4e87a", + "https://esm.sh/twind@0.16.17": "519a5a4d20ff4f797ddf747af70a5600b3581c50a6edf38a4d77e8d7b4caff74", + "https://esm.sh/twind@0.16.17/sheets": "5a2b275c294ceffb1b09f2cc367b386172b6ccaa14273dbde473865d4d3f6721", + "https://esm.sh/v111/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383", + "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.js": "7651121e4bb5ef2a48f840dba3f0c13087293ca2eb825aa23ffea3a87bd463b3", + "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", + "https://esm.sh/v111/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", + "https://esm.sh/v111/preact@10.11.0/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd", + "https://esm.sh/v111/preact@10.11.0/src/index.d.ts": "1a5c331227be54be6515b0c92a469d352834fa413963ae84a39a05a3177111f6", + "https://esm.sh/v111/preact@10.11.0/src/jsx.d.ts": "c423715fd7992b2e1446fea11d2d04e8adbd66c1edca1ce5e85f90e0d26a2eb2", + "https://esm.sh/v111/style-vendorizer@2.2.3/deno/style-vendorizer.js": "4823723c1cd5b34a60b4a1dfdf272a821d367fc472c23aeed6c3bcd7af5b7a7b", + "https://esm.sh/v111/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", + "https://esm.sh/v111/twind@0.16.17/deno/twind.js": "076dfc344b9507b3f5ee8a20d0b1e6eeb2ea3c6688649026ed0e13e4de158ff2", + "https://esm.sh/v111/twind@0.16.17/sheets/sheets.d.ts": "103ecfb19311b86eb5f7a4899f6a3b1192a84cfb73b6ff9a8b84d78b053e6704", + "https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f" } } diff --git a/examples/nginx-golang-mysql/Superfile.hcl b/examples/nginx-golang-mysql/Superfile.hcl index ea31d58..dbd967f 100644 --- a/examples/nginx-golang-mysql/Superfile.hcl +++ b/examples/nginx-golang-mysql/Superfile.hcl @@ -1,43 +1,41 @@ project = "nginx-golang-mysql" -services = [ - { - "name" = "go" - "type" = "exec" - "command" = "go run main.go" - "working_dir" = "./backend" - "description" = "Go Service Example" - "depends_on" = ["mysql"] - "env" = { } - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/go-stdout.log" - "stderr" = "/tmp/go-stderr.log" - flox = { - "environment" = ".#nginx-golang-mysql" - } - }, - { - "name" = "mysql" - "type" = "exec" - "command" = "mysqld --datadir=$MYSQL_DATADIR --pid-file=$MYSQL_PID_FILE --socket=$PWD/$MYSQL_UNIX_PORT" - "stop_command" = "mysqladmin -u root --socket=$PWD/$MYSQL_UNIX_PORT shutdown" - "working_dir" = "." - "description" = "MySQL Service Example" - "depends_on" = [] - "env" = { - MYSQL_HOME = "./.mysql" - MYSQL_DATADIR = "./.mysql/data" - MYSQL_PID_FILE = "./.mysql/./mysql.pid" - MYSQL_UNIX_PORT = "./.mysql/mysql.sock" - } - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/mysql-stdout.log" - "stderr" = "/tmp/mysql-stderr.log" - flox = { - "environment" = ".#nginx-golang-mysql" - } + +service "go" { + "type" = "exec" + "command" = "go run main.go" + "working_dir" = "./backend" + "description" = "Go Service Example" + "depends_on" = ["mysql"] + "env" = { } + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/go-stdout.log" + "stderr" = "/tmp/go-stderr.log" + flox = { + "environment" = ".#nginx-golang-mysql" } -] +} + +service "mysql" { + "type" = "exec" + "command" = "mysqld --datadir=$MYSQL_DATADIR --pid-file=$MYSQL_PID_FILE --socket=$PWD/$MYSQL_UNIX_PORT" + "stop_command" = "mysqladmin -u root --socket=$PWD/$MYSQL_UNIX_PORT shutdown" + "working_dir" = "." + "description" = "MySQL Service Example" + "depends_on" = [] + "env" = { + MYSQL_HOME = "./.mysql" + MYSQL_DATADIR = "./.mysql/data" + MYSQL_PID_FILE = "./.mysql/./mysql.pid" + MYSQL_UNIX_PORT = "./.mysql/mysql.sock" + } + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/mysql-stdout.log" + "stderr" = "/tmp/mysql-stderr.log" + flox = { + "environment" = ".#nginx-golang-mysql" + } +} diff --git a/examples/nginx-nodejs-redis/Superfile.hcl b/examples/nginx-nodejs-redis/Superfile.hcl index f0a194f..eb0fb3f 100644 --- a/examples/nginx-nodejs-redis/Superfile.hcl +++ b/examples/nginx-nodejs-redis/Superfile.hcl @@ -1,46 +1,44 @@ project = "nginx-nodejs-redis" -services = [ - { - "name" = "nodejs" - "type" = "exec" - "command" = "npm start" - "working_dir" = "./web" - "description" = "Ping Service Example" - "depends_on" = ["redis"] - "wait_for" = ["redis"] - "env" = { - REDIS_HOST = "localhost" - } - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/nodejs-stdout.log" - "stderr" = "/tmp/nodejs-stderr.log" - "port" = 5005 - flox = { - "environment" = ".#nginx-nodejs-redis" - } - build = { - "command" = "npm install" - } - }, - { - "name" = "redis" - "type" = "exec" - "command" = "redis-server" - "stop_command" = "redis-cli shutdown" - "working_dir" = "." - "description" = "Redis Service Example" - "depends_on" = [] - "env" = {} - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/redis-stdout.log" - "stderr" = "/tmp/redis-stderr.log" - "port" = 6379 - flox = { - "environment" = ".#nginx-nodejs-redis" - } - }, -] + +service "nodejs" { + "type" = "exec" + "command" = "npm start" + "working_dir" = "./web" + "description" = "Ping Service Example" + "depends_on" = ["redis"] + "wait_for" = ["redis"] + "env" = { + REDIS_HOST = "localhost" + } + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/nodejs-stdout.log" + "stderr" = "/tmp/nodejs-stderr.log" + "port" = 5005 + flox = { + "environment" = ".#nginx-nodejs-redis" + } + build = { + "command" = "npm install" + } +} + +service "redis" { + "type" = "exec" + "command" = "redis-server" + "stop_command" = "redis-cli shutdown" + "working_dir" = "." + "description" = "Redis Service Example" + "depends_on" = [] + "env" = {} + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/redis-stdout.log" + "stderr" = "/tmp/redis-stderr.log" + "port" = 6379 + flox = { + "environment" = ".#nginx-nodejs-redis" + } +} diff --git a/examples/react-nginx/Superfile.hcl b/examples/react-nginx/Superfile.hcl index 933c554..557e162 100644 --- a/examples/react-nginx/Superfile.hcl +++ b/examples/react-nginx/Superfile.hcl @@ -1,20 +1,18 @@ project = "react-nginx" -services = [ - { - "name" = "react" - "type" = "exec" - "command" = "npm start" - "working_dir" = "." - "description" = "React app" - "depends_on" = [] - "env" = { } - "autostart" = true - "autorestart" = false - "namespace" = "demo_namespace" - "stdout" = "/tmp/react-stdout.log" - "stderr" = "/tmp/react-stderr.log" - flox = { - "environment" = ".#react-nginx" - } + +service "react" { + "type" = "exec" + "command" = "npm start" + "working_dir" = "." + "description" = "React app" + "depends_on" = [] + "env" = { } + "autostart" = true + "autorestart" = false + "namespace" = "demo_namespace" + "stdout" = "/tmp/react-stdout.log" + "stderr" = "/tmp/react-stderr.log" + flox = { + "environment" = ".#react-nginx" } -] +} diff --git a/src/cmd/new.rs b/src/cmd/new.rs index e316410..979241a 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, io::Write}; +use indexmap::IndexMap; use owo_colors::OwoColorize; use crate::types::configuration::{ConfigFormat, ConfigurationData, Service}; @@ -8,10 +9,10 @@ pub fn execute_new(cfg_format: ConfigFormat) { let mut env = HashMap::new(); env.insert("GITHUB_DOMAIN".to_string(), "github.com".to_string()); - let config = ConfigurationData { - project: "demo".to_string(), - context: None, - services: vec![Service { + let mut services = IndexMap::new(); + services.insert( + String::from("demo"), + Service { id: None, name: "demo".to_string(), r#type: "exec".to_string(), @@ -32,7 +33,13 @@ pub fn execute_new(cfg_format: ConfigFormat) { wait_for: None, flox: None, build: None, - }], + }, + ); + + let config = ConfigurationData { + project: "demo".to_string(), + context: None, + services, }; let serialized = match cfg_format { ConfigFormat::HCL => hcl::to_string(&config).unwrap(), diff --git a/src/cmd/preview.rs b/src/cmd/preview.rs index 5debddb..3af1c3e 100644 --- a/src/cmd/preview.rs +++ b/src/cmd/preview.rs @@ -42,6 +42,10 @@ pub async fn execute_preview(name: &str) -> Result<(), Error> { Some(process) => { match process.state.as_str() { "Running" => { + if process.port == 0 { + println!("{} does not have any port assigned", process.name.bright_green()); + return Ok(()); + } open::that(format!("http://localhost:{}", process.port))?; println!("Previewing {} at http://localhost:{}", process.name.bright_green(), process.port); } diff --git a/src/graphql/schema/control.rs b/src/graphql/schema/control.rs index e9e4bb5..a3f1c69 100644 --- a/src/graphql/schema/control.rs +++ b/src/graphql/schema/control.rs @@ -7,6 +7,7 @@ use std::{ use async_graphql::{Context, Error, Object, Subscription, ID}; use futures_util::Stream; +use indexmap::IndexMap; use names::Generator; use tokio::sync::mpsc; @@ -98,7 +99,10 @@ impl ControlQuery { let config = config_map.get(&project_id).unwrap(); let services = config.services.clone(); - let mut services = services.iter().map(Service::from).collect::>(); + let mut services = services + .iter() + .map(|(_, x)| Service::from(x)) + .collect::>(); for service in services.iter_mut() { let process = processes @@ -184,10 +188,10 @@ impl ControlQuery { .find(|(p, _)| p.service_id.clone() == id.to_string()) { Some((process, _)) => { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or(Error::new("Service not found"))?; Ok(Service { @@ -196,10 +200,10 @@ impl ControlQuery { }) } None => { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or(Error::new("Service not found"))?; Ok(Service { @@ -230,7 +234,7 @@ impl ControlMutation { let id = generator.next().unwrap(); let config = ConfigurationData { project: name.clone(), - services: vec![], + services: IndexMap::new(), context: Some(context.clone()), }; @@ -280,7 +284,7 @@ impl ControlMutation { let config = config_map.get(&project_id).unwrap(); if id.is_none() { - for service in &config.services { + for (_, service) in &config.services { cmd_tx .send(SuperviseurCommand::Start( service.clone(), @@ -290,7 +294,10 @@ impl ControlMutation { } let services = config.services.clone(); - let services = services.iter().map(Service::from).collect::>(); + let services = services + .iter() + .map(|(_, x)| Service::from(x)) + .collect::>(); SimpleBroker::publish(AllServicesStarted { payload: services }); return Ok(Process { @@ -298,10 +305,10 @@ impl ControlMutation { }); } - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == id.as_ref().map(|x| x.to_string())) + .find(|(_, s)| s.id == id.as_ref().map(|x| x.to_string())) .ok_or(Error::new("Service not found"))?; cmd_tx.send(SuperviseurCommand::Start( @@ -345,7 +352,7 @@ impl ControlMutation { let config = config_map.get(&project_id).unwrap(); if id.is_none() { - for service in &config.services { + for (_, service) in &config.services { cmd_tx .send(SuperviseurCommand::Stop( service.clone(), @@ -354,17 +361,20 @@ impl ControlMutation { .unwrap(); } let services = config.services.clone(); - let services = services.iter().map(Service::from).collect::>(); + let services = services + .iter() + .map(|(_, x)| Service::from(x)) + .collect::>(); SimpleBroker::publish(AllServicesStopped { payload: services }); return Ok(Process { ..Default::default() }); } - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == id.as_ref().map(|x| x.to_string())) + .find(|(_, s)| s.id == id.as_ref().map(|x| x.to_string())) .ok_or(Error::new("Service not found"))?; cmd_tx.send(SuperviseurCommand::Stop( @@ -409,7 +419,7 @@ impl ControlMutation { let config = config_map.get(&project_id).unwrap(); if id.is_none() { - for service in &config.services { + for (_, service) in &config.services { cmd_tx .send(SuperviseurCommand::Restart( service.clone(), @@ -419,7 +429,10 @@ impl ControlMutation { } let services = config.services.clone(); - let services = services.iter().map(Service::from).collect::>(); + let services = services + .iter() + .map(|(_, x)| Service::from(x)) + .collect::>(); SimpleBroker::publish(AllServicesStarted { payload: services }); return Ok(Process { @@ -427,10 +440,10 @@ impl ControlMutation { }); } - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == id.as_ref().map(|x| x.to_string())) + .find(|(_, s)| s.id == id.as_ref().map(|x| x.to_string())) .ok_or(Error::new("Service not found"))?; cmd_tx.send(SuperviseurCommand::Restart( @@ -473,10 +486,10 @@ impl ControlMutation { let config = config_map.get_mut(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter_mut() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or(Error::new("Service not found"))?; service.env.insert(name, value); @@ -515,10 +528,10 @@ impl ControlMutation { let config = config_map.get_mut(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter_mut() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or(Error::new("Service not found"))?; service.env.remove(&name); @@ -559,10 +572,10 @@ impl ControlMutation { let config = config_map.get_mut(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter_mut() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or(Error::new("Service not found"))?; service.env.remove(&name); diff --git a/src/graphql/schema/logging.rs b/src/graphql/schema/logging.rs index fbee10e..9539d37 100644 --- a/src/graphql/schema/logging.rs +++ b/src/graphql/schema/logging.rs @@ -46,10 +46,10 @@ impl LoggingQuery { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or_else(|| Error::new("Service not found"))?; let log_file = File::open(&service.stdout).map_err(|e| Error::new(e.to_string()))?; @@ -82,10 +82,10 @@ impl LoggingQuery { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or_else(|| Error::new("Service not found"))?; let lines = read_lines(&service.stdout)?; @@ -118,10 +118,10 @@ impl LoggingSubscription { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or_else(|| Error::new("Service not found"))?; let log_file = File::open(&service.stdout).map_err(|e| Error::new(e.to_string()))?; @@ -177,10 +177,10 @@ impl LoggingSubscription { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.id == Some(id.to_string())) + .find(|(_, s)| s.id == Some(id.to_string())) .ok_or_else(|| Error::new("Service not found"))?; let log_file = File::open(&service.stdout).map_err(|e| Error::new(e.to_string()))?; diff --git a/src/graphql/schema/objects/project_configuration.rs b/src/graphql/schema/objects/project_configuration.rs index b7e8ebb..0ed3199 100644 --- a/src/graphql/schema/objects/project_configuration.rs +++ b/src/graphql/schema/objects/project_configuration.rs @@ -99,7 +99,7 @@ impl ProjectConfiguration { port: service.port, ..Default::default() }; - config.services.push(service); + config.services.insert(service.name.clone(), service); Ok(&self) } @@ -129,11 +129,11 @@ impl ProjectConfiguration { let mut services = services.into_iter(); // convert services dependencies to ids - for service in &mut config.services { + for (_, service) in &mut config.services { let mut dependencies = vec![]; for dependency in &service.depends_on { - match services.find(|s| s.name == *dependency) { - Some(service) => { + match services.find(|(name, _)| *name == *dependency) { + Some((_, service)) => { dependencies.push(service.id.clone().unwrap()); } None => { @@ -144,7 +144,7 @@ impl ProjectConfiguration { service.dependencies = dependencies; } - for service in services.into_iter() { + for (_, service) in services.into_iter() { cmd_tx.send(SuperviseurCommand::Load(service, config.project.clone()))?; } @@ -153,7 +153,7 @@ impl ProjectConfiguration { futures::executor::block_on_stream(SimpleBroker::::subscribe()).next(); let mut stdout = vec![]; - for service in &config.services { + for (_, service) in &config.services { // loop while the file does not exist while !Path::new(&service.stdout).exists() { sleep(Duration::from_secs(2)); diff --git a/src/server/control.rs b/src/server/control.rs index 02b63ea..738b48d 100644 --- a/src/server/control.rs +++ b/src/server/control.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Error; +use indexmap::IndexMap; use names::Generator; use tokio::sync::mpsc; use tonic::{Request, Response}; @@ -22,7 +23,7 @@ use crate::{ }, superviseur::core::{ProcessEvent, Superviseur, SuperviseurCommand}, types::{ - self, + self, configuration, configuration::ConfigurationData, process::{Process, State}, }, @@ -99,6 +100,17 @@ impl ControlService for Control { let mut config: ConfigurationData = hcl::from_str(&config).map_err(|e| tonic::Status::internal(e.to_string()))?; + // set the name of the services + config.services = config + .services + .iter() + .map(|(name, service)| { + let mut service = service.clone(); + service.name = name.clone(); + (name.clone(), service) + }) + .collect(); + // get directory of the config file config.context = Some(path.clone()); @@ -111,9 +123,13 @@ impl ControlService for Control { if !is_new_config { let old_config = config_map.get_mut(&project_id).unwrap(); // reuse the id of the services - for service in &mut config.services { - match old_config.services.iter().find(|s| s.name == service.name) { - Some(old_service) => { + for (service_name, service) in &mut config.services { + match old_config + .services + .iter() + .find(|(_, s)| s.name == *service_name) + { + Some((_, old_service)) => { service.id = old_service.id.clone(); service.working_dir = convert_dir_path_to_absolute_path( service.working_dir.as_str(), @@ -154,16 +170,16 @@ impl ControlService for Control { config.services = config .services .into_iter() - .map(|mut service| { + .map(|(key, mut service)| { service.id = Some(generator.next().unwrap()); service.working_dir = convert_dir_path_to_absolute_path( service.working_dir.as_str(), path.as_str(), ) .map(|x| x.to_string())?; - Ok(service) + Ok((key, service)) }) - .collect::, Error>>() + .collect::, Error>>() .map_err(|e| tonic::Status::internal(e.to_string()))?; // update the config @@ -172,7 +188,7 @@ impl ControlService for Control { let services = config.services.clone(); let project = config.project.clone(); - for service in services.into_iter() { + for (_, service) in services.into_iter() { match &service.watch_dir { Some(watch_dir) => { self.cmd_tx @@ -201,11 +217,11 @@ impl ControlService for Control { let mut services = services.into_iter(); // convert services dependencies to ids - for service in &mut config.services { + for (_, service) in &mut config.services { let mut dependencies = vec![]; for dependency in &service.depends_on { - match services.find(|s| s.name == *dependency) { - Some(service) => { + match services.find(|(name, _)| *name == *dependency) { + Some((_, service)) => { dependencies.push(service.id.clone().unwrap()); } None => { @@ -221,7 +237,7 @@ impl ControlService for Control { let services = config.services.clone(); - for service in services.into_iter() { + for (_, service) in services.into_iter() { self.cmd_tx .send(SuperviseurCommand::Load(service, config.project.clone())) .map_err(|e| tonic::Status::internal(e.to_string()))?; @@ -251,10 +267,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); if name.len() > 0 { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; self.cmd_tx @@ -292,10 +308,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); if name.len() > 0 { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; self.cmd_tx @@ -333,10 +349,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); if name.len() > 0 { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; self.cmd_tx @@ -373,10 +389,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; let processes = self.processes.lock().unwrap(); @@ -422,7 +438,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); let services = config.services.clone(); let mut list_response = ListResponse { - services: services.into_iter().map(Service::from).collect(), + services: services + .into_iter() + .map(|(_, x)| Service::from(x)) + .collect(), }; let processes = self.processes.lock().unwrap(); @@ -475,10 +494,10 @@ impl ControlService for Control { let config = config_map.get(&project_id).unwrap(); if name.len() > 0 { - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; service diff --git a/src/server/logging.rs b/src/server/logging.rs index 8f290e4..2cf146e 100644 --- a/src/server/logging.rs +++ b/src/server/logging.rs @@ -67,10 +67,10 @@ impl LoggingService for Logging { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; let log_file = @@ -136,10 +136,10 @@ impl LoggingService for Logging { let config = config_map.get(&project_id).unwrap(); - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == name) + .find(|(_, s)| s.name == name) .ok_or_else(|| tonic::Status::not_found("Service not found"))?; let log_file = diff --git a/src/server/project.rs b/src/server/project.rs index 18bfab1..f14807d 100644 --- a/src/server/project.rs +++ b/src/server/project.rs @@ -85,7 +85,7 @@ impl ProjectService for Project { services: x .services .iter() - .map(|service| ServiceProto::from(service.clone())) + .map(|(_, service)| ServiceProto::from(service.clone())) .collect(), ..Default::default() }); diff --git a/src/superviseur/core.rs b/src/superviseur/core.rs index 2da99d0..79dd4b9 100644 --- a/src/superviseur/core.rs +++ b/src/superviseur/core.rs @@ -151,10 +151,11 @@ impl SuperviseurInternal { let mut services = HashMap::new(); let mut graph = DependencyGraph::new(project.clone()); - for service in cfg.services.iter() { + for (key, mut service) in cfg.services.clone().into_iter() { + service.name = key.clone(); services.insert( graph.add_vertex( - service, + &service, self.processes.clone(), self.childs.clone(), self.event_tx.clone(), @@ -164,11 +165,11 @@ impl SuperviseurInternal { } // Add edges to the graph - for service in cfg.services.iter() { + for (service_name, service) in cfg.services.iter() { for dep in service.depends_on.iter() { let from = services .iter() - .find(|(_, s)| s.name == service.name) + .find(|(_, s)| s.name == *service_name) .map(|(from, _)| *from) .ok_or(anyhow!("service not found"))?; services @@ -543,10 +544,10 @@ impl SuperviseurInternal { .find(|(_, k)| k == &project) .map(|(c, _)| c) .ok_or(anyhow::anyhow!("Config not found"))?; - let service = config + let (_, service) = config .services .iter() - .find(|s| s.name == service_name) + .find(|(_, s)| s.name == service_name) .ok_or(anyhow::anyhow!("Service not found"))?; Ok(service.clone()) } @@ -564,7 +565,7 @@ impl SuperviseurInternal { let services = config .services .iter() - .map(|s| schema::objects::service::Service::from(s)) + .map(|(_, s)| schema::objects::service::Service::from(s)) .collect(); Ok(services) } diff --git a/src/superviseur/drivers/exec/driver.rs b/src/superviseur/drivers/exec/driver.rs index 8b9acea..0171cda 100644 --- a/src/superviseur/drivers/exec/driver.rs +++ b/src/superviseur/drivers/exec/driver.rs @@ -113,7 +113,6 @@ impl DriverPlugin for Driver { .unwrap(); let mut processes = self.processes.lock().unwrap(); - let mut process = &mut processes .iter_mut() .find(|(p, key)| p.name == self.service.name && key == &project) diff --git a/src/superviseur/drivers/flox/driver.rs b/src/superviseur/drivers/flox/driver.rs index 2c9ea13..306543d 100644 --- a/src/superviseur/drivers/flox/driver.rs +++ b/src/superviseur/drivers/flox/driver.rs @@ -8,6 +8,7 @@ use std::{ use anyhow::Error; use owo_colors::OwoColorize; +use spinners::{Spinner, Spinners}; use tokio::sync::mpsc; use crate::{ @@ -75,13 +76,14 @@ impl Driver { "flox print-dev-env -A {}", cfg.environment.replace(".#", "") ); - let mut child = std::process::Command::new("sh") + let child = std::process::Command::new("sh") .arg("-c") .arg(command) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn()?; - child.wait()?; + + child.wait_with_output()?; Ok(()) } @@ -125,7 +127,14 @@ impl Driver { impl DriverPlugin for Driver { fn start(&self, project: String) -> Result<(), Error> { let cfg = self.service.flox.as_ref().unwrap(); - self.setup_flox_env(cfg)?; + let message = format!("Setup flox environment {} ...", cfg.environment); + let mut sp = Spinner::new(Spinners::Line, message.into()); + if self.setup_flox_env(cfg).is_err() { + println!("There is an error with flox env"); + return Ok(()); + } + sp.stop(); + println!("\nSetup flox env done !"); let command = format!( "flox activate -e {} -- {}", diff --git a/src/types/configuration.rs b/src/types/configuration.rs index 878e39c..d9b6166 100644 --- a/src/types/configuration.rs +++ b/src/types/configuration.rs @@ -1,3 +1,4 @@ +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -10,6 +11,7 @@ pub enum ConfigFormat { pub struct Service { #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, + #[serde(skip_serializing, skip_deserializing)] pub name: String, pub r#type: String, // docker, podman, exec, wasm pub command: String, @@ -41,8 +43,10 @@ pub struct Service { #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ConfigurationData { pub project: String, + #[serde(skip_serializing, skip_deserializing)] pub context: Option, - pub services: Vec, + #[serde(rename = "service", serialize_with = "hcl::ser::labeled_block")] + pub services: IndexMap, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/src/types/process.rs b/src/types/process.rs index 4ce9654..972d2e6 100644 --- a/src/types/process.rs +++ b/src/types/process.rs @@ -159,6 +159,7 @@ pub fn format_duration(duration: Duration) -> String { fn display_port(port: &Option) -> String { match port { + Some(0) => "-".to_string(), Some(port) => port.clone().to_string(), None => "-".to_string(), } diff --git a/src/types/service.rs b/src/types/service.rs index b30ee3d..de9c351 100644 --- a/src/types/service.rs +++ b/src/types/service.rs @@ -30,6 +30,7 @@ fn display_command(command: &str) -> String { fn display_port(port: &Option) -> String { match port { + Some(0) => "-".to_string(), Some(port) => port.clone().to_string(), None => "-".to_string(), }