Crossplane Provider Mechanics

The way a provider has to be implemented is still (as of Crossplane v1.19) rather not obvious.

Crossplane does have a simple provider development guide.

Basically, one has to implement 2 interfaces in Go: managed.ExternalConnecter and managed.ExternalClient.

There is some more boilerplate required to set it up correctly, but not relevant in this page.

The managed.ExternalConnecter interface is meant as an entrypoint for every reconciliation. It’s the step where the runtime fetches some credentials to connect to an external cloud API. The return value of ExternalConnector.Connect() is an instance of managed.ExternalClient itself.

managed.ExternalClient features the 4 basic CRUD methods: Create(), Observe(), Update(), Delete(). By implementing Connect and these 4 CRUD methods everything should be in place to get started for a managed CRD.

What is not comprehensibly explained is how the reconciliation actually works and that can have a significant impact to the implementation. From the GoDocs it’s not clear how these methods actually have to be implemented and how they’re used (how often, in what order).

So this is the basic order of the reconciliation performed by crossplane-runtime:

  1. Controller attempts to reconcile the resource (Reconcile() from controller-runtime library)

  2. Controller calls Connect() from the managed.ExternalConnecter interface. Implementers are expected to create some client to interact with the external cloud API.

  3. Controller calls Observe() from the managed.ExternalClient interface. The result determines the next call:

    • If resource doesn’t exist, Create() is called.

    • If resource does exist but doesn’t match desired state, Update() is called.

    • If resource does exist and matches desired state, no method is called anymore.

  4. Controller does another reconciliation (start from top) even if Create() or Update() returned no error.

For deletions, the order is slightly different:

  1. Controller attempts to reconcile the resource (Reconcile() from controller-runtime library) This is possible thanks to the finalizer that the crossplane runtime manages.

  2. Controller calls Connect() from the managed.ExternalConnecter interface.

  3. Controller calls Observe() from the managed.ExternalClient interface. The result determines the next call:

    • If resource still exists, Delete() is called.

    • If resource doesn’t exist, no method is called anymore.

  4. Controller does another reconciliation (start from top) even if Delete() returned no error.

After Create(), Update() and Delete() calls, the controller does another reconciliation of the resource. This behavior is arguably unnecessary for synchronous operations, but is intended for cases where the cloud API manages resources asynchronously in the background after issuing requests.

Status updates are only allowed from within the Observe() call! This can make it more difficult in certain scenarios where critical information is only available after immediate creation or update of an external resource.