Skip to content

Commit 59f7371

Browse files
committed
config: add low priority env source
The usual environment configuration source is useful for parametrized run: ``` TT_MEMTX_MEMORY=<...> tarantool --name <...> --config <...> ``` However, sometimes a user may need to set a default value, which doesn't rewrite one that is provided in the configuration. Now, it is possible to do using the environment variables with the `_DEFAULT` suffix. ``` TT_MEMTX_MEMORY_DEFAULT=<...> tarantool --name <...> --config <...> ``` This feature may be especially useful for wrappers that run tarantool internally with some default paths for data directories, socket files, pid file. We likely will use it in the `tt` tool. Part of tarantool#8862 NO_DOC=added into tarantool/doc#3544 manually
1 parent 5ef2727 commit 59f7371

File tree

4 files changed

+148
-11
lines changed

4 files changed

+148
-11
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## feature/config
2+
3+
* Added a low priority environment configuration source that looks through the
4+
`TT_*_DEFAULT` variables. It is useful for declaring default values (gh-8862).

src/box/lua/config/init.lua

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,14 @@ end
104104

105105
function methods._initialize(self)
106106
-- The sources are synchronized in the order of registration:
107-
-- env, file, etcd (the latter is present in Tarantool EE).
107+
-- env, file, etcd (present in Tarantool EE), env for
108+
-- defaults.
108109
--
109110
-- The configuration values from the first source has highest
110111
-- priority. The menthal rule here is the following: values
111112
-- closer to the process are preferred: env first, then file,
112-
-- then etcd (if available).
113+
-- then etcd (if available). And only then the env source with
114+
-- defaults.
113115
self:_register_source(require('internal.config.source.env').new())
114116

115117
if self._config_file ~= nil then
@@ -128,6 +130,10 @@ function methods._initialize(self)
128130
if extras ~= nil then
129131
extras.initialize(self)
130132
end
133+
134+
self:_register_source(require('internal.config.source.env').new({
135+
env_var_suffix = 'default',
136+
}))
131137
end
132138

133139
function methods._collect(self, opts)

src/box/lua/config/source/env.lua

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ local mt = {
66
__index = methods,
77
}
88

9+
function methods._env_var_name(self, path_in_schema)
10+
local env_var_name = 'TT_' .. table.concat(path_in_schema, '_'):upper()
11+
if self._env_var_suffix ~= nil then
12+
return env_var_name .. self._env_var_suffix
13+
end
14+
return env_var_name
15+
end
16+
917
-- Gather most actual config values.
1018
function methods.sync(self, _config_module, _iconfig)
1119
local values = {}
1220

1321
for _, w in instance_config:pairs() do
14-
local env_var_name = 'TT_' .. table.concat(w.path, '_'):upper()
22+
local env_var_name = self:_env_var_name(w.path)
1523
local raw_value = os.getenv(env_var_name)
1624
local value = schema.fromenv(env_var_name, raw_value, w.schema)
1725
if value ~= nil then
@@ -35,11 +43,23 @@ function methods.get(self)
3543
return self._values
3644
end
3745

38-
local function new()
46+
local function new(opts)
47+
local opts = opts or {}
48+
local env_var_suffix = opts.env_var_suffix
49+
50+
local name = 'env'
51+
52+
if env_var_suffix ~= nil then
53+
name = ('%s (%s)'):format(name, env_var_suffix)
54+
env_var_suffix = '_' .. env_var_suffix:upper()
55+
end
56+
3957
return setmetatable({
40-
name = 'env',
58+
name = name,
4159
type = 'instance',
60+
4261
_values = {},
62+
_env_var_suffix = env_var_suffix,
4363
}, mt)
4464
end
4565

test/config-luatest/sources_test.lua

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
local t = require('luatest')
2+
local fun = require('fun')
23
local json = require('json')
4+
local yaml = require('yaml')
35
local treegen = require('test.treegen')
46
local justrun = require('test.justrun')
57
local source_file = require('internal.config.source.file').new()
8+
local server = require('test.luatest_helpers.server')
69

710
local g = t.group()
811

@@ -14,6 +17,13 @@ g.after_all(function()
1417
treegen.clean(g)
1518
end)
1619

20+
g.after_each(function(g)
21+
if g.server ~= nil then
22+
g.server:stop()
23+
g.server = nil
24+
end
25+
end)
26+
1727
g.test_source_file = function()
1828
local config = {_config_file = 'doc/examples/config/single.yaml'}
1929
source_file:sync(config, {})
@@ -49,15 +59,14 @@ g.test_source_env = function()
4959
local dir = treegen.prepare_directory(g, {}, {})
5060
local script = [[
5161
local json = require('json')
52-
local source_env = require('internal.config.source.env').new()
62+
local source_env = require('internal.config.source.env').new({
63+
env_var_suffix = arg[1],
64+
})
5365
source_env:sync({}, {})
5466
print(json.encode(source_env:get()))
5567
]]
5668
treegen.write_script(dir, 'main.lua', script)
5769

58-
local env = {TT_LOG_LEVEL = 'info', TT_MEMTX_MEMORY = 1000000}
59-
local opts = {nojson = true, stderr = false}
60-
local res = justrun.tarantool(dir, env, {'main.lua'}, opts)
6170
local exp = {
6271
config = {
6372
version = 'dev',
@@ -69,6 +78,104 @@ g.test_source_env = function()
6978
memory = 1000000
7079
},
7180
}
72-
t.assert_equals(res.exit_code, 0)
73-
t.assert_equals(json.decode(res.stdout), exp)
81+
82+
local cases = {
83+
{
84+
name = 'env',
85+
env_var_suffix = nil,
86+
env = {
87+
TT_LOG_LEVEL = 'info',
88+
TT_MEMTX_MEMORY = 1000000,
89+
},
90+
},
91+
{
92+
name = 'env default',
93+
env_var_suffix = 'default',
94+
env = {
95+
TT_LOG_LEVEL_DEFAULT = 'info',
96+
TT_MEMTX_MEMORY_DEFAULT = 1000000,
97+
},
98+
},
99+
}
100+
local opts = {nojson = true, stderr = false}
101+
for _, case in ipairs(cases) do
102+
local comment = ('case: %s'):format(case.name)
103+
local args = {'main.lua', case.env_var_suffix}
104+
local res = justrun.tarantool(dir, case.env, args, opts)
105+
t.assert_equals(res.exit_code, 0, comment)
106+
t.assert_equals(json.decode(res.stdout), exp, comment)
107+
end
108+
end
109+
110+
-- Verify priority of configuration sources.
111+
--
112+
-- 1. env (TT_*)
113+
-- 2. file
114+
-- 3. env default (TT_*_DEFAULT)
115+
--
116+
-- Several string options from the instance config are chosen for
117+
-- testing purposes, their meaning is irrelevant for the test.
118+
--
119+
-- The table below shows where the given option is set (which
120+
-- source defines it) and what we expect as a result.
121+
--
122+
-- | option | env | file | env default | result |
123+
-- | ------------------- | --- | ---- | ----------- | ----------- |
124+
-- | process.title | | + | + | file |
125+
-- | log.file | + | + | | env |
126+
-- | log.pipe | + | | + | env |
127+
-- | log.syslog.identity | | | + | env default |
128+
g.test_sources_priority = function(g)
129+
local dir = treegen.prepare_directory(g, {}, {})
130+
local config = {
131+
credentials = {
132+
users = {
133+
guest = {
134+
roles = {'super'},
135+
},
136+
},
137+
},
138+
iproto = {
139+
listen = 'unix/:./{{ instance_name }}.iproto',
140+
},
141+
process = {
142+
title = 'from file',
143+
},
144+
log = {
145+
file = 'from file',
146+
},
147+
groups = {
148+
['group-001'] = {
149+
replicasets = {
150+
['replicaset-001'] = {
151+
instances = {
152+
['instance-001'] = {},
153+
},
154+
},
155+
},
156+
},
157+
},
158+
}
159+
local config_file = treegen.write_script(dir, 'config.yaml',
160+
yaml.encode(config))
161+
local opts = {
162+
config_file = config_file,
163+
chdir = dir,
164+
env = {
165+
TT_PROCESS_TITLE_DEFAULT = 'from env default',
166+
TT_LOG_FILE = 'from env',
167+
TT_LOG_PIPE = 'from env',
168+
TT_LOG_PIPE_DEFAULT = 'from env default',
169+
TT_LOG_SYSLOG_IDENTITY_DEFAULT = 'from env default',
170+
},
171+
}
172+
g.server = server:new(fun.chain(opts, {alias = 'instance-001'}):tomap())
173+
g.server:start()
174+
g.server:exec(function()
175+
local config = require('config')
176+
t.assert_equals(config:get('process.title'), 'from file')
177+
t.assert_equals(config:get('log.file'), 'from env')
178+
t.assert_equals(config:get('log.pipe'), 'from env')
179+
t.assert_equals(config:get('log.syslog.identity'), 'from env default')
180+
end)
74181
end

0 commit comments

Comments
 (0)