crossplane-contrib / function-patch-and-transform

A patch & transform composition function
https://crossplane.io
Apache License 2.0
26 stars 25 forks source link

Don't delete composed resources when we hit an error #81

Closed negz closed 9 months ago

negz commented 9 months ago

Fixes https://github.com/crossplane-contrib/function-patch-and-transform/issues/59 Fixes https://github.com/crossplane-contrib/function-patch-and-transform/issues/70

This PR changes how function-patch-and-transform handles patch errors:

This means that required field paths only meaningfully affects composed resources that don't exist yet. A required field path not existing will block the creation of a composed resource that doesn't exist yet. The function considers a composed resource to not exist if it doesn't appear in observed state.

In any other scenario, a patch from a required field path is treated the same as a patch from an optional field path. The affected resource is added to the desired state, but the patch is skipped. If the resource being patched already exists, the only difference between a missing required field path and a missing optional field path is whether the function emits a warning result. The function will emit a warning result if a required field path not found.

This change ensures that the function won't delete a composed resource when it encounters an error, by omitting the composed resource from its desired state. In some circumstances the function could delete a specific field of an existing composed resource. This would happen if the function had successfully created that field by patching from a field path that was later removed.

negz commented 9 months ago

Marking ready for review. I ran some manual tests with crossplane beta render:

phisco commented 9 months ago

@negz

If patching an existing composed resource fails because the source field path was required and wasn't found, the function emits a warning result and skips the patch. It does add the composed resource to desired state, so it won't be deleted.

Just to be explicit, this would be not be a different behaviour w.r.t. the native implementation, right? Except for:

In some circumstances the function could delete a specific field of an existing composed resource. This would happen if the function had successfully created that field by patching from a field path that was later removed.

I wonder if we could just "copy" the current value in such case to avoid that 🤔

negz commented 9 months ago

I wonder if we could just "copy" the current value in such case to avoid that

I thought about that. I'm not sure how I feel about it though. It starts to seem less like the function's desired state. I guess you could say the function's desired state is to not change the observed state?

I think my preference would be to proceed as-is and consider doing this if it proves to be an issue.

phisco commented 9 months ago

I guess you could say the function's desired state is to not change the observed state?

Yes, which is what I was hinting at here by adding a way to ignore resources.

negz commented 9 months ago

Yes, which is what I was hinting at https://github.com/crossplane-contrib/function-patch-and-transform/issues/59#issuecomment-1884704718 by adding a way to ignore resources.

Yeah, I'm wary of supporting "partial applies" like this. It could get especially tricky to reason about when stacking desired state from multiple functions:

Plus, if we know we're hitting an error, do we want to apply as much of the desired state as possible, or as little as possible? As little as possible seems safest/most conservative to me.

negz commented 9 months ago

I wonder if we could just "copy" the current value in such case to avoid that

It occurred to me this morning that this could get us back into the hairy client-side merge problems we hope to avoid by using SSA. For basic/leaf fields it might be fine, but I could imagine it leading down a path to us needing to merge state client-side. Imagine for example a failed patch to an object in a map-like array.

phisco commented 9 months ago

I'm still not entirely sure it's ok to emit resources without a field set because a required patch went missing, in the best case scenario the target field was required and the resource gets rejected, in the worst case we set it to something and we can't go back anymore (or it's costly to do so). I built an image and asked @haarchri for a more real-world feedback.

negz commented 9 months ago

@phisco The other option would be to return a fatal result if a required patch to an existing resource failed. Main downside of this approach would be deadlocking eventually consistent patches, but maybe that's less of an issue when patching existing resources.

I would suggest it's worth proceeding with this even if we're not sure whether the behaviour for required patches to existing resources is ideal. I imagine it's still better than the state we have right now, i.e. deleting resources when we hit an error. I think it's a priority to ship a version that fixes that.

negz commented 9 months ago

Thanks, I'll cut a release this week.