restructure and work on docker client

This commit is contained in:
beckerinj
2022-11-05 22:22:27 -04:00
parent 6a4991354b
commit 797f33e0eb
19 changed files with 201 additions and 96 deletions

11
lib/db_client/Cargo.toml Normal file
View 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"

View 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
View 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)
}
}

View 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"

View File

@@ -0,0 +1,9 @@
use types::Build;
use crate::DockerClient;
impl DockerClient {
pub async fn build(&self, build: Build) -> anyhow::Result<()> {
todo!()
}
}

View 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)
}

View 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)
}
}

View 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]

View File

@@ -0,0 +1,3 @@
pub struct GitClient;
impl GitClient {}

13
lib/types/Cargo.toml Normal file
View 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
View 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
}
}