From 1dd107eddac19c8955b47d749b01edf01919b72f Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 14:34:42 +0200 Subject: [PATCH 1/5] dctypes lib added, ports converted --- Cargo.lock | 124 +++++- Cargo.toml | 12 + custom-stack-payload-4.json | 2 +- files/docker-compose.yml | 20 + src/helpers/mod.rs | 1 + src/helpers/stack/builder.rs | 207 +++++++++ src/helpers/stack/dctypes.rs | 842 +++++++++++++++++++++++++++++++++++ src/helpers/stack/mod.rs | 2 + src/models/stack.rs | 2 +- src/routes/stack/add.rs | 52 ++- src/routes/stack/get.rs | 1 + src/startup.rs | 1 + 12 files changed, 1259 insertions(+), 7 deletions(-) create mode 100644 files/docker-compose.yml create mode 100644 src/helpers/stack/builder.rs create mode 100644 src/helpers/stack/dctypes.rs create mode 100644 src/helpers/stack/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 21202d53..68ef0eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,6 +562,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "deranged" version = "0.3.9" @@ -571,6 +606,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -645,6 +711,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.5" @@ -803,6 +875,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.21" @@ -815,7 +893,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -983,6 +1061,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1004,6 +1088,17 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + [[package]] name = "instant" version = "0.1.12" @@ -1886,7 +1981,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0adc7a19d45e581abc6d169c865a0b14b84bb43a9e966d1cca4d733e70f7f35a" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itertools 0.10.5", "num-traits", "once_cell", @@ -1924,6 +2019,19 @@ dependencies = [ "regex", ] +[[package]] +name = "serde_yaml" +version = "0.9.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +dependencies = [ + "indexmap 2.1.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2059,7 +2167,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "log", @@ -2129,6 +2237,9 @@ dependencies = [ "actix-web-httpauth", "chrono", "config", + "derive_builder", + "glob", + "indexmap 2.1.0", "rand", "regex", "reqwest", @@ -2136,6 +2247,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_valid", + "serde_yaml", "sqlx", "thiserror", "tokio", @@ -2545,6 +2657,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 47cbb107..581f4d5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,11 @@ tracing-actix-web = "0.7.7" regex = "1.10.2" rand = "0.8.5" +# dctypes +derive_builder = "0.12.0" +indexmap = { version = "2.0.0", features = ["serde"], optional = true } +serde_yaml = "0.9" + [dependencies.sqlx] version = "0.6.3" features = [ @@ -45,3 +50,10 @@ features = [ "json", "offline" ] + +[features] +default = ["indexmap"] +indexmap = ["dep:indexmap"] + +[dev-dependencies] +glob = "0.3" \ No newline at end of file diff --git a/custom-stack-payload-4.json b/custom-stack-payload-4.json index 4f0ce614..07ee1569 100644 --- a/custom-stack-payload-4.json +++ b/custom-stack-payload-4.json @@ -1 +1 @@ -{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/files/docker-compose.yml b/files/docker-compose.yml new file mode 100644 index 00000000..c6e60d64 --- /dev/null +++ b/files/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' +services: + smarty-bot: + image: trydirect/smarty-bot:latest + ports: + - target: 8000 + published: 8000 + restart: always + pgrst: + image: pgrst + ports: + - target: 9999 + published: 9999 + restart: always + portainer_ce_feature: + image: portainer-ce-feature + ports: + - target: 9000 + published: 9000 + restart: always diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 3500bbd3..823b2b1c 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,5 +1,6 @@ pub mod client; pub(crate) mod json; pub mod serialize_datetime; +pub(crate) mod stack; pub use json::*; diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs new file mode 100644 index 00000000..023ee964 --- /dev/null +++ b/src/helpers/stack/builder.rs @@ -0,0 +1,207 @@ +use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, SingleService}; +use serde_yaml; +use crate::forms; +use crate::forms::{StackForm, Web, Feature}; +use crate::models::stack::Stack; + +#[derive(Clone, Debug)] +struct Config { + +} + +impl Default for Config { + fn default() -> Self { + Config {} + } +} + +/// A builder for constructing docker compose. +#[derive(Clone, Debug)] +pub struct DcBuilder { + config: Config, + stack: Stack +} + +impl TryInto> for Web { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +impl TryInto> for &Feature { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +impl TryInto> for &forms::stack::Service { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +fn convert_shared_ports(ports: Vec) -> Result, String> { + let mut _ports: Vec = vec![]; + for p in ports { + let port = p.parse::().map_err(|e| e.to_string())?; + _ports.push(Port { + target: port, + host_ip: None, + published: Some(PublishedPort::Single(port)), + protocol: None, + mode: None, + }); + } + Ok(_ports) +} + +impl DcBuilder { + + pub fn new() -> Self { + DcBuilder { + config: Config::default(), + stack: Stack { + id: 0, + stack_id: Default::default(), + user_id: "".to_string(), + name: "".to_string(), + body: Default::default(), + created_at: Default::default(), + updated_at: Default::default(), + }, + } + } + + // pub fn add_ports(&self, ports: &Vec) -> Vec { + // // @todo re-factor using TryInto or TryFrom + // + // let mut _ports:Vec = vec![]; + // for p in ports { + // let port = p.parse::().unwrap(); + // _ports.push( + // Port { + // target: port, + // host_ip: None, + // published: Some(PublishedPort::Single(port)), + // protocol: None, + // mode: None, + // } + // ); + // } + // _ports + // } + + pub fn build(&self, stack:Stack) -> Option { + + tracing::debug!("Start build docker compose from {:?}", stack.body); + let _stack = serde_json::from_value::(stack.body); + let mut services = indexmap::IndexMap::new(); + match _stack { + Ok(apps) => { + println!("stack item {:?}", apps.custom.web); + + for app in apps.custom.web { + // println!("app name {:?}", app.name); + let tag = "latest"; + let img= format!("{}/{}:{}",app.dockerhub_user, app.dockerhub_name, tag); + let code = app.code.clone().to_owned(); + let mut service = Service { + image: Some(img.to_string()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + + if let Some(srvs) = &apps.custom.service { + + if !srvs.is_empty() { + + for app in srvs { + let code = app.code.to_owned(); + let tag = "latest"; + + let mut service = Service { + image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + } + } + if let Some(features) = &apps.custom.feature { + + if !features.is_empty() { + + for app in features { + let code = app.code.to_owned(); + let mut service = Service { + image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + } + } + } + Err(e) => { + () + } + } + + let compose_content = Compose { + version: Some("3.8".to_string()), + services: { + Services(services) + }, + ..Default::default() + }; + + let target_file = std::path::Path::new("./files/docker-compose.yml"); + // serialize to string + let serialized = match serde_yaml::to_string(&compose_content) { + Ok(s) => s, + Err(e) => panic!("Failed to serialize docker-compose file: {}", e), + }; + // serialize to file + std::fs::write(target_file, serialized).unwrap(); + + Some(compose_content) + } +} diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes.rs new file mode 100644 index 00000000..931deab1 --- /dev/null +++ b/src/helpers/stack/dctypes.rs @@ -0,0 +1,842 @@ +use derive_builder::*; +#[cfg(feature = "indexmap")] +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_yaml::Value; +#[cfg(not(feature = "indexmap"))] +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt; +use std::str::FromStr; + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ComposeFile { + V2Plus(Compose), + #[cfg(feature = "indexmap")] + V1(IndexMap), + #[cfg(not(feature = "indexmap"))] + V1(HashMap), + Single(SingleService), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct SingleService { + pub service: Service, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct Compose { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(default, skip_serializing_if = "Services::is_empty")] + pub services: Services, + #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")] + pub volumes: TopLevelVolumes, + #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")] + pub networks: ComposeNetworks, + #[serde(skip_serializing_if = "Option::is_none")] + pub service: Option, + #[cfg(feature = "indexmap")] + #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")] + pub extensions: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + pub extensions: HashMap, +} + +impl Compose { + pub fn new() -> Self { + Default::default() + } +} + +#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)] +#[builder(setter(into), default)] +pub struct Service { + #[serde(skip_serializing_if = "Option::is_none")] + pub hostname: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub privileged: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub healthcheck: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deploy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub container_name: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "build")] + pub build_: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pid: Option, + #[serde(default, skip_serializing_if = "Ports::is_empty")] + pub ports: Ports, + #[serde(default, skip_serializing_if = "Environment::is_empty")] + pub environment: Environment, + #[serde(skip_serializing_if = "Option::is_none")] + pub network_mode: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub devices: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub restart: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub tmpfs: Option, + #[serde(default, skip_serializing_if = "Ulimits::is_empty")] + pub ulimits: Ulimits, + #[serde(default, skip_serializing_if = "Volumes::is_empty")] + pub volumes: Volumes, + #[serde(default, skip_serializing_if = "Networks::is_empty")] + pub networks: Networks, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub cap_add: Vec, + #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")] + pub depends_on: DependsOnOptions, + #[serde(skip_serializing_if = "Option::is_none")] + pub command: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub entrypoint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub env_file: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_grace_period: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub profiles: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub links: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dns: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipc: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub net: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_signal: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub working_dir: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub expose: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub volumes_from: Vec, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub extends: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub extends: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub logging: Option, + #[serde(default, skip_serializing_if = "is_zero")] + pub scale: i64, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub init: bool, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub stdin_open: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub shm_size: Option, + #[cfg(feature = "indexmap")] + #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")] + pub extensions: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + pub extensions: HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extra_hosts: Vec, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub tty: bool, + #[serde(default, skip_serializing_if = "SysCtls::is_empty")] + pub sysctls: SysCtls, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub security_opt: Vec, +} + +impl Service { + pub fn image(&self) -> &str { + self.image.as_deref().unwrap_or_default() + } + + pub fn network_mode(&self) -> &str { + self.network_mode.as_deref().unwrap_or_default() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum EnvFile { + Simple(String), + List(Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum DependsOnOptions { + Simple(Vec), + #[cfg(feature = "indexmap")] + Conditional(IndexMap), + #[cfg(not(feature = "indexmap"))] + Conditional(HashMap), +} + +impl Default for DependsOnOptions { + fn default() -> Self { + Self::Simple(Vec::new()) + } +} + +impl DependsOnOptions { + pub fn is_empty(&self) -> bool { + match self { + Self::Simple(v) => v.is_empty(), + Self::Conditional(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +pub struct DependsCondition { + pub condition: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct LoggingParameters { + pub driver: String, + #[cfg(feature = "indexmap")] + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option>, + #[cfg(not(feature = "indexmap"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Ports { + Short(Vec), + Long(Vec), +} + +impl Default for Ports { + fn default() -> Self { + Self::Short(Vec::default()) + } +} + +impl Ports { + pub fn is_empty(&self) -> bool { + match self { + Self::Short(v) => v.is_empty(), + Self::Long(v) => v.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Port { + pub target: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_ip: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub published: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub protocol: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum PublishedPort { + Single(u16), + Range(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Environment { + List(Vec), + #[cfg(feature = "indexmap")] + KvPair(IndexMap>), + #[cfg(not(feature = "indexmap"))] + KvPair(HashMap>), +} + +impl Default for Environment { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl Environment { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::KvPair(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)] +#[serde(try_from = "String")] +pub struct Extension(String); + +impl FromStr for Extension { + type Err = ExtensionParseError; + + fn from_str(s: &str) -> Result { + let owned = s.to_owned(); + Extension::try_from(owned) + } +} + +impl TryFrom for Extension { + type Error = ExtensionParseError; + + fn try_from(s: String) -> Result { + if s.starts_with("x-") { + Ok(Self(s)) + } else { + Err(ExtensionParseError(s)) + } + } +} + +/// The result of a failed TryFrom conversion for [`Extension`] +/// +/// Contains the string that was being converted +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ExtensionParseError(pub String); + +impl fmt::Display for ExtensionParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0) + } +} + +impl std::error::Error for ExtensionParseError {} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct Services(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct Services(pub HashMap>); + +impl Services { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Labels { + List(Vec), + #[cfg(feature = "indexmap")] + Map(IndexMap), + #[cfg(not(feature = "indexmap"))] + Map(HashMap), +} + +impl Default for Labels { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl Labels { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::Map(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Tmpfs { + Simple(String), + List(Vec), +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Ulimits(pub IndexMap); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Ulimits(pub HashMap); + +impl Ulimits { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Ulimit { + Single(i64), + SoftHard { soft: i64, hard: i64 }, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Networks { + Simple(Vec), + Advanced(AdvancedNetworks), +} + +impl Default for Networks { + fn default() -> Self { + Self::Simple(Vec::new()) + } +} + +impl Networks { + pub fn is_empty(&self) -> bool { + match self { + Self::Simple(n) => n.is_empty(), + Self::Advanced(n) => n.0.is_empty(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum BuildStep { + Simple(String), + Advanced(AdvancedBuildStep), +} + +#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)] +#[serde(deny_unknown_fields)] +#[builder(setter(into), default)] +pub struct AdvancedBuildStep { + pub context: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dockerfile: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shm_size: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub target: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub cache_from: Vec, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, +} + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum BuildArgs { + Simple(String), + List(Vec), + #[cfg(feature = "indexmap")] + KvPair(IndexMap), + #[cfg(not(feature = "indexmap"))] + KvPair(HashMap), +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct AdvancedNetworks(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct AdvancedNetworks(pub HashMap>); + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct AdvancedNetworkSettings { + #[serde(skip_serializing_if = "Option::is_none")] + pub ipv4_address: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipv6_address: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub aliases: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum SysCtls { + List(Vec), + #[cfg(feature = "indexmap")] + Map(IndexMap>), + #[cfg(not(feature = "indexmap"))] + Map(HashMap>), +} + +impl Default for SysCtls { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl SysCtls { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::Map(m) => m.is_empty(), + } + } +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct TopLevelVolumes(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct TopLevelVolumes(pub HashMap>); + +impl TopLevelVolumes { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeVolume { + #[serde(skip_serializing_if = "Option::is_none")] + pub driver: Option, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub driver_opts: IndexMap>, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub driver_opts: HashMap>, + #[serde(skip_serializing_if = "Option::is_none")] + pub external: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ExternalVolume { + Bool(bool), + Name { name: String }, +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeNetworks(pub IndexMap>); + +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeNetworks(pub HashMap>); + +impl ComposeNetworks { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum ComposeNetwork { + Detailed(ComposeNetworkSettingDetails), + Bool(bool), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct ComposeNetworkSettingDetails { + pub name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct ExternalNetworkSettingBool(bool); + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct NetworkSettings { + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub attachable: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub driver: Option, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub driver_opts: IndexMap>, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub driver_opts: HashMap>, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub enable_ipv6: bool, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub internal: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub external: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipam: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct Ipam { + #[serde(skip_serializing_if = "Option::is_none")] + pub driver: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub config: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct IpamConfig { + pub subnet: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub gateway: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct Deploy { + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub replicas: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub update_config: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub resources: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub restart_policy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub placement: Option, +} + +fn is_zero(val: &i64) -> bool { + *val == 0 +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct Healthcheck { + #[serde(skip_serializing_if = "Option::is_none")] + pub test: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub interval: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "is_zero")] + pub retries: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_period: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub disable: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum HealthcheckTest { + Single(String), + Multiple(Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct Limits { + #[serde(skip_serializing_if = "Option::is_none")] + pub cpus: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct Placement { + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub constraints: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub preferences: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct Preferences { + pub spread: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct Resources { + pub limits: Option, + pub reservations: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct RestartPolicy { + #[serde(skip_serializing_if = "Option::is_none")] + pub condition: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_attempts: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub window: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct UpdateConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub failure_action: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub monitor: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_failure_ratio: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum Volumes { + Simple(Vec), + Advanced(Vec), +} + +impl Default for Volumes { + fn default() -> Self { + Self::Simple(Vec::new()) + } +} + +impl Volumes { + pub fn is_empty(&self) -> bool { + match self { + Self::Simple(v) => v.is_empty(), + Self::Advanced(v) => v.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct AdvancedVolumes { + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + pub target: String, + #[serde(rename = "type")] + pub _type: String, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub read_only: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bind: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub volume: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tmpfs: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct Bind { + pub propagation: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct Volume { + pub nocopy: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] +#[serde(deny_unknown_fields)] +pub struct TmpfsSettings { + pub size: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum Command { + Simple(String), + Args(Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum Entrypoint { + Simple(String), + List(Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)] +#[serde(untagged)] +pub enum SingleValue { + String(String), + Bool(bool), + Unsigned(u64), + Signed(i64), + Float(f64), +} + +impl fmt::Display for SingleValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::String(s) => f.write_str(s), + Self::Bool(b) => write!(f, "{b}"), + Self::Unsigned(u) => write!(f, "{u}"), + Self::Signed(i) => write!(f, "{i}"), + Self::Float(fl) => write!(f, "{fl}"), + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum MapOrEmpty { + Map(T), + Empty, +} + +impl Default for MapOrEmpty { + fn default() -> Self { + Self::Empty + } +} + +impl From> for Option { + fn from(value: MapOrEmpty) -> Self { + match value { + MapOrEmpty::Map(t) => Some(t), + MapOrEmpty::Empty => None, + } + } +} + +impl Serialize for MapOrEmpty + where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Map(t) => t.serialize(serializer), + Self::Empty => { + use serde::ser::SerializeMap; + serializer.serialize_map(None)?.end() + } + } + } +} \ No newline at end of file diff --git a/src/helpers/stack/mod.rs b/src/helpers/stack/mod.rs new file mode 100644 index 00000000..7da2d2ec --- /dev/null +++ b/src/helpers/stack/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod builder; +pub(crate) mod dctypes; \ No newline at end of file diff --git a/src/models/stack.rs b/src/models/stack.rs index 21432d35..9ce7370e 100644 --- a/src/models/stack.rs +++ b/src/models/stack.rs @@ -3,7 +3,7 @@ use serde_json::Value; use uuid::Uuid; use serde::{Serialize,Deserialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Stack { pub id: i32, // id - is a unique identifier for the app stack pub stack_id: Uuid, // external stack ID diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index d1d8b87c..aa1cfcee 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -15,8 +15,7 @@ use sqlx::PgPool; use std::str; use tracing::Instrument; use uuid::Uuid; - - +use crate::helpers::stack::builder::DcBuilder; #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -110,3 +109,52 @@ pub async fn add( } }; } + +#[tracing::instrument(name = "Generate docker-compose.")] +#[post("/{id}")] +pub async fn gen( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + + match stack { + Some(stack) => { + let mut dc = DcBuilder::new(); + let fc = dc.build(stack); + tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + } + None => { + + } + } + + Ok(Json(JsonResponse::::ok(1, "OK"))) +} diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 7b5fa7f5..b3ce3898 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -46,3 +46,4 @@ pub async fn get( } } } + diff --git a/src/startup.rs b/src/startup.rs index edd58c1b..773ce2c0 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -128,6 +128,7 @@ pub async fn run( .wrap(HttpAuthentication::bearer(bearer_guard)) .wrap(Cors::permissive()) .service(crate::routes::stack::add::add) + .service(crate::routes::stack::add::gen) .service(crate::routes::stack::get::get) ) .app_data(db_pool.clone()) From d04ecab475ff60907c257246aa4fed29d9213029 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 16:23:09 +0200 Subject: [PATCH 2/5] return contents of the compose file on build --- files/{docker-compose.yml => 1.yml} | 0 .../c60d329f-9159-48b7-abf3-10ccabbca213.yml | 20 ++++++ src/helpers/stack/builder.rs | 65 +++++++------------ src/routes/stack/add.rs | 21 ++++-- 4 files changed, 59 insertions(+), 47 deletions(-) rename files/{docker-compose.yml => 1.yml} (100%) create mode 100644 files/c60d329f-9159-48b7-abf3-10ccabbca213.yml diff --git a/files/docker-compose.yml b/files/1.yml similarity index 100% rename from files/docker-compose.yml rename to files/1.yml diff --git a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml new file mode 100644 index 00000000..c6e60d64 --- /dev/null +++ b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml @@ -0,0 +1,20 @@ +version: '3.8' +services: + smarty-bot: + image: trydirect/smarty-bot:latest + ports: + - target: 8000 + published: 8000 + restart: always + pgrst: + image: pgrst + ports: + - target: 9999 + published: 9999 + restart: always + portainer_ce_feature: + image: portainer-ce-feature + ports: + - target: 9000 + published: 9000 + restart: always diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index 023ee964..1ee5fe9b 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -1,7 +1,18 @@ -use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, SingleService}; +use crate::helpers::stack::dctypes::{ + Compose, + Port, + Ports, + PublishedPort, + Service, + Services +}; use serde_yaml; use crate::forms; -use crate::forms::{StackForm, Web, Feature}; +use crate::forms::{ + StackForm, + Web, + Feature +}; use crate::models::stack::Stack; #[derive(Clone, Debug)] @@ -60,44 +71,17 @@ fn convert_shared_ports(ports: Vec) -> Result, String> { impl DcBuilder { - pub fn new() -> Self { + pub fn new(stack: Stack) -> Self { DcBuilder { config: Config::default(), - stack: Stack { - id: 0, - stack_id: Default::default(), - user_id: "".to_string(), - name: "".to_string(), - body: Default::default(), - created_at: Default::default(), - updated_at: Default::default(), - }, + stack: stack, } } - // pub fn add_ports(&self, ports: &Vec) -> Vec { - // // @todo re-factor using TryInto or TryFrom - // - // let mut _ports:Vec = vec![]; - // for p in ports { - // let port = p.parse::().unwrap(); - // _ports.push( - // Port { - // target: port, - // host_ip: None, - // published: Some(PublishedPort::Single(port)), - // protocol: None, - // mode: None, - // } - // ); - // } - // _ports - // } - - pub fn build(&self, stack:Stack) -> Option { - - tracing::debug!("Start build docker compose from {:?}", stack.body); - let _stack = serde_json::from_value::(stack.body); + pub fn build(&self) -> Option { + + tracing::debug!("Start build docker compose from {:?}", &self.stack.body); + let _stack = serde_json::from_value::(self.stack.body.clone()); let mut services = indexmap::IndexMap::new(); match _stack { Ok(apps) => { @@ -115,7 +99,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -142,7 +125,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -167,7 +149,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -193,15 +174,17 @@ impl DcBuilder { ..Default::default() }; - let target_file = std::path::Path::new("./files/docker-compose.yml"); + let fname= format!("./files/{}.yml", self.stack.stack_id); + tracing::debug!("Save docker compose to file {:?}", fname); + let target_file = std::path::Path::new(fname.as_str()); // serialize to string let serialized = match serde_yaml::to_string(&compose_content) { Ok(s) => s, Err(e) => panic!("Failed to serialize docker-compose file: {}", e), }; // serialize to file - std::fs::write(target_file, serialized).unwrap(); + std::fs::write(target_file, serialized.clone()).unwrap(); - Some(compose_content) + Some(serialized) } } diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index aa1cfcee..ef3e0fcb 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -16,6 +16,7 @@ use std::str; use tracing::Instrument; use uuid::Uuid; use crate::helpers::stack::builder::DcBuilder; +use crate::helpers::stack::dctypes::Compose; #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -147,14 +148,22 @@ pub async fn gen( match stack { Some(stack) => { - let mut dc = DcBuilder::new(); - let fc = dc.build(stack); - tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + let fc = dc.build(); + // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + return Ok(Json(JsonResponse::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + Some(fc.unwrap()), + None + ))); + } None => { - + return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); } } - - Ok(Json(JsonResponse::::ok(1, "OK"))) } From 327bc563a1ab8fc2dee5451e57b7d4b7d9f396d5 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 16:25:00 +0200 Subject: [PATCH 3/5] exclude ./files from git --- .gitignore | 1 + .idea/stacker.iml | 2 ++ files/1.yml | 20 ------------------- .../c60d329f-9159-48b7-abf3-10ccabbca213.yml | 20 ------------------- 4 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 files/1.yml delete mode 100644 files/c60d329f-9159-48b7-abf3-10ccabbca213.yml diff --git a/.gitignore b/.gitignore index c5078494..3acd8afb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target .idea +files diff --git a/.idea/stacker.iml b/.idea/stacker.iml index 227e58a7..a97e9259 100644 --- a/.idea/stacker.iml +++ b/.idea/stacker.iml @@ -4,6 +4,8 @@ + + diff --git a/files/1.yml b/files/1.yml deleted file mode 100644 index c6e60d64..00000000 --- a/files/1.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' -services: - smarty-bot: - image: trydirect/smarty-bot:latest - ports: - - target: 8000 - published: 8000 - restart: always - pgrst: - image: pgrst - ports: - - target: 9999 - published: 9999 - restart: always - portainer_ce_feature: - image: portainer-ce-feature - ports: - - target: 9000 - published: 9000 - restart: always diff --git a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml deleted file mode 100644 index c6e60d64..00000000 --- a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' -services: - smarty-bot: - image: trydirect/smarty-bot:latest - ports: - - target: 8000 - published: 8000 - restart: always - pgrst: - image: pgrst - ports: - - target: 9999 - published: 9999 - restart: always - portainer_ce_feature: - image: portainer-ce-feature - ports: - - target: 9000 - published: 9000 - restart: always From 9726e6d97c0a2304006769ebd283582857f0c81a Mon Sep 17 00:00:00 2001 From: vsilent Date: Sun, 12 Nov 2023 11:24:44 +0200 Subject: [PATCH 4/5] generate docker compose, response builder, dctypes --- Cargo.lock | 508 ++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- configuration.yaml | 7 + src/configuration.rs | 17 ++ src/forms/stack.rs | 36 ++- src/helpers/json.rs | 95 +++++-- src/helpers/stack/builder.rs | 4 +- src/models/stack.rs | 10 + src/routes/stack/add.rs | 65 +---- src/routes/stack/compose.rs | 71 +++++ src/routes/stack/deploy.rs | 124 ++++++++- src/routes/stack/get.rs | 28 +- src/routes/stack/mod.rs | 2 + src/startup.rs | 3 +- 14 files changed, 873 insertions(+), 101 deletions(-) create mode 100644 src/routes/stack/compose.rs diff --git a/Cargo.lock b/Cargo.lock index 68ef0eb4..332a9ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,54 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "amq-protocol" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d40d8b2465c7959dd40cee32ba6ac334b5de57e9fca0cc756759894a4152a5d" +dependencies = [ + "amq-protocol-tcp", + "amq-protocol-types", + "amq-protocol-uri", + "cookie-factory", + "nom", + "serde", +] + +[[package]] +name = "amq-protocol-tcp" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb2100adae7da61953a2c3a01935d86caae13329fadce3333f524d6d6ce12e2" +dependencies = [ + "amq-protocol-uri", + "tcp-stream", + "tracing", +] + +[[package]] +name = "amq-protocol-types" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156ff13c8a3ced600b4e54ed826a2ae6242b6069d00dd98466827cef07d3daff" +dependencies = [ + "cookie-factory", + "nom", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-uri" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751bbd7d440576066233e740576f1b31fdc6ab86cfabfbd48c548de77eca73e4" +dependencies = [ + "amq-protocol-types", + "percent-encoding", + "url", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -294,6 +342,104 @@ dependencies = [ "libc", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-global-executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75" +dependencies = [ + "async-global-executor", + "async-trait", + "executor-trait", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" +dependencies = [ + "async-io", + "async-trait", + "futures-core", + "reactor-trait", +] + +[[package]] +name = "async-task" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" + [[package]] name = "async-trait" version = "0.1.74" @@ -314,6 +460,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -368,6 +520,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + [[package]] name = "brotli" version = "3.4.0" @@ -416,6 +593,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -448,6 +634,25 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.13.3" @@ -484,6 +689,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -650,6 +861,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -687,6 +907,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.7" @@ -733,6 +959,24 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08" +dependencies = [ + "async-trait", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -755,6 +999,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -812,6 +1068,27 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -1099,6 +1376,16 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1108,6 +1395,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -1173,6 +1471,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lapin" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3067a1fcfbc3fc46455809c023e69b8f6602463201010f4ae5a3b572adb9dc" +dependencies = [ + "amq-protocol", + "async-global-executor-trait", + "async-reactor-trait", + "async-trait", + "executor-trait", + "flume", + "futures-core", + "futures-io", + "parking_lot 0.12.1", + "pinky-swear", + "reactor-trait", + "serde", + "serde_json", + "tracing", + "waker-fn", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1191,6 +1512,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.10" @@ -1420,6 +1747,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p12" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +dependencies = [ + "cbc", + "cipher", + "des", + "getrandom", + "hmac", + "lazy_static", + "rc2", + "sha1", + "yasna", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -1563,12 +1913,51 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinky-swear" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac" +dependencies = [ + "doc-comment", + "flume", + "parking_lot 0.12.1", + "tracing", +] + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1653,6 +2042,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + +[[package]] +name = "reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" +dependencies = [ + "async-trait", + "futures-core", + "futures-io", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1838,6 +2247,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + [[package]] name = "rustix" version = "0.38.19" @@ -1847,7 +2270,7 @@ dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.10", "windows-sys", ] @@ -1863,6 +2286,42 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +dependencies = [ + "log", + "ring 0.17.4", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-connector" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060bcc1795b840d0e56d78f3293be5f652aa1611d249b0e63ffe19f4a8c9ae23" +dependencies = [ + "log", + "rustls 0.21.8", + "rustls-native-certs", + "rustls-webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.3" @@ -1872,6 +2331,16 @@ dependencies = [ "base64 0.21.4", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.4", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.15" @@ -2118,6 +2587,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "sqlformat" @@ -2177,7 +2649,7 @@ dependencies = [ "paste", "percent-encoding", "rand", - "rustls", + "rustls 0.20.9", "rustls-pemfile", "serde", "serde_json", @@ -2238,8 +2710,10 @@ dependencies = [ "chrono", "config", "derive_builder", + "futures-lite", "glob", "indexmap 2.1.0", + "lapin", "rand", "regex", "reqwest", @@ -2325,6 +2799,18 @@ dependencies = [ "libc", ] +[[package]] +name = "tcp-stream" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5" +dependencies = [ + "cfg-if", + "p12", + "rustls-connector", + "rustls-pemfile", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -2332,9 +2818,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.19", "windows-sys", ] @@ -2469,7 +2955,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] @@ -2714,6 +3200,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "want" version = "0.3.1" @@ -2956,6 +3448,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" + [[package]] name = "zstd" version = "0.12.4" diff --git a/Cargo.toml b/Cargo.toml index 581f4d5a..4112688c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ rand = "0.8.5" derive_builder = "0.12.0" indexmap = { version = "2.0.0", features = ["serde"], optional = true } serde_yaml = "0.9" +lapin = { version = "2.3.1", features = ["serde_json"] } +futures-lite = "1.13.0" [dependencies.sqlx] version = "0.6.3" @@ -56,4 +58,4 @@ default = ["indexmap"] indexmap = ["dep:indexmap"] [dev-dependencies] -glob = "0.3" \ No newline at end of file +glob = "0.3" diff --git a/configuration.yaml b/configuration.yaml index 1de0778b..4f88a4cb 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -9,3 +9,10 @@ database: username: postgres password: postgres database_name: stacker + +amqp: + host: 51.15.74.139 + port: 5672 + username: guest + password: wua4Eeyi + diff --git a/src/configuration.rs b/src/configuration.rs index a3beeaf4..90d22c92 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -7,6 +7,7 @@ pub struct Settings { pub app_host: String, pub auth_url: String, pub max_clients_number: i64, + pub amqp: AmqpSettings } #[derive(Debug, serde::Deserialize)] @@ -18,6 +19,13 @@ pub struct DatabaseSettings { pub database_name: String, } +#[derive(Debug, serde::Deserialize)] +pub struct AmqpSettings { + pub username: String, + pub password: String, + pub host: String, + pub port: u16, +} impl DatabaseSettings { // Connection string: postgresql://:@:/ pub fn connection_string(&self) -> String { @@ -35,6 +43,15 @@ impl DatabaseSettings { } } +impl AmqpSettings { + pub fn connection_string(&self) -> String { + format!( + "amqp://{}:{}@{}:{}/%2f", + self.username, self.password, self.host, self.port, + ) + } +} + pub fn get_configuration() -> Result { // Initialize our configuration reader let mut settings = config::Config::default(); diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 93a1be85..ce9105e8 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -3,8 +3,42 @@ use serde_json::Value; use serde_valid::Validate; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub struct StackForm { + #[serde(rename= "commonDomain")] + pub common_domain: String, + pub domain_list: Option, + pub region: String, + pub zone: Option, + pub server: String, + pub os: String, + pub ssl: String, + pub vars: Option>, + #[serde(rename = "integrated_features")] + pub integrated_features: Option>, + #[serde(rename = "extended_features")] + pub extended_features: Option>, + pub subscriptions: Option>, + #[serde(rename = "save_token")] + pub save_token: bool, + #[serde(rename = "cloud_token")] + pub cloud_token: String, + pub provider: String, + #[serde(rename = "stack_code")] + pub stack_code: String, + #[serde(rename = "selected_plan")] + pub selected_plan: String, + pub custom: Custom, +} + + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +#[serde(rename_all = "snake_case")] +pub struct StackPayload { + pub(crate) user_token: Option, + pub(crate) user_email: Option, + pub(crate) installation_id: Option, + #[serde(rename= "commonDomain")] pub common_domain: String, pub domain_list: Option, pub region: String, diff --git a/src/helpers/json.rs b/src/helpers/json.rs index ad183050..de5dfd9f 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -1,8 +1,8 @@ -use actix_web::{HttpRequest, HttpResponse, Responder}; +use actix_web::{Responder, Result}; use serde_derive::Serialize; -use crate::models; +use actix_web::web; -#[derive(Serialize, Default)] +#[derive(Serialize)] pub(crate) struct JsonResponse { pub(crate) status: String, pub(crate) message: String, @@ -23,7 +23,67 @@ pub(crate) struct JsonResponse { // pub(crate) list: Option> // } -impl JsonResponse { +#[derive(Serialize, Default)] +pub struct JsonResponseBuilder + where T: serde::Serialize + Default +{ + status: String, + message: String, + code: u32, + id: Option, + item: Option, + list: Option> +} + +impl JsonResponseBuilder +where T: serde::Serialize + Default +{ + fn new() -> Self { + Self::default() + } + + fn set_item(mut self, item:T) -> Self { + self.item = Some(item); + self + } + + + fn set_list(mut self, list:Vec) -> Self { + self.list = Some(list); + self + } + + pub(crate) fn ok(self) -> Result { + + Ok(web::Json( + JsonResponse { + status: self.status, + message: self.message, + code: self.code, + id: self.id, + item: self.item, + list: self.list, + } + )) + } +} + +impl From for JsonResponseBuilder + where T: serde::Serialize + Default { + fn from(value: T) -> Self { + JsonResponseBuilder::default().set_item(value) + } +} + +impl From> for JsonResponseBuilder +where T: serde::Serialize + Default { + fn from(value: Vec) -> Self { + JsonResponseBuilder::default().set_list(value) + } +} + +impl JsonResponse +{ pub(crate) fn new(status: String, message: String, code: u32, @@ -55,8 +115,7 @@ impl JsonResponse { message: msg, code: 200, id: Some(id), - item: None, - list: None + ..Default::default() } } @@ -65,9 +124,7 @@ impl JsonResponse { status: "Error".to_string(), code: 404, message: format!("Object not found"), - id: None, - item: None, - list: None + ..Default::default() } } @@ -83,9 +140,7 @@ impl JsonResponse { status: "Error".to_string(), code: 500, message: msg, - id: None, - item: None, - list: None + ..Default::default() } } @@ -101,9 +156,19 @@ impl JsonResponse { status: "Error".to_string(), code: 400, message: msg, - id: None, - item: None, - list: None + ..Default::default() + } + } +} + +impl Default for JsonResponse { + + fn default() -> Self { + JsonResponse { + + status: "200".to_string(), + message: "OK".to_string(), + ..Default::default() } } } diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index 1ee5fe9b..a6c9fd99 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -30,7 +30,7 @@ impl Default for Config { #[derive(Clone, Debug)] pub struct DcBuilder { config: Config, - stack: Stack + pub(crate) stack: Stack } impl TryInto> for Web { @@ -88,10 +88,10 @@ impl DcBuilder { println!("stack item {:?}", apps.custom.web); for app in apps.custom.web { - // println!("app name {:?}", app.name); let tag = "latest"; let img= format!("{}/{}:{}",app.dockerhub_user, app.dockerhub_name, tag); let code = app.code.clone().to_owned(); + let mut service = Service { image: Some(img.to_string()), ..Default::default() diff --git a/src/models/stack.rs b/src/models/stack.rs index 9ce7370e..f9ce272e 100644 --- a/src/models/stack.rs +++ b/src/models/stack.rs @@ -14,3 +14,13 @@ pub struct Stack { pub created_at: DateTime, pub updated_at: DateTime, } + +impl Default for Stack { + fn default() -> Self { + Stack { + user_id: "".to_string(), + name: "".to_string(), + ..Default::default() + } + } +} \ No newline at end of file diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index ef3e0fcb..64c65cb6 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -15,8 +15,7 @@ use sqlx::PgPool; use std::str; use tracing::Instrument; use uuid::Uuid; -use crate::helpers::stack::builder::DcBuilder; -use crate::helpers::stack::dctypes::Compose; + #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -28,14 +27,12 @@ pub async fn add( let body_bytes = actix_web::body::to_bytes(body).await.unwrap(); let body_str = str::from_utf8(&body_bytes).unwrap(); - // method 1 let app_state: AppState = serde_json::from_str(body_str).unwrap(); - // method 2 let app_state = serde_json::from_str::(body_str).unwrap(); let form = match serde_json::from_str::(body_str) { Ok(f) => { f } - Err(err) => { - return Ok(Json(JsonResponse::::not_valid(""))); + Err(_err) => { + return Ok(Json(JsonResponse::::not_valid("Invalid data"))); } }; @@ -111,59 +108,3 @@ pub async fn add( }; } -#[tracing::instrument(name = "Generate docker-compose.")] -#[post("/{id}")] -pub async fn gen( - user: web::ReqData, - path: web::Path<(i32,)>, - pool: Data, -) -> Result { - let id = path.0; - tracing::debug!("Received id: {}", id); - - let stack = match sqlx::query_as!( - Stack, - r#" - SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 - "#, - id, user.id - ) - .fetch_one(pool.get_ref()) - .await - { - Ok(stack) => { - tracing::info!("stack found: {:?}", stack.id,); - Some(stack) - } - Err(sqlx::Error::RowNotFound) => { - tracing::error!("Row not found 404"); - None - } - Err(e) => { - tracing::error!("Failed to fetch stack, error: {:?}", e); - None - } - }; - - - match stack { - Some(stack) => { - let id = stack.id.clone(); - let mut dc = DcBuilder::new(stack); - let fc = dc.build(); - // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); - return Ok(Json(JsonResponse::new( - "OK".to_owned(), - "Success".to_owned(), - 200, - Some(id), - Some(fc.unwrap()), - None - ))); - - } - None => { - return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); - } - } -} diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs new file mode 100644 index 00000000..dc3230d4 --- /dev/null +++ b/src/routes/stack/compose.rs @@ -0,0 +1,71 @@ +use actix_web::{ + web, + web::{Data, Json}, + Responder, Result, +}; + +use crate::helpers::JsonResponse; +use crate::models::user::User; +use crate::models::Stack; +use actix_web::post; +use sqlx::PgPool; +use std::str; +use tracing::Instrument; +use crate::helpers::stack::builder::DcBuilder; + +#[tracing::instrument(name = "Generate docker-compose.")] +#[post("/{id}")] +pub async fn add( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + + match stack { + Some(stack) => { + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + let fc = dc.build(); + // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + return Ok(Json(JsonResponse::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + Some(fc.unwrap()), + None + ))); + + } + None => { + return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); + } + } +} diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index 67edaa81..1c871e5c 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -1,5 +1,121 @@ -use actix_web::HttpResponse; +use std::sync::Arc; +use actix_web::{ + web, + post, + web::{Data, Json}, + Responder, Result, +}; +use crate::models::user::User; +use crate::models::stack::Stack; +use sqlx::PgPool; +use lapin::{ + options::*, publisher_confirm::Confirmation, types::FieldTable, BasicProperties, Connection, + ConnectionProperties +}; +use crate::configuration::Settings; +use crate::helpers::JsonResponse; +use crate::helpers::stack::builder::DcBuilder; +use futures_lite::stream::StreamExt; +use serde::Serialize; +use crate::forms::{StackForm, StackPayload}; -pub async fn deploy() -> HttpResponse { - unimplemented!() -} \ No newline at end of file + +#[derive(Serialize, Debug, Clone)] +struct Payload { + user_token: String, + user_email: String, + installation_id: String, +} + + +#[tracing::instrument(name = "Deploy.")] +#[post("/{id}/deploy")] +pub async fn add( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, + sets: Data>, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("Stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + return match stack { + Some(stack) => { + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + dc.build(); + + let addr = sets.amqp.connection_string(); + let routing_key = "install.start.tfa.all.all".to_string(); + tracing::debug!("Sending message to {:?}", routing_key); + + let conn = Connection::connect(&addr, ConnectionProperties::default()) + .await + .unwrap(); + + tracing::info!("RABBITMQ CONNECTED"); + + let channel = conn.create_channel().await.unwrap(); + let mut stack_data = serde_json::from_value::( + dc.stack.body.clone() + ).unwrap(); + + stack_data.installation_id = Some(1); + stack_data.user_token = Some(user.id.clone()); + stack_data.user_email= Some(user.email.clone()); + + let payload = serde_json::to_string::(&stack_data).unwrap(); + let _payload = payload.as_bytes(); + + let confirm = channel + .basic_publish( + "install", + routing_key.as_str(), + BasicPublishOptions::default(), + _payload, + BasicProperties::default(), + ) + .await.unwrap() + .await.unwrap(); + + assert_eq!(confirm, Confirmation::NotRequested); + tracing::debug!("Message sent to rabbitmq"); + + Ok(Json(JsonResponse::::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + None, + None + ))) + } + None => { + Ok(Json(JsonResponse::internal_error("Deployment failed"))) + } + } +} diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index b3ce3898..786d905f 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -1,8 +1,9 @@ use actix_web::{web, get, Responder, Result}; use sqlx::PgPool; -use crate::helpers::JsonResponse; +use crate::helpers::{JsonResponse, JsonResponseBuilder}; use crate::models; use crate::models::user::User; +use std::convert::From; #[tracing::instrument(name = "Get logged user stack.")] @@ -29,20 +30,27 @@ pub async fn get( { Ok(stack) => { tracing::info!("stack found: {:?}", stack.id,); - return Ok(web::Json(JsonResponse::::new( - "Success".to_string(), - "".to_string(), - 200, - Some(stack.id), - Some(stack), - None))); + // return Ok(web::Json(JsonResponse::::new( + // "Success".to_string(), + // "".to_string(), + // 200, + // Some(stack.id), + // Some(stack), + // None))); + let response_builder:JsonResponseBuilder = From::from(stack); + // let response: JsonResponse = From::::from(stack).build(); + return response_builder.ok(); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse::::not_found())); + // return Ok(web::Json(JsonResponse::::not_found())); + let response_builder:JsonResponseBuilder =JsonResponseBuilder::default(); + return response_builder.ok(); } Err(e) => { tracing::error!("Failed to fetch stack, error: {:?}", e); - return Ok(web::Json(JsonResponse::::internal_error(""))); + // return Ok(web::Json(JsonResponse::::internal_error(""))); + let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); + return response_builder.ok(); } } } diff --git a/src/routes/stack/mod.rs b/src/routes/stack/mod.rs index f3e5bc95..27c80617 100644 --- a/src/routes/stack/mod.rs +++ b/src/routes/stack/mod.rs @@ -2,6 +2,8 @@ pub mod add; pub mod deploy; pub mod get; pub mod update; +pub(crate) mod compose; + pub use add::*; pub use update::*; pub use deploy::*; diff --git a/src/startup.rs b/src/startup.rs index 773ce2c0..b7fd3243 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -127,8 +127,9 @@ pub async fn run( web::scope("/stack") .wrap(HttpAuthentication::bearer(bearer_guard)) .wrap(Cors::permissive()) + .service(crate::routes::stack::deploy::add) .service(crate::routes::stack::add::add) - .service(crate::routes::stack::add::gen) + .service(crate::routes::stack::compose::add) .service(crate::routes::stack::get::get) ) .app_data(db_pool.clone()) From d5be426847f7299c38ad9611b29f0b003cc231d9 Mon Sep 17 00:00:00 2001 From: vsilent Date: Sun, 12 Nov 2023 11:25:51 +0200 Subject: [PATCH 5/5] rabbitmq config --- configuration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration.yaml b/configuration.yaml index 4f88a4cb..9498400b 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -11,8 +11,8 @@ database: database_name: stacker amqp: - host: 51.15.74.139 + host: 127.0.0.1 port: 5672 username: guest - password: wua4Eeyi + password: