diff --git a/src/api/error.rs b/src/api/error.rs index 0810f5b..bc7cf28 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -1,9 +1,36 @@ +use actix_web::{ + http::{header::ContentType, StatusCode}, + HttpResponse, ResponseError, +}; use derive_more::{Display, Error}; -impl actix_web::error::ResponseError for ReturnedError {} - #[derive(Debug, Display, Error)] -#[display("my error: {}", name)] -pub struct ReturnedError { - pub name: String, +pub enum ResponseMRDErrors { + #[display("Character {} not allowed in {}", character, parameter)] + CharacterNotAllowed { character: char, parameter: String }, + + #[display("container not found")] + ContainerNotFound, + + #[display("error: {}", message)] + GenericError { message: String }, +} + +impl ResponseError for ResponseMRDErrors { + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .insert_header(ContentType::html()) + .body(self.to_string()) + } + + fn status_code(&self) -> StatusCode { + match *self { + ResponseMRDErrors::CharacterNotAllowed { + character: _, + parameter: _, + } => StatusCode::BAD_REQUEST, + ResponseMRDErrors::ContainerNotFound => StatusCode::NOT_FOUND, + ResponseMRDErrors::GenericError { message: _ } => StatusCode::INTERNAL_SERVER_ERROR, + } + } } diff --git a/src/api/routes.rs b/src/api/routes.rs index 6cb20a3..da3e0d0 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -1,7 +1,7 @@ use std::net::Ipv4Addr; use std::str::FromStr; -use crate::api::error::ReturnedError; +use crate::api::error::ResponseMRDErrors; use crate::config::{ docker::DockerConnectionConfig, mrproxy::MrproxyConnectionConfig, server::ServerConfig, CONFIG_PATH, @@ -33,51 +33,48 @@ async fn create_container( params: web::Query, ) -> impl Responder { let ops = Options::new(params.memory.clone(), params.msg.clone()); - controller - .create_container( - params.name.clone(), - params.ip.clone(), - params.image.clone(), - ops, - ) + let name = params.name.clone(); + for c in name.chars() { + if !c.is_ascii_alphanumeric() { + return Err(ResponseMRDErrors::CharacterNotAllowed { + character: c, + parameter: "domain name".to_string(), + }); + } + } + match controller + .create_container(name, params.ip.clone(), params.image.clone(), ops) .await + { + Ok(result) => Ok(result), + Err(e) => Err(ResponseMRDErrors::GenericError { + message: e.to_string(), + }), + } } #[patch("/container/stop")] async fn stop_container( controller: web::Data, params: web::Query, -) -> Result { - match controller.stop_container(params.name.clone()).await { - Ok(response) => Ok(response), - Err(e) => Err(ReturnedError { - name: e.to_string(), - }), - } +) -> impl Responder { + controller.stop_container(params.name.clone()).await } #[delete("/container")] async fn delete_container( controller: web::Data, params: web::Query, -) -> Result { - match controller.delete_container_and_unbind(¶ms.name).await { - Ok(response) => Ok(response), - Err(e) => Err(ReturnedError { - name: e.to_string(), - }), - } +) -> impl Responder { + controller.delete_container_and_unbind(¶ms.name).await } #[get("/container/{container_name}/ip")] async fn get_ip( controller: web::Data, container_name: web::Path, -) -> Result { - Ok(controller - .get_ip(container_name.into_inner()) - .await - .unwrap()) +) -> impl Responder { + controller.get_ip(&container_name.into_inner()).await } pub async fn start() -> std::io::Result<()> { diff --git a/src/controller/dns_controller.rs b/src/controller/dns_controller.rs new file mode 100644 index 0000000..e025462 --- /dev/null +++ b/src/controller/dns_controller.rs @@ -0,0 +1,13 @@ +use crate::controller::Controller; + +impl Controller { + pub async fn add_domain_to_dns(&self, domain: &str) -> Result<(), Box> { + self.dns_manager.add_domain(domain, self.pub_addr).await?; + Ok(()) + } + + pub async fn del_domain_to_dns(&self, domain: &str) -> Result<(), Box> { + self.dns_manager.del_domain(domain).await?; + Ok(()) + } +} diff --git a/src/controller/docker_controller.rs b/src/controller/docker_controller.rs new file mode 100644 index 0000000..732e3b4 --- /dev/null +++ b/src/controller/docker_controller.rs @@ -0,0 +1,53 @@ +use std::error::Error; + +use crate::{ + controller::Controller, + deploy::{self, container::Container, container_options::Options}, +}; + +impl Controller { + pub async fn load_container( + &self, + docker_id: Option, + domain: String, + ip: Option, + image: String, + ops: Options, + ) -> Result { + deploy::container::Container::new( + self.driver.clone(), + docker_id, + domain, + ip, + image, + self.network.clone(), + ops, + ) + .await + } + + #[allow(dead_code)] + pub async fn is_started(&self) -> bool { + self.started + } + + pub async fn prune_given_container( + &self, + container: Container, + ) -> Result> { + match container.remove(&self.driver).await { + Ok(_i) => Ok(container.get_id()), + Err(e) => Err(Box::new(e)), + } + } + + pub async fn stop_given_container( + &self, + container: Container, + ) -> Result { + match container.stop(&self.driver).await { + Ok(_i) => Ok(container.get_id()), + Err(e) => Err(e), + } + } +} diff --git a/src/controller/mixed_controller.rs b/src/controller/mixed_controller.rs new file mode 100644 index 0000000..e2456d7 --- /dev/null +++ b/src/controller/mixed_controller.rs @@ -0,0 +1,71 @@ +use crate::{ + controller::Controller, + database::instance::Instance, + deploy::{self, container_options::Options}, +}; +use log::error; + +impl Controller { + pub async fn load_container_and_bind( + &self, + docker_id: Option, + domain: String, + ip: Option, + image: String, + ops: Options, + ) -> Result> { + match self + .load_container(docker_id, domain.clone(), ip.clone(), image, ops) + .await + { + Ok(container) => match self + .bind_container_in_proxy( + &domain, + &ip.unwrap_or(container.get_ip(&self.driver).await?), + ) + .await + { + Ok(_) => { + self.add_domain_to_dns(&domain).await?; + Ok(container) + } + Err(e) => { + error!("failed in the bind process with the proxy, deleting the container"); + self.delete_container_and_unbind(&domain).await?; + return Err(e); + } + }, + Err(e) => return Err(Box::new(e)), + } + } + + pub async fn start_container_from_instance(&self, instance: Instance) { + let image = match self + .storage + .lock() + .unwrap() + .id_to_image(instance.image.clone()) + { + Ok(i) => i, + Err(e) => { + error!("image not found: {}", e); + return; + } + }; + match self + .load_container_and_bind( + Some(instance.docker_id.clone()), + instance.domain.clone(), + instance.ip.clone(), + image, + Options::new(None, None), + ) + .await + { + Ok(c) => { + self.storage.lock().unwrap().loaded_instance(instance, c); + } + Err(e) => error!("{}", e), + } + } +} diff --git a/src/controller/mod.rs b/src/controller/mod.rs index f34af91..2a507fd 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,10 +1,8 @@ +use crate::api::error::ResponseMRDErrors; use crate::config::mrproxy::MrproxyConnectionConfig; use crate::database::exposer::MemStorage; -use crate::database::instance::Instance; -use crate::deploy::container::Container; +use crate::deploy; use crate::deploy::container_options::Options; -use crate::{deploy, mcproxy_client}; -use bollard::errors::Error; use bollard::Docker; use dns_manager::{DnsManager, ServerZonesConnection}; use log::{error, warn}; @@ -12,6 +10,11 @@ use std::error::Error as GenericError; use std::net::Ipv4Addr; use std::sync::Mutex; +mod dns_controller; +mod docker_controller; +mod mixed_controller; +mod mrproxy_controller; + pub struct Controller { driver: Docker, pub_addr: Ipv4Addr, @@ -50,135 +53,36 @@ impl Controller { ip: Option, image: String, ops: Options, - ) -> String { - let is_stored = self.storage.lock().unwrap().search_instance(domain.clone()); + ) -> Result> { + let is_stored = self.storage.lock().unwrap().search_instance(&domain); match is_stored { Some(c) => match c.docker_id { - Some(id) => id, - None => "Container without docker_id".to_string(), + Some(id) => Ok(id), + None => Ok("Container without docker_id".to_string()), }, None => { match self - .load_container_and_bind(None, domain.clone(), ip.clone(), image.clone(), ops) + .load_container_and_bind(None, domain, ip, image, ops) .await { Ok(c) => { - self.storage - .try_lock() - .unwrap() - .new_instance(c.clone()) - .unwrap(); - c.get_id() + let c_id = c.get_id(); + self.storage.try_lock().unwrap().new_instance(c)?; + Ok(c_id) } - Err(e) => format!("error creating container: {}", e.to_string()), + Err(e) => Err(e), } } } } - async fn load_container_and_bind( - &self, - docker_id: Option, - domain: String, - ip: Option, - image: String, - ops: Options, - ) -> Result> { - match self - .load_container(docker_id, domain.clone(), ip.clone(), image, ops) - .await - { - Ok(c) => match self.bind_container_in_proxy(&domain, ip, &c).await { - Ok(_) => { - self.add_domain_to_dns(&domain).await?; - Ok(c) - } - Err(e) => { - error!("failed in the bind process with the proxy, deleting the container"); - self.delete_container_and_unbind(&domain).await?; - return Err(e); - } - }, - Err(e) => return Err(Box::new(e)), + pub async fn stop_container(&self, domain: String) -> Result { + match self.storage.lock().unwrap().stop_instance(domain) { + Some(c) => format_error_bollard(self.stop_given_container(c).await), + None => Err(ResponseMRDErrors::ContainerNotFound), } } - async fn bind_container_in_proxy( - &self, - domain: &str, - ip: Option, - container: &Container, - ) -> Result<(), Box> { - let ip_final = ip.unwrap_or(container.get_ip(&self.driver).await?); - log::debug!("ip binded: {}", ip_final); - let mut mrcp_controller = - mcproxy_client::controller::Controller::new(&self.mrproxy_config)?; - mrcp_controller.insert_new_domain(&self.dns_manager.get_full_domain(domain), &ip_final)?; - Ok(()) - } - - async fn unbind_container_in_proxy(&self, domain: &str) -> Result<(), Box> { - let mut mrcp_controller = - mcproxy_client::controller::Controller::new(&self.mrproxy_config)?; - mrcp_controller.remove_domain(&self.dns_manager.get_full_domain(domain))?; - Ok(()) - } - - pub async fn start_container_from_instance(&self, instance: Instance) { - let image = match self - .storage - .lock() - .unwrap() - .id_to_image(instance.image.clone()) - { - Ok(i) => i, - Err(e) => { - error!("image not found: {}", e); - return; - } - }; - match self - .load_container_and_bind( - Some(instance.docker_id.clone()), - instance.domain.clone(), - instance.ip.clone(), - image, - Options::new(None, None), - ) - .await - { - Ok(c) => { - self.storage.lock().unwrap().loaded_instance(instance, c); - } - Err(e) => error!("{}", e), - } - } - - async fn load_container( - &self, - docker_id: Option, - domain: String, - ip: Option, - image: String, - ops: Options, - ) -> Result { - deploy::container::Container::new( - self.driver.clone(), - docker_id, - domain, - ip, - image, - self.network.clone(), - ops, - ) - .await - } - - #[allow(dead_code)] - pub async fn is_started(&self) -> bool { - self.started - } - pub async fn load_all_instances(&self) -> bool { let data = match self.storage.lock().unwrap().get_instances_db() { Ok(d) => d, @@ -194,37 +98,10 @@ impl Controller { return true; } - async fn stop_given_container(&self, container: Container) -> Result { - match container.stop(&self.driver).await { - Ok(_i) => Ok(container.get_id()), - Err(e) => Err(e), - } - } - - async fn prune_given_container( - &self, - container: Container, - ) -> Result> { - match container.remove(&self.driver).await { - Ok(_i) => Ok(container.get_id()), - Err(e) => Err(Box::new(e)), - } - } - - pub async fn stop_container(&self, domain: String) -> Result { - match self.storage.lock().unwrap().stop_instance(domain) { - Some(c) => self.stop_given_container(c).await, - None => Err(Error::DockerResponseServerError { - status_code: 404, - message: "container not found".to_string(), - }), - } - } - pub async fn delete_container_and_unbind( &self, domain: &str, - ) -> Result> { + ) -> Result { match self.storage.lock().unwrap().remove_instance(&domain) { Some(data) => match data.1 { Some(c) => { @@ -236,34 +113,40 @@ impl Controller { if r_del_domain.is_err() { warn!("Error deleting domain {} from dns", domain); } - self.prune_given_container(c).await + format_error(self.prune_given_container(c).await) } None => Ok(data.0.docker_id), }, - None => Err(Box::new(Error::DockerResponseServerError { - status_code: 404, - message: "container not found".to_string(), - })), + None => Err(ResponseMRDErrors::ContainerNotFound), } } - pub async fn get_ip(&self, domain: String) -> Result { - let container = self - .storage - .lock() - .unwrap() - .search_instance(domain.clone()) - .unwrap(); - container.get_ip(&self.driver).await - } - - pub async fn add_domain_to_dns(&self, domain: &str) -> Result<(), Box> { - self.dns_manager.add_domain(domain, self.pub_addr).await?; - Ok(()) - } - - pub async fn del_domain_to_dns(&self, domain: &str) -> Result<(), Box> { - self.dns_manager.del_domain(domain).await?; - Ok(()) + pub async fn get_ip(&self, domain: &str) -> Result { + match self.storage.lock().unwrap().search_instance(domain) { + Some(container) => format_error_bollard(container.get_ip(&self.driver).await), + None => Err(ResponseMRDErrors::ContainerNotFound), + } + } +} + +fn format_error( + result: Result>, +) -> Result { + match result { + Ok(r) => Ok(r), + Err(e) => Err(ResponseMRDErrors::GenericError { + message: e.to_string(), + }), + } +} + +fn format_error_bollard( + result: Result, +) -> Result { + match result { + Ok(r) => Ok(r), + Err(e) => Err(ResponseMRDErrors::GenericError { + message: e.to_string(), + }), } } diff --git a/src/controller/mrproxy_controller.rs b/src/controller/mrproxy_controller.rs new file mode 100644 index 0000000..628b93e --- /dev/null +++ b/src/controller/mrproxy_controller.rs @@ -0,0 +1,24 @@ +use crate::{controller::Controller, mcproxy_client}; + +impl Controller { + pub async fn bind_container_in_proxy( + &self, + domain: &str, + ip: &str, + ) -> Result<(), Box> { + let mut mrcp_controller = + mcproxy_client::controller::Controller::new(&self.mrproxy_config)?; + mrcp_controller.insert_new_domain(&self.dns_manager.get_full_domain(domain), ip)?; + Ok(()) + } + + pub async fn unbind_container_in_proxy( + &self, + domain: &str, + ) -> Result<(), Box> { + let mut mrcp_controller = + mcproxy_client::controller::Controller::new(&self.mrproxy_config)?; + mrcp_controller.remove_domain(&self.dns_manager.get_full_domain(domain))?; + Ok(()) + } +} diff --git a/src/database/exposer.rs b/src/database/exposer.rs index ca797d3..2a4c0f9 100644 --- a/src/database/exposer.rs +++ b/src/database/exposer.rs @@ -53,7 +53,7 @@ impl MemStorage { Ok(()) } - pub fn search_instance(&self, name: String) -> Option { + pub fn search_instance(&self, name: &str) -> Option { for cont in &self.containers { if cont.0.domain == name { return cont.1.clone();