[go: nahoru, domu]

Skip to content
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

Generic cross-reference design doc #4366

Closed
wants to merge 4 commits into from

Conversation

pedjak
Copy link
Contributor
@pedjak pedjak commented Jul 21, 2023

Generic cross-reference design doc

Fixes #1770

I have:

  • Read and followed Crossplane's contribution process.
  • Added or updated unit and E2E tests for my change.
  • Run make reviewable to ensure this PR is ready for review.
  • Added backport release-x.y labels to auto-backport this PR if necessary.

Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
@pedjak pedjak requested review from a team and negz as code owners July 21, 2023 11:04
@pedjak pedjak changed the title ### Description of your changes Generic cross-reference design doc Jul 21, 2023
@pedjak pedjak self-assigned this Jul 21, 2023
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
The above syntax can be used as well when a value might appear in a several
places within single object.

### Value Transformation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to refer to a real-world example that might lead to this scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, @turkenh or @haarchri do you have some better example from the field?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a more complex example would be nice to see, not necessarily a real world example, but at least a bigger toy example would be helpful. I'm not sure how you are envisioning this new API to be used in the context of Compositions. Should users define this new resource as part of their Composition? How will that look like? So users will most probably have to define Usage resources as defined by #4215 to achieve deletion ordering and then this new object to define mappings between values? how's that going to look like?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phisco, we are not aware of many real-world use cases where a value reference requires defining a deletion ordering, see this comment.

However, I agree that having a dedicated section for Compositions makes sense. It would especially be helpful to see the interplay between the existing patches in compositions and the one we're proposing here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar issues have been discussed regarding selecting EnvironmentConfigs and selection in general - there needs to be a way to ensure deterministic selection to avoid flapping between two or more values.

design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
Finally, providers need to be upgraded to use the new version of crossplane-runtime
and for each type, the `ResolveReference` function needs to be regenerated.

## Alternatives Considered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this proposal seems to make the same mistakes that the composition patch and transformation (P&T) has done, and that eventually led to the invention of functions:
    • desirable transformations are open ended and are never enough
    • patching by field paths is a very low-level mechanism, operating on low-level data-type rather than being MR kind aware
    • references don't solve the order problem: (a) no way to order without the MR author having prepared for a reference field (b) dependencies might be more than just a field in an object (think of probes).
  • in general, this proposal seems to clone the P&T idea into the object system. Do we really want that? Then the new approach competes with compositions. This is kind of the goal stated in the beginning of the doc. I miss a discussion why this is an acceptable goal, i.e. a motivation section.
  • the core motivation behind this proposal seems to be that compositions are too slow (e.g. not watch-driven, but run in 60s interval). Maybe we should fix the latter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
This feels like it's adding one more layer of indirection to Compositions and feels overlapping with both the Usage resource introduced by #4215 and a little bit with observe only resources too.

Copy link
Member
@muvaf muvaf Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this proposal seems to make the same mistakes that the composition patch and transformation (P&T) has done, and that eventually led to the invention of functions

FWIW, some context here, we knew we'll need functions even before the design of the Composition itself but it was always about how far we can go with the least logic so that you can go to production without having to deal with a whole software stack to write reconciliation logic. And we wanted to let the use cases pop up over time so we can leave the minimum amount of logic that you users would have to use functions for instead of having to go all in on writing composition in a programming language.

Then the new approach competes with compositions.

Agreed, we need to talk more about the cases where using bi-directional patches in composition wouldn't be suitable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to define and agree on the problem before talking about the solution.

Let's take the VPC / Subnet example. If someone wants to create a Subnet in a VPC created/managed by XP, they need a mechanism to read the id from VPC and put it into the Subnet spec. Today, this is possible in one of the following two ways:

a. Using cross-resource references where they can either use MR name for VPC (e.g. vpcIdRef.name) or labels/controllerRef (e.g. vpcIdRef.selector).
b. Using composition patches, but with the following limitations:

  • VPC and Subnet must exist in the same composition.
  • id needs to be patched to XR first, (e.g. status.vpcId) and then patched to the Subnet (toFieldPath: spec.forProvider.vpcId)

Typically, in compositions we see that the option (a) is used with matchControllerRef where the id of the VPC with the same controller ref (owned by the same XR) is extracted and used in Subnet:

            vpcIdSelector:
              matchControllerRef: true

Then, what is wrong? There are two problems invalidating both of the available options as such:

option (a): Not every MR field directly maps to a single type, so existing XRR falls short. There are a lot of issue references in the original ticket, but one example is the destinationArn on a SubscriptionFilter resource where destination could be one of the multiple types.
option (b): Resources may not be part of the same composite.

So, we need a solution here. If someone creates a SubscriptionFilter and a DeliveryStream (using the same example) with Crossplane but not necessarily within the same composition, they need a way to fill spec.forProvider.destinationArn on SubscriptionFilter with the arn of the DeliveryStream they intend to.

this proposal seems to make the same mistakes that the composition patch and transformation (P&T) has done, and that eventually led to the invention of functions:

I agree that the problem we're trying to solve here is similar to what (P&T) has done, but there is that fundamental difference that makes it impossible to solve with compositions or functions, which is resources may not be part of the same composite. I can see two ways to overcome this, both with some implications:

  • If you want to patch from a resource that is not part of the same composition, define an ObserveOnly resource in the same composition and patch from it.
  • Functions could access to k8s API and read anything it wants without being limited to observed state.

desirable transformations are open ended and are never enough

Yes. While we lack very basics today, like take that ARN and put it here, it would eventually need more and more, just like we see in composition patches. Leveraging and relying on some external,tool might help here.

references don't solve the order problem: (a) no way to order without the MR author having prepared for a reference field (b) dependencies might be more than just a field in an object (think of probes).

I am not aware of a real (creation) ordering problem today where we are good enough with eventual consistency.
We had the deletion ordering problem and tackling it with the Usage type.
I am not claiming it will not complicate anything but we can still stay on the "declarative" world by saying, this value needs to be constructed this way and eventually patched there.

in general, this proposal seems to clone the P&T idea into the object system. Do we really want that?

Not sure if the solution should be the one proposed here, but we need a solution to the problem defined above.

the core motivation behind this proposal seems to be that compositions are too slow (e.g. not watch-driven, but run in 60s interval). Maybe we should fix the latter.

Not sure how you came up with this assumption, but I am not aware of such a motivation for Generic XRR. IIRC, compositions not being watch-driven came up recently in the composition function (re)design; however, Generic XRR has been an open issue for more than 2 years. One can claim that, if we could live without it for 2 years, then maybe it is not that an important problem to solve :) I would love to hear from the community on that, and I believe it is not a blocker mostly because there are workaround/hacks there which could unblock use cases but ultimately complicate things further.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to add a slightly different perspective based on my experience using EnvironmentConfigs. I think there is a fair amount of overlap in the usage scenarios, and for me personally the EnvironemtConfig model is simpler and easier to understand and use than any of the other proposals for separate resource references that I have seen.

One of the common use cases we have found for EnvironmentConfig is making information available from one Composite to another. If Composite A creates a set of resources and wants to make some information regarding those resources available to any other Composite/Composition that may need them, it can create an EnvironmentConfig object with the appropriate labels and the information to be "published". Other Composites can retrieve these EnvironmentConfigs as needed in their Compositions using the appropriate label selectors.

The process of finding the appropriate EnvironmentConfigs and loading them into the Composition environment creates what is essentially a runtime context for that Composite. The user (composition author) can leverage this context to pass information between resources and can reference external data using EnvironmentConfigs. The structure of the local context is defined by the author and information is placed in specific locations by name. This model works extremely well and provides necessary functionality.

So why limit the environment external data references to EnvironmentConfigs? Why not allow the environment specification to include references to any existing Kubernetes object, which can be retrieved by the same mechanism that resolves EnvironmentConfigs today? If an author wants to retrieve data from an existing XR or MR or ConfigMap or Secret they could specify the G/V/K information required to find the object along with a name or label selector, and a path within the object to the data they want, and that information can be populated in the environment in the specified location.

From the author's perspective this would let me put all of my external data references in one place, and they can be retrieved dynamically at runtime as needed if they exist, or they can eventually reconcile as they become available.

I think one of the overarching goals needs to be keeping the user experience as simple as possible, and requiring a specific resource definition for every reference feels like it is adding complexity for the user and making the composition more difficult to develop and maintain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #1770 (comment) I share the concern that generic references grows to be something like P&T Composition.

Also, per #1770 (comment) I'm pretty confident that today's hardcoded style of references isn't going to scale. It's too manual to support, too inflexible for users, and creates a coupling between providers when one provider references a type defined by another.

I could be convinced that the answer is to just not have MR-level references at all if we can find a clean way to address this at the Composition level. I would expect such an approach to be controversial though. I suspect there's a not-insignificant amount of folks who don't use Composition today who would be forced to if Composition was the only way to derive one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be convinced that the answer is to just not have MR-level references at all if we can find a clean way to address this at the Composition level. I would expect such an approach to be controversial though. I suspect there's a not-insignificant amount of folks who don't use Composition today who would be forced to if Composition was the only way to derive one

This proposal talks about that path as well. No matter what strategy/convention we would like to apply to MR-level reference, at the end of the day, it needs to be implemented by providers. On the other hand, solving the problem via compositions requires no changes at provider side.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth a community poll of some sort to try to determine how widespread the use of MR-only deployments is? I'm reluctant to push more composition-like functionality into the MR since that's what we have Compositions for. I agree that having MRs is great, but I think the major benefit of Crossplane is the modeling and abstraction provided by Composites/Compositions and some (most?) types of cross-resource functionality should be based on that.


Some of the above issues can be solved by defining a composition. However, there
are cases where one would like to use and refer to a resource not bound by a
composite instance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compositions today mix specification, relationships and orchestration:

  1. specification: specify how objects look like
  2. relationships: Patch&Transformations (P&T) establish field level dependencies
  3. orchestration: P&T is used to define order, both by (a) implicit field dependencies of required fields and (b) by hacks to intentionally break objects or pause objects before some condition is true.

In that light, I would consider the goal of this proposal to promote (2) to the object system, and primarily leave compositions for (1) and (3). This bigger "philosophical" context must be clearer in this doc, maybe in some motivation section.

There is also a connection between (2) and (3a). An asynchronous controller cannot solve the required fields problem, i.e. late initialization. This means that we encourage late initialization more and more. Late initialization has the problem of more states to reason about. Resources will not be completely or correctly specified fromt he beginning, i.e. the creation of the object. This obviously has consequences: less invariants/consistency, less type-safety.

Is that ^^ a higher level goal? Isn't being more dynamic, less consistent, less type-safety a step into the wrong direction? Thinking about DevEx and "Crossplane is complex".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think It is hard to take (2) out of compositions. At a minimum, we will always need to patch from XR to Composed resources. I would rather see this proposal as a solution to the limitations we have in defining relationships:

a. XR to Composed
b. Composed to XR
c. Composed to Composed
d. External MR/XR to Composed

(a) and (b) are fundamental for compositions, and I don't think aiming to take it out makes sense. (c) is only possible over (b+a), and (d) is not possible today (without hacks). So, the impact of this proposal on compositions is to enable (d) and (c). Of course, the ability to refer generically would have benefits with compositions not in the play.

I agree that clarifying these in a motivation section would be great.

This means that we encourage late initialization more and more. Late initialization has the problem of more states to reason about. Resources will not be completely or correctly specified fromt he beginning, i.e. the creation of the object. This obviously has consequences: less invariants/consistency, less type-safety.

I would rather state it as we rely more on eventual consistency for more flexibility. In an ideal world, I agree that more consistency and full type-safety would be great, but this ends up with limitations, i.e. we already have a referencing solution with type-safety, but it has limitations mentioned in the doc.

design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
The above syntax can be used as well when a value might appear in a several
places within single object.

### Value Transformation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a more complex example would be nice to see, not necessarily a real world example, but at least a bigger toy example would be helpful. I'm not sure how you are envisioning this new API to be used in the context of Compositions. Should users define this new resource as part of their Composition? How will that look like? So users will most probably have to define Usage resources as defined by #4215 to achieve deletion ordering and then this new object to define mappings between values? how's that going to look like?

are cases where one would like to use and refer to a resource not bound by a
composite instance.

__**NOTE**__: [a prior design exists on this topic](https://github.com/crossplane/crossplane/pull/2385).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth having a paragraph here to elaborate on why we paused the work back then and whether the same reasons still hold or not. The proposed changes were to add complexity at a foundational level that is managed resources and I had wanted to make sure we add it for good reason. On the other hand, the proposed approach here is less intrusive with its own CRD, which still requires some justification but maybe not as much. And Crossplane has come a long way since then.

To shed some light as the author of the that draft, the following is a list of the motivations and how they compare to today:

  • The main reason we needed generic references was that we did not have a good way to generate references in Terrajet (Upjet today) and writing code for every reference field was just too much - imagine custom code for every reference field of thousands of CRDs. But then we did figure out a good way to generate the code, hence the only thing left was whether we tell the generator which fields reference what in development time or user tells every time the reference something. We decided to take on the burden once so users don't have to every time they need to reference.
  • Another reason was people asked for retrieving information from resources that they don't want to manage via that specific Crossplane instance.
    • Some of this was common MRs that were managed by something else or another Crossplane instance, like VPC that should be used by resources in different Crossplane instances. This is largely solved by observe-only resources today.
    • The rest was arbitrary metadata information, like cost center tags etc, that they wanted to have on MRs. If you use Composition, we have EnvironmentConfig today for that use case. However, outside of composition, generic references would be needed at MR level.
  • The Route53 thing is still not solved unless you use Composition but it may not be very common.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll recap my understanding of why we want generic cross-resource references here, since it's relevant to what @muvaf wrote above. I touched on some of this at #1770 (comment).

The current spec.forProvider.fooRef implementation:

  1. Requires all possible references to be manually identified and declared at build time.
  2. Is inflexible - users can only reference types the provider developers declared, in the ways they declared.
  3. Requires the referring provider to import any types they refer to.

3 is of particular interest for provider families, because it creates a potential version dependency between two providers in a family. For example if provider-aws-eks v0.42.0 is compiled against the types from provider-aws-ec2 v0.42.0, there's a chance that installing provider-aws-ec2 v0.50.0 will break references. It would be ideal if there was no version coupling between providers within a family.

Finally, providers need to be upgraded to use the new version of crossplane-runtime
and for each type, the `ResolveReference` function needs to be regenerated.

## Alternatives Considered
Copy link
Member
@muvaf muvaf Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this proposal seems to make the same mistakes that the composition patch and transformation (P&T) has done, and that eventually led to the invention of functions

FWIW, some context here, we knew we'll need functions even before the design of the Composition itself but it was always about how far we can go with the least logic so that you can go to production without having to deal with a whole software stack to write reconciliation logic. And we wanted to let the use cases pop up over time so we can leave the minimum amount of logic that you users would have to use functions for instead of having to go all in on writing composition in a programming language.

Then the new approach competes with compositions.

Agreed, we need to talk more about the cases where using bi-directional patches in composition wouldn't be suitable.

Copy link
Member
@turkenh turkenh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @pedjak!

I like the idea of separating the building and the consumption part of referable values, but I am yet to be convinced that it is the right way to go.

In general, I would love to see:

  • More details on motivation - why do we need this?
  • A more detailed comparison between build/patch with a single CR vs build here / patch there approach.
  • How it plays with compositions? Some real-world examples of different use cases where the feature would be helpful.


Some of the above issues can be solved by defining a composition. However, there
are cases where one would like to use and refer to a resource not bound by a
composite instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think It is hard to take (2) out of compositions. At a minimum, we will always need to patch from XR to Composed resources. I would rather see this proposal as a solution to the limitations we have in defining relationships:

a. XR to Composed
b. Composed to XR
c. Composed to Composed
d. External MR/XR to Composed

(a) and (b) are fundamental for compositions, and I don't think aiming to take it out makes sense. (c) is only possible over (b+a), and (d) is not possible today (without hacks). So, the impact of this proposal on compositions is to enable (d) and (c). Of course, the ability to refer generically would have benefits with compositions not in the play.

I agree that clarifying these in a motivation section would be great.

This means that we encourage late initialization more and more. Late initialization has the problem of more states to reason about. Resources will not be completely or correctly specified fromt he beginning, i.e. the creation of the object. This obviously has consequences: less invariants/consistency, less type-safety.

I would rather state it as we rely more on eventual consistency for more flexibility. In an ideal world, I agree that more consistency and full type-safety would be great, but this ends up with limitations, i.e. we already have a referencing solution with type-safety, but it has limitations mentioned in the doc.

design/design-generic-references.md Outdated Show resolved Hide resolved
* be configurable at runtime
* use the existing [Kubernetes API object reference] convention
as much as possible
* provide alternative convention in cases where [Kubernetes API object reference]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this goal is implicitly included in the above one.

design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
design/design-generic-references.md Outdated Show resolved Hide resolved
Finally, providers need to be upgraded to use the new version of crossplane-runtime
and for each type, the `ResolveReference` function needs to be regenerated.

## Alternatives Considered
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to define and agree on the problem before talking about the solution.

Let's take the VPC / Subnet example. If someone wants to create a Subnet in a VPC created/managed by XP, they need a mechanism to read the id from VPC and put it into the Subnet spec. Today, this is possible in one of the following two ways:

a. Using cross-resource references where they can either use MR name for VPC (e.g. vpcIdRef.name) or labels/controllerRef (e.g. vpcIdRef.selector).
b. Using composition patches, but with the following limitations:

  • VPC and Subnet must exist in the same composition.
  • id needs to be patched to XR first, (e.g. status.vpcId) and then patched to the Subnet (toFieldPath: spec.forProvider.vpcId)

Typically, in compositions we see that the option (a) is used with matchControllerRef where the id of the VPC with the same controller ref (owned by the same XR) is extracted and used in Subnet:

            vpcIdSelector:
              matchControllerRef: true

Then, what is wrong? There are two problems invalidating both of the available options as such:

option (a): Not every MR field directly maps to a single type, so existing XRR falls short. There are a lot of issue references in the original ticket, but one example is the destinationArn on a SubscriptionFilter resource where destination could be one of the multiple types.
option (b): Resources may not be part of the same composite.

So, we need a solution here. If someone creates a SubscriptionFilter and a DeliveryStream (using the same example) with Crossplane but not necessarily within the same composition, they need a way to fill spec.forProvider.destinationArn on SubscriptionFilter with the arn of the DeliveryStream they intend to.

this proposal seems to make the same mistakes that the composition patch and transformation (P&T) has done, and that eventually led to the invention of functions:

I agree that the problem we're trying to solve here is similar to what (P&T) has done, but there is that fundamental difference that makes it impossible to solve with compositions or functions, which is resources may not be part of the same composite. I can see two ways to overcome this, both with some implications:

  • If you want to patch from a resource that is not part of the same composition, define an ObserveOnly resource in the same composition and patch from it.
  • Functions could access to k8s API and read anything it wants without being limited to observed state.

desirable transformations are open ended and are never enough

Yes. While we lack very basics today, like take that ARN and put it here, it would eventually need more and more, just like we see in composition patches. Leveraging and relying on some external,tool might help here.

references don't solve the order problem: (a) no way to order without the MR author having prepared for a reference field (b) dependencies might be more than just a field in an object (think of probes).

I am not aware of a real (creation) ordering problem today where we are good enough with eventual consistency.
We had the deletion ordering problem and tackling it with the Usage type.
I am not claiming it will not complicate anything but we can still stay on the "declarative" world by saying, this value needs to be constructed this way and eventually patched there.

in general, this proposal seems to clone the P&T idea into the object system. Do we really want that?

Not sure if the solution should be the one proposed here, but we need a solution to the problem defined above.

the core motivation behind this proposal seems to be that compositions are too slow (e.g. not watch-driven, but run in 60s interval). Maybe we should fix the latter.

Not sure how you came up with this assumption, but I am not aware of such a motivation for Generic XRR. IIRC, compositions not being watch-driven came up recently in the composition function (re)design; however, Generic XRR has been an open issue for more than 2 years. One can claim that, if we could live without it for 2 years, then maybe it is not that an important problem to solve :) I would love to hear from the community on that, and I believe it is not a blocker mostly because there are workaround/hacks there which could unblock use cases but ultimately complicate things further.

@pedjak
Copy link
Contributor Author
pedjak commented Jul 25, 2023

Thanks all for the very live discussion. I think it would be important to reach consensus on the following:

(1) Do we really need a way to define generic cross-resource references on managed resources without using compositions?

or

(2) we would like to use (and extend if needed) compositions for that purpose?

@haarchri and @ytsarev - your feedback would be really helpful, i.e. what are the usecases currently not implementable using compositions (or requires some not-so-nice workarounds/hacks)?

@haarchri
Copy link
Contributor

we discussed the generic reference design poc here -in a offline conversation with @pedjak and @sttts
As part of that discussion, I have included a few examples to illustrate various aspects of the proposal.

https://github.com/haarchri/crossplane-generic-references/tree/main

i will add later comments inline

design/design-generic-references.md Outdated Show resolved Hide resolved
```

The reference resolver could first try to find the `referable-vpc-id` `VPC` instance,
and fallback to `referable-vpc-id` `Referable` if the former does not exist.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree - any time we fallback from one thing to another we risk the possibility that on the next reconciliation the thing we fell-back-from will be there and will change the behavior in unexpected ways.

Also overlaps in names between things we are referring to and the name of the Referrable could cause strange behaviors.

design/design-generic-references.md Outdated Show resolved Hide resolved
The above syntax can be used as well when a value might appear in a several
places within single object.

### Value Transformation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar issues have been discussed regarding selecting EnvironmentConfigs and selection in general - there needs to be a way to ensure deterministic selection to avoid flapping between two or more values.


The controller is responsible for:

* Defining proper `Usage` instance to guard against improper deletion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may cause confusion when users are unable to delete resources that they are not aware are being referenced by other resources, Traceability and verbose error messages will be important for user experience.

Finally, providers need to be upgraded to use the new version of crossplane-runtime
and for each type, the `ResolveReference` function needs to be regenerated.

## Alternatives Considered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to add a slightly different perspective based on my experience using EnvironmentConfigs. I think there is a fair amount of overlap in the usage scenarios, and for me personally the EnvironemtConfig model is simpler and easier to understand and use than any of the other proposals for separate resource references that I have seen.

One of the common use cases we have found for EnvironmentConfig is making information available from one Composite to another. If Composite A creates a set of resources and wants to make some information regarding those resources available to any other Composite/Composition that may need them, it can create an EnvironmentConfig object with the appropriate labels and the information to be "published". Other Composites can retrieve these EnvironmentConfigs as needed in their Compositions using the appropriate label selectors.

The process of finding the appropriate EnvironmentConfigs and loading them into the Composition environment creates what is essentially a runtime context for that Composite. The user (composition author) can leverage this context to pass information between resources and can reference external data using EnvironmentConfigs. The structure of the local context is defined by the author and information is placed in specific locations by name. This model works extremely well and provides necessary functionality.

So why limit the environment external data references to EnvironmentConfigs? Why not allow the environment specification to include references to any existing Kubernetes object, which can be retrieved by the same mechanism that resolves EnvironmentConfigs today? If an author wants to retrieve data from an existing XR or MR or ConfigMap or Secret they could specify the G/V/K information required to find the object along with a name or label selector, and a path within the object to the data they want, and that information can be populated in the environment in the specified location.

From the author's perspective this would let me put all of my external data references in one place, and they can be retrieved dynamically at runtime as needed if they exist, or they can eventually reconcile as they become available.

I think one of the overarching goals needs to be keeping the user experience as simple as possible, and requiring a specific resource definition for every reference feels like it is adding complexity for the user and making the composition more difficult to develop and maintain.

Copy link
Member
@ytsarev ytsarev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we craft some Composition examples of using the proposed referencing mechanism? It would closer illustrate the projected DevEx, how it simplifies patterns like bi-directional patching etc

objects:
- apiVersion: ec2.aws.crossplane.io/v1alpha1
kind: VPC
matchingLabels:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if multiple objects are selected with labels? Reminds me of #3981 when we thought about similar quite late in the implementation

@haarchri
Copy link
Contributor
haarchri commented Aug 2, 2023

@ytsarev i added a few examples ideas here: https://github.com/haarchri/crossplane-generic-references/tree/main but with raw managed resources - because i guess in compositions you can use the patches for references...

Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
@pedjak
Copy link
Contributor Author
pedjak commented Aug 4, 2023

I have pushed the second draft. It is heavily reworked, including the received feedback.

Copy link
Contributor
@bobh66 bobh66 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the purpose of the Referable resource if the environment can retrieve all of the required references? What scenarios would use Referable instead of environment?

@pedjak
Copy link
Contributor Author
pedjak commented Aug 4, 2023

I don't understand the purpose of the Referable resource if the environment can retrieve all of the required references? What scenarios would use Referable instead of environment?

Only if managed resource needs to refer a value in resource owned by another provider and RBAC rules cannot be changed, but users do not want to use compositions.

I consider now Referable type a very optional thing, and perhaps could be dropped fully out of the proposal if we can agree that changing RBAC is ok for users.

# Generic Resource References

* Owner: Predrag Knezevic (@pedjak)
* Reviewers: Crossplane Maintainers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's nominate two specific Crossplane maintainers to make it clear who we expect to approve this design.

Copy link
Contributor Author
@pedjak pedjak Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crossplane/crossplane-maintainers please raise your hand :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Count me in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bobh66

* Patch Task's `spec.forProvider.sourceLocationArn` field with the composite
value

The proposed solution has the following drawbacks:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making sure I understand: by "the proposed solution" do you mean the pattern you describe above that requires using a Composition?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rephrase it as "this solution" so it's clear what it is referring to.

are cases where one would like to use and refer to a resource not bound by a
composite instance.

__**NOTE**__: [a prior design exists on this topic](https://github.com/crossplane/crossplane/pull/2385).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll recap my understanding of why we want generic cross-resource references here, since it's relevant to what @muvaf wrote above. I touched on some of this at #1770 (comment).

The current spec.forProvider.fooRef implementation:

  1. Requires all possible references to be manually identified and declared at build time.
  2. Is inflexible - users can only reference types the provider developers declared, in the ways they declared.
  3. Requires the referring provider to import any types they refer to.

3 is of particular interest for provider families, because it creates a potential version dependency between two providers in a family. For example if provider-aws-eks v0.42.0 is compiled against the types from provider-aws-ec2 v0.42.0, there's a chance that installing provider-aws-ec2 v0.50.0 will break references. It would be ideal if there was no version coupling between providers within a family.


// Policies for referencing.
// +optional
Policy *Policy `json:"policy,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this design, but FYI the presence of this field is a bit of a problem - see crossplane/crossplane-runtime#440.

forProvider:
.
.
sourceLocationArnRef:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that provider developers still need to explicitly identify and declare what fields can be set by a reference at build time? For example here the developer would need to identify that sourceLocationArn could reference another type, and thus make sure sourceLocationArnRef existed.

Copy link
Contributor Author
@pedjak pedjak Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that provider developers still need to explicitly identify and declare what fields can be set by a reference at build time? For example here the developer would need to identify that sourceLocationArn could reference another type, and thus make sure sourceLocationArnRef existed.

There is no change from the current user experience. The only change is that some ref fields can take more than just name to refer the external objects.

My understanding so far (and chatting with @muvaf) is that we have automated that with Upjet, but I could be ofcourse wrong here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding so far (and chatting with @muvaf) is that we have automated that with Upjet, but I could be ofcourse wrong here.

I thought we'd automated generating the code for references, but not automated the process of identifying which fields can be set using a reference. It's possible I'm wrong too though - CC @ulucinar and @muvaf.

Either way though, I suspect only being able to use references for certain fields will be too limiting. If a field can reference an arbitrary type, how would we know at development which fields should be able to be set by a reference and which can't? Do we think that we'll be able to predict all the fields users might want to derive using a reference at provider development time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way though, I suspect only being able to use references for certain fields will be too limiting. If a field can reference an arbitrary type, how would we know at development which fields should be able to be set by a reference and which can't? Do we think that we'll be able to predict all the fields users might want to derive using a reference at provider development time?

My understanding is that providers generated with the help of Upjet have mapped all fields that can reference external values, correct @ulucinar? These fields are all visible in MR schemas, hence easy discoverable for consumers. Other, non-upjet based providers needs to references in their MR schema in some way as well.

@haarchri, how is your experience? Did you stumble across some fields that should be referable, but they are not according to MR schema?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alper can correct me if I am wrong, but AFAIK, upjet infers some of those references by parsing their documentation and the examples there but not all. For unidentified ones, it is still a manual process to configure them.

Copy link
Contributor
@ulucinar ulucinar Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct:

My understanding is that providers generated with the help of Upjet have mapped all fields that can reference external values, correct @ulucinar?

We attempt to deduce the reference parameters from the examples provided in the Terraform docs and this does not result in the generation of all reference parameters. For instance, none of the provided example Terraform configurations could be utilizing a possible reference. We do not attempt to parse the argument documentation for this purpose, only the provided HCL configurations are used.

Thus, we have some automation for determining the reference parameters themselves on top of the reference implementation generator in angryjet. But, it's not 100% reliable, optional (an upjet-based provider's maintainer can opt out not to generate the reference parameters) and to the best of my knowledge, is only applicable for upjet-based providers.

@negz, are you suggesting a referencing scheme where, at runtime, we can declare a reference whose source & target are arbitrary (no a priori knowledge about who can reference what), so that at build time, we don't need to know anything about the references?

My understanding is that the proposed approach here makes the reference source (where the data will be copied from) dynamic (GVK+name+fieldpath) but the reference target (where the data will be copied into) is still static, i.e., you need to know the target at build time.

Finally, providers need to be upgraded to use the new version of crossplane-runtime
and for each type, the `ResolveReference` function needs to be regenerated.

## Alternatives Considered
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #1770 (comment) I share the concern that generic references grows to be something like P&T Composition.

Also, per #1770 (comment) I'm pretty confident that today's hardcoded style of references isn't going to scale. It's too manual to support, too inflexible for users, and creates a coupling between providers when one provider references a type defined by another.

I could be convinced that the answer is to just not have MR-level references at all if we can find a clean way to address this at the Composition level. I would expect such an approach to be controversial though. I suspect there's a not-insignificant amount of folks who don't use Composition today who would be forced to if Composition was the only way to derive one

* Providers implement the support for it.
* RBAC permissions need to be relaxed, if provider `A` needs to refer values from
instances owned by provider `B`. In case of provider families, each provider has
enough rights to access all resources within the family, requiring no changes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if enterprise users may ask for fine-granular access policies in the future. Currently, access is mutually granted to the (service accounts of the) providers A & B, if A & B are in the same family, even if their MRs are not supposed to refer to each other. This's been a natural evolution from the monoliths.


### Referable Type

If adding a number of RBAC rules is not an option for users, cross-provider
Copy link
Contributor
@ulucinar ulucinar Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly:

  • There exists a cross-provider resource reference A->B (a resource in provider A references a resource in provider B)
  • Because it's cross-provider, the Crossplane RBAC manager cannot install the rules that will allow A's service account to read B's resources
  • The user is not willing to add the RBAC rules herself
  • Instead provisions a Referable object
  • The Referable controller is running as a core Crossplane controller and has read access to B's resources: Do we assume here source GVK will be a managed resource?
  • Referable controller exposes the extracted value in status or reports an error if the extraction fails for some reason
  • All installed provider's service accounts have read access to all the Referable objects in the cluster
  • Provider A copies the data from the Referable object via a GenericReference

So this will allow data transfer across all MRs in the cluster. Do I understand this correctly?

I assume the suggestion is to have this controller running as part of the core Crossplane because in the implementation section, it's mentioned under the core Crossplane. Could the source provider (provider B in the above discussion) be publishing the data for improved security? But still, I wonder if some enterprise users may not like this scheme if I understand it correctly if we implicitly enable such transfers. What do you folks think?

* Support for `Referable` type requires new controller on Crossplane side
* Provider RBAC rules should be extended to include read permission
for `Referable` type
* Providers needs to support the new reference mechanism on their side
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing this: How can custom transformation/extraction logic on the data path from source to target be handled? We are preserving the reference.ResolutionRequest.Extract, right? This proposal is mainly for making the reference target dynamic and rest of the data path is (mostly) untouched?

@bobh66
Copy link
Contributor
bobh66 commented Aug 9, 2023

It seems like the two implementations described by this proposal are different enough that they should probably be addressed separately.

@pedjak
Copy link
Contributor Author
pedjak commented Aug 9, 2023

It seems like the two implementations described by this proposal are different enough that they should probably be addressed separately.

They both talk about the same problem, but propose two different paths for solving it: (1) introducing the change in MR schema, so that a reference field can point to an arbitrary type and the path within it, (2) adding values from an arbitrary k8s resource to composition environments and using an appropriate p&t strategy to set the right field on a MR.

These two paths are not mutually exclusive, they could coexist if needed, but the question is (as you mentioned earlier) what makes more sense to implement, i.e. what users really want. If (1) is not needed, then we could drop it from the proposal. Or the opposite. If we go only with (2), then we could even think about removing all reference fields from MR schemas, given that we can set references through compositions?

Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
Copy link
Contributor
@bobh66 bobh66 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I read through this again it strikes me as being three different approaches to several related problems, and I'm not entirely sure that they should all be combined in one design.

The MR-specific generic reference/selector solution is coming at the problem from the perspective of the MR provider, and seems like it is specific to the scenarios faced by MR designers/implementers.

The Referable and environmentsolutions approach the same set of problems from the Composition author's perspective, which makes more sense to me intuitively, but I can see both perspectives. These two implementations seem redundant and I prefer the environment change from a user-experience perspective.

Is the goal of this document just to lay out the different design possibilities? Or is there an implication that some or all of them will be implemented?

It's not clear by the end of the document what the final design plan is, but maybe there doesn't need to be one?

* Patch Task's `spec.forProvider.sourceLocationArn` field with the composite
value

The proposed solution has the following drawbacks:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rephrase it as "this solution" so it's clear what it is referring to.

@pedjak
Copy link
Contributor Author
pedjak commented Aug 30, 2023

It's not clear by the end of the document what the final design plan is, but maybe there doesn't need to be one?

@bobh66 after taking to @phisco and @turkenh it looks like that the majority of us are in favor of taking environment path. Interestingly enough, it turned out we can refer a value from a resource not being part of the composition even without declaring observe only duplicate, if we follow this pattern:

Field foo of MRA needs to be set to the value of field bar of MRB

  • Create CompositionB declaring MRB and EnvironmentConfig containing bar: value entry
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: composition-b
spec:
  resources:
      - name: MRB
        base:
          apiVersion: acme.com/v1beta1
          kind: MRB
          spec:
            forProvider:
              region: "us-east-2"
        patches:
          - type: ToEnvironmentFieldPath
            fromFieldPath: metadata.annotations[crossplane.io/external-name]
            toFieldPath: bar
      - name: Env
        base:
           apiVersion: apiextensions.crossplane.io/v1alpha1
           kind: EnvironmentConfig
           metadata:
             labels:
                env: b
           data:
        patches:
          - type: FromEnvironmentFieldPath
            fromFieldPath: bar
            toFieldPath: data[bar]
  • Create CompositionA declaring MRA and using EnvironmentConfig env-b to patch foo field
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: composition-a
spec:
  environment:
    environmentConfigs:
      - type: Selector
        selector:
          matchLabels:
            env: b
  resources:
    - name: MRA
      base:
        apiVersion: acme.com/v1beta1
        kind: MRA
        spec:
          forProvider:
            region: "us-east-2"
      patches:
        - type: FromEnvironmentFieldPath
          fromFieldPath: bar
          toFieldPath: spec.foo

IMHO, this still looks a bit more complex than it should be:

  • it is just my feeling, but given that we typically want to use values initialized by providers, env config cannot contain the right key within the first reconcile loop. Hence, consumption of that value in another composition would be postponed and successful reconcile would probably take longer than referring that value directly in composition-a, as proposed in this design document.
  • composition-a can refer the created env config only via labels, because the names are generated by crossplane. Thus, each generated env config should posses a unique label

Further chatting with @haarchri revealed that very often users needs to create singleton resources representing network, firewalls and similar and doing that via composition is a simple overkill, because exposing that through claims (and creating API around it) is not interesting for developers. Developers do want to create database claims, but network is created by infra teams just once, and relevant values are injected into other resources when needed.

I will restructure the proposal and mark the changes to environment as the proposed implementation, and move the MR fields changes to the considered alternatives. Happy to get feedback on this direction from others.

@bobh66
Copy link
Contributor
bobh66 commented Aug 30, 2023

Interestingly enough, it turned out we can refer a value from a resource not being part of the composition even without declaring observe only duplicate, if we follow this pattern:

This is the pattern we are using today as a workaround - we create an EnvironmentConfig for every Composite that we need to export data from and then import the data into other Composites by label matching on the EnvironmentConfig.

It is definitely extra work, and requires the data to be explicitly "exported" but it is functional.

I think the proposal to reference arbitrary XRs/MRs from the environment section is a good refinement that will make this process much easier and more intuitive.

@phisco
Copy link
Contributor
phisco commented Aug 30, 2023

Thanks @pedjak, I agree we should investigate into that if that's a common pattern.

A few thoughts/questions.

My only concern is that this would let Compositions read from arbitrary MRs, effectively growing the reach of Compositions and therefore potentially of Composite resources, e.g. what if we end up allowing parametrized references similar to what we do with labels selectors for environmentConfigs atm? Could that potentially result in a claim user being able to read from arbitrary resources? Should we then clearly state that parametrized references are not allowed and should not be implemented for such reasons.

Not saying we should not go down this path, but I feel it's worth discussing it in the proposal.

Then, would it be better, from a UX point of view, to have that at the EnvironmentConfig level or at Composition level, as per your current proposal? The latter feels better for discoverability, but the former feels more encapsulated and would allow to avoid adding one more field to the already pretty complex Composition resource. It wouldn't change much w.r.t. the RBAC issue above, as one could still define an EnvironmentConfig reading from whatever MR they want.

@turkenh
Copy link
Member
turkenh commented Sep 1, 2023

I will restructure the proposal and mark the changes to environment as the proposed implementation, and move the MR fields changes to the considered alternatives. Happy to get feedback on this direction from others.

@pedjak, my biggest concern is we are losing track of the actual problem we’re trying to solve with this proposal.
If we check the original issue that the proposal aims to fix, it is mainly about MR level referencing without composition involved, and we agreed not to invest in that since we didn't see a strong enough signal from the community which could motivate us building a not-so-straightforward solution.

For more context; in our discussions with @phisco and @pedjak, we have identified the following use cases and came up with the following comparison table:

Use Case \ Feature Environment Config Observe Only Resources Generic Cross Resource References
(This Proposal)
Share between Compositions
Share between composed resources in a Composition
Get info from existing external resources into a Composition
Share between standalone MRs (No Composition)
Get info from an arbitrary k8s object into a Composition

The only use case covered by only this proposal is sharing between standalone MRs with no composition involved. As mentioned above, we haven't seen a strong enough signal so far that this is something that we should cover at the cost of additional complexity. There are also some foundational questions with this, like how much functionality we should have at the MR level, which should rather be just building blocks.

So, I suggest holding off iterating on this proposal. We should instead create separate issues/proposals for improving the experience around the existing functionality, e.g. Environment Configs.

@pedjak
Copy link
Contributor Author
pedjak commented Sep 7, 2023

So, I suggest holding off iterating on this proposal. We should instead create separate issues/proposals for improving the experience around the existing functionality, e.g. Environment Configs.

Sure @turkenh - I have created #4583 as the followup, let's continue the discussion overthere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Generic Cross-Resource References