Skip to content

Commit 6125f4a

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 a9f1a4f commit 6125f4a

File tree

4 files changed

+376
-30
lines changed

4 files changed

+376
-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,5 +1,5 @@
11
use futures::AsyncBufRead;
2-
use serde::de::DeserializeOwned;
2+
use serde::{de::DeserializeOwned, Serialize};
33
use std::fmt::Debug;
44

55
use crate::{
@@ -127,6 +127,160 @@ where
127127
}
128128
}
129129

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

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

0 commit comments

Comments
 (0)