Skip to content

Commit 1c2b97e

Browse files
committed
Implement Ephemeral Containers subresource
Extra: An example of an invalid patch that will not cause an error has been added to Patch docs. Signed-off-by: Jessie Chatham Spencer <[email protected]>
1 parent 5806a2d commit 1c2b97e

File tree

4 files changed

+375
-30
lines changed

4 files changed

+375
-30
lines changed

kube-client/src/api/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ mod subresource;
1414
pub use subresource::{Attach, AttachParams, Execute, Portforward};
1515
pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus};
1616

17+
// Ephemeral containers were stabilized in Kubernetes 1.25.
18+
k8s_openapi::k8s_if_ge_1_25! {
19+
pub use subresource::Ephemeral;
20+
}
21+
1722
mod util;
1823

1924
pub mod entry;

kube-client/src/api/subresource.rs

+180-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bytes::Bytes;
22
use futures::Stream;
3-
use serde::de::DeserializeOwned;
3+
use serde::{de::DeserializeOwned, Serialize};
44
use std::fmt::Debug;
55

66
use crate::{
@@ -128,6 +128,160 @@ where
128128
}
129129
}
130130

131+
// ----------------------------------------------------------------------------
132+
// Ephemeral containers
133+
// ----------------------------------------------------------------------------
134+
135+
/// Marker trait for objects that support the ephemeral containers sub resource.
136+
pub trait Ephemeral {}
137+
138+
impl Ephemeral for k8s_openapi::api::core::v1::Pod {}
139+
140+
impl<K> Api<K>
141+
where
142+
K: Clone + DeserializeOwned + Ephemeral,
143+
{
144+
/// Replace the ephemeral containers sub resource entirely.
145+
///
146+
/// This functions in the same way as [`Api::replace`] except only `.spec.ephemeralcontainers` is replaced, everything else is ignored.
147+
///
148+
/// Note that ephemeral containers may **not** be changed or removed once attached to a pod.
149+
///
150+
///
151+
/// You way want to patch the underlying resource to gain access to the main container process,
152+
/// see the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
153+
///
154+
/// See the Kubernetes [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/#what-is-an-ephemeral-container) for more details.
155+
///
156+
/// [`Api::patch_ephemeral_containers`] may be more ergonomic, as you can will avoid having to first fetch the
157+
/// existing subresources with an approriate merge strategy, see the examples for more details.
158+
///
159+
/// Example of using `replace_ephemeral_containers`:
160+
///
161+
/// ```no_run
162+
/// use k8s_openapi::api::core::v1::Pod;
163+
/// use kube::{Api, api::PostParams};
164+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
165+
/// # let client = kube::Client::try_default().await?;
166+
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
167+
/// let pp = PostParams::default();
168+
///
169+
/// // Get pod object with ephemeral containers.
170+
/// let mut mypod = pods.get_ephemeral_containers("mypod").await?;
171+
///
172+
/// // If there were existing ephemeral containers, we would have to append
173+
/// // new containers to the list before calling replace_ephemeral_containers.
174+
/// assert_eq!(mypod.spec.as_mut().unwrap().ephemeral_containers, None);
175+
///
176+
/// // Add an ephemeral container to the pod object.
177+
/// mypod.spec.as_mut().unwrap().ephemeral_containers = Some(serde_json::from_value(serde_json::json!([
178+
/// {
179+
/// "name": "myephemeralcontainer",
180+
/// "image": "busybox:1.34.1",
181+
/// "command": ["sh", "-c", "sleep 20"],
182+
/// },
183+
/// ]))?);
184+
///
185+
/// pods.replace_ephemeral_containers("mypod", &pp, &mypod).await?;
186+
///
187+
/// # Ok(())
188+
/// # }
189+
/// ```
190+
pub async fn replace_ephemeral_containers(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
191+
where
192+
K: Serialize,
193+
{
194+
let mut req = self
195+
.request
196+
.replace_subresource(
197+
"ephemeralcontainers",
198+
name,
199+
pp,
200+
serde_json::to_vec(data).map_err(Error::SerdeError)?,
201+
)
202+
.map_err(Error::BuildRequest)?;
203+
req.extensions_mut().insert("replace_ephemeralcontainers");
204+
self.client.request::<K>(req).await
205+
}
206+
207+
/// Patch the ephemeral containers sub resource
208+
///
209+
/// Any partial object containing the ephemeral containers
210+
/// sub resource is valid as long as the complete structure
211+
/// for the object is present, as shown below.
212+
///
213+
/// You way want to patch the underlying resource to gain access to the main container process,
214+
/// see the [docs](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
215+
///
216+
/// Ephemeral containers may **not** be changed or removed once attached to a pod.
217+
/// Therefore if the chosen merge strategy overwrites the existing ephemeral containers,
218+
/// you will have to fetch the existing ephemeral containers first.
219+
/// In order to append your new ephemeral containers to the existing list before patching. See some examples and
220+
/// discussion related to merge strategies in Kubernetes
221+
/// [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
222+
///
223+
/// See the `Kubernetes` [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)
224+
/// for more information about ephemeral containers.
225+
///
226+
///
227+
/// Example of using `patch_ephemeral_containers`:
228+
///
229+
/// ```no_run
230+
/// use kube::api::{Api, PatchParams, Patch};
231+
/// use k8s_openapi::api::core::v1::Pod;
232+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
233+
/// # let client = kube::Client::try_default().await?;
234+
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
235+
/// let pp = PatchParams::default(); // stratetgic merge patch
236+
///
237+
/// // Note that the strategic merge patch will concatenate the
238+
/// // lists of ephemeral containers so we avoid having to fetch the
239+
/// // current list and append to it manually.
240+
/// let patch = serde_json::json!({
241+
/// "spec":{
242+
/// "ephemeralContainers": [
243+
/// {
244+
/// "name": "myephemeralcontainer",
245+
/// "image": "busybox:1.34.1",
246+
/// "command": ["sh", "-c", "sleep 20"],
247+
/// },
248+
/// ]
249+
/// }});
250+
///
251+
/// pods.patch_ephemeral_containers("mypod", &pp, &Patch::Strategic(patch)).await?;
252+
///
253+
/// # Ok(())
254+
/// # }
255+
/// ```
256+
pub async fn patch_ephemeral_containers<P: serde::Serialize>(
257+
&self,
258+
name: &str,
259+
pp: &PatchParams,
260+
patch: &Patch<P>,
261+
) -> Result<K> {
262+
let mut req = self
263+
.request
264+
.patch_subresource("ephemeralcontainers", name, pp, patch)
265+
.map_err(Error::BuildRequest)?;
266+
267+
req.extensions_mut().insert("patch_ephemeralcontainers");
268+
self.client.request::<K>(req).await
269+
}
270+
271+
/// Get the named resource with the ephemeral containers subresource.
272+
///
273+
/// This returns the whole K, with metadata and spec.
274+
pub async fn get_ephemeral_containers(&self, name: &str) -> Result<K> {
275+
let mut req = self
276+
.request
277+
.get_subresource("ephemeralcontainers", name)
278+
.map_err(Error::BuildRequest)?;
279+
280+
req.extensions_mut().insert("get_ephemeralcontainers");
281+
self.client.request::<K>(req).await
282+
}
283+
}
284+
131285
// ----------------------------------------------------------------------------
132286

133287
// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
@@ -154,23 +308,22 @@ where
154308
/// NB: Requires that the resource has a status subresource.
155309
///
156310
/// ```no_run
157-
/// use kube::{api::{Api, PatchParams, Patch}, Client};
311+
/// use kube::api::{Api, PatchParams, Patch};
158312
/// use k8s_openapi::api::batch::v1::Job;
159-
/// #[tokio::main]
160-
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
161-
/// let client = Client::try_default().await?;
162-
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
163-
/// let mut j = jobs.get("baz").await?;
164-
/// let pp = PatchParams::default(); // json merge patch
165-
/// let data = serde_json::json!({
166-
/// "status": {
167-
/// "succeeded": 2
168-
/// }
169-
/// });
170-
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
171-
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
172-
/// Ok(())
173-
/// }
313+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
314+
/// # let client = kube::Client::try_default().await?;
315+
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
316+
/// let mut j = jobs.get("baz").await?;
317+
/// let pp = PatchParams::default(); // json merge patch
318+
/// let data = serde_json::json!({
319+
/// "status": {
320+
/// "succeeded": 2
321+
/// }
322+
/// });
323+
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
324+
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
325+
/// # Ok(())
326+
/// # }
174327
/// ```
175328
pub async fn patch_status<P: serde::Serialize + Debug>(
176329
&self,
@@ -192,18 +345,17 @@ where
192345
/// You can leave out the `.spec` entirely from the serialized output.
193346
///
194347
/// ```no_run
195-
/// use kube::{api::{Api, PostParams}, Client};
348+
/// use kube::api::{Api, PostParams};
196349
/// use k8s_openapi::api::batch::v1::{Job, JobStatus};
197-
/// #[tokio::main]
198-
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
199-
/// let client = Client::try_default().await?;
200-
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
201-
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
202-
/// o.status = Some(JobStatus::default()); // update the job part
203-
/// let pp = PostParams::default();
204-
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
205-
/// Ok(())
206-
/// }
350+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
351+
/// # let client = kube::Client::try_default().await?;
352+
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
353+
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
354+
/// o.status = Some(JobStatus::default()); // update the job part
355+
/// let pp = PostParams::default();
356+
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
357+
/// # Ok(())
358+
/// # }
207359
/// ```
208360
pub async fn replace_status(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<K> {
209361
let mut req = self

0 commit comments

Comments
 (0)