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}