-
Notifications
You must be signed in to change notification settings - Fork 55
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
feat: Special expansion for env var name #91
feat: Special expansion for env var name #91
Conversation
if pl, ok := l.(*prefixLookuper); ok { | ||
return pl.prefix + key | ||
} | ||
return key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exactly the same code as above. Perhaps it should be extracted into a small utility function like:
func actualKeyName(l Lookuper, key string) string {
if pl, ok := l.(*prefixLookuper); ok {
return pl.prefix + key
}
return key
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that my approval matters on this repo, just signaling that I've got nothing further after seeing the latest commits/replies. 👍
Hi @execjosh thank you for opening a PR. I'm having trouble following the use case here. If I understand correctly, you want the default value to have some kind of dependency on a prefix? The example you provided type A struct {
Field string `env:"FOOBAR,default=banana"`
} will always default the value of type A struct {
Field string `env:"FOOBAR,default=$banana"`
} Note the addition of the prefixed Here's an example of a resolver that handles a secret lookups. |
Hi @sethvargo. Thanks for having a look. Apologies for my explanation was a bit longwinded... TL;DR of the usecase: need to execute secondary processing on fields which are actually intended to contain secrets and whose looked up env vars were undefined (i.e., the Lookuper.Lookup returned the empty string and TL;DR magical expansion: MutatorI indeed wanted to use a ProblemMutators get the key that is written in the struct tag, not the actual key used in the lookup (this behavior might be considered a bug). Even with a default value indicating that the secret manager should be contacted, the correct secret name (i.e., the actual key used in the lookup) is unavailable. Possible SolutionsImplement some way to provide the actual key used in the lookup as a default value. This is probably generic enough that it could benefit other usecases not considered here. Custom LookuperInstead of a Problem(s)There is no point in contacting the secret manager for fields which are not secrets (e.g., The Possible Solutions
|
Sorry, I'm still not following. I've done exactly what you're describing using MutatorFuncs and a made-up protocol (prefixing values with There's a lot of context and goals that aren't coming out in the description or contrived examples. This feels like an xyproblem - what problem are you trying to solve? Please be specific and provide concrete examples instead of foobars. You've also made reference to running Mutators on unfound values - what is the use case for that? In general, it shouldn't matter what the environment variable keys are for mutators. If you want to control a custom lookup for mutators, you look at the value. If the value for the environment variable matches the format that you expect to use for secret resolution (example), then you pass it off to the secret manager to resolve. I'm not supportive of relying on environment variable keys as a mechanism for deciding which secret to lookup. |
Provide a special expansion for the name of the environment variable, including its prefix.
Thank you for the feedback!
Indeed 😅 I have prepared an example, which hopefully helps illustrate the problem more concretely. The ProblemThe Possible Solutions
In my particular usecase, the system defaults to retrieving a value from an external API that happens to be a secret manager, but could just as easily be any other external API like a cache or whatever. No env vars need to be set this way unless intentionally overriding what would have been retrieved from the external API. |
c94d297
to
e6eb364
Compare
It's probably worth exploring, since I'm not sure I agree this is a bug. Mutators are intended to be written from the viewpoint of the struct, and the struct has no understanding or visibility into what prefixes it might be called with. I can't fix this in a backwards-incompatible way, but I can do it in a way that will minimize the number of people affected. I think the real solution is to change the type signature of the MutatorFunc to return the originalKey, resolvedKey, originalValue, and currentValue. While we're there, we should probably also add a third return parameter that indicates whether processing in the mutator stack should stop. I have this working, but it requires changing the type signature from var fns []envconfig.MutatorFunc
fns = append(fns, ...)
envconfig.ProcessWith(ctx, ..., fns...) because the type signature for the variadic would be envconfig.ProcessWith(ctx, ..., func1, func2) POC is here. Ideally I'd just drop support for the old mutator signature entirely and maintain type safety, but I have no data on how prevalent the various usage patterns are. Searching OSS repos is a bit of a mixed bag.
The part I'm struggling to follow is why the map is keyed off of the envvar keys instead of the envvar values; that's the metaphorical equivalent of hard-coding something. Presumably you'd want something more like this, which keys off of the secret values themselves? |
Interesting! I understand now where you're coming from. My interpretation had been different: that it was from the viewpoint of the entire configuration struct which is passed as Your POC would certainly accommodate my usecase. It is unfortunate that it has to resort to using
Is my understanding correct that the mutator funcs are intended to be more of a middleware-esque chain of, well, mutations? Perhaps there could be a
Yes, keying off of the value is essentially what I initially wanted to do, following the example in the README 🙂 However, it won't work in the prefixing case, because we need more information to choose the correct key to use for the external API call. That is what led me to make this pull request 🙂 For a flat config without dynamic prefixes, it is possible to explicitly set different default values. Ideally, the same thing should be possible with a nested config, albeit dynamically, in order to keep things simple and DRY. Here is an example with some comments to illustrate: // Each field can have a different static default value here.
// The default for ASecret1 != BSecret1 != CSecret1, etc.
type FlatConfig struct {
ASecret1 string `env:"A_SECRET1,default=sm://A_SECRET1"`
// ... many different secrets and non-secrets for A
BSecret1 string `env:"B_SECRET1,default=sm://B_SECRET1"`
// ... many different fields for B, which mirror those for A
CSecret1 string `env:"C_SECRET1,default=sm://C_SECRET1"`
// ... many different fields for C, which mirror those for A and B
// - Adding in a D or E, which should also mirror A, B, and C, requires lots
// of copypasta...
// - Adding in a new field requires copypasta for each section...
// - Want to keep this simple and DRY
}
// All fields of SharedConfig get the same default values.
// The default for ConfigA.Secret1 == ConfigB.Secret1 == ConfigC.Secret1, etc.
// It would be nice to add dynamic context to the default so that the default
// for ConfigA.Secret1 != ConfigB.Secret1 != ConfigC.Secret1, etc.
type NestedConfig struct {
ConfigA SharedConfig `env:",prefix=A_"`
ConfigB SharedConfig `env:",prefix=B_"`
ConfigC SharedConfig `env:",prefix=C_"`
// ... many shared configs with different prefixes
}
type SharedConfig struct {
Secret1 string `env:"SECRET1,default=sm://needs-to-be-the-resolved-key-but-is-non-contextual"`
// ... many different fields
} In other words: my usecase requires that the This is what led to my assumption that passing the original instead of the resolved key to the |
Yea - this is also an option. I think I understand the use case a bit more, but I don't see a way to solve this in a backwards-compatible way that doesn't add significant complexity to the existing codebase. I'd rather lump this in with changes I want to make for 1.0 (around December of this year). |
Great!
I'll be looking forward to December and 1.0 😃 |
FWIW, I separately investigated creating a custom I came to the conclusion that: external API calls are best implemented in There is one narrow usecase where it might make sense to have external API calls in a |
Let me first say that I love this package. Thank you so much for your work!
I would really appreciate your having a look at this proposal, and I look forward to discussing it. Thank you very much in advance for your time.
What
Provide a special expansion for the name of the environment variable, including its prefix.
I am uncertain about what to name this magical expansion.
I opted forI think$@
, which is what GNU make uses to reference the target, and shell uses to reference all of the parameters. It could really be anything that is not normally a valid environment variable name; I am open to suggestions.$_
seems like a safe bet.Why
It can be advantageous to set the default value to the actual name used, prefix and all.
I will explain a bit about my usecase, and why I think this change would be helpful for other people.
A project I work on, which unfortunately does not use this package, accesses secrets which are set in a secret manager. The names of those secrets are matched with environment variables. If a corresponding environment variable for a field is set, its value is used directly instead of accessing the secret manager. If, however, the corresponding environment variable is not set, then the project accesses the secret manager using the name of the environment variable.
Some new requirements have led to a need to expand on the current implementation, where basically we will have multiple groups of the same set of secrets with different prefixes. Instead of adding a lot of copypasta, I want to migrate to using this package. However, there is no way that I could see to have as a default value the actual name of the environment variable that was used at runtime.
It is of course possible to set the default value directly to the same name as the environment variable. As far as I can tell, though, there is no way to have the prefix, if any, correctly applied.
Here is some sample code which will hopefully illustrate the pain point with prefixes.
Other Considerations
There were at least two different implementations considered which could achieve the same desired result (i.e., to set the default value to the properly prefixed key). Perhaps this behavior is a bug similar to #85? However, fixing it could potentially break current clients which depend on or expect
k
to not have the prefix, and, as alluded to in this comment, would probably have to wait until 1.0.Only on the User End
I tried implementing this with a client-specific keyword for the default value along with a
MutatorFunc
. However, theMutatorFunc
is not passed the actual key that was used (i.e., the value of parameterk
is missing the prefix when there is one). This is why I felt the need for a magic expansion which evaluates to the actual key.A New Configuration Option
I considered implementing a new configuration option,
default_to_key
(hopefully with a better name), that takes no value (similar torequired
) and is mutually exclusive with bothrequired
anddefault
.Something like this:
While this would be relatively simple to implement, I could not justify opening the floodgates to having a new configuration option for potentially each and every kind of special default, with this just being the first one.
A Special Keyword
I considered implementing a special keyword for use within
default
. I had not decided on any particular keyword, but POSIX regular expression character classes will suffice for illustration.Potentially something like this:
This would require extra pre-/post-processing of the
default
string in addition to the current shell expansion logic. I could not justify adding more complexity when leveraging the shell expansion logic is entirely sufficient.