Last active
November 26, 2016 19:10
-
-
Save hynek/78fef47fea3cbc6b683cd8ab0fb68567 to your computer and use it in GitHub Desktop.
PoC of using attrs’s upcoming metadata feature for using declarative application configuration from env variables including envconsul’s HashiCorp Vault support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import absolute_import, division, print_function | |
from app_config import (app_config, env_var, env_var_from_vault, | |
env_var_group, environ_to_app_config) | |
@app_config(prefix="APP", vault_prefix="WHOIS_{env}") | |
class WhoisConfig(object): | |
@app_config | |
class Prometheus(object): | |
address = env_var(default="127.0.0.1") | |
port = env_var(default="0") | |
consul_token = env_var_from_vault() | |
env = env_var() | |
prometheus = env_var_group(Prometheus) | |
if __name__ == '__main__': | |
ac = environ_to_app_config(WhoisConfig) | |
print(ac) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import attr | |
CNF_KEY = object() | |
RAISE = object() | |
class ConfigError(Exception): | |
pass | |
class MissingEnvValueError(ConfigError): | |
pass | |
def app_config(maybe_cls=None, vault_prefix=None, prefix="APP"): | |
def wrap(cls): | |
cls._vault_prefix = vault_prefix | |
cls._prefix = prefix | |
return attr.s(cls, slots=True, frozen=True) | |
if maybe_cls is None: | |
return wrap | |
else: | |
return wrap(maybe_cls) | |
@attr.s(slots=True, frozen=True) | |
class _ConfigEntry(object): | |
default = attr.ib(default=RAISE) | |
sub_cls = attr.ib(default=None) | |
from_vault = attr.ib(default=False) | |
def env_var(default=RAISE): | |
return attr.ib( | |
default=default, | |
metadata={CNF_KEY: _ConfigEntry(default, None, False)} | |
) | |
def env_var_from_vault(default=RAISE): | |
return attr.ib( | |
default=default, | |
metadata={CNF_KEY: _ConfigEntry(default, None, True)} | |
) | |
def env_var_group(cls): | |
return attr.ib( | |
default=None, | |
metadata={CNF_KEY: _ConfigEntry(None, cls, True)} | |
) | |
def environ_to_app_config(cls, environ=os.environ, | |
prefix=None, vault_prefix=None): | |
if prefix is None: | |
prefix = cls._prefix | |
if vault_prefix is None: | |
vault_prefix = cls._vault_prefix | |
vals = {} | |
for a in attr.fields(cls): | |
name = a.name.upper() | |
cm = a.metadata[CNF_KEY] | |
if cm.sub_cls is None: | |
if cm.from_vault is False: | |
var = prefix + "_" + name | |
else: | |
var = "SECRET_" + vault_prefix + "_" + prefix + "_" + name | |
val = environ.get(var, cm.default) | |
if val is RAISE: | |
raise MissingEnvValueError(var) | |
if name == "ENV" and "{env}" in vault_prefix: | |
vault_prefix = vault_prefix.replace("{env}", val.upper()) | |
else: | |
val = environ_to_app_config( | |
cm.sub_cls, environ, prefix + "_" + name, vault_prefix, | |
) | |
vals[a.name] = val | |
return cls(**vals) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ env APP_ENV=dev APP_PROMETHEUS_PORT=7000 SECRET_WHOIS_DEV_APP_PROMETHEUS_CONSUL_TOKEN=abc python app.py | |
WhoisConfig(env='dev', prometheus=Prometheus(address='127.0.0.1', port='7000', consul_token='abc')) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment