-
Notifications
You must be signed in to change notification settings - Fork 171
How to set up per-environment configs #190
Comments
This fixes the issue: diff --git cue/load/fs.go cue/load/fs.go
index 082efbb..4af150c 100644
--- cue/load/fs.go
+++ cue/load/fs.go
@@ -243,7 +243,7 @@ var skipDir = errors.Newf(token.NoPos, "skip directory")
type walkFunc func(path string, info os.FileInfo, err errors.Error) errors.Error
func (fs *fileSystem) walk(root string, f walkFunc) error {
- fi, err := fs.lstat(root)
+ fi, err := fs.stat(root)
if err != nil {
err = f(root, fi, err)
} else if !fi.IsDir() { Is there any reason not to make this change? |
@vikstrous I'm not sure if this isn't intended as this could lead to non obvious behavior with larger configurations. $ find . -type f
./cue.mod/module.cue
./services/frontend/frontend.cue
./environments/development/development.cue
$ cat ./cue.mod/module.cue
module: "example.com/pkg"
$ cat ./services/frontend/frontend.cue
package frontend
Environment: string
Main: "hello \(Environment)"
$ cat ./environments/development/development.cue
package development
import "example.com/pkg/services/frontend"
frontend & {
Environment: "development"
} Note that the Maybe @mpvl could give a little more insights. |
I'm not understanding how your example would be extended for non-trivial use cases. Would you have to explicitly list every struct and every package for every environment? I agree that symlink hacks are a last resort solution, but in this particular case, the symlink structure feels simpler than multiple packages and removes all the repetition. Could we just restrict symlinks to not be allowed to escape the module and allow them? Could we come up with some other way to inject an environment-specific config? This partial solution kind of works:
but you can't specify a file followed by I was thinking of maybe using Another solution is to wrap everything in a shell script that copies the right environment config to the root, but that seems even worse than symlinks. A related problem: some environments are dynamic (ex. environments created to test pull requests), so how do we inject those values into the config? I actually come up with another solution that uses the hacky syntax from
I'll change the issue title to be about per-environment configs since that's the real issue here. Initially I was hoping that there's a simple solution, but maybe not. |
Symlinks are generally problematic. Why did the Allowing a mixture of |
@vikstrous I'm unable to understand why this approach wouldn't scale. To be fair maybe my current configurations written in CUE aren't that large to allow for more valuable insights. I'm curious where my usual approach fails:
Regarding the following question
I'm not sure if this is you want todo, as it's a little bit more verbose, but you could specify all your services in a
I'm looking forward for your input. |
I can't have anything in a _tool file affect how the config will be rendered. I'd have to set up something to render all possible configs and then exact the relevant environment's version, which is what I'm already doing in the example I provided in #190 (comment) Based on your suggestions it sounds like the structure you are describing is pretty different from the one in the tutorials? Your structure sounds more like the traditional inheritance based structure, but cue actually doesn't allow you to overwrite anything, so isn't that an issue? The tutorials seemed to advocate for the following structure: You just have root, intermediate and leaf directories where each one adds more and more specialization. You can put common structures in the root or import them from other packages. Then you unify things as needed at run time in Re: making environments into packages and everything else as a src package The problem with this approach is that it removes the ability to choose a subset of leaf directories for the tools to act on. You have only one centralized environment specific directory (or cue file as described in the example in #190 (comment)). I don't see how I can keep the ability to specify a subset of packages to act on. I also don't like having to explicitly import everything into a centralized package. That seems to go against the design of cue. I thought that cue was all about not having to explicitly instantiate everything? Sorry if I'm totally misunderstanding things. I would love to see a more complete example of the best way to structure a cue config tree with different environments. |
@vikstrous / @xinau: I see where @vikstrous is coming from and it is a legitimate use case. This is a classic cross-cutting problem. CUE solves this in the language with aspect-oriented features, but the same problem exists at the file level. @vikstrous if command line flags could be passed to tool you could select an environment to mix in at the root of the services. Would that solve your use case? |
Yeah, that would be ideal. It would be very similar in UX to the symlink structure or explicitly selecting a file for the environment. I think it would be helpful to see a more concrete spec so I can comment on whether or not it selves my use case completely. Reminder: in addition to hardcoded environments (dev, prod), I need to have dynmaically generated environments per pull request, so some type of dynamic non-file input would be necessary as well. Without that, I would still have to wrap cue with a tool to generate files. |
@vikstrous and @mpvl sry for the late reply. I'm very grateful for the elaborate input from both of you. As I'm still trying to figure stuff out my self, it seems that even though my solution "works" atm. it might not be sufficient enough for more complex scenarios in the long run. I think this issue should also result in a documentation page on the cuelang.org site, as this seems to be a more advanced topic, where other people could stumble up on. Maybe it also makes sense to elaborate a little bit more about the aspect-oriented features of cue. Thank you :). |
An update on the progress here: we are now adding struct and package-level attributes. One possibility would be to have a build tag-like approach. For instance, you would define configuration for your production and staging environments within the same package as anything else, but tag these files respectively with What I like about this approach over having loose packages or files that need to be mixed in, is that the intent to mix in is encoded within the file representation. So a static analyzer could find the @build tags and, by unifying the tagged files only, determine the possible combinations and do a fully scoped analysis across all cross-cutting possibilities. The tooling could also display the possibly combinations of build tags that are available to the user, or even which combination of build tags would produce a concrete instance. Overall, such a feature fits very well with the aspect oriented nature of configuration. Note that unlike Go, and to emphasize the aspect-oriented intent of this feature, we would only allow a single build tag in the attribute and would not allow negation. This forces the logic, such as default values, to be worked out in the language, instead of by build tags. Note that the purpose of this would be quite different from Go build tags. As a nice side effect, this is much easier to type on the command line as well. Question remains whether the command line flag should be |
One caveat is that in order for this to work in your example, the dev and prod packages would have to be merged at the root directory so that they will be visible throughout the directory hierarchy. This could be accomplished, though, by keeping them as separate packages and then including stub fies at the root directory as such:
Let me know if something like this would work for you. |
I think the proposal does work for static environments like prod or staging.
edit: |
- support attributes for structs and packages - relax unification rules for field attributes In all cases, the presence of attributes can never fail unification in the new definition. It is now fully up to the consumer how to interpret duplicates. Struct and package attributes are handy in several locations, as it turns out. For instance, CUE has a shared name space for definitions and values. In JSON schema, for instance, this is not the case. so a CUE mapping will have to be structured differently. Recording this mapping in field attributes is awkward. See also Issue #259 Issue #190 Change-Id: Ia7b79ca6165faa5eea11fef3cc4c456054632233 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4602 Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
There is one other approach that may help here: specify configuration parts itself on the command line. To improve analyzability, it is probably best if the allowed values are explicitly tagged. For instance,
would then allow Only tags defined in a package itself would be considered: tags defined in imported packages are not.
could allow
could allow Advantages both less intrusive and more flexible than build tags. It feels more like CUE. Unlike build tags, it allows for injection of dynamic values on the command line. The disadvantage is that this is starting to look like the non-hermetic injection of many other config langs. It is not changing the nature of the configuration, however, and having explicit tags, like build tags, makes intent explicit, instead of shoving it under a rug. I expect a similar kind of analysis to be possible as with build tags. |
This proposal makes sense to me and seems simpler. It wouldn't be necessary to have separate files for different environments any more. I assume that the first value would be the default? This still doesn't address the problem of creating per-pull-request environments, but it's simple enough to do a post-processing step in another tool (or shell script) to fix up any constants that include the PR number. |
We have a top-level tool script in a separate package that reads an environment variable using |
The tags flag allows specifying values for fields. Issue #190 Issue #159 Change-Id: Iddbfe8eb9fcb2a163ce773411042e020372ff8be Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4949 Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Also fixes a regression where multi-package errors were gobbled. A related but different issue is whether to allow a directory with both files that have a package close and ones that don't. The idea is to require an explicit `package _` to indicate a package is anonymouse. This change prepares for that in that it recognizes `_` as an anonymous package. Issue #190 Change-Id: Icc720cc033374b54731ed775a2075b8173479111 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5240 Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
It was a deliberate choice to not have any defaults in tags, as one can already specify default values in CUE itself. It is a bit more typing, but it means there is only one mechanism to do things. The tag implementation is dead simple in the end. |
With the current implementation it is possible to specify arbitrary values. Say
But you can indeed also use environment variables. |
@vikstrous @leoluk Tags don't make all cases nice, so it may still be necessary to have another method. We may still consider But for now there is probably enough support to get people going. So we can gather some data and see where this goes. |
The latest release now includes the Two things that may be separately considered are:
I like the first mechanism, as it is in the same spirit as Feel free to open this Issue if field-based injection is not sufficient and whole-file injection is desired. |
This issue has been migrated to cue-lang/cue#190. For more details about CUE's migration to a new home, please see cue-lang/cue#1078. |
I'm trying to set up per-environment configs with cue, but I'm running into a bug.
...
in paths doesn't work with symlinks.Repro:
Expected:
Actual:
If there's any info about how to customize configs for different environments or any best practices, please let me know. I've read all of the docs and I didn't see anything mentioning this issue.
The text was updated successfully, but these errors were encountered: