Skip to content

Feature Request: Support conditional field inclusion in Resource templates (field-level includeWhen) #928

@Tomer9000

Description

@Tomer9000

Feature Description

Issue Description:

Is your feature request related to a problem? Please describe.
Currently, when defining a ResourceGraphDefinition, handling optional fields within a resource template is verbose, error-prone, and in some cases, functionally impossible.

If a field in the schema.spec is optional (e.g., lifecycleRules or policy), we must currently use complex CEL ternary expressions to conditionally include it.

The Critical Issue (Validation Failures):
The standard workaround uses a ternary operator to return a "default" value when the field is missing:

# Current workaround attempts to pass an empty string if the property is missing
policy: '${has(schema.spec.policy) ? schema.spec.policy : ""}'

However, many Kubernetes controllers and CRDs do not accept empty strings or null values for specific fields.

  • Example: The AWS ACK S3Bucket controller validates the policy field. If I pass an empty string ("") because the user didn't provide a policy, the underlying CRD validation fails (e.g., InvalidPolicy: Policy cannot be empty).
  • Result: The resource cannot be created.

There is currently no clean way in kro to completely omit a field key from the generated manifest based on a condition. We are forced to generate the key with an invalid "empty" value.

Describe the solution you'd like
I propose introducing a dedicated syntax or directive for conditional field inclusion within the template section. This would allow defining a condition (CEL expression) that, if true, includes the field. If false, the field key is completely omitted from the generated YAML.

Proposed Syntax (Conceptual):

resources:
  - id: bucket
    template:
      apiVersion: s3.services.k8s.aws/v1alpha1
      kind: Bucket
      spec:
        name: ${schema.spec.bucketName}
        
        # Proposed: Only generate the "policy" key if the schema has it.
        # Otherwise, the "policy" key does not exist in the output manifest.
        policy:
          $kro.includeWhen: '${has(schema.spec.policy)}' 
          value: '${schema.spec.policy}'

        # Example for complex objects
        lifecycle:
          $kro.includeWhen: '${has(schema.spec.lifecycleRules)}'
          value: 
            rules: '${schema.spec.lifecycleRules}'

Describe alternatives you've considered

  1. Ternary Operators: ${has(x) ? x : ""}. As mentioned, this fails for CRDs that validate format (like IAM policies, ARNs, or Enums) and reject empty strings.
  2. Complex JSON Construction: Writing raw JSON strings inside the CEL expression to reconstruct the entire parent object. This is extremely difficult to read and maintain.

Additional Context
In the attached module (S3 Bucket), fields like policy are optional strings. If the user does not provide a policy, we need the generated Bucket resource to look like this:

spec:
  name: my-bucket
  # policy key is missing entirely

Instead of this (which causes errors):

spec:
  name: my-bucket
  policy: "" 

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureCategorizes issue or PR as related to a new feature.needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions