garden-io / garden

Automation for Kubernetes development and testing. Spin up production-like environments for development, testing, and CI on demand. Use the same configuration and workflows at every step of the process. Speed up your builds and test runs via shared result caching
https://garden.io
Mozilla Public License 2.0
3.29k stars 267 forks source link

[FEATURE]: `elseif` branches in templating #5738

Open salotz opened 4 months ago

salotz commented 4 months ago

Feature Request

Background / Motivation

Currently you are limited to in all of the branching templating to only two branches. I.e. ternary operator and if-else.

For instance if I have the following:

group:
  $if: ${environment.name == 'local'}
  $then:
    - thinga
    - thingb
  $else:
    - thing c

And I want to now have two different branches in addition to the one for 'local' there isn't really a way to do that.

So I end up needing to do things like templating the item names e.g.:

group:
  $if: ${environment.name == 'local'}
  $then:
    - thinga
    - thingb
  $else:
    - thing${environment.name == 'staging' ? 'c' : 'd'}

Which you can see is just a band aid.

Whats worse is that I can't even use multiple if-else inside the same map/list due to how it expands those. E.g.

group:
  $if: ${environment.name == 'local'}
  $then:
    - thinga
    - thingb

  $if: ${environment.name == 'staging'}
  $then:
    - thingc
  $if: ${environment.name == 'prod'}
  $then:
    - thingd

Gives an error like:

Could not parse garden.yaml in directory file/path/garden.yaml as valid YAML: YAMLException: duplicated mapping key (219:15)

What should the user be able to do?

group:
  $if: ${environment.name == 'local'}
  $then:
    - thinga
    - thingb
  $elseif: ${environment.name == 'staging'}
  $then:
    - thingc
  $elseif: ${environment.name == 'prod'}
  $then:
    - thingd

If environment.name doesn't match this would be a templating error. If there is the trailing else it would be fine.

Why do they want to do this? What problem does it solve?

See above.

Suggested Implementation(s)

The obvious near term fix is to just add support for the elseif part.

In general I think that support for a pre-existing and powerful templating engine (like jsonnet) would be a more holistic approach to addressing non-essential garden functionality like this.

How important is this feature for you/your team?

Its not critical now, but as our stacks grow they increasingly grow ugly hacks and workarounds to this issue.

🌵 Not having this feature makes using Garden painful

salotz commented 1 month ago

Updated to include another attempt at a workaround that failed.

stefreak commented 4 weeks ago

@salotz absolutely, this is indeed an annoying problem. Thank you for the report!

At the moment we are parsing the document as YAML first, and then apply structural (like $if) and template strings (like ${environment.name}. This means that a syntax that requires duplicate $elseif conditions in the same object would mean implementing our own YAML parser and it would also mean that documents need to be parsed as ordered dictionaries, as the order of the statements matters.

We do have long-term plans internally how we can address this issue to make the configuration more expressive.

In the mean time, I have two other workarounds for you:

  1. Use environment-specific variables (See also https://docs.garden.io/reference/project-config#environments-.variables)

    environments:
    - name: local
     variables:
       group:
         - thinga
         - thingb
    
    - name: staging
     variables:
       group:
         - thingc
    
    - name: prod
     variables:
       group:
         - thingd
  2. Use object lookup syntax instead of if/else statements

    kind: Run
    type: exec
    name: cleanup
    spec:
    command: ${var.commands_by_platform[local.platform]}
    variables:
    commands_by_platform:
    windows: [".\cleanup_windows.bat"]
    linux: [".\cleanup_linux.sh"]
    macos: [".\cleanup_macos.sh"]

These workarounds are in no way optimal or equivalent to elseif branching, but due to the technical limitations of the current template system it's hard to address this problem. There might be lower hanging fruits to improving the situation, and we are definitely open for suggestions.

stefreak commented 4 weeks ago

Here is a proposal of an alternative syntax that is easier to implement with the current limitations of the YAML template system:

groups:
  $match: ${environment.name}
  $exact: # We could implement multiple matchers, e.g. exact, regex, etc
    prod:
      - thingd
    staging:
      - thingc
  $default: # Match could fail if you do not specify default and nothing matches
      - thinga
      - thingb
salotz commented 3 weeks ago

The match construct seems reasonable, at least in the current framework of templating.

salotz commented 3 weeks ago

Except wouldn't you have the same problem with the $exact key being duplicate when parsing the YAML? You would probably have to have some sort of cardinality in the key itself like a case number.

stefreak commented 3 weeks ago

If it only needs to match strings, then it can use object keys for matching, similar to the "object lookup" workaround only that we can have a default value.