What problem are you facing?
In Crossplane API Patterns doc, high fidelity talks about exact representation of the resource in its CRD as much as possible. The assumption is that cloud provider SDK would have one struct with properties and API calls to manipulate that representation in the cloud provider. However, in cases where the main object is very lean and properties are set/get with different API calls and objects, this assumption leads to excessive amount of CRDs to be implemented, resulting in too much effort to implement them and bad UX since you have to deal with a lot of resources.
Let's take the case of IAM. In AWS console, you see 5 main objects:
Group
, User
, Policy
, Role
and Identity Provider
.
A usual workflow is that you create a Role
, create a few Policy
(or use ones that are built-in) then attach them to Role
. You'd add tags to Role
or do other operations on it in its properties tab. If you'd like to do that in Crossplane, you deal with 3 CRs IAMRole
, IAMRolePolicyAttachment
and IAMPolicy
(not implemented yet). In fact, there are quite a few actions like policy attachment that would deem a separate CRD but, in fact, is merely an action on the resource. Basically, the SDK is implemented in a way that you don't actually have a source of truth as object that you can modify/update for changes but a bunch of calls that you set/get those properties. It's not all of the properties, there are properties on the main struct that you can play with but in most of the cases, especially when two resources will have relation like attachment, you have other structs and calls that do the operation and our assumption about what constitutes a CRD causes us to implement a CRD for each of those actions.
When you look at other providers, such as GCP, it's closer to our assumption. You have a ServiceAccount endpoint and you'd construct a CRD for that, whose calls in the SDK would cover most of the cases. Though you have separate calls for resource relations here, too, like setIamPolicy
. My inclination is that this is more verbose in AWS compared to GCP; the main structs are leaner in AWS where you do most of the operations via dedicated API calls who have their own struct.
There are two problems with all this:
- We're drawn to implement too many separate resources because of how their SDK is shaped. @hasheddan was saying that we'd need 27 different resources to mirror IAM group.
- User experience suffers due to too many resources. When you look at their management consoles, they are not designed in a bottom-up fashion like we're trying to do; the consoles are decoupled and represents higher level objects together with all actions that you can do with them.
As you can see above in the image, you don't see a resource IAMRolePolicyAttachment
in the console's main widget; what you have is a tab in Roles
page where you select and attach. In my opinion, this is a better user experience; as a user I don't care how you attach it or whether you need a separate call/struct for this, I would like to do it in the Role
's own page so that I know what I'm doing, in our case I'd like to do it under spec
of IAMRole
by adding a reference to the Policy
to the existing list of attached policies.
The same problem exists in Terraform, too, in some cases. For IAM, they seem to have implemented those relational resources. However, if you look at S3 bucket, they decided to embed all these little property structs into the main S3 Bucket resource, like logging
, cors rule
etc.. It seems like they either drew a line like if it is inter-resource relation, then a separate resoruce but if it belongs solely on one resource then it is just a property
or this is just an inconsistency due to historical reasons.
How could Crossplane help solve your problem?
I believe we need some level of sacrifice here in terms of mirroring the API's struct and the main reason is that those structs are not meant to be interacted in a level as high as user like CRD, as we can see from how they designed their consoles. Terraform seems to have made the decision to implement inter-resource relation resources but keep the property resources on the object itself. In my opinion, we should definitely need to embed those properties to the main CRD just like they do but we can go a step further and say let's embed relational resources, too, when one side of the relation is not affected by the relation. For example, when you attach a Policy
to a Role
, there is no change on the Policy
, so we can actually have spec.attachedPoliciesRefs
under Role
where the user can specify what Policy
s they would like to attach to that Role
.
I know that we don't feel good about designing the CRD in a way that is not fully mirroring the SDK but that's actually holding us back in some major way like verbose UX and excessive number of CRDs (and effort to implement each). What I propose is that we can try to mirror the UX that they provide to their end users instead of mirroring what they provide to us through their SDK. In IAM
group, we'd have 5 different CRDs only as they show in the console and implement the separate actions as a field on the spec
instead of implementing a new CRD for each action.