implement docker build

This commit is contained in:
beckerinj
2022-11-13 23:19:41 -05:00
parent 475f6774bf
commit a7abca2038
12 changed files with 197 additions and 99 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
dist
.env
repos
config.json
config.toml
secrets.json

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

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

View File

@@ -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
}

View 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::*;

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

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1 @@

View File

@@ -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,
)
}),
)

View File

@@ -1,6 +1,7 @@
use axum::{routing::get, Router};
mod accounts;
mod build;
mod container;
mod git;
mod network;

View File

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