Commit 1baef157 authored by Johan Ström's avatar Johan Ström

AgoConfig [py/cpp]: add support for getting whole sections as dict/map

parent b36d5b23
Pipeline #438 failed with stage
in 40 seconds
......@@ -3,6 +3,7 @@ from __future__ import print_function
import agoclient._logging
import argparse
from . import config
import logging
import os.path
......@@ -424,6 +425,29 @@ class AgoApp:
return config.get_config_option(section, option, default_value, app)
def get_config_section(self, section=None, app=None):
"""Read all options in the given section(s) from the configuration subsystem.
Same as get_config_option, but will return a dict with all defined values under the given
section(s). If more than one section/app is defined, all will be looked at, in the order
specified. If an option is set in multiple sections/app, the last one seen will be used.
Returns a dict with key/values.
"""
if section is None:
section = self.app_short_name
if app is None:
if type(section) == str:
app = [self.app_short_name, section]
else:
app = [self.app_short_name] + section
config._iterable_replace_none(section, self.app_short_name)
config._iterable_replace_none(app, self.app_short_name)
return config.get_config_section(section, app)
def set_config_option(self, option, value, section=None, app=None):
"""Write a config option to the configuration subsystem.
......
......@@ -77,7 +77,6 @@ def _conf_file_path(filename):
def _augeas_path(path, section, option):
return "/files%s/%s/%s" % (path, section, option)
return "/files%s/%s/%s" % (path, section, option)
def get_config_option(section, option, default_value=None, app=None):
......@@ -110,7 +109,28 @@ def get_config_option(section, option, default_value=None, app=None):
A unicode object with the value found in the data store, if found.
If not found, default_value is passed through unmodified.
"""
if not augeas: _augeas_init()
value = _get_config_option(section, option, app)
if not value:
return default_value
return value
def get_config_section(section, app=None):
"""Read all options in the given section(s) from the configuration subsystem.
Same as get_config_option, but will return a dict with all defined values under the given
section(s). If more than one section/app is defined, all will be looked at, in the order
specified. If an option is set in multiple sections/app, the last one seen will be used.
Returns a dict with key/values.
"""
return _get_config_option(section, None, app)
def _get_config_option(section, option=None, app=None):
if not augeas:
_augeas_init()
if type(section) == str:
section = (section,)
......@@ -120,25 +140,47 @@ def get_config_option(section, option, default_value=None, app=None):
elif type(app) == str:
app = (app,)
CONFIG_LOCK.acquire(True)
try:
results = {} # Only used for multi-match
with CONFIG_LOCK:
for fn in app:
for s in section:
path = _conf_file_path(fn)
aug_path = _augeas_path(path, s, option)
try:
value = augeas.get(aug_path)
if value:
# First match
return value
except ValueError as e:
logging.error("Failed to read configuration from %s: %s",
aug_path, e)
finally:
CONFIG_LOCK.release()
path = _conf_file_path(fn)
# Fall back on default value
return default_value
for s in section:
if option:
try:
aug_path = _augeas_path(path, s, option)
value = augeas.get(aug_path)
if value:
return value
except ValueError as e:
logging.error("Failed to read configuration from %s: %s", aug_path, e)
else:
# list find all options in section
base_path = _augeas_path(path, s, '')
try:
matches = augeas.match(base_path + '*')
# match returns a list of full paths; figure out each key and put in dict
for path in matches:
key = path[len(base_path):]
# First found takes precedence, just as get_config_option behaves.
if key[0] == '#' or key in results:
continue
try:
value = augeas.get(path)
results[key] = value
except ValueError as e:
logging.error("Failed to read configuration from %s: %s", path, e)
except ValueError as e:
logging.error("Failed to read configuration from %s: %s", aug_path, e)
if not option:
return results
return None
def set_config_option(section, option, value, app=None):
......
......@@ -216,6 +216,31 @@ namespace agocontrol {
return getConfigSectionOption(section_, option, defaultValue, app_);
}
/**
* Read all options in the given section from the configuration subsystem.
*
* Returns a map of all found values in the given section/app.
* If an explicit section/app is set, we only look in those.
* If extra section is used, we look in apps own section first, then on the given extra section.
* If extra app is used, we look in the apps own file first, then in the given extra file.
*
* Note that we do not automaticall set app = section in this method!
*/
std::map<std::string, std::string> getConfigSection(const ConfigNameList &section=BLANK_CONFIG_NAME_LIST, const ConfigNameList &app = BLANK_CONFIG_NAME_LIST) {
ConfigNameList section_(section, appConfigSection);
ConfigNameList app_;
if(!app.empty() && !app.isExtra()) {
app_.addAll(app);
}else if(!app.empty()) {
app_.add(appConfigSection);
app_.addAll(app);
}else
app_.add(appConfigSection);
return agocontrol::getConfigSection(section_, app_);
}
/**
* Write a config option to the configuration subsystem.
*
......
......@@ -257,7 +257,8 @@ const ConfigNameList BLANK_CONFIG_NAME_LIST = ConfigNameList();
ConfigNameList::ConfigNameList(const char* name) {
extra = false;
add(name);
if(name != nullptr)
add(name);
}
ConfigNameList::ConfigNameList(const std::string& name) {
......@@ -359,6 +360,49 @@ std::string getConfigSectionOption(const ConfigNameList& section, const std::str
return std::string();
}
std::map<std::string, std::string> getConfigSection(const ConfigNameList& section, const ConfigNameList &app) {
std::map<std::string, std::string> result;
if (augeas==NULL){
if(!augeas_init()) {
return result;
}
}
// If app has no values, default to section
ConfigNameList app_(app, section);
AGO_TRACE() << "Augeas: get all " << app_ << " / " << section;
BOOST_FOREACH(const std::string & a, app_.names()) {
BOOST_FOREACH(const std::string & s, section.names()) {
char **paths = NULL;
std::string match_path = augeasPath(confPathFromApp(a), s, "*");
int ret = aug_match(augeas, match_path.c_str(), &paths);
for(int i=0; i < ret; i++) {
char *path = paths[i];
const char *value;
if(aug_get(augeas, path, &value) == 1 && value) {
std::string key(path + match_path.size()-1);
if(key[0] == '#')
continue;
// First found takes precedence, just as getConfigSectionOption behaves.
if(result.find(key) != result.end())
continue;
AGO_TRACE() << "Augeas: found matching option in " << path << ", " << key << "=" << value;
result[key] = value;
}
free(path);
}
free(paths);
}
}
return result;
}
bool setConfigSectionOption(const std::string& section, const std::string& option, float value, const std::string& app) {
std::stringstream stringvalue;
stringvalue << value;
......
......@@ -57,8 +57,6 @@ namespace agocontrol {
std::list<std::string> name_list;
protected:
bool extra;
bool isExtra() const { return extra; }
ConfigNameList& addAll(const ConfigNameList& names) ;
public:
ConfigNameList() : extra(false) {}
......@@ -67,8 +65,10 @@ namespace agocontrol {
ConfigNameList(const ConfigNameList &names) ;
ConfigNameList(const ConfigNameList &names1, const ConfigNameList &names2) ;
ConfigNameList& add(const std::string &name) ;
ConfigNameList& addAll(const ConfigNameList& names) ;
int empty() const { return name_list.empty(); }
int size() const { return name_list.size(); }
bool isExtra() const { return extra; }
const std::list<std::string>& names() const { return name_list; }
friend std::ostream& operator<< (std::ostream& os, const ConfigNameList &list) ;
......@@ -81,7 +81,19 @@ namespace agocontrol {
public:
ExtraConfigNameList(const std::string& name)
: ConfigNameList(name)
{extra = true;}
{
extra = true;
}
static ExtraConfigNameList toExtra(const ConfigNameList& existing) {
ExtraConfigNameList n;
n.addAll(existing);
return n;
}
private:
ExtraConfigNameList() {
extra = true;
}
};
/* Use this for default values */
......@@ -103,7 +115,7 @@ namespace agocontrol {
* section -- A ConfigNameList section to look for the option in.
* Note that a regular string can be passed, it will create an implicit ConfigNameList.
*
* option -- The name of the option to retreive
* option -- The name of the option to retrieve
*
* defaultValue -- If the option can not be found in any of the specified
* sections, fall back to this value.
......@@ -123,6 +135,19 @@ namespace agocontrol {
std::string getConfigSectionOption(const ConfigNameList& section, const std::string& option, const char* defaultValue, const ConfigNameList& app = BLANK_CONFIG_NAME_LIST);
std::string getConfigSectionOption(const ConfigNameList& section, const std::string& option, const std::string& defaultValue, const ConfigNameList& app = BLANK_CONFIG_NAME_LIST);
boost::filesystem::path getConfigSectionOption(const ConfigNameList& section, const std::string& option, const boost::filesystem::path& defaultValue, const ConfigNameList& app = BLANK_CONFIG_NAME_LIST);
/**
* Read one or more configuration sections, and return all options as a map.
*
* Section and app arguments behaves same way as for getConfigSectionOption.
* If the key is found in multiple locations, the first match is used (same way as getConfigSectionOption would
* return that specific option).
*
* @param section
* @param app
* @return
*/
std::map<std::string, std::string> getConfigSection(const ConfigNameList& section, const ConfigNameList &app);
Json::Value getConfigTree();
/**
......
......@@ -86,6 +86,18 @@ class TestConfig:
assert gco(['system'], 'units') == 'SI'
assert gco(['test', 'system'], 'units') == 'inv'
def test_get_section(self, setup_config):
d = config.get_config_section(['system'], ['system'])
assert d['broker'] == 'localhost'
assert d['password'] == 'letmein'
assert d['uuid'] == '00000000-0000-0000-000000000000'
d = config.get_config_section(['system'], ['test', 'system'])
assert d['password'] == 'testpwd'
assert d['broker'] == 'localhost'
assert d['uuid'] == '00000000-0000-0000-000000000000'
def test_set_basic(self, setup_config):
assert sco('system', 'new_value', '666')
assert gco('system', 'new_value') == '666'
......@@ -145,6 +157,25 @@ class TestAppConfig:
# Will give system.confs password
assert app_sut.get_config_option('password', app=['other', 'system'], section='system') == 'letmein'
def test_get_section(self, app_sut):
d = app_sut.get_config_section()
assert d == dict(test_value_0='100',
test_value_blank=None,
local='4',
units='inv')
d = app_sut.get_config_section(['system'], ['system'])
assert d['broker'] == 'localhost'
assert d['password'] == 'letmein'
assert d['uuid'] == '00000000-0000-0000-000000000000'
d = app_sut.get_config_section(['system'], ['test', 'system'])
assert d['password'] == 'testpwd'
assert d['broker'] == 'localhost'
assert d['uuid'] == '00000000-0000-0000-000000000000'
assert 'test_value_blank' not in d # from test section
def test_set_basic(self, app_sut):
assert app_sut.set_config_option('new_value', '666') == True
assert app_sut.get_config_option('new_value') == '666'
......
......@@ -21,6 +21,7 @@ class TestAgoConfig : public CppUnit::TestFixture
CPPUNIT_TEST( testSetError );
CPPUNIT_TEST( testAppSet );
CPPUNIT_TEST( testAppGet );
CPPUNIT_TEST( testAppGetSection );
CPPUNIT_TEST_SUITE_END();
ConfigNameList empty;
......@@ -39,6 +40,7 @@ public:
void testSetError();
void testAppGet();
void testAppGetSection();
void testAppSet();
};
......@@ -56,6 +58,9 @@ public:
CPPUNIT_ASSERT_EQUAL(std::string(expected), (actual))
#define ASSERT_NOSTR(actual) \
CPPUNIT_ASSERT_EQUAL(std::string(), (actual))
#define ASSERT_MAP_STR(expected, key, map) \
CPPUNIT_ASSERT(map.find(key) != map.end()); \
ASSERT_STR(expected, map.find(key)->second)
void TestAgoConfig::setUp() {
tempDir = fs::temp_directory_path() / fs::unique_path();
......@@ -102,6 +107,11 @@ void TestAgoConfig::testBasicGet() {
ASSERT_STR("localhost", getConfigSectionOption("system", "broker", nullptr));
ASSERT_STR("value", getConfigSectionOption("test", "existing_key", nullptr));
auto map = getConfigSection("test", nullptr);
CPPUNIT_ASSERT_EQUAL((size_t)1, map.size());
ASSERT_MAP_STR("value", "existing_key", map);
}
void TestAgoConfig::testFallbackGet() {
......@@ -136,6 +146,10 @@ void TestAgoConfig::testSimulatedAppGet() {
// Overriden in test.conf
ASSERT_STR("testpwd", getConfigSectionOption(section, "password", nullptr, app));
auto map = getConfigSection(section, app);
CPPUNIT_ASSERT_EQUAL((size_t)2, map.size());
ASSERT_MAP_STR("testpwd", "password", map);
ASSERT_MAP_STR("value", "existing_key", map);
// from system.conf; will not be found since we only look in app test
ASSERT_NOSTR(getConfigSectionOption(section, "broker", nullptr, app));
......@@ -146,6 +160,13 @@ void TestAgoConfig::testSimulatedAppGet() {
// Add system to app, and make sure we now can read broker
app.add("system");
ASSERT_STR("localhost", getConfigSectionOption(section, "broker", nullptr));
map = getConfigSection(section, app);
CPPUNIT_ASSERT_GREATEREQUAL((size_t)8, map.size());
ASSERT_MAP_STR("testpwd", "password", map);
ASSERT_MAP_STR("value", "existing_key", map);
ASSERT_MAP_STR("00000000-0000-0000-000000000000", "uuid", map);
ASSERT_MAP_STR("SI", "units", map);
}
void TestAgoConfig::testBasicSet() {
......@@ -191,15 +212,38 @@ void TestAgoConfig::testAppGet() {
// This should go directly to system.conf, only
ASSERT_STR("localhost", app.getConfigOption("broker", nullptr, "system"));
ASSERT_STR("letmein", app.getConfigOption("password", nullptr, "system"));
ASSERT_NOSTR(app.getConfigOption("nonexisting_key", nullptr, "system"));
ASSERT_NOSTR(app.getConfigOption("existing_key", nullptr, "system"));
// This shall fall back on system.conf
ASSERT_STR("localhost", app.getConfigOption("broker", nullptr, ExtraConfigNameList("system")));
ASSERT_STR("testpwd", app.getConfigOption("password", nullptr, ExtraConfigNameList("system")));
ASSERT_NOSTR(app.getConfigOption("nonexisting_key", nullptr, ExtraConfigNameList("system")));
ASSERT_STR("value", app.getConfigOption("existing_key", nullptr, ExtraConfigNameList("system")));
}
void TestAgoConfig::testAppGetSection() {
DummyUnitTestApp app;
// This should get the app only
auto map = app.getConfigSection(nullptr, nullptr);
CPPUNIT_ASSERT_EQUAL((size_t)1, map.size());
ASSERT_MAP_STR("value", "existing_key", map);
// This sould only get system
map = app.getConfigSection("system", "system");
ASSERT_MAP_STR("letmein", "password", map);
// This should fall back on system
map = app.getConfigSection("system", ExtraConfigNameList("system"));
ASSERT_MAP_STR("testpwd", "password", map);
ASSERT_MAP_STR("00000000-0000-0000-000000000000", "uuid", map);
ASSERT_MAP_STR("SI", "units", map);
CPPUNIT_ASSERT(map.find("existing_key") == map.end());
}
void TestAgoConfig::testAppSet() {
DummyUnitTestApp app;
ASSERT_NOSTR(app.getConfigOption("nonexisting_key", nullptr));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment