use bytes::Bytes;
use futures::Stream;
use serde::de::DeserializeOwned;
use crate::{
api::{Api, DeleteParams, Patch, PatchParams, PostParams, Resource},
client::Status,
Error, Result,
};
pub use k8s_openapi::api::autoscaling::v1::{Scale, ScaleSpec, ScaleStatus};
#[cfg(feature = "ws")] use crate::api::remote_command::AttachedProcess;
impl<K> Api<K>
where
K: Clone + DeserializeOwned,
{
pub async fn get_scale(&self, name: &str) -> Result<Scale> {
let req = self.resource.get_scale(name)?;
self.client.request::<Scale>(req).await
}
pub async fn patch_scale<P: serde::Serialize>(
&self,
name: &str,
pp: &PatchParams,
patch: &Patch<P>,
) -> Result<Scale> {
let req = self.resource.patch_scale(name, &pp, patch)?;
self.client.request::<Scale>(req).await
}
pub async fn replace_scale(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<Scale> {
let req = self.resource.replace_scale(name, &pp, data)?;
self.client.request::<Scale>(req).await
}
}
impl<K> Api<K>
where
K: DeserializeOwned,
{
pub async fn get_status(&self, name: &str) -> Result<K> {
let req = self.resource.get_status(name)?;
self.client.request::<K>(req).await
}
pub async fn patch_status<P: serde::Serialize>(
&self,
name: &str,
pp: &PatchParams,
patch: &Patch<P>,
) -> Result<K> {
let req = self.resource.patch_status(name, &pp, patch)?;
self.client.request::<K>(req).await
}
pub async fn replace_status(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<K> {
let req = self.resource.replace_status(name, &pp, data)?;
self.client.request::<K>(req).await
}
}
#[derive(Default, Clone, Debug)]
pub struct LogParams {
pub container: Option<String>,
pub follow: bool,
pub limit_bytes: Option<i64>,
pub pretty: bool,
pub previous: bool,
pub since_seconds: Option<i64>,
pub tail_lines: Option<i64>,
pub timestamps: bool,
}
impl Resource {
pub fn logs(&self, name: &str, lp: &LogParams) -> Result<http::Request<Vec<u8>>> {
let base_url = self.make_url() + "/" + name + "/" + "log?";
let mut qp = url::form_urlencoded::Serializer::new(base_url);
if let Some(container) = &lp.container {
qp.append_pair("container", &container);
}
if lp.follow {
qp.append_pair("follow", "true");
}
if let Some(lb) = &lp.limit_bytes {
qp.append_pair("limitBytes", &lb.to_string());
}
if lp.pretty {
qp.append_pair("pretty", "true");
}
if lp.previous {
qp.append_pair("previous", "true");
}
if let Some(ss) = &lp.since_seconds {
qp.append_pair("sinceSeconds", &ss.to_string());
}
if let Some(tl) = &lp.tail_lines {
qp.append_pair("tailLines", &tl.to_string());
}
if lp.timestamps {
qp.append_pair("timestamps", "true");
}
let urlstr = qp.finish();
let req = http::Request::get(urlstr);
req.body(vec![]).map_err(Error::HttpError)
}
}
#[test]
fn log_path() {
use crate::api::Resource;
use k8s_openapi::api::core::v1 as corev1;
let r = Resource::namespaced::<corev1::Pod>("ns");
let lp = LogParams {
container: Some("blah".into()),
..LogParams::default()
};
let req = r.logs("foo", &lp).unwrap();
assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/log?&container=blah");
}
pub trait Loggable {}
impl Loggable for k8s_openapi::api::core::v1::Pod {}
impl<K> Api<K>
where
K: DeserializeOwned + Loggable,
{
pub async fn logs(&self, name: &str, lp: &LogParams) -> Result<String> {
let req = self.resource.logs(name, lp)?;
Ok(self.client.request_text(req).await?)
}
pub async fn log_stream(&self, name: &str, lp: &LogParams) -> Result<impl Stream<Item = Result<Bytes>>> {
let req = self.resource.logs(name, lp)?;
Ok(self.client.request_text_stream(req).await?)
}
}
#[derive(Default, Clone)]
pub struct EvictParams {
pub delete_options: Option<DeleteParams>,
pub post_options: PostParams,
}
impl Resource {
pub fn evict(&self, name: &str, ep: &EvictParams) -> Result<http::Request<Vec<u8>>> {
let base_url = self.make_url() + "/" + name + "/" + "eviction?";
let pp = &ep.post_options;
pp.validate()?;
let mut qp = url::form_urlencoded::Serializer::new(base_url);
if pp.dry_run {
qp.append_pair("dryRun", "All");
}
let urlstr = qp.finish();
let data = serde_json::to_vec(&serde_json::json!({
"delete_options": ep.delete_options,
"metadata": { "name": name }
}))?;
let req = http::Request::post(urlstr);
req.body(data).map_err(Error::HttpError)
}
}
#[test]
fn evict_path() {
use crate::api::Resource;
use k8s_openapi::api::core::v1 as corev1;
let r = Resource::namespaced::<corev1::Pod>("ns");
let ep = EvictParams::default();
let req = r.evict("foo", &ep).unwrap();
assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/eviction?");
}
pub trait Evictable {}
impl Evictable for k8s_openapi::api::core::v1::Pod {}
impl<K> Api<K>
where
K: DeserializeOwned + Evictable,
{
pub async fn evict(&self, name: &str, ep: &EvictParams) -> Result<Status> {
let req = self.resource.evict(name, ep)?;
self.client.request::<Status>(req).await
}
}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub struct AttachParams {
pub container: Option<String>,
pub stdin: bool,
pub stdout: bool,
pub stderr: bool,
pub tty: bool,
pub max_stdin_buf_size: Option<usize>,
pub max_stdout_buf_size: Option<usize>,
pub max_stderr_buf_size: Option<usize>,
}
#[cfg(feature = "ws")]
impl Default for AttachParams {
fn default() -> Self {
Self {
container: None,
stdin: false,
stdout: true,
stderr: true,
tty: false,
max_stdin_buf_size: None,
max_stdout_buf_size: None,
max_stderr_buf_size: None,
}
}
}
#[cfg(feature = "ws")]
impl AttachParams {
pub fn interactive_tty() -> Self {
Self {
stdin: true,
stdout: true,
stderr: false,
tty: true,
..Default::default()
}
}
pub fn container<T: Into<String>>(mut self, container: T) -> Self {
self.container = Some(container.into());
self
}
pub fn stdin(mut self, enable: bool) -> Self {
self.stdin = enable;
self
}
pub fn stdout(mut self, enable: bool) -> Self {
self.stdout = enable;
self
}
pub fn stderr(mut self, enable: bool) -> Self {
self.stderr = enable;
self
}
pub fn tty(mut self, enable: bool) -> Self {
self.tty = enable;
self
}
pub fn max_stdin_buf_size(mut self, size: usize) -> Self {
self.max_stdin_buf_size = Some(size);
self
}
pub fn max_stdout_buf_size(mut self, size: usize) -> Self {
self.max_stdout_buf_size = Some(size);
self
}
pub fn max_stderr_buf_size(mut self, size: usize) -> Self {
self.max_stderr_buf_size = Some(size);
self
}
fn validate(&self) -> Result<()> {
if !self.stdin && !self.stdout && !self.stderr {
return Err(Error::RequestValidation(
"AttachParams: one of stdin, stdout, or stderr must be true".into(),
));
}
if self.stderr && self.tty {
return Err(Error::RequestValidation(
"AttachParams: tty and stderr cannot both be true".into(),
));
}
Ok(())
}
fn append_to_url_serializer(&self, qp: &mut url::form_urlencoded::Serializer<String>) {
if self.stdin {
qp.append_pair("stdin", "true");
}
if self.stdout {
qp.append_pair("stdout", "true");
}
if self.stderr {
qp.append_pair("stderr", "true");
}
if self.tty {
qp.append_pair("tty", "true");
}
if let Some(container) = &self.container {
qp.append_pair("container", &container);
}
}
}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl Resource {
pub fn attach(&self, name: &str, ap: &AttachParams) -> Result<http::Request<Vec<u8>>> {
ap.validate()?;
let base_url = self.make_url() + "/" + name + "/" + "attach?";
let mut qp = url::form_urlencoded::Serializer::new(base_url);
ap.append_to_url_serializer(&mut qp);
let req = http::Request::get(qp.finish());
req.body(vec![]).map_err(Error::HttpError)
}
}
#[cfg(feature = "ws")]
#[test]
fn attach_path() {
use crate::api::Resource;
use k8s_openapi::api::core::v1 as corev1;
let r = Resource::namespaced::<corev1::Pod>("ns");
let ap = AttachParams {
container: Some("blah".into()),
..AttachParams::default()
};
let req = r.attach("foo", &ap).unwrap();
assert_eq!(
req.uri(),
"/api/v1/namespaces/ns/pods/foo/attach?&stdout=true&stderr=true&container=blah"
);
}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub trait Attachable {}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl Attachable for k8s_openapi::api::core::v1::Pod {}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl<K> Api<K>
where
K: Clone + DeserializeOwned + Attachable,
{
pub async fn attach(&self, name: &str, ap: &AttachParams) -> Result<AttachedProcess> {
let req = self.resource.attach(name, ap)?;
let stream = self.client.connect(req).await?;
Ok(AttachedProcess::new(stream, ap))
}
}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl Resource {
pub fn exec<I, T>(&self, name: &str, command: I, ap: &AttachParams) -> Result<http::Request<Vec<u8>>>
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
ap.validate()?;
let base_url = self.make_url() + "/" + name + "/" + "exec?";
let mut qp = url::form_urlencoded::Serializer::new(base_url);
ap.append_to_url_serializer(&mut qp);
for c in command.into_iter() {
qp.append_pair("command", &c.into());
}
let req = http::Request::get(qp.finish());
req.body(vec![]).map_err(Error::HttpError)
}
}
#[cfg(feature = "ws")]
#[test]
fn exec_path() {
use crate::api::Resource;
use k8s_openapi::api::core::v1 as corev1;
let r = Resource::namespaced::<corev1::Pod>("ns");
let ap = AttachParams {
container: Some("blah".into()),
..AttachParams::default()
};
let req = r.exec("foo", vec!["echo", "foo", "bar"], &ap).unwrap();
assert_eq!(
req.uri(),
"/api/v1/namespaces/ns/pods/foo/exec?&stdout=true&stderr=true&container=blah&command=echo&command=foo&command=bar"
);
}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub trait Executable {}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl Executable for k8s_openapi::api::core::v1::Pod {}
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
impl<K> Api<K>
where
K: Clone + DeserializeOwned + Executable,
{
pub async fn exec<I, T>(&self, name: &str, command: I, ap: &AttachParams) -> Result<AttachedProcess>
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
let req = self.resource.exec(name, command, ap)?;
let stream = self.client.connect(req).await?;
Ok(AttachedProcess::new(stream, ap))
}
}