Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/2] Set cfg via environment variables
@ 2021-04-13 12:45 Roman Khabibov via Tarantool-patches
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type Roman Khabibov via Tarantool-patches
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables Roman Khabibov via Tarantool-patches
  0 siblings, 2 replies; 8+ messages in thread
From: Roman Khabibov via Tarantool-patches @ 2021-04-13 12:45 UTC (permalink / raw)
  To: tarantool-patches

Issue: https://github.com/tarantool/tarantool/issues/5602
Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-5602-environment-variables

Roman Khabibov (2):
  lua/log: remove 'module' option type
  box: set cfg via environment variables

 changelogs/unreleased/environment-cfg.md      |   5 +
 src/box/lua/load_cfg.lua                      | 124 +++++++++++++++++-
 test/app-tap/tarantoolctl.test.lua            |  33 ++++-
 .../gh-5602-environment-vars-cfg.result       |  41 ++++++
 .../gh-5602-environment-vars-cfg.test.lua     |  57 ++++++++
 test/box-tap/gh-5602-test-cases-script.lua    |  69 ++++++++++
 6 files changed, 323 insertions(+), 6 deletions(-)
 create mode 100755 changelogs/unreleased/environment-cfg.md
 create mode 100644 test/box-tap/gh-5602-environment-vars-cfg.result
 create mode 100755 test/box-tap/gh-5602-environment-vars-cfg.test.lua
 create mode 100755 test/box-tap/gh-5602-test-cases-script.lua

-- 
2.24.3 (Apple Git-128)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type
  2021-04-13 12:45 [Tarantool-patches] [PATCH v2 0/2] Set cfg via environment variables Roman Khabibov via Tarantool-patches
@ 2021-04-13 12:45 ` Roman Khabibov via Tarantool-patches
  2021-04-13 14:52   ` Leonid Vasiliev via Tarantool-patches
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables Roman Khabibov via Tarantool-patches
  1 sibling, 1 reply; 8+ messages in thread
From: Roman Khabibov via Tarantool-patches @ 2021-04-13 12:45 UTC (permalink / raw)
  To: tarantool-patches

Assign lua types to log_* options instead of 'module' added in
a94a9b3. 'module' is no longer needed.

Needed for #5602
---
 src/box/lua/load_cfg.lua | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index 885a0cac1..f90ba8a9a 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -144,10 +144,10 @@ local template_cfg = {
     vinyl_page_size           = 'number',
     vinyl_bloom_fpr           = 'number',
 
-    log                 = 'module',
-    log_nonblock        = 'module',
-    log_level           = 'module',
-    log_format          = 'module',
+    log                 = 'string',
+    log_nonblock        = 'boolean',
+    log_level           = 'number',
+    log_format          = 'string',
 
     io_collect_interval = 'number',
     readahead           = 'number',
@@ -492,7 +492,7 @@ local function prepare_cfg(cfg, default_cfg, template_cfg,
             end
             v = prepare_cfg(v, default_cfg[k], template_cfg[k],
                             module_cfg[k], modify_cfg[k], readable_name)
-        elseif template_cfg[k] == 'module' then
+        elseif module_cfg[k] ~= nil then
             local old_value = module_cfg[k].cfg_get(k, v)
             module_cfg_backup[k] = old_value or box.NULL
 
-- 
2.24.3 (Apple Git-128)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables
  2021-04-13 12:45 [Tarantool-patches] [PATCH v2 0/2] Set cfg via environment variables Roman Khabibov via Tarantool-patches
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type Roman Khabibov via Tarantool-patches
@ 2021-04-13 12:45 ` Roman Khabibov via Tarantool-patches
  2021-04-13 15:39   ` Leonid Vasiliev via Tarantool-patches
  1 sibling, 1 reply; 8+ messages in thread
From: Roman Khabibov via Tarantool-patches @ 2021-04-13 12:45 UTC (permalink / raw)
  To: tarantool-patches

Add ability to set box.cfg options via env variables. These
variables should have name "TT_<OPTION>". When Tarantool instance
is started under tarantoolctl utility env variables options have
more priority than instance file's cfg.

Closes #5602

@TarantoolBot document
Title: Set box.cfg options via env variables

Now, it is possible to set box.cfg options via environment
variables. The name of variable should correspond the following
pattern: "TT_<NAME>", where <NAME> is an option name. For example:
"TT_LISTEN", "TT_READAHEAD".
If it is complex value, for example, "replication" can be set by a
table, the corresponding environment variable value has the syntax
of lua array (without keys):

`export TT_REPLICATION="{'uri1', 'uri2'}"`
---
 changelogs/unreleased/environment-cfg.md      |   5 +
 src/box/lua/load_cfg.lua                      | 114 ++++++++++++++++++
 test/app-tap/tarantoolctl.test.lua            |  33 ++++-
 .../gh-5602-environment-vars-cfg.result       |  41 +++++++
 .../gh-5602-environment-vars-cfg.test.lua     |  57 +++++++++
 test/box-tap/gh-5602-test-cases-script.lua    |  69 +++++++++++
 6 files changed, 318 insertions(+), 1 deletion(-)
 create mode 100755 changelogs/unreleased/environment-cfg.md
 create mode 100644 test/box-tap/gh-5602-environment-vars-cfg.result
 create mode 100755 test/box-tap/gh-5602-environment-vars-cfg.test.lua
 create mode 100755 test/box-tap/gh-5602-test-cases-script.lua

diff --git a/changelogs/unreleased/environment-cfg.md b/changelogs/unreleased/environment-cfg.md
new file mode 100755
index 000000000..894c40bf6
--- /dev/null
+++ b/changelogs/unreleased/environment-cfg.md
@@ -0,0 +1,5 @@
+## feature/core
+
+* Now, it is possibly to set box.cfg options with environment variables.
+The priority of sources of configuration options is the following (from low
+to high): default, tarantoolctl, environment, box.cfg{}. (gh-5602).
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index f90ba8a9a..df6a52e94 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -525,6 +525,26 @@ local function prepare_cfg(cfg, default_cfg, template_cfg,
     return new_cfg
 end
 
+---
+-- Transfer options from env_cfg to cfg.
+--
+local function apply_env_cfg(cfg, env_cfg)
+    if env_cfg == nil then
+        return
+    end
+
+    for k, v in pairs(env_cfg) do
+        -- If Tarantool started with tarantoolctl then we have to
+        -- ignore configuration from tarantoolctl, because they
+        -- has priority lower than environment variables.
+        if cfg[k] == nil or os.getenv('TARANTOOLCTL') == 'true' then
+            cfg[k] = v
+        elseif type(v) == 'table' then
+            apply_env_cfg(cfg[k], v)
+        end
+    end
+end
+
 local function apply_default_cfg(cfg, default_cfg, module_cfg)
     for k,v in pairs(default_cfg) do
         if cfg[k] == nil then
@@ -647,6 +667,98 @@ local function on_initial_config()
     end
 end
 
+---
+-- Erase quotes at the begin and the end of string.
+--
+local function dequote(str)
+    if string.sub(str, 1, 1) == '\'' and string.sub(str, -1) == '\'' or
+       string.sub(str, 1, 1) == '"' and string.sub(str, -1) == '"' then
+        return string.sub(str, 2, -2)
+    end
+    return str
+end
+
+---
+-- Convert raw option value becoming from the corresponding
+-- environment variable represented as string to string, number or
+-- boolean (depending on option).
+--
+local function process_option_value(option, raw_value)
+    local param_type = template_cfg[option]
+
+    -- May be peculiar to "feedback*" parameters.
+    if param_type == nil then
+        return nil
+    end
+
+    local value = nil
+    local err_msg_fmt = 'Environment variable TT_%s has ' ..
+        'incorrect value for option \'%s\': should be %s'
+
+    -- "log_level" option should be a number.
+    if param_type == 'number' then
+        value = tonumber(raw_value)
+        if value == nil then
+            error(err_msg_fmt:format(string.upper(option), option,
+                    "convertible to number"))
+        end
+    -- "log_nonblock" option should be boolean.
+    elseif param_type == 'boolean' then
+        if raw_value == 'true' then
+            value = true
+        elseif raw_value == 'false' then
+            value = false
+        else
+            error(err_msg_fmt:format(string.upper(option), option,
+                    "'true' or 'false'"))
+        end
+    elseif param_type == 'string, number' then
+        -- Firstly, try to convert to number.
+        value = tonumber(raw_value)
+        if value == nil then
+            value = raw_value
+        end
+    elseif param_type == 'string, number, table' then
+        if string.sub(raw_value, 1, 1) == '{' and
+           string.sub(raw_value, -1) == '}' then
+            value = string.sub(raw_value, 2, -2)
+            value = require('csv').load(value)[1]
+            for i, item in pairs(value) do
+                item = dequote(item)
+                value[i] = tonumber(item)
+                if value[i] == nil then
+                    value[i] = item
+                end
+            end
+        else
+            value = tonumber(raw_value)
+            if value == nil then
+                value = raw_value
+            end
+        end
+    else
+        value = raw_value
+    end
+
+    return value
+end
+
+---
+-- Pull and process configuration values from environment.
+--
+local function collect_env_cfg()
+    local env_cfg = {}
+    for option, _ in pairs(template_cfg) do
+        local env_var_name = 'TT_' .. string.upper(option)
+        local raw_value = os.getenv(env_var_name)
+        if raw_value ~= nil then
+            env_cfg[option] = process_option_value(option, raw_value)
+        end
+    end
+
+    return env_cfg
+end
+
 -- Whether box is loaded.
 --
 -- `false` when box is not configured or when the initialization
@@ -669,6 +781,8 @@ local function load_cfg(cfg)
     cfg = upgrade_cfg(cfg, translate_cfg)
     cfg = prepare_cfg(cfg, default_cfg, template_cfg,
                       module_cfg, modify_cfg)
+    local env_cfg = collect_env_cfg()
+    apply_env_cfg(cfg, env_cfg)
     apply_default_cfg(cfg, default_cfg, module_cfg);
     -- Save new box.cfg
     box.cfg = cfg
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index 3c3023d34..060316714 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -160,7 +160,7 @@ local function merge(...)
 end
 
 local test = tap.test('tarantoolctl')
-test:plan(8)
+test:plan(9)
 
 -- basic start/stop test
 -- must be stopped afterwards
@@ -632,4 +632,35 @@ test:test('filter_xlog', function(test)
     end
 end)
 
+-- gh-5602: Check that environment cfg variables are more
+-- prioritized than tarantoolctl cfg.
+do
+    local dir = fio.tempdir()
+    local code = [[ box.cfg ]]
+    create_script(dir, 'show_cfg.lua', code)
+    local code = [[ box.cfg{checkpoint_interval=3302, readahead=10001} ]]
+    create_script(dir, 'cfg_script.lua', code)
+
+    local status, err = pcall(function()
+        test:test("check answers in case of call", function(test_i)
+            test_i:plan(4)
+            os.setenv('TT_CHECKPOINT_INTERVAL', '3301')
+            os.setenv('TT_READAHEAD', '10000')
+            check_ok(test_i, dir, 'start', 'cfg_script', 0)
+            tctl_wait_start(dir, 'cfg_script')
+            check_ok(test_i, dir, 'eval',  'cfg_script show_cfg.lua', 0,
+                     '---\n- 3301\n- 10000\n...', nil)
+            check_ok(test_i, dir, 'stop', 'cfg_script', 0)
+        end)
+    end)
+
+    cleanup_instance(dir, 'cfg_script')
+    recursive_rmdir(dir)
+
+    if status == false then
+        print(("Error: %s"):format(err))
+        os.exit()
+    end
+end
+
 os.exit(test:check() == true and 0 or -1)
diff --git a/test/box-tap/gh-5602-environment-vars-cfg.result b/test/box-tap/gh-5602-environment-vars-cfg.result
new file mode 100644
index 000000000..c90dd301b
--- /dev/null
+++ b/test/box-tap/gh-5602-environment-vars-cfg.result
@@ -0,0 +1,41 @@
+TAP version 13
+1..5
+ok - box.cfg is successful
+ok - listen
+ok - readahead
+ok - strip_core
+ok - log_format is not set
+TAP version 13
+1..7
+ok - box.cfg is successful
+ok - listen
+ok - replication is table
+ok - replication URI 1
+ok - replication URI 2
+ok - replication_connect_timeout
+ok - replication_synchro_quorum
+TAP version 13
+1..3
+ok - box.cfg is successful
+ok - box.cfg{} background value is prioritized
+ok - box.cfg{} vinyl_timeout value is prioritized
+TAP version 13
+1..2
+ok - box.cfg is not successful
+ok - bad sql_cache_size value
+TAP version 13
+1..2
+ok - box.cfg is not successful
+ok - bad strip_core value
+TAP version 13
+1..2
+ok - box.cfg is not successful
+ok - bad replication value
+TAP version 13
+1..6
+ok - set 1
+ok - set 2
+ok - set 3
+ok - set 4
+ok - set 5
+ok - set 6
diff --git a/test/box-tap/gh-5602-environment-vars-cfg.test.lua b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
new file mode 100755
index 000000000..3d19fdc90
--- /dev/null
+++ b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
@@ -0,0 +1,57 @@
+#!/usr/bin/env tarantool
+
+local os = require('os')
+local fio = require('fio')
+local tap = require('tap')
+
+local test = tap.test('gh-5602')
+
+-- gh-5602: Check that environment cfg variables working.
+local TARANTOOL_PATH = arg[-1]
+local script_name = 'gh-5602-test-cases-script.lua'
+local path_to_script = fio.pathjoin(
+        os.getenv('PWD'),
+        'box-tap',
+        script_name)
+
+---
+-- Generate shell command where set is sequence of environment
+-- variables, then path to tarantool, the first arg is path to
+-- test script and the second one is number of set.
+--
+local function shell_command(set, i)
+    return ('%s %s %s %d'):format(
+        set,
+        TARANTOOL_PATH,
+        path_to_script,
+        i)
+end
+
+local set_1 = ('%s %s %s %s'):format('TT_LISTEN=3301', 'TT_READAHEAD=10000',
+    'TT_STRIP_CORE=false', 'TT_LOG_FORMAT=json')
+local set_2 = ('%s %s %s %s'):format('TT_LISTEN=3301',
+    'TT_REPLICATION=\"{\'0.0.0.0:12345\', \'1.1.1.1:12345\'}\"',
+    'TT_REPLICATION_CONNECT_TIMEOUT=0.01',
+    'TT_REPLICATION_SYNCHRO_QUORUM=\'4 + 1\'')
+local set_3 = 'TT_BACKGROUND=true TT_VINYL_TIMEOUT=60.1'
+local set_4 = 'TT_SQL_CACHE_SIZE=a'
+local set_5 = 'TT_STRIP_CORE=a'
+local set_6 = 'TT_REPLICATION=\"{0.0.0.0:12345\', \'1.1.1.1:12345\'}\"'
+
+local sets = {}
+sets[1] = set_1
+sets[2] = set_2
+sets[3] = set_3
+sets[4] = set_4
+sets[5] = set_5
+sets[6] = set_6
+
+test:plan(#sets)
+for i, set in ipairs(sets) do
+    local tmpdir = fio.tempdir()
+    local new_path = fio.pathjoin(tmpdir, script_name)
+    fio.copyfile(path_to_script, new_path)
+    test:is(os.execute(shell_command(set, i)), 0, 'set '..i)
+end
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/box-tap/gh-5602-test-cases-script.lua b/test/box-tap/gh-5602-test-cases-script.lua
new file mode 100755
index 000000000..a8b2fc987
--- /dev/null
+++ b/test/box-tap/gh-5602-test-cases-script.lua
@@ -0,0 +1,69 @@
+local tap = require('tap')
+
+local test = tap.test('gh-5602')
+
+local status, err = pcall(box.cfg, {background = false, vinyl_timeout = 70.1})
+
+-- Check that environment cfg values are set correctly.
+if arg[1] == '1' then
+    test:plan(5)
+    test:ok(status ~= false, 'box.cfg is successful')
+    test:is(box.cfg['listen'], 3301, 'listen')
+    test:is(box.cfg['readahead'], 10000, 'readahead')
+    test:is(box.cfg['strip_core'], false, 'strip_core')
+    test:is(box.cfg['log_format'], 'json', 'log_format is not set')
+end
+if arg[1] == '2' then
+    test:plan(7)
+    test:ok(status ~= false, 'box.cfg is successful')
+    test:is(box.cfg['listen'], 3301, 'listen')
+    local replication = box.cfg['replication']
+    test:is(type(replication), 'table', 'replication is table')
+    test:ok(replication[1] == '0.0.0.0:12345' or
+            replication[1] == '1.1.1.1:12345', 'replication URI 1')
+    test:ok(replication[2] == '0.0.0.0:12345' or
+            replication[2] == '1.1.1.1:12345', 'replication URI 2')
+    test:is(box.cfg['replication_connect_timeout'], 0.01,
+            'replication_connect_timeout')
+    test:is(box.cfg['replication_synchro_quorum'], '4 + 1',
+            'replication_synchro_quorum')
+end
+
+-- Check that box.cfg{} values are more prioritized than
+-- environment cfg values.
+if arg[1] == '3' then
+    test:plan(3)
+    test:ok(status ~= false, 'box.cfg is successful')
+    test:is(box.cfg['background'], false,
+            'box.cfg{} background value is prioritized')
+    test:is(box.cfg['vinyl_timeout'], 70.1,
+            'box.cfg{} vinyl_timeout value is prioritized')
+end
+
+local err_msg_fmt = 'Environment variable TT_%s has '..
+    'incorrect value for option \'%s\': should be %s'
+
+-- Check bad environment cfg values.
+if arg[1] == '4' then
+    test:plan(2)
+    test:ok(status == false, 'box.cfg is not successful')
+    local index = string.find(err, err_msg_fmt:format('SQL_CACHE_SIZE',
+        'sql_cache_size', 'convertible to number'))
+    test:ok(index ~= nil, 'bad sql_cache_size value')
+end
+if arg[1] == '5' then
+    test:plan(2)
+    test:ok(status == false, 'box.cfg is not successful')
+    local index = string.find(err, err_msg_fmt:format('STRIP_CORE',
+        'strip_core', '\'true\' or \'false\''))
+    test:ok(index ~= nil, 'bad strip_core value')
+end
+if arg[1] == '6' then
+    test:plan(2)
+    test:ok(status == false, 'box.cfg is not successful')
+    local index = string.find(tostring(err), 'Incorrect value for option '..
+        '\'replication\': expected host:service or /unix.socket')
+    test:ok(index ~= nil, 'bad replication value')
+end
+
+os.exit(test:check() and 0 or 1)
-- 
2.24.3 (Apple Git-128)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type Roman Khabibov via Tarantool-patches
@ 2021-04-13 14:52   ` Leonid Vasiliev via Tarantool-patches
  2021-04-13 15:03     ` Cyrill Gorcunov via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Leonid Vasiliev via Tarantool-patches @ 2021-04-13 14:52 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches, Cyrill Gorcunov

Hi! Thank you for the patch.
LGTM.
Cyrill Gorcunov. please also see the patch.

On 4/13/21 3:45 PM, Roman Khabibov via Tarantool-patches wrote:
> Assign lua types to log_* options instead of 'module' added in
> a94a9b3. 'module' is no longer needed.
> 
> Needed for #5602
> ---
>   src/box/lua/load_cfg.lua | 10 +++++-----
>   1 file changed, 5 insertions(+), 5 deletions(-)
> 
> diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
> index 885a0cac1..f90ba8a9a 100644
> --- a/src/box/lua/load_cfg.lua
> +++ b/src/box/lua/load_cfg.lua
> @@ -144,10 +144,10 @@ local template_cfg = {
>       vinyl_page_size           = 'number',
>       vinyl_bloom_fpr           = 'number',
>   
> -    log                 = 'module',
> -    log_nonblock        = 'module',
> -    log_level           = 'module',
> -    log_format          = 'module',
> +    log                 = 'string',
> +    log_nonblock        = 'boolean',
> +    log_level           = 'number',
> +    log_format          = 'string',
>   
>       io_collect_interval = 'number',
>       readahead           = 'number',
> @@ -492,7 +492,7 @@ local function prepare_cfg(cfg, default_cfg, template_cfg,
>               end
>               v = prepare_cfg(v, default_cfg[k], template_cfg[k],
>                               module_cfg[k], modify_cfg[k], readable_name)
> -        elseif template_cfg[k] == 'module' then
> +        elseif module_cfg[k] ~= nil then
>               local old_value = module_cfg[k].cfg_get(k, v)
>               module_cfg_backup[k] = old_value or box.NULL
>   
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type
  2021-04-13 14:52   ` Leonid Vasiliev via Tarantool-patches
@ 2021-04-13 15:03     ` Cyrill Gorcunov via Tarantool-patches
  2021-04-13 15:42       ` Leonid Vasiliev via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Cyrill Gorcunov via Tarantool-patches @ 2021-04-13 15:03 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

On Tue, Apr 13, 2021 at 05:52:11PM +0300, Leonid Vasiliev wrote:
> Hi! Thank you for the patch.
> LGTM.
> Cyrill Gorcunov. please also see the patch.
> 

Guys, I don't like that we're dropping the "module" here. But
please gimme today's evening I'll try to come up with some
idea and code to share, ok? I'll write another email once
I get ready.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables
  2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables Roman Khabibov via Tarantool-patches
@ 2021-04-13 15:39   ` Leonid Vasiliev via Tarantool-patches
  2021-04-13 17:57     ` Cyrill Gorcunov via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Leonid Vasiliev via Tarantool-patches @ 2021-04-13 15:39 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Hi! Thank you for the patch.

On 4/13/21 3:45 PM, Roman Khabibov via Tarantool-patches wrote:
> Add ability to set box.cfg options via env variables. These
> variables should have name "TT_<OPTION>". When Tarantool instance
> is started under tarantoolctl utility env variables options have
> more priority than instance file's cfg.
> 
> Closes #5602
> 
> @TarantoolBot document
> Title: Set box.cfg options via env variables
> 
> Now, it is possible to set box.cfg options via environment
> variables. The name of variable should correspond the following
> pattern: "TT_<NAME>", where <NAME> is an option name. For example:
> "TT_LISTEN", "TT_READAHEAD".
> If it is complex value, for example, "replication" can be set by a
> table, the corresponding environment variable value has the syntax
> of lua array (without keys):
> 
> `export TT_REPLICATION="{'uri1', 'uri2'}"`
> ---
>   changelogs/unreleased/environment-cfg.md      |   5 +
>   src/box/lua/load_cfg.lua                      | 114 ++++++++++++++++++
>   test/app-tap/tarantoolctl.test.lua            |  33 ++++-
>   .../gh-5602-environment-vars-cfg.result       |  41 +++++++
>   .../gh-5602-environment-vars-cfg.test.lua     |  57 +++++++++
>   test/box-tap/gh-5602-test-cases-script.lua    |  69 +++++++++++
>   6 files changed, 318 insertions(+), 1 deletion(-)
>   create mode 100755 changelogs/unreleased/environment-cfg.md
>   create mode 100644 test/box-tap/gh-5602-environment-vars-cfg.result
>   create mode 100755 test/box-tap/gh-5602-environment-vars-cfg.test.lua
>   create mode 100755 test/box-tap/gh-5602-test-cases-script.lua
> 
> diff --git a/changelogs/unreleased/environment-cfg.md b/changelogs/unreleased/environment-cfg.md
> new file mode 100755
> index 000000000..894c40bf6
> --- /dev/null
> +++ b/changelogs/unreleased/environment-cfg.md
> @@ -0,0 +1,5 @@
> +## feature/core
> +
> +* Now, it is possibly to set box.cfg options with environment variables.
> +The priority of sources of configuration options is the following (from low
> +to high): default, tarantoolctl, environment, box.cfg{}. (gh-5602).
> diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
> index f90ba8a9a..df6a52e94 100644
> --- a/src/box/lua/load_cfg.lua
> +++ b/src/box/lua/load_cfg.lua
> @@ -525,6 +525,26 @@ local function prepare_cfg(cfg, default_cfg, template_cfg,
>       return new_cfg
>   end
>   
> +---
> +-- Transfer options from env_cfg to cfg.
> +--

1) Just `--- Transfer options from env_cfg to cfg.`

> +local function apply_env_cfg(cfg, env_cfg)
> +    if env_cfg == nil then
> +        return
> +    end
> +
> +    for k, v in pairs(env_cfg) do
> +        -- If Tarantool started with tarantoolctl then we have to
> +        -- ignore configuration from tarantoolctl, because they
> +        -- has priority lower than environment variables.
> +        if cfg[k] == nil or os.getenv('TARANTOOLCTL') == 'true' then
> +            cfg[k] = v

2) I thought, and it;s strange that we use knowledge about the control
tool inside the core code. Maybe do the additional env processing in 
tarantoolctl?

> +        elseif type(v) == 'table' then

3) What is this case?

> +            apply_env_cfg(cfg[k], v)
> +        end
> +    end
> +end
> +
>   local function apply_default_cfg(cfg, default_cfg, module_cfg)
>       for k,v in pairs(default_cfg) do
>           if cfg[k] == nil then
> @@ -647,6 +667,98 @@ local function on_initial_config()
>       end
>   end
>   
> +---
> +-- Erase quotes at the begin and the end of string.
> +--

4) Just `---Erase quotes at the begin and the end of string.`

> +local function dequote(str)
> +    if string.sub(str, 1, 1) == '\'' and string.sub(str, -1) == '\'' or
> +       string.sub(str, 1, 1) == '"' and string.sub(str, -1) == '"' then
> +        return string.sub(str, 2, -2)
> +    end
> +    return str
> +end
> +
> +---
> +-- Convert raw option value becoming from the corresponding

5) `---Convert raw option value becoming from the corresponding`

> +-- environment variable represented as string to string, number or
> +-- boolean (depending on option).
> +--

6) No extra blank line is needed(`--`).

> +local function process_option_value(option, raw_value)

7) Add `env` to the function name.
For example:`process_env_option_value`

> +    local param_type = template_cfg[option]
> +
> +    -- May be peculiar to "feedback*" parameters.
> +    if param_type == nil then
> +        return nil
> +    end
> +
> +    local value = nil
> +    local err_msg_fmt = 'Environment variable TT_%s has ' ..
> +        'incorrect value for option \'%s\': should be %s'
> +
> +    -- "log_level" option should be a number.

8) This comment is deprecated.

> +    if param_type == 'number' then
> +        value = tonumber(raw_value)
> +        if value == nil then
> +            error(err_msg_fmt:format(string.upper(option), option,
> +                    "convertible to number"))

9) Use one `tab` and single quotes.

> +        end
> +    -- "log_nonblock" option should be boolean.

10) This comment is deprecated.

> +    elseif param_type == 'boolean' then
> +        if raw_value == 'true' then
> +            value = true
> +        elseif raw_value == 'false' then
> +            value = false
> +        else
> +            error(err_msg_fmt:format(string.upper(option), option,
> +                    "'true' or 'false'"))

11) Use one `tab` and single quotes.
12) Why are "TRUE" and "FALSE" is invalid?

> +        end
> +    elseif param_type == 'string, number' then
> +        -- Firstly, try to convert to number.
> +        value = tonumber(raw_value)
> +        if value == nil then
> +            value = raw_value
> +        end
> +    elseif param_type == 'string, number, table' then

13) Move this case to a separate function. It's very hard to read now.

> +        if string.sub(raw_value, 1, 1) == '{' and
> +           string.sub(raw_value, -1) == '}' then
> +            value = string.sub(raw_value, 2, -2)
> +            value = require('csv').load(value)[1]
> +            for i, item in pairs(value) do

14) Add a comment that now it can only be a string or a number (bool and
table are not supported).

> +                item = dequote(item)
> +                value[i] = tonumber(item)
> +                if value[i] == nil then
> +                    value[i] = item
> +                end
> +            end
> +        else
> +            value = tonumber(raw_value)
> +            if value == nil then
> +                value = raw_value
> +            end
> +        end
> +    else
> +        value = raw_value
> +    end
> +
> +    return value
> +end
> +
> +---
> +-- Pull and process configuration values from environment.
> +--

15) Just `--- Pull and process configuration values from environment.`

> +local function collect_env_cfg()
> +    local env_cfg = {}
> +    for option, _ in pairs(template_cfg) do
> +        local env_var_name = 'TT_' .. string.upper(option)
> +        local raw_value = os.getenv(env_var_name)
> +        if raw_value ~= nil then
> +            env_cfg[option] = process_option_value(option, raw_value)
> +        end
> +    end
> +
> +    return env_cfg
> +end
> +
>   -- Whether box is loaded.
>   --
>   -- `false` when box is not configured or when the initialization
> @@ -669,6 +781,8 @@ local function load_cfg(cfg)
>       cfg = upgrade_cfg(cfg, translate_cfg)
>       cfg = prepare_cfg(cfg, default_cfg, template_cfg,
>                         module_cfg, modify_cfg)
> +    local env_cfg = collect_env_cfg()
> +    apply_env_cfg(cfg, env_cfg)
>       apply_default_cfg(cfg, default_cfg, module_cfg);
>       -- Save new box.cfg
>       box.cfg = cfg
> diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
> index 3c3023d34..060316714 100755
> --- a/test/app-tap/tarantoolctl.test.lua
> +++ b/test/app-tap/tarantoolctl.test.lua
> @@ -160,7 +160,7 @@ local function merge(...)
>   end
>   
>   local test = tap.test('tarantoolctl')
> -test:plan(8)
> +test:plan(9)
>   
>   -- basic start/stop test
>   -- must be stopped afterwards
> @@ -632,4 +632,35 @@ test:test('filter_xlog', function(test)
>       end
>   end)
>   
> +-- gh-5602: Check that environment cfg variables are more
> +-- prioritized than tarantoolctl cfg.
> +do
> +    local dir = fio.tempdir()
> +    local code = [[ box.cfg ]]
> +    create_script(dir, 'show_cfg.lua', code)
> +    local code = [[ box.cfg{checkpoint_interval=3302, readahead=10001} ]]
> +    create_script(dir, 'cfg_script.lua', code)
> +
> +    local status, err = pcall(function()
> +        test:test("check answers in case of call", function(test_i)
> +            test_i:plan(4)
> +            os.setenv('TT_CHECKPOINT_INTERVAL', '3301')
> +            os.setenv('TT_READAHEAD', '10000')
> +            check_ok(test_i, dir, 'start', 'cfg_script', 0)
> +            tctl_wait_start(dir, 'cfg_script')
> +            check_ok(test_i, dir, 'eval',  'cfg_script show_cfg.lua', 0,
> +                     '---\n- 3301\n- 10000\n...', nil)
> +            check_ok(test_i, dir, 'stop', 'cfg_script', 0)
> +        end)
> +    end)
> +
> +    cleanup_instance(dir, 'cfg_script')
> +    recursive_rmdir(dir)
> +
> +    if status == false then
> +        print(("Error: %s"):format(err))
> +        os.exit()
> +    end
> +end
> +
>   os.exit(test:check() == true and 0 or -1)
> diff --git a/test/box-tap/gh-5602-environment-vars-cfg.result b/test/box-tap/gh-5602-environment-vars-cfg.result
> new file mode 100644
> index 000000000..c90dd301b
> --- /dev/null
> +++ b/test/box-tap/gh-5602-environment-vars-cfg.result
> @@ -0,0 +1,41 @@
> +TAP version 13
> +1..5
> +ok - box.cfg is successful
> +ok - listen
> +ok - readahead
> +ok - strip_core
> +ok - log_format is not set
> +TAP version 13
> +1..7
> +ok - box.cfg is successful
> +ok - listen
> +ok - replication is table
> +ok - replication URI 1
> +ok - replication URI 2
> +ok - replication_connect_timeout
> +ok - replication_synchro_quorum
> +TAP version 13
> +1..3
> +ok - box.cfg is successful
> +ok - box.cfg{} background value is prioritized
> +ok - box.cfg{} vinyl_timeout value is prioritized
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +ok - bad sql_cache_size value
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +ok - bad strip_core value
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +ok - bad replication value
> +TAP version 13
> +1..6
> +ok - set 1
> +ok - set 2
> +ok - set 3
> +ok - set 4
> +ok - set 5
> +ok - set 6
> diff --git a/test/box-tap/gh-5602-environment-vars-cfg.test.lua b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
> new file mode 100755
> index 000000000..3d19fdc90
> --- /dev/null
> +++ b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
> @@ -0,0 +1,57 @@
> +#!/usr/bin/env tarantool
> +
> +local os = require('os')
> +local fio = require('fio')
> +local tap = require('tap')
> +
> +local test = tap.test('gh-5602')
> +
> +-- gh-5602: Check that environment cfg variables working.
> +local TARANTOOL_PATH = arg[-1]
> +local script_name = 'gh-5602-test-cases-script.lua'
> +local path_to_script = fio.pathjoin(
> +        os.getenv('PWD'),
> +        'box-tap',
> +        script_name)
> +
> +---
> +-- Generate shell command where set is sequence of environment

16) `--- Generate shell command where set is sequence of environment`

> +-- variables, then path to tarantool, the first arg is path to
> +-- test script and the second one is number of set.
> +--

17) No extra blank line is needed(`--`).

> +local function shell_command(set, i)
> +    return ('%s %s %s %d'):format(
> +        set,
> +        TARANTOOL_PATH,
> +        path_to_script,
> +        i)
> +end
> +
> +local set_1 = ('%s %s %s %s'):format('TT_LISTEN=3301', 'TT_READAHEAD=10000',
> +    'TT_STRIP_CORE=false', 'TT_LOG_FORMAT=json')
> +local set_2 = ('%s %s %s %s'):format('TT_LISTEN=3301',
> +    'TT_REPLICATION=\"{\'0.0.0.0:12345\', \'1.1.1.1:12345\'}\"',
> +    'TT_REPLICATION_CONNECT_TIMEOUT=0.01',
> +    'TT_REPLICATION_SYNCHRO_QUORUM=\'4 + 1\'')
> +local set_3 = 'TT_BACKGROUND=true TT_VINYL_TIMEOUT=60.1'
> +local set_4 = 'TT_SQL_CACHE_SIZE=a'
> +local set_5 = 'TT_STRIP_CORE=a'
> +local set_6 = 'TT_REPLICATION=\"{0.0.0.0:12345\', \'1.1.1.1:12345\'}\"'
> +
> +local sets = {}
> +sets[1] = set_1
> +sets[2] = set_2
> +sets[3] = set_3
> +sets[4] = set_4
> +sets[5] = set_5
> +sets[6] = set_6
> +
> +test:plan(#sets)
> +for i, set in ipairs(sets) do
> +    local tmpdir = fio.tempdir()
> +    local new_path = fio.pathjoin(tmpdir, script_name)
> +    fio.copyfile(path_to_script, new_path)
> +    test:is(os.execute(shell_command(set, i)), 0, 'set '..i)
> +end
> +
> +os.exit(test:check() and 0 or 1)
> diff --git a/test/box-tap/gh-5602-test-cases-script.lua b/test/box-tap/gh-5602-test-cases-script.lua

18) `gh-5602-environment-cfg-test-cases.lua`

> new file mode 100755
> index 000000000..a8b2fc987
> --- /dev/null
> +++ b/test/box-tap/gh-5602-test-cases-script.lua
> @@ -0,0 +1,69 @@
> +local tap = require('tap')
> +
> +local test = tap.test('gh-5602')
> +
> +local status, err = pcall(box.cfg, {background = false, vinyl_timeout = 70.1})
> +
> +-- Check that environment cfg values are set correctly.
> +if arg[1] == '1' then
> +    test:plan(5)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['listen'], 3301, 'listen')
> +    test:is(box.cfg['readahead'], 10000, 'readahead')
> +    test:is(box.cfg['strip_core'], false, 'strip_core')
> +    test:is(box.cfg['log_format'], 'json', 'log_format is not set')
> +end
> +if arg[1] == '2' then
> +    test:plan(7)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['listen'], 3301, 'listen')
> +    local replication = box.cfg['replication']
> +    test:is(type(replication), 'table', 'replication is table')
> +    test:ok(replication[1] == '0.0.0.0:12345' or
> +            replication[1] == '1.1.1.1:12345', 'replication URI 1')
> +    test:ok(replication[2] == '0.0.0.0:12345' or
> +            replication[2] == '1.1.1.1:12345', 'replication URI 2')
> +    test:is(box.cfg['replication_connect_timeout'], 0.01,
> +            'replication_connect_timeout')
> +    test:is(box.cfg['replication_synchro_quorum'], '4 + 1',
> +            'replication_synchro_quorum')
> +end
> +
> +-- Check that box.cfg{} values are more prioritized than
> +-- environment cfg values.
> +if arg[1] == '3' then
> +    test:plan(3)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['background'], false,
> +            'box.cfg{} background value is prioritized')
> +    test:is(box.cfg['vinyl_timeout'], 70.1,
> +            'box.cfg{} vinyl_timeout value is prioritized')
> +end
> +
> +local err_msg_fmt = 'Environment variable TT_%s has '..
> +    'incorrect value for option \'%s\': should be %s'
> +
> +-- Check bad environment cfg values.
> +if arg[1] == '4' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    local index = string.find(err, err_msg_fmt:format('SQL_CACHE_SIZE',
> +        'sql_cache_size', 'convertible to number'))
> +    test:ok(index ~= nil, 'bad sql_cache_size value')
> +end
> +if arg[1] == '5' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    local index = string.find(err, err_msg_fmt:format('STRIP_CORE',
> +        'strip_core', '\'true\' or \'false\''))
> +    test:ok(index ~= nil, 'bad strip_core value')
> +end
> +if arg[1] == '6' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    local index = string.find(tostring(err), 'Incorrect value for option '..
> +        '\'replication\': expected host:service or /unix.socket')
> +    test:ok(index ~= nil, 'bad replication value')
> +end
> +
> +os.exit(test:check() and 0 or 1)
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type
  2021-04-13 15:03     ` Cyrill Gorcunov via Tarantool-patches
@ 2021-04-13 15:42       ` Leonid Vasiliev via Tarantool-patches
  0 siblings, 0 replies; 8+ messages in thread
From: Leonid Vasiliev via Tarantool-patches @ 2021-04-13 15:42 UTC (permalink / raw)
  To: Cyrill Gorcunov; +Cc: tarantool-patches



On 4/13/21 6:03 PM, Cyrill Gorcunov wrote:
> On Tue, Apr 13, 2021 at 05:52:11PM +0300, Leonid Vasiliev wrote:
>> Hi! Thank you for the patch.
>> LGTM.
>> Cyrill Gorcunov. please also see the patch.
>>
> 
> Guys, I don't like that we're dropping the "module" here. But
> please gimme today's evening I'll try to come up with some
> idea and code to share, ok? I'll write another email once
> I get ready.
> 

Bro, of course we will wait for your feedback)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables
  2021-04-13 15:39   ` Leonid Vasiliev via Tarantool-patches
@ 2021-04-13 17:57     ` Cyrill Gorcunov via Tarantool-patches
  0 siblings, 0 replies; 8+ messages in thread
From: Cyrill Gorcunov via Tarantool-patches @ 2021-04-13 17:57 UTC (permalink / raw)
  To: Leonid Vasiliev, Roman Khabibov; +Cc: TML

Guys, I must confess I don't like dropping 'module' from template_cfg,
the reason it has been introduced in first place is that modules might
be used _before_ the box.cfg{} even called. So that the modules should
carry own config and provide it back to the load_lua code.

Currently we have one such 'early' module -- it is logger. But we might
extend it in future. Moreover spreading types here and there won't make
code anyhow easier to read and modify.

Also the question raises -- the environment variables are considered
only at box.cfg{} call, but should not be they also handled by modules
which support early use? If yes -- then we need to add such support
into the logger code. If no -- then it should be pointed in documentation
that log module ignores env variables if set up before box.cfg{} call.
Anoter option -- defer such support to the next release.

Now back to types. Current Roman's code test for basic types from @template_cfg
which means later when these values are setting into real @cfg variable they
are passing same testing again

prepare_cfg
  ...
            local good_types = string.gsub(template_cfg[k], ' ', '');
            if (string.find(',' .. good_types .. ',', ',' .. type(v) .. ',') == nil) then
                box.error(box.error.CFG, readable_name, "should be one of types "..
                    template_cfg[k])
            end

so the question is why do we need to do this double work? As far as I understand
the only thing what we need to do is to *fetch* data from environment variables
and set them into @cfg variable. Then loader will process every entry and report
an error if something goes wrong. Though I think we might need to dequote variables
just like it is done in the patch.

Now back to types. If you really think that we still need the validation of types
on the stage when we fetch the values from env variables then please don't revert
the commit with 'module' keyword but rather provide the types separately so
we rework them if needed. Modules should be extendable not squashed into defaults.

Say for now we could use
---
From: Cyrill Gorcunov <gorcunov@gmail.com>
Date: Tue, 13 Apr 2021 20:50:14 +0300
Subject: [PATCH] cfg: provide types for logger options

This needed for fast type check when fetching
options from environment variable.

Part-of #5602

Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
---
 src/box/lua/load_cfg.lua | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index d31c9eb2c..7f57fcbb1 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -118,6 +118,18 @@ local module_cfg = {
     log_format          = log.box_api,
 }
 
+-- cfg types for modules, probably better to
+-- provide some API with type enumeration or
+-- similar. Currently it has use for environment
+-- processing only.
+local module_cfg_type = {
+    -- logging
+    log                 = 'string',
+    log_nonblock        = 'boolean',
+    log_level           = 'number, string',
+    log_format          = 'string',
+}
+
 -- types of available options
 -- could be comma separated lua types or 'any' if any type is allowed
 local template_cfg = {
@@ -686,6 +698,10 @@ end
 local function process_option_value(option, raw_value)
     local param_type = template_cfg[option]
 
+    if param_type == 'module' then
+        param_type = module_cfg_type[option]
+    end
+
     -- May be peculiar to "feedback*" parameters.
     if param_type == nil then
         return nil
-- 
2.30.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-04-13 17:57 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-13 12:45 [Tarantool-patches] [PATCH v2 0/2] Set cfg via environment variables Roman Khabibov via Tarantool-patches
2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 1/2] lua/log: remove 'module' option type Roman Khabibov via Tarantool-patches
2021-04-13 14:52   ` Leonid Vasiliev via Tarantool-patches
2021-04-13 15:03     ` Cyrill Gorcunov via Tarantool-patches
2021-04-13 15:42       ` Leonid Vasiliev via Tarantool-patches
2021-04-13 12:45 ` [Tarantool-patches] [PATCH v2 2/2] box: set cfg via environment variables Roman Khabibov via Tarantool-patches
2021-04-13 15:39   ` Leonid Vasiliev via Tarantool-patches
2021-04-13 17:57     ` Cyrill Gorcunov via Tarantool-patches

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox