From cd58c321bc494d0ccac3350cf373cec6cf0f5fd9 Mon Sep 17 00:00:00 2001 From: Gleb Kashkin <g.kashkin@tarantool.org> Date: Tue, 11 Apr 2023 07:15:42 +0000 Subject: [PATCH] box: allow to set box.cfg table value via env var All box.cfg options values used to be plain values or arrays. Now some options contain key-value or nested tables. This patch allows all such options to be set through environment variables too. Closes #8494 Closes #8051 @TarantoolBot document Title: Set box.cfg table value via env vars This patch implements a way to set tables as box.cfg options value in two different ways: * as plain key-value table: ``` bash> export TT_LOG_MODULES=aaa=info,bbb=error && ./tarantool Tarantool 2.10.0-beta1-1977-g2970bd57a type 'help' for interactive help tarantool> box.cfg{} ... 2023-04-11 07:22:10.951 [219020] main/103/interactive/box.load_cfg I> set \ 'log_modules' configuration option to {"aaa":"info","bbb":"error"} --- ... tarantool> box.cfg.log_modules.aaa --- - info ... tarantool> box.cfg.log_modules.bbb --- - error ... ``` * as a table in json encoding (important: don't forget to put env var value into single quotes): NO_WRAP ``` bash> export TT_METRICS='{"labels":{"alias":"mystorage"},"include":"all","exclude":["vinyl"]}' && ./tarantool Tarantool 2.10.0-beta1-1977-g2970bd57a type 'help' for interactive help tarantool> box.cfg{} ... 2023-04-11 07:26:03.635 [219288] main/103/interactive/box.load_cfg I> set \ 'metrics' configuration option to {"exclude":["vinyl"],"include":"all",\ "labels":{"alias":"mystorage"}} --- ... tarantool> box.cfg.metrics.include --- - all ... ``` NO_WRAP --- .../gh-8051-set-box-cfg-thru-env.md | 4 + src/box/lua/load_cfg.lua | 25 ++++- .../gh_8051_set_box_cfg_thru_env_test.lua | 92 +++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/gh-8051-set-box-cfg-thru-env.md create mode 100644 test/box-luatest/gh_8051_set_box_cfg_thru_env_test.lua diff --git a/changelogs/unreleased/gh-8051-set-box-cfg-thru-env.md b/changelogs/unreleased/gh-8051-set-box-cfg-thru-env.md new file mode 100644 index 0000000000..58a3659a7a --- /dev/null +++ b/changelogs/unreleased/gh-8051-set-box-cfg-thru-env.md @@ -0,0 +1,4 @@ +## feature/box/cfg + +* Implemented a way to set a table as box.cfg{} options value via + environment variables (gh-8051). diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 3ccf9bc65e..d4f3fae9e0 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -1151,7 +1151,30 @@ local function get_option_from_env(option) -- This code lean on the existing set of template_cfg -- types for simplicity. - if param_type:find('table') and raw_value:find(',') then + if param_type:find('table') and (raw_value:startswith('{') or + raw_value:startswith('[')) then + return json.decode(raw_value) + elseif param_type:find('table') and raw_value:find('=') then + assert(not param_type:find('boolean')) + local res = {} + for _, v in ipairs(raw_value:split(',')) do + local eq = v:find('=') + if eq == nil then + error(err_msg_fmt:format(env_var_name, option, + 'in `key=value` or `value` format')) + end + local lhs = string.sub(v, 1, eq - 1) + local rhs = string.sub(v, eq + 1) + + if lhs == '' then + error(err_msg_fmt:format(env_var_name, option, + 'in `key=value` or `value` format, ' .. + '`key` must not be empty')) + end + res[lhs] = tonumber(rhs) or rhs + end + return res + elseif param_type:find('table') and raw_value:find(',') then assert(not param_type:find('boolean')) local res = {} for i, v in ipairs(raw_value:split(',')) do diff --git a/test/box-luatest/gh_8051_set_box_cfg_thru_env_test.lua b/test/box-luatest/gh_8051_set_box_cfg_thru_env_test.lua new file mode 100644 index 0000000000..fcbec619ec --- /dev/null +++ b/test/box-luatest/gh_8051_set_box_cfg_thru_env_test.lua @@ -0,0 +1,92 @@ +local server = require('luatest.server') +local popen = require('popen') +local clock = require('clock') + +local t = require('luatest') +local g = t.group() + +g.after_each = function() + if g.server ~= nil then + g.server:stop() + end +end + +local TARANTOOL_PATH = arg[-1] + +local function popen_run(command) + local cmd = TARANTOOL_PATH .. ' -i 2>&1' + local ph = popen.new({cmd}, { + shell = true, + setsid = true, + group_signal = true, + stdout = popen.opts.PIPE, + stderr = popen.opts.DEVNULL, + stdin = popen.opts.PIPE, + }) + t.assert(ph, 'process is not up') + + ph:write(command) + + local output = '' + local time_quota = 10.0 + local start_time = clock.monotonic() + while clock.monotonic() - start_time < time_quota do + local chunk = ph:read({timeout = 1.0}) + if chunk == '' or chunk == nil then + -- EOF or error + break + end + output = output .. chunk + end + + ph:close() + return output +end + +g.test_json_table_curly_bracket = function() + local env = {["TT_METRICS"] = '{"labels":{"alias":"gh_8051"},' .. + '"include":"all","exclude":["vinyl"]}'} + + g.server = server:new{alias='json_table_curly_bracket', env=env} + g.server:start() + + t.assert_equals(g.server:get_box_cfg().metrics.labels.alias, 'gh_8051') +end + +g.test_json_table_square_bracket = function() + local res = popen_run([=[ + os.setenv('TT_LISTEN', '["localhost:0"]') + box.cfg{} + box.cfg.listen + ]=]) + + t.assert_str_contains(res, "- ['localhost:0']") +end + +g.test_plain_table = function() + local env = {["TT_LOG_MODULES"] = 'aaa=info,bbb=error'} + + g.server = server:new{alias='plain_table', env=env} + g.server:start() + + t.assert_equals(g.server:get_box_cfg().log_modules, + {['aaa'] = 'info', ['bbb'] = 'error'}) +end + +g.test_format_error = function() + local res = popen_run([[ + os.setenv('TT_LOG_MODULES', 'aaa=info,bbb') + box.cfg{} + ]]) + + t.assert_str_contains(res, "in `key=value` or `value` format'") +end + +g.test_format_error_empty_key = function() + local res = popen_run([[ + os.setenv('TT_LOG_MODULES', 'aaa=info,=error') + box.cfg{} + ]]) + + t.assert_str_contains(res, "`key` must not be empty'") +end -- GitLab