kube_core/params.rs
1//! A port of request parameter *Optionals from apimachinery/types.go
2use crate::{request::Error, Selector};
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 {
107 // When there's a continue token, we don't want to set resourceVersion
108 if let Some(rv) = &self.resource_version {
109 if rv != "0" || self.limit.is_none() {
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}
126
127/// Builder interface to ListParams
128///
129/// Usage:
130/// ```
131/// use kube::api::ListParams;
132/// let lp = ListParams::default()
133/// .match_any()
134/// .timeout(60)
135/// .labels("kubernetes.io/lifecycle=spot");
136/// ```
137impl ListParams {
138 /// Configure the timeout for list/watch calls
139 ///
140 /// This limits the duration of the call, regardless of any activity or inactivity.
141 /// Defaults to 290s
142 #[must_use]
143 pub fn timeout(mut self, timeout_secs: u32) -> Self {
144 self.timeout = Some(timeout_secs);
145 self
146 }
147
148 /// Configure the selector to restrict the list of returned objects by their fields.
149 ///
150 /// Defaults to everything.
151 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
152 /// The server only supports a limited number of field queries per type.
153 #[must_use]
154 pub fn fields(mut self, field_selector: &str) -> Self {
155 self.field_selector = Some(field_selector.to_string());
156 self
157 }
158
159 /// Configure the selector to restrict the list of returned objects by their labels.
160 ///
161 /// Defaults to everything.
162 /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
163 #[must_use]
164 pub fn labels(mut self, label_selector: &str) -> Self {
165 self.label_selector = Some(label_selector.to_string());
166 self
167 }
168
169 /// Configure typed label selectors
170 ///
171 /// Configure typed selectors from [`Selector`](crate::Selector) and [`Expression`](crate::Expression) lists.
172 ///
173 /// ```
174 /// use kube::core::{Expression, Selector, ParseExpressionError};
175 /// # use kube::core::params::ListParams;
176 /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
177 ///
178 /// // From expressions
179 /// let selector: Selector = Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
180 /// let lp = ListParams::default().labels_from(&selector);
181 /// let lp = ListParams::default().labels_from(&Expression::Exists("foo".into()).into());
182 ///
183 /// // Native LabelSelector
184 /// let selector: Selector = LabelSelector::default().try_into()?;
185 /// let lp = ListParams::default().labels_from(&selector);
186 /// # Ok::<(), ParseExpressionError>(())
187 ///```
188 #[must_use]
189 pub fn labels_from(mut self, selector: &Selector) -> Self {
190 self.label_selector = Some(selector.to_string());
191 self
192 }
193
194 /// Sets a result limit.
195 #[must_use]
196 pub fn limit(mut self, limit: u32) -> Self {
197 self.limit = Some(limit);
198 self
199 }
200
201 /// Sets a continue token.
202 #[must_use]
203 pub fn continue_token(mut self, token: &str) -> Self {
204 self.continue_token = Some(token.to_string());
205 self
206 }
207
208 /// Sets the resource version
209 #[must_use]
210 pub fn at(mut self, resource_version: &str) -> Self {
211 self.resource_version = Some(resource_version.into());
212 self
213 }
214
215 /// Sets an arbitary resource version match strategy
216 ///
217 /// A non-default strategy such as `VersionMatch::Exact` or `VersionMatch::NotOlderThan`
218 /// requires an explicit `resource_version` set to pass request validation.
219 #[must_use]
220 pub fn matching(mut self, version_match: VersionMatch) -> Self {
221 self.version_match = Some(version_match);
222 self
223 }
224
225 /// Use the semantic "any" resource version strategy
226 ///
227 /// This is a less taxing variant of the default list, returning data at any resource version.
228 /// It will prefer the newest avialable resource version, but strong consistency is not required;
229 /// data at any resource version may be served.
230 /// It is possible for the request to return data at a much older resource version than the client
231 /// has previously observed, particularly in high availability configurations, due to partitions or stale caches.
232 /// Clients that cannot tolerate this should not use this semantic.
233 #[must_use]
234 pub fn match_any(self) -> Self {
235 self.matching(VersionMatch::NotOlderThan).at("0")
236 }
237}
238
239/// Common query parameters used in get calls
240#[derive(Clone, Debug, Default, PartialEq)]
241pub struct GetParams {
242 /// An explicit resourceVersion with implicit version matching strategies
243 ///
244 /// Default (unset) gives the most recent version. "0" gives a less
245 /// consistent, but more performant "Any" version. Specifing a version is
246 /// like providing a `VersionMatch::NotOlderThan`.
247 /// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions> for details.
248 pub resource_version: Option<String>,
249}
250
251/// Helper interface to GetParams
252///
253/// Usage:
254/// ```
255/// use kube::api::GetParams;
256/// let gp = GetParams::at("6664");
257/// ```
258impl GetParams {
259 /// Sets the resource version, implicitly applying a 'NotOlderThan' match
260 #[must_use]
261 pub fn at(resource_version: &str) -> Self {
262 Self {
263 resource_version: Some(resource_version.into()),
264 }
265 }
266
267 /// Sets the resource version to "0"
268 #[must_use]
269 pub fn any() -> Self {
270 Self::at("0")
271 }
272}
273
274/// The validation directive to use for `fieldValidation` when using server-side apply.
275#[derive(Clone, Debug)]
276pub enum ValidationDirective {
277 /// Strict mode will fail any invalid manifests.
278 ///
279 /// This will fail the request with a BadRequest error if any unknown fields would be dropped from the
280 /// object, or if any duplicate fields are present. The error returned from the server will contain
281 /// all unknown and duplicate fields encountered.
282 Strict,
283 /// Warn mode will return a warning for invalid manifests.
284 ///
285 /// This will send a warning via the standard warning response header for each unknown field that
286 /// is dropped from the object, and for each duplicate field that is encountered. The request will
287 /// still succeed if there are no other errors, and will only persist the last of any duplicate fields.
288 Warn,
289 /// Ignore mode will silently ignore any problems.
290 ///
291 /// This will ignore any unknown fields that are silently dropped from the object, and will ignore
292 /// all but the last duplicate field that the decoder encounters.
293 Ignore,
294}
295
296impl ValidationDirective {
297 /// Returns the string format of the directive
298 pub fn as_str(&self) -> &str {
299 match self {
300 Self::Strict => "Strict",
301 Self::Warn => "Warn",
302 Self::Ignore => "Ignore",
303 }
304 }
305}
306
307/// Common query parameters used in watch calls on collections
308#[derive(Clone, Debug, PartialEq)]
309pub struct WatchParams {
310 /// A selector to restrict returned objects by their labels.
311 ///
312 /// Defaults to everything if `None`.
313 pub label_selector: Option<String>,
314
315 /// A selector to restrict returned objects by their fields.
316 ///
317 /// Defaults to everything if `None`.
318 pub field_selector: Option<String>,
319
320 /// Timeout for the watch call.
321 ///
322 /// This limits the duration of the call, regardless of any activity or inactivity.
323 /// If unset for a watch call, we will use 290s.
324 /// We limit this to 295s due to [inherent watch limitations](https://github.com/kubernetes/kubernetes/issues/6513).
325 pub timeout: Option<u32>,
326
327 /// Enables watch events with type "BOOKMARK".
328 ///
329 /// Servers that do not implement bookmarks ignore this flag and
330 /// bookmarks are sent at the server's discretion. Clients should not
331 /// assume bookmarks are returned at any specific interval, nor may they
332 /// assume the server will send any BOOKMARK event during a session.
333 /// If this is not a watch, this field is ignored.
334 /// If the feature gate WatchBookmarks is not enabled in apiserver,
335 /// this field is ignored.
336 pub bookmarks: bool,
337
338 /// Kubernetes 1.27 Streaming Lists
339 /// `sendInitialEvents=true` may be set together with `watch=true`.
340 /// In that case, the watch stream will begin with synthetic events to
341 /// produce the current state of objects in the collection. Once all such
342 /// events have been sent, a synthetic "Bookmark" event will be sent.
343 /// The bookmark will report the ResourceVersion (RV) corresponding to the
344 /// set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation.
345 /// Afterwards, the watch stream will proceed as usual, sending watch events
346 /// corresponding to changes (subsequent to the RV) to objects watched.
347 ///
348 /// When `sendInitialEvents` option is set, we require `resourceVersionMatch`
349 /// option to also be set. The semantic of the watch request is as following:
350 /// - `resourceVersionMatch` = NotOlderThan
351 /// is interpreted as "data at least as new as the provided `resourceVersion`"
352 /// and the bookmark event is send when the state is synced
353 /// to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
354 /// If `resourceVersion` is unset, this is interpreted as "consistent read" and the
355 /// bookmark event is send when the state is synced at least to the moment
356 /// when request started being processed.
357 /// - `resourceVersionMatch` set to any other value or unset
358 /// Invalid error is returned.
359 pub send_initial_events: bool,
360}
361
362impl WatchParams {
363 pub(crate) fn validate(&self) -> Result<(), Error> {
364 if let Some(to) = &self.timeout {
365 // https://github.com/kubernetes/kubernetes/issues/6513
366 if *to >= 295 {
367 return Err(Error::Validation("WatchParams::timeout must be < 295s".into()));
368 }
369 }
370 if self.send_initial_events && !self.bookmarks {
371 return Err(Error::Validation(
372 "WatchParams::bookmarks must be set when using send_initial_events".into(),
373 ));
374 }
375 Ok(())
376 }
377
378 // Partially populate query parameters (needs resourceVersion out of band)
379 pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
380 qp.append_pair("watch", "true");
381
382 // https://github.com/kubernetes/kubernetes/issues/6513
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 #[serde(skip_serializing_if = "Option::is_none")]
779 pub grace_period_seconds: Option<u32>,
780
781 /// Whether or how garbage collection is performed.
782 ///
783 /// The default policy is decided by the existing finalizer set in
784 /// `metadata.finalizers`, and the resource-specific default policy.
785 #[serde(skip_serializing_if = "Option::is_none")]
786 pub propagation_policy: Option<PropagationPolicy>,
787
788 /// Condtions that must be fulfilled before a deletion is carried out
789 ///
790 /// If not possible, a `409 Conflict` status will be returned.
791 #[serde(skip_serializing_if = "Option::is_none")]
792 pub preconditions: Option<Preconditions>,
793}
794
795impl DeleteParams {
796 /// Construct `DeleteParams` with `PropagationPolicy::Background`.
797 ///
798 /// This allows the garbage collector to delete the dependents in the background.
799 pub fn background() -> Self {
800 Self {
801 propagation_policy: Some(PropagationPolicy::Background),
802 ..Self::default()
803 }
804 }
805
806 /// Construct `DeleteParams` with `PropagationPolicy::Foreground`.
807 ///
808 /// This is a cascading policy that deletes all dependents in the foreground.
809 pub fn foreground() -> Self {
810 Self {
811 propagation_policy: Some(PropagationPolicy::Foreground),
812 ..Self::default()
813 }
814 }
815
816 /// Construct `DeleteParams` with `PropagationPolicy::Orphan`.
817 ///
818 ///
819 /// This orpans the dependents.
820 pub fn orphan() -> Self {
821 Self {
822 propagation_policy: Some(PropagationPolicy::Orphan),
823 ..Self::default()
824 }
825 }
826
827 /// Perform a dryRun only
828 #[must_use]
829 pub fn dry_run(mut self) -> Self {
830 self.dry_run = true;
831 self
832 }
833
834 /// Set the duration in seconds before the object should be deleted.
835 #[must_use]
836 pub fn grace_period(mut self, secs: u32) -> Self {
837 self.grace_period_seconds = Some(secs);
838 self
839 }
840
841 /// Set the condtions that must be fulfilled before a deletion is carried out.
842 #[must_use]
843 pub fn preconditions(mut self, preconditions: Preconditions) -> Self {
844 self.preconditions = Some(preconditions);
845 self
846 }
847
848 pub(crate) fn is_default(&self) -> bool {
849 !self.dry_run
850 && self.grace_period_seconds.is_none()
851 && self.propagation_policy.is_none()
852 && self.preconditions.is_none()
853 }
854}
855
856// dryRun serialization differ when used as body parameters and query strings:
857// query strings are either true/false
858// body params allow only: missing field, or ["All"]
859// The latter is a very awkward API causing users to do to
860// dp.dry_run = vec!["All".into()];
861// just to turn on dry_run..
862// so we hide this detail for now.
863fn dry_run_all_ser<S>(t: &bool, s: S) -> std::result::Result<S::Ok, S::Error>
864where
865 S: serde::ser::Serializer,
866{
867 use serde::ser::SerializeTuple;
868 match t {
869 true => {
870 let mut map = s.serialize_tuple(1)?;
871 map.serialize_element("All")?;
872 map.end()
873 }
874 false => s.serialize_none(),
875 }
876}
877#[cfg(test)]
878mod test {
879 use crate::{params::WatchParams, Expression, Selector};
880
881 use super::{DeleteParams, ListParams, PatchParams};
882 #[test]
883 fn delete_param_serialize() {
884 let mut dp = DeleteParams::default();
885 let emptyser = serde_json::to_string(&dp).unwrap();
886 //println!("emptyser is: {}", emptyser);
887 assert_eq!(emptyser, "{}");
888
889 dp.dry_run = true;
890 let ser = serde_json::to_string(&dp).unwrap();
891 //println!("ser is: {}", ser);
892 assert_eq!(ser, "{\"dryRun\":[\"All\"]}");
893 }
894
895 #[test]
896 fn delete_param_constructors() {
897 let dp_background = DeleteParams::background();
898 let ser = serde_json::to_value(dp_background).unwrap();
899 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Background"}));
900
901 let dp_foreground = DeleteParams::foreground();
902 let ser = serde_json::to_value(dp_foreground).unwrap();
903 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Foreground"}));
904
905 let dp_orphan = DeleteParams::orphan();
906 let ser = serde_json::to_value(dp_orphan).unwrap();
907 assert_eq!(ser, serde_json::json!({"propagationPolicy": "Orphan"}));
908 }
909
910 #[test]
911 fn patch_param_serializes_field_validation() {
912 let pp = PatchParams::default().validation_ignore();
913 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
914 pp.populate_qp(&mut qp);
915 let urlstr = qp.finish();
916 assert_eq!(String::from("some/resource?&fieldValidation=Ignore"), urlstr);
917
918 let pp = PatchParams::default().validation_warn();
919 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
920 pp.populate_qp(&mut qp);
921 let urlstr = qp.finish();
922 assert_eq!(String::from("some/resource?&fieldValidation=Warn"), urlstr);
923
924 let pp = PatchParams::default().validation_strict();
925 let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
926 pp.populate_qp(&mut qp);
927 let urlstr = qp.finish();
928 assert_eq!(String::from("some/resource?&fieldValidation=Strict"), urlstr);
929 }
930
931 #[test]
932 fn list_params_serialize() {
933 let selector: Selector =
934 Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
935 let lp = ListParams::default().labels_from(&selector);
936 let labels = lp.label_selector.unwrap();
937 assert_eq!(labels, "env in (development,sandbox)");
938 }
939
940 #[test]
941 fn watch_params_serialize() {
942 let selector: Selector =
943 Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
944 let wp = WatchParams::default().labels_from(&selector);
945 let labels = wp.label_selector.unwrap();
946 assert_eq!(labels, "env in (development,sandbox)");
947 }
948}
949
950/// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
951#[derive(Default, Clone, Serialize, Debug, PartialEq)]
952#[serde(rename_all = "camelCase")]
953pub struct Preconditions {
954 /// Specifies the target ResourceVersion
955 #[serde(skip_serializing_if = "Option::is_none")]
956 pub resource_version: Option<String>,
957 /// Specifies the target UID
958 #[serde(skip_serializing_if = "Option::is_none")]
959 pub uid: Option<String>,
960}
961
962/// Propagation policy when deleting single objects
963#[derive(Clone, Debug, Serialize, PartialEq)]
964pub enum PropagationPolicy {
965 /// Orphan dependents
966 Orphan,
967 /// Allow the garbage collector to delete the dependents in the background
968 Background,
969 /// A cascading policy that deletes all dependents in the foreground
970 Foreground,
971}