Fetch variables for your application easily from Consul KV ➡️ config*.json ➡️ environment. via simple python method/s!
pip install confgetti
# my_app/config.py
from confgetti import Confgetti
cgtti = Confgetti({'host': 'consul_instance_host'})
my_variable = cgtti.get_variable('MY_VARIABLE')
# my_app/config.py
from confgetti import Confgetti
cgtti = Confgetti({'host': 'consul_instance_host'})
my_variables_dict = cgtti.get_variables(keys=[
'MY_VARIABLE',
'YOUR_VARIABLE',
'OUR_VARIABLE'
])
ENV CONSUL_HOST=consul_instance_host
- variables under
MY_APP
namespace in consul
# my_app/config.py
from voluptuous import Schema, Coerce
from confgetti import load_and_validate_config
my_variable = None
your_variable = None
_schema = Schema({
"my_variable": str,
"your_variable": Coerce(int)
})
load_and_validate_config(__name__, 'MY_APP', _schema)
# my_app/some_logic.py
from .config import my_variable, your_variable
print(my_variable) # should be a string and not None
print(your_variable # should be an integer and not None
Modern web app development and deployment often consider isolated app environments that are easily manageble and quickly deployed with software as VMs or Docker.
As your app gets bigger and needs more and more settings variables declared in your configuration modules or classes, management of those variables becomes frustrating, especially for those who manage the production state of the app or multiple apps and do not care about actual application code.
Imagine a simple web application that uses a database for data storage, cache mechanism, and AWS S3 Bucket static file storage. Oh yes, and our app is Dockerized.
To run that app successfully usually you need to pass configuration variables to methods/drivers that are communicating with those services. So at least, you'll need:
- Database
- Database name
- Host
- Username
- Password
- Port?
- Cache
- Host
- Index?
- Username?
- Password?
- S3 Bucket
- AWS secret access key
- AWS access key id
- Bucket name
So for just 3 external services, we could end with up to 12 different settings variables that are crucial for the successful running of our simple web app.
How do you deal with a problem like an n00b?
You could always leave those variable hardcoded to your configuration module, but perhaps, what if suddenly you need to switch to different AWS user and use the different bucket?
We even do not touch application code or logic actually but we need to:
- push those changes to our repository (wait for a Merge request, eh?)
- build a new Docker image
- deploy a new image
Whole deployment process just because of those 3 simple variables. Not to mention that there are some sensitive data in those 12 variables, so storing them inside codebase is NEVER a good idea and ALWAYS security issue.
The most common way of variables management, especially in the Docker world, is to assign necessary variables into the container environment via Docker runner and then get them from application code by checking the value of some agreed and known environment key name.
If we put aside the security problem of such an approach (yes, environment variables could be readable by a malicious user), there is still one more common and frustrating problem: A bunch of sensitive variables that need to be correctly passed each time as our Docker container is restarted or redeployed.
Each time you need to pass those 12 variables to docker
command, and even with docker-compose
you still need to declare those variables in the docker-compose.yml
file which returns us to previous "King of n00bs" way.
Do not forget, we are dealing with just one simple web app. Imagine the size of the problem on some cluster of web apps. Your DevOps(in most cases you) will hate you.
Here comes Confgetti to save a day! 🎉🎉🎉
Confgetti uses Consul key/value storage for setting and getting your variables.
If you have running consul instance and MY_VARIABLE
exists in its KV, you can get it simple as that:
from Confgetti import get_variable
cgtti = Confgetti({'host': 'consul_instance_host'})
my_variable = cgtti.get_variable('MY_VARIABLE')
Maybe you still want to store some or all variables into environment?
No problem!
Confgetti can get a variable from your environment also.
So now we set environment variable MY_VARIABLE
with some custom value.
How to get a variable from the environment?
With the same get_variable
method used in the example above.
No need for extra setup, custom code or monkey patching and it is because of Confgetti efficient logic flow.
Confgetti tries to fetch a variable from two different sources in order, overriding the previous source result.
When you ask for a variable with get_variable
, the lookup is made in the following order:
Consul
⬇️
environment
⬇️
App
So if you have MY_VARIABLE
key stored in consul and in the environment, Confgetti will return the value
stored in the environment (if you do not tell Confgetti otherwise.).
Confgetti does not punish you if you do not have the Consul server running, it will still return value from the environment variable!
Slightly high-level function load_and_validate_config, that is used for fetching multiple variables at once and overriding declared module variables, will try to get variable from one extra source, the local JSON configuration file in the following order:
Consul
⬇️
config.json
⬇️
environment
⬇️
App
With the same override logic.
Confgetti uses a python-consul package for communication with Consul's KV store.
Default connection settings are:
host: consul
port: 8500
scheme: http
Connection settings can be configured in 2 ways:
CONSUL_HOST - default: 'consul'
CONSUL_PORT - default: 8500
CONSUL_SCHEME - default: 'http'
CONSUL_TOKEN - default: None
CONSUL_DC - default: None
You have running consul instance on my_host
, port 7500
, and on secured https
,
all you need to set the following environment variables:
CONSUL_HOST=my_host
CONSUL_PORT=7500
CONSUL_SCHEME=https
And you do not have to pass any configuration dictionary when initializing Confgetti, because it will read settings from the environment.
# my_app/config.py
from confgetti import Confgetti
cgtti = Confgetti()
my_variable = cgtti.get_variable('MY_VARIABLE')
!!!ALERT: This is only way of configuration for
load_and_validate_config
shorthand use.
When initializing Confgetti instance you can pass a dictionary with Consnul connection settings.
consul_settings = {
'host': 'consul',
'port': 8500,
'scheme': 'http',
'token': None,
'dc': None
}
You have running consul instance on my_host
, port 7500
, and on secured https
:
# my_app/config.py
from confgetti import Confgetti
cgtti = Confgetti({
'host': 'my_host'
'port': 7500,
'scheme': 'https'
})
my_variable = cgtti.get_variable('MY_VARIABLE')
!!!ALERT: If you are using shorthand functions, make sure that you have provided Consul connection settings via environment variables!
This is shorthand function for confgetti.Confgetti.get_variables
.
Used for fetching multiple variables at once from Consul or environment. Returns dictionary for fetched variables.
Arguments:
-
path(optional) - Namespace of variable location inside Consul KV storage. By default as
None
, it looks to root of KV for the variable. -
keys - list of keys under which variables are defined. This can be a plain
list
of names, ordict
where the key is the key name of variable and value type of value that should be returned. By default, variables are returned as a string. Available types:str
,int
,bool
,float
,dict
. For example:my_variables_dict = cgtti.get_variables(keys=[ 'MY_VARIABLE', 'YOUR_VARIABLE', 'OUR_VARIABLE' ])
or
my_variables_dict = cgtti.get_variables(keys={ 'MY_VARIABLE': str, 'YOUR_VARIABLE': bool, 'OUR_VARIABLE': int })
-
use_env(optional) - should Confgetti look to environment or no?
-
use_consul(optional) - should Confgetti look to Consul or no?
Example:
from confgetti import get_variables
convert_dict = {
"my_variable": str,
"your_variable": int,
"my_bool": bool,
"my_env_variable": str
}
variables = get_variables(path='AWESOMEAPP', keys=convert_dict)
confgetti.load_and_validate_config(config_module_name, env_var, schema=None, keys=None, uppercase=False)
Used for overriding current module variables. Usually it is used with voluptuous.Schema as schema
argument for validation, but can be used without it, or with some custom method.
Arguments:
- config_module_name - Usually
__name__
variable from current configuration file - env_var - Prefix of variables in the environment, the namespace of variables location in CONSUL and key under which
JSON
configuration path is stored in the environment. See the example for better understanding. - schema(optional) - Pass custom method here that should return
dict
of variables which will be glued to module later. Usually used with voluptuous.Schema. - keys(optional) - variable names list or dict with the desired type. If you do not pass this, and you pass
voluptuous.Schema
underschema
argument, the method will return variables declared dict passed toSchema
instance. - uppercase(optional) - By default, variables are glued to the module in lowercase. If this is passed as
True
, variables will be glued in uppercase.
Example:
- variables must be defined under
MY_APP
namespace in consul - *if
configuration.json
is used, environment variableMY_APP
must be set with a path to the file
# my_app/config.py
from voluptuous import Schema, Coerce
from confgetti import load_and_validate_config
my_variable = None
your_variable = None
_schema = Schema({
"my_variable": str,
"your_variable": Coerce(int)
})
load_and_validate_config(__name__, 'MY_APP', _schema)
# my_app/some_logic.py
from .config import my_variable, your_variable
print(my_variable) # should be a string and not None
print(your_variable # should be an integer and not None
Confgetti intialization accepts two optional arguments, both refering to communication with Consul.
Arguments:
-
consul_config(optional) - if dictionary with connection settings is passed client for communication with consul is initialized wit these settings.
Example:
from confgetti import Confgetti cgtti = Confgetti({ 'host': 'localhost' })
-
prepare_consul(optional) - if
False
is passed, connection to Consul instance is not prepared, and Confgetti will only seek for variables in environment.Example:
from confgetti import Confgetti cgtti = Confgetti(prepare_consul=False) cggti.get_variable('my_variable') # only environment lookup
confgetti.Confgetti.get_variable(key, path=None, fallback=None, convert_to=None, use_env=True, use_consul=True)
Used for fetching single variable from Consul or environment. Returns single variable value
Arguments:
- key - key under which variable is defined
- path(optional) - Namespace of variable location insike Consul KV storage. By default as
None
it looks to root of KV for variable. - fallback(optional) - what is returned if variable is not found
- convert_to(optional) - should variable be converted to certain type? If type is passed, Confgetti tries to convert variable to passed type. By default, variable is returend as string. Available types:
str
,int
,bool
,float
,dict
- use_env(optional) - should Confgetti look to environment or no?
- use_consul(optional) - should Confgetti look to Consul or no?
Example:
from confgetti import Confgetti
cgtti = Confgetti()
my_variable = cgtti.get_variable('my_variable')
This is internal method that is used for get_variables shorthand. Arguments and logic is exactly the same.
Example:
from confgetti import Confgetti
cgtti = Confgetti()
my_variable = cgtti.get_variables(['my_variable', 'your_variable'])
Check demos folder for example usages as simple python scripts.
This repository has automatic deployment configured via CI runner.
Steps for automatic deployment to Styria
's PyPI server:
- Update
CHANGELOG.md
with future version release notes - Commit and push changelog to
master
branch - From the root of repository run
bumpversion patch
- Push changes that command in the previous step has made to the repo
- Wait for
CI/CD
pipelinedeploy
step is finished, done!