From 35b3f4920b6fc50635dcdc898a8a2b5bf614f2ef Mon Sep 17 00:00:00 2001 From: Niko Reunanen Date: Tue, 11 Feb 2025 18:38:33 +0200 Subject: [PATCH] Pull image from public registry --- src/bin/main.rs | 114 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 6b61be0..46163eb 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,25 +1,24 @@ use std::{process::ExitCode, sync::Arc, time::Duration}; use axum::{ - extract::State, - http::StatusCode, - routing::{get, post}, - Json, Router, + extract::State, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Json, Router }; use bollard::{ container::{CreateContainerOptions, StartContainerOptions}, - image::BuildImageOptions, + image::{BuildImageOptions, CreateImageOptions}, Docker, }; use futures_util::stream::StreamExt; use git2::Repository; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio::{signal, sync::Mutex}; use tower_http::{timeout::TimeoutLayer, trace::TraceLayer}; use tracing::level_filters::LevelFilter; struct AppState { docker: Docker, + pub(crate) image: Option, + pub(crate) tag: Option, pub(crate) name: Option, } @@ -45,6 +44,51 @@ struct CdBuild { serve: bool, } +#[derive(Serialize)] +struct MsgStatus { + image: Option, + tag: Option, + name: Option, +} + +#[derive(Deserialize)] +struct Selector { + image: String, + tag: String, +} + +async fn pull( + State(state): State>>, + Json(cd): Json, +) -> Response { + let mtx = state.lock().await; + + let options = Some(CreateImageOptions{ + from_image: format!("{}:{}", cd.image, cd.tag), + ..Default::default() + }); + + let mut image_create_stream = mtx.docker.create_image(options, None, None); + while let Some(msg) = image_create_stream.next().await { + if let Ok(msg) = msg { + tracing::info!("docker create image: {} {}", msg.status.unwrap_or_default(), msg.progress.unwrap_or_default()); + } + } + + StatusCode::OK.into_response() +} + +async fn status( + State(state): State>>, +) -> Json { + let mtx = state.lock().await; + Json(MsgStatus { + name: mtx.name.clone(), + image: mtx.image.clone(), + tag: mtx.tag.clone(), + }) +} + async fn build( State(state): State>>, Json(cd): Json, @@ -105,25 +149,25 @@ async fn build( (StatusCode::OK, "ok") } -async fn stop(State(state): State>>) -> (StatusCode, &'static str) { +async fn stop(State(state): State>>) -> (StatusCode, String) { let mut mtx = state.lock().await; - if let Some(name) = &mtx.name { + if let Some(name) = mtx.name.clone() { tracing::info!("stopping container {name}"); - match mtx.docker.stop_container(name, None).await { + match mtx.docker.stop_container(&name, None).await { Ok(()) => { mtx.name = None; - tracing::info!("container stopped"); - (StatusCode::OK, "container stopped") + tracing::info!("container {name} stopped"); + (StatusCode::OK, format!("container {name} stopped")) } Err(error) => { tracing::error!("stop_container: {error}"); - (StatusCode::INTERNAL_SERVER_ERROR, "failed to stop") + (StatusCode::INTERNAL_SERVER_ERROR, "failed to stop".into()) } } } else { - tracing::warn!("stop: container is not running"); - (StatusCode::OK, "stop: container is not running") + tracing::warn!("no running container available"); + (StatusCode::OK, "no running container available".into()) } } @@ -146,14 +190,10 @@ async fn serve_container( }); let config = bollard::container::Config { - image: Some(cd.tag), + image: Some(format!("{}:{}", cd.image, cd.tag)), ..bollard::container::Config::default() }; - tracing::info!("create_container"); - tracing::info!("{options:?}"); - tracing::info!("{config:?}"); - if let Err(error) = mtx.docker.create_container(options, config).await { tracing::error!("create_container: {error}"); return (StatusCode::INTERNAL_SERVER_ERROR, "create_container"); @@ -169,6 +209,8 @@ async fn serve_container( } mtx.name = Some(cd.name); + mtx.tag = Some(cd.tag); + mtx.image = Some(cd.image); (StatusCode::OK, "ok") } @@ -195,6 +237,14 @@ async fn shutdown_signal() { #[tokio::main] async fn main() -> ExitCode { + if let Err(error) = tracing_subscriber::fmt() + .with_max_level(LevelFilter::INFO) + .try_init() + { + eprintln!("failed to register a global tracing logger: {error}"); + return ExitCode::FAILURE; + } + let docker = match Docker::connect_with_socket_defaults() { Ok(docker) => docker, Err(error) => { @@ -204,7 +254,9 @@ async fn main() -> ExitCode { }; match docker.version().await { - Ok(version) => println!("docker version: {version:?}"), + Ok(version) => { + tracing::info!("connected to docker version {}", version.version.unwrap_or_default()); + }, Err(error) => { eprintln!("failed to read docker version: {error}"); return ExitCode::FAILURE; @@ -213,14 +265,6 @@ async fn main() -> ExitCode { let bind = std::env::var("BIND").unwrap_or("0.0.0.0:8080".into()); - if let Err(error) = tracing_subscriber::fmt() - .with_max_level(LevelFilter::DEBUG) - .try_init() - { - eprintln!("failed to register a global tracing logger: {error}"); - return ExitCode::FAILURE; - } - tracing::info!("launching microdeploy on {bind}"); let listener = match tokio::net::TcpListener::bind(&bind).await { @@ -231,19 +275,21 @@ async fn main() -> ExitCode { } }; - let shared_state = Arc::new(Mutex::new(AppState { docker, name: None })); + let shared_state = Arc::new(Mutex::new(AppState {docker, name: None, image: None, tag: None })); let app = Router::new() .route("/api/v1/build", post(build)) - .route("/api/v1/serve", get(serve)) + .route("/api/v1/serve", post(serve)) .route("/api/v1/stop", get(stop)) + .route("/api/v1/status", get(status)) + .route("/api/v1/pull", post(pull)) .layer(( TraceLayer::new_for_http(), TimeoutLayer::new(Duration::from_secs(10)), )) .with_state(shared_state); - match axum::serve(listener, app) + let exit_code = match axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await { @@ -252,5 +298,9 @@ async fn main() -> ExitCode { tracing::error!("axum::serve: {error}"); ExitCode::FAILURE } - } + }; + + tracing::info!("shutting down"); + + exit_code }