Skip to main content

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}