Allowing setting config vars from environment#409
Conversation
Signed-off-by: vsoch <[email protected]>
bdc7994 to
404f138
Compare
cpeel
left a comment
There was a problem hiding this comment.
Overall I like this approach! The challenge with doing it here at the end of config.py rather than __init__.py is that we can't override other variables set in other settings/* files, like those in tasks.py (which we would want to do in our k8s scenario as we move redis elsewhere).
I also fear that we now need to keep the lists up-to-date when a new variable is added. We could add some CI that checks to see if any variable is in the file that isn't in one of the other lists.
|
@cpeel totally agree - let me rework this a bit (probably after work) to see if I can make it a neater solution. I don't like the redundancy either! |
|
@cpeel I've been considering this for some time - but it might bet time to migrate the settings folder into a single settings.py to make this more clear, generally. What do you think? I'm happy to take the first shot (and I'll refactor this PR along with it!) |
|
I'm not sure refactoring it into a single One way to decrease the redundancy in this approach might be to iterate over all env settings and pull out all of them with Another possible approach would be to have the settings in a JSON file that is specified via env variable ( |
I also like the idea of having this as an option, although not required. And I'm also leaning towards making everything very flexible - e.g., "provide the value directly in the file, via the config file, or environment - up to you!" Is JSON preferred to YAML? I think (for configs like this) my preference would be YAML, but maybe my config-ometer sense is off. 😆 |
|
okay will have a PR update for you this evening! |
YAML is probably better (says my professional half -- my personal half thinks YAML is rubbish 😁 ). |
Signed-off-by: vsoch <[email protected]>
9b25c8c to
78e0452
Compare
Haha, I totally feel that! So I just pushed a refactor that has a single settings.py (to give more confidence we find everything in one place) and it should work to define variables from:
I decided to still have tight control over variables and placed them into groups based on type, as oppose to a strategy that looks for any The one change I'm still thinking about doing is not having any "special prefix" for secrets as I do now (e.g., |
cpeel
left a comment
There was a problem hiding this comment.
The one change I'm still thinking about doing is not having any "special prefix" for secrets as I do now (e.g.,
SREGISTRY_SECRET_).
Yes, I was thinking that too after reviewing the code. I don't think prefixing SECRET makes the developer any more (or less) aware of the importance of that and think it would be simpler overall if we didn't do the special prefixing. And as a counter-example we're not prefixing that to the postgres DB password but that's arguably also important.
While I haven't yet tested this, the concept is solid and I think this will work for what we need to do.
One thing I am wary of is that this will break backwards compatibility for users who upgrade and in the past may have been replacing CONFIG.py with their settings. This also renames some configuration variables. I would classify this is an "API-breaking change" and would rev this as a major point release, rather than a fix update, per semver
shub/settings.py
Outdated
|
|
||
| # STORAGE | ||
|
|
||
| MINIO_ROOT_USER = os.environ.get("MINIO_ROOT_USER") |
There was a problem hiding this comment.
While we're here, why not allow this to be set via the config as well? To keep backwards compatibility we could do:
MINIO_ROOT_USER = os.environ.get("MINIO_ROOT_USER", cfg.MINIO_ROOT_USER)
and make the default for MINIO_ROOT_USER in the STRING_DEFAULTS to be None.
There was a problem hiding this comment.
We could definitely do that, although for the actual Minio server it is expecting MINIO_ROOT_USER explicitly. I think the use case that you have in mind is having deployed your Minio separately (and so you could use SREGISTRY_MINIO_ROOT_USER without an issue? I'm definitely happy to add that!
There was a problem hiding this comment.
I also realize that I need to do a check if a value is set before setting to locals, e.g., for the case that the cfg MINIO_ROOT_USER (from SREGISTRY_MINIO_ROOT_USER) is None, we don't want it to override MINIO_ROOT_USER
There was a problem hiding this comment.
Correct - I need to decouple the two. Eventually I need this to work against a genuine S3 bucket rather than a minio server, but I haven't yet gotten to testing that / making changes to support it.
There was a problem hiding this comment.
Oh nice! That would be amazing to have (and based on the shared protocol hopefully not too hard to support). I can definitely help when the time comes.
38d1c21 to
854b7ab
Compare
|
okay (barring no issues with building) another round of changes! 854b7ab The only detail we have to be careful about now is that values that are expected to be in settings (e.g., |
cpeel
left a comment
There was a problem hiding this comment.
This is looking 💯 . Some small PR feedback. Still need to do some testing on it.
|
|
||
| Order of preference or variables honored is: | ||
|
|
||
| 1. secrets.py |
There was a problem hiding this comment.
The way it is coded now, the order is:
- secrets.py
- settings.yaml
- the environment
- defaults directly in settings.py
Which I think is the right order, because we:
- set the defaults
- override any values from the env
- override any values from settings.yaml
- only override values already set in the file from
secrets.yaml.
There was a problem hiding this comment.
Environment (starting on line 226) actually comes before settings.yaml (275) , however we could switch this around so the settings.yaml is first. I was thinking that the secrets.py should take first preference for backwards compatibility, and then environment should always override everything else (e.g., a config file might already exist but the user can easily set the envar to override it) and then the defaults in settings.py are last. What do you think?
There was a problem hiding this comment.
Yes, sorry, I read the code wrong. What you have here in settings.md matches the behavior in settings.py.
It might be better if the env overrides the settings.yaml file, but I'm not going to lose any sleep over what you have here.
There was a problem hiding this comment.
I do agree environment should not be over-ridden - the challenge here is that we don't have a good way to determine if a variable was set in the environment (or is still a default). We can't just check if a DEFAULT[key] is not None, because this won't work for booleans, or generally any variable with a default that isn't None. It's a bit of a catch 22 because in order to set anything from settings.yaml, we need to have all the variables in one lookup, DEFAULTS, but we can't set that one variable until we've parsed the different types from the environment! So I think (for now at least) this approach is a reasonable start. If you have a good idea for an implementation though I'd definitely be down to try it!
shub/settings.py
Outdated
| return envar_list | ||
|
|
||
|
|
||
| # Try reading in from secrets first (no issue if not found) |
There was a problem hiding this comment.
I would move this to the end of the file so that the load order maps to the conceptual "define and then override" that we're doing:
- defaults
- environment
- settings.yaml
- secrets.py
The secrets currently take precedence because of this code on line 664:
# Don't set if the value is empty, or it's been set previously
if value is None or key in locals() and locals()[key] is not None:I think the order you have is correct and moving the secrets imports to line 648 makes it very clear that the file trumps everything else.
There was a problem hiding this comment.
Ah ok, so you agree about environment being honored first? I think my point of confusion is that I'm loading in this order:
- secrets.py
- environment
- settings.yaml
- defaults
and the implementation does that by using that order, but never setting something that is already defined.
There was a problem hiding this comment.
Agree - we can put secrets at the bottom, although I'll want to double check / think about if any of the previous settings (e.g., putting a value into a data structure) would prevent that.
There was a problem hiding this comment.
okay I think this will work - with the assumption that secrets.py will work only for values you could previously put there (e.g., the new values for the various API settings that I'm adding newly will not work) and to over-ride entire datastructures (e.g., the database) you need to define the entire thing (and it's not exposed just via attributes of that).
the user can now define variables directly in settings.py, via an external secrets.py or settings.yaml, or in the environment with SREGISTRY_ as a prefix. Signed-off-by: vsoch <[email protected]>
854b7ab to
4a51afb
Compare
Signed-off-by: vsoch <[email protected]>
061a6d2 to
ea1a148
Compare
|
Note to self: don't look back at old code, because it undoubtedly will look terrible and you'll want to change everything to make it look nicer! 😭 (true story, just now...) 😆 Thanks for the back and forth today @cpeel it was fun! |
lol - I've been doing software development for 22 years and this only gets worse! On the plus side this means that we're becoming better software developers!
Same! Appreciate all of your work in this PR! I will give this a test run ASAP. Tomorrow is busy for me so might be Tuesday. |
cpeel
left a comment
There was a problem hiding this comment.
This is working great!
Found two small minor comments in my testing but this looks good to go on my end.
|
|
||
| # If we don't have a secret key, no go | ||
| if "SECRET_KEY" not in locals(): | ||
| sys.exit("SECRET_KEY is required but not set. Set SREGISTRY_SECRET_KEY.") |
There was a problem hiding this comment.
Setting SECRET_KEY in the YAML file works fantastically, but setting SREGISTRY_SECRET_KEY env var doesn't work because we don't define it in the STRING_DEFAULTS. It's probably easiest to keep the functionality the way it is now and just update the message to Set SECRET_KEY in secrets.py or a YAML config file.
There was a problem hiding this comment.
Any reason to not allow this in the environment? I am thinking we can just add to the STRING_DEFAULTS and be more consistent?
There was a problem hiding this comment.
No reason I can think of. Adding it to STRING_DEFAULTS and updating the check for it to be not None makes sense.
shub/settings.py
Outdated
| "AUTH_LDAP_STAFF_GROUP_FLAGS": None, # "cn=staff,ou=django,ou=groups,dc=example,dc=com", | ||
| # Anyone in this group is a superuser for the app | ||
| "AUTH_LDAP_SUPERUSER_GROUP_FLAGS": None, # "cn=superuser,ou=django,ou=groups,dc=example,dc=com" | ||
| # "cn=sregistry_admin,ou=groups,dc=example,dc=com" |
There was a problem hiding this comment.
Not sure what this LDAP string is for -- is it a second example for AUTH_LDAP_SUPERUSER_GROUP_FLAGS above?
There was a problem hiding this comment.
yes, just a second example! I had two previously and wanted to keep both of them for the developer user. I'm not great with LDAP so I couldn't tell you the difference, I'd have to ping the plugin contributor to get details.
There was a problem hiding this comment.
I would remove this line then as I don't think it provides any additional information for someone who is integrating with LDAP -- the other one is sufficient. (Says someone who worked on LDAP code for 10 years.)
|
@cpeel I'll get these final tweaks in tonight - I don't typically commit to personal projects during the work day! |
Signed-off-by: vsoch <[email protected]>
|
okay final two changes in! b9335b6 |
Signed-off-by: vsoch <[email protected]>
This is an idea to address the same issue brought up in #408 - we want to be able to easily set variables in settings from the environment. This currently just includes values in config.py (and currently can be extended). I decided to explicitly list the names and types to have better control (e.g., instead of iterating over locals().
Signed-off-by: vsoch [email protected]