I’ve been looking at this for a while now, and I haven’t yet been able to find a way it can be handled “properly”.
Configuration aren’t a one-way thing, bindings also write to configurations. By converting the configuration before the bindings receive them in some paths and not in others, it’s very hard keep track of this. The binding doesn’t know “which version” is has, and how do you handle updates? If the binding wants to write to a configuration value that has an env variable, it has no way to “convert it back” (which would be impossible anyway, unless they actually then changed the OS variable) or even know that they’re dealing with a substituted setting.
On top of this comes the “automatic update” of configuration variables, that goes a bit like this: Any configuration parameter that has the same key as any of the “discovery properties” will automatically be overwritten when discovery runs. This will also overwrite any environment variables stored there, thus “exposing” what the user is trying to keep secret.
I’m just not sure if this is doable in this way.
Maybe instead, we could make a special wrapper type SecretHolder whose toString() method would just return the environment variable, and you had to call some other method e.g. resolve() to get the resolved value? This “wrapper” could then hold the logic for how variables are resolved. But, there are lots of challenges there too, for example, it would only work when bindings explicitly expected that a SecretHolder might be used for that parameter, and called resolve() when needed. So, it wouldn’t apply “universally” like this tries to do. Also, how would we get the UI to create such a SecretHolder instead of a String. I guess the REST API could scan for a pattern, and create a wrapper if the pattern fits. The same would have to be done in the file configuration parsers (DSL/YAML).
I just don’t know, at the moment I don’t see a “clear path”.
edit: Here is another reason why I don’t like when String is used everywhere. String in Java is final and can’t be extended. CharSequence on the other hand, is an interface, and can be used instead in most places. If those were used throughout, it would be easy to make the “wrapper” implement CharSequence and then go on their merry way, being treated like “just another string” throughout the code (where they would only return the unsubstituted value). Only when resolving them was needed, they could pass through some utility function that resolved them if they were in fact wrapper implementations, and otherwise just passed them through. But, since that’s not what has been done in OH, we don’t have any such possibility. String is set in stone, and we can’t do anything to pass other implementations through.
@holger_hees One thing I’m wondering, is why using this for IP addresses etc. simplifies things for you. It doesn’t sound “simpler” to me to have to set them than to have them set in files. Is that because you run some scripts that somehow automatically sets the environment variables?
Have you considered using YAML configuration instead, in combination with YAML Composer? It gives quite a few possibilities to build “dynamic” configurations and do substitutions. I don’t know your exact needs though.