-
-
Notifications
You must be signed in to change notification settings - Fork 322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document Controller::reconcile_on
and remove Err
input requirement
#1304
Conversation
The unstable method currently suggests that this method can be used to help share a store with the reconciler. This is actually nothing specific to `reconcile_on`, and you can do the same with the streams interface with `watches_stream`. We made the `reconcile_on` right before `watches_stream` became a thing so this makes sense. Have reworded the example to highlight that this has a better use-case with actually getting arbitrary third-party info, and then mapping that to kubernetes objects. First example that came to mind was using an IntervalStream with tokio and just cycle through a bunch of objects, but there may be a better example that does not pull in the extra dev dep. Signed-off-by: clux <sszynrae@gmail.com>
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #1304 +/- ##
=======================================
- Coverage 72.4% 72.4% -0.0%
=======================================
Files 75 75
Lines 6341 6343 +2
=======================================
Hits 4586 4586
- Misses 1755 1757 +2
|
see kube-rs/kube#1304 for the root example Signed-off-by: clux <sszynrae@gmail.com>
Signed-off-by: clux <sszynrae@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me the example is hard to understand and doesn't look like a real usecase. ~5 lines of setup which seems to be very constructed and a magic constant managed-cm1
(Question when reading the code that come to mind: Where does that come from? What is it used for? Won't this trigger always the same object?)
Maybe it'd be better to hide the boilerplate from the example but instead just have an Pseudo: struct ExternalObject {
name: String,
}
let external_stream = watch_external_objects().map_ok(|ext| ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns)); |
Co-authored-by: David Herberth <github@dav1d.de> Signed-off-by: Eirik A <sszynrae@gmail.com>
Signed-off-by: clux <sszynrae@gmail.com>
Signed-off-by: clux <sszynrae@gmail.com>
That sounds a lot better to me as well. Ended up making a doc hidden function and refer to it using your name to help indicate that you are meant to write this yourself. Updated the post and image above. Have used a very dumb signature for this hidden fn; fn watch_external_objects() -> impl Stream<Item = ExternalObject> { stream::iter(vec![]) } as a way to help the people who go digging, but can also show this line by default if we think it is helpful. |
Note there is also a bit of an interesting quirk about this fn as it stands; it needs a Not sure what to do about that at the moment. I can change the example to highlight it with: diff --git a/kube-runtime/src/controller/mod.rs b/kube-runtime/src/controller/mod.rs
index 69399d89..0c46fc58 100644
--- a/kube-runtime/src/controller/mod.rs
+++ b/kube-runtime/src/controller/mod.rs
@@ -1107,13 +1107,13 @@ where
/// # let client: Client = todo!();
/// # async fn reconcile(_: Arc<ConfigMap>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
/// # fn error_policy(_: Arc<ConfigMap>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
- /// # fn watch_external_objects() -> impl Stream<Item = ExternalObject> { stream::iter(vec![]) }
+ /// # fn watch_external_objects() -> impl Stream<Item = Result<ExternalObject, kube::runtime::watcher::Error>> { stream::iter(vec![]) }
/// # let ns = "controller-ns".to_string();
/// struct ExternalObject {
/// name: String,
/// }
- /// let external_stream = watch_external_objects().map(|ext| {
- /// Ok(ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns))
+ /// let external_stream = watch_external_objects().map_ok(|ext| {
+ /// ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns)
/// });
///
/// Controller::new(Api::<ConfigMap>::namespaced(client, &ns), Config::default()) but it's probably better to shift the signature of this function to not get an user error type passed in? |
ok, if we are to omit the We could do that in the signature by doing: diff --git a/kube-runtime/src/controller/mod.rs b/kube-runtime/src/controller/mod.rs
index 69399d89..cd85f241 100644
--- a/kube-runtime/src/controller/mod.rs
+++ b/kube-runtime/src/controller/mod.rs
@@ -1113,7 +1113,7 @@ where
/// name: String,
/// }
/// let external_stream = watch_external_objects().map(|ext| {
- /// Ok(ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns))
+ /// ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns)
/// });
///
/// Controller::new(Api::<ConfigMap>::namespaced(client, &ns), Config::default())
@@ -1125,15 +1125,14 @@ where
/// ```
#[cfg(feature = "unstable-runtime-reconcile-on")]
#[must_use]
- pub fn reconcile_on(
- mut self,
- trigger: impl Stream<Item = Result<ObjectRef<K>, watcher::Error>> + Send + 'static,
- ) -> Self {
+ pub fn reconcile_on(mut self, trigger: impl Stream<Item = ObjectRef<K>> + Send + 'static) -> Self {
self.trigger_selector.push(
trigger
- .map_ok(move |obj| ReconcileRequest {
- obj_ref: obj,
- reason: ReconcileReason::Unknown,
+ .map(move |obj| {
+ Ok(ReconcileRequest {
+ obj_ref: obj,
+ reason: ReconcileReason::Unknown,
+ })
})
.boxed(),
); which I think is better? |
Seems definitely simpler, also the |
We don't really handle errors to a large extent here. Inside the Input errors are turned into a kube/kube-runtime/src/controller/mod.rs Lines 277 to 278 in 68ae55c
which is a variant of the error type returned by the Controller stream meaning you can pick up on this as in the example: kube/examples/configmapgen_controller.rs Lines 114 to 118 in 68ae55c
so you can get back a more nested error than the one literally saw earlier 🙃 before it reaches the kube/kube-runtime/src/controller/mod.rs Lines 1271 to 1273 in 68ae55c
so if users posted We probably don't want to encourage this though. If they are spamming us with errors they can take down the controller (because the backoff is global and continued triggering of it could starve the input stream). Plus it sends the wrong message that they don't need to handle the errors from the 3rd party / external api because they can pass it to us - and that only hurts them long term. |
Signed-off-by: clux <sszynrae@gmail.com>
Controller::reconcile_on
Controller::reconcile_on
docs remove Err requirement
Controller::reconcile_on
docs remove Err requirementController::reconcile_on
and remove Err
input requirement
follow-up for kube-rs/kube#1304 Signed-off-by: clux <sszynrae@gmail.com>
It's less about kube-rs handling those errors, and more about ensuring that applications can have the choice of propagating them through to wherever it makes sense to handle them. At this point, whoever is calling Going with your own example of |
But what can you reasonably do with these errors in the Controller? Yes you could have a common/joined error handler, but what option does that actually give you other than logging and ignoring? I feel like you end up with a better design if you handle these errors where they occur and then you can make the decision whether to trigger the controller anyways or not. |
Yeah. The example is meant to be a type-only stand-in for a typically much larger external interface module in your application that deserves to have its own error handling not interwoven with the controller side (latter of which often is not so big). All the use cases I have for this does not even have a stream as input, but will rather produce a synthetic stream via periodic fetching on external APIs / or listening to |
The unstable method currently suggests that this method can be used to help share a store with the reconciler. This is actually nothing specific to
reconcile_on
, and you can do the same with the streams interface withwatches_stream
.(We made the
reconcile_on
right beforewatches_stream
became a thing so this makes sense.)Secondly, we pretended we would handle errors being passed in from a third-party resource which is not true. See #1304 (comment). We now no longer accept a stream of
Result<ObjectRef, Error>
but a stream ofObjectRef
. Users creating reconcilers around third party apis / external resources need to do error handling when dealing with these separately.The example now highlights that this has a better use-case with actually getting arbitrary third-party info, and then mapping that to kubernetes objects.
Planning on putting whatever we settle on here as the de-facto example for "external relations" on https://kube.rs/controllers/relations/#external-relations as it would be good to have something that compiled that i can refer it to.
(CI required a fixup in #1305)