Last active
April 23, 2023 19:52
-
-
Save mysteriouspants/9d7f4f6873a7b105ec0af4a319d394a5 to your computer and use it in GitHub Desktop.
start a podman system service and tie that child process to your podtender api layer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Start a Podman service socket. | |
use podtender::podman_service::PodmanService; | |
use std::{ | |
env::{var, VarError}, | |
io::Error as IOError, | |
ops::Deref, | |
sync::Arc, | |
time::{SystemTime, UNIX_EPOCH}, | |
}; | |
use thiserror::Error; | |
use tokio::process::{Child, Command}; | |
use tokio_retry::{strategy::FixedInterval, Retry}; | |
/// A default socket location with a low probability of collision. | |
/// | |
/// This is a default socket location at XDG_RUNTIME_DIR/podman. | |
/// Individual sockets are named with the current UNIX timestamp at time | |
/// of creation to minimize the chance of collisions with other sockets. | |
fn default_socket() -> Result<String, VarError> { | |
let timestamp = SystemTime::now() | |
.duration_since(UNIX_EPOCH) | |
.unwrap() | |
.as_secs(); | |
Ok(format!( | |
"unix://{}/podman/surrealctl_{}.sock", | |
var("XDG_RUNTIME_DIR")?, | |
timestamp, | |
)) | |
} | |
#[derive(Debug, Error)] | |
pub enum Error { | |
#[error("Missing environment var")] | |
MissingEnv(#[from] VarError), | |
#[error("Cannot spawn Podman system service")] | |
CannotSpawnChild(#[from] IOError), | |
} | |
#[derive(Debug, Error)] | |
pub enum WaitForReadyError { | |
#[error("The socket never appeared")] | |
NoSocket, | |
#[error("The daemon did not respond as expected")] | |
DaemonUnresponsive, | |
} | |
/// Wrapper around a short-lived Podman system service. | |
/// | |
/// This system service starts a Podman system service as a child | |
/// process and dereferences to a Podtender [`PodmanService`], which | |
/// provides API functionality with Podman. | |
#[derive(Clone, Debug)] | |
pub struct PodmanSystemService { | |
_handle: Arc<Child>, | |
service: PodmanService, | |
} | |
impl PodmanSystemService { | |
/// Start a Podman system service. | |
/// | |
/// Starts a Podman service at a relatively unique socket location. | |
pub fn start() -> Result<Self, Error> { | |
let socket = format!("unix://{}", default_socket()?); | |
Ok(Self::start_at_socket(&socket)?) | |
} | |
/// Start a Podman system service at a specific socket location. | |
pub fn start_at_socket(socket: &str) -> Result<Self, IOError> { | |
let child = Command::new("podman") | |
.arg("system") | |
.arg("service") | |
.arg("-t") | |
.arg("0") // run until SIGKILL | |
.arg(socket) | |
.kill_on_drop(true) | |
.spawn()?; | |
Ok(Self { | |
_handle: Arc::new(child), | |
service: PodmanService::new(socket), | |
}) | |
} | |
/// Wait for the Podman service to be ready. | |
/// | |
/// Waits for the socket to exist and for the Podman System Service | |
/// to response to a system information request. Times out after | |
/// five seconds. | |
pub async fn wait_for_ready(&self) -> Result<(), WaitForReadyError> { | |
let strategy = FixedInterval::from_millis(10).take(500); // 5 seconds of retry | |
Retry::spawn(strategy, move || async move { | |
let svc = self.clone(); | |
if !svc.check_socket_exists() { | |
return Err(WaitForReadyError::NoSocket); | |
} | |
if !svc.system().get_info().await.is_ok() { | |
return Err(WaitForReadyError::DaemonUnresponsive); | |
} | |
Ok(()) | |
}) | |
.await | |
} | |
} | |
// doing this through deref keeps the PodmanService lifetime tied to the | |
// PodmanSystemService's lifetime, so there is a guarantee that the | |
// PodmanService will always have an active socket to talk to. | |
impl Deref for PodmanSystemService { | |
type Target = PodmanService; | |
fn deref(&self) -> &Self::Target { | |
&self.service | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment