add remove feature

This commit is contained in:
2023-09-25 18:30:14 +02:00
parent 565cb660ee
commit 40385bbae2
9 changed files with 222 additions and 36 deletions

1
Cargo.lock generated
View File

@@ -1004,6 +1004,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"bollard", "bollard",
"derive_more",
"env_logger", "env_logger",
"fallible-iterator", "fallible-iterator",
"futures", "futures",

View File

@@ -10,6 +10,7 @@ tokio = { version = "1", features = ["full"] }
rusqlite = { version = "0.29.0", features = ["bundled"] } rusqlite = { version = "0.29.0", features = ["bundled"] }
actix-web = "4" actix-web = "4"
env_logger = "*" env_logger = "*"
derive_more = "*"
log = "*" log = "*"
serde = "*" serde = "*"
fallible-iterator = "*" fallible-iterator = "*"

View File

@@ -12,6 +12,7 @@ pub struct ConfServer{
ip_base_limit: u8, ip_base_limit: u8,
} }
#[derive(Clone)]
pub struct Instance { pub struct Instance {
id: i64, id: i64,
pub docker_id: String, pub docker_id: String,
@@ -114,6 +115,11 @@ impl InstanceStorage {
}) })
} }
pub fn remove_instance(&mut self, id: String) -> Result<usize> {
let mut stmt = self.con.prepare("DELETE from l_instances where id=?1")?;
stmt.execute([id])
}
pub fn image_to_id(&self, image: String) -> Result<i64> { pub fn image_to_id(&self, image: String) -> Result<i64> {
self.create_or_get_image_id(image, 0) self.create_or_get_image_id(image, 0)
} }

View File

@@ -37,6 +37,7 @@ impl MemStorage {
pub fn id_to_image(&mut self, image: i64) -> Result<String, Error>{ pub fn id_to_image(&mut self, image: i64) -> Result<String, Error>{
self.storage.get_image_name(image) self.storage.get_image_name(image)
} }
pub fn new_instance(&mut self, container: Container) -> Result<(), Error> { pub fn new_instance(&mut self, container: Container) -> Result<(), Error> {
let image_id = self.image_to_id(container.image.clone())?; let image_id = self.image_to_id(container.image.clone())?;
let ins = self.storage.new_instance( let ins = self.storage.new_instance(
@@ -48,6 +49,37 @@ impl MemStorage {
Ok(()) Ok(())
} }
pub fn search_instance(&self, name: String) -> Option<Container> {
for cont in &self.containers {
if cont.0.domain == name { return cont.1.clone(); }
}
None
}
pub fn stop_instance(&mut self, name: String) -> Option<Container> {
for cont in &mut self.containers {
if cont.0.domain == name {
return cont.1.take();
}
}
None
}
pub fn remove_instance(&mut self, name: String) -> Option<(Instance, Option<Container>)> {
for i in 0..self.containers.len() {
match self.containers.get(i) {
Some(c) => {
if c.0.domain == name {
self.storage.remove_instance(c.0.id.to_string().clone());
return Some(self.containers.remove(i));
}
}
None => break
}
}
None
}
pub fn loaded_instance(&mut self, ins: Instance, container: Container) { pub fn loaded_instance(&mut self, ins: Instance, container: Container) {
self.containers.push((ins, Some(container))); self.containers.push((ins, Some(container)));
} }

View File

@@ -1,9 +1,11 @@
use bollard::Docker; use bollard::Docker;
use bollard::errors::Error;
use crate::deploy; use crate::deploy;
use crate::deploy::container::Container;
use crate::conf::storage::MemStorage; use crate::conf::storage::MemStorage;
use crate::conf::Instance; use crate::conf::Instance;
use std::sync::Mutex; use std::sync::Mutex;
use log::{error}; use log::error;
pub struct Controller { pub struct Controller {
driver: Docker, driver: Docker,
@@ -25,13 +27,22 @@ impl Controller {
} }
pub async fn create_container(&self, domain: String, ip: String, image: String) -> String { pub async fn create_container(&self, domain: String, ip: String, image: String) -> String {
match self.load_container(None,domain.clone(),ip.clone(),image.clone()).await { let is_stored = self.storage.lock().unwrap().search_instance(domain.clone());
Ok(c) => { match is_stored {
let ret = c.get_id(); Some(c) => match c.docker_id {
self.storage.lock().unwrap().new_instance(c).unwrap(); Some(id) => id,
ret None => "Container without docker_id".to_string()
}, },
Err(_e) => "error creating container".to_string(), None => {
match self.load_container(None,domain.clone(),ip.clone(),image.clone()).await {
Ok(c) => {
log::debug!("poisoned: {}",self.storage.is_poisoned());
self.storage.try_lock().unwrap().new_instance(c.clone()).unwrap();
c.get_id()
},
Err(_e) => "error creating container".to_string(),
}
}
} }
} }
@@ -39,7 +50,7 @@ impl Controller {
let image = match self.storage.lock().unwrap().id_to_image(instance.image.clone()) { let image = match self.storage.lock().unwrap().id_to_image(instance.image.clone()) {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
log::error!("image not found: {}", e); error!("image not found: {}", e);
return return
}, },
}; };
@@ -51,7 +62,7 @@ impl Controller {
Ok(c) => { Ok(c) => {
self.storage.lock().unwrap().loaded_instance(instance, c); self.storage.lock().unwrap().loaded_instance(instance, c);
}, },
Err(e) => log::error!("{}",e), Err(e) => error!("{}",e),
} }
} }
@@ -77,7 +88,7 @@ impl Controller {
let data = match self.storage.lock().unwrap().get_instances_db() { let data = match self.storage.lock().unwrap().get_instances_db() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("instances can't be loaded: {}",e); error!("instances can't be loaded: {}",e);
return false return false
}, },
}; };
@@ -88,5 +99,44 @@ impl Controller {
return true; return true;
} }
//pub async fn stop_container(&self, ) async fn stop_given_container(&self, container:Container) -> Result<String, Error> {
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<String, Error> {
match container.remove(&self.driver).await {
Ok(_i) => Ok(container.get_id()),
Err(e) => Err(e),
}
}
pub async fn stop_container(&self, domain: String) -> Result<String, Error> {
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(&self, domain: String) -> Result<String, Error> {
match self.storage.lock().unwrap().remove_instance(domain) {
Some(data) => {
match data.1 {
Some(c) => self.prune_given_container(c).await,
None => Ok(data.0.docker_id),
}
},
None => Err(Error::DockerResponseServerError {
status_code: 404, message: "container not found".to_string()
}),
}
}
} }

18
src/deploy/bind.rs Normal file
View File

@@ -0,0 +1,18 @@
use std::vec;
use std::fs::{create_dir, canonicalize};
const BASE_PATH : &str = "mrdeploy_data/";
pub fn generate_bindpaths(mut name: String) -> Vec<String>{
name.insert_str(0,BASE_PATH);
let path = match canonicalize(name.clone()) {
Ok(p) => String::from(p.to_str().unwrap()),
Err(_e) => name,
};
vec![format!("{}:{}", path, "/data")]
}
pub fn check_fs(mut name: String) -> Result<(), std::io::Error> {
name.insert_str(0,BASE_PATH);
create_dir(name)
}

View File

@@ -1,13 +1,17 @@
use bollard::container::{StartContainerOptions, use bollard::container::{StartContainerOptions,
StopContainerOptions, StopContainerOptions,
CreateContainerOptions, CreateContainerOptions,
RemoveContainerOptions,
Config, Config,
NetworkingConfig, NetworkingConfig,
ListContainersOptions}; ListContainersOptions};
use bollard::models::{EndpointSettings, EndpointIpamConfig}; use bollard::models::{EndpointSettings, EndpointIpamConfig, HostConfig};
use bollard::errors::Error; use bollard::errors::{Error, Error::IOError};
use std::io::ErrorKind::AlreadyExists;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use crate::deploy::bind;
#[derive(Clone)]
pub struct Container { pub struct Container {
pub name: String, //Must be the domain name pub name: String, //Must be the domain name
pub start: bool, pub start: bool,
@@ -35,7 +39,6 @@ impl Container {
docker_id: docker_id, docker_id: docker_id,
database_id: None, database_id: None,
}; };
match container.start(docker).await { match container.start(docker).await {
Ok(c) => { Ok(c) => {
container.start=true; container.start=true;
@@ -46,12 +49,22 @@ impl Container {
} }
async fn start(&mut self, docker: bollard::Docker) -> Result<bool, Error>{ async fn start(&mut self, docker: bollard::Docker) -> Result<bool, Error>{
let ret = match &self.exist_container(docker.clone()).await? { match bind::check_fs(self.name.clone()) {
Some(_id) => true, Ok(_r) => (),
Err(ref e) if e.kind() == AlreadyExists => (),
Err(e) => return Err(IOError { err: e }),
}
let ret = match self.exist_container(docker.clone()).await? {
Some(id) => {
self.docker_id=Some(id);
true
},
None => { None => {
log::info!("creating container with name: {}", self.name);
self.docker_id = Some(docker.create_container(Some( self.docker_id = Some(docker.create_container(Some(
create_options(self.name.clone())), create_options(self.name.clone())),
create_config(self.ip.clone(), create_config(self.name.clone(),
self.ip.clone(),
self.net.clone(), self.net.clone(),
self.image.clone())) self.image.clone()))
.await?.id); .await?.id);
@@ -70,30 +83,40 @@ impl Container {
} }
async fn exist_container(&self, docker: bollard::Docker) -> Result<Option<String>, Error> { async fn exist_container(&self, docker: bollard::Docker) -> Result<Option<String>, Error> {
match &self.docker_id { let list = docker.list_containers(
Some(_id) => { Some(create_search_container(self.name.clone()))
let list = docker.list_containers(
Some(create_search_container(self.get_id().clone()))
).await?; ).await?;
Ok (if list.len() > 0 { let mut comparable_name = self.name.clone();
list[0].id.clone() comparable_name.insert_str(0, "/");
} else { for cs in list {
None match cs.names {
}) Some(names) => {
for name in names {
if name == comparable_name {
return Ok(cs.id.clone());
}
}
},
None => (),
} }
None => Ok(None),
} }
Ok(None)
} }
async fn stop(&self, docker: bollard::Docker) -> Result<(), Error>{ pub async fn stop(&self, docker: &bollard::Docker) -> Result<(), Error>{
docker.stop_container(self.name.as_str(), Some(stop_options())).await docker.stop_container(self.name.as_str(), Some(create_stop_options())).await
}
pub async fn remove(&self, docker: &bollard::Docker) -> Result<String, Error> {
docker.remove_container(self.get_id().as_str(), Some(create_remove_option())).await?;
Ok(self.docker_id.clone().unwrap())
} }
} }
fn create_search_container(name: String) -> ListContainersOptions<String> { fn create_search_container(name: String) -> ListContainersOptions<String> {
let mut filters = HashMap::new(); let mut filters = HashMap::new();
filters.insert("id".to_string(), vec![name]); filters.insert("name".to_string(), vec![name]);
ListContainersOptions{ ListContainersOptions{
all: true, all: true,
@@ -102,7 +125,7 @@ fn create_search_container(name: String) -> ListContainersOptions<String> {
} }
} }
fn create_config(ip: String, net: String, image: String) -> Config<String> { fn create_config(name: String, ip: String, net: String, image: String) -> Config<String> {
let endpoint = EndpointSettings { let endpoint = EndpointSettings {
ipam_config: Some(EndpointIpamConfig{ ipam_config: Some(EndpointIpamConfig{
ipv4_address: Some(ip), ipv4_address: Some(ip),
@@ -119,11 +142,19 @@ fn create_config(ip: String, net: String, image: String) -> Config<String> {
Config { Config {
image: Some(image), image: Some(image),
env: Some(env), env: Some(env),
host_config: Some(create_hostconfig(name)),
networking_config: Some(net), networking_config: Some(net),
..Default::default() ..Default::default()
} }
} }
fn create_hostconfig(name: String) -> HostConfig {
HostConfig {
binds: Some(bind::generate_bindpaths(name.clone())),
..Default::default()
}
}
fn create_options(name: String) -> CreateContainerOptions<String> { fn create_options(name: String) -> CreateContainerOptions<String> {
CreateContainerOptions{ CreateContainerOptions{
name: name, name: name,
@@ -131,8 +162,15 @@ fn create_options(name: String) -> CreateContainerOptions<String> {
} }
} }
fn stop_options() -> StopContainerOptions{ fn create_stop_options() -> StopContainerOptions {
StopContainerOptions { StopContainerOptions {
t: 30, t: 30,
} }
} }
fn create_remove_option() -> RemoveContainerOptions {
RemoveContainerOptions{
force: true,
..Default::default()
}
}

View File

@@ -1,2 +1,3 @@
pub mod container; pub mod container;
pub mod network; pub mod network;
mod bind;

View File

@@ -1,10 +1,20 @@
use actix_web::{get, post, put, web, App, HttpResponse, HttpServer, Responder}; use actix_web::{get, patch, post, put, delete, web, App, HttpResponse, HttpServer, Responder};
use bollard::Docker; use bollard::Docker;
use derive_more::{Display, Error};
use log::info;
use serde::Deserialize; use serde::Deserialize;
use env_logger; use env_logger;
use crate::controller::Controller; use crate::controller::Controller;
#[derive(Debug, Display, Error)]
#[display(fmt = "my error: {}", name)]
pub struct ReturnedError {
name: String,
}
impl actix_web::error::ResponseError for ReturnedError{}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ConainerParams { struct ConainerParams {
name: String, name: String,
@@ -12,6 +22,11 @@ struct ConainerParams {
image: String, image: String,
} }
#[derive(Debug, Deserialize)]
struct ConainerStopParams {
name: String,
}
#[get("/")] #[get("/")]
async fn hello() -> impl Responder { async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!") HttpResponse::Ok().body("Hello world!")
@@ -23,6 +38,24 @@ async fn create_container(controller: web::Data<Controller>,
controller.create_container(params.name.clone(), params.ip.clone(), params.image.clone()).await controller.create_container(params.name.clone(), params.ip.clone(), params.image.clone()).await
} }
#[patch("/container/stop")]
async fn stop_container(controller: web::Data<Controller>,
params: web::Query<ConainerStopParams>) -> Result<String, ReturnedError> {
match controller.stop_container(params.name.clone()).await {
Ok(response) => Ok(response),
Err(e) => Err(ReturnedError { name: e.to_string()})
}
}
#[delete("/container")]
async fn delete_container(controller: web::Data<Controller>,
params: web::Query<ConainerStopParams>) -> Result<String, ReturnedError> {
match controller.delete_container(params.name.clone()).await {
Ok(response) => Ok(response),
Err(e) => Err(ReturnedError { name: e.to_string()})
}
}
#[post("/echo")] #[post("/echo")]
async fn echo(req_body: String) -> impl Responder { async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body) HttpResponse::Ok().body(req_body)
@@ -37,6 +70,7 @@ pub async fn start() -> std::io::Result<()> {
Ok(d) => d, Ok(d) => d,
Err(e) => panic!("error:{}",e.to_string()), Err(e) => panic!("error:{}",e.to_string()),
}; };
env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug"));
let controller = match Controller::new(docker, let controller = match Controller::new(docker,
"customnetwork".to_string(), "customnetwork".to_string(),
"172.20.0.0/24".to_string()).await { "172.20.0.0/24".to_string()).await {
@@ -45,13 +79,14 @@ pub async fn start() -> std::io::Result<()> {
}; };
controller.load_all_instances().await; controller.load_all_instances().await;
let data = web::Data::new(controller); let data = web::Data::new(controller);
env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug"));
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(actix_web::middleware::Logger::default()) .wrap(actix_web::middleware::Logger::default())
.app_data(data.clone()) .app_data(data.clone())
.service(hello) .service(hello)
.service(create_container) .service(create_container)
.service(stop_container)
.service(delete_container)
.service(echo) .service(echo)
.route("/hey", web::get().to(manual_hello)) .route("/hey", web::get().to(manual_hello))
}) })
@@ -59,3 +94,7 @@ pub async fn start() -> std::io::Result<()> {
.run() .run()
.await .await
} }