kube_core/params.rs
1//! A port of request parameter *Optionals from apimachinery/types.go
2use crate::{Selector, request::Error};
3use serde::Serialize;
4
5/// Controls how the resource version parameter is applied for list calls
6///
7/// Not specifying a `VersionMatch` strategy will give you different semantics
8/// depending on what `resource_version`, `limit`, `continue_token` you include with the list request.
9///
10/// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#semantics-for-get-and-list> for details.
11#[derive(Clone, Debug, PartialEq)]
12pub enum VersionMatch {
13 /// Returns data at least as new as the provided resource version.
14 ///
15 /// The newest available data is preferred, but any data not older than the provided resource version may be served.
16 /// This guarantees that the collection's resource version is not older than the requested resource version,
17 /// but does not make any guarantee about the resource version of any of the items in that collection.
18 ///
19 /// ### Any Version
20 /// A degenerate, but common sub-case of `NotOlderThan` is when used together with `resource_version` "0".
21 ///
22 /// It is possible for a "0" resource version request to return data at a much older resource version
23 /// than the client has previously observed, particularly in HA configurations, due to partitions or stale caches.
24 /// Clients that cannot tolerate this should not use this semantic.
25 NotOlderThan,
26
27 /// Return data at the exact resource version provided.
28 ///
29 /// If the provided resource version is unavailable, the server responds with HTTP 410 "Gone".
30 /// For list requests to servers that honor the resource version Match parameter, this guarantees that the collection's
31 /// resource version is the same as the resource version you requested in the query string.
32 /// That guarantee does not apply to the resource version of any items within that collection.
33 ///
34 /// Note that `Exact` cannot be used with resource version "0". For the most up-to-date list; use `Unset`.
35 Exact,
36}
37
38/// Common query parameters used in list/delete calls on collections
39#[derive(Clone, Debug, Default, PartialEq)]
40pub struct ListParams {
41 /// A selector to restrict the list of returned objects by their labels.
42 ///
43 /// Defaults to everything if `None`.
44 pub label_selector: Option<String>,
45
46 /// A selector to restrict the list of returned objects by their fields.
47 ///
48 /// Defaults to everything if `None`.
49 pub field_selector: Option<String>,
50
51 /// Has no effect since this value is not sent with list requests.
52 ///
53 /// To limit the duration of a watch call, use [`WatchParams::timeout`].
54 #[deprecated(
55 since = "5.0.0",
56 note = "has no effect on list requests; use `WatchParams::timeout` for watch calls"
57 )]
58 pub timeout: Option<u32>,
59
60 /// Limit the number of results.
61 ///
62 /// If there are more results, the server will respond with a continue token which can be used to fetch another page
63 /// of results. See the [Kubernetes API docs](https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks)
64 /// for pagination details.
65 pub limit: Option<u32>,
66
67 /// Fetch a second page of results.
68 ///
69 /// After listing results with a limit, a continue token can be used to fetch another page of results.
70 pub continue_token: Option<String>,
71
72 /// Determines how resourceVersion is matched applied to list calls.
73 pub version_match: Option<VersionMatch>,
74
75 /// An explicit resourceVersion using the given `VersionMatch` strategy
76 ///
77 /// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions> for details.
78 pub resource_version: Option<String>,
79}
80
81impl ListParams {
82 pub(crate) fn validate(&self) -> Result<(), Error> {
83 if let Some(rv) = &self.resource_version {
84 if self.version_match == Some(VersionMatch::Exact) && rv == "0" {
85 return Err(Error::Validation(
86 "A non-zero resource_version is required when using an Exact match".into(),
87 ));
88 }
89 } else if self.version_match.is_some() {
90 return Err(Error::Validation(
91 "A resource_version is required when using an explicit match".into(),
92 ));
93 }
94 Ok(())
95 }
96
97 // Partially populate query parameters (needs resourceVersion out of band)
98 pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
99 if let Some(fields) = &self.field_selector {
100 qp.append_pair("fieldSelector", fields);
101 }
102 if let Some(labels) = &self.label_selector {
103 qp.append_pair("labelSelector", labels);
104 }
105 if let Some(limit) = &self.limit {
106 qp.append_pair("limit", &limit.to_string());
107 }
108 if let Some(continue_token) = &self.continue_token {
109 qp.append_pair("continue", continue_token);
110 } else if let Some(rv) = &self.resource_version
111 && (rv != "0" || self.limit.is_none())
112 {
113 // NB: When there's a continue token, we don't want to set resourceVersion
114 qp.append_pair("resourceVersion", rv.as_str());
115
116 match &self.version_match {
117 None => {}
118 Some(VersionMatch::NotOlderThan) => {
119 qp.append_pair("resourceVersionMatch", "NotOlderThan");
120 }
121 Some(VersionMatch::Exact) => {
122 qp.append_pair("resourceVersionMatch", "Exact");
123 }
124 }
125 }
126 }
127}
128
129/// Builder interface to ListParams
130///
131/// Usage:
132/// ```
133/// use kube::api::ListParams;
134/// let lp = ListParams::default()
135/// .match_any()
136/// .labels("kubernetes.io/lifecycle=spot");
137/// ```
138impl ListParams {
139 /// Set the timeout field
140 ///
141 /// Has no effect since this value is not sent with list requests.
142 /// To limit the duration of a watch call, use [`WatchParams::timeout`].
143 #[deprecated(
144 since = "5.0.0",
145 note = "has no effect on list requests; use `WatchParams::timeout` for watch calls"
146 )]
147 #[must_use]
148 #[allow(deprecated)]
149 pub fn timeout(mut self, timeout_secs: u32) -> Self {
150 self.timeout = Some(timeout_secs);
151 self
152 }
153
154 /// Configure the selector to restrict the list of returned objects by their fields.
155 ///
156 /// Defaults to everything.
157 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
158 /// The server only supports a limited number of field queries per type.
159 #[must_use]
160 pub fn fields(mut self, field_selector: &str) -> Self {
161 self.field_selector = Some(field_selector.to_string());
162 self
163 }
164
165 /// Configure the selector to restrict the list of returned objects by their labels.
166 ///
167 /// Defaults to everything.
168 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
169 #[must_use]
170 pub fn labels(mut self, label_selector: &str) -> Self {
171 self.label_selector = Some(label_selector.to_string());
172 self
173 }
174
175 /// Configure typed label selectors
176 ///
177 /// Configure typed selectors from [`Selector`](crate::Selector) and [`Expression`](crate::Expression) lists.
178 ///
179 /// ```
180 /// use kube::core::{Expression, Selector, ParseExpressionError};
181 /// # use kube::core::params::ListParams;
182 /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
183 ///
184 /// // From expressions
185 /// let selector: Selector = Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
186 /// let lp = ListParams::default().labels_from(&selector);
187 /// let lp = ListParams::default().labels_from(&Expression::Exists("foo".into()).into());
188 ///
189 /// // Native LabelSelector
190 /// let selector: Selector = LabelSelector::default().try_into()?;
191 /// let lp = ListParams::default().labels_from(&selector);
192 /// # Ok::<(), ParseExpressionError>(())
193 ///```
194 #[must_use]
195 pub fn labels_from(mut self, selector: &Selector) -> Self {
196 self.label_selector = Some(selector.to_string());
197 self
198 }
199
200 /// Sets a result limit.
201 #[must_use]
202 pub fn limit(mut self, limit: u32) -> Self {
203 self.limit = Some(limit);
204 self
205 }
206
207 /// Sets a continue token.
208 #[must_use]
209 pub fn continue_token(mut self, token: &str) -> Self {
210 self.continue_token = Some(token.to_string());
211 self
212 }
213
214 /// Sets the resource version
215 #[must_use]
216 pub fn at(mut self, resource_version: &str) -> Self {
217 self.resource_version = Some(resource_version.into());
218 self
219 }
220
221 /// Sets an arbitrary resource version match strategy
222 ///
223 /// A non-default strategy such as `VersionMatch::Exact` or `VersionMatch::NotOlderThan`
224 /// requires an explicit `resource_version` set to pass request validation.
225 #[must_use]
226 pub fn matching(mut self, version_match: VersionMatch) -> Self {
227 self.version_match = Some(version_match);
228 self
229 }
230
231 /// Use the semantic "any" resource version strategy
232 ///
233 /// This is a less taxing variant of the default list, returning data at any resource version.
234 /// It will prefer the newest available resource version, but strong consistency is not required;
235 /// data at any resource version may be served.
236 /// It is possible for the request to return data at a much older resource version than the client
237 /// has previously observed, particularly in high availability configurations, due to partitions or stale caches.
238 /// Clients that cannot tolerate this should not use this semantic.
239 #[must_use]
240 pub fn match_any(self) -> Self {
241 self.matching(VersionMatch::NotOlderThan).at("0")
242 }
243}
244
245/// Common query parameters used in get calls
246#[derive(Clone, Debug, Default, PartialEq)]
247pub struct GetParams {
248 /// An explicit resourceVersion with implicit version matching strategies
249 ///
250 /// Default (unset) gives the most recent version. "0" gives a less
251 /// consistent, but more performant "Any" version. Specifying a version is
252 /// like providing a `VersionMatch::NotOlderThan`.
253 /// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions> for details.
254 pub resource_version: Option<String>,
255}
256
257/// Helper interface to GetParams
258///
259/// Usage:
260/// ```
261/// use kube::api::GetParams;
262/// let gp = GetParams::at("6664");
263/// ```
264impl GetParams {
265 /// Sets the resource version, implicitly applying a 'NotOlderThan' match
266 #[must_use]
267 pub fn at(resource_version: &str) -> Self {
268 Self {
269 resource_version: Some(resource_version.into()),
270 }
271 }
272
273 /// Sets the resource version to "0"
274 #[must_use]
275 pub fn any() -> Self {
276 Self::at("0")
277 }
278}
279
280/// The validation directive to use for `fieldValidation` when using server-side apply.
281#[derive(Clone, Debug)]
282pub enum ValidationDirective {
283 /// Strict mode will fail any invalid manifests.
284 ///
285 /// This will fail the request with a BadRequest error if any unknown fields would be dropped from the
286 /// object, or if any duplicate fields are present. The error returned from the server will contain
287 /// all unknown and duplicate fields encountered.
288 Strict,
289 /// Warn mode will return a warning for invalid manifests.
290 ///
291 /// This will send a warning via the standard warning response header for each unknown field that
292 /// is dropped from the object, and for each duplicate field that is encountered. The request will
293 /// still succeed if there are no other errors, and will only persist the last of any duplicate fields.
294 Warn,
295 /// Ignore mode will silently ignore any problems.
296 ///
297 /// This will ignore any unknown fields that are silently dropped from the object, and will ignore
298 /// all but the last duplicate field that the decoder encounters.
299 Ignore,
300}
301
302impl ValidationDirective {
303 /// Returns the string format of the directive
304 pub fn as_str(&self) -> &str {
305 match self {
306 Self::Strict => "Strict",
307 Self::Warn => "Warn",
308 Self::Ignore => "Ignore",
309 }
310 }
311}
312
313/// Common query parameters used in watch calls on collections
314#[derive(Clone, Debug, PartialEq)]
315pub struct WatchParams {
316 /// A selector to restrict returned objects by their labels.
317 ///
318 /// Defaults to everything if `None`.
319 pub label_selector: Option<String>,
320
321 /// A selector to restrict returned objects by their fields.
322 ///
323 /// Defaults to everything if `None`.
324 pub field_selector: Option<String>,
325
326 /// Timeout for the watch call.
327 ///
328 /// This limits the duration of the call, regardless of any activity or inactivity.
329 /// If unset for a watch call, we will use 290s.
330 /// The watcher's dead-connection detection window is this value plus 5s,
331 /// so larger values delay noticing a silently dropped connection.
332 pub timeout: Option<u32>,
333
334 /// Enables watch events with type "BOOKMARK".
335 ///
336 /// Servers that do not implement bookmarks ignore this flag and
337 /// bookmarks are sent at the server's discretion. Clients should not
338 /// assume bookmarks are returned at any specific interval, nor may they
339 /// assume the server will send any BOOKMARK event during a session.
340 /// If this is not a watch, this field is ignored.
341 /// If the feature gate WatchBookmarks is not enabled in apiserver,
342 /// this field is ignored.
343 pub bookmarks: bool,
344
345 /// Kubernetes 1.27 Streaming Lists
346 /// `sendInitialEvents=true` may be set together with `watch=true`.
347 /// In that case, the watch stream will begin with synthetic events to
348 /// produce the current state of objects in the collection. Once all such
349 /// events have been sent, a synthetic "Bookmark" event will be sent.
350 /// The bookmark will report the ResourceVersion (RV) corresponding to the
351 /// set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation.
352 /// Afterwards, the watch stream will proceed as usual, sending watch events
353 /// corresponding to changes (subsequent to the RV) to objects watched.
354 ///
355 /// When `sendInitialEvents` option is set, we require `resourceVersionMatch`
356 /// option to also be set. The semantic of the watch request is as following:
357 /// - `resourceVersionMatch` = NotOlderThan
358 /// is interpreted as "data at least as new as the provided `resourceVersion`"
359 /// and the bookmark event is send when the state is synced
360 /// to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
361 /// If `resourceVersion` is unset, this is interpreted as "consistent read" and the
362 /// bookmark event is send when the state is synced at least to the moment
363 /// when request started being processed.
364 /// - `resourceVersionMatch` set to any other value or unset
365 /// Invalid error is returned.
366 pub send_initial_events: bool,
367}
368
369impl WatchParams {
370 pub(crate) fn validate(&self) -> Result<(), Error> {
371 if self.send_initial_events && !self.bookmarks {
372 return Err(Error::Validation(
373 "WatchParams::bookmarks must be set when using send_initial_events".into(),
374 ));
375 }
376 Ok(())
377 }
378
379 // Partially populate query parameters (needs resourceVersion out of band)
380 pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
381 qp.append_pair("watch", "true");
382
383 qp.append_pair("timeoutSeconds", &self.timeout.unwrap_or(290).to_string());
384
385 if let Some(fields) = &self.field_selector {
386 qp.append_pair("fieldSelector", fields);
387 }
388 if let Some(labels) = &self.label_selector {
389 qp.append_pair("labelSelector", labels);
390 }
391 if self.bookmarks {
392 qp.append_pair("allowWatchBookmarks", "true");
393 }
394 if self.send_initial_events {
395 qp.append_pair("sendInitialEvents", "true");
396 qp.append_pair("resourceVersionMatch", "NotOlderThan");
397 }
398 }
399}
400
401impl Default for WatchParams {
402 /// Default `WatchParams` without any constricting selectors
403 fn default() -> Self {
404 Self {
405 // bookmarks stable since 1.17, and backwards compatible
406 bookmarks: true,
407
408 label_selector: None,
409 field_selector: None,
410 timeout: None,
411 send_initial_events: false,
412 }
413 }
414}
415
416/// Builder interface to WatchParams
417///
418/// Usage:
419/// ```
420/// use kube::api::WatchParams;
421/// let lp = WatchParams::default()
422/// .timeout(60)
423/// .labels("kubernetes.io/lifecycle=spot");
424/// ```
425impl WatchParams {
426 /// Configure the timeout for watch calls
427 ///
428 /// This limits the duration of the call, regardless of any activity or inactivity.
429 /// Defaults to 290s
430 #[must_use]
431 pub fn timeout(mut self, timeout_secs: u32) -> Self {
432 self.timeout = Some(timeout_secs);
433 self
434 }
435
436 /// Configure the selector to restrict the list of returned objects by their fields.
437 ///
438 /// Defaults to everything.
439 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
440 /// The server only supports a limited number of field queries per type.
441 #[must_use]
442 pub fn fields(mut self, field_selector: &str) -> Self {
443 self.field_selector = Some(field_selector.to_string());
444 self
445 }
446
447 /// Configure the selector to restrict the list of returned objects by their labels.
448 ///
449 /// Defaults to everything.
450 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
451 #[must_use]
452 pub fn labels(mut self, label_selector: &str) -> Self {
453 self.label_selector = Some(label_selector.to_string());
454 self
455 }
456
457 /// Configure typed label selectors
458 ///
459 /// Configure typed selectors from [`Selector`](crate::Selector) and [`Expression`](crate::Expression) lists.
460 ///
461 /// ```
462 /// use kube::core::{Expression, Selector, ParseExpressionError};
463 /// # use kube::core::params::WatchParams;
464 /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
465 ///
466 /// // From expressions
467 /// let selector: Selector = Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
468 /// let wp = WatchParams::default().labels_from(&selector);
469 /// let wp = WatchParams::default().labels_from(&Expression::Exists("foo".into()).into());
470 ///
471 /// // Native LabelSelector
472 /// let selector: Selector = LabelSelector::default().try_into()?;
473 /// let wp = WatchParams::default().labels_from(&selector);
474 /// # Ok::<(), ParseExpressionError>(())
475 ///```
476 #[must_use]
477 pub fn labels_from(mut self, selector: &Selector) -> Self {
478 self.label_selector = Some(selector.to_string());
479 self
480 }
481
482 /// Disables watch bookmarks to simplify watch handling
483 ///
484 /// This is not recommended to use with production watchers as it can cause desyncs.
485 /// See [#219](https://github.com/kube-rs/kube/issues/219) for details.
486 #[must_use]
487 pub fn disable_bookmarks(mut self) -> Self {
488 self.bookmarks = false;
489 self
490 }
491
492 /// Kubernetes 1.27 Streaming Lists
493 /// `sendInitialEvents=true` may be set together with `watch=true`.
494 /// In that case, the watch stream will begin with synthetic events to
495 /// produce the current state of objects in the collection. Once all such
496 /// events have been sent, a synthetic "Bookmark" event will be sent.
497 /// The bookmark will report the ResourceVersion (RV) corresponding to the
498 /// set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation.
499 /// Afterwards, the watch stream will proceed as usual, sending watch events
500 /// corresponding to changes (subsequent to the RV) to objects watched.
501 ///
502 /// When `sendInitialEvents` option is set, we require `resourceVersionMatch`
503 /// option to also be set. The semantic of the watch request is as following:
504 /// - `resourceVersionMatch` = NotOlderThan
505 /// is interpreted as "data at least as new as the provided `resourceVersion`"
506 /// and the bookmark event is send when the state is synced
507 /// to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
508 /// If `resourceVersion` is unset, this is interpreted as "consistent read" and the
509 /// bookmark event is send when the state is synced at least to the moment
510 /// when request started being processed.
511 /// - `resourceVersionMatch` set to any other value or unset
512 /// Invalid error is returned.
513 ///
514 /// Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward
515 /// compatibility reasons) and to false otherwise.
516 #[must_use]
517 pub fn initial_events(mut self) -> Self {
518 self.send_initial_events = true;
519
520 self
521 }
522
523 /// Constructor for doing Kubernetes 1.27 Streaming List watches
524 ///
525 /// Enables [`VersionMatch::NotOlderThan`] semantics and [`WatchParams::send_initial_events`].
526 pub fn streaming_lists() -> Self {
527 Self {
528 send_initial_events: true,
529 bookmarks: true, // required
530 ..WatchParams::default()
531 }
532 }
533}
534
535/// Common query parameters for put/post calls
536#[derive(Default, Clone, Debug, PartialEq)]
537pub struct PostParams {
538 /// Whether to run this as a dry run
539 pub dry_run: bool,
540 /// fieldManager is a name of the actor that is making changes
541 pub field_manager: Option<String>,
542}
543
544impl PostParams {
545 pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
546 if self.dry_run {
547 qp.append_pair("dryRun", "All");
548 }
549 if let Some(ref fm) = self.field_manager {
550 qp.append_pair("fieldManager", fm);
551 }
552 }
553
554 pub(crate) fn validate(&self) -> Result<(), Error> {
555 if let Some(field_manager) = &self.field_manager {
556 // Implement the easy part of validation, in future this may be extended to provide validation as in go code
557 // For now it's fine, because k8s API server will return an error
558 if field_manager.len() > 128 {
559 return Err(Error::Validation(
560 "Failed to validate PostParams::field_manager!".into(),
561 ));
562 }
563 }
564 Ok(())
565 }
566}
567
568/// Describes changes that should be applied to a resource
569///
570/// Takes arbitrary serializable data for all strategies except `Json`.
571///
572/// We recommend using ([server-side](https://kubernetes.io/blog/2020/04/01/kubernetes-1.18-feature-server-side-apply-beta-2)) `Apply` patches on new kubernetes releases.
573///
574/// See [kubernetes patch docs](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) for the older patch types.
575///
576/// Note that patches have different effects on different fields depending on their merge strategies.
577/// These strategies are configurable when deriving your [`CustomResource`](https://docs.rs/kube-derive/*/kube_derive/derive.CustomResource.html#customizing-schemas).
578///
579/// # Creating a patch via serde_json
580/// ```
581/// use kube::api::Patch;
582/// let patch = serde_json::json!({
583/// "apiVersion": "v1",
584/// "kind": "Pod",
585/// "metadata": {
586/// "name": "blog"
587/// },
588/// "spec": {
589/// "activeDeadlineSeconds": 5
590/// }
591/// });
592/// let patch = Patch::Apply(&patch);
593/// ```
594/// # Creating a patch from a type
595/// ```
596/// use kube::api::Patch;
597/// use k8s_openapi::api::rbac::v1::Role;
598/// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
599/// let r = Role {
600/// metadata: ObjectMeta { name: Some("user".into()), ..ObjectMeta::default() },
601/// rules: Some(vec![])
602/// };
603/// let patch = Patch::Apply(&r);
604/// ```
605#[non_exhaustive]
606#[derive(Debug, PartialEq, Clone)]
607pub enum Patch<T: Serialize> {
608 /// [Server side apply](https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply)
609 ///
610 /// Requires kubernetes >= 1.16
611 Apply(T),
612
613 /// [JSON patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
614 ///
615 /// Using this variant will require you to explicitly provide a type for `T` at the moment.
616 ///
617 /// # Example
618 ///
619 /// ```
620 /// use kube::api::Patch;
621 /// let json_patch = json_patch::Patch(vec![]);
622 /// let patch = Patch::Json::<()>(json_patch);
623 /// ```
624 #[cfg(feature = "jsonpatch")]
625 #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
626 Json(json_patch::Patch),
627
628 /// [JSON Merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
629 Merge(T),
630 /// [Strategic JSON Merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-strategic-merge-patch-to-update-a-deployment)
631 Strategic(T),
632}
633
634impl<T: Serialize> Patch<T> {
635 pub(crate) fn is_apply(&self) -> bool {
636 matches!(self, Patch::Apply(_))
637 }
638
639 pub(crate) fn content_type(&self) -> &'static str {
640 match &self {
641 Self::Apply(_) => "application/apply-patch+yaml",
642 #[cfg(feature = "jsonpatch")]
643 #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
644 Self::Json(_) => "application/json-patch+json",
645 Self::Merge(_) => "application/merge-patch+json",
646 Self::Strategic(_) => "application/strategic-merge-patch+json",
647 }
648 }
649}
650
651impl<T: Serialize> Patch<T> {
652 pub(crate) fn serialize(&self) -> Result<Vec<u8>, serde_json::Error> {
653 match self {
654 Self::Apply(p) => serde_json::to_vec(p),
655 #[cfg(feature = "jsonpatch")]
656 #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
657 Self::Json(p) => serde_json::to_vec(p),
658 Self::Strategic(p) => serde_json::to_vec(p),
659 Self::Merge(p) => serde_json::to_vec(p),
660 }
661 }
662}
663
664/// Common query parameters for patch calls
665#[derive(Default, Clone, Debug)]
666pub struct PatchParams {
667 /// Whether to run this as a dry run
668 pub dry_run: bool,
669 /// force Apply requests. Applicable only to [`Patch::Apply`].
670 pub force: bool,
671 /// fieldManager is a name of the actor that is making changes. Required for [`Patch::Apply`]
672 /// optional for everything else.
673 pub field_manager: Option<String>,
674 /// The server-side validation directive to use. Applicable only to [`Patch::Apply`].
675 pub field_validation: Option<ValidationDirective>,
676}
677
678impl PatchParams {
679 pub(crate) fn validate<P: Serialize>(&self, patch: &Patch<P>) -> Result<(), Error> {
680 if let Some(field_manager) = &self.field_manager {
681 // Implement the easy part of validation, in future this may be extended to provide validation as in go code
682 // For now it's fine, because k8s API server will return an error
683 if field_manager.len() > 128 {
684 return Err(Error::Validation(
685 "Failed to validate PatchParams::field_manager!".into(),
686 ));
687 }
688 }
689 if self.force && !patch.is_apply() {
690 return Err(Error::Validation(
691 "PatchParams::force only works with Patch::Apply".into(),
692 ));
693 }
694 Ok(())
695 }
696
697 pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
698 if self.dry_run {
699 qp.append_pair("dryRun", "All");
700 }
701 if self.force {
702 qp.append_pair("force", "true");
703 }
704 if let Some(ref fm) = self.field_manager {
705 qp.append_pair("fieldManager", fm);
706 }
707 if let Some(sv) = &self.field_validation {
708 qp.append_pair("fieldValidation", sv.as_str());
709 }
710 }
711
712 /// Construct `PatchParams` for server-side apply
713 #[must_use]
714 pub fn apply(manager: &str) -> Self {
715 Self {
716 field_manager: Some(manager.into()),
717 ..Self::default()
718 }
719 }
720
721 /// Force the result through on conflicts
722 ///
723 /// NB: Force is a concept restricted to the server-side [`Patch::Apply`].
724 #[must_use]
725 pub fn force(mut self) -> Self {
726 self.force = true;
727 self
728 }
729
730 /// Perform a dryRun only
731 #[must_use]
732 pub fn dry_run(mut self) -> Self {
733 self.dry_run = true;
734 self
735 }
736
737 /// Set the validation directive for `fieldValidation` during server-side apply.
738 pub fn validation(mut self, vd: ValidationDirective) -> Self {
739 self.field_validation = Some(vd);
740 self
741 }
742
743 /// Set the validation directive to `Ignore`
744 #[must_use]
745 pub fn validation_ignore(self) -> Self {
746 self.validation(ValidationDirective::Ignore)
747 }
748
749 /// Set the validation directive to `Warn`
750 #[must_use]
751 pub fn validation_warn(self) -> Self {
752 self.validation(ValidationDirective::Warn)
753 }
754
755 /// Set the validation directive to `Strict`
756 #[must_use]
757 pub fn validation_strict(self) -> Self {
758 self.validation(ValidationDirective::Strict)
759 }
760}
761
762/// Common query parameters for delete calls
763#[derive(Default, Clone, Serialize, Debug, PartialEq)]
764#[serde(rename_all = "camelCase")]
765pub struct DeleteParams {
766 /// When present, indicates that modifications should not be persisted.
767 #[serde(
768 serialize_with = "dry_run_all_ser",
769 skip_serializing_if = "std::ops::Not::not"
770 )]
771 pub dry_run: bool,
772
773 /// The duration in seconds before the object should be deleted.
774 ///
775 /// Value must be non-negative integer. The value zero indicates delete immediately.
776 /// If this value is `None`, the default grace period for the specified type will be used.
777 /// Defaults to a per object value if not specified. Zero means delete immediately.
778 ///
779 /// Graceful deletion support is resource-specific. Setting this does not generally
780 /// guarantee that `metadata.deletionTimestamp` will be populated: Pods support
781 /// graceful deletion, while resources such as PodDisruptionBudgets do not.
782 /// Use finalizers when cleanup must complete before an object is deleted.
783 #[serde(skip_serializing_if = "Option::is_none")]
784 pub grace_period_seconds: Option<u32>,
785
786 /// Whether or how garbage collection is performed.
787 ///
788 /// The default policy is decided by the existing finalizer set in
789 /// `metadata.finalizers`, and the resource-specific default policy.
790 #[serde(skip_serializing_if = "Option::is_none")]
791 pub propagation_policy: Option<PropagationPolicy>,
792
793 /// Conditions that must be fulfilled before a deletion is carried out
794 ///
795 /// If not possible, a `409 Conflict` status will be returned.
796 #[serde(skip_serializing_if = "Option::is_none")]
797 pub preconditions: Option<Preconditions>,
798}
799
800impl DeleteParams {
801 /// Construct `DeleteParams` with `PropagationPolicy::Background`.
802 ///
803 /// This allows the garbage collector to delete the dependents in the background.
804 pub fn background() -> Self {
805 Self {
806 propagation_policy: Some(PropagationPolicy::Background),
807 ..Self::default()
808 }
809 }
810
811 /// Construct `DeleteParams` with `PropagationPolicy::Foreground`.
812 ///
813 /// This is a cascading policy that deletes all dependents in the foreground.
814 pub fn foreground() -> Self {
815 Self {
816 propagation_policy: Some(PropagationPolicy::Foreground),
817 ..Self::default()
818 }
819 }
820
821 /// Construct `DeleteParams` with `PropagationPolicy::Orphan`.
822 ///
823 ///
824 /// This orphans the dependents.
825 pub fn orphan() -> Self {
826 Self {
827 propagation_policy: Some(PropagationPolicy::Orphan),
828 ..Self::default()
829 }
830 }
831
832 /// Perform a dryRun only
833 #[must_use]
834 pub fn dry_run(mut self) -> Self {
835 self.dry_run = true;
836 self
837 }
838
839 /// Set the duration in seconds before the object should be deleted.
840 #[must_use]
841 pub fn grace_period(mut self, secs: u32) -> Self {
842 self.grace_period_seconds = Some(secs);
843 self
844 }
845
846 /// Set the conditions that must be fulfilled before a deletion is carried out.
847 #[must_use]
848 pub fn preconditions(mut self, preconditions: Preconditions) -> Self {
849 self.preconditions = Some(preconditions);
850 self
851 }
852
853 pub(crate) fn is_default(&self) -> bool {
854 !self.dry_run
855 && self.grace_period_seconds.is_none()
856 && self.propagation_policy.is_none()
857 && self.preconditions.is_none()
858 }
859}
860
861// dryRun serialization differ when used as body parameters and query strings:
862// query strings are either true/false
863// body params allow only: missing field, or ["All"]
864// The latter is a very awkward API causing users to do to
865// dp.dry_run = vec!["All".into()];
866// just to turn on dry_run..
867// so we hide this detail for now.
868fn dry_run_all_ser<S>(t: &bool, s: S) -> std::result::Result<S::Ok, S::Error>
869where
870 S: serde::ser::Serializer,
871{
872 use serde::ser::SerializeTuple;
873 match t {
874 true => {
875 let mut map = s.serialize_tuple(1)?;
876 map.serialize_element("All")?;
877 map.end()
878 }
879 false => s.serialize_none(),
880 }
881}
882#[cfg(test)]
883mod test {
884 use crate::{Expression, Selector, params::WatchParams};
885
886 use super::{DeleteParams, ListParams, PatchParams};
887 #[test]
888 fn delete_param_serialize() {
889 let mut dp = DeleteParams::default();
890 let emptyser = serde_json::to_string(&dp).unwrap();
891 //println!("emptyser is: {}", emptyser);
892 assert_eq!(emptyser, "{}");
893
894 dp.dry_run = true;
895 let ser = serde_json::to_string(&dp).unwrap();
896 //println!("ser is: {}", ser);
897 assert_eq!(ser, "{\"dryRun\":[\"All\"]}");
898 }
899
900 #[test]
901 fn delete_param_constructors() {
902 let dp_background = DeleteParams::background();
903 let ser = serde_json::to_value(dp_background).unwrap();
904 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Background"}));
905
906 let dp_foreground = DeleteParams::foreground();
907 let ser = serde_json::to_value(dp_foreground).unwrap();
908 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Foreground"}));
909
910 let dp_orphan = DeleteParams::orphan();
911 let ser = serde_json::to_value(dp_orphan).unwrap();
912 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Orphan"}));
913 }
914
915 #[test]
916 fn patch_param_serializes_field_validation() {
917 let pp = PatchParams::default().validation_ignore();
918 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
919 pp.populate_qp(&mut qp);
920 let urlstr = qp.finish();
921 assert_eq!(String::from("some/resource?&fieldValidation=Ignore"), urlstr);
922
923 let pp = PatchParams::default().validation_warn();
924 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
925 pp.populate_qp(&mut qp);
926 let urlstr = qp.finish();
927 assert_eq!(String::from("some/resource?&fieldValidation=Warn"), urlstr);
928
929 let pp = PatchParams::default().validation_strict();
930 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
931 pp.populate_qp(&mut qp);
932 let urlstr = qp.finish();
933 assert_eq!(String::from("some/resource?&fieldValidation=Strict"), urlstr);
934 }
935
936 #[test]
937 fn list_params_serialize() {
938 let selector: Selector =
939 Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
940 let lp = ListParams::default().labels_from(&selector);
941 let labels = lp.label_selector.unwrap();
942 assert_eq!(labels, "env in (development,sandbox)");
943 }
944
945 #[test]
946 fn watch_params_serialize() {
947 let selector: Selector =
948 Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
949 let wp = WatchParams::default().labels_from(&selector);
950 let labels = wp.label_selector.unwrap();
951 assert_eq!(labels, "env in (development,sandbox)");
952 }
953}
954
955/// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
956#[derive(Default, Clone, Serialize, Debug, PartialEq)]
957#[serde(rename_all = "camelCase")]
958pub struct Preconditions {
959 /// Specifies the target ResourceVersion
960 #[serde(skip_serializing_if = "Option::is_none")]
961 pub resource_version: Option<String>,
962 /// Specifies the target UID
963 #[serde(skip_serializing_if = "Option::is_none")]
964 pub uid: Option<String>,
965}
966
967/// Propagation policy when deleting single objects
968#[derive(Clone, Debug, Serialize, PartialEq)]
969pub enum PropagationPolicy {
970 /// Orphan dependents
971 Orphan,
972 /// Allow the garbage collector to delete the dependents in the background
973 Background,
974 /// A cascading policy that deletes all dependents in the foreground
975 Foreground,
976}