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