-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
218 additions
and
77 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,33 @@ | |
|
||
Provides an interface for querying the podman rest api. Most of the interface is generated from | ||
the the official podman swagger file. This crate adds a layer to make it possible to connect to | ||
podman over ssh. This is commonly necessary on macOs where the container runtime runs in a | ||
virtual machine you connect to over ssh. | ||
the podman rest api over ssh to a unix socket and directl to a unix socket. Connections over | ||
ssh are commonly necessary on macOs where the container runtime runs in a virtual machine | ||
accessible over ssh. | ||
|
||
### Connecting via a unix socket on Linux | ||
|
||
```rust | ||
let client = PodmanRestCli | ||
use podman_rest_client::PodmanRestClient; | ||
|
||
let uri = "unix://path_to_unix_socket"; | ||
|
||
let client = PodmanRestClient::new(uri, None).await.unwrap(); | ||
|
||
let images = client.images_api().image_list_libpod(None,None).await.unwrap(); | ||
``` | ||
|
||
### Connecting via ssh from macOS | ||
|
||
```rust | ||
use podman_rest_client::PodmanRestClient; | ||
|
||
let uri = "ssh://[email protected]:63169/run/user/501/podman/podman.sock"; | ||
let key: Option<String> = Some("/path/to/key".into()); | ||
|
||
let client = PodmanRestClient::new(uri, None).await.unwrap(); | ||
|
||
let images = client.images_api().image_list_libpod(None,None).await.unwrap(); | ||
``` | ||
|
||
<!-- cargo-rdme end --> | ||
|
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,43 @@ | ||
//! Provides an interface for querying the podman rest api. Most of the interface is generated from | ||
//! the the official podman swagger file. This crate adds a layer to make it possible to connect to | ||
//! podman over ssh. This is commonly necessary on macOs where the container runtime runs in a | ||
//! virtual machine you connect to over ssh. | ||
//! the podman rest api over ssh to a unix socket and directl to a unix socket. Connections over | ||
//! ssh are commonly necessary on macOs where the container runtime runs in a virtual machine | ||
//! accessible over ssh. | ||
//! | ||
//! ## Connecting via a unix socket on Linux | ||
//! | ||
//! ```no_run | ||
//! # tokio_test::block_on(async { | ||
//! use podman_rest_client::PodmanRestClient; | ||
//! | ||
//! let uri = "unix://path_to_unix_socket"; | ||
//! | ||
//! let client = PodmanRestClient::new(uri, None).await.unwrap(); | ||
//! | ||
//! let images = client.images_api().image_list_libpod(None,None).await.unwrap(); | ||
//! # }) | ||
//! ``` | ||
//! let client = PodmanRestCli | ||
//! ``` | ||
//! | ||
//! ## Connecting via ssh from macOS | ||
//! | ||
//! ```no_run | ||
//! # tokio_test::block_on(async { | ||
//! use podman_rest_client::PodmanRestClient; | ||
//! | ||
//! let uri = "ssh://[email protected]:63169/run/user/501/podman/podman.sock"; | ||
//! let key: Option<String> = Some("/path/to/key".into()); | ||
//! | ||
//! let client = PodmanRestClient::new(uri, None).await.unwrap(); | ||
//! | ||
//! let images = client.images_api().image_list_libpod(None,None).await.unwrap(); | ||
//! # }) | ||
//! ``` | ||
|
||
pub mod cli; | ||
mod error; | ||
mod podman_rest_client; | ||
mod ssh; | ||
mod unix_socket; | ||
|
||
pub use error::Error; | ||
pub use podman_rest_client::Config; | ||
pub use podman_rest_client::PodmanRestClient; | ||
|
||
/* | ||
impl PodmanConnection { | ||
fn into_config(&mut self) -> Result<Config, hyper::http::uri::InvalidUri> { | ||
let uri = hyper::Uri::from_str(&self.uri)?; | ||
let user_name = uri.authority().and_then(|authority| { | ||
if let Some((user_name, _)) = authority.to_string().split_once('@') { | ||
Some(user_name.to_string()) | ||
} else { | ||
None | ||
} | ||
}); | ||
Ok(()) | ||
} | ||
}*/ |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,75 @@ | ||
use std::ops::Deref; | ||
use std::str::FromStr; | ||
|
||
use hyper_util::client::legacy::connect::Connect; | ||
use hyper_util::client::legacy::Client; | ||
use hyper_util::rt::TokioExecutor; | ||
use podman_api::apis::client::APIClient; | ||
use podman_api::apis::configuration::Configuration as APIConfiguration; | ||
|
||
use crate::error; | ||
use crate::error::Error; | ||
use crate::ssh; | ||
use crate::unix_socket; | ||
|
||
const BASE_PATH: &str = "http://d/v5.1.0"; | ||
|
||
pub struct PodmanRestClient(pub APIClient); | ||
|
||
struct SshConfiguration { | ||
pub user: String, | ||
key: String, | ||
address: String, | ||
socket_path: String, | ||
} | ||
impl PodmanRestClient { | ||
pub async fn new(uri: &str, key_path: Option<String>) -> Result<PodmanRestClient, Error> { | ||
let uri = hyper::Uri::from_str(uri)?; | ||
|
||
pub struct Config { | ||
socket: Option<String>, | ||
ssh: Option<SshConfiguration>, | ||
} | ||
if let Some(scheme) = uri.scheme() { | ||
match scheme.as_str() { | ||
"unix" => PodmanRestClient::new_unix(uri).await, | ||
"ssh" => PodmanRestClient::new_ssh(uri, key_path).await, | ||
_ => Err(Error::InvalidScheme), | ||
} | ||
} else { | ||
Err(Error::InvalidScheme) | ||
} | ||
} | ||
|
||
impl PodmanRestClient { | ||
pub async fn new(config: Config) -> Result<PodmanRestClient, error::Error> { | ||
let ssh_config = config.ssh.unwrap(); | ||
pub async fn new_ssh( | ||
uri: hyper::Uri, | ||
key_path: Option<String>, | ||
) -> Result<PodmanRestClient, Error> { | ||
let user_name = uri.authority().and_then(|authority| { | ||
if let Some((user_name, _)) = authority.to_string().split_once('@') { | ||
Some(user_name.to_string()) | ||
} else { | ||
None | ||
} | ||
}); | ||
|
||
let user_name = user_name.ok_or(Error::SshUserNameRequired)?; | ||
let key_path = key_path.ok_or(Error::SshKeyPathRequired)?; | ||
let host = uri.host().ok_or(Error::SshHostRequired)?; | ||
let address = uri.port().map_or(host.to_string(), |port| format!("{}:{}", host, port)); | ||
|
||
println!("{}, {}, {}", user_name, host, uri.path()); | ||
|
||
let connector = ssh::SshConnector::new( | ||
&ssh_config.user, | ||
&ssh_config.key, | ||
&ssh_config.address, | ||
&ssh_config.socket_path, | ||
) | ||
.await?; | ||
let connector = ssh::SshConnector::new(&user_name, &key_path, &address, uri.path()).await?; | ||
|
||
let client = | ||
hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(connector); | ||
PodmanRestClient::new_connector(connector).await | ||
} | ||
|
||
pub async fn new_unix( | ||
uri: hyper::Uri, | ||
) -> Result<PodmanRestClient, Error> { | ||
let connector = unix_socket::UnixConnector::new(uri.to_string()); | ||
|
||
PodmanRestClient::new_connector(connector).await | ||
} | ||
|
||
async fn new_connector<C>(connector: C) -> Result<PodmanRestClient, Error> | ||
where | ||
C: Connect + Clone + Send + Sync + 'static, | ||
{ | ||
let client = Client::builder(TokioExecutor::new()).build(connector); | ||
|
||
let configuration = APIConfiguration { | ||
base_path: "http://d/v5.1.0".to_string(), | ||
base_path: BASE_PATH.to_string(), | ||
..APIConfiguration::new(client) | ||
}; | ||
let api_client = APIClient::new(configuration); | ||
|
@@ -53,31 +85,3 @@ impl Deref for PodmanRestClient { | |
&self.0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn it_works() { | ||
let client = PodmanRestClient::new(Config { | ||
socket: None, | ||
ssh: Some(SshConfiguration { | ||
user: "[email protected]:63169".into(), | ||
key: "/Users/blazzy/.local/share/containers/podman/machine/machine".into(), | ||
address: "127.0.0.1:63169".into(), | ||
socket_path: "/run/user/501/podman/podman.sock".into(), | ||
}), | ||
}) | ||
.await | ||
.unwrap(); | ||
let images = client | ||
.images_api() | ||
.image_list_libpod(None, None) | ||
.await | ||
.unwrap(); | ||
println!("{:?}", images); | ||
|
||
assert_eq!(4, 4); | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
use std::future::Future; | ||
use std::io; | ||
use std::pin::Pin; | ||
use std::task::{Context, Poll}; | ||
|
||
use hyper::rt::ReadBufCursor; | ||
use hyper::Uri; | ||
use hyper_util::client::legacy::connect::{Connected, Connection}; | ||
use tokio::io::AsyncRead; | ||
use tokio::io::AsyncWrite; | ||
use tokio::net::UnixStream; | ||
use tower_service::Service; | ||
|
||
use crate::error::Error; | ||
|
||
#[derive(Clone)] | ||
pub(crate) struct UnixConnector { | ||
path: String, | ||
} | ||
|
||
pub struct UnixStreamWrapper(pub UnixStream); | ||
|
||
impl UnixConnector { | ||
pub fn new(path: String) -> UnixConnector { | ||
UnixConnector { path } | ||
} | ||
} | ||
|
||
impl Service<Uri> for UnixConnector { | ||
type Response = UnixStreamWrapper; | ||
type Error = Error; | ||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; | ||
|
||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | ||
Poll::Ready(Ok(())) | ||
} | ||
|
||
fn call(&mut self, _req: Uri) -> Self::Future { | ||
let path = self.path.clone(); | ||
let future = async move { | ||
let stream = UnixStreamWrapper(UnixStream::connect(path).await?); | ||
|
||
Ok(stream) | ||
}; | ||
Box::pin(future) | ||
} | ||
} | ||
|
||
impl Connection for UnixStreamWrapper { | ||
fn connected(&self) -> Connected { | ||
Connected::new() | ||
} | ||
} | ||
|
||
impl hyper::rt::Read for UnixStreamWrapper { | ||
fn poll_read( | ||
self: Pin<&mut Self>, | ||
cx: &mut Context<'_>, | ||
mut buf: ReadBufCursor<'_>, | ||
) -> Poll<std::io::Result<()>> { | ||
let mut temp_buf = unsafe { tokio::io::ReadBuf::uninit(buf.as_mut()) }; | ||
|
||
match Pin::new(&mut self.get_mut().0).poll_read(cx, &mut temp_buf) { | ||
Poll::Ready(Ok(())) => { | ||
let n = temp_buf.filled().len(); | ||
unsafe { | ||
buf.advance(n); | ||
} | ||
Poll::Ready(Ok(())) | ||
} | ||
other => other, | ||
} | ||
} | ||
} | ||
|
||
impl hyper::rt::Write for UnixStreamWrapper { | ||
fn poll_write( | ||
self: Pin<&mut Self>, | ||
cx: &mut Context<'_>, | ||
buf: &[u8], | ||
) -> Poll<io::Result<usize>> { | ||
Pin::new(&mut self.get_mut().0).poll_write(cx, buf) | ||
} | ||
|
||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
Pin::new(&mut self.get_mut().0).poll_flush(cx) | ||
} | ||
|
||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
Pin::new(&mut self.get_mut().0).poll_shutdown(cx) | ||
} | ||
} |