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