crossplane-runtime
crossplane-runtime copied to clipboard
Webhook implementation of resource.ExternalClient
What problem are you facing?
Currently folks wishing to add support for a new managed resource to Crossplane must write Go. Specifically, they must author a type that satisfies managed.ExternalClient in order to CRUD external resources, and instantiate a managed.Reconciler that uses said managed.ExternalClient.
How could Crossplane help solve your problem?
It should be possible to model the parameters and results of the managed.ExternalClient methods as a JSON REST webhook. If Crossplane supported an ExternalClient-to-webhook adaptor folks would be able to implement managed resource CRUD logic in their language of choice behind the webhook.
I think the challenging part of this feature is how to get the REST endpoints registered so that they are called by the controller. I imagine that we'd have a CRD like ExternalClientRegistration that will allow you to specify which managed resource CRs you'd like to reconcile and what URLs that each of the ExternalClient methods should call.
We'd probably create an ExternalClient on-the-fly with functions designated to call the URL endpoints given in the ExternalClientRegistration.
There is also the fact that there has to be an actual manager up and running to register that controller. Maybe crossplane pod itself could watch ExternalClientRegistration instances and do the necessary stuff.
I'd also question whether we should do this at all. In the best scenario, here is what the developer will do to implement a managed resource reconciler:
- An HTTP server that will take actions against the cloud provider API with the information it gets from endpoints.
- That HTTP server will have to implement an authorization path itself; fetch the
Providerobject, read secret and authorize to the cloud API. Note that it will have to do this for every call since the calls are completely separate as opposed to current managed reconciler where you auth once and it's used in the subsequent calls to cloud provider API. - The conversion methods from Crossplane struct to provider API struct. This has to be done in either case.
The advantage is that they can write this HTTP server in any language they want. However, I feel like this is more important when you develop an application rather than a provider stack. Maybe early prototypes of the provider stack would be in this webhook form but eventually they'd write a Go version (assuming mid-sized cloud provider or one of big 4).
So, I'm not sure. Maybe there are scenarios where we expand the definition of managed resource and the audience isn't limited to providers anymore, then it'd make more sense to me.
I mostly agree with your points. Some notes:
The conversion methods from Crossplane struct to provider API struct. This has to be done in either case.
I don't think we'd need to write conversion methods. I suspect we wouldn't even need to write Go API types. I believe it would be possible to write an adaptor from *unstructured.Unstructured to resource.Managed using the new fieldpath.Paved type. I believe we could then effectively encode all or part of the API type as JSON and pass it to the webhook.
That HTTP server will have to implement an authorization path itself; fetch the Provider object, read secret and authorize to the cloud API. Note that it will have to do this for every call since the calls are completely separate as opposed to current managed reconciler where you auth once and it's used in the subsequent calls to cloud provider API.
Currently each reconcile fetches the credentials and then uses them to form a managed.ExternalClient. One option would be to (presuming we connected to our webhook via mTLS or similar) fetch these credentials from a Crossplane Provider kind as usual, then include them in each webhook request.
API wise I'd imagine something like the below. We may want to use a different kind of request/response for each operation (e.g. Observe, Create, etc) or have the request subobject have different shapes depending on what kind of request we're dealing with.
{
"apiVersion": "webhook.crossplane.io/v1alpha1",
"kind": "ExternalOperationRequest",
"uid": "uid-of-this-request",
"request": {
"apiVersion": "database.example.org/v1alpha2",
"kind": "FancyDBInstance",
"name": "kubernetes-name-of-the-object",
"external-name": "external-name-of-the-object",
"credentials": { "...": "..." },
"operation": "Create",
"forProvider": { "json-encoded": "for-provider-substruct" }
}
}
{
"apiVersion": "webhook.crossplane.io/v1alpha1",
"kind": "ExternalOperationResponse",
"uid": "uid-of-the-request-that-triggered-this-response",
"response": {
"operation": "Create",
"external-name": "external-name-of-the-object",
"forProvider": { "json-encoded": "late-initialized-for-provider-substruct" },
"atProvider": { "json-encoded": "at-provider-substruct" },
"connection": { "json-encoeded": "connection-details"},
"error": ""
}
}
Just found this old issue and realized it has a lot of similarities to https://github.com/hasheddan/crisscross
@hasheddan Yeah, I had meant to cross link this issue with Crisscross the other day but couldn't find it. From what I understand Crisscross basically solves this issue.
@negz yep! However, I also imagine crisscross doing things like "read-only" loops to send connection details to alternative locations, other day 2 operations, etc.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@hasheddan @negz @muvaf @jbw976 is there an official method to do this now? I mean literally a CRUD provider?
Crossplane does not currently have enough maintainers to address every issue and pull request. This issue has been automatically marked as stale because it has had no activity in the last 90 days. It will be closed in 14 days if no further activity occurs. Leaving a comment starting with /fresh will mark this issue as not stale.