add limited interpolation to eden config parsing

Summary: This allows us to templatize centrally managed config files

Reviewed By: bolinfest

Differential Revision: D4444058

fbshipit-source-id: a44372084d32dcf0b27922490ab40a48478a720d
This commit is contained in:
Wez Furlong 2017-01-23 23:52:39 -08:00 committed by Facebook Github Bot
parent bafc809c36
commit dff225b60e
4 changed files with 105 additions and 2 deletions

View File

@ -14,10 +14,17 @@ python_library(
name = 'lib',
srcs = [
'config.py',
'configinterpolator.py',
'util.py',
],
)
python_unittest(
name = 'test',
srcs = glob(['test/*.py']),
deps = [':lib']
)
for build_target, suffix in get_daemon_versions():
# The :all rule is a convenience to ensure that both the CLI and the daemon
# are built.

View File

@ -20,7 +20,7 @@ import subprocess
import tempfile
import time
from . import util
from . import (util, configinterpolator)
import eden.thrift
import facebook.eden.ttypes as eden_ttypes
from fb303.ttypes import fb_status
@ -61,9 +61,18 @@ class Config:
if not self._system_config_dir:
self._system_config_dir = SYSTEM_CONFIG_DIR
self._user_config_path = os.path.join(home_dir, USER_CONFIG)
self._home_dir = home_dir
def _loadConfig(self):
parser = configparser.ConfigParser()
''' to facilitate templatizing a centrally deployed config, we
allow a limited set of env vars to be expanded.
${HOME} will be replaced by the user's home dir,
${USER} will be replaced by the user's login name.
'''
defaults = {'USER': os.environ.get('USER'),
'HOME': self._home_dir}
parser = configparser.ConfigParser(
interpolation=configinterpolator.EdenConfigInterpolator(defaults))
parser.read(self.get_rc_files())
return parser

View File

@ -0,0 +1,45 @@
# Copyright (c) 2017-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import configparser
class EdenConfigInterpolator(configparser.Interpolation):
''' Python provides a couple of interpolation options but neither
of them quite match the simplicity that we want. This class
will interpolate the keys of the provided map and replace
those tokens with the values from the map. There is no
recursion or referencing of values from other sections of
the config.
Limiting the scope interpolation makes it easier to replicate
this approach in the C++ implementation of the parser.
'''
def __init__(self, defaults):
self._defaults = {}
''' pre-construct the token name that we're going to substitute.
eg: {"foo": "bar"} is stored as {"${foo}": "bar"} internally
'''
for k, v in defaults.items():
self._defaults['${' + k + '}'] = v
def _interpolate(self, value):
''' simple brute force replacement using the defaults that were
provided to us during construction '''
for k, v in self._defaults.items():
value = value.replace(k, v)
return value
def before_get(self, parser, section, option, value, defaults):
return self._interpolate(value)
def before_read(self, parser, section, option, value):
return self._interpolate(value)

View File

@ -0,0 +1,42 @@
# Copyright (c) 2017-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from .. import configinterpolator
import configparser
import unittest
class InterpolatorTest(unittest.TestCase):
def test_basic_subs(self):
defaults = {'USER': 'wez', 'RECURSIVE': 'a${RECURSIVE}b'}
parser = configparser.ConfigParser(
interpolation=configinterpolator.EdenConfigInterpolator(defaults))
parser.add_section('section')
parser.set('section', 'user', '${USER}')
parser.set('section', 'rec', '${RECURSIVE}')
parser.set('section', 'simple', 'value')
self.assertEqual('wez', parser.get('section', 'user'))
self.assertEqual('value', parser.get('section', 'simple'))
self.assertEqual('a${RECURSIVE}b', parser.get('section', 'rec'))
actual = {}
for section in parser.sections():
actual[section] = dict(parser.items(section))
expect = {
'section': {
'user': 'wez',
'simple': 'value',
'rec': 'a${RECURSIVE}b',
}
}
self.assertEqual(expect, actual)