Pull image from public registry
This commit is contained in:
parent
f3366b3b2b
commit
35b3f4920b
1 changed files with 82 additions and 32 deletions
114
src/bin/main.rs
114
src/bin/main.rs
|
|
@ -1,25 +1,24 @@
|
||||||
use std::{process::ExitCode, sync::Arc, time::Duration};
|
use std::{process::ExitCode, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Json, Router
|
||||||
http::StatusCode,
|
|
||||||
routing::{get, post},
|
|
||||||
Json, Router,
|
|
||||||
};
|
};
|
||||||
use bollard::{
|
use bollard::{
|
||||||
container::{CreateContainerOptions, StartContainerOptions},
|
container::{CreateContainerOptions, StartContainerOptions},
|
||||||
image::BuildImageOptions,
|
image::{BuildImageOptions, CreateImageOptions},
|
||||||
Docker,
|
Docker,
|
||||||
};
|
};
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{signal, sync::Mutex};
|
use tokio::{signal, sync::Mutex};
|
||||||
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
|
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
docker: Docker,
|
docker: Docker,
|
||||||
|
pub(crate) image: Option<String>,
|
||||||
|
pub(crate) tag: Option<String>,
|
||||||
pub(crate) name: Option<String>,
|
pub(crate) name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +44,51 @@ struct CdBuild {
|
||||||
serve: bool,
|
serve: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct MsgStatus {
|
||||||
|
image: Option<String>,
|
||||||
|
tag: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Selector {
|
||||||
|
image: String,
|
||||||
|
tag: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn pull(
|
||||||
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
|
Json(cd): Json<Selector>,
|
||||||
|
) -> 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<Arc<Mutex<AppState>>>,
|
||||||
|
) -> Json<MsgStatus> {
|
||||||
|
let mtx = state.lock().await;
|
||||||
|
Json(MsgStatus {
|
||||||
|
name: mtx.name.clone(),
|
||||||
|
image: mtx.image.clone(),
|
||||||
|
tag: mtx.tag.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn build(
|
async fn build(
|
||||||
State(state): State<Arc<Mutex<AppState>>>,
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
Json(cd): Json<CdBuild>,
|
Json(cd): Json<CdBuild>,
|
||||||
|
|
@ -105,25 +149,25 @@ async fn build(
|
||||||
(StatusCode::OK, "ok")
|
(StatusCode::OK, "ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stop(State(state): State<Arc<Mutex<AppState>>>) -> (StatusCode, &'static str) {
|
async fn stop(State(state): State<Arc<Mutex<AppState>>>) -> (StatusCode, String) {
|
||||||
let mut mtx = state.lock().await;
|
let mut mtx = state.lock().await;
|
||||||
|
|
||||||
if let Some(name) = &mtx.name {
|
if let Some(name) = mtx.name.clone() {
|
||||||
tracing::info!("stopping container {name}");
|
tracing::info!("stopping container {name}");
|
||||||
match mtx.docker.stop_container(name, None).await {
|
match mtx.docker.stop_container(&name, None).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
mtx.name = None;
|
mtx.name = None;
|
||||||
tracing::info!("container stopped");
|
tracing::info!("container {name} stopped");
|
||||||
(StatusCode::OK, "container stopped")
|
(StatusCode::OK, format!("container {name} stopped"))
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!("stop_container: {error}");
|
tracing::error!("stop_container: {error}");
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, "failed to stop")
|
(StatusCode::INTERNAL_SERVER_ERROR, "failed to stop".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("stop: container is not running");
|
tracing::warn!("no running container available");
|
||||||
(StatusCode::OK, "stop: container is not running")
|
(StatusCode::OK, "no running container available".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,14 +190,10 @@ async fn serve_container(
|
||||||
});
|
});
|
||||||
|
|
||||||
let config = bollard::container::Config {
|
let config = bollard::container::Config {
|
||||||
image: Some(cd.tag),
|
image: Some(format!("{}:{}", cd.image, cd.tag)),
|
||||||
..bollard::container::Config::default()
|
..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 {
|
if let Err(error) = mtx.docker.create_container(options, config).await {
|
||||||
tracing::error!("create_container: {error}");
|
tracing::error!("create_container: {error}");
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, "create_container");
|
return (StatusCode::INTERNAL_SERVER_ERROR, "create_container");
|
||||||
|
|
@ -169,6 +209,8 @@ async fn serve_container(
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx.name = Some(cd.name);
|
mtx.name = Some(cd.name);
|
||||||
|
mtx.tag = Some(cd.tag);
|
||||||
|
mtx.image = Some(cd.image);
|
||||||
|
|
||||||
(StatusCode::OK, "ok")
|
(StatusCode::OK, "ok")
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +237,14 @@ async fn shutdown_signal() {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> ExitCode {
|
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() {
|
let docker = match Docker::connect_with_socket_defaults() {
|
||||||
Ok(docker) => docker,
|
Ok(docker) => docker,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -204,7 +254,9 @@ async fn main() -> ExitCode {
|
||||||
};
|
};
|
||||||
|
|
||||||
match docker.version().await {
|
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) => {
|
Err(error) => {
|
||||||
eprintln!("failed to read docker version: {error}");
|
eprintln!("failed to read docker version: {error}");
|
||||||
return ExitCode::FAILURE;
|
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());
|
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}");
|
tracing::info!("launching microdeploy on {bind}");
|
||||||
|
|
||||||
let listener = match tokio::net::TcpListener::bind(&bind).await {
|
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()
|
let app = Router::new()
|
||||||
.route("/api/v1/build", post(build))
|
.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/stop", get(stop))
|
||||||
|
.route("/api/v1/status", get(status))
|
||||||
|
.route("/api/v1/pull", post(pull))
|
||||||
.layer((
|
.layer((
|
||||||
TraceLayer::new_for_http(),
|
TraceLayer::new_for_http(),
|
||||||
TimeoutLayer::new(Duration::from_secs(10)),
|
TimeoutLayer::new(Duration::from_secs(10)),
|
||||||
))
|
))
|
||||||
.with_state(shared_state);
|
.with_state(shared_state);
|
||||||
|
|
||||||
match axum::serve(listener, app)
|
let exit_code = match axum::serve(listener, app)
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -252,5 +298,9 @@ async fn main() -> ExitCode {
|
||||||
tracing::error!("axum::serve: {error}");
|
tracing::error!("axum::serve: {error}");
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
tracing::info!("shutting down");
|
||||||
|
|
||||||
|
exit_code
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue