1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

config: and WIP version of env subsitution and include directives

This commit is contained in:
Steffen Vogel 2020-06-15 22:22:20 +02:00
parent a5bc8eb90f
commit 8c01a7914f
2 changed files with 148 additions and 80 deletions

View file

@ -23,6 +23,8 @@
#pragma once
#include <functional>
#include <regex>
#include <jansson.h>
#include <villas/node/config.h>
@ -34,10 +36,7 @@ namespace node {
class Config {
protected:
std::string uri;
FILE *local_file;
AFILE *remote_file;
using str_walk_fcn_t = std::function<json_t *(json_t *)>;
Logger logger;
@ -48,21 +47,30 @@ protected:
}
/** Decode configuration file. */
void decode();
json_t * decode(FILE *f);
#ifdef WITH_CONFIG
/** Convert libconfig .conf file to libjansson .json file. */
void libconfigDecode();
json_t * libconfigDecode(FILE *f);
#endif /* WITH_CONFIG */
/** Load configuration from standard input (stdim). */
void loadFromStdio();
FILE * loadFromStdio();
/** Load configuration from local file. */
void loadFromLocalFile(const std::string &u);
FILE * loadFromLocalFile(const std::string &u);
/** Load configuration from a remote URI via advio. */
void loadFromRemoteFile(const std::string &u);
AFILE * loadFromRemoteFile(const std::string &u);
/** Resolve custom include directives. */
void resolveIncludes();
/** To shell-like subsitution of environment variables in strings. */
void expandEnvVars();
/** Run a callback function for each string in the config */
json_t * walkStrings(json_t *root, str_walk_fcn_t cb);
public:
json_t *root;
@ -72,11 +80,8 @@ public:
~Config();
void load(const std::string &u);
/** Pretty-print libjansson error. */
void prettyPrintError(json_error_t err);
json_t * load(const std::string &u);
};
} // namespace node
} // namespace villas
} /* namespace node */
} /* namespace villas */

View file

@ -40,8 +40,6 @@ using namespace villas;
using namespace villas::node;
Config::Config() :
local_file(nullptr),
remote_file(nullptr),
root(nullptr)
{
logger = logging.get("config");
@ -50,81 +48,87 @@ Config::Config() :
Config::Config(const std::string &u) :
Config()
{
load(u);
root = load(u);
}
Config::~Config()
{
/* Close configuration file */
if (remote_file)
afclose(remote_file);
else if (local_file && local_file != stdin)
fclose(local_file);
if (root)
json_decref(root);
}
void Config::load(const std::string &u)
json_t * Config::load(const std::string &u)
{
if (u == "-")
loadFromStdio();
else if (isLocalFile(u))
loadFromLocalFile(u);
else
loadFromRemoteFile(u);
FILE *f;
AFILE *af = nullptr;
decode();
if (u == "-")
f = loadFromStdio();
else if (isLocalFile(u))
f = loadFromLocalFile(u);
else {
af = loadFromRemoteFile(u);
f = af->file;
}
json_t *root = decode(f);
if (af)
afclose(af);
else
fclose(f);
return root;
}
void Config::loadFromStdio()
FILE * Config::loadFromStdio()
{
logger->info("Reading configuration from standard input");
local_file = stdin;
return stdin;
}
void Config::loadFromLocalFile(const std::string &u)
FILE * Config::loadFromLocalFile(const std::string &u)
{
logger->info("Reading configuration from local file: {}", u);
local_file = fopen(u.c_str(), "r");
if (!local_file)
FILE *f = fopen(u.c_str(), "r");
if (!f)
throw RuntimeError("Failed to open configuration from: {}", u);
uri = u;
return f;
}
void Config::loadFromRemoteFile(const std::string &u)
AFILE * Config::loadFromRemoteFile(const std::string &u)
{
logger->info("Reading configuration from remote URI: {}", u);
remote_file = afopen(u.c_str(), "r");
if (!remote_file)
AFILE *f = afopen(u.c_str(), "r");
if (!f)
throw RuntimeError("Failed to open configuration from: {}", u);
local_file = remote_file->file;
uri = u;
return f;
}
void Config::decode()
json_t * Config::decode(FILE *f)
{
json_error_t err;
root = json_loadf(local_file, 0, &err);
json_t *root = json_loadf(f, 0, &err);
if (root == nullptr) {
#ifdef WITH_CONFIG
/* We try again to parse the config in the legacy format */
libconfigDecode();
root = libconfigDecode(f);
#else
throw JanssonParseError(err);
#endif /* WITH_CONFIG */
}
return root;
}
#ifdef WITH_CONFIG
void Config::libconfigDecode()
json_t * Config::libconfigDecode(FILE *f)
{
int ret;
@ -135,59 +139,118 @@ void Config::libconfigDecode()
/* Setup libconfig include path.
* This is only supported for local files */
if (isLocalFile(uri)) {
char *cpy = strdup(uri.c_str());
// if (isLocalFile(uri)) {
// char *cpy = strdup(uri.c_str());
config_set_include_dir(&cfg, dirname(cpy));
// config_set_include_dir(&cfg, dirname(cpy));
free(cpy);
}
// free(cpy);
// }
if (remote_file)
arewind(remote_file);
else
rewind(local_file);
/* Rewind before re-reading */
rewind(f);
ret = config_read(&cfg, local_file);
ret = config_read(&cfg, f);
if (ret != CONFIG_TRUE)
throw LibconfigParseError(&cfg);
cfg_root = config_root_setting(&cfg);
root = config_to_json(cfg_root);
if (root == nullptr)
json_t *root = config_to_json(cfg_root);
if (!root)
throw RuntimeError("Failed to convert JSON to configuration file");
config_destroy(&cfg);
return root;
}
#endif /* WITH_CONFIG */
void Config::prettyPrintError(json_error_t err)
json_t * Config::walkStrings(json_t *root, str_walk_fcn_t cb)
{
std::ifstream infile(uri);
std::string line;
const char *key;
size_t index;
json_t *val, *new_val, *new_root;
int context = 4;
int start_line = err.line - context;
int end_line = err.line + context;
switch (json_typeof(root)) {
case JSON_STRING:
return cb(root);
for (int line_no = 0; std::getline(infile, line); line_no++) {
if (line_no < start_line || line_no > end_line)
continue;
case JSON_OBJECT:
new_root = json_object();
std::cerr << std::setw(4) << std::right << line_no << " " BOX_UD " " << line << std::endl;
json_object_foreach(root, key, val) {
new_val = walkStrings(val, cb);
if (line_no == err.line) {
for (int col = 0; col < err.column; col++)
std::cerr << " ";
json_object_set_new(new_root, key, new_val);
}
std::cerr << BOX_UD;
json_decref(root);
for (int col = 0; col < err.column; col++)
std::cerr << " ";
return new_root;
std::cerr << BOX_UR << err.text << std::endl;
std::cerr << std::endl;
}
}
case JSON_ARRAY:
new_root = json_array();
json_array_foreach(root, index, val) {
new_val = walkStrings(val, cb);
json_array_append_new(new_root, new_val);
}
json_decref(root);
return new_root;
default:
return root;
};
}
void Config::expandEnvVars()
{
static const std::regex env_re{R"--(\$\{([^}]+)\})--"};
root = walkStrings(root, [this](json_t *str) -> json_t * {
std::string text = json_string_value(str);
std::smatch match;
while (std::regex_search(text, match, env_re)) {
auto const from = match[0];
auto const var_name = match[1].str().c_str();
char * var_value = std::getenv(var_name);
text.replace(from.first, from.second, var_value);
logger->debug("Replace env var {} in \"{}\" with value \"{}\"",
var_name, text, var_value);
}
return json_string(text.c_str());
});
}
void Config::resolveIncludes()
{
root = walkStrings(root, [this](json_t *str) -> json_t * {
std::string text = json_string_value(str);
static const std::string kw = "@include ";
if (text.find(kw) != 0)
return str;
else {
std::string path = text.substr(kw.size());
json_error_t err;
json_t *incl;
incl = load(path);
if (!incl)
throw ConfigError(str, err, "Failed to include config file from {}", path);
logger->debug("Included config from: {}", path);
return incl;
}
});
}