mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-29 21:27:26 -05:00
implement docker build
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ node_modules
|
||||
dist
|
||||
.env
|
||||
|
||||
repos
|
||||
config.json
|
||||
config.toml
|
||||
secrets.json
|
||||
|
||||
86
lib/helpers/src/docker/build.rs
Normal file
86
lib/helpers/src/docker/build.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use run_command::async_run_command;
|
||||
use types::{Build, DockerBuildArgs, Log};
|
||||
|
||||
use crate::{git, run_monitor_command, to_monitor_name};
|
||||
|
||||
pub async fn prune_images() -> Log {
|
||||
let command = format!("docker image prune -a -f");
|
||||
run_monitor_command("prune images", command).await
|
||||
}
|
||||
|
||||
pub async fn build(
|
||||
Build {
|
||||
name,
|
||||
version,
|
||||
docker_build_args,
|
||||
branch,
|
||||
docker_account,
|
||||
..
|
||||
}: &Build,
|
||||
repo_dir: &str,
|
||||
docker_token: Option<String>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
let DockerBuildArgs {
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
} = docker_build_args
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("build missing docker build args"))?;
|
||||
let name = to_monitor_name(name);
|
||||
let docker_account_pw = get_docker_username_pw(docker_account, &docker_token)?;
|
||||
let repo_dir = PathBuf::from_str(repo_dir)
|
||||
.context(format!("invalid repo dir: {repo_dir}"))?
|
||||
.join(&name);
|
||||
let pull_log = git::pull(
|
||||
&repo_dir
|
||||
.to_str()
|
||||
.context(format!("invalid repo dir: {}", repo_dir.display()))?,
|
||||
branch,
|
||||
)
|
||||
.await;
|
||||
if let Some((username, password)) = &docker_account_pw {
|
||||
let login = format!("docker login -u {username} -p {password}");
|
||||
async_run_command(&login).await;
|
||||
}
|
||||
let build_dir = repo_dir.join(build_path);
|
||||
let cd = build_dir.display();
|
||||
let dockerfile_path = match dockerfile_path {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
let image_name = get_image_name(docker_account, &name);
|
||||
let docker_push = if docker_account_pw.is_some() {
|
||||
format!(" && docker push {image_name}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let command =
|
||||
format!("cd {cd} && docker build -t {image_name} -f {dockerfile_path} .{docker_push}");
|
||||
let build_log = run_monitor_command("docker build", command).await;
|
||||
Ok(vec![pull_log, build_log])
|
||||
}
|
||||
|
||||
fn get_docker_username_pw(
|
||||
docker_account: &Option<String>,
|
||||
docker_token: &Option<String>,
|
||||
) -> anyhow::Result<Option<(String, String)>> {
|
||||
match docker_account {
|
||||
Some(docker_account) => match docker_token {
|
||||
Some(docker_token) => Ok(Some((docker_account.to_owned(), docker_token.to_owned()))),
|
||||
None => Err(anyhow!(
|
||||
"docker token for account {docker_account} has not been configured on this client"
|
||||
)),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image_name(docker_account: &Option<String>, name: &str) -> String {
|
||||
match docker_account {
|
||||
Some(docker_account) => format!("{docker_account}/{name}"),
|
||||
None => name.to_string(),
|
||||
}
|
||||
}
|
||||
49
lib/helpers/src/docker/client.rs
Normal file
49
lib/helpers/src/docker/client.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::Extension;
|
||||
use bollard::{container::ListContainersOptions, Docker};
|
||||
use types::BasicContainerInfo;
|
||||
|
||||
pub type DockerExtension = Extension<Arc<DockerClient>>;
|
||||
|
||||
pub struct DockerClient {
|
||||
docker: Docker,
|
||||
}
|
||||
|
||||
impl DockerClient {
|
||||
pub fn extension() -> DockerExtension {
|
||||
let client = DockerClient {
|
||||
docker: Docker::connect_with_local_defaults()
|
||||
.expect("failed to connect to docker daemon"),
|
||||
};
|
||||
Extension(Arc::new(client))
|
||||
}
|
||||
|
||||
pub async fn list_containers(&self) -> anyhow::Result<Vec<BasicContainerInfo>> {
|
||||
let res = self
|
||||
.docker
|
||||
.list_containers(Some(ListContainersOptions::<String> {
|
||||
all: true,
|
||||
..Default::default()
|
||||
}))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let info = BasicContainerInfo {
|
||||
id: s.id.unwrap_or_default(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::Extension;
|
||||
use bollard::{container::ListContainersOptions, Docker};
|
||||
use run_command::async_run_command;
|
||||
use types::{
|
||||
BasicContainerInfo, Build, Conversion, Deployment, DockerContainerStats, DockerRunArgs,
|
||||
EnvironmentVar, Log, RestartMode,
|
||||
Conversion, Deployment, DockerContainerStats, DockerRunArgs, EnvironmentVar, Log, RestartMode,
|
||||
};
|
||||
|
||||
use crate::run_monitor_command;
|
||||
|
||||
pub type DockerExtension = Extension<Arc<DockerClient>>;
|
||||
|
||||
pub struct DockerClient {
|
||||
docker: Docker,
|
||||
}
|
||||
|
||||
impl DockerClient {
|
||||
pub fn extension() -> DockerExtension {
|
||||
let client = DockerClient {
|
||||
docker: Docker::connect_with_local_defaults()
|
||||
.expect("failed to connect to docker daemon"),
|
||||
};
|
||||
Extension(Arc::new(client))
|
||||
}
|
||||
|
||||
pub async fn list_containers(&self) -> anyhow::Result<Vec<BasicContainerInfo>> {
|
||||
let res = self
|
||||
.docker
|
||||
.list_containers(Some(ListContainersOptions::<String> {
|
||||
all: true,
|
||||
..Default::default()
|
||||
}))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let info = BasicContainerInfo {
|
||||
id: s.id.unwrap_or_default(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// CONTAINER COMMANDS
|
||||
|
||||
pub fn parse_container_name(name: &str) -> String {
|
||||
name.to_lowercase().replace(" ", "_")
|
||||
}
|
||||
use crate::{run_monitor_command, to_monitor_name};
|
||||
|
||||
pub async fn container_log(container_name: &str, tail: Option<u64>) -> Log {
|
||||
let tail = match tail {
|
||||
@@ -95,25 +41,25 @@ pub async fn prune_containers() -> Log {
|
||||
}
|
||||
|
||||
pub async fn start_container(container_name: &str) -> Log {
|
||||
let container_name = parse_container_name(container_name);
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let command = format!("docker start {container_name}");
|
||||
run_monitor_command("docker start", command).await
|
||||
}
|
||||
|
||||
pub async fn stop_container(container_name: &str) -> Log {
|
||||
let container_name = parse_container_name(container_name);
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let command = format!("docker stop {container_name}");
|
||||
run_monitor_command("docker stop", command).await
|
||||
}
|
||||
|
||||
pub async fn stop_and_remove_container(container_name: &str) -> Log {
|
||||
let container_name = parse_container_name(container_name);
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let command = format!("docker stop {container_name} && docker container rm {container_name}");
|
||||
run_monitor_command("docker stop and remove", command).await
|
||||
}
|
||||
|
||||
pub async fn deploy(deployment: &Deployment) -> Log {
|
||||
let _ = stop_and_remove_container(&parse_container_name(&deployment.name)).await;
|
||||
let _ = stop_and_remove_container(&to_monitor_name(&deployment.name)).await;
|
||||
let command = docker_run_command(deployment);
|
||||
run_monitor_command("docker run", command).await
|
||||
}
|
||||
@@ -136,7 +82,7 @@ pub fn docker_run_command(
|
||||
..
|
||||
}: &Deployment,
|
||||
) -> String {
|
||||
let name = parse_container_name(name);
|
||||
let name = to_monitor_name(name);
|
||||
let container_user = parse_container_user(container_user);
|
||||
let ports = parse_conversions(ports, "-p");
|
||||
let volumes = parse_conversions(volumes, "-v");
|
||||
@@ -194,35 +140,3 @@ fn parse_post_image(post_image: &Option<String>) -> String {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
// BUILD COMMANDS
|
||||
|
||||
pub async fn build(_build: &Build) -> Log {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn prune_images() -> Log {
|
||||
let command = format!("docker image prune -a -f");
|
||||
run_monitor_command("prune images", command).await
|
||||
}
|
||||
|
||||
// NETWORKS
|
||||
|
||||
pub async fn create_network(name: &str, driver: Option<String>) -> Log {
|
||||
let driver = match driver {
|
||||
Some(driver) => format!(" -d {driver}"),
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
run_monitor_command("create network", command).await
|
||||
}
|
||||
|
||||
pub async fn delete_network(name: &str) -> Log {
|
||||
let command = format!("docker network rm {name}");
|
||||
run_monitor_command("delete network", command).await
|
||||
}
|
||||
|
||||
pub async fn prune_networks() -> Log {
|
||||
let command = format!("docker network prune -f");
|
||||
run_monitor_command("prune networks", command).await
|
||||
}
|
||||
9
lib/helpers/src/docker/mod.rs
Normal file
9
lib/helpers/src/docker/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod build;
|
||||
mod client;
|
||||
mod container;
|
||||
mod network;
|
||||
|
||||
pub use build::*;
|
||||
pub use client::*;
|
||||
pub use container::*;
|
||||
pub use network::*;
|
||||
22
lib/helpers/src/docker/network.rs
Normal file
22
lib/helpers/src/docker/network.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use types::Log;
|
||||
|
||||
use crate::run_monitor_command;
|
||||
|
||||
pub async fn create_network(name: &str, driver: Option<String>) -> Log {
|
||||
let driver = match driver {
|
||||
Some(driver) => format!(" -d {driver}"),
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
run_monitor_command("create network", command).await
|
||||
}
|
||||
|
||||
pub async fn delete_network(name: &str) -> Log {
|
||||
let command = format!("docker network rm {name}");
|
||||
run_monitor_command("delete network", command).await
|
||||
}
|
||||
|
||||
pub async fn prune_networks() -> Log {
|
||||
let command = format!("docker network prune -f");
|
||||
run_monitor_command("prune networks", command).await
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use async_timing_util::unix_timestamp_ms;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{Build, Command, Deployment, GithubToken, GithubUsername, Log};
|
||||
|
||||
use crate::run_monitor_command;
|
||||
use crate::{run_monitor_command, to_monitor_name};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct CloneArgs {
|
||||
@@ -41,6 +41,15 @@ impl From<&Build> for CloneArgs {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn pull(path: &str, branch: &Option<String>) -> Log {
|
||||
let branch = match branch {
|
||||
Some(branch) => branch.to_owned(),
|
||||
None => "main".to_string(),
|
||||
};
|
||||
let command = format!("cd path && git pull origin {branch}");
|
||||
run_monitor_command("git pull", command).await
|
||||
}
|
||||
|
||||
pub async fn clone_repo(
|
||||
clone_args: impl Into<CloneArgs>,
|
||||
repo_dir: &str,
|
||||
@@ -55,6 +64,7 @@ pub async fn clone_repo(
|
||||
} = clone_args.into();
|
||||
let repo = repo.as_ref().ok_or(anyhow!("build has no repo attached"))?;
|
||||
let mut repo_dir = PathBuf::from_str(repo_dir)?;
|
||||
let name = to_monitor_name(&name);
|
||||
repo_dir.push(name);
|
||||
let destination = repo_dir.display().to_string();
|
||||
let clone_log = clone(repo, &destination, &branch, access_token).await;
|
||||
|
||||
@@ -42,6 +42,10 @@ pub fn get_socket_addr(port: u16) -> SocketAddr {
|
||||
SocketAddr::from_str(&format!("0.0.0.0:{}", port)).expect("failed to parse socket addr")
|
||||
}
|
||||
|
||||
pub fn to_monitor_name(name: &str) -> String {
|
||||
name.to_lowercase().replace(" ", "_")
|
||||
}
|
||||
|
||||
pub async fn run_monitor_command(stage: &str, command: String) -> Log {
|
||||
let start_ts = unix_timestamp_ms() as i64;
|
||||
let output = async_run_command(&command).await;
|
||||
|
||||
1
periphery/src/api/build.rs
Normal file
1
periphery/src/api/build.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -5,8 +5,8 @@ use axum::{
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use helpers::{
|
||||
docker::{self, parse_container_name, DockerClient, DockerExtension},
|
||||
handle_anyhow_error,
|
||||
docker::{self, DockerClient, DockerExtension},
|
||||
handle_anyhow_error, to_monitor_name,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use types::Deployment;
|
||||
@@ -30,20 +30,20 @@ pub fn router() -> Router {
|
||||
.route(
|
||||
"/start",
|
||||
post(|Json(container): Json<Container>| async move {
|
||||
Json(docker::start_container(&parse_container_name(&container.name)).await)
|
||||
Json(docker::start_container(&to_monitor_name(&container.name)).await)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/stop",
|
||||
post(|Json(container): Json<Container>| async move {
|
||||
Json(docker::stop_container(&parse_container_name(&container.name)).await)
|
||||
Json(docker::stop_container(&to_monitor_name(&container.name)).await)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/remove",
|
||||
post(|Json(container): Json<Container>| async move {
|
||||
Json(
|
||||
docker::stop_and_remove_container(&parse_container_name(&container.name)).await,
|
||||
docker::stop_and_remove_container(&to_monitor_name(&container.name)).await,
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
mod accounts;
|
||||
mod build;
|
||||
mod container;
|
||||
mod git;
|
||||
mod network;
|
||||
|
||||
@@ -19,6 +19,7 @@ pub fn load() -> (u16, PeripheryConfigExtension) {
|
||||
let env: Env = envy::from_env().expect("failed to parse env");
|
||||
let config: PeripheryConfig =
|
||||
parse_config_file(&env.config_path).expect("failed to parse config file");
|
||||
let _ = std::fs::create_dir(&config.repo_dir);
|
||||
print_startup_log(&config);
|
||||
(config.port, Extension(Arc::new(config)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user