forked from github-starred/komodo
restructure and work on docker client
This commit is contained in:
11
lib/db_client/Cargo.toml
Normal file
11
lib/db_client/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "db_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../types" }
|
||||
mungos = "0.2.24"
|
||||
anyhow = "1.0"
|
||||
84
lib/db_client/src/collections.rs
Normal file
84
lib/db_client/src/collections.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use anyhow::Context;
|
||||
use mungos::{Collection, Mungos};
|
||||
use types::{Build, Deployment, Procedure, Server, Update, User};
|
||||
|
||||
pub async fn users_collection(mungos: &Mungos, db_name: &str) -> anyhow::Result<Collection<User>> {
|
||||
let coll = mungos.collection(db_name, "users");
|
||||
coll.create_unique_index("username")
|
||||
.await
|
||||
.context("failed at creating username index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
|
||||
pub async fn servers_collection(
|
||||
mungos: &Mungos,
|
||||
db_name: &str,
|
||||
) -> anyhow::Result<Collection<Server>> {
|
||||
let coll = mungos.collection(db_name, "servers");
|
||||
coll.create_unique_index("name")
|
||||
.await
|
||||
.context("failed at creating name index")?;
|
||||
coll.create_index("permissions")
|
||||
.await
|
||||
.context("failed at creating permissions index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
|
||||
pub async fn deployments_collection(
|
||||
mungos: &Mungos,
|
||||
db_name: &str,
|
||||
) -> anyhow::Result<Collection<Deployment>> {
|
||||
let coll = mungos.collection(db_name, "deployments");
|
||||
coll.create_unique_index("name")
|
||||
.await
|
||||
.context("failed at creating name index")?;
|
||||
coll.create_index("permissions")
|
||||
.await
|
||||
.context("failed at creating permissions index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
|
||||
pub async fn builds_collection(
|
||||
mungos: &Mungos,
|
||||
db_name: &str,
|
||||
) -> anyhow::Result<Collection<Build>> {
|
||||
let coll = mungos.collection(db_name, "builds");
|
||||
coll.create_unique_index("name")
|
||||
.await
|
||||
.context("failed at creating name index")?;
|
||||
coll.create_index("permissions")
|
||||
.await
|
||||
.context("failed at creating permissions index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
|
||||
pub async fn updates_collection(
|
||||
mungos: &Mungos,
|
||||
db_name: &str,
|
||||
) -> anyhow::Result<Collection<Update>> {
|
||||
let coll = mungos.collection(db_name, "updates");
|
||||
coll.create_index("entity_id")
|
||||
.await
|
||||
.context("failed at creating entity_id index")?;
|
||||
coll.create_index("ts")
|
||||
.await
|
||||
.context("failed at creating ts index")?;
|
||||
coll.create_index("operator")
|
||||
.await
|
||||
.context("failed at creating operator index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
|
||||
pub async fn procedures_collection(
|
||||
mungos: &Mungos,
|
||||
db_name: &str,
|
||||
) -> anyhow::Result<Collection<Procedure>> {
|
||||
let coll = mungos.collection(db_name, "procedures");
|
||||
coll.create_index("name")
|
||||
.await
|
||||
.context("failed at creating entity_id index")?;
|
||||
coll.create_index("permissions")
|
||||
.await
|
||||
.context("failed at creating permissions index")?;
|
||||
Ok(coll)
|
||||
}
|
||||
34
lib/db_client/src/lib.rs
Normal file
34
lib/db_client/src/lib.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use collections::{
|
||||
builds_collection, deployments_collection, procedures_collection, servers_collection,
|
||||
updates_collection, users_collection,
|
||||
};
|
||||
use mungos::{Collection, Mungos};
|
||||
use types::{Build, Deployment, Procedure, Server, Update, User};
|
||||
|
||||
mod collections;
|
||||
|
||||
pub struct DbClient {
|
||||
pub users: Collection<User>,
|
||||
pub servers: Collection<Server>,
|
||||
pub deployments: Collection<Deployment>,
|
||||
pub builds: Collection<Build>,
|
||||
pub procedures: Collection<Procedure>,
|
||||
pub updates: Collection<Update>,
|
||||
}
|
||||
|
||||
impl DbClient {
|
||||
pub async fn new(mongo_uri: &str, app_name: &str, db_name: &str) -> anyhow::Result<DbClient> {
|
||||
let mungos = Mungos::new(mongo_uri, app_name, Duration::from_secs(3), None).await?;
|
||||
let client = DbClient {
|
||||
users: users_collection(&mungos, db_name).await?,
|
||||
servers: servers_collection(&mungos, db_name).await?,
|
||||
deployments: deployments_collection(&mungos, db_name).await?,
|
||||
builds: builds_collection(&mungos, db_name).await?,
|
||||
updates: updates_collection(&mungos, db_name).await?,
|
||||
procedures: procedures_collection(&mungos, db_name).await?,
|
||||
};
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
13
lib/docker_client/Cargo.toml
Normal file
13
lib/docker_client/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "docker_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../types" }
|
||||
run_command = { version = "0.0.5", features = ["async_tokio"] }
|
||||
bollard = "0.13"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
9
lib/docker_client/src/build.rs
Normal file
9
lib/docker_client/src/build.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use types::Build;
|
||||
|
||||
use crate::DockerClient;
|
||||
|
||||
impl DockerClient {
|
||||
pub async fn build(&self, build: Build) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
45
lib/docker_client/src/deploy.rs
Normal file
45
lib/docker_client/src/deploy.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use run_command::{async_run_command, CommandOutput};
|
||||
use types::{Deployment, Log};
|
||||
|
||||
use crate::DockerClient;
|
||||
|
||||
impl DockerClient {
|
||||
pub async fn deploy(&self, deployment: &Deployment) -> (bool, Log) {
|
||||
let docker_run = docker_run_command(deployment);
|
||||
let output = async_run_command(&docker_run).await;
|
||||
output_into_log("docker run", output)
|
||||
}
|
||||
|
||||
pub async fn docker_start_command(&self, container_name: &str) -> (bool, Log) {
|
||||
let command = format!("start stop {container_name}");
|
||||
let output = async_run_command(&command).await;
|
||||
output_into_log("docker stop", output)
|
||||
}
|
||||
|
||||
pub async fn docker_stop_command(&self, container_name: &str) -> (bool, Log) {
|
||||
let command = format!("docker stop {container_name}");
|
||||
let output = async_run_command(&command).await;
|
||||
output_into_log("docker stop", output)
|
||||
}
|
||||
|
||||
pub async fn docker_stop_and_remove(&self, container_name: &str) -> (bool, Log) {
|
||||
let command =
|
||||
format!("docker stop {container_name} && docker container rm {container_name}");
|
||||
let output = async_run_command(&command).await;
|
||||
output_into_log("docker stop and remove", output)
|
||||
}
|
||||
}
|
||||
|
||||
fn docker_run_command(deployment: &Deployment) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn output_into_log(stage: &str, output: CommandOutput) -> (bool, Log) {
|
||||
let success = output.success();
|
||||
let log = Log {
|
||||
stage: stage.to_string(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
};
|
||||
(success, log)
|
||||
}
|
||||
44
lib/docker_client/src/lib.rs
Normal file
44
lib/docker_client/src/lib.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use anyhow::anyhow;
|
||||
use bollard::{container::ListContainersOptions, Docker};
|
||||
use types::BasicContainerInfo;
|
||||
|
||||
mod build;
|
||||
mod deploy;
|
||||
|
||||
pub struct DockerClient {
|
||||
client: Docker,
|
||||
}
|
||||
|
||||
impl DockerClient {
|
||||
pub fn new() -> anyhow::Result<DockerClient> {
|
||||
Ok(DockerClient {
|
||||
client: Docker::connect_with_local_defaults()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_containers(&self) -> anyhow::Result<Vec<BasicContainerInfo>> {
|
||||
let res = self
|
||||
.client
|
||||
.list_containers(Some(ListContainersOptions::<String> {
|
||||
all: true,
|
||||
..Default::default()
|
||||
}))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let info = BasicContainerInfo {
|
||||
name: s
|
||||
.names
|
||||
.ok_or(anyhow!("no names on container"))?
|
||||
.pop()
|
||||
.ok_or(anyhow!("no names on container (empty vec)"))?
|
||||
.replace("/", ""),
|
||||
state: s.state.unwrap().parse().unwrap(),
|
||||
status: s.status,
|
||||
};
|
||||
Ok::<_, anyhow::Error>(info)
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<BasicContainerInfo>>>()?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
8
lib/git_client/Cargo.toml
Normal file
8
lib/git_client/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "git_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
3
lib/git_client/src/lib.rs
Normal file
3
lib/git_client/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub struct GitClient;
|
||||
|
||||
impl GitClient {}
|
||||
13
lib/types/Cargo.toml
Normal file
13
lib/types/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
mungos = "0.2.24"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
275
lib/types/src/lib.rs
Normal file
275
lib/types/src/lib.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mungos::ObjectId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{Display, EnumString};
|
||||
|
||||
pub type PermissionsMap = HashMap<String, PermissionLevel>;
|
||||
|
||||
pub type ServerId = String;
|
||||
pub type DeploymentId = String;
|
||||
pub type BuildId = String;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct User {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub username: String,
|
||||
pub enabled: bool,
|
||||
pub admin: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar: Option<String>,
|
||||
|
||||
// used with auth
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub password: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub github_id: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub google_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Server {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub name: String,
|
||||
pub host: String,
|
||||
pub permissions: PermissionsMap,
|
||||
pub to_notify: Vec<String>,
|
||||
pub cpu_alert: f64,
|
||||
pub mem_alert: f64,
|
||||
pub disk_alert: f64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub passkey: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_core: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stats_interval: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub region: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub instance_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
name: String::new(),
|
||||
host: String::new(),
|
||||
permissions: HashMap::new(),
|
||||
to_notify: Vec::new(),
|
||||
cpu_alert: 50.0,
|
||||
mem_alert: 75.0,
|
||||
disk_alert: 75.0,
|
||||
passkey: None,
|
||||
is_core: None,
|
||||
stats_interval: None,
|
||||
region: None,
|
||||
instance_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Deployment {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub name: String, // must be formatted to be compat with docker
|
||||
pub server_id: ServerId,
|
||||
pub permissions: PermissionsMap,
|
||||
pub docker_run_args: DockerRunArgs,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_core: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build_id: Option<BuildId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct Build {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub name: String,
|
||||
pub permissions: PermissionsMap,
|
||||
pub version: Version,
|
||||
|
||||
// git related
|
||||
pub repo: Option<String>,
|
||||
pub branch: Option<String>,
|
||||
pub github_account: Option<String>,
|
||||
pub on_clone: Option<Command>,
|
||||
|
||||
// build related
|
||||
pub pre_build: Option<Command>,
|
||||
pub docker_build_args: Option<DockerBuildArgs>,
|
||||
pub docker_account: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct BuildRecord {
|
||||
pub start_ts: i64,
|
||||
pub end_ts: i64,
|
||||
pub successful: bool,
|
||||
pub logs: Vec<Log>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<Version>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Update {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub entity_type: EntityType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub entity_id: Option<String>,
|
||||
pub operation: Operation,
|
||||
pub command: String,
|
||||
pub log: Vec<Log>,
|
||||
pub ts: i64,
|
||||
pub is_error: bool,
|
||||
pub operator: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Procedure {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
pub name: String,
|
||||
pub procedure: Vec<Operation>,
|
||||
pub permissions: PermissionsMap,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DockerBuildArgs {
|
||||
pub build_path: String,
|
||||
pub dockerfile_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct DockerRunArgs {
|
||||
pub image: Option<String>,
|
||||
pub ports: Vec<Conversion>,
|
||||
pub volumes: Vec<Conversion>,
|
||||
pub environment: Vec<EnvironmentVar>,
|
||||
pub network: Option<String>,
|
||||
pub restart: RestartMode,
|
||||
pub post_image: Option<String>,
|
||||
pub container_user: Option<String>,
|
||||
pub docker_account: Option<String>, // the username of the dockerhub account
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BasicContainerInfo {
|
||||
pub name: String,
|
||||
pub state: ContainerState,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct Log {
|
||||
pub stage: String,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Command {
|
||||
pub path: String,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct Version {
|
||||
pub major: u64,
|
||||
pub minor: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct Conversion {
|
||||
pub local: String,
|
||||
pub container: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct EnvironmentVar {
|
||||
pub variable: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Permission {
|
||||
pub entity_type: EntityType,
|
||||
pub id: String,
|
||||
pub level: PermissionLevel,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum EntityType {
|
||||
System,
|
||||
Build,
|
||||
Deployment,
|
||||
Server,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Operation {
|
||||
// server
|
||||
PruneImagesServer,
|
||||
PruneContainersServer,
|
||||
PruneNetworksServer,
|
||||
|
||||
// build
|
||||
BuildBuild,
|
||||
RecloneBuild,
|
||||
|
||||
// deployment
|
||||
DeployDeployment,
|
||||
StopDeployment,
|
||||
StartDeployment,
|
||||
PullDeployment,
|
||||
RecloneDeployment,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum PermissionLevel {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ContainerState {
|
||||
Created,
|
||||
Restarting,
|
||||
Running,
|
||||
Removing,
|
||||
Paused,
|
||||
Exited,
|
||||
Dead,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum RestartMode {
|
||||
#[serde(rename = "no")]
|
||||
NoRestart,
|
||||
#[serde(rename = "on-failure")]
|
||||
OnFailure,
|
||||
#[serde(rename = "always")]
|
||||
Always,
|
||||
#[serde(rename = "unless-stopped")]
|
||||
UnlessStopped,
|
||||
}
|
||||
|
||||
impl Default for RestartMode {
|
||||
fn default() -> RestartMode {
|
||||
RestartMode::NoRestart
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user