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

[Suggestion] Flexible Runtime Variable Management for Configurations #466

Open
rrmistry opened this issue Dec 23, 2024 · 3 comments
Open

Comments

@rrmistry
Copy link

rrmistry commented Dec 23, 2024

Problem Being Solved For

Currently, environment variables can only be assigned to values at the bundle level, not the module level. While it is possible to assign Timoni runtime tags to module-level values for setting them via environment variables, this process can be cumbersome and error-prone.

For example:

// bundle.cue
package bundle

bundle: {
	apiVersion: "v1alpha1"
	name:       string | *"my-timoni-app" @timoni(runtime:string:RELEASE)

	_env:                string | *"dev"                  @timoni(runtime:string:ENVIRONMENT_NAME)
	_namespace:          string | *"my-app"               @timoni(runtime:string:KUBERNETES_NAMESPACE)
	_timoniRegistryHost: string | *"mycompany.azurecr.io" @timoni(runtime:string:TIMONI_REGISTRY_HOST)

	instances: {
		"\(name)": {
			module: {
				url:     string | *"oci://\(_timoniRegistryHost)/timoni/module" @timoni(runtime:string:TIMONI_MODULE_REPOSITORY)
				version: string | *"latest"                                     @timoni(runtime:string:TIMONI_BUILD_TAG)
			}
			namespace: _namespace
			values:    #BundleValues
		}
	}
}

And corresponding values:

// values.cue
package bundle

#BundleValues: {
	frontend: {
		image: {
			repository: string @timoni(runtime:string:IMAGE_REPOSITORY_FRONTEND)
			tag:        string @timoni(runtime:string:IMAGE_TAG_FRONTEND)
		}
	}
	api: {
		image: {
			repository: string @timoni(runtime:string:IMAGE_REPOSITORY_API)
			tag:        string @timoni(runtime:string:IMAGE_TAG_API)
		}
	}
}

This approach requires developers to explicitly map each value to a unique environment variable, introducing complexity and increasing the risk of naming collisions.

Proposal

To simplify configuration management and expand its flexibility, Timoni should integrate a configuration management library like spf13/viper (or an equivalent). This enhancement would enable users to manage both module-level and bundle-level values effortlessly.

Key Benefits:

  • Enhanced Format Support: Ability to define defaults and read configurations from formats like JSON, YAML, TOML, HCL, envfiles, and Java properties.
  • Dynamic Configuration: Support for live watching and reloading of config files (optional).
  • Centralized Management: Read values from environment variables, command-line flags, remote config systems (e.g., etcd, Consul), and even buffers.
  • Reduced Boilerplate: Eliminate the need to tag each value explicitly, reducing setup time and improving maintainability.
  • No Collisions: Automatic management of environment variable namespaces to avoid conflicts.

This approach would not only simplify the developer experience but also unlock new possibilities, such as:

  • More dynamic configurations with live reloads.
  • Cleaner module definitions with reduced boilerplate.
  • Greater flexibility for different deployment environments.

By adopting a widely-used library like Viper, Timoni can deliver a powerful and developer-friendly configuration experience while maintaining compatibility with existing setups.

@salotz
Copy link

salotz commented Jan 8, 2025

I'm having trouble picturing what your proposal would look like. Can you provide an example?

Reduced Boilerplate: Eliminate the need to tag each value explicitly, reducing setup time and improving maintainability.

You need to tag them so timoni knows which Cue values it should inject vs. being handled by the normal Cue functionality.

No Collisions: Automatic management of environment variable namespaces to avoid conflicts.

This kind of makes sense. AFAIK you can't use Cue itself to manage the values in the annotations. I don't see how this would work though. You still need to have some identifier to map an outside value to the Cue config value.

Enhanced Format Support: Ability to define defaults and read configurations from formats like JSON, YAML, TOML, HCL, envfiles, and Java properties.

This is really the responsibility of Cue which is good. You can always set the runtime variable annotated values via normal Cue unification. This is what I do in the testing phase.

I can agree though that testing the actual runtime injection is a little annoying via the environment variables.

Dynamic Configuration: Support for live watching and reloading of config files (optional).

What would this enable in Timoni itself? You mean like applying values to the cluster? I don't know if this is something Timoni would actually use.

Centralized Management: Read values from environment variables, command-line flags, remote config systems (e.g., etcd, Consul), and even buffers.

I like this part!

@rrmistry rrmistry changed the title [Suggestion] Enhance Timoni Configuration with Flexible Runtime Variable Management [Suggestion] Flexible Runtime Variable Management for Configurations Jan 11, 2025
@rrmistry
Copy link
Author

@salotz , The proposal would look like this:

(Taking the example from the proposal message above)

# The key is leveraging the "spf13/viper" library to interpret "values" from various sources

# Environment variables feed values by naming convention (not by users maintaining it manually via CUE tags)
export TIMONI_MYAPP_VALUES_FRONTEND_IMAGE_REPOSITORY="quay.io/salotz/myapp-frontend"
export TIMONI_MYAPP_VALUES_FRONTEND_IMAGE_TAG="latest"

# Also additional values provided via databases (e.g. etcd, consul, vault, etc.)
timoni apply ... \
    --values="values.json" \
    --values="etcd://company.production.etcd:2379/" \
    --values="consul://myapp.consul:8500/"

# Alternatively we could introduce new CLI flags for different configuration source types
timoni apply ... \
    --values="values.toml" \
    --values-etcd="http://myapp.etcd:2379/" \
    --values-consul="http://myapp.consul:8500/"

The scope of the proposal is opens up a whole new world of possibilities far beyond environment variable tagging in CUE.

For an example of "spf13/viper" environment variable convention, see how it is used by Coder configurations (an adopter of spf13/viper).

The overall idea is something like this:

graph LR
    M[apply.go<br/>build.go] --> V
    B[bundle_apply.go<br/>bundle_build.go<br/>bundle_vet.go] --> V

    V[spf13/viper] --> |Produces the final values going into manifests|C[Cue]

    subgraph timoni
        M
        B
        V
    end

    E[Environment variables] -.-> V
    F["Config files (YAML, JSON, TOML)"] -.-> V
    D["Database (etcd, consul, vault, etc.)"] -.-> V
Loading

The focus is on the "values" part that is going into CUE.

@salotz , Is there a way to make this proposal more concrete?

@salotz
Copy link

salotz commented Jan 12, 2025

The reason I'm following on is I do think there is something interesting here. However, I feel there is some general confusion about the utility of Cue and how Timoni interacts with that.

Most of what you presented are features which would be added to the CUE lang tool chain itself and not Timoni. CUE is responsible for integrating configuration data from many different sources. This is similar to viper, but with a different set of supported inputs and with a general lack of network awareness.

timoni apply ...
--values="values.json"
--values="etcd://company.production.etcd:2379/"
--values="consul://myapp.consul:8500/"

The only thing Timoni does on top of CUE is add the runtime injection of config values which would not be used with the --values flags as you've shown. As you've written it these would be either cue features or some lower-level add-ons that timoni could leverage (that are completely hypothetical). Timoni is just forwarding its --values flags to CUE.

Where I think your proposal is interesting is specifically for runtime variable injection. Runtime value injection is something that the cue tool chain explicitly does not handle. The annotation syntax is specifically for supporting this kind of custom injection, and Timoni leverages it as it should be.

So I don't think you are going to replace things like:

repository: string @timoni(runtime:string:IMAGE_REPOSITORY_FRONTEND)

In your Timoni CUE.

However, what I think could be improved is the interpretation of IMAGE_REPOSITORY_FRONTEND which is where your proposal comes in.

Currently this annotated value can be set by the environment variable IMAGE_REPOSITORY_FRONTEND, or by using Kubernetes secrets. I personally only use the environment variables for testing and only use the K8s secrets for real deployments. That said to test this is kind of annoying.

Let me take an aside to demonstrate your point, for testing bundles that take runtime injected values. To test them I use environment variables which I have part of my project scripting. I do this while using a .env file with the values I want to use for testing:

  (set -o allexport && source .env && set +o allexport && \
          timoni bundle vet -f bundle.cue --runtime-from-env)

It would be very nice to be able to just do something this:

timoni bundle vet -f bundle.cue --runtime-url .env

This is where I think viper comes in since it could be the engine for reading the runtime settings.

I hope that made sense!


One more thing is that I also do like having the environment variables be implicitly namespaced based on the bundle name and a Timoni prefix.

That is for your example:

repository: string @timoni(runtime:string:IMAGE_REPOSITORY_FRONTEND)

Having the environment variable mapped to this annotation be TIMONI_<bundle_name>_IMAGE_REPOSITORY_FRONTEND, makes sense to me.
However, it would be a backwards incompatible change and the proposal about using viper for e.g. .env files would largely solve this use case as the environment variables would only be used in highly constrained environments anyways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants