Skip to content

Instantly share code, notes, and snippets.

@mysteriouspants
Last active April 23, 2023 19:52
Show Gist options
  • Save mysteriouspants/9d7f4f6873a7b105ec0af4a319d394a5 to your computer and use it in GitHub Desktop.
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
//! 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