Skip to main content

kube_runtime/controller/
mod.rs

1//! Runs a user-supplied reconciler function on objects when they (or related objects) are updated
2
3use self::runner::Runner;
4#[allow(deprecated)] use crate::watcher::metadata_watcher;
5use crate::{
6    reflector::{
7        self, ObjectRef, reflector,
8        store::{Store, Writer},
9    },
10    scheduler::{ScheduleRequest, debounced_scheduler},
11    utils::{
12        Backoff, CancelableJoinHandle, KubeRuntimeStreamExt, StreamBackoff, WatchStreamExt, trystream_try_via,
13    },
14    watcher::{self, DefaultBackoff, watcher},
15};
16use educe::Educe;
17use futures::{
18    FutureExt, Stream, StreamExt, TryFuture, TryFutureExt, TryStream, TryStreamExt, channel,
19    future::{self, BoxFuture},
20    stream,
21};
22use kube_client::api::{Api, DynamicObject, Resource};
23use pin_project::pin_project;
24use serde::de::DeserializeOwned;
25use std::{
26    fmt::{Debug, Display},
27    hash::Hash,
28    sync::Arc,
29    task::{Poll, ready},
30    time::Duration,
31};
32use stream::BoxStream;
33use thiserror::Error;
34use tokio::{runtime::Handle, time::Instant};
35use tracing::{Instrument, info_span};
36
37mod future_hash_map;
38mod runner;
39mod trigger;
40
41use trigger::trigger_others;
42#[cfg(feature = "unstable-runtime-subscribe")]
43use trigger::trigger_owners_shared;
44pub use trigger::{trigger_owners, trigger_self, trigger_with};
45
46/// The reasons the internal runner can fail
47pub type RunnerError = runner::Error<reflector::store::WriterDropped>;
48
49/// Errors returned by the applier and visible in a controller stream if inspecting it
50///
51/// WARNING: These errors do not terminate `Controller::run`, and are not passed to the `reconcile` fn
52/// as they exist primarily for diagnostics.
53///
54/// To inspect these errors, you can run a `for_each` on the run stream:
55///
56/// ```compile_fail
57///    Controller::new(api, watcher_config)
58///        .run(reconcile, error_policy, context)
59///        .for_each(|res| async move {
60///            match res {
61///                Ok(o) => info!("reconciled {:?}", o),
62///                /// Reconciler errors visible here:
63///                Err(e) => warn!("reconcile failed: {}", e),
64///            }
65///        })
66///        .await;
67/// ```
68#[derive(Debug, Error)]
69pub enum Error<ReconcilerErr: 'static, QueueErr: 'static> {
70    /// A scheduled reconcile for an object refers to an object that no longer exists
71    ///
72    /// This is usually not a problem and often expected with certain relations.
73    /// See <https://github.com/kube-rs/kube/issues/1167#issuecomment-1636773541>
74    /// for a more detailed explanation of how/why this happens.
75    #[error("tried to reconcile object {0} that was not found in local store")]
76    ObjectNotFound(Box<ObjectRef<DynamicObject>>),
77
78    /// User's reconcile fn failed for the object
79    #[error("reconciler for object {1} failed: {0}")]
80    ReconcilerFailed(#[source] ReconcilerErr, Box<ObjectRef<DynamicObject>>),
81
82    /// The queue stream contained an error
83    #[error("event queue error: {0}")]
84    QueueError(#[source] QueueErr),
85
86    /// The internal runner returned an error
87    #[error("runner error: {0}")]
88    RunnerError(#[source] RunnerError),
89}
90
91/// Results of the reconciliation attempt
92#[derive(Debug, Clone, Eq, PartialEq)]
93pub struct Action {
94    /// Whether (and when) to next trigger the reconciliation if no external watch triggers hit
95    ///
96    /// For example, use this to query external systems for updates, expire time-limited resources, or
97    /// (in your `error_policy`) retry after errors.
98    requeue_after: Option<Duration>,
99}
100
101impl Action {
102    /// Action to the reconciliation at this time even if no external watch triggers hit
103    ///
104    /// This is the best-practice action that ensures eventual consistency of your controller
105    /// even in the case of missed changes (which can happen).
106    ///
107    /// Watch events are not normally missed, so running this once per hour (`Default`) as a fallback is reasonable.
108    #[must_use]
109    pub const fn requeue(duration: Duration) -> Self {
110        Self {
111            requeue_after: Some(duration),
112        }
113    }
114
115    /// Do nothing until a change is detected
116    ///
117    /// This stops the controller periodically reconciling this object until a relevant watch event
118    /// was **detected**.
119    ///
120    /// **Warning**: If you have watch desyncs, it is possible to miss changes entirely.
121    /// It is therefore not recommended to disable requeuing this way, unless you have
122    /// frequent changes to the underlying object, or some other hook to retain eventual consistency.
123    #[must_use]
124    pub const fn await_change() -> Self {
125        Self { requeue_after: None }
126    }
127}
128
129/// A request to reconcile an object, annotated with why that request was made.
130///
131/// NOTE: The reason is ignored for comparison purposes. This means that, for example,
132/// an object can only occupy one scheduler slot, even if it has been scheduled for multiple reasons.
133/// In this case, only *the first* reason is stored.
134#[derive(Educe)]
135#[educe(
136    Debug(bound("K::DynamicType: Debug")),
137    Clone(bound("K::DynamicType: Clone")),
138    PartialEq(bound("K::DynamicType: PartialEq")),
139    Hash(bound("K::DynamicType: Hash"))
140)]
141pub struct ReconcileRequest<K: Resource> {
142    /// A reference to the object to be reconciled
143    pub obj_ref: ObjectRef<K>,
144    /// The reason for why reconciliation was requested
145    #[educe(PartialEq(ignore), Hash(ignore))]
146    pub reason: ReconcileReason,
147}
148
149impl<K: Resource> Eq for ReconcileRequest<K> where K::DynamicType: Eq {}
150
151impl<K: Resource> From<ObjectRef<K>> for ReconcileRequest<K> {
152    fn from(obj_ref: ObjectRef<K>) -> Self {
153        ReconcileRequest {
154            obj_ref,
155            reason: ReconcileReason::Unknown,
156        }
157    }
158}
159
160/// The reason a reconcile was requested
161///
162/// Note that this reason is deliberately hidden from the reconciler.
163/// See <https://kube.rs/controllers/reconciler/#reasons-for-reconciliation>.
164#[derive(Debug, Clone)]
165pub enum ReconcileReason {
166    /// A custom reconcile triggered via `reconcile_on`
167    Unknown,
168
169    /// The main object was updated.
170    ObjectUpdated,
171
172    /// A related object was updated through a mapper
173    ///
174    /// The related object traversed its relation up to the object kind you are reconciling.
175    /// Your object may not have changed, but you may need to update child objects.
176    RelatedObjectUpdated {
177        /// An object ref to the related object
178        obj_ref: Box<ObjectRef<DynamicObject>>,
179    },
180
181    /// The users `reconcile` scheduled a reconciliation via an `Action`
182    ReconcilerRequestedRetry,
183
184    /// The users `error_policy` scheduled a reconciliation via an `Action`
185    ErrorPolicyRequestedRetry,
186
187    /// A bulk reconcile was requested via `reconcile_all_on`
188    BulkReconcile,
189
190    /// A custom reconcile reason for custom integrations.
191    ///
192    /// Can be used when injecting elements into the queue stream directly.
193    Custom {
194        /// A user provided reason through a custom integration
195        reason: String,
196    },
197}
198
199impl Display for ReconcileReason {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        match self {
202            ReconcileReason::Unknown => f.write_str("unknown"),
203            ReconcileReason::ObjectUpdated => f.write_str("object updated"),
204            ReconcileReason::RelatedObjectUpdated { obj_ref: object } => {
205                f.write_fmt(format_args!("related object updated: {object}"))
206            }
207            ReconcileReason::BulkReconcile => f.write_str("bulk reconcile requested"),
208            ReconcileReason::ReconcilerRequestedRetry => f.write_str("reconciler requested retry"),
209            ReconcileReason::ErrorPolicyRequestedRetry => f.write_str("error policy requested retry"),
210            ReconcileReason::Custom { reason } => f.write_str(reason),
211        }
212    }
213}
214
215const APPLIER_REQUEUE_BUF_SIZE: usize = 100;
216
217/// Apply a reconciler to an input stream, with a given retry policy
218///
219/// Takes a `store` parameter for the core objects, which should usually be updated by a [`reflector()`].
220///
221/// The `queue` indicates which objects should be reconciled. For the core objects this will usually be
222/// the [`reflector()`] (piped through [`trigger_self`]). If your core objects own any subobjects then you
223/// can also make them trigger reconciliations by [merging](`futures::stream::select`) the [`reflector()`]
224/// with a [`watcher()`] or [`reflector()`] for the subobject.
225///
226/// This is the "hard-mode" version of [`Controller`], which allows you some more customization
227/// (such as triggering from arbitrary [`Stream`]s), at the cost of being a bit more verbose.
228#[allow(clippy::needless_pass_by_value)]
229#[allow(clippy::type_complexity)]
230pub fn applier<K, QueueStream, ReconcilerFut, Ctx>(
231    mut reconciler: impl FnMut(Arc<K>, Arc<Ctx>) -> ReconcilerFut,
232    error_policy: impl Fn(Arc<K>, &ReconcilerFut::Error, Arc<Ctx>) -> Action,
233    context: Arc<Ctx>,
234    store: Store<K>,
235    queue: QueueStream,
236    config: Config,
237) -> impl Stream<Item = Result<(ObjectRef<K>, Action), Error<ReconcilerFut::Error, QueueStream::Error>>>
238where
239    K: Clone + Resource + 'static,
240    K::DynamicType: Debug + Eq + Hash + Clone + Unpin,
241    ReconcilerFut: TryFuture<Ok = Action> + Unpin,
242    ReconcilerFut::Error: std::error::Error + 'static,
243    QueueStream: TryStream,
244    QueueStream::Ok: Into<ReconcileRequest<K>>,
245    QueueStream::Error: std::error::Error + 'static,
246{
247    let (scheduler_shutdown_tx, scheduler_shutdown_rx) = channel::oneshot::channel();
248    let (scheduler_tx, scheduler_rx) =
249        channel::mpsc::channel::<ScheduleRequest<ReconcileRequest<K>>>(APPLIER_REQUEUE_BUF_SIZE);
250    let error_policy = Arc::new(error_policy);
251    let delay_store = store.clone();
252    // Create a stream of ObjectRefs that need to be reconciled
253    trystream_try_via(
254        // input: stream combining scheduled tasks and user specified inputs event
255        Box::pin(stream::select(
256            // 1. inputs from users queue stream
257            queue
258                .map_err(Error::QueueError)
259                .map_ok(|request| ScheduleRequest {
260                    message: request.into(),
261                    run_at: Instant::now(),
262                })
263                .on_complete(async move {
264                    // On error: scheduler has already been shut down and there is nothing for us to do
265                    let _ = scheduler_shutdown_tx.send(());
266                    tracing::debug!("applier queue terminated, starting graceful shutdown")
267                }),
268            // 2. requests sent to scheduler_tx
269            scheduler_rx
270                .map(Ok)
271                .take_until(scheduler_shutdown_rx)
272                .on_complete(async { tracing::debug!("applier scheduler consumer terminated") }),
273        )),
274        // all the Oks from the select gets passed through the scheduler stream, and are then executed
275        move |s| {
276            Runner::new(
277                debounced_scheduler(s, config.debounce),
278                config.concurrency,
279                move |request| {
280                    let request = request.clone();
281                    match store.get(&request.obj_ref) {
282                        Some(obj) => {
283                            let scheduler_tx = scheduler_tx.clone();
284                            let error_policy_ctx = context.clone();
285                            let error_policy = error_policy.clone();
286                            let reconciler_span = info_span!(
287                                "reconciling object",
288                                "object.ref" = %request.obj_ref,
289                                object.reason = %request.reason
290                            );
291                            TryFutureExt::into_future(
292                                reconciler_span.in_scope(|| reconciler(Arc::clone(&obj), context.clone())),
293                            )
294                            .then(move |res| {
295                                let error_policy = error_policy;
296                                RescheduleReconciliation::new(
297                                    res,
298                                    |err| error_policy(obj, err, error_policy_ctx),
299                                    request.obj_ref.clone(),
300                                    scheduler_tx,
301                                )
302                                // Reconciler errors are OK from the applier's PoV, we need to apply the error policy
303                                // to them separately
304                                .map(|res| Ok((request.obj_ref, res)))
305                            })
306                            .instrument(reconciler_span)
307                            .left_future()
308                        }
309                        None => {
310                            std::future::ready(Err(Error::ObjectNotFound(Box::new(request.obj_ref.erase()))))
311                                .right_future()
312                        }
313                    }
314                },
315            )
316            .delay_tasks_until(async move {
317                tracing::debug!("applier runner held until store is ready");
318                let res = delay_store.wait_until_ready().await;
319                tracing::debug!("store is ready, starting runner");
320                res
321            })
322            .map(|runner_res| runner_res.unwrap_or_else(|err| Err(Error::RunnerError(err))))
323            .on_complete(async { tracing::debug!("applier runner terminated") })
324        },
325    )
326    .on_complete(async { tracing::debug!("applier runner-merge terminated") })
327    // finally, for each completed reconcile call:
328    .and_then(move |(obj_ref, reconciler_result)| async move {
329        match reconciler_result {
330            Ok(action) => Ok((obj_ref, action)),
331            Err(err) => Err(Error::ReconcilerFailed(err, Box::new(obj_ref.erase()))),
332        }
333    })
334    .on_complete(async { tracing::debug!("applier terminated") })
335}
336
337/// Internal helper [`Future`] that reschedules reconciliation of objects (if required), in the scheduled context of the reconciler
338///
339/// This could be an `async fn`, but isn't because we want it to be [`Unpin`]
340#[pin_project]
341#[must_use]
342struct RescheduleReconciliation<K: Resource, ReconcilerErr> {
343    reschedule_tx: channel::mpsc::Sender<ScheduleRequest<ReconcileRequest<K>>>,
344
345    reschedule_request: Option<ScheduleRequest<ReconcileRequest<K>>>,
346    result: Option<Result<Action, ReconcilerErr>>,
347}
348
349impl<K, ReconcilerErr> RescheduleReconciliation<K, ReconcilerErr>
350where
351    K: Resource,
352{
353    fn new(
354        result: Result<Action, ReconcilerErr>,
355        error_policy: impl FnOnce(&ReconcilerErr) -> Action,
356        obj_ref: ObjectRef<K>,
357        reschedule_tx: channel::mpsc::Sender<ScheduleRequest<ReconcileRequest<K>>>,
358    ) -> Self {
359        let reconciler_finished_at = Instant::now();
360
361        let (action, reschedule_reason) = result.as_ref().map_or_else(
362            |err| (error_policy(err), ReconcileReason::ErrorPolicyRequestedRetry),
363            |action| (action.clone(), ReconcileReason::ReconcilerRequestedRetry),
364        );
365
366        Self {
367            reschedule_tx,
368            reschedule_request: action.requeue_after.map(|requeue_after| ScheduleRequest {
369                message: ReconcileRequest {
370                    obj_ref,
371                    reason: reschedule_reason,
372                },
373                run_at: reconciler_finished_at
374                    .checked_add(requeue_after)
375                    .unwrap_or_else(crate::scheduler::max_schedule_time),
376            }),
377            result: Some(result),
378        }
379    }
380}
381
382impl<K, ReconcilerErr> Future for RescheduleReconciliation<K, ReconcilerErr>
383where
384    K: Resource,
385{
386    type Output = Result<Action, ReconcilerErr>;
387
388    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
389        let this = self.get_mut();
390
391        if this.reschedule_request.is_some() {
392            let rescheduler_ready = ready!(this.reschedule_tx.poll_ready(cx));
393            let reschedule_request = this
394                .reschedule_request
395                .take()
396                .expect("PostReconciler::reschedule_request was taken during processing");
397            // Failure to schedule item = in graceful shutdown mode, ignore
398            if let Ok(()) = rescheduler_ready {
399                let _ = this.reschedule_tx.start_send(reschedule_request);
400            }
401        }
402
403        Poll::Ready(
404            this.result
405                .take()
406                .expect("PostReconciler::result was already taken"),
407        )
408    }
409}
410
411/// Accumulates all options that can be used on a [`Controller`] invocation.
412#[derive(Clone, Debug, Default)]
413pub struct Config {
414    debounce: Duration,
415    concurrency: u16,
416}
417
418impl Config {
419    /// The debounce duration used to deduplicate reconciliation requests.
420    ///
421    /// When set to a non-zero duration, debouncing is enabled in the [`scheduler`](crate::scheduler())
422    /// resulting in __trailing edge debouncing__ of reconciler requests.
423    /// This option can help to reduce the amount of unnecessary reconciler calls
424    /// when using multiple controller relations, or during rapid phase transitions.
425    ///
426    /// ## Warning
427    /// This option delays (and keeps delaying) reconcile requests for objects while
428    /// the object is updated. It can **permanently hide** updates from your reconciler
429    /// if set too high on objects that are updated frequently (like nodes).
430    #[must_use]
431    pub fn debounce(mut self, debounce: Duration) -> Self {
432        self.debounce = debounce;
433        self
434    }
435
436    /// The number of concurrent reconciliations of that are allowed to run at an given moment.
437    ///
438    /// This can be adjusted to the controller's needs to increase
439    /// performance and/or make performance predictable. By default, its 0 meaning
440    /// the controller runs with unbounded concurrency.
441    ///
442    /// Note that despite concurrency, a controller never schedules concurrent reconciles
443    /// on the same object.
444    #[must_use]
445    pub fn concurrency(mut self, concurrency: u16) -> Self {
446        self.concurrency = concurrency;
447        self
448    }
449}
450
451/// Controller for a Resource `K`
452///
453/// A controller is an infinite stream of objects to be reconciled.
454///
455/// Once `run` and continuously awaited, it continuously calls out to user provided
456/// `reconcile` and `error_policy` callbacks whenever relevant changes are detected
457/// or if errors are seen from `reconcile`.
458///
459/// Reconciles are generally requested for all changes on your root objects.
460/// Changes to managed child resources will also trigger the reconciler for the
461/// managing object by traversing owner references (for `Controller::owns`),
462/// or traverse a custom mapping (for `Controller::watches`).
463///
464/// This mapping mechanism ultimately hides the reason for the reconciliation request,
465/// and forces you to write an idempotent reconciler.
466///
467/// General setup:
468/// ```no_run
469/// use kube::{Api, Client, CustomResource};
470/// use kube::runtime::{controller::{Controller, Action}, watcher};
471/// # use serde::{Deserialize, Serialize};
472/// # use tokio::time::Duration;
473/// use futures::StreamExt;
474/// use k8s_openapi::api::core::v1::ConfigMap;
475/// use schemars::JsonSchema;
476/// # use std::sync::Arc;
477/// use thiserror::Error;
478///
479/// #[derive(Debug, Error)]
480/// enum Error {}
481///
482/// /// A custom resource
483/// #[derive(CustomResource, Debug, Clone, Deserialize, Serialize, JsonSchema)]
484/// #[kube(group = "nullable.se", version = "v1", kind = "ConfigMapGenerator", namespaced)]
485/// struct ConfigMapGeneratorSpec {
486///     content: String,
487/// }
488///
489/// /// The reconciler that will be called when either object change
490/// async fn reconcile(g: Arc<ConfigMapGenerator>, _ctx: Arc<()>) -> Result<Action, Error> {
491///     // .. use api here to reconcile a child ConfigMap with ownerreferences
492///     // see configmapgen_controller example for full info
493///     Ok(Action::requeue(Duration::from_secs(300)))
494/// }
495/// /// an error handler that will be called when the reconciler fails with access to both the
496/// /// object that caused the failure and the actual error
497/// fn error_policy(obj: Arc<ConfigMapGenerator>, _error: &Error, _ctx: Arc<()>) -> Action {
498///     Action::requeue(Duration::from_secs(60))
499/// }
500///
501/// /// something to drive the controller
502///
503/// async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
504/// #   let client: Client = todo!();
505///     let context = Arc::new(()); // bad empty context - put client in here
506///     let cmgs = Api::<ConfigMapGenerator>::all(client.clone());
507///     let cms = Api::<ConfigMap>::all(client.clone());
508///     Controller::new(cmgs, watcher::Config::default())
509///         .owns(cms, watcher::Config::default())
510///         .run(reconcile, error_policy, context)
511///         .for_each(|res| async move {
512///             match res {
513///                 Ok(o) => println!("reconciled {:?}", o),
514///                 Err(e) => println!("reconcile failed: {:?}", e),
515///             }
516///         })
517///         .await; // controller does nothing unless polled
518/// #    Ok(())
519/// # }
520/// ```
521pub struct Controller<K>
522where
523    K: Clone + Resource + Debug + 'static,
524    K::DynamicType: Eq + Hash,
525{
526    // NB: Need to Unpin for stream::select_all
527    trigger_selector: stream::SelectAll<BoxStream<'static, Result<ReconcileRequest<K>, watcher::Error>>>,
528    trigger_backoff: Box<dyn Backoff + Send>,
529    /// [`run`](crate::Controller::run) starts a graceful shutdown when any of these [`Future`]s complete,
530    /// refusing to start any new reconciliations but letting any existing ones finish.
531    graceful_shutdown_selector: Vec<BoxFuture<'static, ()>>,
532    /// [`run`](crate::Controller::run) terminates immediately when any of these [`Future`]s complete,
533    /// requesting that all running reconciliations be aborted.
534    /// However, note that they *will* keep running until their next yield point (`.await`),
535    /// blocking [`tokio::runtime::Runtime`] destruction (unless you follow up by calling [`std::process::exit`] after `run`).
536    forceful_shutdown_selector: Vec<BoxFuture<'static, ()>>,
537    dyntype: K::DynamicType,
538    reader: Store<K>,
539    config: Config,
540}
541
542impl<K> Controller<K>
543where
544    K: Clone + Resource + DeserializeOwned + Debug + Send + Sync + 'static,
545    K::DynamicType: Eq + Hash + Clone,
546{
547    /// Create a Controller for a resource `K`
548    ///
549    /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `K`.
550    ///
551    /// The [`watcher::Config`] controls to the possible subset of objects of `K` that you want to manage
552    /// and receive reconcile events for.
553    /// For the full set of objects `K` in the given `Api` scope, you can use [`watcher::Config::default`].
554    #[must_use]
555    pub fn new(main_api: Api<K>, wc: watcher::Config) -> Self
556    where
557        K::DynamicType: Default,
558    {
559        Self::new_with(main_api, wc, Default::default())
560    }
561
562    /// Create a Controller for a resource `K`
563    ///
564    /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `K`.
565    ///
566    /// The [`watcher::Config`] lets you define a possible subset of objects of `K` that you want the [`Api`]
567    /// to watch - in the Api's  configured scope - and receive reconcile events for.
568    /// For the full set of objects `K` in the given `Api` scope, you can use [`Config::default`].
569    ///
570    /// This variant constructor is for [`dynamic`] types found through discovery. Prefer [`Controller::new`] for static types.
571    ///
572    /// [`watcher::Config`]: crate::watcher::Config
573    /// [`Api`]: kube_client::Api
574    /// [`dynamic`]: kube_client::core::dynamic
575    /// [`Config::default`]: crate::watcher::Config::default
576    pub fn new_with(main_api: Api<K>, wc: watcher::Config, dyntype: K::DynamicType) -> Self {
577        let writer = Writer::<K>::new(dyntype.clone());
578        let reader = writer.as_reader();
579        let mut trigger_selector = stream::SelectAll::new();
580        let self_watcher = trigger_self(
581            reflector(writer, watcher(main_api, wc)).applied_objects(),
582            dyntype.clone(),
583        )
584        .boxed();
585        trigger_selector.push(self_watcher);
586        Self {
587            trigger_selector,
588            trigger_backoff: Box::<DefaultBackoff>::default(),
589            graceful_shutdown_selector: vec![
590                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
591                future::pending().boxed(),
592            ],
593            forceful_shutdown_selector: vec![
594                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
595                future::pending().boxed(),
596            ],
597            dyntype,
598            reader,
599            config: Default::default(),
600        }
601    }
602
603    /// Create a Controller for a resource `K` from a stream of `K` objects
604    ///
605    /// Same as [`Controller::new`], but instead of an `Api`, a stream of resources is used.
606    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
607    /// as well as sharing input streams between multiple controllers.
608    ///
609    /// # Example:
610    ///
611    /// ```no_run
612    /// # use futures::StreamExt;
613    /// # use k8s_openapi::api::apps::v1::Deployment;
614    /// # use kube::runtime::controller::{Action, Controller};
615    /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
616    /// # use kube::{Api, Client, Error, ResourceExt};
617    /// # use std::sync::Arc;
618    /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
619    /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
620    /// # async fn doc(client: kube::Client) {
621    /// let api: Api<Deployment> = Api::default_namespaced(client);
622    /// let (reader, writer) = reflector::store();
623    /// let deploys = watcher(api, watcher::Config::default())
624    ///     .default_backoff()
625    ///     .reflect(writer)
626    ///     .applied_objects()
627    ///     .predicate_filter(predicates::generation, Default::default());
628    ///
629    /// Controller::for_stream(deploys, reader)
630    ///     .run(reconcile, error_policy, Arc::new(()))
631    ///     .for_each(|_| std::future::ready(()))
632    ///     .await;
633    /// # }
634    /// ```
635    ///
636    /// Prefer [`Controller::new`] if you do not need to share the stream, or do not need pre-filtering.
637    #[cfg(feature = "unstable-runtime-stream-control")]
638    pub fn for_stream(
639        trigger: impl Stream<Item = Result<K, watcher::Error>> + Send + 'static,
640        reader: Store<K>,
641    ) -> Self
642    where
643        K::DynamicType: Default,
644    {
645        Self::for_stream_with(trigger, reader, Default::default())
646    }
647
648    /// Create a Controller for a resource `K` from a stream of `K` objects
649    ///
650    /// Same as [`Controller::new`], but instead of an `Api`, a stream of resources is used.
651    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
652    /// as well as sharing input streams between multiple controllers.
653    ///
654    /// Prefer [`Controller::new`] if you do not need to share the stream, or do not need pre-filtering.
655    ///
656    /// This variant constructor is for [`dynamic`] types found through discovery. Prefer [`Controller::for_stream`] for static types.
657    ///
658    /// [`dynamic`]: kube_client::core::dynamic
659    #[cfg(feature = "unstable-runtime-stream-control")]
660    pub fn for_stream_with(
661        trigger: impl Stream<Item = Result<K, watcher::Error>> + Send + 'static,
662        reader: Store<K>,
663        dyntype: K::DynamicType,
664    ) -> Self {
665        let mut trigger_selector = stream::SelectAll::new();
666        let self_watcher = trigger_self(trigger, dyntype.clone()).boxed();
667        trigger_selector.push(self_watcher);
668        Self {
669            trigger_selector,
670            trigger_backoff: Box::<DefaultBackoff>::default(),
671            graceful_shutdown_selector: vec![
672                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
673                future::pending().boxed(),
674            ],
675            forceful_shutdown_selector: vec![
676                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
677                future::pending().boxed(),
678            ],
679            dyntype,
680            reader,
681            config: Default::default(),
682        }
683    }
684
685    /// This is the same as [`Controller::for_stream`]. Instead of taking an
686    /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
687    /// streams can be created out-of-band by subscribing on a store `Writer`.
688    /// Through this interface, multiple controllers can use the same root
689    /// (shared) input stream of resources to keep memory overheads smaller.
690    ///
691    /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
692    /// need to share the stream.
693    ///
694    /// ## Warning:
695    ///
696    /// You **must** ensure the root stream (i.e. stream created through a `reflector()`)
697    /// is driven to readiness independently of this controller to ensure the
698    /// watcher never deadlocks.
699    ///
700    /// # Example:
701    ///
702    /// ```no_run
703    /// # use futures::StreamExt;
704    /// # use k8s_openapi::api::apps::v1::Deployment;
705    /// # use kube::runtime::controller::{Action, Controller};
706    /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
707    /// # use kube::{Api, Client, Error, ResourceExt};
708    /// # use std::sync::Arc;
709    /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
710    /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
711    /// # async fn doc(client: kube::Client) {
712    /// let api: Api<Deployment> = Api::default_namespaced(client);
713    /// let (reader, writer) = reflector::store_shared(128);
714    /// let subscriber = writer
715    ///     .subscribe()
716    ///     .expect("subscribers can only be created from shared stores");
717    /// let deploys = watcher(api, watcher::Config::default())
718    ///     .default_backoff()
719    ///     .reflect(writer)
720    ///     .applied_objects()
721    ///     .for_each(|ev| async move {
722    ///         match ev {
723    ///             Ok(obj) => tracing::info!("got obj {obj:?}"),
724    ///             Err(error) => tracing::error!(%error, "received error")
725    ///         }
726    ///     });
727    ///
728    /// let controller = Controller::for_shared_stream(subscriber, reader)
729    ///     .run(reconcile, error_policy, Arc::new(()))
730    ///     .for_each(|ev| async move {
731    ///         tracing::info!("reconciled {ev:?}")
732    ///     });
733    ///
734    /// // Drive streams using a select statement
735    /// tokio::select! {
736    ///   _ = deploys => {},
737    ///   _ = controller => {},
738    /// }
739    /// # }
740    #[cfg(feature = "unstable-runtime-subscribe")]
741    pub fn for_shared_stream(trigger: impl Stream<Item = Arc<K>> + Send + 'static, reader: Store<K>) -> Self
742    where
743        K::DynamicType: Default,
744    {
745        Self::for_shared_stream_with(trigger, reader, Default::default())
746    }
747
748    /// This is the same as [`Controller::for_stream`]. Instead of taking an
749    /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
750    /// streams can be created out-of-band by subscribing on a store `Writer`.
751    /// Through this interface, multiple controllers can use the same root
752    /// (shared) input stream of resources to keep memory overheads smaller.
753    ///
754    /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
755    /// need to share the stream.
756    ///
757    /// This variant constructor is used for [`dynamic`] types found through
758    /// discovery. Prefer [`Controller::for_shared_stream`] for static types (i.e.
759    /// known at compile time).
760    ///
761    /// [`dynamic`]: kube_client::core::dynamic
762    #[cfg(feature = "unstable-runtime-subscribe")]
763    pub fn for_shared_stream_with(
764        trigger: impl Stream<Item = Arc<K>> + Send + 'static,
765        reader: Store<K>,
766        dyntype: K::DynamicType,
767    ) -> Self {
768        let mut trigger_selector = stream::SelectAll::new();
769        let self_watcher = trigger_self(trigger.map(Ok), dyntype.clone()).boxed();
770        trigger_selector.push(self_watcher);
771        Self {
772            trigger_selector,
773            trigger_backoff: Box::<DefaultBackoff>::default(),
774            graceful_shutdown_selector: vec![
775                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
776                future::pending().boxed(),
777            ],
778            forceful_shutdown_selector: vec![
779                // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
780                future::pending().boxed(),
781            ],
782            dyntype,
783            reader,
784            config: Default::default(),
785        }
786    }
787
788    /// Specify the configuration for the controller's behavior.
789    #[must_use]
790    pub fn with_config(mut self, config: Config) -> Self {
791        self.config = config;
792        self
793    }
794
795    /// Specify the backoff policy for "trigger" watches
796    ///
797    /// This includes the core watch, as well as auxiliary watches introduced by [`Self::owns`] and [`Self::watches`].
798    ///
799    /// The [`default_backoff`](crate::watcher::default_backoff) follows client-go conventions,
800    /// but can be overridden by calling this method.
801    #[must_use]
802    pub fn trigger_backoff(mut self, backoff: impl Backoff + 'static) -> Self {
803        self.trigger_backoff = Box::new(backoff);
804        self
805    }
806
807    /// Retrieve a copy of the reader before starting the controller
808    pub fn store(&self) -> Store<K> {
809        self.reader.clone()
810    }
811
812    /// Specify `Child` objects which `K` owns and should be watched
813    ///
814    /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `Child`.
815    /// All owned `Child` objects **must** contain an [`OwnerReference`] pointing back to a `K`.
816    ///
817    /// The [`watcher::Config`] controls the subset of `Child` objects that you want the [`Api`]
818    /// to watch - in the Api's configured scope - and receive reconcile events for.
819    /// To watch the full set of `Child` objects in the given `Api` scope, you can use [`watcher::Config::default`].
820    ///
821    /// [`OwnerReference`]: k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference
822    #[must_use]
823    pub fn owns<Child: Clone + Resource<DynamicType = ()> + DeserializeOwned + Debug + Send + 'static>(
824        self,
825        api: Api<Child>,
826        wc: watcher::Config,
827    ) -> Self {
828        self.owns_with(api, (), wc)
829    }
830
831    /// Specify `Child` objects which `K` owns and should be watched
832    ///
833    /// Same as [`Controller::owns`], but accepts a `DynamicType` so it can be used with dynamic resources.
834    #[must_use]
835    pub fn owns_with<Child: Clone + Resource + DeserializeOwned + Debug + Send + 'static>(
836        mut self,
837        api: Api<Child>,
838        dyntype: Child::DynamicType,
839        wc: watcher::Config,
840    ) -> Self
841    where
842        Child::DynamicType: Debug + Eq + Hash + Clone,
843    {
844        // TODO: call owns_stream_with when it's stable
845        #[allow(deprecated)]
846        let child_watcher = trigger_owners(
847            metadata_watcher(api, wc).touched_objects(),
848            self.dyntype.clone(),
849            dyntype,
850        );
851        self.trigger_selector.push(child_watcher.boxed());
852        self
853    }
854
855    /// Trigger the reconciliation process for a stream of `Child` objects of the owner `K`
856    ///
857    /// Same as [`Controller::owns`], but instead of an `Api`, a stream of resources is used.
858    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
859    /// as well as sharing input streams between multiple controllers.
860    ///
861    /// Watcher streams passed in here should be filtered first through `touched_objects`.
862    ///
863    /// # Example:
864    ///
865    /// ```no_run
866    /// # use futures::StreamExt;
867    /// # use k8s_openapi::api::core::v1::ConfigMap;
868    /// # use k8s_openapi::api::apps::v1::StatefulSet;
869    /// # use kube::runtime::controller::Action;
870    /// # use kube::runtime::{predicates, metadata_watcher, watcher, Controller, WatchStreamExt};
871    /// # use kube::{Api, Client, Error, ResourceExt};
872    /// # use std::sync::Arc;
873    /// # type CustomResource = ConfigMap;
874    /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
875    /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
876    /// # async fn doc(client: kube::Client) {
877    /// let sts_stream = metadata_watcher(Api::<StatefulSet>::all(client.clone()), watcher::Config::default())
878    ///     .touched_objects()
879    ///     // Retain observed deletions so the owner can recreate missing children.
880    ///     .predicate_filter(predicates::resource_version, Default::default());
881    ///
882    /// Controller::new(Api::<CustomResource>::all(client), watcher::Config::default())
883    ///     .owns_stream(sts_stream)
884    ///     .run(reconcile, error_policy, Arc::new(()))
885    ///     .for_each(|_| std::future::ready(()))
886    ///     .await;
887    /// # }
888    /// ```
889    ///
890    /// A generation predicate can suppress deletion triggers because deletion need not
891    /// change generation. Resource-version filtering also passes status-only updates.
892    /// See [predicate deletion handling](crate::predicates) for details.
893    #[cfg(feature = "unstable-runtime-stream-control")]
894    #[must_use]
895    pub fn owns_stream<Child: Resource<DynamicType = ()> + Send + 'static>(
896        self,
897        trigger: impl Stream<Item = Result<Child, watcher::Error>> + Send + 'static,
898    ) -> Self {
899        self.owns_stream_with(trigger, ())
900    }
901
902    /// Trigger the reconciliation process for a stream of `Child` objects of the owner `K`
903    ///
904    /// Same as [`Controller::owns`], but instead of an `Api`, a stream of resources is used.
905    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
906    /// as well as sharing input streams between multiple controllers.
907    ///
908    /// Same as [`Controller::owns_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
909    #[cfg(feature = "unstable-runtime-stream-control")]
910    #[must_use]
911    pub fn owns_stream_with<Child: Resource + Send + 'static>(
912        mut self,
913        trigger: impl Stream<Item = Result<Child, watcher::Error>> + Send + 'static,
914        dyntype: Child::DynamicType,
915    ) -> Self
916    where
917        Child::DynamicType: Debug + Eq + Hash + Clone,
918    {
919        let child_watcher = trigger_owners(trigger, self.dyntype.clone(), dyntype);
920        self.trigger_selector.push(child_watcher.boxed());
921        self
922    }
923
924    /// This is the same as [`Controller::for_stream`]. Instead of taking an
925    /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
926    /// streams can be created out-of-band by subscribing on a store `Writer`.
927    /// Through this interface, multiple controllers can use the same root
928    /// (shared) input stream of resources to keep memory overheads smaller.
929    ///
930    /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
931    /// need to share the stream.
932    ///
933    /// ## Warning:
934    ///
935    /// You **must** ensure the root stream (i.e. stream created through a `reflector()`)
936    /// is driven to readiness independently of this controller to ensure the
937    /// watcher never deadlocks.
938    ///
939    ///
940    /// Trigger the reconciliation process for a shared stream of `Child`
941    /// objects of the owner `K`
942    ///
943    /// Conceptually the same as [`Controller::owns`], but a stream is used
944    /// instead of an `Api`. This interface behaves similarly to its non-shared
945    /// counterpart [`Controller::owns_stream`].
946    ///
947    /// # Example:
948    ///
949    /// ```no_run
950    /// # use futures::StreamExt;
951    /// # use k8s_openapi::api::{apps::v1::Deployment, core::v1::Pod};
952    /// # use kube::runtime::controller::{Action, Controller};
953    /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
954    /// # use kube::{Api, Client, Error, ResourceExt};
955    /// # use std::sync::Arc;
956    /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
957    /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
958    /// # async fn doc(client: kube::Client) {
959    /// let deploys: Api<Deployment> = Api::default_namespaced(client.clone());
960    /// let pod_api: Api<Pod> = Api::default_namespaced(client);
961    ///
962    /// let (reader, writer) = reflector::store_shared(128);
963    /// let subscriber = writer
964    ///     .subscribe()
965    ///     .expect("subscribers can only be created from shared stores");
966    /// let pods = watcher(pod_api, watcher::Config::default())
967    ///     .default_backoff()
968    ///     .reflect(writer)
969    ///     .applied_objects()
970    ///     .for_each(|ev| async move {
971    ///         match ev {
972    ///             Ok(obj) => tracing::info!("got obj {obj:?}"),
973    ///             Err(error) => tracing::error!(%error, "received error")
974    ///         }
975    ///     });
976    ///
977    /// let controller = Controller::new(deploys, Default::default())
978    ///     .owns_shared_stream(subscriber)
979    ///     .run(reconcile, error_policy, Arc::new(()))
980    ///     .for_each(|ev| async move {
981    ///         tracing::info!("reconciled {ev:?}")
982    ///     });
983    ///
984    /// // Drive streams using a select statement
985    /// tokio::select! {
986    ///   _ = pods => {},
987    ///   _ = controller => {},
988    /// }
989    /// # }
990    #[cfg(feature = "unstable-runtime-subscribe")]
991    #[must_use]
992    pub fn owns_shared_stream<Child: Resource<DynamicType = ()> + Send + 'static>(
993        self,
994        trigger: impl Stream<Item = Arc<Child>> + Send + 'static,
995    ) -> Self {
996        self.owns_shared_stream_with(trigger, ())
997    }
998
999    /// Trigger the reconciliation process for a shared stream of `Child` objects of the owner `K`
1000    ///
1001    /// Same as [`Controller::owns`], but instead of an `Api`, a shared stream of resources is used.
1002    /// The source stream can be shared between multiple controllers, optimising
1003    /// resource usage.
1004    ///
1005    /// Same as [`Controller::owns_shared_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1006    #[cfg(feature = "unstable-runtime-subscribe")]
1007    #[must_use]
1008    pub fn owns_shared_stream_with<Child: Resource<DynamicType = ()> + Send + 'static>(
1009        mut self,
1010        trigger: impl Stream<Item = Arc<Child>> + Send + 'static,
1011        dyntype: Child::DynamicType,
1012    ) -> Self
1013    where
1014        Child::DynamicType: Debug + Eq + Hash + Clone,
1015    {
1016        let child_watcher = trigger_owners_shared(trigger.map(Ok), self.dyntype.clone(), dyntype);
1017        self.trigger_selector.push(child_watcher.boxed());
1018        self
1019    }
1020
1021    /// Specify `Watched` object which `K` has a custom relation to and should be watched
1022    ///
1023    /// To define the `Watched` relation with `K`, you **must** define a custom relation mapper, which,
1024    /// when given a `Watched` object, returns an option or iterator of relevant `ObjectRef<K>` to reconcile.
1025    ///
1026    /// If the relation `K` has to `Watched` is that `K` owns `Watched`, consider using [`Controller::owns`].
1027    ///
1028    /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `Watched`.
1029    ///
1030    /// The [`watcher::Config`] controls the subset of `Watched` objects that you want the [`Api`]
1031    /// to watch - in the Api's configured scope - and run through the custom mapper.
1032    /// To watch the full set of `Watched` objects in given the `Api` scope, you can use [`watcher::Config::default`].
1033    ///
1034    /// # Example
1035    ///
1036    /// Tracking cross cluster references using the [Operator-SDK] annotations.
1037    ///
1038    /// ```
1039    /// # use kube::runtime::{Controller, controller::Action, reflector::ObjectRef, watcher};
1040    /// # use kube::{Api, ResourceExt};
1041    /// # use k8s_openapi::api::core::v1::{ConfigMap, Namespace};
1042    /// # use futures::StreamExt;
1043    /// # use std::sync::Arc;
1044    /// # type WatchedResource = Namespace;
1045    /// # struct Context;
1046    /// # async fn reconcile(_: Arc<ConfigMap>, _: Arc<Context>) -> Result<Action, kube::Error> {
1047    /// #     Ok(Action::await_change())
1048    /// # };
1049    /// # fn error_policy(_: Arc<ConfigMap>, _: &kube::Error, _: Arc<Context>) -> Action {
1050    /// #     Action::await_change()
1051    /// # }
1052    /// # async fn doc(client: kube::Client) -> Result<(), Box<dyn std::error::Error>> {
1053    /// # let memcached = Api::<ConfigMap>::all(client.clone());
1054    /// # let context = Arc::new(Context);
1055    /// Controller::new(memcached, watcher::Config::default())
1056    ///     .watches(
1057    ///         Api::<WatchedResource>::all(client.clone()),
1058    ///         watcher::Config::default(),
1059    ///         |ar| {
1060    ///             let prt = ar
1061    ///                 .annotations()
1062    ///                 .get("operator-sdk/primary-resource-type")
1063    ///                 .map(String::as_str);
1064    ///
1065    ///             if prt != Some("Memcached.cache.example.com") {
1066    ///                 return None;
1067    ///             }
1068    ///
1069    ///             let (namespace, name) = ar
1070    ///                 .annotations()
1071    ///                 .get("operator-sdk/primary-resource")?
1072    ///                 .split_once('/')?;
1073    ///
1074    ///             Some(ObjectRef::new(name).within(namespace))
1075    ///         }
1076    ///     )
1077    ///     .run(reconcile, error_policy, context)
1078    ///     .for_each(|_| futures::future::ready(()))
1079    ///     .await;
1080    /// # Ok(())
1081    /// # }
1082    /// ```
1083    ///
1084    /// [Operator-SDK]: https://sdk.operatorframework.io/docs/building-operators/ansible/reference/retroactively-owned-resources/
1085    #[must_use]
1086    pub fn watches<Other, I>(
1087        self,
1088        api: Api<Other>,
1089        wc: watcher::Config,
1090        mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1091    ) -> Self
1092    where
1093        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1094        Other::DynamicType: Default + Debug + Clone + Eq + Hash,
1095        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1096        I::IntoIter: Send,
1097    {
1098        self.watches_with(api, Default::default(), wc, mapper)
1099    }
1100
1101    /// Specify `Watched` object which `K` has a custom relation to and should be watched
1102    ///
1103    /// Same as [`Controller::watches`], but accepts a `DynamicType` so it can be used with dynamic resources.
1104    #[must_use]
1105    pub fn watches_with<Other, I>(
1106        mut self,
1107        api: Api<Other>,
1108        dyntype: Other::DynamicType,
1109        wc: watcher::Config,
1110        mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1111    ) -> Self
1112    where
1113        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1114        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1115        I::IntoIter: Send,
1116        Other::DynamicType: Debug + Clone + Eq + Hash,
1117    {
1118        let other_watcher = trigger_others(watcher(api, wc).touched_objects(), mapper, dyntype);
1119        self.trigger_selector.push(other_watcher.boxed());
1120        self
1121    }
1122
1123    /// Trigger the reconciliation process for a stream of `Other` objects related to a `K`
1124    ///
1125    /// Same as [`Controller::watches`], but instead of an `Api`, a stream of resources is used.
1126    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1127    /// as well as sharing input streams between multiple controllers.
1128    ///
1129    /// Watcher streams passed in here should be filtered first through `touched_objects`.
1130    ///
1131    /// # Example:
1132    ///
1133    /// ```no_run
1134    /// # use futures::StreamExt;
1135    /// # use k8s_openapi::api::core::v1::ConfigMap;
1136    /// # use k8s_openapi::api::apps::v1::DaemonSet;
1137    /// # use kube::runtime::controller::Action;
1138    /// # use kube::runtime::{predicates, reflector::ObjectRef, watcher, Controller, WatchStreamExt};
1139    /// # use kube::{Api, Client, Error, ResourceExt};
1140    /// # use std::sync::Arc;
1141    /// # type CustomResource = ConfigMap;
1142    /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1143    /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1144    /// fn mapper(_: DaemonSet) -> Option<ObjectRef<CustomResource>> { todo!() }
1145    /// # async fn doc(client: kube::Client) {
1146    /// let api: Api<DaemonSet> = Api::all(client.clone());
1147    /// let cr: Api<CustomResource> = Api::all(client.clone());
1148    /// let daemons = watcher(api, watcher::Config::default())
1149    ///     .touched_objects()
1150    ///     // Retain observed deletions as triggers for the mapped objects.
1151    ///     .predicate_filter(predicates::resource_version, Default::default());
1152    ///
1153    /// Controller::new(cr, watcher::Config::default())
1154    ///     .watches_stream(daemons, mapper)
1155    ///     .run(reconcile, error_policy, Arc::new(()))
1156    ///     .for_each(|_| std::future::ready(()))
1157    ///     .await;
1158    /// # }
1159    /// ```
1160    ///
1161    /// A generation predicate can suppress deletion triggers because deletion need not
1162    /// change generation. Resource-version filtering also passes status-only updates.
1163    /// See [predicate deletion handling](crate::predicates) for details.
1164    #[cfg(feature = "unstable-runtime-stream-control")]
1165    #[must_use]
1166    pub fn watches_stream<Other, I>(
1167        self,
1168        trigger: impl Stream<Item = Result<Other, watcher::Error>> + Send + 'static,
1169        mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1170    ) -> Self
1171    where
1172        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1173        Other::DynamicType: Default + Debug + Clone,
1174        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1175        I::IntoIter: Send,
1176    {
1177        self.watches_stream_with(trigger, mapper, Default::default())
1178    }
1179
1180    /// Trigger the reconciliation process for a stream of `Other` objects related to a `K`
1181    ///
1182    /// Same as [`Controller::watches`], but instead of an `Api`, a stream of resources is used.
1183    /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1184    /// as well as sharing input streams between multiple controllers.
1185    ///
1186    /// Same as [`Controller::watches_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1187    #[cfg(feature = "unstable-runtime-stream-control")]
1188    #[must_use]
1189    pub fn watches_stream_with<Other, I>(
1190        mut self,
1191        trigger: impl Stream<Item = Result<Other, watcher::Error>> + Send + 'static,
1192        mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1193        dyntype: Other::DynamicType,
1194    ) -> Self
1195    where
1196        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1197        Other::DynamicType: Debug + Clone,
1198        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1199        I::IntoIter: Send,
1200    {
1201        let other_watcher = trigger_others(trigger, mapper, dyntype);
1202        self.trigger_selector.push(other_watcher.boxed());
1203        self
1204    }
1205
1206    /// Trigger the reconciliation process for a shared stream of `Other`
1207    /// objects related to a `K`
1208    ///
1209    /// Same as [`Controller::watches`], but instead of an `Api`, a shared
1210    /// stream of resources is used. This allows for sharing input streams
1211    /// between multiple controllers.
1212    ///
1213    /// Watcher streams passed in here should be filtered first through `touched_objects`.
1214    ///
1215    /// # Example:
1216    ///
1217    /// ```no_run
1218    /// # use futures::StreamExt;
1219    /// # use k8s_openapi::api::core::v1::ConfigMap;
1220    /// # use k8s_openapi::api::apps::v1::DaemonSet;
1221    /// # use kube::runtime::controller::Action;
1222    /// # use kube::runtime::{predicates, reflector::ObjectRef, watcher, Controller, WatchStreamExt};
1223    /// # use kube::{Api, Client, Error, ResourceExt};
1224    /// # use std::sync::Arc;
1225    /// # type CustomResource = ConfigMap;
1226    /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1227    /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1228    /// fn mapper(_: Arc<DaemonSet>) -> Option<ObjectRef<CustomResource>> { todo!() }
1229    /// # async fn doc(client: kube::Client) {
1230    /// let api: Api<DaemonSet> = Api::all(client.clone());
1231    /// let cr: Api<CustomResource> = Api::all(client.clone());
1232    /// let (reader, writer) = kube_runtime::reflector::store_shared(128);
1233    /// let subscriber = writer
1234    ///     .subscribe()
1235    ///     .expect("subscribers can only be created from shared stores");
1236    /// let daemons = watcher(api, watcher::Config::default())
1237    ///     .reflect(writer)
1238    ///     .touched_objects()
1239    ///     .for_each(|ev| async move {
1240    ///         match ev {
1241    ///             Ok(obj) => {},
1242    ///             Err(error) => tracing::error!(%error, "received err")
1243    ///         }
1244    ///     });
1245    ///
1246    /// let controller = Controller::new(cr, watcher::Config::default())
1247    ///     .watches_shared_stream(subscriber, mapper)
1248    ///     .run(reconcile, error_policy, Arc::new(()))
1249    ///     .for_each(|_| std::future::ready(()));
1250    ///
1251    /// // Drive streams using a select statement
1252    /// tokio::select! {
1253    ///   _ = daemons => {},
1254    ///   _ = controller => {},
1255    /// }
1256    /// # }
1257    /// ```
1258    #[cfg(feature = "unstable-runtime-subscribe")]
1259    #[must_use]
1260    pub fn watches_shared_stream<Other, I>(
1261        self,
1262        trigger: impl Stream<Item = Arc<Other>> + Send + 'static,
1263        mapper: impl Fn(Arc<Other>) -> I + Sync + Send + 'static,
1264    ) -> Self
1265    where
1266        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1267        Other::DynamicType: Default + Debug + Clone,
1268        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1269        I::IntoIter: Send,
1270    {
1271        self.watches_shared_stream_with(trigger, mapper, Default::default())
1272    }
1273
1274    /// Trigger the reconciliation process for a shared stream of `Other` objects related to a `K`
1275    ///
1276    /// Same as [`Controller::watches`], but instead of an `Api`, a shared
1277    /// stream of resources is used. This allows for sharing of streams between
1278    /// multiple controllers.
1279    ///
1280    /// Same as [`Controller::watches_shared_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1281    #[cfg(feature = "unstable-runtime-subscribe")]
1282    #[must_use]
1283    pub fn watches_shared_stream_with<Other, I>(
1284        mut self,
1285        trigger: impl Stream<Item = Arc<Other>> + Send + 'static,
1286        mapper: impl Fn(Arc<Other>) -> I + Sync + Send + 'static,
1287        dyntype: Other::DynamicType,
1288    ) -> Self
1289    where
1290        Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1291        Other::DynamicType: Debug + Clone,
1292        I: 'static + IntoIterator<Item = ObjectRef<K>>,
1293        I::IntoIter: Send,
1294    {
1295        let other_watcher = trigger_others::<_, Other, _, _>(trigger.map(Ok), mapper, dyntype);
1296        self.trigger_selector.push(other_watcher.boxed());
1297        self
1298    }
1299
1300    /// Trigger a reconciliation for all managed objects whenever `trigger` emits a value
1301    ///
1302    /// For example, this can be used to reconcile all objects whenever the controller's configuration changes.
1303    ///
1304    /// To reconcile all objects when a new line is entered:
1305    ///
1306    /// ```
1307    /// # async {
1308    /// use futures::stream::StreamExt;
1309    /// use k8s_openapi::api::core::v1::ConfigMap;
1310    /// use kube::{
1311    ///     Client,
1312    ///     api::{Api, ResourceExt},
1313    ///     runtime::{
1314    ///         controller::{Controller, Action},
1315    ///         watcher,
1316    ///     },
1317    /// };
1318    /// use std::{convert::Infallible, io::BufRead, sync::Arc};
1319    /// let (mut reload_tx, reload_rx) = futures::channel::mpsc::channel(0);
1320    /// // Using a regular background thread since tokio::io::stdin() doesn't allow aborting reads,
1321    /// // and its worker prevents the Tokio runtime from shutting down.
1322    /// std::thread::spawn(move || {
1323    ///     for _ in std::io::BufReader::new(std::io::stdin()).lines() {
1324    ///         let _ = reload_tx.try_send(());
1325    ///     }
1326    /// });
1327    /// Controller::new(
1328    ///     Api::<ConfigMap>::all(Client::try_default().await.unwrap()),
1329    ///     watcher::Config::default(),
1330    /// )
1331    /// .reconcile_all_on(reload_rx.map(|_| ()))
1332    /// .run(
1333    ///     |o, _| async move {
1334    ///         println!("Reconciling {}", o.name_any());
1335    ///         Ok(Action::await_change())
1336    ///     },
1337    ///     |_object: Arc<ConfigMap>, err: &Infallible, _| Err(err).unwrap(),
1338    ///     Arc::new(()),
1339    /// );
1340    /// # };
1341    /// ```
1342    ///
1343    /// This can be called multiple times, in which case they are additive; reconciles are scheduled whenever *any* [`Stream`] emits a new item.
1344    ///
1345    /// If a [`Stream`] is terminated (by emitting [`None`]) then the [`Controller`] keeps running, but the [`Stream`] stops being polled.
1346    #[must_use]
1347    pub fn reconcile_all_on(mut self, trigger: impl Stream<Item = ()> + Send + 'static) -> Self {
1348        let store = self.store();
1349        let dyntype = self.dyntype.clone();
1350        self.trigger_selector.push(
1351            trigger
1352                .flat_map(move |()| {
1353                    let dyntype = dyntype.clone();
1354                    stream::iter(store.state().into_iter().map(move |obj| {
1355                        Ok(ReconcileRequest {
1356                            obj_ref: ObjectRef::from_obj_with(&*obj, dyntype.clone()),
1357                            reason: ReconcileReason::BulkReconcile,
1358                        })
1359                    }))
1360                })
1361                .boxed(),
1362        );
1363        self
1364    }
1365
1366    /// Trigger the reconciliation process for a managed object `ObjectRef<K>` whenever `trigger` emits a value
1367    ///
1368    /// This can be used to inject reconciliations for specific objects from an external resource.
1369    ///
1370    /// # Example:
1371    ///
1372    /// ```no_run
1373    /// # async {
1374    /// # use futures::{StreamExt, Stream, stream, TryStreamExt};
1375    /// # use k8s_openapi::api::core::v1::{ConfigMap};
1376    /// # use kube::api::Api;
1377    /// # use kube::runtime::controller::Action;
1378    /// # use kube::runtime::reflector::{ObjectRef, Store};
1379    /// # use kube::runtime::{reflector, watcher, Controller, WatchStreamExt};
1380    /// # use kube::runtime::watcher::Config;
1381    /// # use kube::{Client, Error, ResourceExt};
1382    /// # use std::future;
1383    /// # use std::sync::Arc;
1384    /// #
1385    /// # let client: Client = todo!();
1386    /// # async fn reconcile(_: Arc<ConfigMap>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1387    /// # fn error_policy(_: Arc<ConfigMap>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1388    /// # fn watch_external_objects() -> impl Stream<Item = ExternalObject> { stream::iter(vec![]) }
1389    /// # let ns = "controller-ns".to_string();
1390    /// struct ExternalObject {
1391    ///     name: String,
1392    /// }
1393    /// let external_stream = watch_external_objects().map(|ext| {
1394    ///     ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns)
1395    /// });
1396    ///
1397    /// Controller::new(Api::<ConfigMap>::namespaced(client, &ns), Config::default())
1398    ///     .reconcile_on(external_stream)
1399    ///     .run(reconcile, error_policy, Arc::new(()))
1400    ///     .for_each(|_| future::ready(()))
1401    ///     .await;
1402    /// # };
1403    /// ```
1404    #[cfg(feature = "unstable-runtime-reconcile-on")]
1405    #[must_use]
1406    pub fn reconcile_on(mut self, trigger: impl Stream<Item = ObjectRef<K>> + Send + 'static) -> Self {
1407        self.trigger_selector.push(
1408            trigger
1409                .map(move |obj| {
1410                    Ok(ReconcileRequest {
1411                        obj_ref: obj,
1412                        reason: ReconcileReason::Unknown,
1413                    })
1414                })
1415                .boxed(),
1416        );
1417        self
1418    }
1419
1420    /// Start a graceful shutdown when `trigger` resolves. Once a graceful shutdown has been initiated:
1421    ///
1422    /// - No new reconciliations are started from the scheduler
1423    /// - The underlying Kubernetes watch is terminated
1424    /// - All running reconciliations are allowed to finish
1425    /// - [`Controller::run`]'s [`Stream`] terminates once all running reconciliations are done.
1426    ///
1427    /// For example, to stop the reconciler whenever the user presses Ctrl+C:
1428    ///
1429    /// ```rust
1430    /// # async {
1431    /// use futures::future::FutureExt;
1432    /// use k8s_openapi::api::core::v1::ConfigMap;
1433    /// use kube::{Api, Client, ResourceExt};
1434    /// use kube_runtime::{
1435    ///     controller::{Controller, Action},
1436    ///     watcher,
1437    /// };
1438    /// use std::{convert::Infallible, sync::Arc};
1439    /// Controller::new(
1440    ///     Api::<ConfigMap>::all(Client::try_default().await.unwrap()),
1441    ///     watcher::Config::default(),
1442    /// )
1443    /// .graceful_shutdown_on(tokio::signal::ctrl_c().map(|_| ()))
1444    /// .run(
1445    ///     |o, _| async move {
1446    ///         println!("Reconciling {}", o.name_any());
1447    ///         Ok(Action::await_change())
1448    ///     },
1449    ///     |_, err: &Infallible, _| Err(err).unwrap(),
1450    ///     Arc::new(()),
1451    /// );
1452    /// # };
1453    /// ```
1454    ///
1455    /// This can be called multiple times, in which case they are additive; the [`Controller`] starts to terminate
1456    /// as soon as *any* [`Future`] resolves.
1457    #[must_use]
1458    pub fn graceful_shutdown_on(mut self, trigger: impl Future<Output = ()> + Send + Sync + 'static) -> Self {
1459        self.graceful_shutdown_selector.push(trigger.boxed());
1460        self
1461    }
1462
1463    /// Initiate graceful shutdown on Ctrl+C or SIGTERM (on Unix), waiting for all reconcilers to finish.
1464    ///
1465    /// Once a graceful shutdown has been initiated, Ctrl+C (or SIGTERM) can be sent again
1466    /// to request a forceful shutdown (requesting that all reconcilers abort on the next yield point).
1467    ///
1468    /// NOTE: On Unix this leaves the default handlers for SIGINT and SIGTERM disabled after the [`Controller`] has
1469    /// terminated. If you run this in a process containing more tasks than just the [`Controller`], ensure that
1470    /// all other tasks either terminate when the [`Controller`] does, that they have their own signal handlers,
1471    /// or use [`Controller::graceful_shutdown_on`] to manage your own shutdown strategy.
1472    ///
1473    /// NOTE: If developing a Windows service then you need to listen to its lifecycle events instead, and hook that into
1474    /// [`Controller::graceful_shutdown_on`].
1475    ///
1476    /// NOTE: [`Controller::run`] terminates as soon as a forceful shutdown is requested, but leaves the reconcilers running
1477    /// in the background while they terminate. This will block [`tokio::runtime::Runtime`] termination until they actually terminate,
1478    /// unless you run [`std::process::exit`] afterwards.
1479    #[must_use]
1480    pub fn shutdown_on_signal(mut self) -> Self {
1481        async fn shutdown_signal() {
1482            futures::future::select(
1483                tokio::signal::ctrl_c().map(|_| ()).boxed(),
1484                #[cfg(unix)]
1485                tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
1486                    .unwrap()
1487                    .recv()
1488                    .map(|_| ())
1489                    .boxed(),
1490                // Assume that ctrl_c is enough on non-Unix platforms (such as Windows)
1491                #[cfg(not(unix))]
1492                futures::future::pending::<()>(),
1493            )
1494            .await;
1495        }
1496
1497        let (graceful_tx, graceful_rx) = channel::oneshot::channel();
1498        self.graceful_shutdown_selector
1499            .push(graceful_rx.map(|_| ()).boxed());
1500        self.forceful_shutdown_selector.push(
1501            async {
1502                tracing::info!("press ctrl+c to shut down gracefully");
1503                shutdown_signal().await;
1504                if let Ok(()) = graceful_tx.send(()) {
1505                    tracing::info!("graceful shutdown requested, press ctrl+c again to force shutdown");
1506                } else {
1507                    tracing::info!(
1508                        "graceful shutdown already requested, press ctrl+c again to force shutdown"
1509                    );
1510                }
1511                shutdown_signal().await;
1512                tracing::info!("forced shutdown requested");
1513            }
1514            .boxed(),
1515        );
1516        self
1517    }
1518
1519    /// Consume all the parameters of the Controller and start the applier stream
1520    ///
1521    /// This creates a stream from all builder calls and starts an applier with
1522    /// a specified `reconciler` and `error_policy` callbacks. Each of these will be called
1523    /// with a configurable `context`.
1524    pub fn run<ReconcilerFut, Ctx>(
1525        self,
1526        mut reconciler: impl FnMut(Arc<K>, Arc<Ctx>) -> ReconcilerFut,
1527        error_policy: impl Fn(Arc<K>, &ReconcilerFut::Error, Arc<Ctx>) -> Action,
1528        context: Arc<Ctx>,
1529    ) -> impl Stream<Item = Result<(ObjectRef<K>, Action), Error<ReconcilerFut::Error, watcher::Error>>>
1530    where
1531        K::DynamicType: Debug + Unpin,
1532        ReconcilerFut: TryFuture<Ok = Action> + Send + 'static,
1533        ReconcilerFut::Error: std::error::Error + Send + 'static,
1534    {
1535        applier(
1536            move |obj, ctx| {
1537                CancelableJoinHandle::spawn(
1538                    TryFutureExt::into_future(reconciler(obj, ctx)).in_current_span(),
1539                    &Handle::current(),
1540                )
1541            },
1542            error_policy,
1543            context,
1544            self.reader,
1545            StreamBackoff::new(self.trigger_selector, self.trigger_backoff)
1546                .take_until(future::select_all(self.graceful_shutdown_selector)),
1547            self.config,
1548        )
1549        .take_until(futures::future::select_all(self.forceful_shutdown_selector))
1550    }
1551}
1552
1553#[cfg(test)]
1554mod tests {
1555    use std::{convert::Infallible, pin::pin, sync::Arc, time::Duration};
1556
1557    use super::{APPLIER_REQUEUE_BUF_SIZE, Action};
1558    use crate::{
1559        Config, Controller, applier,
1560        reflector::{self, ObjectRef},
1561        watcher::{self, Event, watcher},
1562    };
1563    use futures::{Stream, StreamExt, TryStreamExt};
1564    use k8s_openapi::api::core::v1::ConfigMap;
1565    use kube_client::{
1566        Api, Resource,
1567        core::{ObjectMeta, PartialObjectMeta},
1568    };
1569    use serde::de::DeserializeOwned;
1570    use tokio::time::timeout;
1571
1572    fn assert_send<T: Send>(x: T) -> T {
1573        x
1574    }
1575
1576    // Used to typecheck that a type T is a generic type that implements Stream
1577    // and returns a WatchEvent generic over a resource `K`
1578    fn assert_stream<T, K>(x: T) -> T
1579    where
1580        T: Stream<Item = watcher::Result<Event<K>>> + Send,
1581        K: Resource + Clone + DeserializeOwned + std::fmt::Debug + Send + 'static,
1582    {
1583        x
1584    }
1585
1586    fn mock_type<T>() -> T {
1587        unimplemented!(
1588            "mock_type is not supposed to be called, only used for filling holes in type assertions"
1589        )
1590    }
1591
1592    // not #[test] because we don't want to actually run it, we just want to assert that it typechecks
1593    #[allow(dead_code, unused_must_use)]
1594    fn test_controller_should_be_send() {
1595        assert_send(
1596            Controller::new(mock_type::<Api<ConfigMap>>(), Default::default()).run(
1597                |_, _| async { Ok(mock_type::<Action>()) },
1598                |_: Arc<ConfigMap>, _: &std::io::Error, _| mock_type::<Action>(),
1599                Arc::new(()),
1600            ),
1601        );
1602    }
1603
1604    // not #[test] because we don't want to actually run it, we just want to
1605    // assert that it typechecks
1606    //
1607    // will check return types for `watcher` and `watcher with PartialObjectMeta` do not drift
1608    // given an arbitrary K that implements `Resource` (e.g ConfigMap)
1609    #[allow(dead_code, unused_must_use)]
1610    fn test_watcher_stream_type_drift() {
1611        assert_stream(watcher(mock_type::<Api<ConfigMap>>(), Default::default()));
1612        assert_stream(watcher(
1613            mock_type::<Api<PartialObjectMeta<ConfigMap>>>(),
1614            Default::default(),
1615        ));
1616    }
1617
1618    #[tokio::test]
1619    async fn applier_must_not_deadlock_if_reschedule_buffer_fills() {
1620        // This tests that `applier` handles reschedule queue backpressure correctly, by trying to flood it with no-op reconciles
1621        // This is intended to avoid regressing on https://github.com/kube-rs/kube/issues/926
1622
1623        // Assume that we can keep APPLIER_REQUEUE_BUF_SIZE flooded if we have 100x the number of objects "in rotation"
1624        // On my (@nightkr)'s 3900X I can reliably trigger this with 10x, but let's have some safety margin to avoid false negatives
1625        let items = APPLIER_REQUEUE_BUF_SIZE * 50;
1626        // Assume that everything's OK if we can reconcile every object 3 times on average
1627        let reconciles = items * 3;
1628
1629        let (queue_tx, queue_rx) = futures::channel::mpsc::unbounded::<ObjectRef<ConfigMap>>();
1630        let (store_rx, mut store_tx) = reflector::store();
1631        let mut applier = pin!(applier(
1632            |_obj, _| {
1633                Box::pin(async move {
1634                    // Try to flood the rescheduling buffer buffer by just putting it back in the queue immediately
1635                    //println!("reconciling {:?}", obj.metadata.name);
1636                    Ok(Action::requeue(Duration::ZERO))
1637                })
1638            },
1639            |_: Arc<ConfigMap>, _: &Infallible, _| todo!(),
1640            Arc::new(()),
1641            store_rx,
1642            queue_rx.map(Result::<_, Infallible>::Ok),
1643            Config::default(),
1644        ));
1645        store_tx.apply_watcher_event(&watcher::Event::InitDone);
1646        for i in 0..items {
1647            let obj = ConfigMap {
1648                metadata: ObjectMeta {
1649                    name: Some(format!("cm-{i}")),
1650                    namespace: Some("default".to_string()),
1651                    ..Default::default()
1652                },
1653                ..Default::default()
1654            };
1655            store_tx.apply_watcher_event(&watcher::Event::Apply(obj.clone()));
1656            queue_tx.unbounded_send(ObjectRef::from_obj(&obj)).unwrap();
1657        }
1658
1659        timeout(
1660            Duration::from_secs(10),
1661            applier
1662                .as_mut()
1663                .take(reconciles)
1664                .try_for_each(|_| async { Ok(()) }),
1665        )
1666        .await
1667        .expect("test timeout expired, applier likely deadlocked")
1668        .unwrap();
1669
1670        // Do an orderly shutdown to ensure that no individual reconcilers are stuck
1671        drop(queue_tx);
1672        timeout(
1673            Duration::from_secs(10),
1674            applier.try_for_each(|_| async { Ok(()) }),
1675        )
1676        .await
1677        .expect("applier cleanup timeout expired, individual reconciler likely deadlocked?")
1678        .unwrap();
1679    }
1680}