Skip to content

Commit 4c3f6bf

Browse files
committed
Implement Ephemeral Containers subresource
Signed-off-by: Jessie Chatham Spencer <[email protected]>
1 parent 2e5e4de commit 4c3f6bf

File tree

3 files changed

+343
-17
lines changed

3 files changed

+343
-17
lines changed

kube-client/src/api/mod.rs

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

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

2025
pub mod entry;

kube-client/src/api/subresource.rs

+203-15
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,196 @@ 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, Client, api::PostParams};
164+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
165+
/// # let client = 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+
/// If an invalid patch is provided the method will **not** always return
210+
/// an error and no changes will be made to the target object in
211+
/// the cluster, see the examples below.
212+
///
213+
/// Any partial object containing the ephemeral containers
214+
/// sub resource is valid as long as the complete structure
215+
/// for the object is present, as shown below.
216+
///
217+
/// You way want to patch the underlying resource to gain access to the main container process,
218+
/// see the [docs](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
219+
///
220+
/// Ephemeral containers may **not** be changed or removed once attached to a pod.
221+
/// Therefore if the chosen merge strategy overwrites the existing ephemeral containers,
222+
/// you will have to fetch the existing ephemeral containers first.
223+
/// In order to append your new ephemeral containers to the existing list before patching. See some examples and
224+
/// discussion related to merge strategies in Kubernetes
225+
/// [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
226+
///
227+
/// See the `Kubernetes` [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)
228+
/// for more information about ephemeral containers.
229+
///
230+
/// **Valid and invalid Examples for patching a pod**
231+
///
232+
/// Valid as this can be merged with a complete Pod object:
233+
/// ```rust,no_run
234+
/// let patch = serde_json::json!({
235+
/// "spec":{
236+
/// "ephemeralContainers": [
237+
/// {
238+
/// "name": "myephemeralcontainer",
239+
/// "image": "busybox:1.34.1",
240+
/// "command": ["sh", "-c", "sleep 20"],
241+
/// },
242+
/// ]
243+
/// }});
244+
///
245+
/// ```
246+
///
247+
/// Not valid as the outer layer of the `Pod` object is missing, instead
248+
/// we have provided a partial `Pod` spec. Again note that no error will
249+
/// be returned, the patch will simply have no effect.
250+
/// ```rust,no_run
251+
/// let patch = serde_json::json!(
252+
/// {
253+
/// "ephemeralContainers": [
254+
/// {
255+
/// "name": "myephemeralcontainer",
256+
/// "image": "busybox:1.34.1",
257+
/// "command": ["sh", "-c", "sleep 20"],
258+
/// },
259+
/// ]
260+
/// });
261+
/// ```
262+
///
263+
/// Example of using `patch_ephemeral_containers`:
264+
///
265+
/// ```rust,no_run
266+
/// # use kube::{api::{Api, PatchParams, Patch}, Client};
267+
/// # use k8s_openapi::api::core::v1::Pod;
268+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
269+
/// # let client = Client::try_default().await?;
270+
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
271+
/// let pp = PatchParams::default(); // stratetgic merge patch
272+
///
273+
/// // Note that the strategic merge patch will concatenate the
274+
/// // lists of ephemeral containers so we avoid having to fetch the
275+
/// // current list and append to it manually.
276+
/// let patch = serde_json::json!({
277+
/// "spec":{
278+
/// "ephemeralContainers": [
279+
/// {
280+
/// "name": "myephemeralcontainer",
281+
/// "image": "busybox:1.34.1",
282+
/// "command": ["sh", "-c", "sleep 20"],
283+
/// },
284+
/// ]
285+
/// }});
286+
///
287+
/// pods.patch_ephemeral_containers("mypod", &pp, &Patch::Strategic(patch)).await?;
288+
///
289+
/// # Ok(())
290+
/// # }
291+
/// ```
292+
pub async fn patch_ephemeral_containers<P: serde::Serialize>(
293+
&self,
294+
name: &str,
295+
pp: &PatchParams,
296+
patch: &Patch<P>,
297+
) -> Result<K> {
298+
let mut req = self
299+
.request
300+
.patch_subresource("ephemeralcontainers", name, pp, patch)
301+
.map_err(Error::BuildRequest)?;
302+
303+
req.extensions_mut().insert("patch_ephemeralcontainers");
304+
self.client.request::<K>(req).await
305+
}
306+
307+
/// Get the named resource with the ephemeral containers subresource.
308+
///
309+
/// This returns the whole K, with metadata and spec.
310+
pub async fn get_ephemeral_containers(&self, name: &str) -> Result<K> {
311+
let mut req = self
312+
.request
313+
.get_subresource("ephemeralcontainers", name)
314+
.map_err(Error::BuildRequest)?;
315+
316+
req.extensions_mut().insert("get_ephemeralcontainers");
317+
self.client.request::<K>(req).await
318+
}
319+
}
320+
131321
// ----------------------------------------------------------------------------
132322

133323
// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
@@ -154,11 +344,10 @@ where
154344
/// NB: Requires that the resource has a status subresource.
155345
///
156346
/// ```no_run
157-
/// use kube::{api::{Api, PatchParams, Patch}, Client};
158-
/// 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?;
347+
/// # use kube::{api::{Api, PatchParams, Patch}, Client};
348+
/// # use k8s_openapi::api::batch::v1::Job;
349+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
350+
/// # let client = Client::try_default().await?;
162351
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
163352
/// let mut j = jobs.get("baz").await?;
164353
/// let pp = PatchParams::default(); // json merge patch
@@ -169,8 +358,8 @@ where
169358
/// });
170359
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
171360
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
172-
/// Ok(())
173-
/// }
361+
/// # Ok(())
362+
/// # }
174363
/// ```
175364
pub async fn patch_status<P: serde::Serialize + Debug>(
176365
&self,
@@ -192,18 +381,17 @@ where
192381
/// You can leave out the `.spec` entirely from the serialized output.
193382
///
194383
/// ```no_run
195-
/// use kube::{api::{Api, PostParams}, Client};
196-
/// 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?;
384+
/// # use kube::{api::{Api, PostParams}, Client};
385+
/// # use k8s_openapi::api::batch::v1::{Job, JobStatus};
386+
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
387+
/// # let client = Client::try_default().await?;
200388
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
201389
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
202390
/// o.status = Some(JobStatus::default()); // update the job part
203391
/// let pp = PostParams::default();
204392
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
205-
/// Ok(())
206-
/// }
393+
/// # Ok(())
394+
/// # }
207395
/// ```
208396
pub async fn replace_status(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<K> {
209397
let mut req = self

0 commit comments

Comments
 (0)