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

Conditional Resources in Composition #2712

Closed
MisterMX opened this issue Nov 19, 2021 · 24 comments
Closed

Conditional Resources in Composition #2712

MisterMX opened this issue Nov 19, 2021 · 24 comments
Assignees
Labels

Comments

@MisterMX
Copy link
Contributor

What problem are you facing?

Currently, if you create a claim in Crossplane, a composition is selected and all defined resources are rendered using parameters from the claim. In most cases this works fine and without issues.

However, in some cases we only want to create a resource if a condition is met, i.e. a flag is set by the user or another resource is ready and provides specific info needed by the conditional resource, for example an AWS ARN.

How could Crossplane help solve your problem?

It would be great to specify conditions for a resource in a composition to be rendered.

These conditions should be optional and could be defined alongside patches and base. I'm imagining something like this:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: sample-composition
spec:
  resources:
    - name: sample-resource
      conditions:
        - type: matchValue
          fieldPath: spec.paramaters.samplePlag
          value: true
      base:
        # ...
      patches:
        # ...
@MisterMX MisterMX added the enhancement New feature or request label Nov 19, 2021
@Feggah
Copy link
Member

Feggah commented Nov 19, 2021

Hey @MisterMX , thanks for raising this issue!

I'm wondering if the current issue #2524 isn't what you are looking for. If so, I think you could input your use case there and close this issue 🙂

@negz
Copy link
Member

negz commented Nov 22, 2021

As @Feggah alluded to, this functionality is something we explicitly avoided building in favor of 'Custom Compositions' per #2524.

That said, I don't think we're fundamentally opposed to ever adding this functionality into the existing Composition type - we'd just need to make sure we felt good about the API and implementation. I like your proposed array of conditions at first glance from an API perspective. Some further thoughts:

  • Would we want to support conditional patches too? In a way we do today via optional patches, but I'm thinking of something more like 'patch an object from spec.foo to spec.forProvider.foo if spec.bar == true. Would we want to mirror the "conditions" API there? Perhaps this would be a new kind of patch policy (in addition to optional and required)?
  • We'd need to enforce named resource templates in the Composition to make this work; for backward compatibility when you omit spec.resources[N].name the Composition engine will try to match templates to an XR's composed resources based on their position in the XR's spec.resourceRefs array, which is fragile and would break if a condition changed such that a resource was now rendered that wasn't before.
  • Speaking of the above, would we delete the conditional managed resource if the XR was updated such that the conditions were no longer met?

Is using different compositions a feasible way to 'branch' here? e.g. One composition with the conditional resource and one without, then use a compositionSelector to select based on appropriate labels?

@negz negz added the proposal label Nov 22, 2021
@MisterMX
Copy link
Contributor Author

@Feggah thanks for your input. I'm unsure if #2524 might be overkill for our usecase.

Basically, we would want a resource to be NOT rendered until a condition is met. Like a no-op resource. It would still be part of the composition and listed in the XR.

@negz thanks for your considerations. We actually haven't thought about patches yet but it sounds very interesting.

Do you think we need to enforce named resources? (I personally see the name as required anyways). What if we leave a nil any kind of no-op entry until the resource is rendered? Would this break Crossplane?

@diranged
Copy link

Just chiming in here. I see Custom Compositions as a great feature down the road, but until its there, the current Compositions framework leaves much to be desired. The basic premise of conditional resources seems very sane to me within a composition.

In our case, we want to create a Bucket resource.. and that Bucket resource should take a Policy field that has a series of named policies. Based on the policy the user requests, we want to then create the appropriate BucketPolicy resource. Right now it seems there is no way to do this directly.. instead we basically need to expose to our end-users our Bucket compositions and then BucketPolicy compositions and tell them to mix-and-match the resources themselves.

@rytswd
Copy link

rytswd commented Jan 30, 2022

I have asked about the conditional resource creation support in Slack, and thought about this a bit more in detail.
Ref: https://crossplane.slack.com/archives/CEG3T90A1/p1643401499611699

I find there are a couple of use cases where this conditional resource creation would benefit the community.

1. Environment specific requirements

The original question I had was about how to disable creating EKS NodeGroup only for staging or development environment. Yes, patching a single NodeGroup resource is definitely possible, but if I were to have multiple separate NodeGroups (e.g. 1 being expensive production-ready node, and another being cheaper spot instance), I don't want to separate Composition definition just for that difference. If I had a control for disabling the creation of NodeGroup with some XR input, I could reuse the same Composition and tune it based on the environment.

So, as an example, I can do this today:

    - base:
        apiVersion: eks.aws.crossplane.io/v1alpha1
        kind: NodeGroup
        spec:
          # ...

      patches:
        - fromFieldPath: spec.parameters.deploymentMode
          toFieldPath: spec.forProvider.instanceTypes[0]
          transforms:
            - type: map
              map:
                production: m6g.large
                staging: t4g.large
                dev: t4g.medium

But that's "either m6g or t4g", and instead, I want production to have "both m6g and t4g", while staging and dev to have "only t4g". I was hoping to do something like

    - name: node-group-m6g
      base:
        apiVersion: eks.aws.crossplane.io/v1alpha1
        kind: NodeGroup
      # **********
      # Patch to disable this resource altogether if env is staging or dev 
      # **********
      # ...

    - name: node-group-t4g
      base:
        apiVersion: eks.aws.crossplane.io/v1alpha1
        kind: NodeGroup
      # ...

SIDE NOTE:
For this specific use case with EKS NodeGroup, you could actually set the NodeGroup's spec.forProvider.scalingConfig.minSize and spec.forProvider.scalingConfig.desiredSize to be 0, which effectively disables the node creation (NodeGroup is created still, though).

Another example I can think of is Subnet configuration - production could use multiple subnets, but environments such as development may not need the full redundancy. Just like the NodeGroup, it would be great if I can configure all the possible use cases in Composition, mainly aimed for production use cases, and disable any of those that do not need to be deployed in staging or dev env using XR input.

2. Dry run Composition update

I have been iterating on my Composition spec, and when adding an extra resource, there is no way to stop it to actually create the resource. Sometimes I just want to ensure that my Composition spec is correct, and when a Claim or XR is applied, I want to see if my spec was correctly rendered. This is similar to how I would use Terraform Plan to check the expected impact before applying, or use helm template before installing / upgrading.
I think of Composition similar to Terraform code or Helm Chart. But because of the nature of Crsossplane being a controller, I understand how it is difficult to provide the Terraform plan or helm template equivalent. Unlike those CLI tools, by the time Composition is handled by the controller, it's ready for resource creation. Because there is no way for Composition author to stop Crossplane from deploying, the only thing I can do is actually to create those cloud resources, and delete after testing.

If there was a way to disable the resource creation, that would make it much easier to adjust Composition spec, without worrying about breaking or introducing unnecessary resources.


I believe the condition field can support both of the use cases I described above. I personally find creationPolicy field, similar to deletionPolicy, would be easy to get my head around, with which the patching logic does not need to change.

An example of that would be:

    - name: new-node-group
      base:
        apiVersion: eks.aws.crossplane.io/v1alpha1
        kind: NodeGroup
        spec:
          forProvider:
            # ...
          creationPolicy: Create # Default, to be patched
          deletionPolicy: Delete # Default, to be patched
      patches:
        - fromFieldPath: spec.parameters.deploymentMode
          toFieldPath: spec.forProvider.instanceTypes[0]
          transforms:
            - type: map
              map:
                production: m6g.large
                staging: t4g.large
                dev: t4g.medium

        - fromFieldPath: spec.parameters.deploymentMode
          toFieldPath: spec.creationPolicy
          transforms:
            - type: map
              map:
                production: Disable # Do not create the cloud resource at all
                staging: DryRun     # Create managed resource, but do not update, only check whether it's sync'ed
                dev: Create         # Default, simply create the cloud resource

        - fromFieldPath: spec.parameters.deploymentMode
          toFieldPath: spec.deletionPolicy
          transforms:
            - type: map
              map:
                production: Orphan
                staging: Delete
                dev: Delete

With Custom Composition, this sort of handling should certainly be possible. But, IMHO, I feel adding another customisation layer is an overkill for achieving resource creation control. I personally see the deletionPolicy and creationPolicy are both universally useful, and thus should be considered to be added to Composition spec rather than Custom Composition proposal. As we already have the deletionPolicy in place, the addition of creationPolicy wouldn't require mindset change, and it seems natural to have. But if it's too much change to update all managed resources, I agree conditions is still a great option.

@shanproofpoint
Copy link

I also would like this as well. creating an entire composition violates DRY. I understand conditionals can get out of hand... but is there anyway we can limit it to just what was originally proposed here ? This feature I am surprised cross plane does not support. I also understand your arguments for avoiding compositions to devolve into a full fledged DSL. It is so critical for example if a gcp region does not have a particular machine type, we have to be able to conditionally use another one. or to conditionally reference or own a created base resource.

@James-Riordan
Copy link

This feature is absolutely necessary. It would instantly replace helm templates... Please for the love of god let me get rid of the moustaches

@github-actions
Copy link

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 7 days if no further activity occurs. Leaving a comment starting with /fresh will mark this issue as not stale.

@github-actions github-actions bot added the stale label Oct 11, 2022
@pcktdmp
Copy link

pcktdmp commented Oct 11, 2022

/fresh

@github-actions github-actions bot removed the stale label Oct 11, 2022
@github-actions
Copy link

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 7 days if no further activity occurs. Leaving a comment starting with /fresh will mark this issue as not stale.

@github-actions github-actions bot added the stale label Jan 10, 2023
@elohmrow
Copy link

/fresh

@github-actions github-actions bot removed the stale label Jan 10, 2023
@MaksymBilenko
Copy link

/fresh

@ibakshay
Copy link

ibakshay commented Jun 7, 2023

Is there any update on this topic?

@bobh66
Copy link
Contributor

bobh66 commented Jun 7, 2023

See the Composition Functions alpha feature in 1.11 and later. That is intended to address this need.

@negz
Copy link
Member

negz commented Oct 18, 2023

With Crossplane v1.14 about to ship we'll have Composition Functions in beta. Our current longer term thinking is that we'll eventually remove (or at least deprecate and freeze) support for native P&T, per the issue above. At that point whether Crossplane supports conditional resources is really up to what Functions you use to do Composition. If your Function(s) support conditionals, so does Crossplane. For example crossplane-contrib/function-patch-and-transform#14 proposes adding conditionals to the P&T function.

So, it's early, but I suspect we probably won't do anything in core Crossplane to enable this other than continue working on making Functions the future of Composition.

@stevendborrelli
Copy link
Contributor

I have a pull request open against function-patch-and-transform that uses the Common Expression Language (CEL) that is used with other parts of the K8s ecosystem (like CRD validation).

crossplane-contrib/function-patch-and-transform#26

Since multiple function steps can be run in a pipeline, and they update the partial desired state, these conditions can be use to build conditional logic for rendering resources.

@jcooklin
Copy link
Contributor

I have a pull request open against function-patch-and-transform that uses the Common Expression Language (CEL) that is used with other parts of the K8s ecosystem (like CRD validation).

crossplane-contrib/function-patch-and-transform#26

Since multiple function steps can be run in a pipeline, and they update the partial desired state, these conditions can be use to build conditional logic for rendering resources.

Building on @stevendborrelli's PR I added conditionals down to the desired resources. We wanted a single patch-and-transform function to be able to apply our global patchsets and the condition enables this by only running when the resource is being included.

@Ranger-X
Copy link

I workaround this "feature miss" with go-templating function:

              - step: create-pg-chat-db
                functionRef:
                  name: function-go-templating
                input:
                  apiVersion: gotemplating.fn.crossplane.io/v1beta1
                  kind: GoTemplate
                  source: Inline
                  inline:
                    template: |
                      {{ $enabled := (hasKey $.observed.composite.resource.spec "chatDBEnabled" | ternary $.observed.composite.resource.spec.chatDBEnabled false) }}
                      {{ if $enabled }}
                      {{ $resName := (printf "%s-chat" $.observed.composite.resource.spec.id) }}
                      apiVersion: postgresql.sql.crossplane.io/v1alpha1
                      kind: Database
                      metadata:
                        name: {{ $resName }}
                        annotations:
                          gotemplating.fn.crossplane.io/composition-resource-name: {{ $resName }}
                      spec:
                        forProvider:
                          owner: {{ (printf "%s-pg-user" $.observed.composite.resource.spec.id) }}
                          template: "{{ $.observed.composite.resource.spec.chatTemplate }}"
                          encoding: "UTF8"

                        providerConfigRef:
                          name: {{ .observed.composite.resource.spec.pgSuperuserProviderConfigName }}
                      {{ end }}

              - step: auto-detect-pg-db
                functionRef:
                  name: function-auto-ready

@negz
Copy link
Member

negz commented Jan 8, 2024

Building on @stevendborrelli's PR I added conditionals down to the desired resources. We wanted a single patch-and-transform function to be able to apply our global patchsets and the condition enables this by only running when the resource is being included.

@jcooklin How have you found this to be working out? Any issues?

@haarchri
Copy link
Contributor

haarchri commented Jan 8, 2024

@negz is here available https://github.com/stevendborrelli/function-conditional-patch-and-transform and working great

@negz
Copy link
Member

negz commented Jan 8, 2024

@haarchri Do you have a sense of how many different people/orgs have tried it?

I'm trying to get a sense of whether it's been well exercised enough to merge the feature upstream, into function-patch-and-transform.

@haarchri
Copy link
Contributor

haarchri commented Jan 8, 2024

On my side i running this in 2 orgs

@stevendborrelli
Copy link
Contributor

Can we close this issue? With functions we now have multiple ways to embed conditional logic into compositions, including https://github.com/crossplane-contrib/function-cel-filter.

@negz
Copy link
Member

negz commented Mar 4, 2024

Can we close this issue?

Yes, I think so. We now have the following options available:

You can also write your own function using the full expressiveness of a general purpose programming language using https://github.com/crossplane/function-template-go or https://github.com/crossplane/function-template-python.

Given that we're likely to deprecate native patch and transform compositions once functions are GA, I don't think we'll be making any major improvements (such as adding conditionals) to native P&T.

@negz negz closed this as completed Mar 4, 2024
@negz negz self-assigned this Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests