Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ephemeral containers subresource #1153

Merged
merged 1 commit into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions kube-client/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ mod subresource;
pub use subresource::{Attach, AttachParams, Execute, Portforward};
pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus};

// Ephemeral containers were stabilized in Kubernetes 1.25.
k8s_openapi::k8s_if_ge_1_25! {
clux marked this conversation as resolved.
Show resolved Hide resolved
pub use subresource::Ephemeral;
}

mod util;

pub mod entry;
Expand Down
208 changes: 180 additions & 28 deletions kube-client/src/api/subresource.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use futures::AsyncBufRead;
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;

use crate::{
Expand Down Expand Up @@ -127,6 +127,160 @@
}
}

// ----------------------------------------------------------------------------
// Ephemeral containers
// ----------------------------------------------------------------------------

/// Marker trait for objects that support the ephemeral containers sub resource.
pub trait Ephemeral {}

impl Ephemeral for k8s_openapi::api::core::v1::Pod {}

impl<K> Api<K>
where
K: Clone + DeserializeOwned + Ephemeral,
{
/// Replace the ephemeral containers sub resource entirely.
///
/// This functions in the same way as [`Api::replace`] except only `.spec.ephemeralcontainers` is replaced, everything else is ignored.
///
/// Note that ephemeral containers may **not** be changed or removed once attached to a pod.
///
///
/// You way want to patch the underlying resource to gain access to the main container process,
/// see the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
///
/// See the Kubernetes [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/#what-is-an-ephemeral-container) for more details.
///
/// [`Api::patch_ephemeral_containers`] may be more ergonomic, as you can will avoid having to first fetch the
/// existing subresources with an approriate merge strategy, see the examples for more details.
///
/// Example of using `replace_ephemeral_containers`:
///
/// ```no_run
/// use k8s_openapi::api::core::v1::Pod;
/// use kube::{Api, api::PostParams};
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
/// let pp = PostParams::default();
///
/// // Get pod object with ephemeral containers.
/// let mut mypod = pods.get_ephemeral_containers("mypod").await?;
///
/// // If there were existing ephemeral containers, we would have to append
/// // new containers to the list before calling replace_ephemeral_containers.
/// assert_eq!(mypod.spec.as_mut().unwrap().ephemeral_containers, None);
///
/// // Add an ephemeral container to the pod object.
/// mypod.spec.as_mut().unwrap().ephemeral_containers = Some(serde_json::from_value(serde_json::json!([
/// {
/// "name": "myephemeralcontainer",
/// "image": "busybox:1.34.1",
/// "command": ["sh", "-c", "sleep 20"],
/// },
/// ]))?);
///
/// pods.replace_ephemeral_containers("mypod", &pp, &mypod).await?;
///
/// # Ok(())
/// # }
/// ```
pub async fn replace_ephemeral_containers(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>

Check warning on line 189 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L189

Added line #L189 was not covered by tests
where
K: Serialize,
{
let mut req = self
.request

Check warning on line 194 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L193-L194

Added lines #L193 - L194 were not covered by tests
.replace_subresource(
"ephemeralcontainers",
name,
pp,
serde_json::to_vec(data).map_err(Error::SerdeError)?,

Check warning on line 199 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L197-L199

Added lines #L197 - L199 were not covered by tests
)
.map_err(Error::BuildRequest)?;
req.extensions_mut().insert("replace_ephemeralcontainers");
self.client.request::<K>(req).await

Check warning on line 203 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L201-L203

Added lines #L201 - L203 were not covered by tests
}

/// Patch the ephemeral containers sub resource
///
/// Any partial object containing the ephemeral containers
/// sub resource is valid as long as the complete structure
/// for the object is present, as shown below.
///
/// You way want to patch the underlying resource to gain access to the main container process,
/// see the [docs](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
///
/// Ephemeral containers may **not** be changed or removed once attached to a pod.
/// Therefore if the chosen merge strategy overwrites the existing ephemeral containers,
/// you will have to fetch the existing ephemeral containers first.
/// In order to append your new ephemeral containers to the existing list before patching. See some examples and
/// discussion related to merge strategies in Kubernetes
/// [here](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment). The example below uses a strategic merge patch which does not require
///
/// See the `Kubernetes` [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)
/// for more information about ephemeral containers.
///
///
/// Example of using `patch_ephemeral_containers`:
clux marked this conversation as resolved.
Show resolved Hide resolved
///
/// ```no_run
/// use kube::api::{Api, PatchParams, Patch};
/// use k8s_openapi::api::core::v1::Pod;
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
/// let pp = PatchParams::default(); // stratetgic merge patch
///
/// // Note that the strategic merge patch will concatenate the
/// // lists of ephemeral containers so we avoid having to fetch the
/// // current list and append to it manually.
/// let patch = serde_json::json!({
/// "spec":{
/// "ephemeralContainers": [
/// {
/// "name": "myephemeralcontainer",
/// "image": "busybox:1.34.1",
/// "command": ["sh", "-c", "sleep 20"],
/// },
/// ]
/// }});
///
/// pods.patch_ephemeral_containers("mypod", &pp, &Patch::Strategic(patch)).await?;
///
/// # Ok(())
/// # }
/// ```
pub async fn patch_ephemeral_containers<P: serde::Serialize>(

Check warning on line 255 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L255

Added line #L255 was not covered by tests
&self,
name: &str,
pp: &PatchParams,
patch: &Patch<P>,
) -> Result<K> {
let mut req = self
.request
.patch_subresource("ephemeralcontainers", name, pp, patch)
.map_err(Error::BuildRequest)?;

Check warning on line 264 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L261-L264

Added lines #L261 - L264 were not covered by tests

req.extensions_mut().insert("patch_ephemeralcontainers");
self.client.request::<K>(req).await

Check warning on line 267 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L266-L267

Added lines #L266 - L267 were not covered by tests
}

/// Get the named resource with the ephemeral containers subresource.
///
/// This returns the whole K, with metadata and spec.
pub async fn get_ephemeral_containers(&self, name: &str) -> Result<K> {
let mut req = self
.request
.get_subresource("ephemeralcontainers", name)
.map_err(Error::BuildRequest)?;

Check warning on line 277 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L273-L277

Added lines #L273 - L277 were not covered by tests

req.extensions_mut().insert("get_ephemeralcontainers");
self.client.request::<K>(req).await

Check warning on line 280 in kube-client/src/api/subresource.rs

View check run for this annotation

Codecov / codecov/patch

kube-client/src/api/subresource.rs#L279-L280

Added lines #L279 - L280 were not covered by tests
}
}

// ----------------------------------------------------------------------------

// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
Expand All @@ -153,23 +307,22 @@
/// NB: Requires that the resource has a status subresource.
///
/// ```no_run
/// use kube::{api::{Api, PatchParams, Patch}, Client};
/// use kube::api::{Api, PatchParams, Patch};
/// use k8s_openapi::api::batch::v1::Job;
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut j = jobs.get("baz").await?;
/// let pp = PatchParams::default(); // json merge patch
/// let data = serde_json::json!({
/// "status": {
/// "succeeded": 2
/// }
/// });
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
/// Ok(())
/// }
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut j = jobs.get("baz").await?;
/// let pp = PatchParams::default(); // json merge patch
/// let data = serde_json::json!({
/// "status": {
/// "succeeded": 2
/// }
/// });
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
/// # Ok(())
/// # }
/// ```
pub async fn patch_status<P: serde::Serialize + Debug>(
&self,
Expand All @@ -191,18 +344,17 @@
/// You can leave out the `.spec` entirely from the serialized output.
///
/// ```no_run
/// use kube::{api::{Api, PostParams}, Client};
/// use kube::api::{Api, PostParams};
/// use k8s_openapi::api::batch::v1::{Job, JobStatus};
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
/// o.status = Some(JobStatus::default()); // update the job part
/// let pp = PostParams::default();
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
/// Ok(())
/// }
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
/// o.status = Some(JobStatus::default()); // update the job part
/// let pp = PostParams::default();
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
/// # Ok(())
/// # }
/// ```
pub async fn replace_status(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<K> {
let mut req = self
Expand Down
Loading
Loading