operator-framework/helm-operator-plugins

Multiple values mapper with fixed execution order

SimonBaeumer opened this issue · 5 comments

Currently it is possible to add one values mapper via WithValueMapper to the helm reconciler.
For our use-case it is necessary to overwrite values from different sources with different priorities.
Example:

  1. Default values configuration
  2. Values applied derived from CRD
  3. Values overwritten from environment
  4. Values overwritten based on custom-logic
  5. [...]

A possible implementation could look like this:

r, err := reconciler.New(
    reconciler.WithChart(*chart),
    reconciler.WithValueMapper(values.PlaceholderValuesMapper{}, values.CRValuesMapper{})
)

The order would be determined by the slice index of each mapper.

If multiple mappers could run in a fixed order/priority it would be really helpful.
If this feature may gather interest here I would prepare a PR and upstream it. 👍

Hey Simon!

Without changing the existing WithValueMapper option, you could accomplish this on your own by defining a new implementation of values.Mapper or values.MapperFunc. I actually took some small inspiration from Go's http.Handler type, which uses a very similar pattern for building http middlewares.

For example:

func multiMapper(mappers ...values.Mapper) values.Mapper {
	return values.MapperFunc(func(in chartutil.Values) (chartutil.Values) {
		out := in
		for _, m := range mappers {
			out = m.Map(out)
		}
		return out
	})
}

func main() {
	r, err := reconciler.New(
	    reconciler.WithChart(*chart),
	    reconciler.WithValueMapper(multiMapper(values.PlaceholderValuesMapper{}, values.CRValuesMapper{}))
	)
}
  1. Default values configuration

I'm also curious about this use case. It sounds like you're setting up what amounts to defaults for the custom object's spec, which then pass through the various follow-on mappers to eventually get mapped to the helm chart's values.

I (perhaps naively) only ever considered needing to map the actual values provided in the custom object's spec because I figured anything left undefined after passing through the mapper would get the chart's default value. Do you have an example where that wouldn't result in the desired final values used to render the chart templates?

Relatedly, are you aware that you can set defaults for custom object values via the CRD validation schema?

Hey Simon!

Without changing the existing WithValueMapper option, you could accomplish this on your own by defining a new implementation of values.Mapper or values.MapperFunc. I actually took some small inspiration from Go's http.Handler type, which uses a very similar pattern for building http middlewares.

For example:

func multiMapper(mappers ...values.Mapper) values.Mapper {
	return values.MapperFunc(func(in chartutil.Values) (chartutil.Values) {
		out := in
		for _, m := range mappers {
			out = m.Map(out)
		}
		return out
	})
}

func main() {
	r, err := reconciler.New(
	    reconciler.WithChart(*chart),
	    reconciler.WithValueMapper(multiMapper(values.PlaceholderValuesMapper{}, values.CRValuesMapper{}))
	)
}

Thanks, that's a good idea. 👍

  1. Default values configuration

I'm also curious about this use case. It sounds like you're setting up what amounts to defaults for the custom object's spec, which then pass through the various follow-on mappers to eventually get mapped to the helm chart's values.

I (perhaps naively) only ever considered needing to map the actual values provided in the custom object's spec because I figured anything left undefined after passing through the mapper would get the chart's default value. Do you have an example where that wouldn't result in the desired final values used to render the chart templates?

Relatedly, are you aware that you can set defaults for custom object values via the CRD validation schema?

Yes, if something wasn't mapped it uses the charts default values.
In this case we need defaults for our development environment, resource requests for our services are >10GB which is not really necessary for local development.

The helm charts defaults are more like production defaults if a customer deploys it into their cluster to have ready-to-go setup.

This can be closed as @joelanford solution works.